Appearance
ES6标准入门(第三版)
😀对《ES6标准入门(第三版)》一书的笔记总结
第1章 ECMAScript 6简介(略)
第2章 let和const命令
ES6 中声明变量的6种方法:var 、function、let、const、import、class
let
声明的变量只在其代码块中有效
不存在变量提升
暂时性死区:只要块级作用域内存在
let命令,它所声明的变量就绑定这个区域,不再受外部的影响。在代码块中,使用let命令声明变量之前,该变量都是不可用的,这在语法上称为暂时性死区不允许在相同作用域中重复声明同一个变量
块级作用域
ES5没有块级作用域不合理的场景:
内层变量可能会覆盖外层变量
用来计数的循环变量泄露为全局变量
ES6中的
let实际上为javascript新增了块级作用域,块级作用的出现使得立即执行匿名函数不再必要
const
声明一个只读的常量,一旦声明,常量的值就不能改变,只声明不赋值会报错
const的本质:实际上保证的并不是变量的值不可改动,而是变量指向的那个内存地址不得改动,对于简单数据类型而言,值就保存在变量指向的那个内存地址不得改动,而对于复杂类型而言,变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的则不能控制,例如可以为const声明的常量添加属性,但不能将其直接改为其他对象
第3章 变量的解构赋值
数组的解构赋值(按次序):
若解构不成功,则变量的值等于
undefined若等号右边的不是数组(严格来说是不可遍历结构),则会报错
只要某种数据结构具有
Iterator接口,则都可以采用数组形式的解构赋值允许指定默认值
对象的解构赋值(按同名属性名):
数组的解构是与数组中元素的顺序保持一致的,但对象不同,对象解构的变量名必须与属性同名才能取得值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' } foo // 'aaa' bar // 'bbb' // 实际上是(上面是应用了对象的属性简写) let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' } foo // 'aaa' bar // 'bbb' // 若变量名和属性名不一致,则 let { foo: a, bar: b } = { foo: 'aaa', bar: 'bbb' } a // 'aaa' b // 'bbb' foo // error: foo is not defined bar // error: bar is not defined对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量(上述代码中的a、b),真正被赋值的是后者,而不是前者
允许指定默认值
字符串的解构赋值
数值和布尔值的解构赋值:若等号右边是数值或者布尔值,则会先转为对象
函数参数的解构赋值
第4章 字符串的扩展
Unicode相关:codePonitAt()、codePointAt()、String.fromCodePonit()、at()、normalize()字符串实现遍历器接口:可使用
for...of循环遍历判断一个字符串是否包含在另一个字符串中:
includes():返布尔值,表示是否找到了参数字符串startsWith():返布尔值,表示参数字符串是否在源字符串的头部endsWith():返布尔值,表示参数字符串是否在源字符串的尾部
repeat():返新字符串,表示将原字符串重复n次,但参数不同时表现不同,参数为:正整数:最佳实践
0:返空字符串
字符串:会先转换为数字
小数:向下取整
负数或无穷:报错
0到-1:等用于取0,返空字符串
字符串补全:
padStart():头部补全padEnd():尾部补全均接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数则是用来补全的字符串
模板字符串(``):常用于拼接,反斜杠
\转义,${}、<%= ... %>放置js代码
第5章 正则的扩展(略)
字符串中可使用正则表达式的方法:
match()===RegExp的exec()方法 => 返回匹配项的数组,无匹配项返回nullreplace()search()=> 返回第一个匹配项的索引,没找到返回-1split()
RegExp的方法exec()test()=> 返布尔值
第6章 数值的扩展
Number方法的扩展
Number.isFinite():判断一个数值是否是有限的,返布尔值Number.isNaN():判断一个数值是否是NaN,返布尔值
这两个方法与传统的全局方法 isFinite() 和 isNaN() 的差别在于,传统方法先调用 Number() 将非数值转化为数值,再进行判断,而方法只对数值,对于非数值一律返回 false
Number.parseInt() 、Number.parseFloat():将全局方法parseInt()和parseFloat()移植到Number对象上,行为不变Number.isInteger():判断一个值是否为整数,返布尔值,对25.0返回true,对非数值一律返回falseNumber.EPSILON:极小的常量,本质是一个可以接受的误差范围Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER表示安全数值的上下限Number.isSafeInteger()判断一个整数是否落在安全范围内,返布尔值,对25.0返回true,对非数值一律返回false
Math 对象的扩展
Math.trunc():去除小数部分取整,对于非数值,先用Number()转为数值,对于空值和无法截取整数的值,返回NaNMath.sign():判断一个数值是正数、负数还是0,对于非数值,会先用Number()转为数值,返回值有5种情况:参数为正数,返回+1
参数为负数,返回-1
参数为0,返回0
参数为-0,返回-0
其他值,返回
NaN
Math.cbrt():用于计算一个数的立方根,对于非数值,会先用Number()转为数值
第7章 函数的扩展
设置函数参数的默认值,和解构赋值默认值结合使用
当设置了参数默认值时,传入
undefined会触发默认值,传入null则不会触发默认值当设置了参数默认值时,函数的
length属性等于全部参数的数量减去设置了默认值参数的数量,不包含rest多余参数函数参数的作用域:当设置了参数默认值时,参数会形成一个单独的作用域,等到初始化结束,这个作用域会消失,这种语法行为在不设置参数默认值时是不会出现的
使用
rest参数(形式为"...变量名"),用于获取函数的多余参数,后面不能再其他参数,搭配的变量是一个数组,可替代内部的arguments对象,如:
fucntion add(...values) {
let sum = 0
for(var val of values){
sum += val
}
return sum
}
add(1,2,3) // 6
箭头函数注意事项
函数体内的
this对象就是定义时所在的对象,而不是使用时所在的对象不可以当做构造函数,不能使用
new命令不可以使用
arguments对象,如果要用可用rest参数代替不可以使用
yield命令,不能用做Generator函数
尾调用、尾递归及其优化(略)
第8章 数组的扩展
扩展运算符...
将一个数组转化为用逗号分隔的参数序列
- 替代数组的
apply方法,如
// ES5
Math.max.apply(null,[1,2,3])
// ES6
Math.max(...[1,2,3])
// ES5
var arr1 = [0,1,2]
var arr2 = [3,4,5]
Array.propotype.push.apply(arr1,arr2)
// ES6
var arr1 = [0,1,2]
var arr2 = [3,4,5]
arr1.push(...arr2)
- 合并数组
// ES5
[1,2].concat(more)
// ES6
[1,2,...more]
// ES5
arr1.concat(arr2,arr3)
// ES6
[...arr1,...arr2,...arr3]
- 与解构赋值结合,如
// ES5
var a = list[0]
var rest = list.slice(1)
// ES6
[a,...rest] = list
用于数组赋值时候,只能置于参数的最后一位
- 任何实现了
Iterator接口的对象都可以用扩展运算符转为真正的数组,扩展运算符内部调用的就是数据结构的Iterator接口
Array.from()
定义:将类数组对象(常见有NodeList、arguments)和可遍历对象(部署了 Iterator 接口)转为真正的对象
// 类数组
var arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
// ES5
var arr = [].slice().call(arrayLike) // ['a','b','c']
// ES6
var arr = Array.from(arrayLike) // ['a','b','c']
任何具有 length 属性的对象及部署了 Iterator 接口的对象,都可以通过 Array.from() 转为真正的数组
接受第2个参数,用来对每个元素进行处理,将处理后的值放入返回的数组
Array.of()
定义:将一组值转为数组,弥补 Array() 因参数个数不同导致的行为差异
数组实例的方法
copyWithin()
find()
用于找到第一个符合条件的数组成员,若无返回 undefined
findIndex()
用于找到第一个符合条件的数组成员的索引,无则返回 -1
find()和findIndex()第1个参数均为回调函数,回调函数中的3个参数分别为当前的值、当前的索引、原数组;第2个参数均为用来绑定回调函数的 this 对象
findIndex() 配合 Object.is() 方法可弥补 indexOf() 不能发现NaN的不足
[NaN].indexof(NaN) // -1
[NaN].findIndex(y => Object.is(NaN,y)) // 0
fill()
使用给定值填充一个数组
entries()、keys()、values()
均返回一个遍历器对象,可用 for...of 循环遍历,其中 entries() 返回的是单项是数组形式
let arr = ['a','b']
for(let index of arr.keys()){
console.log(index)
}
// 0
// 1
for(let value of arrr.values()){
console.log(value)
}
// 'a'
// 'b'
for(let [index,value] of arr.entries()){
console.log(index,value)
}
// 0 'a'
// 1 'b'
for(let item of arr.entries()){
console.log(item)
}
// [0,'a']
// [1,'b']
includes()
表示数组是否包含给定的值,返布尔值,相比于 indexOf() 更直观,解决了 indexOf() 对NaN的误判
Map 的 has()
用来查找键名
Set 的 has()
用来查找键值
第9章 对象的扩展
属性的简写:只写属性名,不写属性值,此时属性值等于属性名所代表的的变量
属性名可使用表达式:以方括号
[]表示的属性名中可使用表达式
Object.is()
比较两值是否相等,可看做时 === 的增强版
==会自动转换类型===比较+0和-0时返回true,比较NaN时会返回false+0 === -0 // true NaN === NaN // false Object.is(+0,-0) // false Object.is(NaN,NaN) // true
Obejct.assign(target,source1,source2)
用于将源对象( source )的所有可枚举属性复制到目标对象( target )上,同名属性时后面属性会覆盖前面属性,若属性值为对象,则仅复制其引用值(浅拷贝)
用途:为对象添加属性、方法,克隆对象、合并多个对象、为属性指定默认值
属性的遍历
for...in:自身和继承的可枚举属性(不含Symbol属性)Object.keys(obj):返回数组,自身的可枚举属性(不含Symbol属性)Object.getOwnPropertyNames(obj):返回数组,自身的可枚举属性和不可枚举属性(不含Symbol属性 )Object.getOwnPropertySymbols(obj):返回数组,自身Symbol属性Reflect.ownKeys(obj):返回数组,自身可枚举属性和不可枚举属性和Symbol属性
读取、设置对象的 prototype 对象
Object.getPrototypeOf(obj) 、Object.setPrototypeOf(obj,prototype) ,尽量不使用 __proto__
Object.keys()、Object.values()、Object.entries()
均返回数组
Object.keys():数组每一项为键名Object.values():数组每一项为键值`Object.entries():数组每一项为键值对数组let obj = { id: '1', name: 'ljx' } let arr = Object.entries(obj); // [['id','1'],['name','ljx']] for(let [k,v] of arr){ console.log(k,v) } // 'id' '1' // 'name' 'ljx'
for...of 和 for...in 的区别:
for...in :遍历对象
for...of :遍历数组、字符串等任何部署了 Iterator 接口的数据结构,不能用于普通对象,会报错
解构赋值(浅复制):
数组中:
let [a,...b] = [1,2,3] a // 1 b // [2,3]对象中:
let {x,y,...z} = {x: 1,y: 2,a: 3,b: 4} x // 1 y // 2 z // {a: 3,b: 4}
扩展运算符:
克隆对象:
let a = {id: '1',name: 'ljx'} let b = {...a}; b // {id: '1',name: 'ljx'} // 相当于 let c = Object.assign({},a) c // {id: '1',name: 'ljx'}合并对象:
let a = {id: '1'} let b = {name: 'ljx'} let c = {...a,...b} c // {id: '1',name: 'ljx'} // 相当于 let d = Object.assign({},a,b) d // {id: '1',name: 'ljx'}
Object.getOwnPropertyDescriptor(obj,key)
返回对象指定属性的描述对象
let obj = {
id: '1'
}
let desc = Object.getOwnPropertyDescriptor(obj,'id')
desc // {value: '1',writable: true,enumberable: true,configurable: true}
Object.getOwnPropertyDescriptors(obj)
返回对象所有属性的描述对象
let obj = {
id: '1',
name: 'ljx'
}
let descs = Object.getOwnPropertyDescriptors(obj)
descs
// {
// id: {value: '1',writable: true,enumberable: true,configurable: true},
// name: {value: 'ljx',writable: true,enumberable: true,configurable: true}
// }
Null 传导运算符(提案):?.
解决读取对象内部属性的时候,往往需要先判断该对象是否存在,若改为 Null 传导运算符则只要其中一个返回 null 或 undefined ,则不再继续运算
let result = (a && a.b && a.b.c) || 'default'
// Null运算符表示
let result = a?.b?.c || 'default'
第10章 Symbol
ES6 新引入的原始数据类型,类似于字符串,表示独一无二的值,使用 Symbol 函数生成,前面不能用 New ,可接受一个字符串作为参数(仅表示描述,即使相同参数返回值还是不相等的),若传参为对象,会先调用对象的 toString() 方法,作为对象属性名的时候不能使用点运算符
Symbol作为对象属性是公有属性,但不会被常规方法遍历得到,可利用这个特性定义对象的非私有但又希望只用于内部的属性方法Symbol作为对象属性遍历的方法:Object.getOwnPropertySymbols():放回对象所有作为属性名的Symbol值的数组Reflect.ownkeys():返回数组,自身可枚举属性和不可枚举属性和Symbol属性
Symbol.for(key)
接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值,若有,则返回这个 Symbol 值,若无则新建 ,并登记在全局环境中供搜索(搜索的范围是用 Symbol.for() 创建的 Symbol 值,因为 Symbol() 生成的 Symbol 值是不会被登记在全局环境中供搜索的,每次调用都是生成一个不同的 Symbol 值)
let a = Symbol.for('name')
let b = Symbol.for('name')
a === b // true
Symbol.keyFor()
返回一个已登记的 Symbol 类型值的 key ,若未登记则返回 undefined
let a = Symbol.for('name')
Symbol.keyFor(a) // 'name'
内置 Symbol 值
指向语言内部使用的方法
Symbol.hasInstanceSymbol.isConcatSpreadableSymbol.speciesSymbol.matchSymbol.replaceSymbol.searchSymbol.splitSymbol.iteratorSymbol.toPrimitiveSymbol.toStringTagSymbol.unscopables
第11章 Set和Map数据结构
Set
类似数组,但值唯一,没有重复,构造函数接收数组或类数组对象作为参数,想 Set 中加入值时不会发生类型转换,认为 NaN 等于 NaN,且两个对象总是不相等的,即使是两个空对象
**利用 Set 去重:[...new Set(arr)] 或 Array.from(new Set(arr)) **
let arr1 = [1,1,2,3]
let arr2 = [...new Set(arr1)] // [1,2,3]
let arr3 = Array.from(new Set(arr1)) // [1,2,3]
实例属性
size:成员总数
实例操作方法
add(value):增值,返回Set结构本身delete(value):删值,返回表示删除成功与否的布尔值has(value):返回是否包含value的布尔值clear():清空,无返回值
实例遍历方法(优先使用 values()):
keys()/values():返回键名/键值的遍历器(由于Set键名和键值是同一个值,故这两个方法行为完全一致)entries():返回键值对数组的遍历器let a = new Set(['a','b','c']) for(let item of a.entries){ console.log(item) } // ['a','a'] // ['b','b'] // ['c','c']forEach():使用调函数遍历每个成员,无返回值for...of:由于Set实例默认可遍历,其默认遍历器生成函数就是values()方法,所以直接用for..of循环Set就行,不用加上values()方法
使用 Set 实现并集(Union)、交集(Intersect)和差集(Difference)
let arr1 = [1,2]
let arr2 = [2,3]
// 并集
let union = new Set([...arr1,...arr2]) // Set {1,2,3}
// 交集
let intersect = new Set(arr1.filter(x => arr2.includes(x))) // Set {2}
// 差集
let difference = new Set(arr1.filter(x => !arr2.includes(x))) // Set {1,3}
let arr3 = new Set([1,2])
let arr4 = new Set([2,3])
// 并集
let union = new Set([...arr3,...arr4]) // Set {1,2,3}
// 交集
let intersect = new Set([...arr3].filter(x => arr4.has(x))) // Set {2}
// 差集
let difference = new Set([...arr3].filter(x => !arr4.has(x))) // Set {1,3}
WeakSet
与 Set 类似,但成员只能是对象,且成员对象都是弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的引用,所以 WeakSet 成员不适合引用,会随时消失,不可遍历
方法:add(value) 、 delete(value) 、 has(value)
Map
类似于对象,但解放了对象只能使用字符串作为键名的限制,相当于对象提供了 “字符串 — 值 ” 的对应,而 Map 提供了 “ 值 — 值 ” 的对应
构造函数的参数,可以是数组,类似 [['id','1'],['name','ljx']] 的结构,也可以是任何具有 Interator 接口且每个成员都是一个双元素数组的数据结构,所以 Set 和 Map 都可以做参数,多次赋值同一个键则覆盖,只有对同一对象的引用,Map 结构才将其视为同一个键(即根据内存地址判断)
实例属性
size:成员数量
实例操作方法
set(key,value):设置键名、键值,返回该Map结构,可链式调用get(key):获取key对应的键值,找不到返回undefinedhas(key):返回是否包含key的布尔值delete(key):删除键,返回删除成功与否的布尔值clear():清空,无返回值
实例遍历方法
keys():返回键名的遍历器values():返回键值的遍历器entries():返回所有成员的遍历器forEach():使用调函数遍历每个成员,无返回值for...of:由于Map实例默认可遍历,其默认遍历器生成函数就是entries()方法,所以直接用for..of循环Map就行,不用加上entries()方法
WeakMap
与 Map 类似,但键名只能是对象( null 除外),且键名对象都是弱引用的,键值是正常引用的,即垃圾回收机制不考虑 WeakMap 对该对象的引用,不可遍历,典型的应用场景是,在网页的 DOM 元素上添加数据时使用 Map 结构,当该 DOM 元素被清除时,其对应的 WeakMap 记录就会自动被移除,总之,就是适用于键名对象可能会会在将来消失的场景,有助于防止内存泄露
方法:get(key) 、 set(key,value) 、 has(key) 、 delete(key)
第12章 Proxy(代理)
Proxy 可以理解成在目标对象前架设一个 “拦截” 层,外界对该对象的访问都必须通过这层拦截,提供了一种机制可以对外界的访问进行过滤和改写
生成 Proxy 实例:let proxy = new Proxy(target,handler),其中 target 表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为
要想 Proxy 起作用,必须针对 Proxy 实例进行操作,而不是针对目标对象进行操作,若 handler 上无设任何拦截,等同于直接通向原对象
存在 this 指向问题,在 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理,而并非目标对象
Proxy 支持的所有拦截操作(13种) ,作为 handler 的方法
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) ,返回一个数组。该方法返回目标对象所有自身属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性
getOwnPropertyDescriptor(target,propKey)
拦截 Object.getOwnPropertyDescriptor(proxy,propKey) ,返回属性的描述对象
defineProperty(target,propKey,propDesc)
拦截 Object.defineProperty(proxy,propKey,propDesc) 、object.defineProperties(proxy,propDesc) ,返回一个布尔值
preventExtensions(target)
拦截 Object.preventExtensions(proxy) ,返回一个布尔值
getPrototypeOf(target)
拦截 Object.getPrototypeOf() ,返回一个对象
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)
Proxy.revocable()
- 返回一个对象,其
proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例,当执行完revoke函数再访问Proxy实例,会报错 - 使用场景:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许下次再次访问
let target = {}
let handler = {}
let { proxy, revoke } = Proxy.revocable(target, handler)
proxy.foo = 123
proxy.foo // 123
revoke()
proxy.foo // TypeError: Revoked
第13章 Reflect
为了优化操作对象而生,可以看做是 Object 对象方法的优化扩展版,设计目的:
将
Object对象的一些明显属于语言内部的方法(如Object.defineProperty())放在Reflect上,现在Object和Reflect上均存在,但将来新方法只在Reflect上部署修改某些
Object方法的返回结果,让其变得更合理,如Object.defineProperty(obj,name,desc)在无法定义属性时会报错,但Reflect.defineProperty(obj,name,desc)则会返回false让
Object操作都变成函数行为,如name in obj和delete obj[name]分别变成Reflect.has(obj,name)和Reflect.deleteProperty(obj,name)让
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法,使得Proxy对象能方便地调用对应的Reflect方法来完成默认行为
Reflect 对象的静态方法(13个):
**对于一些需要传入对象作为参数的方法,Reflect 对象方法表现的更为严谨,即当参数不是对象时,会直接报错,而非像对应的 Obejct 方法一样会将参数先转化为对象 **
Reflect.get(target,name,receiver):返回target的name属性,若无返回undefinedReflect.set(target,name,value,receiver):设置target的name属性等于value,返回设置成功与否的布尔值Reflect.has(obj,name):判断Obj是否包含name属性,返布尔值Reflect.deleteProperty(obj,name):删除Obj的name属性,返布尔值Reflect.construct(target,args):等同于new target(...args),提供了一种不使用new来调用构造函数的方法,如function A(name){ this.name = name } // new 的写法 const instance = New A('ljx') // Reflect.construct的写法 const instance = Reflect.construct(A,['ljx'])Reflect.getPrototypeOf(obj):用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)Reflect.setPropertypeOf(obj,newProto):用于设置对象的__proto__属性,返回第一个参数对象,对应Object.setPrototypeOf(obj,newProto)Reflect.apply(func,thisArg,args):等同于Function.prototype.apply.call(func,thisArg,args),用于绑定this对象后执行给定函数Reflect.defineProperty(target,propertyKey,attributes):等同于Object.defineProperty(),用来为对象定义属性Reflect.getOwnPropertyDescriptor(target,propertyKey):等同于Object.getOwnPropertyDescriptor(),用于获得指定属性的描述对象Reflect.isExtensible(target):对应Object.isExtensible(),返回一个布尔值,表示当前对象是否可扩展Reflect.preventExtensions(target):用于使目标对象变为不可扩展的,返回一个布尔值,百世是否操作成功Reflect.ownKeys(target):用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames()与Object.getOwnPropertySymbols()之和
使用 Proxy 实现观察者模式
第14章 Promise对象
Promise 是异步编程的一种解决方案,简单来说,是一个容器,保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上来说,Promise 是一个对象,从它可以获取异步操作的消息
特点
对象的状态不受外界影响,有3种状态,分别是
Pending(进行中)、Fulfilled(已成功)、Rejected(已失败),只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态一旦状态改变就不会再变,任何时候都可以得到这个结果,
Promise对象的状态改变只有两种可能,从Pending变为Fulfilled和从Pending变为Rejected,只要这两种情况发生,状态就凝固定型(Resolved)了,不会再变,此时再对其添加回调函数,也是会立即得到这个结果无法取消
Promise,一旦新建就会立即执行,无法中途取消
构造函数生成 Promise 实例
let promise = new Promise((resolve,reject) => {
if(/* 异步操作成功 */){
resolve(value)
}else{
reject(error)
}
})
Promise.prototype.then()
作用是为 promise 实例添加状态改变时的回调函数,第一次参数是 Resolved 状态的回调函数,第二个参数(可选)是 Rejected 状态的回调函数,then() 方法返回的是一个新的 Promise 实例,因此可以使用链式写法,即 .then().then()
Promise.prototype.catch()
是 .then(null,rejection) 的别名,用于指定发生错误时的回调函数,**Promise 对象的错误具有 “冒泡” 性质,会一直向后传递,直到被捕获位置,也就是说,错误总会被下一个 catch 语句捕获。一般来说,不要在 then() 方法中定义 Rejected 状态的回调函数(即 then 的第二个参数),而应总使用 catch() 方法
Promise.all()
用于将多个 Promise 实例包装成一个新的 Promise 实例,接受一个具有 Iterator 接口的包含多个 Promise 实例的集合(通常为数组),若集合中参数非 Promise 实例,会先调用 Promise.resolve() 方法将其转化为 Promise 实例
let p = Promise.all([p1,p2,p3])
上述代码中,p 的状态由 p1、p2、p3 决定,分两种情况
只有
p1、p2、p3的状态都变为Fulfilled,p的状态才会变成Fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数只要
p1、p2、p3中有一个被Rejected,p的状态就变成Rejected,此时第一个被Rejected的实例的返回值会传递给p的回调函数(如果作为参数的Promise实例自身定义了catch方法,那么它被rejected时并不会触发Promise.all().catch()方法)
Promise.race()
同样是将多个 Promise 实例包装成一个新的 Promise 实例,参数处理和 Promise.all() 一致,但返回的状态取决于最先改变状态的实例,只要 Promise 实例中有一个率先改变状态,返回的状态就跟着改变,并将返回值传出到新的 Promise 的回调函数
Promise.resolve()
将现有对象转化为 Promise 对象
参数为
Promise实例时:不做任何修改,原封不动的返回这个实例参数为
thenable对象时:thenable对象是指具有then()方法的对象,会将这个对象转为Promise对象,然后立即执行thenable对象的then方法参数不是
thenable对象或根本不是对象或不带任何参数时:返回一个Resolved状态的Promise对象(回调函数立即执行)
Promise.reject()
返回一个状态为 Rejected 的 Promise 对象,参数会原封不动的作为 reject 的理由变成后续方法的参数
Promise.done()
总是处于回调链的尾端,保证抛出任何可能出现的错误
Promise.finally()
接受一个回调函数作为参数,该函数不管最终状态怎样都必须执行
第15章 Iterator(遍历器) 和 for...of 循环
Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署 Iterator 接口,就可以完成遍历操作,此时称该数据结构是可遍历的
作用:
为各种数据接口提供一个统一的、简便的访问接口
使得数据结构的成员能够按某种次序排列
供
for...of消费
默认的 Iterator 接口部署在数据结构的 [Symbol.ietrator] 属性上,换句话说,数据结构只要具有 [Symbol.iterator] 属性,就可认为是可遍历的,执行 [Symbol.iterator] 方法,会返回一个遍历器对象,该对象的根本特征是具有 next() 方法,每次调用 next() 都会返回一个代表当前成员的信息对象,具有 value 和 done 两个属性,此外还具有 return() 方法和 throw() 方法
原生具有 Iterator 接口的数据结构
ArrayStringSetMapTypeArray函数的 arguments 对象NodeList 对象
默认调用 Iterator 接口场合
解构赋值:对数组和
Set结构进行解构赋值时扩展运算符(
...)yield*:yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
其他调用 Iterator 接口场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合其实都调用了遍历器接口,比如:
for...ofArray.from()Map()、Set()、WeakMap()、WeakSet()Promise.all()Promise.race()
部署 Iterator 接口
最简易的方法:使用 Generator 函数
let obj = {}
obj[Symbol.iterator] = function* (){
yield 'a'
yield 'b'
yield 'c'
}
for(let value of obj){
console.log(value)
}
// a
// b
// c
第16章 Generator(生成器) 函数的语法
Generator 函数是一种异步编程解决方案( async 函数就是 Generator 函数的语法糖),可以理解为一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象
特点
function命令和函数名之间有一个*星号函数内部使用
yield(产出)语句定义不同的内部状态调用
Generator函数后,该函数并不执行,而是返回一个指向内部状态的遍历器对象,之后需要调用next()方法使指针移向下一个状态,也就是说,每一次调用next()方法,内部指针就从函数头部或上一次停下来的地方继续执行,直到遇到下一条yiled语句(或return语句),换句话说,Generator函数是分段执行的,yield语句是暂停执行的标记,而next()方法可以恢复执行
function* abc(){
yield 'a'
yield 'b'
return 'c'
}
let gen = abc() // 返回遍历器对象
gen.next() // {value: 'a', done: false}
gen.next() // {value: 'b', done: false}
gen.next() // {value: 'c', done: true}
gen.next() // {value: undefined, done: true}
遍历器对象的 next() 方法的运行逻辑:
遇到
yield语句就暂停执行后面的操作,并将紧跟在yield后面的表达式的值作为返回的对象的value属性值下一次调用
next()方法时再继续往下执行,直到遇到下一条yield语句如果没有遇到新的
yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值如果该函数没有
return语句,则返回对象的value属性值为undefined
yield 表达式为惰性求值,若 yield 表达式用在另一个表达式中,必须放在圆括号内
在 Generator 函数内调用另一个 Generator 函数,默认是无效的,需要使用 yield* 表达式
Generator 函数作为对象属性时的简写:
// 原写法
let obj = {
gen: function* (){
}
}
// 缩写
let obj = {
* gen() {
}
}
Generator 函数的应用:
异步操作的同步化表达
控制流管理
部署
Iterator接口作为数据结构
第17章 Generator 函数的异步应用(略)
第18章 async 函数
Generator 函数的语法糖
async 函数对 Generator 函数的改进
内置执行器,即与普通函数的执行一样,不需要调用
next()方法明确的语义,用
async代替*,await代替yield更好的适用性,
await命令后面可以是Promise对象和原始类型的值(数值、字符串和布尔值,但此时等同于同步操作,因为当不是Promise对象时,会将其转为一个立即resolve的Promise对象)返回值是
Promise,比起Generator函数的返回值是Iterator对象更方便,可以用then()方法指定下一步的操作
用法
async函数返回一个Promise对象,可以使用then()方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回(取决于await后面是否为异步操作),等到异步操作完成,再接着执行函数体内后面的语句async函数内部的return语句返回的值,会成为then()方法回调函数的参数async函数返回的Promise对象必须等到内部所有的await命令后面的Promise对象执行完才会发生状态改变,除非遇到return语句或抛出错误,也就是说,只有async函数内部的异步操作执行完,才会执行then()方法指定的回调函数正常情况下,
await命令后面是一个Promise对象,如果不是,会转为一个立即resolve的Promise对象,如果await命令后面的Promise对象变为reject转态,则reject的参数会被catch方法的回调函数接收到,且只要一个await后面的Promise对象变为reject,那么整个async函数都会中断执行,相当于async返回的Promise对象被reject
进阶用法
- 由于
await后面的Promise被rejected时会中断后续操作,所以最好将await命令放在try...catch代码块中(可放置多个)
async function func() {
try{
await doSomething1()
await doSomething2()
}catch(err){
console.log(err)
}
}
- 若多个
await命令后面的异步操作不存在继发关系,则最好同时触发
// 继发关系,只有等 getFoo 完成以后才会执行 getBar
let foo = await getFoo()
let bar = await getBar()
// 同时触发写法1
let [foo,bar] = await Promise.all([getFoo(),getBar()])
// 同时触发写法2
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise
第19章 Class的基本语法(略)
第20章 Class的继承(略)
第21章 修饰器(略)
第22章 Module的语法
ES6 之前的模块加载方案,主要有用于服务器的 CommonJS 和 用于浏览器的 AMD ,ES6 模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块都是只能在运行时确定这些东西,如 CommonJS 模块就是对象,输入时必须查找对象属性
// CommonJS 模块
let { stat, exists, readFile } = require('fs')
// 等同于
let _fs = require('fs')
let stat = _fs.stat
let exists = _fs.exists
let readFile = _fs.readfile
上述代码实质是整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象 (fs),然后再从这个对象上面读取3个方法,这种加载称为 ""运行时加载",因为只有运行时才能得到这个对象,导致完全没办法在编译时进行 “静态优化”
ES6 模块不是对象,而是通过 export 命令显示指定输出的代码,再通过 import 命令输入
// ES6 模块
import { stat, exists, readfile } from 'fs'
上述代码实质上是从 fs 模块加载3个方法,而不是加载其他方法。这种加载称为 编译时加载 或 静态加载,即 ES6 可以在编译时就完成模块加载,效率比 CommonJS 模块的加载方式高
ES6模块中,顶层的 this 指向 undefined ,即不应该在顶层代码中使用 this
export
// 写法1
export const a = 'a'
export function b(){}
// 写法2
const a = 'a'
function b(){}
export { a, b }
// 写法3 (重命名 old as new)
const a = 'a'
function b(){}
export {
a as renameA,
b as renameB
}
export 语句输出的接口与其对应的值是动态绑定关系,即通过该接口可以获取模块内部实时的值,而 CommonJS 模块输出的是值的缓存,不存在动态更新
import
// 写法1
import { a, b } from "模块路径(相对/绝对)"
// 写法2 (重命名 old as new)
import { a as renameA } from "模块路径(相对/绝对)"
// 写法3 (整体加载,all为自己的重命名)
import * as all from "模块路径(相对/绝对)"
all.a
all.b
// 整体加载所在的对象是可以静态分析的,所以不允许运行时改变,故以下代码是不允许的
all.a = '666'
all.b = function() {}
import 命令会有提升效果,会提升到整个模块的头部并首先执行,因为 import 命令是在编译阶段执行的,在代码运行之前,如
foo()
import { foo } from "模块路径(相对/绝对)"
export default 和对应的 import :当 export default 时其中函数或变量的名称变得不再重要(其实是输出一个叫做 default的变量,后面不能跟变量声明语句),此时 import 时可取任意名,且不需要大括号,如
export default function a() {}
// 此时等同于
// export { a as default }
import b from '模块路径(相对/绝对)'
// 此时等同于
// import { default as b } from "模块路径(相对/绝对)"
import() 函数(提案):由于是静态加载,所以无法在代码运行时加载,即不能在代码块中动态加载,该函数就是解决此类动态加载问题的(按需加载、条件加载、动态的模块路径等)