前言
先吐槽一件事,最近把原先的 TOP 域名更换到 CN 域名,并且用 Gatsby
重建个人站点,之前是用采用 HTTPS 部署的方式绕过阿里云的域名备案系统。更换 CN 域名后,这招不管用了,?? 域名必须要备案了,等待幕布邮寄中……
有人要问了,都 9102 年,ES10 都出来了,怎么还在讲 ES6,非也!本文针对 ES6 几个不为人知、和重要的特性做讲解,精彩的在后面!
基础篇
Let + Const
ES6 除了固有的函数作用域,还引入了块级作用域({})
function f() {
{
let x; // ①
{
// 包含在当前块中,与 ① 中的 x 分属不同作用域
const x = "sneaky";
// 错误,const 定义的变量不可以重新赋值,如果 const 定义了一个对象,那么对象的属性是可以修改的
x = "foo";
}
// let 定义的变量可以重新赋值
x = "bar";
// 错误,x 在 ① 块中已被定义
let x = "inner";
}
}
复制代码
默认、剩余、展开参数(Default + Rest + Spread)
function f(x, y=12) {
// y 等于 12 如果不传递 (或者传递 undefined)
return x + y;
}
f(3); // 15
复制代码
function f(x, ...y) {
// y 是一个数组
return x * y.length;
}
f(3, "hello", true); // 6
复制代码
function f(x, y, z) {
return x + y + z;
}
// 将数组的每一项作为参数传递
f(...[1,2,3]); // 6
复制代码
解构(Destructuring)
var [a, ,b] = [1,2,3];
a === 1; // true
b === 3; // true
var { op: a, lhs: { op: b }, rhs: c } = getASTNode()
// var {op: op, lhs: lhs, rhs: rhs} = getASTNode()
var {op, lhs, rhs} = getASTNode()
// 参数解构
function g({name: x}) {
console.log(x);
}
g({name: 5})
var [a] = [];
a === undefined; // true
var [a = 1] = [];
a === 1; // true
// 解构 + 默认参数
function r({x, y, w = 10, h = 10}) {
return x + y + w + h;
}
r({x:1, y:2}) === 23 // true
复制代码
箭头函数(Arrows and Lexical This)
// 除了支持返回语句,还可以将表达式作为返回主体
const foo = () => ({ name: 'es6' });
const bar = (num) => (num++, num ** 2);
foo(); // 返回一个对象 { name: 'es6' }
bar(3); // 执行多个表达式,并返回最后一个表达式的值 16
复制代码
JS 中 this
的指向问题一直都是面试高频考点,不少人在实战中也掉入坑中,总结起来就是一句话:“ this
永远指向调用它的那个对象”,而箭头函数则改写了这一规则,就是
箭头函数共享当前代码上下文的
this
什么意思呢?可以理解为
- 箭头函数不会创建自己的
this
,它只会从自己的作用域链的上一层继承this
,如果上一层还是箭头函数,则继续向上查找,直至全局作用域,在浏览器环境下即window
。 - 函数具有作用域链,对象则不具有
因此,在下面的代码中,传递给 setInterval
的函数内的 this
与 sayHello
函数中的 this
一致:
const bob = {
name: 'Bob',
sayHello() {
setTimeout(() => {
console.log(`hello, I am ${this.name}`);
}, 1000);
}
};
const hello = bob.sayHello;
bob.sayHello();
// hello, I am Bob
// 作为对象的方法调用,sayHello的this指向bob
hello();
// hello, I am undefined
// 作为普通函数调用,相当于window.hello(),this指向全局对象
hello.call({name:'Mike'});
// hello, I am Mike
// call,apply调用,第一个参数为this指向的对象
复制代码
language = 'Python';
const obj = {
language: 'TS',
speak() {
language = 'GO';
return function() {
return () => {
console.log(`I speak ${this.language}`);
};
};
}
};
obj.speak()()(); // 做个小测试,会打印什么呢?
复制代码
箭头函数还有以下特点
- 由于箭头函数没有自己的
this
指针,通过call
或apply
调用,第一个参数会被忽略 - 不绑定
Arguments
对象,其引用上一层作用域链的Arguments
对象 - 不能用作构造器,和
new
一起用会抛出错误。 - 没有
prototype
属性。
现在你应该明白为何 React 中的函数写法都为箭头函数,就是为了绑定 this
Symbols
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值,它的功能类似于一种标识唯一性的 ID
// 每个 Symbol 实例都是唯一的。因此,当你比较两个 Symbol 实例的时候,将总会返回 false
const s1 = Symbol('macOS');
const s2 = Symbol('macOS');
// Symbol.for 机制有点类似于单例模式
const s3 = Symbol.for('windows'); // 注册一个全局 Symbol
const s4 = Symbol.for('windows'); // 已存在相同名称的 Symbol,返回全局 Symbol
s1 === s2; // false
s3 === s4; // true
复制代码
let key = Symbol('key');
function MyClass(privateData) {
// 注意,Symbol值作为对象属性名时,不能用点运算符
this[key] = privateData;
}
MyClass.prototype = {
doStuff() {
console.log(this[key]);
}
};
// Symbol的一些特性必须要浏览器的原生实现,不可被 transpiled 或 polyfilled
typeof key // symbol
let c = new MyClass('hello');
c.key; // undefined
c[key]; // hello
复制代码
应用场景
- 更好的设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加优雅。
在实际应用中,我们经常会需要使用 Object.keys()
或者 for...in
来枚举对象的属性名,那在这方面,Symbol
类型的 key
表现的会有什么不同之处呢?来看以下示例代码:
let obj = {
[Symbol('name')]: '一斤代码',
age: 18,
title: 'Engineer'
}
Object.keys(obj) // ['age', 'title']
for (let p in obj) {
console.log(p) // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj) // ['age', 'title']
复制代码
也正因为这样一个特性,当使用 JSON.stringify()
将对象转换成 JSON
字符串的时候,Symbol
属性也会被排除在输出内容之外:
JSON.stringify(obj) // {"age":18,"title":"Engineer"}
复制代码
由上代码可知,Symbol
类型的 key
是不能通过 Object.keys()
或者 for...in
来枚举的,所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用 Symbol
来定义。
- 消除魔术字符串
const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'
function handleFileResource(resource) {
switch(resource.type) {
case TYPE_AUDIO:
playAudio(resource)
break
case TYPE_VIDEO:
playVideo(resource)
break
case TYPE_IMAGE:
previewImage(resource)
break
default:
throw new Error('Unknown type of resource')
}
}
复制代码
上面的代码中那样,我们需要为常量赋一个唯一的值(比如这里的 'AUDIO'
),'AUDIO'
就是一个魔术字符串,它本身没意义,只是为了保证常量唯一的关系。常量一多,就变得十分臃肿且难以理解
现在有了 Symbol
,我们大可不必这么麻烦了:
// 保证了三个常量的值是唯一的
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()
复制代码
增强的对象字面量(Enhanced Object Literals)
const obj = {
// 允许设置原型
__proto__: theProtoObj,
// 允许覆盖属性
['__proto__']: somethingElse,
// 属性简写,等于 ‘handler: handler’
handler,
// 计算 (动态) 属性名
['prop_' + (() => 42)()]: 42
};
obj.prop_42 // 42
obj.__proto__ // somethingElse
复制代码
__proto__
需要原生支持,它在之前的ECMAScript
版本中被移除,但大多数浏览器都实现了这一特性,包括Node
环境
Map + Set + WeakMap + WeakSet
Set
Set
是 ES6 中新增的数据结构,它允许创建唯一值的集合。集合中的值可以是简单的基本类型(如字符串或数值),但更复杂的对象类型(如对象或数组)也可以,亦或是一个新的 Set
let animals = new Set();
animals.add('?');
animals.add('?');
animals.add('?');
animals.add('?');
console.log(animals.size); // 4
animals.add('?');
console.log(animals.size); // 4
console.log(animals.has('?')); // true
animals.delete('?');
console.log(animals.has('?')); // false
animals.forEach(animal => {
console.log(`Hey ${animal}!`);
});
// Hey ?!
// Hey ?!
// Hey ?!
animals.clear();
console.log(animals.size); // 0
复制代码
我们还可以传入一个数组来初始化集合
let myAnimals = new Set(['?', '?', '?', '?']);
myAnimals.add(['?', '?']);
myAnimals.add({ name: 'Rud', type: '?' });
console.log(myAnimals.size); // 4
// Set 内置了遍历器,可以调用 forEach, for…of
myAnimals.forEach(animal => {
console.log(animal);
});
// ?
// ?
// ["?", "?"]
// Object { name: "Rud", type: "?" }
复制代码
Map
与普通对象(Object
)不同,Map
的键名(Key
)可以是任何类型,不再局限于字符串(String
),包括但不限于 objects
或 functions
let things = new Map();
const myFunc = () => '?';
things.set('?', 'Car');
things.set('?', 'House');
things.set('✈️', 'Airplane');
things.set(myFunc, '? Key is a function!');
things.size; // 4
things.has('?'); // true
things.has(myFunc) // true
things.has(() => '?'); // false
things.get(myFunc); // '? Key is a function!'
things.delete('✈️');
things.has('✈️'); // false
things.clear();
things.size; // 0
// 链式设置键值对
things.set('?', 'Wrench')
.set('?', 'Guitar')
.set('?', 'Joystick');
const myMap = new Map();
// 甚至键名可以是另一个 Map
things.set(myMap, 'Oh gosh!');
things.size; // 4
things.get(myMap); // 'Oh gosh!'
复制代码
可以通过传入包含两个元素的数组来初始化 Map
const funArray = [
['?', 'Champagne'],
['?', 'Lollipop'],
['?', 'Confetti'],
];
let funMap = new Map(funArray);
funMap.get('?'); // Champagne
复制代码
WeakMap
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。它最重要的特性是 WeakMap
保持了对键名所引用的对象的弱引用
我们可以通过 Node 来证明一下这个问题:
// 允许手动执行垃圾回收机制
node --expose-gc
global.gc();
// 返回 Nodejs 的内存占用情况,单位是 bytes
process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M
let map = new Map();
let key = new Array(5 * 1024 * 1024); // new Array 当为 Obj
map.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46751472 注意这里大约是 44.6M
// 所以当你设置 key = null 时,只是去掉了 key 对 Obj 的强引用
// 并没有去除 arr 对 Obj 的强引用,所以 Obj 还是不会被回收掉
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M
// 这句话其实是无用的,因为 key 已经是 null 了
map.delete(key);
global.gc();
process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M
复制代码
node --expose-gc
global.gc();
process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4M
const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
global.gc();
process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M
// 当我们设置 key = null 的时候,就只有 wm 对所引用对象的弱引用
// 下次垃圾回收机制执行的时候,该引用对象就会被回收掉。
key = null;
global.gc();
process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M
复制代码
应用场景
传统使用 jQuery
的时候,我们会通过 $.data()
方法在 DOM
对象上储存相关信息(就比如在删除按钮元素上储存帖子的 ID 信息),jQuery
内部会使用一个对象管理 DOM
和对应的数据,当你将 DOM
元素删除,DOM
对象置为空的时候,相关联的数据并不会被删除,你必须手动执行 $.removeData()
方法才能删除掉相关联的数据,WeakMap
就可以简化这一操作:
let wm = new WeakMap(), element = document.querySelector(".element");
wm.set(element, "data");
let value = wm.get(elemet);
console.log(value); // data
element.parentNode.removeChild(element);
element = null;
复制代码
WeakSet
特性与 WeakMap
相似
遍历器(Iterators + For..Of)
遍历器(Iterator
)它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator
接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator
的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator
接口主要供for...of
消费。
ES6 规定,默认的 Iterator
接口部署在数据结构的 Symbol.iterator
属性,或者说,一个数据结构只要具有 Symbol.iterator
属性,就可以认为是“可遍历的”(iterable
)
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur]; // 数组解构
return { done: false, value: cur }
}
}
}
}
for (var n of fibonacci) {
// 当n超过1000时停止
if (n > 1000)
break;
console.log(n);
}
复制代码
上面代码中,对象 fibonacci
是可遍历的(iterable
),因为具有 Symbol.iterator
属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有 next
方法。每次调用 next
方法,都会返回一个代表当前成员的信息对象,具有 value
和 done
两个属性
原生具备 Iterator
接口的数据结构如下
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
for...in 和 for..of 的差别
- for...in 遍历键名(Key)并转化为字符串,for...of 遍历键值(Value)
- for...in 语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非 Symbol 的属性
- for...in 更适合遍历对象,for...of 更适合遍历数组。
for (let i in [1, 2, 3]) {
console.log(typeof i); // string 数组下标被转化字符串
console.log(i); // '1', '2', '3'
}
var triangle = { a: 1, b: 2, c: 3 };
function ColoredTriangle() {
this.color = 'red';
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) { // 如果去了 hasOwnProperty() 这个约束条件会怎么样?
console.log(`obj.${prop} = ${obj[prop]}`); // obj.color = red
}
}
复制代码
新增 API(Math + Number + String + Object APIs)
我们先来看看新增的 Number.EPSILON
,不少人都是懵逼的状态,WTF?
先来看看的 JS 世界中的一道送命题
0.1 + 0.2 // 结果0.30000000000000004 而不是0.3
复制代码
事出必有因,这是因为 JS 的数值采用了 IEEE 754
标准,而且 JS 是弱类型语言,所以数字都是以64位双精度浮点数据类型储存。也就是说,JS 语言底层根本没有整数,所有数字都是小数!当我们以为在用整数进行计算时,都会被转换为小数
而浮点数都是以多位二进制的方式进行存储的
十进制的0.1用二进制表示为:0.0 0011 0011 0011 0011…,循环部分是0011
十进制0.2用二进制表示为:0.0011 0011 0011 0011…,循环部分是0011
由于存储空间有限,最后计算机会舍弃后面的数值,所以我们最后就只能得到一个近似值
JS中采用的 IEEE 754
的双精度标准也是一样的道理在存储空间有限的情况下,当出现这种无法整除的小数的时候就会取一个近似值,在 JS 中如果这个近似值足够近似,那么 JS 就会认为他就是那个值。
console.log(0.1000000000000001)
// 0.1000000000000001 (中间14个0,不会被近似处理,输出本身)
console.log(0.10000000000000001)
// 0.1 (中间15个0,js会认为两个值足够近似,所以输出0.1)
复制代码
那么这个近似的界限如何判断呢?
ES6的 Number.EPSILON
就是一个界限,它表示 1 与大于 1 的最小浮点数之间的差。
对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方
Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"
复制代码
Number.EPSILON
实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。
0.1 + 0.2 - 0.3
// 5.551115123125783e-17
5.551115123125783e-17.toFixed(20)
// '0.00000000000000005551'
复制代码
0.00000000000000005551 < 0.00000000000000022204 // true
复制代码
显然,0.30000000000000004
不存在误差,不会被近似处理
我们可以通过以下手段来达到我们想要的效果
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
复制代码
其他一些新增的 API
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.sign(-5) // 判断一个数到底是正数、负数、还是零 -1
Math.hypot(3, 4) // 返回所有参数的平方和的平方根 5
Math.imul(-2, -2) // 返回两个数以 32 位带符号整数形式相乘的结果 4
"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll("*")) // 返回一个真正的数组
Array.of(1, 2, 3) // [1,2,3]
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // 0, 1, 2
["a", "b", "c"].values() // "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) }) // 合并对象
复制代码
二进制和八进制字面量(Binary and Octal Literals)
0b111 === 7 // true 二进制
0o111 === 73 // true 八进制
0x111 === 273 // true 十六进制
复制代码
进阶篇
尾递归(Tail Calls)
假设现在要实现一个阶乘函数,即 5!= 120
,我们很容易想到递归实现
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
复制代码
但递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow
)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。
何为调用记录,在示例代码中,由于最后一步返回了一个表达式,内存会保留
n
这个变量的信息和factorial(n - 1)
调用下一次函数的位置,形成一层层的调用栈
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身,返回函数本身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。尾递归优化如下
function factorial(n, acc = 1) {
"use strict";
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
factorial(100000)
复制代码
由此可见,"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 也是如此,第一次明确规定,所有 ECMAScript
的实现,都必须部署"尾调用优化"。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。
ES6的尾调用优化只在严格模式下开启,正常模式是无效的。
反射(Reflect)
Reflect
对象与 Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect
对象的设计目的有这样几个。
- 将Object对象的一些明显属于语言内部的方法(比如
Object.defineProperty
),放到Reflect
对象上 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
var O = {a: 1};
Reflect.defineProperty(O, 'b', {value: 2});
O[Symbol('c')] = 3;
Reflect.ownKeys(O); // ['a', 'b', Symbol(c)]
Reflect.getOwnPropertyDescriptor(O, 'b');
// { value: 2, writable: false, enumerable: false, configurable: false }
function C(a, b){
this.c = a + b;
}
var instance = Reflect.construct(C, [20, 22]);
instance.c; // 42
复制代码
获取属性名的方法有很多,以上面的代码为例子,它们的区别如下
方法 | 结果 | 解释 |
---|---|---|
Object.getOwnPropertyNames(O) | [ 'a', 'b' ] | 获取除 Symbol 外的所有属性 |
Object.getOwnPropertySymbols(O) | [ Symbol(c) ] | 只获取 Symbol 属性 |
OReflect.ownKeys(O) | [ 'a', 'b', Symbol(c) ] | 获取所有属性 |
for...in | a | 获取除 Symbol 外的可枚举属性 |
代理(Proxy)
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy
这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
// 代理一个对象
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};
var p = new Proxy(target, handler);
p.world; // "Hello, world!"
复制代码
// 代理一个函数
var target = function () { return "I am the target"; };
var handler = {
apply: function (receiver, ...args) {
return "I am the proxy";
}
};
var p = new Proxy(target, handler);
p(); // "I am the proxy"
复制代码
// 代理会将所有应用到它的操作转发到这个对象上
let target = {};
let p = new Proxy(target, {});
p.a = 37;
target.a; // 37 操作转发到目标
复制代码
// 如何实现 a == 1 && a == 2 && a == 3,利用Proxy的get劫持
const a = new Proxy(
{},
{
val: 1,
get() {
return () => this.val++;
}
}
);
a == 1 && a == 2 && a == 3; // true
复制代码
由于 ES5 的限制,Proxy 不能被 transpiled or polyfilled,自己亲自入的坑,由于在项目中使用了 Mobx5.x,其内部是用 Proxy 写的,结果 IE11 不支持 ES6,只得回退版本 Mobx 到 4.x
生成器(Generators)
Generator
函数是 ES6 提供的一种异步编程解决方案。 Generator
函数有多种理解角度。语法上,首先可以把它理解成,Generator
函数是一个状态机,封装了多个内部状态。
形式上,Generator
函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用 yield
表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello'; // yield使Generator函数暂停了执行,并将结果返回给调用者
yield 'world'; // 当下一次调用时,从它中断的地方恢复执行
return 'ending';
}
var hw = helloWorldGenerator();
a = hw.next(); // { value: 'hello', done: false }
b = hw.next(); // { value: 'world', done: false }
c = hw.next(); // { value: 'ending', done: true }
复制代码
可以利用这种暂停执行的特性,来实现惰性求值
向Generator传递数据
function* sayFullName() {
const firstName = yield;
const secondName = yield;
console.log(firstName + ' ' + secondName);
}
let fullName = sayFullName();
fullName.next();
// 第一次调用,代码暂停在 const firstName = yield,因为没有通过 yield 发送任何值,因此 next 将返回 undefined
fullName.next('Handsome');
// 第二次调用,传入了值 Handsome,yield 被 Handsome 替代,因此 firstName 的值变为 Handsome,代码执行恢复
// 直到再次遇到 const secondName = yield 暂停执行
fullName.next('Jack');
// 第三次调用,传入了值 Jack,yield 被 Jack 替代,因此 secondName 的值变为 Jack,代码执行恢复
// 打印 Handsome Jack
复制代码
使用Generator处理异步调用
let generator;
let getDataOne = () => {
setTimeout(() => {
generator.next('dummy data one');
}, 1000);
};
let getDataTwo = () => {
setTimeout(() => {
generator.next('dummy data one');
}, 1000);
};
function* main() {
let dataOne = yield getDataOne();
let dataTwo = yield getDataTwo();
console.log(dataOne, dataTwo);
}
generator = main();
generator.next();
// 执行 getDataOne(),然后 yield 暂停
// 直至一秒后 generator.next('dummy data one') 恢复代码执行,并赋值 dataOne
console.log('i am previous print');
// i am previous print
// dummy data one dummy data one
复制代码
Promises
Promises
是一个异步编程的解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓 Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的 API
,各种异步操作都可以用同样的方法进行处理。
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}
var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
})
复制代码
这里强调几点
- 不要剥夺函数
return
的能力,很多人写Promise
,照样有大量嵌套,掉进Promise
地狱,要记得及时return
,避免嵌套 - 当需要多个请求全部结束时,才更新数据,可以用
Promise.all(fetch1,fetch2)
- 当需要从多个请求中,接受最先返回数据的那个请求,可以用
Promise.race(fetch1,fetch2)
结尾
ES6 是 ECMAScript
一个非常重要的版本,我们必须深入理解,不仅能提高我们书写代码的能力,还能增强业务能力
附上一张我之前精心整理的思维导图
本文参考资料