一份精心总结的3万字ES6实用指南(上)

数值扩展

  • 二进制(0b)和八进制(0o)表示法:

let num = 100

let b = num.toString(2)  // 二进制的100:1100100

let o = num.toString(8)  // 八进制的100:144

0b1100100 === 100        // true

0o144 === 100            // true

  • Number.isFinite() 判断一个数是否是有限的数,入参如果不是数值一律返回 false

Number.isFinite(-2.9)      // true

Number.isFinite(NaN)       // false

Number.isFinite(‘’)        // false

Number.isFinite(false)     // true

Number.isFinite(Infinity)  // false

  • Number.isNaN() 判断一个数值是否为 NaN,如果入参不是 NaN 那结果都是 false

Number.isNaN(NaN)       // true

Number.isFinite(‘a’/0)  // true

Number.isFinite(‘NaN’)  // false

  • 数值转化:Number.parseInt()Number.parseFloat(),非严格转化,从左到右解析字符串,遇到非数字就停止解析,并且把解析的数字返回:

parseInt(‘12a’)  // 12

parseInt(‘a12’)  // NaN

parseInt(‘’)  // NaN

parseInt(‘0xA’)  // 10,0x开头的将会被当成十六进制数

parseInt() 默认是用十进制去解析字符串的,其实他是支持传入第二个参数的,表示要以多少进制的 基数去解析第一个参数:

parseInt(‘1010’, 2)  // 10

parseInt(‘ff’, 16)  // 255

参考:parseInt

  • Number.isInteger() 判断一个数值是否为整数,入参为非数值则一定返回 false

Number.isInteger(25)   // true

Number.isInteger(25.0) // true

Number.isInteger()     // false

Number.isInteger(null) // false

Number.isInteger(‘15’) // false

Number.isInteger(true) // false

Number.isInteger(3.0000000000000002) // true

如果对数据精度的要求较高,不建议使用 Number.isInteger() 判断一个数值是否为整数。

  • Number.EPSILON 表示一个可接受的最小误差范围,通常用于浮点数运算:

0.1 + 0.2 === 0.3  // false

Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON  // true

  • Number.isSafeInteger() 用来判断一个数是否在最大安全整数(Number.MAX_SAFE_INTEGER)和最小安全整数(Number.MIN_SAFE_INTEGER)之间:

Number.MAX_SAFE_INTEGER === 2 ** 53 -1        // true

Number.MAX_SAFE_INTEGER === 9007199254740991  // true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER  // true

Number.isSafeInteger(2)         // true

Number.isSafeInteger(‘2’)       // false

Number.isSafeInteger(Infinity)  // false

  • Math.trunc():返回数值整数部分

  • Math.sign():返回数值类型(正数 1、负数 -1、零 0)

  • Math.cbrt():返回数值立方根

  • Math.clz32():返回数值的 32 位无符号整数形式

  • Math.imul():返回两个数值相乘

  • Math.fround():返回数值的 32 位单精度浮点数形式

  • Math.hypot():返回所有数值平方和的平方根

  • Math.expm1():返回 e^n - 1

  • Math.log1p():返回 1 + n 的自然对数(Math.log(1 + n))

  • Math.log10():返回以 10 为底的 n 的对数

  • Math.log2():返回以 2 为底的n的对数

  • Math.sinh():返回 n 的双曲正弦

  • Math.cosh():返回 n 的双曲余弦

  • Math.tanh():返回 n 的双曲正切

  • Math.asinh():返回 n 的反双曲正弦

  • Math.acosh():返回 n 的反双曲余弦

  • Math.atanh():返回 n 的反双曲正切

数组扩展

  • 数组扩展运算符(…)将数组展开成用逗号分隔的参数序列,只能展开一层数组:

// 应用一:函数传参

Math.max(…[1, 2, 3])  // 3

// 应用二:数组合并

let merge = […[1, 2], …[3, 4], 5, 6]  // 1, 2, 3, 4, 5, 6

// 应用三:浅克隆

let a = [1, 2, 3]

let clone = […a]

a === clone  // false

// 应用四:数组解构

const [x, …y] = [1, 2, 3]

x  // 1

y  // [2, 3]

  • Array.from() 可以将类数组对象( NodeListarguments)和可迭代对象转成数组:

// 应用一:字符串转数组

Array.from(‘foo’)  // [‘f’, ‘o’, ‘o’]

// 应用二:数组合并去重

let merge = […[1, 2], …[2, 3]]

Array.from(new Set(merge))  // [‘1’, ‘2’, ‘3’]

// 应用三:arguments 转数组

function f() {

return Array.from(arguments)

}

f(1, 2, 3)  // [1, 2, 3]

如果 Array.from() 带第二个参数 mapFn,将对生成的新数组执行一次 map 操作:

Array.from([1, 2, 3], (x) => x * x )    // [1, 4, 9]

Array.from({length: 3}, (v, i) => ++i)  // [1, 2, 3]

  • Array.of() 将一组参数转成数组:

Array.of(1, 2, 3)  // [1, 2, 3]

// 类似于

function arrayOf(…params){

return [].slice.call(params)

}

