语法

parseInt() 函数解析一个字符串参数,指定该字符串为指定基数的进制值,并返回一个 10 进制的整数,如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN

参考 parseInt

// 语法
parseInt(string, radix)
// string 要被解析的值,如果参数不是一个字符串,则将其转换为字符串
// radix 基数,表示进制,介于 2 和 36 之间的整数,参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。如果输入的 string 以 "0x"或 "0x"(一个0,后面是小写或大写的X)开头,那么radix被假定为16,字符串的其余部分被解析为十六进制数。如果输入的 string以 "0"(0)开头, radix被假定为10(十进制)。如果输入的 string 以任何其他值开头, radix 是 10 (十进制)。

// 返回解析后的整数值。 如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN
parseInt('123', 5) // 将 '123' 看作 5 进制数,返回十进制数 38
parseInt('4215213', 5) // 4 * 5^2 + 2 * 5^1 + 1 * 5^0 = 111 返回 111
// [1, 2, 3].map(parseInt) // [1, NaN, NaN]
// [1, 2, 3].map(parseInt(item, index))

parseFloat() 函数解析一个字符串参数并返回一个浮点数,如果给定值不能被转换成数值,则会返回 NaN

// 语法
parseFloat(value) // value 需要被解析成为浮点数的值
parseFloat('3.14') // 3.14

// [1, 2, 3].map(parseFloat) // [1, 2, 3]

问题

parseInt(0.0000005) === 5
// 如果 parseInt 第一个参数不是字符串,会将其转换成字符串,之后在解析
// 小于 10-6 的浮点数以指数表示
// parseint 从 float 的指数法中提取整数
String(0.0000005) // => '5e-7'
parseInt(5e-7) // => 5
parseInt('5e-7') // => 5

parseInt(1111111111111111111111) // => 1
parseInt(999999999999999999999) // => 1

parseInt((5e-7).toFixed()) // => 0

parseFloat(9999999999999999) // 10000000000000000

转换成字符串

  • 调用 toString() 方法 (显式转换)
var a = 1
a.toString() // '1'

null 、undefined 没有 toString() 方法

  • 调用 String() 构造函数(显式转换)
String(1) // '1'
  • 直接和字符串做加法运算(隐式转换)
// 任意数据类型的变量和字符串做加法运算结果都是字符串
1 + '' // '1'
true + '' // 'true'
  • 引用类型转换成字符串
[].toString() // ''
[1, 2].toString() // '1,2'
({}).toString() // "[object Object]"

[1, '123', [], undefined, null, NaN, true ].toString() // "1,123,,,,NaN,true"

转换成数值

  • Number()
Number('123') // 123
Number('123c') // NaN
// 如果字符串不能转换成合法数字,转换结果为 NaN
  • 使用 parseInt()
parseInt('12.3') // 12 只保留整数

parseInt('15xyz') // 15
parseInt('15x6yz') // 15
// 如果字符串里有非法的数字,会逐个转换,直到遇到无法转化的字符串为止
  • 使用 parseFloat() 完成
parseFloat('12.34') // 12.34 可以保留小数位
  • 让字符串和数字做除了加法以外的运算(隐式转换)
var d = '345'
+d === 345 // true
d - 0 === 345 // true
d * 1 === 345 // true
d / 1 === 345 // true
  • 引用类型转换成数值
// 空数组为 0,存在一个元素且可转换成数字,则转换成该数字,其他情况为 NaN
+[] // 0
+['2'] // 2
+['1', '2'] // NaN
+{} // NaN

转换成布尔值

0, NaN, 空字符串,undefined, null, false 会被转换成为 false

  • 使用 Boolean() 完成
Boolean(123) // true
// 对于数字类型来说:一般的数字都转换成为 true,0、NaN 会被转换成为 false

Boolean('123') // true
Boolean(' ') // true
Boolean('') // false
// 字符串中只有空的字符串会被转换成为 false

Boolean(undefined) // false
Boolean(null) // false
  • 使用 !!
!!2 // true
  • 自动转换
if ('') { console.log('哈哈') }

变量转换表

Value Boolean Number String
undefined false NaN “undefined”
null false 0 “null”
true true 1 “true”
false false 0 “false”
“” false 0 “”
“123” true 123 “123”
“1a” true NaN “1a”
0 false 0 “0”
1 true 1 “1”
Infinity true Infinity “Infinity”
NaN false NaN “NaN”
{} true NaN “[object Object]”
数组 true 空数组为 0,存在一个元素且为数字转数字,其他 NaN [1, ‘123’, [], undefined, null, NaN, true ] => “1,123,,,,NaN,true”

算数操作符

+-*/%

对于加法 转换方向: 布尔值 -> 数值 -> 字符串, 数组/对象 -> 字符串
除加法以外 转换方向:字符串 -> 数值,布尔值 -> 数值, 数组 -> 数值

var num = 5 + 6 // 11
var num = 5 % 2 // 1 取余数
var num = 5 % -2 // 1
var num = -5 % 2 // -1 只与左边值的符号有关

// 数字与字符串相加,数字会转换成字符串,返回字符串
var num = '5' + 6 + 7 // '567'
var num = 5 + 6 + '7' // '117'

// 数值与布尔值相加,布尔值转化成数值,false 转成 0,true 转成 1
var num = 5 + true // 6

// 字符串与布尔值相加,布尔值转化成字符串
var num = '1' + true // '1true'

'a' + +'b' // -> 'a' + +'b' -> 'aNaN'
4 * '3' // 12
true + true // 2

1 / 0 // Infinity
0 / 0 // NaN

赋值操作符

赋值运算符左边不能是常量或表达式

