前言:学习ES新语法的记录。
1. let与const
let与var作用类似,声明变量用的,而const则是声明常量用的。
1.1 没有变量提升
使用var声明变量的时候,会有变量提升。
console.log(name) // 小明
var name = '小明'
即使在声明变量之前使用了变量,也不会有问题。
而let和const不会有提升,在声明前使用则会报错。
console.log(name) // 报错 name is not defined
let name = '小明'
console.log(age) // 报错 age is not defined
const age = 10
1.2 块级作用域内可用
let和const声明的变量只在其声明的块或者字块中可用。
{
var name = '小明'
let age = 10
const gender = '男'
console.log(age) // 10
}
console.log(name) // 小明
console.log(gender) // 报错 age is not defined
1.3 不允许重复声明
let name = '小明'
let name = '小智' // 报错 Identifier 'name' has already been declared
const name = '小明'
const name = '小智' // 报错 同上
1.4 初始值的不同
var和let初始值可不给值,const必须给定一个值。
var name
let age
const gender // 报错 Missing initializer in const declaration
1.5 修改值的不同
使用let和const定义变量的时候,若定义的值为简单类型,则let与var类似,可以随意地修改值。
let name = '小明'
name = []
console.log(name) // []
const age = 10
age = 11 // 报错 Assignment to constant variable
console.log(age)
可以看到const定义的变量修改之后报错了,是不允许修改的。
但如果定义的是一个复杂类型,是可以修改其中的属性值的。
// 定义数组
const arr = [1, 2, 3]
arr.push(4)
console.log(arr) // [1, 2, 3, 4]
arr[4] = 88
console.log(arr) // [1, 2, 3, 4, 88]
// 定义对象
const obj = {
name: '小明'
}
obj.age = 10
console.log(obj) // { name: '小明', age: 10 }
2. 模板字符串
2.1 基本用法
包裹在两个 ` 中间即可使用模板字符串。英文输入法下,按键盘上面的一排数字键的1的左边的按键就可以打出。
let name = `我是,小明`
// 在模板字符串中,换行,空格等都会被保留
let list = `
<ul>
<li>1 </li>
<li>12</li>
</ul>
`
2.2 拼接变量
以前拼接字符串都是要用上引号和加号,稍不留神就拼错了,找错误还得半天,现在可以直接在模板字符串中用${}包裹变量的方式添加变量来拼接。
let name = '小明',
age = 10
// 普通拼接
let str = '你好,我的名字是' + name + ',今年' + age + '岁了。'
// 使用模板字符串
let str2 = `你好,我的名字是${name},今年${age}岁了。`
2.3 使用函数
在模板字符串中添加函数也是可以的。
let dom = `
<ul>
${
lists.map(item => {
return `<li>${item.name}</li>`
}).join('')
}
</ul>
`
3. 箭头函数
3.1 基本语法
let func = (x, y)=> {
return x + y
}
翻译成普通函数就是
let func = function (x, y) {
return x + y
}
在箭头函数里,如果参数为一个,可以省略括号,如果只有一条执行语句,可以省略花括号和return。
如:
let func = x => x
3.2 与普通函数的区别
3.2.1 this指向
箭头函数是没有自己this的。
可以回顾下普通函数的this是如何确定的:
- 如何函数时一个构造函数,那么this指向实例对象
- 全局函数的this指向Window,严格模式下this指向undefined
- 如果是一个对象中的方法,则this指向该对象 箭头函数的this:
来自MDN:箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。详解
作用域值的是函数内部,箭头函数的this指向定义该函数时所在作用域指向的对象。
// 构造函数中有一个定时器,定时器的this指向Window
function Person() {
setTimeout(function () {
console.log(this) // Window
}, 1000)
}
// 定时器的this指向Window,箭头函数的this指向外层作用域
function Person() {
setTimeout(() => {
console.log(this) // Person {}
}, 1000)
}
换个例子 aFunc的this指向obj,obj不是函数,所以bFunc的this其实是指向Window的。
let obj = {
count: 1,
aFunc: function () {
console.log(this, this.count)
},
bFunc: () => {
console.log(this, this.count)
}
}
obj.aFunc() // obj {}, 1
obj.bFunc() // Window, undefined
上面的例子改造一下
var count = 0
let obj = {
count: 1,
aFunc: function () {
console.log(this, this.count)
},
bFunc: () => {
console.log(this, this.count)
}
}
let obj2 = {
count: 2
}
obj.aFunc.call(obj2) // obj2 {}, 2
obj.bFunc.call(obj2) // Window, 0
可以看到的是箭头函数的this无法被call改变,同理,apply,bind也是无效的。
3.2.2 没有arguments参数
在普通函数中,可以通过arguments对象获取参数列表,但是在箭头函数中,没有自己的此参数。
function aFunc() {
console.log(arguments)
}
aFunc(1, 2, 3)
let bFunc = () => {
console.log(arguments) // arguments is not defined
}
bFunc(1, 2, 3)
为什么说是没有自己的arguments呢,改造一下上面的bFunc。
let bFunc = function () {
return () => {
console.log(arguments)
}
}
bFunc(1, 2, 3)() // [1, 2, 3]
可以看到箭头函数中可以拿到父作用域的arguments。
那这样很不方便啊,有时参数不定,该怎么办呢。
对于这样的情况可以使用reset参数,在传参的时候使用(...参数)的形式,一样可以拿到参数列表。
let cFunc = (...args) => {
console.log(args)
}
cFunc(1, 2, 3) // [1, 2, 3]
3.2.3 不能作为构造函数来使用
function Person(name) {
this.name = name
}
let people = new Person('小明')
console.log(people.name) // 小明
let Animal = (name) => {
this.name = name
}
let cat = new Animal('猫') // 报错 Animal is not a constructor
为什么不能使用new关键字呢,可以回顾下new出实例的步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this指向新对象)
- 执行构造函数中的代码
- 返回此对象 原因:在过程中
(1)会将构造函数的prototype赋给新对象的__proto__属性,但是构造函数没有prototype。
let Animal = (name) => {
this.name = name
}
console.log(Animal.prototype) // undefined
(2)通过call将this指向新对象,而call对箭头函数无效。
4. Symbol
Symbol是在ES6引入的一种新的基本数据类型,表示独一无二的值。
4.1 定义Symbol
let s = Symbol()
// 也可以传入一个字符串作为描述
let s1 = Symbol('s1')
console.log(s) // Symbol()
console.log(s1) // Symbol(s1)
console.log(typeof s) symbol
因为是一个独一无二的值,即使传入的描述字符串相同,也是不同的。
let sym1 = Symbol('s')
let sym2 = Symbol('s')
console.log(sym1 === sym2) // false
4.2 描述参数
描述参数也可以传一个对象。
let obj = {
name: '小明'
}
let arr = [1, 2, 3]
let s = Symbol(obj)
let s1 = Symbol(arr)
console.log(s) // Symbol([object Object])
console.log(s1) // Symbol(1,2,3)
当描述参数是一个对象的时候,会先调用对象的toString方法,然后将返回的值当做参数,生成Symbol。
let obj = {
toString: function () {
return '小明'
}
}
let s = Symbol(obj)
console.log(s) // Symbol(小明)
4.3 不能进行运算
let s = Symbol('s')
console.log(s + 10) // 报错 Cannot convert a Symbol value to a number
console.log('Symbol是:' + s) // 报错 Cannot convert a Symbol value to a string
console.log(`Symbol值是:${s}`) // 报错 Cannot convert a Symbol value to a string
4.4 可以转化为字符串
let s = Symbol('s')
console.log(String(s)) // 'Symbol(s)'
console.log(s.toString()) // 'Symbol(s)'
转化为字符串后,相同描述参数的Symbol就相同了。
4.5 JSON.stringify会被忽略
当作为对象的属性值的时候,使用stringify的时候会被忽略。
let s = Symbol('s')
let obj = {
name: '小明',
sym: s
}
console.log(JSON.stringify(obj)) // {"name":"小明"}
4.6 可作为对象属性名出现
因为Symbol是一个独一无二的值,所以当它作为一个对象的属性名的时候,可以确保不会出现相同名字的属性。
let s = Symbol('s')
let obj = {
[s]: '你好'
}
console.log(obj) // {Symbol(s): "你好"}
但是它不会被 Object.getOwnPropertyNames,for...in,for...of,Object.keys捕获。
这时可以使用另一个方法:Object.getOwnPropertySymbols 获取对象中用Symbol作为key的属性名。
let s = Symbol('s')
let s1 = Symbol('s1')
let obj = {
name: '小明',
age: 10,
[s]: 's',
[s1]: 's1',
}
let propNames = Object.getOwnPropertyNames(obj)
let propSymbolNames = Object.getOwnPropertySymbols(obj)
console.log(propNames) // ["name", "age"]
console.log(propSymbolNames) // [Symbol(s), Symbol(s1)]
4.7 Symbol.for 和 Symbol.keyFor
来自MDN:要创建跨文件可用的symbol,甚至跨域(每个都有它自己的全局作用域) , 使用
Symbol.for()方法和Symbol.keyFor()方法从全局的symbol注册表设置和取得symbol。
使用Symbol.for创建一个Symbol,此方法创建的Symbol会被放在一个全局的Symbol注册表中,如何再次创建一个新的Symbol,参数与之前的相同,变回返回这个Symbol,而不会新建。值得一提的是,这个Symbol跨域都可以共享。
let s = Symbol.for('s')
// 会返回已经存在的Symbol
console.log(s === Symbol.for('s')) // true
可以通过symbol.keyFor去取通过Symbol.for创建的Symbol的描述参数。不是通过这种方式创建的Symbol无法获取。
let s = Symbol('s')
console.log(Symbol.keyFor(s)) // undefined
let s1 = Symbol.for('s1')
console.log(Symbol.keyFor(s1)) // s1
5. BigInt
BigInt是新增的一种基本类型数据。原先的Number类型,它可以表示的最大安全整数就是2^53 - 1,即9007199254740991。而BigInit可以用于表示大于2^53 - 1的整数。
5.1 定义BIgInt
let b1 = 10n
let b2 = BigInt(20)
console.log(b1, b2) // 10n 20n
console.log(typeof b1, typeof b2) // bigint bigint
来自MDN:它在某些方面类似于
Number,但是也有几个关键的不同点:不能用于Math对象中的方法;不能和任何Number实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为BigInt变量在转换成Number变量时可能会丢失精度。
5.2 与运算符使用
let b = 100n
b + 20n // 120n
b - 20n // 80n
b * 20n // 2000n
b ** 2n // 10000n
b / 20n // 5n
值得注意的是/运算符,该操作符的结果会向下取整,不会返回小数部分。
let b = 5n
console.log(b / 2n) // 按道理是2.5n,但其实是2n
5.3 比较
2n === 2n // true
5n === 2n // false
BigInt和Number是可以进行比较的。
// 当宽松比较的时候,结果为true
2n == 2 // true
// 当严格比较的时候,结果为false
2n === 2 // false
大于和小于的比较也是可以的。
(2n > 2 // false
2n >= 2 // true
2n > 5 // false
5n > 2 // true
5.4 转化Boolean值
转化为Boolean时与Number类似
Boolean(0n) // false
Boolean(2n) // true
!2n // false
0n && 2n // 0n
0n || 2n // 2n
6. 函数的默认参数
函数现在可以传入默认参数了,允许在没有值或者值为undefined的时候使用传入的默认参数了。
在以前,对参数判空,如果是空就设置一个默认值可能需要这样。
function func(name) {
if (!name) {
name = '小明'
}
return name
}
func() // 小明
现在有了默认参数,可以这么写。
function func(name = '小明') {
return name
}
func()
很方便,在形参位置设置即可。
7. 解构赋值
解构赋值是一种js表达式。可以通过解构赋值,将属性或者值从对象或者数组中取出,赋值给其他的变量。
在以前,交换变量的值,需要一个中间变量。比如,现在我想交换a和b的值。
let a = 1,
b = 2;
let change;
change = a
a = b
b = change
console.log(a, b) // 2 1
有了解构赋值之后,我们只需要这么做。
[a, b] = [b, a]
console.log(a, b) // 2 1
7.1 在数组中使用
在数组中使用,可以方便快捷地把数组中的值赋给变量。
let arr = [1, 2, 3]
let [a, b, c] = arr
console.log(a, b, c) // 1 2 3
也可以设置一个默认值,应对想要的值不存在的情况。
let arr = [1, 2, 3]
let [a, b, c = 8, d = 4] = arr
console.log(a, b, c, d) // 1 2 3 4
在取值的过程中,也可以忽略某些值。
let arr = [1, 2, 3]
let [a, , b] = arr
console.log(a, b) // 1 3
7.2 在对象中使用
在对象中使用
let obj = {
name: '小明',
age: 10
}
let {name} = obj
console.log(name) // 小明
也可以对取出的变量重新设置变量名。
let obj = {
name: '小明',
age: 10
}
let {name: x} = obj
console.log(x) // 小明
也是可以设置默认值的,这个与数组类似。
7.3 针对于嵌套的对象与数组
let obj = {
name: '小明',
message: {
age: 10,
gender: '男',
books: ['西游记', '红楼梦', ['三国演义', '水浒传']]
}
}
let {
name,
message: {
age: y,
books: [book1, book2, [, book3]]
}
} = obj
8. 展开语法
来自MDN:展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。 可以将数组的值展开传入函数中作为参数。
let arr = ['小明', 10]
function func(name, age) {
console.log(name, age)
}
func(...arr)
可以合并两个对象作为一个新的对象。
let obj = {name: '小明'}
let obj2 = {age: 10}
let obj3 = {...obj, ...obj2}
console.log(obj3) // {name: "小明", age: 10}
获得一个新的数组。
let arr = [1, 2, 3]
let arr2 = [4, 5, 6]
let arr3 = [...arr, ...arr2, 7]
console.log(arr3) // [1, 2, 3, 4, 5, 6, 7]
拷贝对象或者数组。
let arr = [1, 2, 3]
let arr2 = [...arr]
arr[0] = 11
console.log(arr, arr2)// [11, 2, 3] [1, 2, 3]
let obj = {name: '小明'}
let obj2 = {...obj}
obj.name = '小智'
console.log(obj, obj2) // {name: "小智"} {name: "小明"}
虽然改变原数组或者原对象的值,对拷贝过的数组或者对象没有影响,但其实还是浅拷贝,只会遍历一层,多维数组或者多层对象,就会出现问题。
9. for...of与迭代器
来自MDN:
for...of语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
let arr = [1, 2, 3]
for (let num of arr){
console.log(num)
}
这一个最基本的结构的for...of循环结构,循环体内会依次输出1 2 3。
上面说了要在一个可迭代对象上面使用,比如Object就不是可迭代对象。
let obj = {name: '小明'}
for (let value of obj) {
console.log(value) // 报错 obj is not iterable
}
那如何成为一个可迭代对象呢。那就是必须实现@@iterator方法。意思就是在此对象的原型链上,必须有一个@@iterator属性。这个属性是可以通过Symbol.iterator访问的。
迭代器,是一个对象,在其中有一个叫next的方法,该方法返回两个属性的值,value和done。value表示每次迭代返回的值;done为布尔值,表示是否迭代完毕。
可以自定义一个可迭代对象:
// 使用 Generator
let obj = {name: '小明'}
obj[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
}
for (let value of obj) {
console.log(value) // 分别输出 1 2 3
}
// 自定义
let obj = {name: '小明', age: 10}
// 添加Symbol.iterator属性,一个无参数的函数
// next方法返回一个对象,包含value和done
obj[Symbol.iterator] = function () {
let index = 0
let propList = Object.keys(obj)
return {
next: () => {
let value = this[propList[index]]
let done = (index >= propList.length)
index++
return {
value,
done
}
}
}
}
for (let value of obj) {
console.log(value) // 分别输出 小明 10
}
现在可以改造一下上面报错的例子。
let obj = {name: '小明'}
function createIterator(obj) {
let index = 0
let propList = Object.keys(obj)
return {
next: () => {
let done = index >= propList.length
let value = obj[propList[index++]]
return {value, done}
}
}
}
obj[Symbol.iterator] = function () {
return createIterator(this)
}
for (let value of obj) {
console.log(value) // 小明
}
10. Set和WeakSet
10.1 Set
Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象中的元素是唯一的。
10.1.1 创建Set
直接调用Set构造函数可以创建一个新的Set对象,其中可以传入任意类型(带有iterable接口)的唯一值。
如果传递的是一个可迭代对象,那么其中的元素会不重复地添加进Set对象中。
// 调用Set构造函数
let set = new Set()
let set1 = new Set([1, 2, 3])
10.1.2 Set的长度
使用当前Set对象的size属性会返回其中元素的个数。
let set = new Set()
let set1 = new Set([1, 2, 3, 3])
console.log(set.size) // 0
console.log(set1.size) // 3
10.1.3 添加元素
使用add方法可以往Set对象的尾部添加新的元素。可以链式调用,重复的值无法调用。
let set = new Set()
set.add(1)
set.add(2).add(3).add(3)
console.log(set) // Set {1, 2, 3}
10.1.4 移除所有元素
使用clear方法可以清空Set对象中所有的元素。
let set = new Set([1, 2, 3])
set.clear()
console.log(set) // Set {size: 0}
10.1.5 移除指定元素
使用delete方法,传入将要删除的元素,可以移除对应元素。返回值为布尔值,代表成功或者失败。
let set = new Set([1, 2, 3])
// 传入将要删除的元素
set.delete(2)
console.log(set) // Set {1, 3}
10.1.6 entries迭代
使用entries方法会返回一个新的迭代器,循环返回 [value, value] 形式的数组。
let set = new Set([1, 2])
set.add(55)
let setIterable = set.entries()
for (let value of setIterable) {
console.log(value) // 依次输出[1, 1] [2, 2] [55, 55]
}
10.1.7 values和keys迭代
values和keys一样,都是返回一个新的迭代器,循环返回Set对象中的元素值。
let set = new Set([1, 2, 3])
set.add(55)
for (let value of set.values()) {
console.log(value) // 1 2 3 55
}
for (let value of set.keys()) {
console.log(value) // 1 2 3 55
}
10.1.8 forEach循环
与数组的forEach了,依次遍历每个元素,并提供回调函数。
let set = new Set([1, 2, 3])
set.forEach(item => {
console.log(item)
})
10.1.9 has
使用has方法,传入一个值,可以返回一个布尔值来表示这个值是否在当前Set对象中。
let set = new Set([1, 2, 3])
console.log(set.has(1)) // true
console.log(set.has(4)) // false
10.1.10 与数组的相互转换
数组转换成Set对象,将数组传入Set构造函数即可。
Set对象转换成数组,可以使用...扩展运算符。
let set = new Set([1, 2, 3])
set.add(55)
console.log([...set]) // [1, 2, 3, 55]
前面有提起过,Set对象中的元素是不重复的,传入一个可迭代对象,会不重复地把元素添加进去。利用这个特性,可以实现数组的去重。
// 有重复值的数组
let repeatArr = [1, 1, 2, 2, 3, 4, 4, 5, 6]
// 塞入Set
let set = new Set(repeatArr)
console.log(set) // Set {1, 2, 3, 4, 5, 6}
// 转换为数组
let arr = [...set]
console.log(arr) // [1, 2, 3, 4, 5, 6]
10.2 WeakSet
WeakSet对象允许将弱保持对象存储在一个集合中。
WeakSet与Set类似,但是只有add,delete,has三个方法。
区别有两点:
- WeakSet只能是对象的集合,而不能是任意类型的值。 只能添加对象类型的值,添加基本数据类型的值会报错。
let weakSet = new WeakSet()
let obj = {name: '小明'}
let arr = [8, 88]
let str = '1'
weakSet.add(obj)
weakSet.add(arr)
weakSet.add(str) // Invalid value used in weakset
- 集合中对象的引用为弱引用。垃圾回收机制会这样做,如果一个对象,没有被除了WeakSet以外的其他地方引用,那么这个对象会被回收。
因为没有size属性,所以无法得知长度,无法遍历。
11. Map和WeakMap
11.1 Map
Map对象保存键值对,并且能够记住键的原始插入顺序。任何值都可以作为一个键或一个值。
11.1.1 创建Map
调用Map构造函数可以创建一个Map,其中的参数应为一个数组或者可迭代对象,其中元素为两个元素的数组,代表键与值。
let map = new Map([
['name', '小明'],
[Symbol(), {a: 1}],
['age', 10]
])
11.1.2 Map的长度
使用size属性可以知道一个Map对象的长度。
let map = new Map([
['name', '小明'],
[Symbol(), {a: 1}],
['age', 10]
])
console.log(map.size) // 3
11.1.3 添加键值对
使用set方法可以在Map对象中添加键值对。
let map = new Map([
['name', '小明']
])
map.set('age', 10)
console.log(map) // Map {"name" => "小明", "age" => 10}
11.1.4 获取值
使用get方法可以方便地获取Map对象中的值。
let map = new Map([
['name', '小明'],
['age', 10]
])
console.log(map.get('age')); // 10
11.1.5 迭代Map对象
与Set类似,Map也有entries,values,keys三个方法,返回一个新的迭代器,之后可以用for...of迭代Map对象。直接迭代Map对象的话,效果与entries相同。还可以使用forEach迭代Map对象。
- entries 与直接迭代Map对象效果相同
let map = new Map([
['name', '小明']
])
map.set('age', 20)
for (let value of map.entries()){
console.log(value) // 依次输出 ['name', '小明'] ['age', 20]
}
for (let value of map){
console.log(value) // 效果同上
}
- 11.5.2 values 使用values进行迭代,会拿到Map对象的值。
let map = new Map([
['name', '小明']
])
map.set('age', 20)
for (let value of map.values()){
console.log(value) // 依次输出 小明 20
}
- 10.5.3 keys 使用keys进行迭代,会拿到Map对象的键。
let map = new Map([
['name', '小明']
])
map.set('age', 20)
for (let value of map.keys()){
console.log(value) // 依次输出 name age
}
- 10.5.4 forEach
let map = new Map([
['name', '小明']
])
map.set('age', 20)
map.forEach((value,key)=>{
console.log(value,key) // 依次输出键和值
})
10.1.6 与数组的相互转化
数组转化为Map对象。因为创建Map对象的时候需要去传参,那么按照要求的格式传入一个数组,就可以了。
let arr = [['name', '小明'], ['age', 10]]
let map = new Map(arr)
Map对象转化为数组。使用Array.from与扩展运算符都是可以的。
let arr = [['name', '小明'], ['age', 10]]
let map = new Map(arr)
// 均可转化为原数组
console.log(Array.from(map));
console.log([...map]);
在使用keys,values方法的时候也可以转化为数组,转化为相对应的只包含键或者值的数组。
11.1.7 合并Map对象
在合并时,使用扩展运算符可以合并两个Map对象。在键相同的时候,则后面的会覆盖前面的。
let map1 = new Map([['name', '小明']])
let map2 = new Map([['age', 10]])
let map3 = new Map([...map1, ...map2])
11.1.8 清空与删除
使用clear方法可以清空Map,使用delete可以删除指定的元素。
11.2 WeakMap
与Map类似,WeakMap对象是一组键值对的集合,其中的键是弱引用的。其键必须是对象(基本数据类型不可以作为键),而值可以是任意的。
// 这是错误的
new WeakMap([
['name', '小明'] // Invalid value used as weak map key
])
// 这是正确的
let func = function () {}
let weakMap = new WeakMap([
[[1, 2], 'arr'],
[{a: 1}, 'obj'],
[func, 'func']
])
其他的与Map类似,但没有迭代方法,不可迭代。
12. Promise
来自MDN:Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
在以前,针对js中的异步处理,大多是利用回调函数。
当一个函数,作为另一个函数的参数,在满足一定条件下执行,这个函数就被称为回调函数。
定时器中的第一个参数是函数,这个函数就是回调函数。
setTimeout(function () {
console.log(1)
setTimeout(function () {
console.log(2)
setTimeout(function () {
console.log(3)
}, 1000)
}, 1000)
}, 1000)
此段代码会在1秒后输出3次,是一段典型的使用回调函数的代码。那么如果想在后面按顺序继续输出,就会往里面加入更多的代码,代码的缩进会越来越多,就形成了回调地狱。
用Promise就可以解决这个问题。
let promise = new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve()
}, 1000)
})
promise.then(() => {
setTimeout(() => {
console.log(2)
return 2
}, 2000)
})
.then(() => {
setTimeout(() => {
console.log(3)
return 3
}, 3000)
})
12.1 Promise定义
通过Promise构造函数就可以创建一个promise对象,它可以接收一个函数参数,函数参数中有两个方法,resolve与reject,在此函数中,进行异步操作,成功了调用resolve,失败了调用reject。
成功之后可以使用promise.then()方法去接收结果,进行下一步操作。
失败了可以使用promise.catch()方法去处理错误。
let promise = new Promise((resolve, reject) => {
if ( /* 成功 */) {
resolve(true)
} else {
reject(false)
}
})
promise
.then((res) => {
})
.catch(err => {
})
promise对象有三种状态。
- Fulfilled:resolve的时候。
- Rejected:reject的时候。
- Pending:promise对象刚被创建时的初始状态。 而promise对象的状态,只会从 Pending 转变为 Fulfilled 或者 Rejected,此后,这个对象的状态就不会变化了。
12.2 then与catch
它需要两个参数,promise的成功或者失败的回调函数。在执行完其中的回调函数时候,会返回一个全新的promise对象,所以它还可以链式调用,在上一个then中return就可以将其作为下一个then中的回调函数的参数。
像这样。
let promise = new Promise((resolve, reject) => {
resolve(1)
})
promise
.then(res => {
console.log(res) // 1
return res + 1
}, err => {
})
.then(res => {
console.log(res) // 2
})
而catch其实是then的第二个参数的语法糖,它可以处理promise中reject或者出错的情况。
比如上一段代码,可以这么写。
let promise = new Promise((resolve, reject) => {
resolve(1)
})
promise
.then(res => {
console.log(res) // 1
return res + 1
})
.catch(err => {
})
.then(res => {
console.log(res) // 2
})
那么这两种写法的区别就是:第一种写法无法捕捉then中出现的错误,从而无法进行统一的错误处理。所以一般的做法是在链式调用的最后写上一个catch,进行错误处理。
12.3 resolve和reject
Promise.resolve(value)会返回一个Fulfilled的promise对象,可以直接跟上then使用。
Promise.reject(value)会返回一个Rejected的promise对象,可以直接跟上catch使用。
12.4 Promise.all
Promise.all方法可接受一个带iterable接口的参数,如一个包含n多操作的数组,返回一个Promise实例。使用then之后,参数为数组,其中包含了操作的结果。操作中的如果出现了错误,不会调用then,会被catch捕捉。
let a = Promise.resolve(1)
let b = new Promise(resolve => {
setTimeout(() => {
resolve(2)
},1000)
})
let c = 3
Promise.all([a, b, c])
.then(values => {
console.log(values) // [1, 2, 3]
})
上面的代码会在1秒之后输出[1, 2, 3],由此可见,不管传入的操作是异步还是同步,都会等到所有的操作全部变成resolve或者reject之后返回结果,调用then方法。
function delayLog(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay)
}, delay)
})
}
Promise.all([
delayLog(1),
delayLog(100),
delayLog(1000),
delayLog(3000),
delayLog(2000)
])
.then(values => {
console.log(values) // [1, 100, 1000, 3000, 2000]
})
可以看到,即使参数数组中的程序是同时进行的,最短时间是1毫秒,还是会在3秒之后一起输出结果。
12.5 Promise.race
与Promise.all类似,Promise.race也是用于处理多个promise对象的方法。 他们的不同点是,all会在所有的操作resolve或者reject之后才会调用then,而race,顾名思义,比赛,赛跑,就是看谁快,在一个操作resolve或者reject之后,就可以调用then。
比如上面的例子,改为使用Promise.race之后,只会输出最快完成的那个,所以会输出1。当然了,这个输出的是谁和传入的的顺序无关。
function delayLog(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay)
}, delay)
})
}
Promise.race([
delayLog(1),
delayLog(100),
delayLog(1000),
delayLog(3000),
delayLog(2000)
])
.then(values => {
console.log(values) // 1
})
12.6 Promise.allSettled
前面说了,all方法如果在promise数组中某个操作reject的时候,会被catch捕捉,不会执行then。
allSettled方法返回一个在所有给定的promise都已经Fulfilled或者rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
let a = Promise.resolve(1)
let b = new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 1000)
})
let c = Promise.reject(3)
Promise.allSettled([a, b, c])
.then(values => {
values.forEach(item => {
console.log(item)
// 会依次输出
// {status: "fulfilled", value: 1}
// {status: "fulfilled", value: 2}
// {status: "rejected", reason: 3}
})
})
需要知道每个promise的结果的时候,适合使用。
12.7 Promise.finally
finally方法在promise结束时,无论结果如何,都会执行指定的回调函数。在需要执行不论怎样都要执行的代码时,是一个好方式。
let a = Promise.resolve(1)
let b = Promise.reject(2)
a.then(res => {
console.log(res) // 1
})
.finally(() => {
console.log('a finally') // a finally
})
b.then(null, err => {
console.log(err) // 2
})
.finally(() => {
console.log('b finally') // b finally
})
12.8 Promise.any
这个方法与all方法相反。也是接收一个Promise对象数组,只要其中一个resolve,就返回成功的promise;如若都失败了,就返回一个失败的promise。
目前该方法未被任何浏览器支持。
13. Generator
Generator,生成器函数,异步解决方案,执行之后可生成一个可遍历对象,通过yield可多次返回值。每次执行在yield时停止,通过调用next方法可以继续执行。
13.1 创建Generator
function* createGen () {
yield 1
yield 2
yield 3
}
let gen = createGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next()) // {value: undefined, done: true}
在function后加一个*,就可以创建一个Generator函数,这是它与普通函数的区别。
用一个变量接收Generator函数,便可以通过调用next方法去依次获取yield返回的值。
13.2 next方法
next的方法的返回值是一个对象。
done:为布尔类型,表示是否迭代完成。如果还有迭代器超过了迭代序列的末尾,则为true;如果迭代器可以生成下一个值,则为false。value:代表的是在函数中给定的返回值。当done的值为true时,该值为undefined。
next方法是可以传参的,每个传入next的参数,会替代上一个yeild表达式返回的值。
function* createGen(x) {
let y = yield x + 1
console.log(y) // 7
let z = yield y * 2
console.log(z) // 8
}
let gen = createGen(90)
console.log(gen.next())
console.log(gen.next(7))
console.log(gen.next(8))
在上述代码中,给Generator函数传入一个90,执行next方法,y变为91,但其实是7,因为下调一个next方法传入了一个7,代替了上一个yeild表达式返回的值,同理,z就是8了。
这就可以看出,在第一个next方法传入参数是不起作用的,因为在此之前并没有执行yeild表达式,所以会被忽略。
13.3 return方法
return方法可以给一个值,并结束Generator函数,等同于在Generator函数中使用return。
像正常的使用next方法会这样。
function* createGen() {
yield 1
yield 2
yield 3
}
let gen = createGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
依次会是1,2,3。但使用了return方法就可以提前结束Generator方法,并返回给定的值。
function* createGen() {
yield 1
yield 2
yield 3
}
let gen = createGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.return('over')) // {value: 'over', done: true}
console.log(gen.next()) // {value: undefined, done: true}
可以看到使用了return的地方,返回了{value: 'over', done: true},而且下一个next也并没有返回值,可知Generator函数被提前结束了。
return方法是可以调用多次的。但Generator函数会一直在结束状态。
13.4 throw方法
用于抛出异常。
如果在Generator函数内部没有try...catch,那么函数的运行会终止。
function* createGen() {
yield 1
yield 2
yield 3
}
let gen = createGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.throw('error')) // error
console.log(gen.next()) // 不会运行
在Generator函数内部添加try...catch可以继续运行。
function* createGen() {
try{
yield 1
yield 2
yield 3
}catch (e) {
console.log(e) // error
}
}
let gen = createGen()
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.throw('error')) // {value: undefined, done: true}
console.log(gen.next()) // {value: undefined, done: true}
如果在Generator函数内部没有添加try...catch,而是在执行的时候添加,也会被捕捉到,但是接下来的next方法也不会执行。
function* createGen() {
yield 1
yield 2
yield 3
}
let gen = createGen()
try {
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.throw('error'))
console.log(gen.next()) // 不会执行
} catch (e) {
console.log(e) // error
}
13.5 异步处理
Generator函数是异步处理方案,那么怎么处理呢。
function getNumber(x, time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x)
}, time)
})
}
function* createGen() {
yield getNumber(1, 1500)
}
let gen = createGen()
gen.next().value.then(res => {
console.log(res)
return res
})
这段代码用setTimeout模拟异步,返回一个Promise对象,在next方法执行后,可以用then去接收并处理接下来的事务。
可以多添加几个异步任务。
// 模拟异步任务
function getNumber(x, time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x)
}, time)
})
}
function* createGen() {
yield getNumber(1, 1500)
yield getNumber(2, 100)
yield getNumber(3, 1000)
}
let gen = createGen()
gen.next().value.then(res => {
console.log(res) // 1
return res
})
.then(res => {
// 返回promise
return gen.next().value
})
.then(res => {
console.log(res) // 2
// do sth
})
.then(res => {
// 返回promise
return gen.next().value
})
.then(res => {
console.log(res) // 3
// do sth
})
越写越多,这种写法和回调函数感觉也差不了多少。
所以可以添加一个nextGen函数。
function nextGen(generator) {
let gen = generator()
function getNext(res) {
let result = gen.next(res)
if (result.done) return
result.value
.then(res => {
console.log(res)
getNext(res)
})
}
getNext()
}
nextGen(createGen)
nextGen函数的作用就是每次执行完一个next,返回一个Promise对象,利用递归再次执行,一直到done为true时停止。
14. async/await
14.1 描述
async在函数前使用,await在一个操作前使用,可以用同步的方式写出异步代码。
在使用了async的函数里面,可以使用0或无数个await。
await在一个Promise对象或者需要去等待的值前面使用,返回Promise对象的执行结果,若非Promise对象,则返回该值本身。await无法在没有使用async的函数中使用。
await会暂停整个函数的执行进程并让出其控制权,只有当其等待的操作执行完毕之后才会恢复,Promise的解决值或者其他值会被当做该await的返回值。抛出的错误可以用try...catch捕捉。
function getNumber(x, time) {
return new Promise(resolve => {
setTimeout(() => {
console.log(x)
resolve(x)
}, time)
})
}
async function run() {
await getNumber(1, 1000) // 1
await console.log(2) // 2
await getNumber(3, 500) // 3
await getNumber(4, 0) // 4
}
run()
async函数一定会返回一个Promise对象,一个非Promise对象也会被隐式地包装在Promise中。
async function func() {
return 1
}
等同于:
function func() {
return Promise.resolve(1)
}
14.2 与Promise
相比Promise来说,使用async/await会使代码更加简洁。
// 以前这么写
function getData() {
ajax.get('http://url.com/test')
.then(res => {
return res.data
})
}
// 现在这么写
async function getData() {
const res = await ajax.get('http://www.url.com/test')
return res.data
}
// 以前这么写
function getData() {
ajax.get('http://www.url.com/test')
.then(res => {
if (res.status) {
ajax.get('http://www.url.com/test2')
.then(res => {
return res
})
}
})
}
// 现在这么写
async function getData() {
const res = await ajax.get('http://www.url.com/test')
if (res.status) {
const res2 = ajax.get('http://www.url.com/test2')
return res2.data
}
}
15. Proxy
来自MDN:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
15.1 创建Proxy代理
使用Proxy构造函数就可以创建一个代理对象,
- 第一个参数为:需要代理的对象。
- 第二个参数为:以函数为属性的对象,其中属性为函数,定义了各种在操作代理对象时的行为。
let proxy = new Proxy({}, {})
15.2 各种操作的方法
15.2.1 get
get用于拦截对象的读取属性的操作。
可以接收三个参数:
- target:表示目标对象。
- prop:想要获取的属性名称。
- receiver:Proxy代理对象。 里面的this,是整个操作对象。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
get: function (target, prop, receiver) {
console.log('get')
return target[prop]
}
})
// 操作代理对象才可以,操作原对象是不会触发给定的操作的
console.log(proxy.name); // get 小明
15.2.2 set
set用于拦截设置属性值操作。
可以接收四个参数:
- target:表示目标对象。
- prop:将要被设置的属性名。
- value:想要设置的属性值。
- receiver:最初被调用的对象。 里面的this,是整个操作对象。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
set(target, prop, value, receiver) {
console.log('set')
obj[prop] = value
}
})
proxy.age = 11 // set
15.2.3 getPrototypeOf
当读取代理对象的原型的时候,该方法就会被调用。
可以接收一个参数:
- target:表示目标对象。 有五种触发的方法:
- Object.getPrototypeOf()
- Reflect.getPrototyprOf()
__proto__- Object.prototype.isPrototypeOf()
- instanceof 该方法的返回的值必须是一个对象或者null。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
getPrototypeOf(target) {
console.log('getPrototypeOf')
return Object.prototype
}
})
// 以下操作可以触发
// 1. Object.getPrototypeOf
console.log(Object.getPrototypeOf(proxy))
// 2. Reflect.getPrototypeOf
console.log(Reflect.getPrototypeOf(proxy))
// 3. __ proto__
console.log(proxy.__proto__)
// 4. Object.prototype.isPrototypeOf
console.log(Object.prototype.isPrototypeOf(proxy))
// 5. instanceof
console.log(proxy instanceof Object)
15.2.4 setPrototypeOf
主要用于拦截Object.setPrototypeOf()。
可以接收两个参数:
- target:代表目标对象。
- prototype:对象的新原型或为null。
有返回值:如果成功修改了
[[prototype]],会返回true,否则返回false。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
setPrototypeOf(target, prototype) {
console.log('setPrototypeOf')
return true
}
})
// 可拦截以下操作
// 1. Object.setPrototypeOf
Object.setPrototypeOf(proxy, Array.prototype)
// 2. Reflect.setPrototypeOf
Reflect.setPrototypeOf(proxy, null)
15.2.5 isExtensible
用于拦截对象的Object.isExtensible()。
可以接收一个参数:
- target:代表目标对象。 有返回值:必须返回一个布尔值或者可转化为布尔值的值。
Object.isExtensible()方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。Object.preventExtensions()可创建一个无法扩展的对象。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
isExtensible(target) {
console.log('isExtensible')
return true
}
})
console.log(Object.isExtensible(proxy)) // isExtensible true
15.2.6 preventExtensions
用于设置Object.preventExtensions()的拦截。
可以接收一个参数:
- target:表示目标对象。
有返回值:布尔值,如果目标是可扩展的,那么只能返回
false。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
preventExtensions(target) {
console.log('preventExtensions')
// 将当前目标对象设置为不可扩展,否则将会报错
Object.preventExtensions(target)
return true
}
})
console.log(Object.preventExtensions(proxy)) // preventExtensions
15.2.7 getOwnPropertyDescriptor
用于设置Object.getOwnPropertyDescriptor()的拦截。
可以接收两个参数:
- target:表示目标对象。
- prop:属性名。 有返回值:必须返回一个对象或者undefined。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
getOwnPropertyDescriptor(target, prop) {
console.log('getOwnPropertyDescriptor')
return {configurable: true, value: '小明'}
}
})
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
// getOwnPropertyDescriptor
// {value: '小明', writable: false, enumerable: false, configurable: true}
15.2.8 defineProperty
用于拦截Object.defineProperty()操作。
可以接收两个参数:
- target:表示目标对象。
- prop:属性名。
- desc:待定义或者修改的属性的描述。
- enumerable
- configurable
- writable
- value
- get
- set 有返回值:布尔值,表示操作是否成功。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
defineProperty(target, prop, desc) {
console.log('defineProperty')
return true
}
})
Object.defineProperty(proxy, 'gender', {
configurable: true,
enumerable: true,
value: '男'
}) // defineProperty
15.2.9 has
是针对in操作符的代理方法。
可以接收两个参数:
- target:目标对象。
- prop:需要检测是否存在的属性名。 有返回值:布尔值。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
has(target, prop) {
console.log('has')
return true
}
})
console.log('name' in obj)
15.2.10 deleteProperty
用于拦截对象属性的delete操作。
可以接收两个参数:
- target:目标对象。
- prop:需要删除的属性名。 有返回值:布尔值,表示是否删除成功。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
deleteProperty(target, prop) {
console.log('deleteProperty')
delete target[prop]
return true
}
})
delete proxy.name // deleteProperty
15.2.11 ownKeys
用于拦截Reflect.ownKeys()。
可以接收以下参数:
- target:目标对象。 有返回值:必须返回一个可枚举对象(包含String或Symbol的数组)。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
ownKeys(target) {
console.log('ownKeys')
return ['a', 'b', Symbol('s')]
}
})
// 以下操作可以触发
// 1. Object.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(proxy));
// 2. Object.getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(proxy))
// 3.Object.keys
console.log(Object.keys(proxy))
// 4. Reflect.keys
console.log(Reflect.ownKeys(proxy))
15.2.12 apply
用于拦截函数的调用。
可以接收以下参数:
- target:目标对象(必须是一个函数)。
- thisArg:被调用时的上下文。
- argsList:被调用时的参数数组。 有返回值:可返回任意值。
function say(name) {
console.log(`你好,我是${name}`)
}
let proxy = new Proxy(say, {
apply(target, thisArg, argsList) {
console.log('apply')
}
})
proxy('小明')
15.2.13 construct
用于拦截new操作符。
可以接收以下参数:
- target:目标对象,必须是可以new的。
- argsList:参数列表。
- newTarget:最初被调用的构造函数。 有返回值:必须返回一个对象。
function Person(name, age) {
this.name = name
this.age = age
}
let proxy = new Proxy(Person, {
construct(target, argsList, newTarget) {
console.log('construct')
return new target(...argsList)
}
})
console.log(new proxy('小明', 10));
15.3 应用
15.3.1 为对象设置默认值
let obj = {
name: '小明',
age: 10
}
console.log(obj.gender)
获取一个对象中不存在的属性值,会获得undefined,如果想在获取不存在的属性时设置一个默认值,可以使用proxy。
let obj = {
name: '小明',
age: 10
}
let proxy = new Proxy(obj, {
get(target, prop, receiver) {
return prop in target ? target[prop] : '默认值'
}
})
console.log(proxy.name, proxy.gender) // 小明 默认值
15.3.2 对表单数据进行验证
假设formData是一个表单的值,每次表单中输入框的值改变,会重新设置formData中的值,那么可以通过proxy对表单的值进行验证,做出一些限制。
let formData = {
name: '小明',
age: 10
}
let validator = {
name(value) {
return {
result: value.length < 5,
err: value.length < 5 ? '' : 'error: name'
}
},
age(value) {
return {
result: typeof value === 'number',
err: typeof value === 'number' ? '' : 'error: age'
}
}
}
let proxy = new Proxy(formData, {
set(target, prop, value, receiver) {
let {result, err} = validator[prop](value)
if (!result) alert(err)
target[prop] = value
}
})
proxy.name = '小智'
proxy.age = '11' // 报错 error: age
16. Reflect
Reflect是一个内置对象,提供了拦截js操作的方法,与proxy内的操作方法相同。
因为Reflect并非一个构造函数,所以不能通过new调用。
16.1 Reflect.apply
Reflect.apply()调用目标函数,并传入指定的参数列表。
可以接收三个参数:
- target:目标函数对象。
- thisArg:target函数调用时绑定的对象。
- argsList:参数列表,为一个数组或者类数组。
这个方法类似于
Function.prototype.apply(),绑定this之后去执行函数。
function func(a, b) {
return a + b
}
console.log(Reflect.apply(func, null, [1, 2])); // 3
let name = '小明'
let obj = {
name: '小智',
sayName() {
return '我是' + this.name
}
}
console.log(Reflect.apply(obj.sayName, window, [...name])) // 我是小明
16.2 Reflect.construct
Reflect.construct()相当于new操作符,可以调用构造函数。
可以接收三个参数:
- target:构造函数,必须是。
- argsList:参数列表。
- newTarget:作为新创建对象的原型对象的
constructor属性,默认值为target,可不传。
function Person(name, age) {
this.name = name
this.age = age
}
// new
let xm = new Person('小明', 10)
// Reflect.construct
let xz = Reflect.construct(Person, ['小智', 11])
16.3 Reflect.defineProperty
Reflect.defineProperty()等同于Object.defineProperty(),会返回一个布尔值,表示操作是否成功。
可以接收三个参数:
- target:目标对象。
- prop:需要定义或修改的属性名。
- attr:需要定义或修改的属性描述。
相比于Object.defineProperty(),Reflect.defineProperty()在指定属性未被成功定义的时候,不会抛出错误,而是返回false,如果未使用try...catch,保证了程序不被中断。
let obj = {
name: '小明'
}
// Object.defineProperty
Object.defineProperty(obj, 'age', {
configurable: true,
enumerable: true,
value: 10
})
// Reflect.defineProperty
Reflect.defineProperty(obj, 'name', {
value: '小智'
})
console.log(obj) // {name: '小智', age: 10}
16.4 Reflect.deleteProperty
Reflect.deleteProperty用于删除对象的指定属性,会返回一个布尔值,表示是否删除成功,功能类似于delete操作符。
可以接收两个参数:
- target:目标对象。
- propKey:需要删除的属性名称。
let obj = {
name: '小明',
age: 10
}
Reflect.defineProperty(obj, 'gender', {
configurable: false,
value: '男'
})
console.log(Reflect.deleteProperty(obj, 'age')) // true
console.log(Reflect.deleteProperty(obj, 'gender')) // false
16.5 Reflect.get
Reflect.get() 方法与从对象 (target[propKey]) 中读取属性类似,但它是通过一个函数执行来操作的。
可以接收三个参数:
- target:目标对象。
- propKey:需要获取的属性名称。
- receiver:如果
target对象中指定了getter(读取函数),receiver则为getter调用时的this值。
let obj = {
name: '小明',
age: 10,
get sayName() {
return '我是' + this.name
}
}
let obj2 = {
name: '小智'
}
console.log(Reflect.get(obj, 'age')) // 10
console.log(Reflect.get(obj, 'sayName', obj2)) // 我是小智
16.6 Reflect.set
Reflect.set() 方法就是在对象上设置一个属性。
可以接收四个参数:
- target:目标对象。
- propKey:需要获取的属性名称。
- value:设置的值。
- receiver:如果
target对象中指定了setter(赋值函数),receiver则为setter调用时的this值。
let obj = {
name: '小明',
set setName(value) {
console.log(value)
return this.name = value
}
}
let obj2 = {}
Reflect.set(obj, 'age', 10)
console.log(obj) // {name: '小明', age: 10}
Reflect.set(obj, 'setName', '小智', obj2) // 小智
console.log(obj2) // {name: '小智'}
16.7 Reflect.getOwnPropertyDescriptor
Reflect.getOwnPropertyDescriptort() 方法返回给定的属性的属性描述符,如属性不存在,则返回undefined。
可以接收两个参数:
- target:目标对象,必须是一个对象,否则会报错。
- propKey:需要获取的属性描述符的属性名称。
let obj = {}
Reflect.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
writable: false,
value: '小明'
})
Reflect.defineProperty(obj, 'age', {
configurable: false,
enumerable: true,
writable: false,
value: 10
})
console.log(Reflect.getOwnPropertyDescriptor(obj, 'name'))
// {value: '小明', writable: false, enumerable: true, configurable: true}
console.log(Reflect.getOwnPropertyDescriptor(obj, 'age'))
// {value: 10, writable: false, enumerable: true, configurable: false}
16.8 Reflect.getPrototypeOf
Reflect.getPrototypeOf() 方法返回对象的原型,即[[Prototype]]属性的值。
可以接收一个参数:
- target:目标对象,必须是一个对象,否则会报错。
let obj = {
name: '小明'
}
console.log(Reflect.getPrototypeOf(obj) === Object.prototype) // true
16.9 Reflect.has
Reflect.has() 方法与in操作符相同,返回一个布尔值表示属性是否存在。
可以接收两个参数:
- target:目标对象,必须是一个对象,否则会报错。
- propKey:需要检查是否存在的属性名。
let obj = {
name: '小明'
}
console.log(Reflect.has(obj, 'name')) // true
console.log(Reflect.has(obj, 'age')) // false
16.10 Reflect.isExtensible
Reflect.has() 方法可判断一个对象是否可以扩展(即是否能添加新的属性),会返回一个布尔值表示是否可以扩展。
可以接收一个参数:
- target:目标对象,如果不是对象,会报错。
let obj = {
name: '小明'
}
console.log(Reflect.isExtensible(obj)) // true
// 使用Object.freeze冻结对象
Object.freeze(obj)
console.log(Reflect.isExtensible(obj)) // false
16.11 Reflect.preventExtensions
Reflect.preventExtensions() 方法会使对象变得不可扩展,会返回一个布尔值表示目标对象是否成功设置为不可扩展。与Object.preventExtensions()类似。
可以接收一个参数:
- target:目标对象,如果不是对象,会报错。
let obj = {
name: '小明',
}
// 属性可以添加
obj.age = 10
// 检测到可以扩展
Reflect.isExtensible(obj) // true
// 使用Reflect.preventExtensions禁止扩展
Reflect.preventExtensions(obj)
// 检测到无法扩展
Reflect.isExtensible(obj) // false
console.log(obj) // {name: '小明', age: 10}
16.12 Reflect.ownKeys
Reflect.ownKeys() 方法返回一个由目标对象自身的属性名组成的数组。
可以接收一个参数:
- target:目标对象,如果不是对象,会报错。
let obj = {
name: '小明',
age: 10,
[Symbol('gender')]: '男'
}
let arr = [1, 2, 3]
console.log(Reflect.ownKeys(obj)) // ['name', 'age', Symbol(gender)]
console.log(Reflect.ownKeys(arr)) // ['0', '1', '2', 'length']
16.13 Reflect.setPrototypeOf
Reflect.setPrototypeOf() 方法可将对象的原型,即[[Prototype]]属性设置为另一个对象或null,返回一个布尔值表示操作是否成功。
可以接收两个参数:
- target:目标对象,如果不是对象,会报错。
- prototype:新原型。
let obj = {
name: '小明'
}
console.log(Reflect.setPrototypeOf({}, Array.prototype)) // true
console.log(Reflect.setPrototypeOf([], Object.prototype)) // true
console.log(Reflect.setPrototypeOf([], null)) // true
console.log(Reflect.setPrototypeOf(Object.freeze({}), null)) // false
console.log(Reflect.setPrototypeOf(obj, Object.create(obj))) // false
17. 数组新方法
17.1 Array.from
Array.from用将一个类数组或可迭代对象创建一个新的,浅拷贝的数组实例。
可以接收三个参数:
- arrLike:类数组或可迭代对象。
- callback:可选,回调函数,每个新数组中的元素都会执行这个函数。
- thisArg:可选,执行回调函数callback时的this对象。
可将字符串转化为数组。
let str = '123456'
console.log(Array.from(str)) // ['1', '2', '3', '4', '5', '6']
或是将参数列表这样的类数组转化为数组,并指定回调函数。
function add() {
return Array.from(arguments, (item) => {
return item * 2
})
}
console.log(add(1, 2, 3, 6, 5)) // [2, 4, 6, 12, 10]
17.2 Array.of
Array.of方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
可以接收一个参数:
eleNum:任意个数的参数,他们将会按顺序成为新数组中的元素。
Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of(undefined); // [undefined]
17.3 Array.prototype.includes
includes()方法用来判断一个数组是否包含一个指定的值,返回一个布尔值,表示是否包含。
let arr = [1, 2, 3]
console.log(arr.includes(1)) // true
console.log(arr.includes(4)) // false
17.4 Array.prototype.copyWithin
coptWith方法会浅复制数组的一部分到同一数组的另一个位置,并返回他,不会改变原数组的长度,只会改变数组的内容。
可以接收三个参数:
- index:索引需要复制到的位置。
- startIndex:开始复制元素的起始位置。为负数则从末尾开始计算,若被忽略则从0开始。
- endIndex:开始复制元素的结束位置,不会包含这个位置元素。为负数则从末尾开始计算,若被忽略则会一直复制到数组结尾(arr.length)。
endIndex参数需要注意,因为是不包含它的。
[1, 2, 3, 4, 5].copyWithin(0, 1, 3) // [2, 3, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 1, 4) // [2, 3, 4, 4, 5]
17.5 Array.prototype.find / Array.prototype.findIndex
find() 方法返回满足条件的第一个值,否则返回 undefined。
而findIndex() 方法返回满足条件的第一个值的索引,否则返回-1。
可以接收两个参数:
- callback:回调函数,可以接收三个参数:
- item:当前遍历到的元素
- index:当前遍历到的索引
- array:数组本身
- thisArg:执行回调函数时用作this的对象。
let arr = [
{name: '小明', age: 10},
{name: '小智', age: 11}
]
console.log(arr.find((item, index, array) => {
return item.name === '小明'
})) // {name: '小明', age: 10}
console.log(arr.findIndex((item, index, array) => {
return item.name === '小智'
})) // 1
17.6 Array.prototype.fill
find() 方法用于将一个固定值填充一个数组给定的范围内的所有元素,不包括结束索引。
可以接收三个参数:
- value:用于填充的值。
- startIndex:起始索引,默认为0。
- endndex:结束索引,默认为数组的length。
[1, 2, 3].fill(4) // [4, 4, 4]
[1, 2, 3].fill(4, 1) // [1, 4, 4]
[1, 2, 3].fill(4, 1, 2) // [1, 4, 3]
[1, 2, 3].fill() // [undefined, undefined, undefined]
17.7 Array.prototype.keys
keys() 方法将返回一个以索引为元素的可遍历对象。
let arr = [11, 22, 33]
for (let k of arr.keys()){
console.log(k) // 依次输出 1 2 3
}
17.8 Array.prototype.values
values() 方法将返回一个以值为元素的可遍历对象。
let arr = [11, 22, 33]
for (let k of arr.values()){
console.log(k) // 依次输出 11 22 33
}
17.9 Array.prototype.entries
entries() 方法将返回一个以值与索引为元素的可遍历对象。
let arr = [11, 22, 33]
for (let key of arr.entries()) {
console.log(key) // [0, 11]
// [1, 22]
// [2, 33]
}
17.10 Array.prototype.flat
flat()方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
可接受一个参数:
- depth:可选,指定要提取嵌套数组的深度,默认为1,传入Infinity,可展开任意深度的嵌套数组。
let arr = [1, [2, 3, [4, 5]]]
console.log(arr.flat()) // [1, 2, 3, [4, 5]]
console.log(arr.flat(Infinity)) // [1, 2, 3, 4, 5]
flat()方法还会移除数组中的空项。
let arr = [1, [2, , 3, [4, null, 5, [6, undefined, 7, , 8]]]]
console.log(arr.flat(Infinity)) // [1, 2, 3, 4, null, 5, 6, undefined, 7, 8]
17.11 Array.prototype.flatMap
flaMapt()方法首先会使用map()遍历每一个元素,执行回调函数,将结果压缩成一个新数组,接着使用depth为1的flat()返回一个新数组,不会改变原数组。
可接受两个参数:
- callback:回调函数,可生成一个新数组中的元素的函数,可接受三个参数:
- currentValue:当前遍历到的元素。
- index:可选,当前遍历到的元素的索引。
- array:可选,当前数组。
- thisArg:可选,执行回调函数时的this。
let arr = [1, 2, 3, 4, 5, 6]
arr.flatMap(item => {
return item + 1
}) // [2, 3, 4, 5, 6, 7]
arr.flatMap(item => {
return [item + 1]
}) // [2, 3, 4, 5, 6, 7]
arr.flatMap(item => {
return [[item + 1]]
}) // [[2], [3], [4], [5], [6], [7]]
18. 对象新方法
18.1 Object.values
Object.values()方法会返回一个指定对象的可枚举属性值的数组。
会过滤Symbol为键的属性,不可枚举属性的值也不会出现。
let obj = {
name: '小明',
age: 10,
[Symbol('s')]: '哈哈'
}
Reflect.defineProperty(obj, 'gender', {
value: '男',
enumerable: false
})
//
console.log(Object.values(obj)) // ['小明', 10]
当使用数字作为对象的键时,遍历的顺序会根据数组的大小来,这与使用 for...in 的顺序相同。
let obj2 = {
10: 'a',
5: 'b',
100: 'c',
1: 'd'
}
console.log(Object.values(obj2)) // ['d', 'b', 'a', 'c']
当对字符串使用时,会将它转化为对象。
//
Object.values('一段文字') // ['一', '段', '文', '字']
对数字和布尔值使用,会返回空数组。
Object.values(123) // []
Object.values(true) // []
对null和undefined使用会报错。
Object.values(null) // 报错
Object.values(undefined) // 报错
18.2 Object.entries
Object.entries()方法返回一个指定对象的可枚举属性的键值对数组。性质与Object.values()类似。
let obj = {
name: '小明',
age: 10,
[Symbol('s')]: '哈哈'
}
Reflect.defineProperty(obj, 'gender', {
value: '男',
enumerable: false
})
// 会过滤Symbol为键的属性,不可枚举属性的值也不会出现
console.log(Object.entries(obj)) // [['name', '小明'], ['age', 10]]
18.3 Object.fromEntries
Object.fromEntries()方法把键值对列表(可迭代对象)转换为一个对象。
Object.fromEntries([
['name', '小明'],
['age', 10]
]) // {name: '小明', age: 10}
Object.fromEntries(new Map([
['a',1],
['b',2]
])) // {a: 1, b: 2}
18.4 Object.getOwnPropertyDescriptor / Object.getOwnPropertyDescriptors
Object.getOwnPropertyDescriptor()方法返回指定对象上一个自有属性对应的属性描述符。
可以接收两个参数
- obj:需要查找的对象
- prop:可选,需要查找的对象的属性名,不传时返回所有属性的属性描述符。
let obj = {
name: '小明',
age: 10,
[Symbol('s')]: '哈哈'
}
Reflect.defineProperty(obj, 'gender', {
value: '男',
writable: true,
enumerable: false
})
console.log(Object.getOwnPropertyDescriptor(obj, 'gender')) // {value: '男', writable: true, enumerable: false, configurable: false}
Object.getOwnPropertyDescriptors()方法返回指定对象上所有的属性描述符。
let obj = {
name: '小明',
age: 10,
[Symbol('s')]: '哈哈'
}
Reflect.defineProperty(obj, 'gender', {
value: '男',
writable: true,
enumerable: false
})
console.log(Object.getOwnPropertyDescriptors(obj).gender.enumerable) // false
18.5 Object.assign
Object.assign() 方法会拷贝源对象自身的并且可枚举的属性到目标对象,最后返回目标对象。
可以接收两个参数:
- target:目标对象。
- sources:源对象,可以传多个。
let obj1 = {
name: '小明',
}
let obj2 = {
gender: '男'
}
let obj3 = {
age: 10
}
console.log(Object.assign(obj1, obj2, obj3)) // {name: '小明', age: 10, gender: '男'}
console.log(obj1) // {name: '小明', gender: '男', age: 10}
18.6 Object.defineProperties
Object.defineProperties()方法与Object.defineProperty()类似,可以直接在一个对象上定义新的属性或修改现有属性,并返回该对象。它可以批量修改。
let obj = {}
Object.defineProperties(obj, {
name: {
value: '小明'
},
age: {
value: 10,
configurable: true
}
})
console.log(obj) // {name: '小明', age: 10}
19. 字符串新方法
19.1 String.prototype.padStart
padStart()方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。
可以接收两个参数:
- targetLength:需要填充到的目标长度。如果小于当前字符串的长度,则会返回当前字符串。
- padString:用于填充的字符串。会被重复或截断。
let str = '123'
console.log(str.padStart(6, 'ab')) // aba123
19.2 String.prototype.padEnd
padEnd()方法与padStart()类似,从右侧开始填充。
let str = '123'
console.log(str.padEnd(6, 'ab')) // 123aba
19.3 String.prototype.trimStart / trimEnd
这两个方法可以去除字符串开头或者结尾的空格。
trimStart可写作trimLeft。
trimEnd可写作trimRight。
let str = ' 123 '
console.log(str.trimStart()) // '123 '
console.log(str.trimEnd()) // '123'
20. Class类
class声明创建一个基于原型继承的具有给定的名字的新类。
在以前,想要一个实例对象,需要先定义一个构造函数,需要这么写。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log('hi,我是' + this.name);
}
let xm = new Person('小明',10)
xm.sayHi()
有了class之后,可以这么写。
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log('hi,我的名字是:' + this.name)
}
}
let xm = new Person('小明', 10)
xm.sayHi()
class可看做一个语法糖,使对象原型的写法看上去更清晰,更像是面向对象的语法。
20.1 constructor
constructor就是构造方法。
当通过new命令生成实例对象的时候,会调用该方法。如未定义,会默认添加一个空的constructor方法。
// 这两个是一样的
class Person{}
class Person{
constructor() {
}
}
其中的this关键字代表实例对象。constructor方法默认返回实例对象。
类的方法都会定义在prototype属性上。
20.2 静态方法
当在类中的一个方法前面加上了static之后,该方法就不会被实例继承,只能通过类本身来调用,这就称为静态方法。
class Person {
funcA(){
console.log('a')
}
static funcB(){
console.log('b')
}
}
let xm = new Person()
xm.funcA() // a
Person.funcB() // b
xm.funcB() // xm.funcB is not a function
在静态方法中有this,表示的是这个类,而不是实例。
class Person {
funcA() {
console.log(this)
}
static funcB() {
console.log(this)
}
}
Person.funcB() // class Person {}
20.3 静态属性
静态属性与静态方法类似,在属性前加上static,只能通过类来访问。
class Person {
// 定义静态属性num
static num = 1
constructor(num) {
this.num = num
console.log(Person.num) // 1
console.log(this.num) // 2
}
}
let per = new Person(2)
console.log(Person.num) // 1
20.4 继承
类可以通过extends关键字实现继承。
class Animal {
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name, sound) {
super(name)
this.sound = sound
}
say() {
console.log(this.sound)
}
}
let cat = new Cat('猫', 'miao')
cat.say() // miao
其中的super代表父类的构造函数,子类必须在constructor中调用此方法,否则会报错,因为子类的对象是通过super得到父类的属性和方法,然后加上子类的属性和方法,如不调用,子类获取不了this。
但是子类不写constructor方法的话,会默认调用这个方法。
class Animal {
constructor(name) {
this.name = name
}
}
class Cat extends Animal {}
let cat = new Cat('猫')
console.log(cat.name) // 猫
20.5 super
super关键字用于访问和调用一个对象的父对象上的函数。
20.5.1 在类中使用super
class Animal {
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name, age) {
super(name);
this.age = age
}
}
let cat = new Cat('猫', 1)
console.log(cat) // '猫'
20.5.2 可以用于访问父类的静态方法
此时的super是指向父类的。
class Animal {
constructor(name) {
this.name = name
}
static say() {
console.log(`hihi`)
}
}
class Cat extends Animal {
constructor(name, age) {
super(name);
this.age = age
}
static catSay() {
super.say()
}
}
Cat.catSay() // hihi
20.5.3 可以访问父类的普通方法
此时的super是指向父类的原型对象的。
class Animal {
constructor(name) {
this.name = name
}
say() {
console.log(`hihi`)
}
}
class Cat extends Animal {
constructor(name, age) {
super(name);
this.age = age
}
catSay() {
super.say()
}
}
let cat = new Cat('猫', 2)
cat.catSay() // hihi
20.6 set / get
在类的内部,可以使用set和get关键字,对某个属性设置存取行为的拦截。
class Person {
constructor(name) {
this.name = name
}
get getName() {
console.log(`getter: ${this.name}`);
return this.name
}
set setName(value) {
console.log(`setter: ${value}`)
this.name = '小智'
}
}
let xm = new Person('小明')
xm.getName // getter: 小明
xm.setName = 1 // setter: 1
console.log(xm.name) // 小智
20.7 其他
根据class语法,可以简单地实现一个webSocket连接方法。
class WebSocketConnect {
constructor(url) {
this.url = url
this.socket = null
this.timer = null
}
// 创建连接
create = () => {
this.socket = new WebSocket(this.url)
console.warn('连接')
this.open()
}
// 连接之后
open = () => {
this.socket.onopen = () => {
this.sendPing()
}
}
// 接收信息
msg = (cb) => {
this.socket.onmessage = function (data) {
cb(data)
}
}
// 发送信息
send = (data) => {
this.socket.send(JSON.stringify(data))
};
// 连接错误,重连
error = () => {
this.socket.error = () => {
console.warn('连接错误')
clearInterval(this.timer)
this.close()
this.create()
}
}
// 关闭连接
close = () => {
this.send({msg: 'close'})
this.socket.close()
clearInterval(this.timer)
console.warn('关闭')
}
// 心跳检测
sendPing = () => {
this.send('ping')
this.timer = setInterval(() => {
this.send('ping')
}, 3000)
}
}
over。