arrayOf(1, 2, 3)  // [1, 2, 3]

  • Array.copyWithin() 在当前数组内部,将制定位置的成员复制到其他位置(会覆盖原来位置的成员),最后返回一个新数组。接收 3 个参数,参数为负数表示右边开始计算:

[1, 2, 3, 4, 5].copyWithin(-1)         // [1, 2, 3, 4, 1]

[1, 2, 3, 4, 5].copyWithin(1)          // [1, 1, 2, 3, 4]

[1, 2, 3, 4, 5].copyWithin(0, 3, 4)    // [4, 2, 3, 4, 5]

[1, 2, 3, 4, 5].copyWithin(0, -3, -1)  // [3, 4, 3, 4, 5]

  • 查找第一个出现的子成员:find()findIndex()

// 找出第一个偶数

[1, 6, 9].find((val, index, arr) => val % 2 === 0)       // 6

// 找出第一个偶数的索引位置

[1, 6, 9].findIndex((val, index, arr) => val % 2 === 0)  // 1

  • fill() 使用给定的值来填充数组,有 3 个参数:

// 初始化空数组

Array(3).fill(1)  // [1, 1, 1]

[1, 2, 3, 4].fill(‘a’, 2, 4)  // [1, 2, ‘a’, ‘a’]

  • 通过 keys()(键名)、entries()(键值)和 values()(键值对) 获取数组迭代器对象,可以被 for...of 迭代,

let arr = [‘a’, ‘b’, ‘c’]

for (let x of arr.keys()) {

console.log(x)  // 1, 2, 3

}

for (let v of arr.values()) {

console.log(v)  // ‘a’ ‘b’ ‘c’

}

for (let e of arr.entries()) {

console.log(e)  // [0, ‘a’] [0, ‘b’] [0, ‘c’]

}

  • 数组空位,是指数组没有值,比如:[,,],而像这种 [undefined] 是不包含空位的。由于 ES6 之前的一些 API 对空位的处理规则很不一致,所以实际操作的时候应该尽量避免空位的出现,而为了改变这个现状,ES6API 会默认将空位处理成 undefined

[…[1, , 3].values()]  // [1, undefined, 3]

[1, , 3].findIndex(x => x === undefined)  // 1

对象扩展

  • 对象属性简写:

let name = ‘布兰’

let person = {

name,

getName() {

return this.name

}

}

// 等同于

let person1 = {

name: ‘布兰’,

getName: function() {

return this.name

}

}

  • 属性名表达式:在用对象字面量定义对象的时候,允许通过属性名表达式来定义对象属性:

let name = ‘name’,

let person = {

‘get’+ name{

return this.name

}

}

  • 方法的 name 属性,存在好几种情况,这里仅列出常见的几种:

情况一:普通对象方法的 name 属性直接返回方法名,函数声明亦是如此,函数表达式返回变量名:

let person = {

hi(){}

}

person.hi.name  // ‘hi’

情况二:构造函数的 nameanonymous

(new Function).name  // ‘anonymous’

情况三:绑定函数的 name 将会在函数名前加上 bound

function foo() {}

foo.bind({}).name  // ‘bound foo’

情况四:如果对象的方法使用了取值函数(getter)和存值函数(setter),则 name 属性不是在该方法上面,而是该方法的属性的描述对象的 getset 属性上面:

let o = {

get foo(){},

set foo(x){}

}

o.foo.name  // TypeError: Cannot read property ‘name’ of undefined

let descriptor = Object.getOwnPropertyDescriptor(o, “foo”)

descriptor.get.name  // “get foo”

descriptor.set.name  // “set foo”

参考:function_name

  • 属性的可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。可以通过 Object.getOwnPropertyDescriptor() 来获取对象某个属性的描述:

let person = { name: ‘布兰’, age: 12 }

Object.getOwnPropertyDescriptor(person, ‘name’)

// {

//     configurable: true,

//     enumerable: true,

//     value: “布兰”,

//     writable: true,

// }

这里的 enumerable 就是对象某个属性的可枚举属性,如果某个属性的 enumerable 值为 false 则表示该属性不能被枚举,所以该属性会被如下 4 种操作忽略:

let person = { name: ‘布兰’ }

Object.defineProperty(person, ‘age’, {

configurable: true,

enumerable: false,

value: 12,

writable: true

})

person  // { name: ‘布兰’, age: 12 }

// 以下操作都将忽略 person 对象的 age 属性

for (let x in person) {

console.log(x)         // ‘name’

}

Object.keys(person)        // [‘name’]

JSON.stringify(person)     // ‘{“name”: “布兰”}’

Object.assign({}, person)  // { name: ‘布兰’ }

Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举:

// 基于上面的代码

Reflect.ownKeys(person)  // [‘name’, ‘age’]

  • super 关键字,指向对象的原型对象,只能用于对象的方法中,其他地方将报错:

let person = {

name: ‘布兰’,

getName() {

return super.name

}

}

Object.setPrototypeOf(person, {name: ‘hello’})

person.getName()  // ‘hello’

// 以下几种 super 的使用将报错

const obj1 = {

foo: super.foo

}

const obj2 = {

foo: () => super.foo

}

const obj3 = {

foo: function () {

return super.foo

}

}

  • Object.is() 用来判断两个值是否相等,表现基本和 === 一样,除了以下两种情况:

