ES6、ESNext
ES6 及以后新增的常用 API 解析
let 和 const
先来一道经典的面试题
for (var i=0; i<=3; i++) {
setTimeout(() => {
console.log(i)
}, 10)
}
// 4
// 4
// 4
// 4
为什么?
- var 声明变量会被提升至当前上下文的最前面,这里是全局,可以被重复定义,所以四次是同一个变量的赋值
- for 循环同步代码在第一轮时间循环执行完成,而 setTimeout 在 10 毫秒后的下一轮宏任务中执行。此时全局变量 i 的值已经变为了 4。所以会打印 4 个 4.
变量提升:变量的声明无论出现在代码的何处都会被提升至当前上下文的最前面,如果在函数内会被提升至函数头部,不在则会被提升至全局头部
如何正确打印?
- 把
var
改成let
let 引入了块级作用域,只能先声明后使用,而且作用域仅在所属的代码块中。
- 或者使用立即执行函数创建局部作用域
for (var i=0; i<=3; i++) {
(function(i) {
setTimeout(() => {
console.log(i)
}, 10)
})(i)
}
箭头函数
- 箭头函数的 this 指向是在定义的时候决定的,普通函数的 this 指向 是在使用的时候决定的
- 箭头函数不能被用做构造函数
构造函数:在 new 的时候改变 this 的指向到新实例
箭头函数:this 指向在定义的时候就决定了,不会随运行时改变
这两者是相悖的,所以箭头函数不能作为构造函数,否则会报错
Uncaught TypeError: x is not a constructor
拓展: new constructor()的时候会发生什么?
- 生成一个实例对象
- 将构造函数内
this
指向这个对象- 执行构造函数内的代码进行初始化(主要是实例对象的属性)
- 返回这个对象
class
class A {
_name = ''
constructor(name) {
this.name = name
}
get name () {
return this._name
}
set name (val) {
console.log('属性赋值:', val)
this._name = val
}
static getClassName() {
return A.name
}
}
const a = new A('shaoshuai')
console.log(a.name)
console.log(A.getClassName())
模板字符串
编写一个render
函数来实现template
的功能
const template = '${year}-${month}-${day}'
const context = {
year: '2021',
month: '12',
day: '26'
}
function render (template) {
return (context) => {
return template.replace(/\$\{(.*?)\}/g, (match, p1) => {
console.log(match) // ${year}
console.log(p1) // year
return context[p1]
})
}
}
const result = render(template)(context)
console.log(result) // '2021-12-26'
解构
数组的解构
对象的解构
解构的原理
解构是 ES6 提供的语法糖,原理是利用可迭代对象的 Iterator 接口,通过遍历迭代器对变量依次赋值。
3.1 Iterator 是什么?
Iterator 为不同的数据结构提供统一的访问机制
迭代协议
迭代协议有两种:可迭代协议和迭代器协议
-
可迭代协议允许 JavaScript 对象定制他们的迭代行为,也就是说实现了可迭代协议的对象就能被类似
for...of
的迭代行为访问.实现了可迭代协议的对象被称为可迭代对象,可迭代对象必须实现
@@iterator
方法。这意味着对象或他的原型链上的某个对象必须有一个键为@@iterator
属性,可通过常量Symbol.iterator
访问该属性该属性值应该为一个无参数的函数,其返回值为一个符合迭代器协议的对象
迭代器协议是实现了
next
方法的对象,该方法返回一个拥有value
和done
两个属性的对象
const myIterator = {
data: ['a', 'b', 'c'],
curIndex: 0,
next () {
const i = this.curIndex++
return i < this.data.length ? {
value: this.data[i],
done: false
} : {
value: undefined,
done: true
}
//...
},
[Symbol.iterator] () {
return this
}
}
for (let x of myIterator) {
console.log(x)
}
生成器对象既是迭代器,也是可迭代对象
const generatorObject = (function* () {
yield* [1, 2, 3]
})()
const iteratorNext = generatorObject.next
const iterableFun = generatorObject[Symbol.iterator]
console.log(typeof iterableFun)
// function 因为有实现 @@iterator 方法,所以是可迭代对象
console.log(typeof iteratorNext)
// function 因为有 next 方法所以是迭代器
console.log(generatorObject[Symbol.iterator]() === generatorObject)
// true 因为 @@iterator 方法返回迭代器自身,所以这是一个格式良好的可迭代对象
console.log([...generatorObject])
// [1, 2, 3]
循环
-
for in
- 不仅会遍历当前对象,还会遍历原型链上的属性
const obj = { a: 1 } Object.prototype.b = 2; for (let key in obj) { console.log(key) } // a // b for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log(key) } } // a
- 不仅会遍历当前对象,还会遍历原型链上的属性
for of
Object
- Object.keys()。拿到的是自有属性集
手写一个 Object.keys
const obj = {
a: 1,
b: 2,
c: 3
}
function getObjKeys(obj) {
const result = []
for (let key in obj) {
if (obj.hasOwnProperty(key)) result.push(key)
}
return result
}
console.log(getObjKeys(obj))
Object.values
Object.entries
Object.getOwnPropertyNames
Object.getOwnPropertyDescriptor
获取一个对象属性的描述符,是一个对象
configurable
writable
enumerable
get/set
const obj = {}
value = undefined
Object.defineProperty(obj, 'a', {
configurable: true,
enumerable: true,
get () {
return value
},
set (val) {
value = val
}
})
obj.a = 2
console.log(obj)
Proxybj
const obj = new Proxy({}, {
get (target, propKey) {
console.log('getter', target, propKey)
return Reflect.get(target, propKey)
},
set (target, propKey, value) {
console.log('setter', target, propKey, value)
return Reflect.set(target, propKey, value)
},
})
obj.a = 1
console.log('out', obj, obj.a, typeof obj)
Promise
function PromiseAllSettled(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
return reject('params not array')
}
const result = []
let cnt = 0
for (let i=0; i {
result[i] = {
status: 'fulfilled',
value
}
},
(reason) => {
result[i] = {
status: 'rejected',
reason
}
}
).finally(() => {
cnt++
if (cnt === arr.length) {
resolve(result)
}
})
}
})
}
Array
Array.prototype.flat()
Array.prototype.includes
Array.from
把一个类数组转换成真数组
const arr = [1,2,3]
const result = Array.from(arr, x => x + 1)
console.log(result)
如何把 arguments 转换成真数组
- [...arguments]
- Array.from(arguments)
- Array.prototype.map.call(arguments)