var age = 10
var num = age++ // num = 10 age = 11 (先将变量中的值取出做赋值操作,再自身+1)

var age = 10
var num = ++age // num = 11 age = 11 (先自身+1,然后再将+1后的结果赋值)

var num = 5
console.log(num++) // 5
console.log(++num) // 7

var x = 3
var y = x++ + ++x + x * 10 // x = 3
// y = 3 + ++x + x * 10 // x = 4
// y = 3 + 5 + x * 10 // x = 5
// y = 58

关系操作符

><>=<=

== 相等 、!= 不相等 、=== 全等 、!== 不全等

in instanceof

  • ><>=<===!=

在比较前先执行类型转换

  • 如果有一个操作数是布尔值,则在比较相等性前先将其转换为数值 -> false 转换为 0,true 转换为 1
  • 如果一个操作数是字符串,另一个操作数是数值,则在比较相等性前将字符串转换为数值
  • 如果两个值都是字符串,则按照字符串的字符编码进行逐位比较
  • 如果一个数是对象,另一个不是,则调用对象的 valueOf()方法,用得到的基本类型值按照前面的规则比较
  • 如果两个操作数都是对象,则比较它们是否指向同一个对象
  • null 和 undefined 是相等的
  • 在比较相等性之前,不能将 null 和 undefined 转换为其他任何值
  • 在比较大小之前,null,undefined 会被 Number()强制转换成数字类型 Number(null) -> 0, Number(undefined) -> NaN
  • 如果有一个操作符是 NaN,则相等操作符返回 false,不相等操作符返回 true;即使两个操作数都是 NaN,也一样
  • === 全等、!== 不全等
    两个操作数在未经转换的情况下相等返回 true,不相等返回 false
0 == false // true
1 == true // true
2 == true // false

false == '0' // true
'' == 0 // true
'4' == 4 // true

null == undefined // true
undefined == 0 // false

null >= 0 // true
null <= 0 // true
null == 0 // false

null >= false // true
null <= false // true
null == false // false

'NaN' == NaN // false
5 == NaN // false
NaN == NaN // false
NaN != NaN // true

[] != [] // true
[] == ![] // true
0 == [] // true
1 == [] // false
1 == [1] // true

0 + [] // '0'
0 + {} // "0[object Object]"

{} + [] === 0 // true {} 被解析为空的 block, + 被解析为正号运算符,结果等于 +[]
[] + {} === "[object Object]" // true [] 被解析为数组,后续的 + 被解析为加法运算符
({} + []) // '[object Object] 括号会阻止js将{}识别为block,因此他的运算结果与 []+{} 一致
console.log({} + []) // '[object Object] 当表达式作为参数传递给函数时,不会被默认为新的block
console.log({} + [] === 0) // false

[] + {} === {} + [] // true

-0 === 0 // true
'4' === 4 // false
undefined === null // false
  • in

    判断对象是否能够访问到该属性

  • instanceof

    判断一个对象是否是另一个对象的实例

逻辑操作符

! 非、&& 与、||

  • ! 对 Boolean 值取反
var flag = true
alert(!flag) // false

alert(!0) // true
alert(![]) // false
alert(!'') // true
alert(!![]) // true
alert(!!1) // true
  • && 如果第一个值转换成 boolean 值之后为 true, 则输出第二个值;如果第一个值转换成 boolean 值之后为 false,则输出第一个值,且第二个值不在执行。(取第一个为 false 的值,如果都为 true ,则输出最后一个值。)
var result = true && 3 // 3
var result = 1 && 3 // 3
var result = [] && '' // ''
var result = false && 3 // false
var result = '' && 3 // ''
var result = null && true // null

var num = 0
var result = '' && num++ // '' num = 0

&& 使用场景

function animate(fn) {
fn && fn()
}
// 不传参数不会报错
animate()
  • || 如果第一个值转换成 boolean 值之后为 true, 则输出第一个值,且第二个值不在执行;如果第一个值转换成 boolean 值之后为 false,则输出第二个值,以此类推,(取第一个为 true 的值,如果都为 false ,则输出最后一个值。)
var result = true || 3 // true
var result = 1 || 3 // 1
var result = [] || '' // []
var result = false || 0 // 0
var result = '' || 3 // 3
var result = num || true // true

var num = 0
var result = 3 || num++ // 3 num=0

|| 使用场景

// 1.兼容性问题:
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop

// 2. 函数的参数默认值
function sum(n) {
n = n || 10 // 给形参 n 设置默认值
console.log(n + 10)
}
sum()

操作符的优先级

从高到低如下:

  • () 优先级最高

  • 一元运算符 ++ – !

  • 算数运算符 先 * / % 后 + -

  • 关系运算符 > >= < <=

  • 相等运算符 == != === !==

  • 逻辑运算符 先 && 后 ||

  • 赋值运算符 =

空值合并操作符 ??

注意与或操作符区别

var a = b ?? c

// 当且仅当 b 为 null 或 undefined 时,返回 c,否则返回 b

// 等价于
var a
if ( b === null || b === undefined ){
a = c
} else {
a = b
}

可选链操作符 ?.

在引用为空(nullish) (null 或者 undefined) 的情况下不会引起错误,会短路返回值,返回 undefined

// 访问属性、调用方法
obj?.customMethod?.()

// 数组取值
arr?.[5]

// 短路计算
let obj1 = null
let a = 0
let prop1 = obj1?.[a++] // a => 0

let obj2 = 0
let b = 0
let prop2 = obj2?.[b++] // b => 1

ES6(ECMAScript)

ECMAScript 6.0(以下简称 ES6)是在 2015 年 6 月正式发布的标准。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言

ECMAScript 6 入门 阮一峰