+0 === -0            //true

NaN === NaN          // false

Object.is(+0, -0)    // false

Object.is(NaN, NaN)  // true

  • Object.assign() 用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),如果有同名属性,则后面的会直接替换前面的:

let target = { a: 1 }

let source1 = { a: 2, b: 3, d: {e: 1, f: 2} }

let source2 = { a: 3, c: 4, d: {g: 3} }

Object.assign(target, source1, source2)

target  // { a: 3, b: 3, c: 4, d: {g: 3} }

Object.assign() 实行的是浅拷贝,如果源对象某个属性是对象,那么拷贝的是这个对象的引用:

let target = {a: {b: 1}}

let source = {a: {b: 2}}

Object.assign(target, source)

target.a.b = 3

source.a.b  // 3

  • __proto__ 属性是用来读取和设置当前对象的原型,而由于其下划线更多的是表面其是一个内部属性,所以建议不在正式场合使用它,而是用下面的 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

  • Object.setPrototypeOf() 用于设置对象原型,Object.getPrototypeOf() 用于读取对象原型:

let person = {name: ‘布兰’}

Object.setPrototypeOf(person, {name: ‘动物’})

Object.getPrototypeOf(person)  // {name: ‘动物’}

正则扩展

  • RegExp 构造函数,允许首参为正则表达式,第二个参数为修饰符,如果有第二个参数,则修饰符以第二个为准:

let reg = new RegExp(/xYz\d+/gi, i)

reg.flags  // ‘i’

  • 正则方法调用变更:字符串对象的 match()replace()search()split() 内部调用转为调用 RegExp 实例对应的 RegExp.prototype[Symbol.方法]

  • u 修饰符:含义为 Unicode 模式,用来正确处理大于 \uFFFFUnicode 字符。也就是说,如果待匹配的字符串中可能包含有大于 \uFFFF 的字符,就必须加上 u 修饰符,才能正确处理。

// 加上 u 修饰符才能让 . 字符正确识别大于 \uFFFF 的字符

/^.$/.test(‘???’)   // false

/^.$/u.test(‘???’)  // true

// 大括号 Unicode 字符表示法必须加上 u 修饰符

/\u{61}/.test(‘a’)   // false

/\u{61}/u.test(‘a’)  // true

// 有 u 修饰符,量词才能正确匹配大于 \uFFFF 的字符

/???{2}/.test(‘???’)  // false

/???{2}/u.test(‘???’) // true

RegExp.prototype.unicode 属性表示正则是否设置了 u 修饰符:

/???/.unicode   // false

/???/u.unicode  // true

  • y 修饰符,与 g 修饰符类似也是全局匹配;不同的是 g 是剩余字符中匹配即可,而 y 则是必须在剩余的第一个字符开始匹配才行,所以 y 修饰符也叫黏连修饰符:

let s = ‘aaa_aa_a’

let r1 = /a+/g

let r2 = /a+/y

r1.exec(s)  // [“aaa”]

r2.exec(s)  // [“aaa”]

r1.exec(s)  // [“aa”]

r2.exec(s)  // null

RegExp.prototype.sticky 属性表示是否设置了 y 修饰符:

/abc/y.sticky  // true

  • RegExp.prototype.flags 属性会返回当前正则的所有修饰符:

/abc???/iuy.flags  // ‘iuy’

函数扩展

  • 函数参数默认值。参数不能有同名的,函数体内不能用 letconst 声明同参数名的变量:

function printInfo(name = ‘布兰’, age = 12) {}

使用参数默认值的时候,参数不能有同名的:

function f(x, x, y) {}      // 不报错

function f(x, x, y = 1) {}  // 报错

函数体内不能用 letconst 声明同参数名的变量:

// 报错

function f(x, y) {

let x = 0

}

函数的 length 属性会返回没有指定默认值的参数个数,且如果设置默认值的参数不是尾参数,则 length 不再计入后面的参数:

(function f(x, y){}).length      // 2

(function f(x, y = 1){}).length  // 1

(function f(x = 1, y){}).length  // 0

  • 剩余(rest) 参数(…变量名)的形式,用于获取函数的剩余参数,注意 rest 参数必须放在最后一个位置,可以很好的代替 arguments 对象:

function f(x, …y) {

console.log(x)  // 1

for (let val of y) {

coonsole.log(val)  // 2 3

}

}

f(1, 2, 3)

  • 严格模式:只要函数参数使用了默认值、解构赋值或者扩展运算符,那么函数体内就不能显示的设定为严格模式,因为严格模式的作用范围包含了函数参数,而函数执行的顺序是先执行参数,然后再执行函数体,执行到函数体里的 use strict 的时候,那么此时因为函数参数已经执行完成了,那函数参数还要不要受到严格模式的限制呢?这就出现矛盾了。规避限制的办法有两个:设置全局的严格模式或者在函数体外在包一个立即执行函数并且声明严格模式:

// 解法一

‘use strict’

function f(x, y = 2) {

}

// 解法二

let f = (function(){

‘use strict’

return function(x, y = 2) {}

})()

  • 箭头函数语法比函数表达式更简洁,并且没有自己的 thisarguments,不能用作构造函数和用作生成器。

