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

// 简写

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

// 改名且设置默认值

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

console.log(name1, age1)  // ‘布兰’ 12

  • 函数参数解构:其实就是运用上面的对象解构和数组解构规则;

function move({x = 0, y = 0} = {}) {

console.log([x, y])

return [x, y];

}

move({x: 3, y: 8})  // [3, 8]

move({x: 3})        // [3, 0]

move({})            // [0, 0]

move()              // [0, 0]

解构要点

  • 只要等号两边的模式相同(同是对象或同是数组),则左边的变量会被赋予对应的值;

  • 解构不成功的变量值为 undefined

  • 默认值生效的前提是当等号右边对应的值全等于 undefined 的时候;

  • 只要等号右边的值不是对象或者数组,则会进行自动装箱将其转成对象;

  • nullundefined 都无法转成对象,所以无法解构。

解构应用

  • 交换变量的值;

let x = 1, y = 2;

[x, y] = [y, x]

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

  • 通过函数返回对象属性

function getParams() {

return {

name: ‘布兰’,

age: 12,

}

}

let {name, age} = getParams()

  • 通过定义函数参数来声明变量

let person = {

name: ‘布兰’,

age: 12

}

init(person)

// 普通用法

function init(person) {

let {name, age} = person

}

// 更简洁用法

function init({name, age}) {}

  • 指定函数参数默认值

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

console.log(name, age)

}

initPerson()  // ‘布兰’ 12

initPerson({age: 20})  // ‘布兰’ 20

  • 提取 JSON 数据

let responseData = {

code: 1000,

data: {},

message: ‘success’

}

let { code, data = {} } = responseData

  • 遍历 Map 结构

let map = new Map()

map.set(‘beijing’, ‘北京’)

map.set(‘xiamen’, ‘厦门’)

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

console.log(key, value)

}

  • 输入模块的指定方法和属性

const { readFile, writeFile } = require(“fs”)

字符串扩展

  • 可以使用 Unicode 编码来表示一个字符:

// 以下写法都可以用来表示字符 z

‘\z’      // 转义

‘\172’    // 十进制表示法

‘\x7A’    // 十六进制表示法

‘\u007A’  // Unicode 普通表示法

‘\u{7A}’  // Unicode 大括号表示法

www.52unicode.com 这个网站可以查询到常见符号的 Unicode 编码。

  • 可以使用 for...of 正确遍历字符串:

let str = ‘???’

for (const emoji of str) {

console.log(emoji) // ???

}

for(let i = 0, l = str.length; i < l; i++) {

console.log(str[i])  // 不能正确输出表情

}

  • 模板字符串使用两个反引号标识(``),可以用来定义多行字符串,或者使用它在字符串中插入变量:

let name = ‘hero’

let tips = `Hello ${name},

welcome to my world.`

alert( tips )

  • 标签模板:在函数名后面接一个模板字符串相当于给函数传入了参数进行调用:

let name = ‘布兰’, age = 12;

let tips = parseHello ${name}, are you ${age} years old this year?

function parse(stringArr, …variable) {

}

// 相当于传递如下参数进行调用 parse 函数

parse(["Hello ", ", are you “, " years old this year?”], name, age)

  • String.fromCodePoint() 用于从 Unicode 码点返回对应字符,可以支持 0xFFFF 的码点:

String.fromCharCode(0x1f600)   // “”

String.fromCodePoint(0x1f600)  // “???”

  • String.raw() 返回把字符串所有变量替换且对斜杠进行转义的结果:

String.rawHi\n${2+3}!  // “Hi\n5!”

  • codePointAt() 返回字符的十进制码点,对于 Unicode 大于 0xFFFF 的字符,会被认为是2个字符,十进制码点转成十六进制可以使用 toString(16)

let emoji = ‘???’

emoji.length  // 2

emoji.charCodeAt(0).toString(16)  // ‘d83d’

emoji.charCodeAt(1).toString(16)  // ‘de00’

String.fromCodePoint(0xd83d, 0xde00) === ‘???’  // true

  • normalize() 方法会按照指定的一种 Unicode 正规形式将当前字符串正规化。(如果该值不是字符串,则首先将其转换为一个字符串):

let str1 = ‘\u00F1’

let str2 = ‘\u006E\u0303’

str1  //  ñ

str2  //  ñ

str1 === str2  // false

str1.length === str2.length  // false

str1.normalize() === str2.normalize()  // true

  • 字符串是否包含子串:

let s = ‘Hello world!’

s.includes(‘o’)        // true

s.startsWith(‘Hello’)  // true

s.endsWith(‘!’)        // true

这三个方法都支持第二个参数,表示开始搜索的位置:

let s = ‘Hello world!’

s.includes(‘Hello’, 6)   // false

s.startsWith(‘world’, 6) // true

s.endsWith(‘Hello’, 5)   // true

上面代码表示,使用第二个参数 n 时,endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。

  • repeat(n) 将当前字符串重复 n 次后,返回一个新字符串:

‘x’.repeat(2)         // ‘xx’

‘x’.repeat(1.9)       // ‘x’

‘x’.repeat(NaN)       // ‘’

‘x’.repeat(undefined) // ‘’

‘x’.repeat(‘2a’)      // ‘’

‘x’.repeat(-0.6)      // ‘’,解释:0 ~ 1 之间的小数相当于 0

‘x’.repeat(-2)        // RangeError

‘x’.repeat(Infinity)  // RangeError

数值扩展

  • 二进制(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() {}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
返回一个布尔值。

  • 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() {}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ZNz2QMFd-1715799771252)]

[外链图片转存中…(img-uX8Xi8SG-1715799771253)]

[外链图片转存中…(img-Y9dNsSsM-1715799771253)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值