let 与 const

ES6 中提供了两个声明变量的关键字:const 和 let

参考链接

[知乎]我用了两个月的时间才理解 let

[MDN]变量提升

let 的使用

ES6 新增了let命令,用来声明变量。它的用法类似于var

  • let 声明的变量只有在当前作用域(块作用域)有效
{
var a = 1
let b = 2
}

console.log(a) // 1
console.log(b) // ReferenceError: b is not defined
  • 不允许重复声明
let a = 1
let a = 2 // SyntaxError: Identifier 'a' has already been declared

谷歌浏览器控制台对重复声明已经不报错了,估计是为了方面调试

> let a = 1
< undefined
> let a = 2
< undefined
> const a = 3
x VM331:1 Uncaught SyntaxError: Identifier 'a' has already been declared
> const b = 1
< undefined
> const b = 2
< undefined
> let b = 3
x VM378:1 Uncaught SyntaxError: Identifier 'b' has already been declared
  • 使用 let 声明的全局变量,不会成为 window 的属性
var c = 1
console.log(window.c) // 1
console.log(c) // 1
let c = 1
console.log(window.c) // undefined
console.log(c) // 1
  • 存在变量提升
let a = 1
{
a = 2
let a
}
// 如果 let 不会提升,那么 a = 2 就会将外面的 a 由 1 变成 2
// 但运行发现 a = 2 报错:Uncaught ReferenceError: Cannot access 'a' before initialization
a = 1; let a  // Uncaught ReferenceError: Cannot access 'a' before initialization

总结:

  • let/const 声明的「创建」过程被提升了,但是「初始化」没有提升,var 声明的「创建」和「初始化」都被提升了,但「赋值」没被提升,function 声明的「创建」、「初始化」和「赋值」都被提升了
  • let 声明会提升到块顶部,从块顶部到该变量的初始化语句,这块区域叫做 TDZ(暂时死区),所谓暂时死区,就是不能在初始化之前,使用变量
  • 如果你在 TDZ 内使用该变量,JS 就会报错

如果 let x 的初始化过程失败了,那么

  • x 变量就将永远处于 created 状态
  • 你无法再次对 x 进行初始化(初始化只有一次机会,而那次机会你失败了)
  • 由于 x 无法被初始化,所以 x 永远处在暂时死区

const 的使用

const声明一个常量。常量:代码执行的过程中,不可以修改常量里面的值

  • const 声明的量不可以改变
const PI = 3.1415
PI = 3 // TypeError: Assignment to constant variable
  • const 声明的变量必须赋值
const num
// SyntaxError: Missing initializer in const declaration
  • 如果 const 声明了一个对象,仅仅保证地址不变,可以修改对象的属性
const obj = { name: 'zs' }
obj.age = 18 // 正确
obj = {} // TypeError: Assignment to constant variable
  • 其他用法和 let 一样

模板字符串(模板字面量)

模板字面量 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能

// 1. 通过``可以定义一个字符串
let str = `hello world`

// 2. 模板字符串内部允许换行
let str = `
hello
world
`

// 3. 模板字符串内部可以使用表达式
let str = `
你好,我是${name}
`

箭头函数

ES6 标准新增了一种新的函数:Arrow Function(箭头函数),为什么叫 Arrow Function?因为它的定义用的就是一个箭头

使用

// 语法: (参数列表) => {函数体}
let fn = (x, y) => {
console.log(x + y)
}

相当于

let fn = function(x, y) {
console.log(x + y)
}

特点

  • 不存在 prototype 这个属性
let a = () => {}
console.log(a.prototype) // undefined
  • 没有自己的 this,arguments

箭头函数的 this、arguments 都是在定义函数时绑定外层的 this 和 arguments,而不是在执行过程中绑定的,所以不会因为调用者不同而发生变化。
可以使用剩余参数(Rest 参数)表示法获得的自身入参列表

因为箭头函数没有 this,因此箭头函数不能作为构造函数

不能用 call()、apply()、bind() 这些方法改变 this 的指向

fn = function(){
let arrow = (...args) => {
console.log(arguments) // 外层的入参列表 -> Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(args) // 使用剩余参数表示法获得的自身入参列表 -> (3) [4, 5, 6]
}
arrow(4, 5, 6)
console.log(arrow.length) // 0
}
fn(1, 2, 3)
  • 如果函数体只有一行语句,并且需要返回这个值,那么可以省略 {} 和 return
let fn = (n1, n2) => n1 + n2
  • Rest 参数和 arguments 对象的区别:

rest 参数只包括那些没有给出名称的参数,arguments 包含所有参数

rest 参数之后不能再有其他参数,否则会报错

函数的 length 属性,不包括 rest 参数

arguments 对象不是真正的数组,而 rest 参数是数组实例,可以直接使用数组的方法

对象简化语法

// 当属性的 key 和变量的名相同时可以简写
let person = { name: name } ==> let person = { name }

// 声明函数
let cal = {
add: function () {
return 1
},
// 可以省略 `:function`
add(){
return 1
}
}

属性名表达式

  • ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'
let methodKey = 'bar'

let obj = {
[propKey]: true, // foo: true
['a' + 'bc']: 123, // abc: 123
[methodKey]() {
return 'hi'
}
}

class 关键字

ES5 中通过 构造函数 + 原型 的方式来实现面向对象

// 构造函数
function Person() {
this.name = 'jack'
this.age = 18
}

// 在原型中添加实例方法
Person.prototype.say = function() {
console.log(this.name, this.age)
}

// 创建实例
const p = new Person()
p.say()

ES6 中出现了 class 关键字,用来实现面向对象