几种箭头函数写法:

let f1 = () => {}               // 没有参数

let f2 = (x) => {}              // 1个参数

let f3 = x => {}                // 1个参数可以省略圆括号

let f4 = (x, y) => {}           // 2个参数以上必须加上圆括号

let f5 = (x = 1, y = 2) => {}   // 支持参数默认值

let f6 = (x, …y) => {}        // 支持 rest 参数

let f7 = ({x = 1, y = 2} = {})  // 支持参数解构

箭头函数没有自己的 this

function Person(){

this.age = 0

setInterval(() => {

this.age++

}, 1000)

}

var p = new Person()  // 1 秒后 Person {age: 1}

通过 call/apply 调用箭头函数的时候将不会绑定第一个参数的作用域:

let adder = {

base: 1,

add: function(a) {

let f = v => v + this.base

return f(a)

},

addThruCall: function(a) {

let f = v => v + this.base

let b = {

base: 2

}

return f.call(b, a)

}

}

adder.add(1)          // 输出 2

adder.addThruCall(1)  // 仍然输出 2

箭头函数没有自己的 arguments 对象,不过可以使用 rest 参数代替:

let log = () => {

console.log(arguments)  // ReferenceError

}

log(2, 3)

// 剩余参数代替写法

let restLog = (…arr) => {

console.log(arr)  // [2, 3]

}

restLog(2, 3)

箭头函数不能用作构造器,和 new 一起用会抛出错误:

let Foo = () => {}

let foo = new Foo()

// TypeError: Foo is not a constructor

箭头函数返回对象字面量,需要用圆括号包起来:

let func2 = () => ({foo: 1})

参考:Arrow_functions

  • 尾调用和尾递归

首先得知道什么是尾调用:函数的最后一步调用另外一个函数:

// 是尾调用

function f(x) {

return g(x)

}

// 以下都不是尾调用

function f(x) {

let y = g(x)

return y

}

function f(x) {

let y = g(x)

return g(x) + 1

}

function f(x) {

g(x)

// 因为最后一步是 return: undefined

}

尾调用有啥用?我们知道函数的相互调用是会生成“调用帧”的,而“调用帧”里存了各种信息比如函数的内部变量和调用函数的位置等,所有的“调用帧”组成了一个“调用栈”。如果在函数的最后一步操作调用了另外一个函数,因为外层函数里调用位置、内部变量等信息都不会再用到了,所有就无需保留外层函数的“调用帧”了,只要直接用内层函数的“调用帧”取代外层函数的“调用帧”即可:

function f() {

let m = 1

let n = 2

return g(m + n)

}

f()

// 等同于

function f() {

return g(3)

}

f()

// 等同于

g(3)

这样一来就很明显的减少了调用栈中的帧数,内存占用就少了,所以这就是尾调用的优化作用。尾递归也是如此,递归如果次数多那就需要保留非常多的“调用帧”,所以经常会出现栈溢出错误,而使用了尾递归优化后就不会发生栈溢出的错误了:

// 常规递归的斐波那契函数

function Fibonacci(n) {

if ( n <= 1 ) {return 1}

return Fibonacci(n - 1) + Fibonacci(n - 2)

}

Fibonacci(100) // 超时

// 尾递归优化后的斐波那契函数

function Fibonacci2(n, ac1 = 1, ac2 = 1) {

if( n <= 1 ) {return ac2}

return Fibonacci2(n - 1, ac2, ac1 + ac2)

}s

Fibonacci2(100)  // 573147844013817200000

Symbol

  • Symbol 是一个新的原始类型,用来表示一个独一无二的值,可以通过 Symbol() 函数来创建一个 Symbol 类型的值,为了加以区分,可以传入一个字符串作为其描述:

let s1 = Symbol(‘foo’)

let s2 = Symbol(‘foo’)

s1 === s2  // false

  • Symbol 类型无法通过数学运算符进行隐式类型转换,但是可以通过 String() 显示转成字符串或者通过 Boolean() 显示转成布尔值:

let s = Symbol(‘foo’)

String(s)     // “Symbol(‘foo’)”

s.toString()  // “Symbol(‘foo’)”

Boolean(s)    // true

  • 引入 Symbol 最大的初衷其实就是为了让它作为对象的属性名而使用,这样就可以有效避免属性名的冲突了:

let foo = Symbol(‘foo’)

let obj = {

foo: ‘foo2’

}

objfoo  // ‘foo1’

obj.foo   // ‘foo2’

  • Symbol 属性的不可枚举性,不会被 for...infor...ofObject.keys()Object.getOwnPropertyNames()JSON.stringify() 等枚举:

let person = {

name: ‘布兰’,

}

for (let x in person) {

console.log(x)  // ‘name’

}

Object.keys(person)  // [‘name’]

Object.getOwnPropertyNames(person)  // [‘name’]

JSON.stringify(person)  // ‘{“name”:“布兰”}’

但是可以通过 Object.getOwnPropertySymbols() 获取到对象的所有 Symbol 属性名,返回一个数组:

// 基于上面的代码

Object.getOwnPropertySymbols(person)  // [Symbol(age)]

静态方法

  • Symbol.for() 按照描述去全局查找 Symbol,找不到则在全局登记一个:

let s1 = Symbol.for(‘foo’)

let s2 = Symbol.for(‘foo’)

s1 === s2 // true

Symbol.for() 的这个全局登记特性,可以用在不同的 iframeservice worker 中取到同一个值。

  • Symbol.keyFor() 根据已经在全局登记的 Symbol 查找其描述:

let s = Symbol.for(‘foo’)

Symbol.keyFor(s)  // ‘foo’

Symbol 的内置值

  • Symbol.hasInstance:指向一个内部方法,当其他对象使用 instanceof 运算符判断是否为此对象的实例时会调用此方法;

  • Symbol.isConcatSpreadable:指向一个布尔,定义对象用于 Array.prototype.concat() 时是否可展开;

  • Symbol.species:指向一个构造函数,当实例对象使用自身构造函数时会调用指定的构造函数;

  • Symbol.match:指向一个函数,当实例对象被 String.prototype.match() 调用时会重新定义match()的行为;

  • Symbol.replace:指向一个函数,当实例对象被 String.prototype.replace() 调用时会重新定义 replace() 的行为;

  • Symbol.search:指向一个函数,当实例对象被 String.prototype.search() 调用时会重新定义 search() 的行为;s

  • Symbol.split:指向一个函数,当实例对象被 String.prototype.split() 调用时会重新定义 split() 的行为;

  • Symbol.iterator:指向一个默认遍历器方法,当实例对象执行for...of 时会调用指定的默认遍历器;

  • Symbol.toPrimitive:指向一个函数,当实例对象被转为原始类型的值时会返回此对象对应的原始类型值;

  • Symbol.toStringTag:指向一个函数,当实例对象被 Object.prototype.toString() 调用时其返回值会出现在 toString() 返回的字符串之中表示对象的类型;

  • Symbol.unscopables:指向一个对象,指定使用 with 时哪些属性会被 with 环境排除;

Set

  • Set 是一种新的数据结构,类似数组,但是它没有键只有值,且值都是唯一的。可以通过构造函数生成一个新实例,接收一个数组或者可迭代数据结构作为参数:

new Set([1, 2, 3])  // Set {1, 2, 3}

new Set(‘abc’)      // Set {‘a’, ‘b’, ‘c’}

  • Set 判断两个值是不是相等用的是 sameValueZero 算法,类似于 ===,唯一的区别是,在 SetNaN 之间被认为是相等的:

let set = new Set()

let a = NaN

let b = NaN

set.add(a)

set.add(b)

set.size  // 1

  • 相同对象的不同实例也被 Set 认为是不相等的:

let set = new Set()

let a = {a: 1}

let b = {a: 1}

set.add(a)

set.add(b)

set.size  // 2

  • Set 是有顺序的,将按照插入的顺序进行迭代,可以使用 for...of 迭代:

let set = new Set([1, 3])

set.add(5)

set.add(7)

for(let x of set) {

console.log(x)

}

Set 实例属性和方法

  • Set.prototype.constructor:构造函数,默认就是 Set 函数;

  • Set.prototype.size:返回 Set 实例的成员总数;

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身;

  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功;

  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员;

  • Set.prototype.clear():清除所有成员,没有返回值;

  • Set.prototype.keys():返回键名的遍历器;

  • Set.prototype.values():返回键值的遍历器;

  • Set.prototype.entries():返回键值对的遍历器;

  • Set.prototype.forEach():使用回调函数遍历每个成员;

let set = new Set([1, 3])

set.add(5)     // Set {1, 3, 5}

set.size       // 3

set.delete(1)  // true,1 已被删除

set.has(1)     // false

set.keys()     // SetIterator {3, 5}

set.clear()

set.size       // 0

Set 应用场景

  • 数组去重:

[…new Set([1, 3, 6, 3, 1])]         // [1, 3, 6]

Array.from(new Set([1, 3, 6, 3, 1]))  // [1, 3, 6]

  • 字符串去重:

[…new Set(‘abcbacd’)].join(‘’)  // ‘abcd’

  • 求两个集合的交集/并集/差集:

let a = new Set([1, 2, 3])

let b = new Set([4, 3, 2])

// 并集

let union = new Set([…a, …b])  // Set {1, 2, 3, 4}

// 交集

let intersect = new Set([…a].filter(x => b.has(x)))  // set {2, 3}

// (a 相对于 b 的)差集

let difference = new Set([…a].filter(x => !b.has(x)))  // Set {1}

  • 遍历修改集合成员的值:

let set = new Set([1, 2, 3])

// 方法一

let set1 = new Set([…set].map(val => val * 2))     // Set {2, 3, 6}

// 方法二

let set2 = new Set(Array.from(set, val => val * 2))  // Set {2, 4, 6}

WeakSet

  • WeakSet 对象允许将弱保持对象存储在一个集合中:

let ws = new WeakSet()

let foo = {}

ws.add(foo)  // WeakSet {{}}

ws.has(foo)  // true

ws.delete(foo)  // WeakSet {}

Set 的区别

  • WeakSet 只能是对象的集合,而不能是任何类型的任意值;

  • WeakSet 持弱引用:集合中对象的引用为弱引用。如果没有其他的对 WeakSet 中对象的引用,那么这些对象会被当成垃圾回收掉。这也意味着 WeakSet 中没有存储当前对象的列表。正因为这样,WeakSet 是不可枚举的,也就没有 size 属性,没有 clear 和遍历的方法。

