ES6 学习笔记(一)
let 和 const
let 命令
基本用法
1
2
3
4
5
6
7// let 声明的变量只在 let 命令所在的代码块内有效
{
let a = 10;
var b = 10;
}
console.log(a); // ReferenceError: a is not defined.
console.log(b); // 101
2
3
4
5// for 循环中同样如此
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: a is not defined.1
2
3
4
5
6
7
8// 另外在 for 循环中,循环变量的那部分是一个父级作用域,循环体内部是一个单独的子作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc不存在变量提升
1
2
3
4
5
6
7// var 的情况
console.log(foo); // undefined
var foo = 'foo';
// let 的情况
console.log(bar); // ReferenceError
let bar = 'bar';暂时性死区(temporal dead zone)
本质上,已进入当前的块级作用域,所需变量就已经存在了,不过不可取,只有等到声明变量的哪一行代码出现才可以获取和使用该变量
1
2
3
4
5
6
7// 在这个例子中,块级作用域已经有了 temp 变量,但没有等到声明语句出现就开始赋值,所以报错
var temp = 123;
if (true) {
temp = 233; // ReferenceError
let temp;
}1
2
3
4
5
6// bar 函数报错,因为 x 的默认值 y 没有声明
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // ReferenceError: y is not defined.1
2// 这个例子同样如此
let x = x; // ReferenceError: x is not defined.不允许重复声明
1
2
3var a = 1;
var a = 2;
let a = 3; // SyntaxError: Identifier 'a' has already been declared.块级作用域与函数声明
ES6中规定,块级作用域中函数声明语句的行为类似于
let
,在块级作用域之外不可用。
但在浏览器中的实现可以不遵守以上规定,有自己的行为方式
- 允许在块级作用域内声明函数
- 函数声明类似于var
,即会提升到全局作用域或函数作用域的头部
- 同时函数声明还会提升到所在的块级作用域的头部
上述的三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作 let 处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 浏览器的 ES6 环境
(function () {
if (false) {
function f() {
console.log('I am inside!');
}
}
f(); // TypeError: f is not a function.
}());
// 在符合 ES6 的浏览器中等同于一下代码
(function () {
var f = undefined;
if (false) {
function f() {
console.log('I am inside!');
}
}
f(); // TypeError: f is not a function.
}());所以应该避免在块级作用域内声明函数,确实需要的话也应该写成函数表达式,而非声明语句
1
2
3
4// 另外块级作用域需要大括号,否则也会报错
if (true)
function f() {} // In strict mode code, functions can only be declared at top level or inside a block.
const命令
const
声明的通常是一个只读的常量,一旦声明,值不可被改变。本质上 const
保证的并不是变量的值而是变量指向的内存地址。对于简单类型的数据,值就是保存在变量指向的那个内存地址。而复合类型的数据,变量指向的内存地址保存的是一个指针,指向这个数据结构,指针是固定的,但数据结构可能会改变。
另外 const
与 let
一样,声明的常量不提升,存在暂时性死区以及不可重复声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// const 声明的简单类型的数据不可改变
const PI = 3.1415;
PI = 123; // TypeError: Assignment to constant variable.
// const 只声明不赋值也会报错
const foo; // SyntaxError: Missing initializer in const declaration
// const 声明基本类型与引用类型的区别
const a = [];
a.push("hello");
a = ["hello"]; // TypeError: Assignment to constant variable.
// 从 ES6 开始,let 命令,const 命令,class 命令声明的全局变量不属于顶层对象的属性
let a = 1;
window.a // undefined
var b = 2;
window.b // 2
解构赋值
- 基本用法
1 | let [a, b, c] = [1, 2, 3]; |
- 默认值
1 | // 解构赋值允许指定默认值 |
- 对象的解构赋值
1 | // 基本用法 |
- 字符串的解构赋值
1 | const [a, b, c, d, e] = 'hello'; |
- 数值和布尔值的解构赋值
1 | // 进行解构赋值时会将右边的数值或布尔值转换成对象 |
- 函数参数的解构赋值
1 | // 基本用法 |
解构赋值用途
- 交换变量的值
- 从函数返回多个值
- 函数参数的定义
- 提取 JSON 数据
- 函数参数的默认值
- 遍历 Map 结构
- 输入模块的指定方法
模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识
当作普通字符串使用
1
2
3
4var a = `this is a \n string`;
a;
// 'this is a
// string'用来定义多行字符串,所有的空格和缩进会被保留在输出之中
1
2
3
4
5
6
7var b = `this is a temp late strin
g`;
b;
// 'this is a temp late strin
//
// g'在字符串中嵌入变量,需要将变量名写在
${}
之中,并且大括号内还可以放入任意的 JavaScript 表达式1
2
3
4// 大括号内可以放入变量名或表达式,未声明的变量会报错
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`; // '1 + 2 = 3'1
2
3
4
5
6// 模板字符串还能调用函数
function fn() {
return 'Hello World';
}
`foo ${fn()} bar`; // foo Hello World bar标签模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 将模板字符串紧跟在一个函数后面,该函数将被调来处理这个模板字符串
alert`123`; // 等同于
alert(123);
// 如果模板字符串里面有变量,需要先将模板字符串处理成多个参数,再调用函数
let a = 5;
let b = 10;
function tag(stringArr, value1, value2) {
// ...
}
tag`Hello ${a + b} world ${a * b}`; // 等同于
tag(['Hello ', ' world '], 15, 50);
// 标签模板还有一个重要应用就是过滤 HTML 字符串,防止用户输入恶意内容
let sender = `<script>alert("devil script");</script>`;
function SaferHTML(templateData) {
let s = templateData[0];
for (let i = 1; i < arguments.length; i++) {
let arg = String(arguments[i]);
s += arg.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
s += templateData[i];
}
return s;
}
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;
message
// "<p><script>alert("devil script");</script> has sent you a message.</p>"
箭头函数
箭头函数表达式的语法比 函数表达式 更短,并且不绑定自己的 arguments
,super
以及 new.target
。其函数体内的 this
对象就是定义时所在的对象,而不是使用时所在的对象。另外箭头函数不能用作构造函数以及 Generator 函数。
语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// Basic Syntax
(p1, p2, ..., pN) => { statements };
(p1, p2, ..., pN) => expression // 等同于 { expression }
(singleParam) => { statements }
singleParam => { statements } // 单个参数括号可以省略
() => { statements } // 无参数
// Advanced Syntax
params => ({foo: bar}) // 返回值为对象时,需要加上括号
(p1, p2, ...rest) => { statements } // 支持rest参数
(p1, p2, p3 = default) => { statements } // 支持默认参数
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 支持解构赋值使用注意点
- 函数体内的
this
对象其实就是外层代码块的this
,也就是说箭头函数根本没有自己的this
。 - 不可以当作构造函数,即不可以使用
new
命令,否则会抛出一个错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在,可以用rest
代替。 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
- 函数体内的
箭头函数转换成 ES5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
},100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
});
}部署pipeline
1
2
3
4
5
6
7
8
9const pipeline = (...funcs) => val => funcs.reduce((a, b) =>
b(a), val);
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5);
扩展运算符
扩展运算符(spread)是三个点(...
)。类似于 rest
参数的逆运算,将一个数组转为用逗号分隔的参数序列。
扩展运算符调用的是遍历器接口 (Symbol.iterator
),只有具有该接口的对象都可以使用该运算符将其转换成真正的数组
而 Array.from
方法可以将可遍历对象(iterabel)以及 类似数组的对象(array-like object)转换成真正的数组
Array.of
方法则是将一组值,转换为数组
1 | // 基本用法 |
Set 和 Map 数据结构
Set
Set
数据结构类似于数组,但是成员的值都是唯一的,不存在重复的值。Set
函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化
基本用法
1
2
3
4
5const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
// 去除数组的重复成员,Set 内部判断两个值是否相等的算法类似于 '===' ,主要区别在于 NaN 等于自身
[...new Set(array)]属性与方法
属性:
-Set.prototype.constructor
: 构造函数,默认就是Set
函数。
-Set.prototypr.size
: 返回Set
实例的成员总数方法:
-add(value)
:添加某个值,返回 Set 结构本身
-delete(value)
:删除某个值,返回一个布尔值,表示是否删除成功
-has(value)
:返回一个布尔值,表示该值是否为Set
的成员
-clear()
:清除所有成员,没有返回值1
2
3
4
5
6
7
8
9
10
11
12
13let s = new Set();
s.add(1).add(2).add(3).add(2);
s // Set(3) {1, 2, 3}
s.size // 3
s.has(1) // true
s.delete(2);
s.has(2) // false
s.clear() // Set(0) {}
// 应用,数组去重
function dedupe(array) {
return Array.from(new Set(array));
}遍历操作
-
keys()
:返回键名的遍历器
-values()
:返回键值的遍历器
-entries()
:返回键值对的遍历器
-forEach()
:返回回调函数遍历的每个成员1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item); // red green blue
}
// 由于 Set 结构没有键名,只有键值,所以 keys 方法和 values 方法的行为完全一致
for (let item of set.values()) {
console.log(item); // red green blue
}
for (let item of set.entries()) {
console.log(item); // ['red', 'red'] ['green', 'green'] ['blue', 'blue']
}
// Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的 values 方法
Set.prototype[Symbol.iterator] === Set.prototype.values // true
for (let item of set) {
console.log(item); // red green blue
}
// forEach 方法,用于对每个成员执行某种操作,没有返回值
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ': ' + value));
// Set 具有遍历器接口 (`Symbol.iterator`),所以可以用扩展运算符将其变为真正的数组
let set = new Set(['red', 'green', 'blue']);
let arr = [...set]; // ['red', 'green', 'blue']
Map
Map
类似于对象,也是键值对的集合,但“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14// Map 可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
map.set(NaN, 123);
map.get(NaN) // 123属性与方法
-
size
:属性返回 Map 结构的成员总数
-set(key, value)
:方法设置键名key
对应的键值value
,然后返回整个 Map 结构,会覆盖已有的键值
-get(key)
:方法读取key
对应的键值,找不到key
,返回undefined
-has(key)
:方法返回一个布尔值,表示某个键是否在当前Map
对象之中
-delete(key)
:方法删除某个key
,返回true
,如果删除失败,返回false
-clear()
:方法清除所有成员,没有返回值1
2
3
4
5
6
7
8
9const m = new Map();
m.set('year', 2018);
m.set('target', 'done');
m.size // 2
m.get('year') // 2018
m.has('year') // true
m.delete('year') // true
m.clear()
m // Map(0) {}遍历方法
-
keys()
:返回键名的遍历器
-values()
:返回键值的遍历器
-entries()
:返回所有成员的遍历器
-forEach()
:遍历 Map 的所有成员1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key); // 'F' 'T'
}
for (let value of map.values()) {
console.log(value); // 'no' 'yes'
}
for (let [key, value] of map.entries()) {
console.log(key, value); // 'F' 'no' 'T' 'yes'
}
// 等同于使用 map.entries()
// Map 结构的默认遍历接口(Symbol.iterator属性),就是 entries 方法
for (let [key, value] of map) {
console.log(key, value); // 'F' 'no' 'T' 'yes'
}
Iterator 和 for…of循环
基本概念
遍历器(Iterator)是一种接口,为各种不同的数据结构(
Array
,Map
,Set
,String
,TypedArray
,arguments
,NodeList
),提供一个统一的访问机制。使得数据结构的成员能够按某种次序排列。另外在 ES6 中创造了一种新的遍历命令for...of
循环,Iterator 接口主要提供for...of
消费。ES6 规定,默认的 Iterator 接口部署在数据结构的
Symbol.iterator
属性上,或者说一个数据结构只要具有Symbol.iterator
属性,就可以认为是”可遍历的“。执行这个属性会返回一个遍历器对象,该对象的根本特征就是具有
next
方法。每次调用next
方法,都会返回一个代表当前成员的信息对象,具有value
和done
两个属性凡是部署了
Symbol.iterator
属性的数据结构,就称为部署了遍历器接口,调用这个接口,就会返回一个遍历器对象原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- arguments
- NodeList对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。就对象而言,部署遍历器接口不是很必要,因为这使得对象实际上被当作 Map 结构使用,而 ES6 原生提供了
对于类似数组的对象(存在数值键名和
length
属性),部署 Iterator 接口,可以将Symbol.iterator
方法直接引用数组的 Iterator 接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next(); // { vlaue: 'a', done: false }
iter.next(); // { vlaue: 'b', done: false }
iter.next(); // { vlaue: 'c', done: false }
iter.next(); // { vlaue: 'undefined', done: true }
// 一个为对象添加 Iterator 接口的例子
let obj = {
data: ['Hello', 'World'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: true
};
} else {
return { value: undefined, done: true};
}
}
};
}
};
// 类数组对象部署 Iterator 方法
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}使用场合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 1. 解构赋值
// 对数组和 Set 结构进行结构赋值的时候,会默认调用 Symbol.iterator 方法
let set = new Set().add('a').add('b').add('c');
let [first, ...rest] = set; // first = 'a'; rest = ['b', 'c'];
// 2. 扩展运算符
// 扩展运算符(...)也会调用默认的 Iterator 接口
var str = 'Hello';
[...str] // ['H', 'e', 'l', 'l', 'o']
// 3. yield*
// yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
let generator = function* () {
yield 1;
yield* [2, 3, 4];
yield 5;
}
let iterator = generator();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: 3, done: false }
iterator.next(); // { value: 4, done: false }
iterator.next(); // { value: 5, done: false }
iterator.next(); // { value: undefined, done: true }
// 4. 其他场合
// 由于数组的遍历会调用遍历器的接口,所以任何接受数组作为参数的场合,都调用了遍历器接口
// - for...of
// - Array.from()
// - Map(), Set(), WeakMap(), WeakSet()
// - Promise.all()
// - Promise.race()字符串的 Iterator 接口
1
2
3
4
5var str = 'hi';
var iterator = str[Symbol.iterator]();
iterator.next() // { value: 'h', done: false }
iterator.next() // { value: 'i', done: false }
iterator.next() // { value: undefined, done: true }Iterator 接口与 Generator 函数
1
2
3
4
5
6
7
8
9let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
}
for (let x of obj) {
console.log(x); // 'hello' 'world'
}遍历器对象的 return(),throw()
遍历器对象除了具有
next
方法,还可以具有return
方法和throw
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// return 方法的使用场合时,如果 for...of 循环提前退出(出错,break 语句或 continue),就会调用 return 方法
function readLineSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
}
}
}
}
// throw 方法主要配合 Generator 函数使用for…in 与 for…of
for...in
循环可以遍历数组的键名1
2
3for (let index in myArray) {
console.log(myArray[index]);
}缺点:
- 数组的键名是数字,但是for...in
循环是以字符串作为键名,如 ‘1’, ‘2’
-for...in
循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键
- 某些情况下,for...in
循环会以任意顺序遍历键名
-for...in
主要是为遍历对象而设计的,不适用于遍历数组for...of
循环有着同for...in
一样的简洁语法,没有for...in
的诸多缺点。不同于forEach
方法,它可以与break
、continue
和return
配合使用,另外它也提供了遍历所有数据结构的统一接口。
Promise 对象
所谓 Promise
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息
Promise
对象有以下三个特点
对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态,pending
(进行中),fulfiled
(已成功)和rejected
(已失败)。只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态一旦状态改变,就不会再变,任何时候都可以得到这个结果。对象的状态改变只有两种可能:从
pending
变为fulfiled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了。不会再改变,也称为resolved
。无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。另外如果不设置回调函数,Promise
内部抛出的错误不会反应到外部。还有,当处于pending
状态时,无法得知目前进展到哪一个阶段
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 一个 Promise 实例
const promise = new Promise(function (resolve, reject) { // 接受一个函数作为参数,函数的参数分别是 resolve 和 reject
// ... some code
if (/* 异步操作成功 */) {
resolve(value); // 将 Promise 对象的状态从”未完成“,变为”成功“,并将异步操作的结果作为参数传递出去
} else {
reject(error); // 将 Promise 对象的状态从”未完成“,变为”失败“,并将异步操作报出的错误作为参数传递出去
}
});
// 实例生成后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数
promise.then(function(value) {
// success
}, function(error) {
// failure
});
// Promise 新建后就会立即执行
let promise = new Promise(function (resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function () {
console.log('resolved');
});
console.log('end');
// Promise
// end
// resolved
// 如果调用 `resolve` 函数和 `reject` 函数时带有参数,那么它们的参数会被传递给回调函数,resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000);
});
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000);
});
p2.then(result => console.log(result)).catch(error => console.log(error));Promise.prototype.then()
为 Promise 实例添加状态改变时的回调函数,该方法的第一个参数是
resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数then
方法返回的是一个新的Promise
实例,因此可以采用链式写法1
2
3
4
5
6getJSON('/post/1.json').then(
post => getJSON(post.commentURL);
).then(
comments => console.log('resolved: ', comments),
err => console.log('rejected: ', err)
);Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误时的回调函数
跟传统的try/catch
代码块不同的是,如果没有使用catch
方法指定错误处理的回掉函数,Promise
对象抛出的错误不会传递到外层代码,只会在内部打印出错误信息,但是不会退出进程,终止脚本执行1
2
3
4
5
6
7
8
9// 这个例子中三个 Promise 对象中任何一个抛出的错误都会被最后一个 catch 捕获
getJSON('/post/1.json').then(
post => getJSON(post.commentURL);
).then(
// ...some code
).catch(
// ...处理前面三个 Promise 产生的错误
);
// 建议 Promise 对象后面要紧跟 catch 方法,这样可以处理 Promise 内部发生的错误Promise.prototype.finally()
finally
方法用于指定不管Promise
对象最后的状态如何,都会执行的操作。而且该方法的回调函数不接受任何参数,所以没法知道前面的Promise
状态到底是fulfilled
还是rejected
。所以finally
方法里面的操作,应该是与状态无关的,不依赖于Promise
的执行结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// finally 本质上是 then 方法的特例
promise.finally(() => {
// ...some code
});
//等同于
promise.then(
result => {
// ...
return result;
},
error => {
// ...
throw error;
}
);
// finally 方法总是会返回原来的值
Promise.resolve(2).then(() => {}, () => {}); // undefined
Promise.resolve(2).finally(() => {}); // 2
Promise.reject(3).then(() => {}, () => {}); // undefined
Promise.reject(3).then(() => {}, () => {}); // 3Promise.all()
Promise.all
方法用于将多个Promise
实例,包装成一个新的Promise
实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 下面是一个例子,在参数数组中,p1,p2,p3 都是 Promise 对象的实例
const p = new Promise([p1, p2, p3]);
// 参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
// 只要 p1,p2,p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1,p2,p3 的返回值组成一个数组,传递给 p 的 回调函数
// 只要 p1,p2,p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1,p2,p3 的返回值组成一个数组,传递给 p 的 回调函数
// 另外,如果作为参数的 Promise 实例,自己定义了 catch 方法,那么一旦其被 rejected 并不会触发 Promise.all() 的 catch 方法
const p1 = new Promise((resolve, reject) => {
resolve('Hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ['Hello', Error: 报错了]
// 反之,上述例子中如果 p2 没有自己的 catch 方法,则会调用 Promise.all() 的 catch 方法Promise.race()
Promise.race
同样用于将多个Promise
实例,包装成一个新的Promise
实例。1
2// 和 Promise.all 方法很类似,不过不同的是,p1,p2,p3 之中只要有一个实例率先改变状态,p的状态就会跟着改变,其实例的返回值就会传递给 p 的回调函数
const p = Promise.race([p1, p2, p3]);Promise.resolve()
该方法可以将现有的对象转换成 Promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 基本用法
Promise.resolve('foo'); // 等价于
new Promise(resolve => resolve('foo'))
// 参数是一个 Promise 实例,那么 Promise.resolve 将不做任何修改,原封不动地返回这个实例
// 参数是一个 thenable 对象,Promise.resolve 方法将这个对象转换成 Promise 对象,然后立即执行 thenable 对象的 then 方法
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(value => console.log(value)); // 42
// 参数不具有 then 方法的对象,或根本就不是对象
const p = Promise.resolve('Hello');
p.then(value => console.log(value)); // Hello
// 不带有任何参数,这个例子中立即执行的 Promise 对象,是在本轮“事件循环”的结束之前,而不是下一轮的开始时
setTimeout(() => {console.log('three');}, 0);
Promise.resolve().then(() => {console.log('two');});
console.log('one');
// one
// two
// threePromise.reject()
Promise.reject(reason)
该方法也会返回一个新的 Promise 实例,该实例的状态为reject
1
2
3
4
5
6
7
8
9
10const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable);
})
// true
Generator 函数
基本概念
该函数是 ES6 提供的一种异步编程解决方案。从语法上理解,可以把 Generator 函数理解成一个状态机,封装了多个内部状态
执行 Generator 函数 会返回一个遍历器对象,即该函数不仅是状态机,还是一个遍历器对象生成函数。从返回的遍历器对象中,可以依次遍历 Generator 函数内部的每一个状态
相对于普通函数,Generator 函数有两个基本特征。一是,
function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态1
2
3
4
5
6
7
8
9
10
11// 例子
function* helloWorldGenerator() {
yield 'Hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next(); // { value: 'Hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: false }
hw.next(); // { value: undefined, done: true }- yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用
next
方法才会遍历下一个内部状态,所以需要提供一种可以暂停执行的函数。yield
表达式就是暂停标志。且只有调用next
方法并且内部指针指向该语句时才会执行,即近似于手动的“惰性求值”。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错
(function () {
yield 1;
})() // SyntaxError: Unexpected number
// yield 表达式如果用在另一个表达式中,必须放在圆括号里面
function* demo() {
console.log('Hello' + yield); // SyntaxError: Unexpected identifier
console.log('Hello' + yield 123); // SyntaxError: Unexpected identifier
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield + 123)); // OK
}
// yield 表达式用作函数参数或放在赋值表达式的右边,可以不加括号
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}与 Iterator 接口的关系
任意一个对象的
Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var myIterable = {};
myIterable[Symbol.iterator] = function* {
yield 1;
yield 2;
yield 3;
}
[...myIterable] // [1, 2, 3]
// 另外 Generator 函数执行后,返回一个遍历器对象,该对象本身也具有 `Symbol.iterator` 属性,执行后返回其自身
function* gen() {
// ...
}
var g = gen();
g[Symbol.iterator]() === g // truenext 方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数会被当作上一个yield
表达式的返回值。1
2
3
4
5
6
7
8
9
10
11
12
13// 一个实例
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next(); // { value: 6, done: false }
b.next(12) // { value: 8, done: false }
b.next(13) // { value: 42. done: true}
// 想要第一次调用 next 方法时,就能够输入值,可以在 Generator 函数外面再包一层for…of 循环
for...of
循环可以自动遍历 Generator 函数执行时生成的Iterator
对象,且不需要调用next
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 一个实例
// 这里面一旦 next 方法返回对象的属性为 true,for...of循环就会中止,并不包含该返回对象,所以 return 中的 5 没有被打印出来
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let v of foo()) {
console.log(v);
} // 1, 2, 3, 4
[...foo()]; // [1, 2, 3, 4]
Array.from(foo()); // [1, 2, 3, 4]
// 可以将 Generator 函数加到对象的 Symbol.iterator 属性上面,可以用 for...of 循环遍历该对象
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'jane', last: 'Doe'};
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: jane
// last: DoeGenerator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个
throw
方法,可以在函数体外抛出错误,然后再 Generator 函数体内捕获1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78// 第一个实例
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
}
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
// 1. 遍历器对象 i 连续抛出两个错误。第一个被 Generator 函数内的 catch 语句捕获,在第二次抛出错误的时候,由于 Generator 函数内部的 catch 语句已经执行过了,不会再捕捉这个错误,所以就被外部的 catch 捕获了
// 2. throw 方法可以接受一个参数,该参数会被 catch 语句接受
// 3. 如果 Generator 函数内部没有部署 try...catch 代码块,那么通过 throw 方法抛出的错误,将被外部 try...cathc 代码块捕获
// 4. 如果 Generator 函数内部和外部,都没有部署 try...catch 代码块,那么程序将报错,直接中断执行
// Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 体内抛出的错误,也可以被函数体外 catch 捕获
// 第二个实例
var gen = function* gen() {
try {
yield console.log('a');
} catch(e) {
console.log('error');
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next(); // a
g.throw(); // error b
g.next(); // c
// 可以看到,g.throw 方法被捕获后,自动执行了一次 next 方法
// 第三个实例
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', err);
}
try {
v = generator.next();
console.log('第二次运行next方法', v);
} catch (err) {
console.log('捕捉错误', err);
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', err);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 Error: generator broke!
// 第三次运行next方法 { value: undefined, done: true }
// caller doneGenerator.prototype.return()
Generator 函数返回的遍历器对象,还有一个
return
方法,可以返回给定的值,并且终结遍历 Generator 函数如果 Generator 函数内部有
try...catch
代码块,那么return
方法会推迟到finally
代码执行完再执行,而且应该被执行的yield
表达式会被忽略,直接之后后面的那次1
2
3
4
5
6
7
8
9
10// 实例
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next(); // { value: 1, done: false }
g.return('foo'); // { value: 'foo', done: true } return方法不带参数的话,value 为undefined
g.next(); // { value: undefined, done: true }next(), throw(), return() 的共同点
- next(),实际上是将
yield
表达式换成一个值 - throw(),实际上是将
yield
表达式换成一个throw
语句 - return(),实际上是将
yield
表达式替换成一个return
语句
- next(),实际上是将
yield* 表达式
用来在一个 Generator 函数里面执行另一个 Generator 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44// 一个实例
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
// foo 中有 return 语句的情况
function* foo() {
yield 'a';
yield 'b';
return c;
}
function* bar() {
yield 'x';
var v = yield* foo();
console.log(v);
yield 'y';
}
var a = bar();
a.next() // {value: "x", done: false}
a.next() // {value: "a", done: false}
a.next() // {value: "b", done: false}
a.next() // c {value: "y", done: false}
a.next() // {value: undefined, done: true}作为对象属性的 Generator 函数
1
2
3
4
5
6
7
8
9
10
11let obj = {
* myGeneratorMethod() {
// ...
}
}
// 等同于
let obj = {
myGeneratorMethod: function* () {
// ...
}
}Generator 函数的 this
Generator 函数总是返回一个遍历器,这个遍历器是 Generator 函数的实例同时也继承了 Generator 函数的
prototype
对象上的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 一个实例
function* g() {}
g.prototype.hello = function () {
return 'hi';
};
let obj = g();
obj instanceof g // true
obj.hello() // hi!
// Generator 函数也不能和 `new` 命令一起用
function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F(); // TypeError: F is not a constructor