class 声明不允许再次声明已经存在的类,否则将会抛出一个类型错误
class 声明不可以提升
class 仅仅是一个语法结构(语法糖),本质还是函数,实现继承本质上还是通过构造函数 + 原型的方式

class Person {}
Person instanceof Function // true

类声明

// 创建 Person 类
class Person {
// 类的构造函数 constructor 固定名称
constructor(name, age) {
this.name = name
this.age = age
}

// 添加实例方法
say() {
console.log(this.name, this.age)
}
}

// 创建实例
const p = new Person('tom', 18)
console.log(p) // Person {name: 'tom', age: 18}
p.say() // tom 18

类表达式

赋予一个命名类表达式的名称是类的主体的本地名称

// 匿名类
let Person = class {}
new Person() // Person {}

// 命名类
let Person = class A {}
new Person() // A {}
new A() // Uncaught ReferenceError: A is not defined
console.log(Person) // class A {}
console.log(A) // Uncaught ReferenceError: A is not defined

类表达式也不存在提升

static 关键字用来定义一个类的静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法

class Point {
constructor(x, y) {
this.x = x
this.y = y
}

static distance(a, b) {
const dx = a.x - b.x
const dy = a.y - b.y

return Math.hypot(dx, dy)
}
}

const p1 = new Point(5, 5)
const p2 = new Point(10, 10)

console.log(Point.distance(p1, p2))

继承:要实现至少需要两个 class(子类 和 父类),子类继承自父类,继承后,子类就可以使用父类中的属性或方法

// 继承

// 父类
class Person {
constructor(name, age) {
this.name = name
}

say() {
console.log('父类中的 say 方法', this.name)
}
}

// 子类
class Chinese extends Person {
constructor(name, age) {
// 子类中使用 constructor 必须手动调用 super
// super 表示父类的构造函数
// 先调用 super() 在使用 this
super()
this.name = name
this.age = age
}
}

// 创建实例
const c = new Chinese('zs', 18)
console.log(c)
c.say() // 父类中的方法

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)

// 1. 对象解构
var { a, b } = { a: 10, b: 20 }

// 同
;({ a, b } = { a: 10, b: 20 }) // 使用没有声明的赋值,数组解构类似
console.log(a, b) // 10 20

// 提取变量并赋值
var { a: p, b: q } = { a: 10, b: 20 }
console.log(p, q) // 10 20

// 将剩余属性赋值给一个变量
var { a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 }
console.log(a, b, rest) // 10 20 {c: 30, d: 40}

// 提供默认值
var { a = 1, b = 1 } = { a: 10 }
console.log(a, b) // 10 1

// 赋值并提供默认值
var { a: aa = 10, b: bb = 1 } = { a: 10 }
console.log(aa, bb) // 10 1

// 2. 数组解构
var [a, b] = [1, 2]
console.log(a, b) // 1 2

// 将剩余数组赋值给一个变量
var [a, b, ...rest] = [1, 2, 3, 4]
console.log(a, b, rest) // 1 2 [3, 4]
// ==> var a = arr[0]; var b = arr[1]

// 提供默认值
var [c = 2, d = 2] = [10]
console.log(c, d) // 10 2

// 忽略某些值
var [a = 2, , b = 2] = [10, 20, 30]
console.log(a, b) // 10 30

// 3. 函数参数的解构赋值
function foo({ x }) {
console.log(x) // 1
}
foo({ x: 1, y: 2 })

// 函数参数默认值
function foo({ x = 10 }) {
console.log(x) // 10
}
foo()

// 4. 解构的特殊应用
// 交换变量
var a = 1
var b = 3
;[a, b] = [b, a]
console.log(a) // 3
console.log(b) // 1

// 字符串解构
var str = 'love'
var [a, b, c, d] = str
console.log(a, b, c, d) // l o v e

扩展运算符

扩展运算符(spread)是三个点(…)。作用:将一个数组转为用逗号分隔的参数序列

var arr = ['a', 'b', 'c']
console.log(...arr) // a b c

应用

// 数组深拷贝
var arr = [1, 2, 3]
var arr1 = [...arr]
console.log(arr === arr1) // false, 说明arr1和arr指向不同数组

// 把一个数组插入另一个数组字面量
var arr2 = [...arr, 4, 5, 6]
console.log(arr2) // [1, 2, 3, 4, 5, 6]

// 字符串转数组
var str = 'love'
var arr3 = [...str]
console.log(arr3) // [ 'l', 'o', 'v', 'e' ]

对象展开

let defaults = { name: 'zs', age: 18 }
let search = { ...defaults, age: 12 } // { name: 'zs', age: 12 } 后面的属性会覆盖前面的属性

对象展开仅包含对象自身的可枚举属性

class C {
p = 12
m() {}
}
let c = new C()
let clone = { ...c }
clone.p // ok
clone.m() // error!

ES6 模块化

相关链接

v2ray 用户手册 github
V2Ray 白话文指南 github
在线配置生成
v2ray web面板 v2-ui
SSPanel-Uim 节点管理面板/用户管理系统
使用宝塔部署 SSPanel 魔改版

安装

一键脚本安装