实例方法

  • WeakSet.prototype.add(value):添加一个新元素 value

  • WeakSet.prototype.delete(value):从该 WeakSet 对象中删除 value 这个元素;

  • WeakSet.prototype.has(value):返回一个布尔值,  表示给定的值 value 是否存在于这个 WeakSet 中;

Map

  • Map 是一种类似于 Object 的这种键值对的数据结构,区别是对象的键只能是字符串或者 Symbol,而 Map 的键可以是任何类型(原始类型、对象或者函数),可以通过 Map 构造函数创建一个实例,入参是具有 Iterator 接口且每个成员都是一个双元素数组 [key, value] 的数据结构:

let map1 = new Map()

map1.set({}, ‘foo’)

let arr = [[‘name’, ‘布兰’], [‘age’, 12]]

let map2 = new Map(arr)

  • Map 中的键和 Set 里的值一样也必须是唯一的,遵循 sameValueZero 算法,对于同一个键后面插入的会覆盖前面的,

let map = new Map()

let foo = {foo: ‘foo’}

map.set(foo, ‘foo1’)

map.set(foo, ‘foo2’)

map.get(foo)  // ‘foo2’

  • 对于键名同为 NaN 以及相同对象而不同实例的处理同 Set 的值一样:

let a = NaN

let b = NaN

let map = new Map()

map.set(a, ‘a’)

map.set(b, ‘b’)

map.size    // 1

map.get(a)  // ‘b’

let c = {c: ‘c’}

let d = {c: ‘c’}

map.set(c, ‘c’)

map.set(d, ‘d’)

map.size    // 3

map.get©  // ‘c’

实例属性和方法

  • Map.prototype.size:返回 Map 对象的键值对数量;

  • Map.prototype.set(key, value):设置 Map 对象中键的值。返回该 Map 对象;

  • Map.prototype.get(key):返回键对应的值,如果不存在,则返回 undefined

  • Map.prototype.has(key):返回一个布尔值,表示 Map 实例是否包含键对应的值;

  • Map.prototype.delete(key):如果 Map 对象中存在该元素,则移除它并返回 true

  • Map.prototype.clear():移除 Map 对象的所有键/值对;

  • Map.prototype.keys():返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键;

  • Map.prototype.values():返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值;

  • Map.prototype.entries():返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组;

  • Map.prototype.forEach(callbackFn[, thisArg]):按插入顺序遍历 Map

let map = new Map()

map.set({a: 1}, ‘a’)

map.set({a: 2}, ‘b’)

for (let [key, value] of map) {

console.log(key, value)

}

// {a: 1} ‘a’

// {a: 2} ‘b’

for (let key of map.keys()) {

console.log(key)

}

// {a: 1}

// {a: 2}

WeakMap

  • 类似于 Map 的结构,但是键必须是对象的弱引用,注意弱引用的是键名而不是键值,因而 WeakMap 是不能被迭代的;

let wm = new WeakMap()

let foo = {name: ‘foo’}

wm.set(foo, ‘a’)  // Weak

wm.get(foo)       // ‘a’

wm.has(foo)       // true

虽然 wm 的键对 foo 对象有引用,但是丝毫不会阻止 foo 对象被 GC 回收。当引用对象 foo 被垃圾回收之后,wmfoo 键值对也会自动移除,所以不用手动删除引用。

实例方法

  • WeakMap.prototype.delete(key):移除 key 的关联对象;

  • WeakMap.prototype.get(key):返回key关联对象, 或者 undefined(没有key关联对象时);

  • WeakMap.prototype.has(key):根据是否有 key 关联对象返回一个 Boolean 值;

  • WeakMap.prototype.set(key, value):在 WeakMap 中设置一组 key 关联对象,返回这个 WeakMap 对象;

Proxy

  • Proxy 用来定义基本操作的的自定义行为,可以理解为当对目标对象 target 进行某个操作之前会先进行拦截(执行 handler 里定义的方法),必须要对 Proxy 实例进行操作才能触发拦截,对目标对象操作是不会拦截的,可以通过如下方式定义一个代理实例

let proxy = new Proxy(target, handler)

let instance = new Proxy({name: ‘布兰’}, {

get(target, propKey, receiver) {

return hello, ${target.name}

},

})

instance.name  // ‘hello, 布兰’

  • 如果 handle 没有设置任何拦截,那么对实例的操作就会转发到目标对象身上:

let target = {}

let proxy = new Proxy(target, {})

proxy.name = ‘布兰’

target.name  // ‘布兰’

  • 目标对象被 Proxy 代理的时候,内部的 this 会指向代理的实例:

const target = {

m: function () {

console.log(this === proxy)

}

}

const handler = {}

const proxy = new Proxy(target, handler)

target.m()  // false

proxy.m()   // true

静态方法

  • Proxy.revocable() 用以定义一个可撤销的 Proxy

let target = {}

let handler = {}

let {proxy, revoke} = Proxy.revocable(target, handler)

proxy.foo = 123

proxy.foo  // 123

