ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ES6给我们提供了很多js的新特性和规范,使得我们编写Js代码更加灵活和强大,接下来让我们来学习一下吧。
let和const
let声明的变量只在它所在的代码块有效。for循环计数很适合此变量
1 | for (let i = 0; i < 10; i++) { |
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
1 | for (let i = 0; i < 3; i++) { |
不存在变量提升
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
let不允许在相同作用域内,重复声明同一个变量
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错
const声明一个只读的常量。一旦声明,常量的值就不能改变
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心
1 | // 将对象彻底冻结 |
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
变量的解构赋值
解构赋值允许指定默认值
1 | let [foo = true] = []; |
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效
1 | let [x = 1] = [undefined]; |
对象的解构
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
1 | // p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。 |
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量
1 | let { log, sin, cos } = Math; |
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
1 | const [a, b, c, d, e] = 'hello'; |
字符串扩展
字符基础
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。
汉字“𠮷”的码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript 不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。
字符串的遍历器接口
ES6 为字符串添加了遍历器接口,使得字符串可以被for…of循环遍历。
includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
1 | let d = 'Hello world!'; |
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次
如果repeat的参数是负数或者Infinity,会报错。
如果repeat的参数是字符串,则会先转换成数字
参数NaN等同于 0
1 | 'hello'.repeat(2) // "hellohello" |
padStart(),padEnd() 字符串补全
1 | 'x'.padStart(5, 'ab') // 'ababx' |
模板字符串 ${}
大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性
模板字符串之中还能调用函数
1 | // 简单的模板函数 |
标签模板
tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推
1 | let total = 30; |
“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容
1 | let message = |
String.raw()
String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用
1 | String.raw`Hi\n${2+3}!`; |
正则的扩展
u 修饰符
ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码
y 修饰符
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
单单一个y修饰符对match方法,只能返回第一个匹配,必须与g修饰符联用,才能返回所有匹配。
1 | 'a1a2a3'.match(/a\d/y) // ["a1"] |
y修饰符的一个应用,是从字符串提取 token(词元),y修饰符确保了匹配之间不会有漏掉的字符。
1 | const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y; |
sticky 属性
表示是否设置了y修饰符 ,返回true/false
flags 属性
返回正则表达式的修饰符
如何让 . 匹配包括换行符(行终止符)的所有字符
1 | //U+000A 换行符(\n) |
ES5先行断言
”先行断言“指的是,x只有在y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。”先行否定断言“指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。
ES5组匹配
正则表达式使用圆括号进行组匹配
1 | const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/; |
数值扩展
Number.isFinite()
Number.isFinite()用来检查一个数值是否为有限的(finite),如果参数类型不是数值,一律返回false
Number.isNaN()
用来检查一个值是否为NaN
Number.parseInt(), Number.parseFloat()
ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
Number.isInteger()
Number.isInteger()用来判断一个数值是否为整数。
如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数,
1 | // 由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。 |
Number.EPSILON
在Number对象上,新增一个极小的常量Number.EPSILON。它表示 1 与大于 1 的最小浮点数之间的差
Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了
1 | // Number.EPSILON可以用来设置“能够接受的误差范围” |
安全整数和 Number.isSafeInteger()
JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值
ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内
Math 对象的扩展
Math.trunc()
Math.trunc方法用于去除一个数的小数部分,返回整数部分
对于空值和无法截取整数的值,返回NaN
对于非数值,Math.trunc内部使用Number方法将其先转为数值
1 | // 对于没有部署这个方法的环境,可以用下面的代码模拟 |
Math.hypot()
返回所有参数的平方和的平方根
1 | Math.hypot(3, 4); // 5 |
指数运算符(**)
注意,在 V8 引擎中,指数运算符与Math.pow的实现不相同,对于特别大的运算结果,两者会有细微的差异。
1 | let a = 1.5; |
函数扩展
函数参数的默认值
1 | function Point(x = 0, y = 0) { |
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
1 | function f(x, y = 5, z) { |
函数的 length 属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
1 | (function (a) {}).length // 1 |
箭头函数
大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
1 | // 报错 |
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
this指向的固定化
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数
尾调用优化
调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数
1 | function f(x){ |
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
1 | 下面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。 |
尾递归优化
它的原理非常简单。尾递归之所以需要优化,原因是调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出。怎么做可以减少调用栈呢?就是采用“循环”换掉“递归”。
蹦床函数
1 | function trampoline(f) { |
真正的尾递归优化
1 | unction tco(f) { |
数组的扩展
1.扩展运算符
将一个数组转为用逗号分隔的参数序列。
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
替代函数的 apply 方法
1 | // ES5 的写法 |
复制数组
1 | const a1 = [1, 2]; |
合并数组
1 | var arr1 = ['a', 'b']; |
与解构赋值结合
1 | // ES5 |
字符串
JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题
凡是涉及到操作四个字节的 Unicode 字符的函数,最好都用扩展运算符改写。
1 | [...'hello'] |
Array.from()
Array.from方法用于将类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
1 | let arrayLike = { |
扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
1 | // Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。 |
如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。
1 | // Array.from()可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。 |
Array.of()
用于将一组值转化为数组
1 | Array.of(3, 11, 8) // [3,11,8] |
数组实例的 copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组
1 | // 参数说明 |
数组实例的 find() 和 findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象
1 | // find |
数组实例的 fill()
ill方法使用给定值,填充一个数组
1 | ['a', 'b', 'c'].fill(7) |
数组实例的 entries(),keys() 和 values()
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
1 | // 如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历 |
数组实例的 includes()
返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似
1 | [1, 2, 3].includes(2) // true |
数组的空位
数组的空位指,数组的某一个位置没有任何值,ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位,ES6 则是明确将空位转为undefined
对象扩展
属性的赋值器与取值器的简洁写法
简洁写法的属性名总是字符串,所以说属性中有关键字也不会报错
1 | const cart = { |
Object.is()
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
不同之处只有两个:一是+0不等于-0,二是NaN等于自身
1 | Object.is('foo', 'foo') |
Object.assign()
用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
如果只有一个参数,Object.assign会直接返回该参数
如果该参数不是对象,则会先转成对象,然后返回
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错
1 | const target = { a: 1 }; |
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错
1 | let obj = {a: 1}; |
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果
因为只有字符串的包装对象,会产生可枚举属性
1 | const v1 = 'abc'; |
注意点
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用
于嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加
Object.assign可以用来处理数组,但是会把数组视为对象
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制
1 | onst source = { |
常见用途
为对象添加属性
1 | class Point { |
为对象添加方法
1 | Object.assign(SomeClass.prototype, { |
合并多个对象
1 | const merge = |
为属性指定默认值
1 | const DEFAULTS = { |
Object.setPrototypeOf()
用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法
1 | let proto = {}; |
Object.getPrototypeOf()
用来读取一个对象的原型
super 关键字
指向当前对象的原型对象
1 | const proto = { |
super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法
JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)
Object.keys
ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用
1 | let {keys, values, entries} = Object; |
symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)
1 | // Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分 |
Symbol 值不能与其他类型的值进行运算,否则会报错
Symbol 值可以显式转为字符串
1 | let sym = Symbol('My symbol'); |
属性名的遍历
ymbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名
另一个新的 API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
1 | let obj = { |
Symbol.for(),Symbol.keyFor()
有时,我们希望重新使用同一个 Symbol 值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值
ymbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
1 | let s1 = Symbol.for('foo'); |
需要注意的是,Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值
Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构
1 | const s = new Set(); |
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化
1 | // 例一 |
在 Set 内部,两个NaN是相等
另外,两个对象总是不相等的
1 | let set = new Set(); |
Set 实例的属性和方法
Set 结构的实例的属性
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)
操作方法
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。
1 | // 去除数组重复成员的另一种方法 |
遍历操作
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用
keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值,所以keys方法和values方法的行为完全一致。
1 | for (let item of set.entries()) { |
应用
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构
1 | // 转换为数组 |
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
1 | //Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组 |
任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
1 | const set = new Set([ |
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键
我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名
实例的属性和操作方法
(1)size 属性
属性返回 Map 结构的成员总数
(2)set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键
(3)get(key)
get方法读取key对应的键值,如果找不到key,返回undefined
(4)has(key)
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
(5)delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。
(6)clear()
clear方法清除所有成员,没有返回值
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)
1 | const map = new Map([ |
结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤
1 | const map0 = new Map() |
此外,Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历
1 | map.forEach(function(value, key, map) { |
WeakMap
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
WeakMap的键名所指向的对象,不计入垃圾回收机制
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用
WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
应用
向dom节点存入数据
1 | let myElement = document.getElementById('logo'); |
WeakMap 的另一个用处是部署私有属性
1 | const _counter = new WeakMap(); |
Promise
Promise是一种异步编程的解决方案。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
1 | const promise = new Promise(function(resolve, reject) { |
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
1 | // 图片异步加载 |
resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例
1 | const p1 = new Promise(function (resolve, reject) { |
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
1 | new Promise((resolve, reject) => { |
Promise.prototype.then()
then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法
Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
Promise.prototype.finally()
Promise.all()
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
1 | // 生成一个Promise对象的数组 |
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法
Promise.race()
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
只要有一个实例率先改变状态,Promise.race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给Promise.race的回调函数。
1 | // 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve |
Promise.resolve()
将现有对象转为 Promise 对象
1 | // (1)参数是一个 Promise 实例 |
需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
1 | setTimeout(function () { |
Generator函数的异步应用
1.异步:所谓”异步”,简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
1 | var readFile = require('fs-readfile-promise'); |
2.Thunkify 模块
1 | // npm install thunkify |
async函数
async函数返回一个 Promise 对象
async函数内部return语句返回的值,会成为then方法回调函数的参数
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到
1 | async function f() { |
Promise 对象的状态变化: async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。
await 命令: 正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象
1 | // 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行 |
下面的例子使用try…catch结构,实现多次重复尝试
1 | const superagent = require('superagent'); |