bash <(curl -s -L https://git.io/v2ray.sh)

安装完成后,输入 v2ray 即可管理 V2Ray

管理命令

v2ray info # 查看 V2Ray 配置信息
v2ray config # 修改 V2Ray 配置
v2ray link # 生成 V2Ray 配置文件链接
v2ray infolink # 生成 V2Ray 配置信息链接
v2ray qr # 生成 V2Ray 配置二维码链接
v2ray ss # 修改 Shadowsocks 配置
v2ray ssinfo # 查看 Shadowsocks 配置信息
v2ray ssqr # 生成 Shadowsocks 配置二维码链接
v2ray status # 查看 V2Ray 运行状态
v2ray start # 启动 V2Ray
v2ray stop # 停止 V2Ray
v2ray restart # 重启 V2Ray
v2ray log # 查看 V2Ray 运行日志
v2ray update # 更新 V2Ray
v2ray update.sh # 更新 V2Ray 管理脚本
v2ray uninstall # 卸载 V2Ray

官方脚本

官方脚本

# 安装
bash <(curl -L -s https://install.direct/go.sh)

# 安装完成后记住 PORT UUID,忘记也没关系可以使用下面命令查看

# 启动
systemctl start v2ray

# 查看端口 Port
cat /etc/v2ray/config.json | grep port

# 查看 id (UUID)
cat /etc/v2ray/config.json | grep id

卸载

如果脚本不支持卸载,可使用以下方法手动卸载

其中 systemd 和 sysv 二选一,取决于你的系统

#停用并卸载服务(systemd)
systemctl stop v2ray
systemctl disable v2ray

#停用并卸载服务(sysv)
service v2ray stop
update-rc.d -f v2ray remove

# 删除文件
# 配置文件
rm -rf /etc/v2ray/*
# 程序
rm -rf /usr/bin/v2ray/*
# 日志
rm -rf /var/log/v2ray/*
# systemd 启动项
rm -rf /lib/systemd/system/v2ray.service
# sysv 启动项
rm -rf /etc/init.d/v2ray

Nginx + WebSocket + TLS

参考:https://guide.v2fly.org/advanced/wss_and_web.html

V2Ray 脚本可直接使用 Caddy 配置 WebSocket + TLS 传输协议,但是如果想在 vps 上同时使用 nginx 跑一个小博客,那么会导致 caddy 和 nginx 监听端口时发生冲突,这显然不是我们想要的

所以就要将 TLS 部分放到 nginx 程序里面去实现

首先使用官方脚本安装好服务端程序

申请 SSL 证书

如果使用宝塔面板,可以通过面板一键生成 ssl 证书,快速配置 nginx+ssl

申请免费 SSL 证书参考这里

nginx 配置

如果使用宝塔面板,可以通过面板一键生成 ssl 证书,快速配置 nginx+ssl

server {
listen 443 ssl;
listen [::]:443 ssl;
server_name aaa.com; # 域名

# 配置ssl
ssl_certificate /etc/letsencrypt/aaa.com.crt; # 证书
ssl_certificate_key /etc/letsencrypt/aaa.com.key; # 密钥
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
#

location /ray { # 与 V2Ray 服务端配置中的 path 保持一致
if ($http_upgrade != "websocket") { # WebSocket协商失败时返回404
return 404;
}
proxy_redirect off;
proxy_pass http://127.0.0.1:2333; # 假设 WebSocket 监听在环回地址的 2333 端口上
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# Show real IP in v2ray access.log
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

v2ray 服务端配置

/etc/v2ray/config.json

{
"inbounds": [
{
"port": 2333, // WebSocket 监听端口
"listen": "127.0.0.1", // 只监听 127.0.0.1,避免除本机外的机器探测到开放了 22445 端口
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
"alterId": 32
}
]
},
"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "/ray"
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
]
}

客户端配置

{
"inbounds": [
{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
},
"settings": {
"auth": "noauth",
"udp": false
}
}
],
"outbounds": [
{
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "aaa.com", // nginx server_name
"port": 443,
"users": [
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", // 同服务端配合
"alterId": 32 // 同服务端配合
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"wsSettings": {
"path": "/ray" // 与 V2Ray 服务端配置中的 path 保持一致
}
}
}
]
}

Nginx+vmess+ws+tls/ http2 over tls 一键安装脚本

https://github.com/wulabing/V2Ray_ws-tls_bash_onekey

v2ray 配置好后无法连接解决办法

# 检查端口占用情况
yum install net-tools
netstat -apn | grep v2ray

# 发现v2ray并没有监听我们的公网IP,只监听了一个IPV6:
# tcp6 0 0 :::40682 :::* LISTEN 19553/v2ray
# unix 3 [ ] STREAM CONNECTED 80938 19553/v2ray

# 修改配置文件添加 listen 字段

# v2ray默认配置文件在/etc/v2ray/conf.json
"inbound": {
"listen":"12.34.56.78",
}

# v2ray 测试配置文件是否正确
/usr/bin/v2ray/v2ray --test --config /etc/v2ray/config.json

客户端

客户端可以参考这里

n

github地址

支持 macOS, Linux

安装

npm install -g n

使用

n --latest               # Output the latest node version available
n --lts # Output the latest LTS node version available
n ls # Output downloaded versions
n ls-remote [version] # Output matching versions available for download
n uninstall # Remove the installed node and npm

# 安装最新版
n latest

# 安装长期支持版本
n lts

# 安装指定版本
n 12.4.1

# 切换nodejs版本
n
# 上下键选择已安装的版本,回车切换
ο node/13.6.0
node/10.4.1

# 查看当前版本 node -v
node -v

nvm

github地址

支持 macOS, Linux

NodeSource

github地址

支持 Linux

nvm-windows

支持 Windows

github地址

安装

注意:1. 需要卸载已经安装的 nodejs 版本,并删除残留的 nodejs 程序目录(例如:”C:\Program Files\nodejs”)

  1. 删除 npm 全局安装的包(”C:\Users<user>\AppData\Roaming\npm”
  • 安装 nvm-windows 下载

  • 重新安装全局包
    安装完成后,必须为每个安装版本的node重新安装全局工具

使用

nvm-windows runs in an Admin shell. You’ll need to start powershell or Command Prompt as Administrator to use nvm-windows

NVM for Windows is a command line tool. Simply type nvm in the console for help. The basic commands are:

  • nvm arch [32|64]: Show if node is running in 32 or 64 bit mode. Specify 32 or 64 to override the default architecture.
  • nvm install <version> [arch]: The version can be a node.js version or “latest” for the latest stable version. Optionally specify whether to install the 32 or 64 bit version (defaults to system arch). Set [arch] to “all” to install 32 AND 64 bit versions.
  • nvm list [available]: List the node.js installations. Type available at the end to show a list of versions available for download.
  • nvm on: Enable node.js version management.
  • nvm off: Disable node.js version management (does not uninstall anything).
  • nvm proxy [url]: Set a proxy to use for downloads. Leave [url] blank to see the current proxy. Set [url] to “none” to remove the proxy.
  • nvm uninstall <version>: Uninstall a specific version.
  • nvm use <version> [arch]: Switch to use the specified version. Optionally specify 32/64bit architecture. nvm use will continue using the selected version, but switch to 32/64 bit mode based on the value supplied to . For information about using use in a specific directory (or using .nvmrc), please refer to issue #16.
  • nvm root <path>: Set the directory where nvm should store different versions of node.js. If is not set, the current root will be displayed.
  • nvm version: Displays the current running version of NVM for Windows.
  • nvm node_mirror <node_mirror_url>: Set the node mirror.People in China can use https://npm.taobao.org/mirrors/node/
  • nvm npm_mirror <npm_mirror_url>: Set the npm mirror.People in China can use https://npm.taobao.org/mirrors/npm/

Babel与Polyfill的关系和区别
Babel 默认只转换新的 js 句法(syntax),而不转换新的 API,例如箭头函数等
Polyfill 用于实现浏览器并不支持的原生API的代码,如新增的方法等

Babel

Babel的安装,配置

创建 babel.config.js 文件,内容如下

babel.config.js 是Babel执行时会默认在当前目录寻找的Babel配置文件,除了babel.config.js,我们也可以选择用.babelrc或.babelrc.js这两种配置文件

module.exports = {
presets: ["@babel/env"]
}

安装如下包

npm i -D @babel/cli @babel/core @babel/preset-env
  • @babel/cli是Babel命令行转码工具,如果我们使用命令行进行Babel转码就需要安装它。
  • @babel/cli依赖@babel/core,因此也需要安装@babel/core这个Babel核心npm包。
  • @babel/preset-env这个npm包提供了ES6转换ES5的语法转换规则,我们在Babel配置文件里指定使用它。如果不使用的话,也可以完成转码,但转码后的代码仍然是ES6的,相当于没有转码。

创建 index.js 文件,如下

const fn = (num) => num + 1
new Promise(() => {})

执行命令

npx babel index.js -o output.js

输出结果为

"use strict";

var fn = function fn(num) {
return num + 1;
};

new Promise(() => {});

可以看到,ES6的箭头函数语法转换成了 ES5 的函数定义语法,但是并没有对ES6的Promise进行转换。因为Babel默认只转换新的 js 语法(箭头函数,解构…),而不转换新的 API。新的API分类两类,一类是Promise、Map、Symbol、Proxy、Iterator等全局对象及其对象自身的方法,例如Object.assign,Promise.resolve;另一类是新的实例方法,例如数组实例方法[1, 2, 3].find((item) => item < 2)

polyfill

安装

npm install --save @babel/polyfill

在 index.js 中引入 import '@babel/polyfill'

执行命令

npx babel index.js -o output.js

输出结果

"use strict";

require("@babel/polyfill");

var fn = function fn(num) {
return num + 1;
};

new Promise(function () {});

输出结果中 require("@babel/polyfill");

import被编译成了require,如果想要编译出来的模块引入规范还是import,则可以在preset-env的配置项中添加”modules”: false即可

有时候我们项目里并没有用到那么多的新增API,但是 @babel/polyfill 会把所有浏览器环境的的polyfill都引入,整个包的体积就会很大,我们想要对目标环境按需引入相应的polyfill应该怎么办呢,这个时候我们就可以使用 preset-env 的配置项中的 useBuiltIns 属性来按需引入 polyfill。

module.exports = {
presets: [
[
"@babel/preset-env", {
"modules": false, // 用来设置是否把ES6的模块化语法改成其它模块化语法
"useBuiltIns": "usage",
}
]
]
}

使用useBuiltIns:”usage”后,Babel除了会考虑目标环境缺失的API模块,同时考虑我们项目代码里使用到的ES6特性,且不需要在项目入口处手动引入polyfill

import "core-js/modules/es6.object.to-string.js";
import "core-js/modules/es6.promise.js";

var fn = function fn(num) {
return num + 1;
};

new Promise(function () {});

可以看到 多了 import "core-js/... 的引用,因为 @babel/polyfil 是由core-js2和regenerator-runtime组成的一个集成包,
Babel 7.4.0 之后已经弃用了 @babel/polyfill,所以core-js官方现在推荐我们使用polyfill的时候直接引入core-js和regenerator-runtime/runtime这两个包完全取代 @babel/polyfil

core-js
babel-polyfill

js 模块化编程

最初 js 不是一种模块化编程语言(es6 开始支持)。为了能够尽可能的实现 js 的模块化,我们会把代码写成这样:

  1. 最原始: 封装函数写法
function fn1() {
// code
}
function fn2() {
// code
}

上面的函数 fn1()fn2(),组成一个模块。使用的时候,直接调用就行了,这种做法的缺点很明显:”污染”了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系

  1. 对象写法

为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面

var module1 = {
_count: 0,
fn1: function() {
//code
},
fn2: function() {
//code
}
}

上面的函数 fn1()fn2(),都封装在 module1 对象里。使用的时候,就是调用这个对象的属性:module1.fn1()

但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值:module1._count = 666

  1. 立即执行函数(自调用函数)写法 (沙箱模式)
// 使用 立即执行函数,可以达到不暴露私有成员的目的
var module1 = (function() {
var _count = 0 // 一般私有的变量,申明变量名时,以 _ 开头
function fn1() {
// code
}
function fn2() {
// code
}
return {
fn1: fn1,
fn2: fn2
}
})()

使用上面的写法,外部代码无法读取内部的 _count 变量:console.info(module1._count) => undefined

模块化的标准

让模块拥有更好的通用性

  • AMD : Async Module Definition 异步模块定义:依赖前置、提前执行: 在一开始就将所有的依赖项全部加载

  • CMD : Common Module Definition 通用模块定义:依赖就近、延迟执行: 在需要的时候才去 require 加载依赖项

  • commonJS: node.js 同步加载模块,适用于服务端

  • ES 标准模块化规范

AMD (Asynchronous Module Definition)

异步加载模块 requireJs 库应用这一规范

// module add.js
define(function () {
return {
add: function (a, b) { return a + b }
}
})

// main.js
// 第一个参数是要请求的模块, 第二个参数是依赖模块请求完成的回调函数
require(['add'], function (add) {
console.log('1 + 2 = ' + add(1, 2)
})

CMD (Common Module Definition)

同步加载模块 SeaJS

// module add.js
define(function(require, exports, module) {
// 正确写法
// 给 module.exports 赋值
module.exports = {
add: function (a, b) { return a + b }
}

// 或使用 return
return {
add: function (a, b) { return a + b }
}

// 错误用法
// 对 module.exports 的赋值需要同步执行,不能放在回调函数里
setTimeout(function() {
module.exports = { a: 'hello' }
}, 0)
// exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口
exports = {
add: function (a, b) { return a + b }
}
})

// main.js
var { add } = require('./add') // 此处是同步加载,且可以实现条件加载,因为只有运行到该行代码的时候才会加载模块
console.log('1 + 2 = ' + add(1, 2)

// 同步加载这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态

AMD 和 CMD 区别

AMD 和 CMD 最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。

AMD(requirejs)是将所有文件同时加载、一次性引入、推崇依赖前置、也就是在定义模块时要先声明其依赖的模块、加载完模块后会立马执行该模块(运行时加载),所有模块都加载执行完后会进入 require 的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行

CMD(seajs)强调的是一个文件一个模块、可按需引入、推崇依赖就近、加载完某个模块后不会立即执行,只是下载而已,所有依赖模块加载完成后进入主逻辑,遇到 require 语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的

// AMD
define(['./a', './b'], function(a, b) {
// 依赖必须一开始就写好
a.doSomething()
b.doSomething()
})

// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
var b = require('./b') // 依赖可以就近书写
b.doSomething()
})

CommonJS 规范

Node 应用由模块组成,采用 CommonJS 模块规范,每个文件就是一个模块,有自己的作用域

在前端浏览器里面并不支持 module.exports

有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global

node 中模块分类

  • 核心模块:由 node 本身提供,不需要单独安装(npm),可直接引入使用

    • fs:文件操作模块
    • http:网络操作模块
    • path:路径操作模块
    • url:解析地址的模块
    • querystring:解析参数字符串的模块
  • 第三方模块:由社区或个人提供,需要通过 npm 安装后使用,比如:mime

  • 自定义模块:由开发人员自己创建,比如:tool.js、user.js

模块导入

  • 核心模块直接引入使用:require('fs') 加载文件操作模块
// 引入模块
let fs = require('fs')
  • 第三方模块,需要先使用 npm 进行下载
  • 自定义模块,需要加上相对路径 ./ 或者 ../ ,可以省略 .js 后缀,如果文件名是 index.js 那么 index.js 也可以省略
// 加载模块
require('./a') // 推荐使用,省略 .js 后缀
require('./a.js')
  • 模块可以被多次导入,但是只会在第一次加载

模块导出

  • 在模块的内部,module 变量代表的就是当前模块,它的 exports 属性就是对外的接口,加载某个模块,加载的就是 module.exports 属性,这个属性指向一个空的对象
// module.exports 指向的是一个对象,我们给对象增加属性即可
module.exports.name = 'zs'
module.exports.age = 18

setTimeout(function() {
module.exports.gender = 'man'
}, 0)
let m = require('./module.js')
console.log(m.name) // zs
console.log(m.age) // 18
console.log(m.gender) // undefined

setTimeout(function() {
console.log(m.gender) // man
}, 0)
// 也可以直接给 module.exports 赋值,但是多次导出会覆盖
module.exports = '123'
module.exports = () => {}
module.exports = {}

// 对 module.exports 的赋值需要同步执行,不能放在回调函数里
setTimeout(function() {
module.exports = { a: 1 }
}, 0)
let m = require('./module.js')
console.log(m) // {}
setTimeout(function() {
console.log(m.a) // undefined
}, 0)

module.exports 与 exports

  • exports 不是 module.exports 的缩写,exports 是单独存在的

  • exports 和 module.exports 默认指向同一个对象

  • 模块最终导出的一定是 module.exports 中的数据

  • 结论:

    • 直接添加属性两者皆可

    • 赋值对象时,只能使用 module.exports

console.log(module.exports === exports) // ==> true

// 等价操作
module.exports.num = 123
exports.num = 123

// 赋值为新对象
exports = {}
module.exports = {}
// 模块导出的是 module.exports 指向的对象

nodejs 中 require 加载模块的规则

require(‘mime’) 以 mime 为例

  1. 如果加载的模块是一个路径,表示加载的自定义模块,根据路径查找对应的 js 文件
  2. 如果加载的模块是一个名字,不是一个路径,说明加载的是核心模块或者是第三方模块
  3. 判断是否是核心模块,如果不是核心模块,会在当前目录下查找是否有 node_modules 目录
  4. 如果有,在 node_modules 目录下查找 mime 这个文件夹,找到 mime 文件夹下的 package.json 文件,找到 main 属性,即模块的入口文件,如果没有 main,默认查找当前目录下的 index.js 文件
  5. 如果没有找到对应的模块,回去上一层目录,继续查找,一直找到根目录 C: || D: || E:
  6. 报错: can not find module xxx

ES 模块化 - import 和 export

Modules 不是对象,import 命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能

export 导出多个模块,都放在一个对象里

export default 默认只能导出一个,一个模块只允许有一个 export default,否则报错
export default 后面不可以用 var、let、const 可用 export default function(){} function add(){}

命名导出(Named exports)

// 导出
export const a = 1 // 声明后立即导出,这可以与 `var`, `let`, `const`, `class`, and `function` 配合使用

const b = 2
export { b } // 导出以前声明的值 必须用对象包裹,否则报错

export function c () {}
export const d = function () {}
export { something as somethingElse } // 在导出时重命名

// 导入
import { a, b, c, d, e } from 'test.js'
a // 1
b // 2
c // ƒ c() {}
d // ƒ d() {}
e // undefined

默认导出(Default Export)

仅当源模块只有一个导出时,才建议使用此做法

// 导出
export default a = 1
// 等价于
let a = 1
export { a as default }
// 等价于
let a = 1
export default a

// 导入
import a from 'test.js'
a // 1

// 导入的名字可以任意
import b from 'test.js'
b // 1

将默认和命名导出组合在同一模块中是不好的做法,尽管它是规范允许的。

// 导出
export default a = 1
export const b = 2
export const c = 3
// 导入
import * as tool from 'test.js'
tool.a // 1
tool.b // 2
tool.c // 3

// 或者
import a, { b as d, c } from 'test.js'

// 以下写法错误
import { b as d, c }, a from 'test.js' // x
import a, e from 'test.js' // x

es import() 函数

参数同 import 命令的参数,返回一个 promise 对象

import() 函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,才会加载指定的模块。另外,import() 函数与所加载的模块没有静态连接关系

import 命令会被 js 引擎静态分析,import 语句放在 if 代码块之中毫无意义,因此会报句法错误,即不能用于条件加载

import() 类似于 Node 的 require 方法,区别主要是前者是异步加载,后者是同步加载

应用: 按需加载,条件加载,动态模块路径

import('./module.js').then(({ export1, export2 }) => {
// ...
})

同时加载多个模块

Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]).then(([module1, module2, module3]) => {
// ···
})

import() 也可以用在 async 函数之中。

在 webpack 中使用 import() 动态加载模块时,webpack 默认会将所有 import() 的模块都进行单独打包,https://webpack.js.org/api/module-methods/#import-1

CommonJS 模块 和 ES 模块化区别

CommonJS 模块

// commonJsModule.js
var num = 1
let obj = {}
var addNum = () => num++
var addObj = () => obj.num = num

module.exports = {
addNum, num,
addObj, obj,
get getNum() {
return num
}
}
let m = require('./commonJsModule.js')
console.log(m.num) // 1
m.addNum()
console.log(m.num) // 1
// 这里 num 输出的结果还是原来的值,因为 num 是一个原始类型的值,会被缓存
// 除非写成一个函数,才能得到内部变动的值
console.log(m.getNum) // 2

// 引用类型
console.log(m.obj) // {}
m.addObj()
console.log(m.obj) // { count: 2 }

// 重新赋值
m = {}
console.log(m.obj) // undefined

ES 模块

// esModule.js
export let num = 1
export let obj = {}
export function addNum() {
num++
}
import  { num,obj, addNum } from './esModule.js'
console.log(num) // 1
addNum()
console.log(num) // 2

// 重新赋值 报错
num = 3 // TypeError: Assignment to constant variable.
console.log(num)

// 修改属性
obj.a = 1
console.log(obj) // { a: 1 }

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
CommonJS和 ES 模块都可以对导出对象内部属性的值进行改变
CommonJS 模块输出的是一个值的拷贝,类似浅拷贝
ES 模块输出的 不论是基本类还是引用类型的数据,都是值的引用
ES 模块对导出的数据不可以重新赋值(只读状态),重新赋值会编译报错(即导出的数据指针指向不能变),但可以改变对象的属性,类似 const 声明的变量
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 Modules不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import 时采用静态命令的形式。即在 import 时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”

在 Vue 组件中,可以用过 $on, $once 去监听所有的生命周期钩子函数,如监听组件的 updated 钩子函数可以写成 this.$on(‘hook:updated’, () => {})

使用$.once(‘hook:beforeDestory’,() => {})清理定时器

https://cn.vuejs.org/v2/guide/components-edge-cases.html#程序化的事件侦听器

const timer = setInterval(() => {
console.log('1')
}, 1000)
// 直接在需要定时器的方法或者生命周期函数中声明并销毁
this.$once('hook:beforeDestory', () => {
clearInterval(timer)
timer = null
})

在父组件监听子组件的生命周期方法

<!-- 父组件中 -->
<child-component @hook:mounted="handleChildMounted" />

监听第三方组件数据的变化,但是组件又没有提供change事件,可以在外部监听组件的updated钩子函数

<child-component @hook:updated="handleChildUpdated" />