revoke()

proxy.foo  // TypeError

handle 对象的方法

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如 proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

  • has(target, propKey):拦截 propKey in proxy 的操作,返回一个布尔值。deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作,返回一个布尔值。

  • ownKeys(target):拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

  • defineProperty(target, propKey, propDesc):拦截 Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。

  • preventExtensions(target):拦截 Object.preventExtensions(proxy),返回一个布尔值。

  • getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy),返回一个对象。

  • isExtensible(target):拦截 Object.isExtensible(proxy),返回一个布尔值。

  • setPrototypeOf(target, proto):拦截 Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)

  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args)

Reflect

  • Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

  • 设计的目的:

静态方法

  • Reflect.apply(target, thisArgument, argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似;

  • Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行 new 操作,相当于执行 new target(...args)

  • Reflect.defineProperty(target, propertyKey, attributes)Object.defineProperty() 类似。如果设置成功就会返回 true

  • Reflect.deleteProperty(target, propertyKey) 作为函数的 delete 操作符,相当于执行 delete target[name]

  • Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于 target[name]

  • Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,  否则返回 undefined

  • Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()

  • Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同;

  • Reflect.isExtensible(target) 类似于 Object.isExtensible()

  • Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受 enumerable 影响);

  • Reflect.preventExtensions(target) 类似于 Object.preventExtensions()。返回一个 Boolean

  • Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true

  • Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回 true

Class

  • 可以用 class 关键字来定义一个类,类是对一类具有共同特征的事物的抽象,就比如可以把小狗定义为一个类,小狗有名字会叫也会跳;类是特殊的函数,就像函数定义的时候有函数声明和函数表达式一样,类的定义也有类声明和类表达式,不过类声明不同于函数声明,它是无法提升的;类也有 name 属性

// 类声明

class Dog {

constructor(name) {

this.name = name

}

bark() {}

jump() {}

}

Dog.name  // ‘Dog’

// 类表达式:可以命名(类的 name 属性取类名),也可以不命名(类的 name 属性取变量名)

let Animal2 = class {

// xxx

}

Animal2.name  // ‘Animal2’

  • JS 中的类建立在原型的基础上(通过函数来模拟类,其实类就是构造函数的语法糖),和 ES5 中构造函数类似,但是也有区别,比如类的内部方法是不可被迭代的:

class Dog {

constructor() {}

bark() {}

jump() {}

}

Object.keys(Dog.prototype)  // []

// 类似于

function Dog2(){

}

Dog2.prototype = {

constructor() {},

bark() {},

jump() {},

}

Object.keys(Dog2.prototype)  // [‘constructor’, ‘bark’, ‘jump’]

  • 基于原型给类添加新方法:

Object.assign(Dog.prototype, {

wag() {}  // 摇尾巴

})

  • 类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,gettersetter 都在严格模式下执行。

  • 类内部的 this 默认指向类实例,所以如果直接调用原型方法或者静态方法会导致 this 指向运行时的环境,而类内部是严格模式,所以此时的 this 会是 undefined

class Dog {

constructor(name) {

this.name = name

}

bark() {

console.log( ${this.name} is bark. )

}

static jump() {

console.log( ${this.name} is jump. )

}

}

let dog = new Dog(‘大黄’)

let { bark } = dog

let { jump } = Dog

bark()  // TypeError: Cannot read property ‘name’ of undefined

jump()  // TypeError: Cannot read property ‘name’ of undefined

方法和关键字

  • constructor 方法是类的默认方法,通过 new 关键字生成实例的时候,会自动调用;一个类必须有constructor 方法,如果没有显示定义,则会自动添加一个空的;constructor 默认会返回实例对象:

class Point {}

// 等同于

class Point {

constructor() {}

}

  • 通过 getset 关键字拦截某个属性的读写操作:

class Dog {

get age(){

return 1

}

set age(val){

this.age = val

}

}

  • static 关键字给类定义静态方法,静态方法不会存在类的原型上,所以不能通过类实例调用,只能通过类名来调用,静态方法和原型方法可以同名:

class Dog {

bark() {}

jump() {

console.log(‘原型方法’)

}

static jump() {

console.log(‘静态方法’)

}

}

Object.getOwnPropertyNames(Dog.prototype)  // [‘constructor’, ‘bark’, ‘jump’]

Dog.jump()  // ‘静态方法’

let dog = new Dog()

dog.jump()  // ‘原型方法’

  • 公有字段和私有字段:

静态公有字段和静态方法一样只能通过类名调用;私有属性和私有方法只能在类的内部调用,外部调用将报错:

class Dog {

age = 12                   // 公有字段

static sex = ‘male’        // 静态公有字段

#secret = ‘我是人类的好朋友’  // 私有字段

#getSecret() {              // 私有方法

return this.#secret

}

}

Dog.sex  // ‘male’

let dog = new Dog()

dog.#getSecret()  // SyntaxError

公共和私有字段声明是 JavaScript 标准委员会 TC39 提出的实验性功能(第 3 阶段)。浏览器中的支持是有限的,但是可以通过 Babel 等系统构建后使用此功能。

  • new.target 属性允许你检测函数、构造方法或者类是否是通过 new 运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined,子类继承父类的时候会返回子类:

class Dog {

constructor() {

console.log(new.target.name)

}

}

function fn(){

if (!new.target) return ‘new target is undefined’

console.log(‘fn is called by new’)

}

let dog = new Dog()  // ‘Dog’

fn()                 // ‘new target is undefined’

new fn()             // ‘fn is called by new’

类的继承

  • 类可以通过 extends 关键字实现继承,如果子类显示的定义了 constructor 则必须在内部调用 super() 方法,内部的 this 指向当前子类:

class Animal {

constructor(name) {

this.name = name

}

run() {

console.log(${this.name} is running.)

}

}

class Dog extends Animal{

constructor(name){

super(name)  // 必须调用

this.name = name

}

bark() {

console.log(${this.name} is barking.)

}

}

let dog = new Dog(‘大黄’)

dog.run()  // ‘大黄 is running.’

  • 通过 super() 调用父类的构造函数或者通过 super 调用父类的原型方法;另外也可以在子类的静态方法里通过 super 调用父类的静态方法:

// 基于上面的代码改造

class Dog extends Animal{

constructor(name){

super(name)  // 调用父类构造函数

this.name = name

}

bark() {

super.run()  // 调用父类原型方法

console.log(${this.name} is barking.)

}

}

let dog = new Dog()

dog.bark()s

// ‘大黄 is running.’

// ‘大黄 is barking.’

  • 子类的 __proto__ 属性,表示构造函数的继承,总是指向父类;子类 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的prototype属性:

class Animal {}

class Dog extends Animal {}

Dog.proto === Animal  // true

Dog.prototype.proto === Animal.prototype  // true

子类原型的原型指向父类的原型:

// 基于上面的代码

let animal = new Animal()

let dog = new Dog()

dog.proto.proto  === animal.proto  // true

  • 使用 extends 还可以实现继承原生的构造函数,如下这些构造函数都可以被继承:

class MyString extends String {

constructor(name){

super(name)

this.name = name

}

welcome() {

return hello ${this.name}

}

}

let ms = new MyString(‘布兰’)

ms.welcome()      // ‘hello 布兰’

ms.length         // 2

ms.indexOf(‘兰’)  // 1

Module

  • 浏览器传统加载模块方式:

// 同步加载

// defer异步加载:顺序执行,文档解析完成后执行;

// async异步加载:乱序加载,下载完就执行。

  • 浏览器现在可以按照模块(加上 type="module")来加载脚本,默认将按照 defer 的方式异步加载;ES6 的模块加载依赖于 importexport 这 2 个命令;模块内部自动采用严格模式:

// 模块加载

  • export 用于输出模块的对外接口,一个模块内只能允许一个 export default 存在,以下是几种输出模块接口的写法:

// person.js

// 写法一:单独导出

export const name = ‘布兰’

export const age = 12

// 写法二:按需导出

const name = ‘布兰’, age = 12

export { name, age }

// 写法三:重命名后导出

const name = ‘布兰’, age = 12

export { name as name1, age as age1 }

// 写法四:默认导出

const name = ‘布兰’

export default name

  • import 用于输入其他模块的接口:

// 按需导入

import { name, age } ‘./person.js’

// 导入后重命名

import { name1 as name, age1 as age } from ‘./person.js’

// 默认导入

import person from ‘./person.js’

// 整体导入

import * as person from ‘./person.js’

// 混合导入

import _, { each } from ‘lodash’

import 导入的细节:

  • importexport 的复合写法:exportimport 语句可以结合在一起写成一行,相当于是在当前模块直接转发外部模块的接口,复合写法也支持用 as 重命名。以下例子中需要在 hub.js 模块中转发 person.js 的接口:

// person.js

const name = ‘布兰’, age = 12

export { name, age }

// 按需转发接口(中转模块:hub.js)

export { name, age } from ‘./person.js’

// 相当于

import { name, age } from ‘./person.js’

export { name, age }

// person.js

const name = ‘布兰’, age = 12

export default { name, age }

// 转发默认接口(中转模块:hub.js)

export { default } from ‘./person.js’

// 相当于

import person from ‘./person.js’

export default person

  • ES6 模块和 CommonJS 模块的差异:

Iterator和for…of

  • Iterator 迭代器协议,为各种数据结构提供了一种统一按照某种顺序进行访问的机制。通常部署在一个可迭代数据结构内部或其原型上。一个对象要能够成为迭代器,它必须有一个 next() 方法,每次执行 next() 方法会返回一个对象,这个对象包含了一个 done 属性(是个布尔值,true 表示可以继续下次迭代)和一个 value 属性(每次迭代的值):

// 生成一个迭代器

let makeIterator = (arr) => {

let index = 0

return {

next(){

return index < arr.length ? {

value: arr[index++],

done: false

} : { done: true }

}

}

}

  • iterable 可迭代数据结构:内部或者原型上必须有一个 Symbol.iterator 属性(如果是异步的则是 Symbol.asyncIterator),这个属性是一个函数,执行后会生成一个迭代器:

let obj = {

Symbol.iterator {

return {

index: 0,

next() {

if (this.index < 3) {

return {value: this.index++, done: false}

} else {

return {done: true}

}

}

}

}

}

for (let x of obj) {

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值