ES6 速通(上)https://developer.aliyun.com/article/1504136?spm=a2c6h.13148508.setting.20.36834f0eMJOehx
17. Iterator 和 for … of 循环
扩展运算符
只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
let arr = [...iterable];
yield
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var 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 }
其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
- for…of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
generator 函数
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // [1, 2, 3]
// 或者采用下面的简洁写法
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
遍历器对象除了具有next
方法,还可以具有return
方法和throw
方法。如果你自己写遍历器对象生成函数,那么next
方法是必须部署的,return
方法和throw
方法是否部署是可选的。
return
方法的使用场合是,如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return
方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return
方法。
function readLinesSync(file) {
return {
`[Symbol.iterator]() {`
`return {`
`next() {`
`return { done: false };`
`},`
`return() {`
`file.close();`
`return { done: true };`
`}`
`};`
`},`
};
}
上面代码中,函数readLinesSync
接受一个文件对象作为参数,返回一个遍历器对象,其中除了next
方法,还部署了return
方法。下面的两种情况,都会触发执行return
方法。// 情况一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
18. Generator 函数
函数的写法如下:
function* foo(x, y) { ··· }
yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
需要注意的是,yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
复制代码
function* gen() {
yield 123 + 456;
}
上面代码中,yield
后面的表达式123 + 456
,不会立即求值,只会在next
方法将指针移到这一句时,才会求值。
yield
表达式与return
语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。
Generator 函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数。
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
上面代码中,函数f
如果是普通函数,在为变量generator
赋值时就会执行。但是,函数f
是一个 Generator 函数,就变成只有调用next
方法时,函数f
才会执行。
另外需要注意,yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
Generator 是实现状态机的最佳结构。比如,下面的clock
函数就是一个状态机。
var ticking = true;
var clock = function() {
if (ticking)
`console.log('Tick!');`
else
`console.log('Tock!');`
ticking = !ticking;
}
上面代码的clock
函数一共有两种状态(Tick
和Tock
),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。
var clock = function* () {
while (true) {
`console.log('Tick!');`
`yield;`
`console.log('Tock!');`
`yield;`
}
};
18. Generator 函数的语法 - 应用 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack
generator 控制流
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
let steps = [step1Func, step2Func, step3Func];
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
for … of 无法遍历return 对象
for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
上面代码使用for...of
循环,依次显示 5 个yield
表达式的值。这里需要注意,一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6
,不包括在for...of
循环之中。
Generator 函数返回的遍历器对象,还有一个return
方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
上面代码中,调用return()
方法后,就开始执行finally
代码块,不执行try
里面剩下的代码了,然后等到finally
代码块执行完,再返回return()
方法指定的返回值。
next()
、throw()
、return()
这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield
表达式。
next()
是将yield
表达式替换成一个值。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
上面代码中,第二个next(1)
方法就相当于将yield
表达式替换成一个值1
。如果next
方法没有参数,就相当于替换成undefined
。
throw()
是将yield
表达式替换成一个throw
语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
return()
是将yield
表达式替换成一个return
语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
yield*
表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
yield*
命令可以很方便地取出嵌套数组的所有成员。
复制代码
function* iterTree(tree) {
if (Array.isArray(tree)) {
`for(let i=0; i < tree.length; i++) {`
`yield* iterTree(tree[i]);`
`}`
} else {
`yield tree;`
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
由于扩展运算符...
默认调用 Iterator 接口,所以上面这个函数也可以用于嵌套数组的平铺。
[...iterTree(tree)] // ["a", "b", "c", "d", "e"]
this 结合 generator :
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
19. Generator 函数的异步应用
Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
下面看看如何使用 Generator 函数,执行一个真实的异步任务。
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield
命令。
执行这段代码的方法如下。
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next
方法(第二行),执行异步任务的第一阶段。由于Fetch
模块返回的是一个 Promise 对象,因此要用then
方法调用下一个next
方法。
Thunk 函数是自动执行 Generator 函数的一种方法。
传值调用和传名调用
”传值调用”(call by value),即在进入函数体之前,就计算x + 5
的值(等于 6),再将这个值传入函数f
。C 语言就采用这种策略。
“传名调用”(call by name),即直接将表达式x + 5
传入函数体,只在用到它的时候求值。Haskell 语言采用这种策略。
Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。
function run(fn) {
var gen = fn();
function next(err, data) {
`var result = gen.next(data);`
`if (result.done) return;`
`result.value(next);`
}
next();
}
function* g() {
// ...
}
run(g);
上面代码的run
函数,就是一个 Generator 函数的自动执行器。内部的next
函数就是 Thunk 的回调函数。next
函数先将指针移到 Generator 函数的下一步(gen.next
方法),然后判断 Generator 函数是否结束(result.done
属性),如果没结束,就将next
函数再传入 Thunk 函数(result.value
属性),否则就直接退出。
co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。
下面是一个 Generator 函数,用于依次读取两个文件。
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co 模块可以让你不用编写 Generator 函数的执行器。
var co = require('co');
co(gen);
co就是把对象转化为promise对象如何层层then
20. async 函数
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
`fs.readFile(fileName, function(error, data) {`
`if (error) return reject(error);`
`resolve(data);`
`});`
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数gen
可以写成async
函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。
ad
async
函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
(2)更好的语义。
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
async 函数有多种使用形式。
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
Promise 对象的状态变化
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
下面是一个例子。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/([\s\S]+)<\/title>/i)[1];</code></li><li><code>}</code></li><li><code>getTitle('https://tc39.github.io/ecma262/').then(console.log)</code></li><li><code>// "ECMAScript 2017 Language Specification"</code></li></ol><div style="background-color: #FCFCFC;">上面代码中,函数<code>getTitle</code>内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行<code>then</code>方法里面的<code>console.log</code>。</div><div style="background-color: #FCFCFC;"><strong>await 命令</strong></div><div style="background-color: #FCFCFC;">正常情况下,<code>await</code>命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。另一种情况是,<code>await</code>命令后面是一个<code>thenable</code>对象(即定义了<code>then</code>方法的对象),那么<code>await</code>会将其等同于 Promise 对象。</div><div style="background-color: #FCFCFC;">任何一个<code>await</code>语句后面的 Promise 对象变为<code>reject</code>状态,那么整个<code>async</code>函数都会中断执行。</div><div style="background-color: #FCFCFC;">另一种方法是<code>await</code>后面的 Promise 对象再跟一个<code>catch</code>方法,处理前面可能出现的错误。</div><div style="background-color: #FCFCFC;">async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。</div><ol style="background-color: #FCFCFC;"><li><code>async function fn(args) {</code><br /></li><li><code>// ...</code><br /></li><li><code>}</code><br /></li><li><code>// 等同于</code><br /></li><li><code>function fn(args) {</code><br /></li><li><code>return spawn(function* () {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%2F%2F%20...%60%22%2C%22id%22%3A%22DGaTS%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>});</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">所有的<code>async</code>函数都可以写成上面的第二种形式,其中的<code>spawn</code>函数就是自动执行器。</div><div style="background-color: #FCFCFC;">下面给出<code>spawn</code>函数的实现,基本就是前文自动执行器的翻版。</div><div style="background-color: #FCFCFC;">复制代码</div><ol style="background-color: #FCFCFC;"><li><code>function spawn(genF) {</code></li><li><code>return new Promise(function(resolve, reject) {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60const%20gen%20%3D%20genF()%3B%60%22%2C%22id%22%3A%22GS35C%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60function%20step(nextF)%20%7B%60%22%2C%22id%22%3A%22CIeu4%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60let%20next%3B%60%22%2C%22id%22%3A%22XHKlI%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60try%20%7B%60%22%2C%22id%22%3A%22HhnmP%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60next%20%3D%20nextF()%3B%60%22%2C%22id%22%3A%22Z0XBA%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%20catch(e)%20%7B%60%22%2C%22id%22%3A%22InqAQ%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20reject(e)%3B%60%22%2C%22id%22%3A%22m3GN7%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22pZ2ZW%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60if(next.done)%20%7B%60%22%2C%22id%22%3A%22DhamT%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20resolve(next.value)%3B%60%22%2C%22id%22%3A%22KeJ2m%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22eUh7i%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60Promise.resolve(next.value).then(function(v)%20%7B%60%22%2C%22id%22%3A%22xg9gH%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60step(function()%20%7B%20return%20gen.next(v)%3B%20%7D)%3B%60%22%2C%22id%22%3A%22alLxg%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%2C%20function(e)%20%7B%60%22%2C%22id%22%3A%22GzMya%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60step(function()%20%7B%20return%20gen.throw(e)%3B%20%7D)%3B%60%22%2C%22id%22%3A%22LklaN%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D)%3B%60%22%2C%22id%22%3A%22yZXoH%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22IQCff%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60step(function()%20%7B%20return%20gen.next(undefined)%3B%20%7D)%3B%60%22%2C%22id%22%3A%222wCPO%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>});</code></li><li><code>}</code></li></ol><div style="background-color: #FCFCFC;">三种异步的比较</div><div style="background-color: #FCFCFC;">我们通过一个例子,来看 async 函数与 Promise、Generator 函数的比较。</div><div style="background-color: #FCFCFC;">假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。</div><div style="background-color: #FCFCFC;">首先是 Promise 的写法。</div><ol style="background-color: #FCFCFC;"><li><code>function chainAnimationsPromise(elem, animations) {</code><br /></li><li><code>// 变量ret用来保存上一个动画的返回值</code><br /></li><li><code>let ret = null;</code><br /></li><li><code>// 新建一个空的Promise</code><br /></li><li><code>let p = Promise.resolve();</code><br /></li><li><code>// 使用then方法,添加所有动画</code><br /></li><li><code>for(let anim of animations) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60p%20%3D%20p.then(function(val)%20%7B%60%22%2C%22id%22%3A%22Fr7tO%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60ret%20%3D%20val%3B%60%22%2C%22id%22%3A%22L63ga%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20anim(elem)%3B%60%22%2C%22id%22%3A%22iC11S%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D)%3B%60%22%2C%22id%22%3A%22GAUhp%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>// 返回一个部署了错误捕捉机制的Promise</code><br /></li><li><code>return p.catch(function(e) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%2F*%20%E5%BF%BD%E7%95%A5%E9%94%99%E8%AF%AF%EF%BC%8C%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%20*%2F%60%22%2C%22id%22%3A%22VzrBG%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}).then(function() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20ret%3B%60%22%2C%22id%22%3A%22GrGvi%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>});</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(<code>then</code>、<code>catch</code>等等),操作本身的语义反而不容易看出来。</div><div style="background-color: #FCFCFC;">接着是 Generator 函数的写法。</div><ol style="background-color: #FCFCFC;"><li><code>function chainAnimationsGenerator(elem, animations) {</code><br /></li><li><code>return spawn(function*() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60let%20ret%20%3D%20null%3B%60%22%2C%22id%22%3A%22TIxBO%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60try%20%7B%60%22%2C%22id%22%3A%22jdQZP%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60for(let%20anim%20of%20animations)%20%7B%60%22%2C%22id%22%3A%22QRrnQ%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60ret%20%3D%20yield%20anim(elem)%3B%60%22%2C%22id%22%3A%22Mi8Ud%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%227htBX%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%20catch(e)%20%7B%60%22%2C%22id%22%3A%228BGZz%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%2F*%20%E5%BF%BD%E7%95%A5%E9%94%99%E8%AF%AF%EF%BC%8C%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%20*%2F%60%22%2C%22id%22%3A%22nwcCR%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22Szsbd%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20ret%3B%60%22%2C%22id%22%3A%22T9DTg%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>});</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在<code>spawn</code>函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的<code>spawn</code>函数就是自动执行器,它返回一个 Promise 对象,而且必须保证<code>yield</code>语句后面的表达式,必须返回一个 Promise。</div><div style="background-color: #FCFCFC;">最后是 async 函数的写法。</div><ol style="background-color: #FCFCFC;"><li><code>async function chainAnimationsAsync(elem, animations) {</code></li><li><code>let ret = null;</code></li><li><code>try {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60for(let%20anim%20of%20animations)%20%7B%60%22%2C%22id%22%3A%22HjhVG%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60ret%20%3D%20await%20anim(elem)%3B%60%22%2C%22id%22%3A%221Xhpa%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22b4Su4%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>} catch(e) {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%2F*%20%E5%BF%BD%E7%95%A5%E9%94%99%E8%AF%AF%EF%BC%8C%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%20*%2F%60%22%2C%22id%22%3A%22QdQmy%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code></li><li><code>return ret;</code></li><li><code>}</code></li></ol><div style="background-color: #FCFCFC;">可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。</div><div style="background-color: #FCFCFC;"><strong>顺序完成异步操作</strong></div><ol style="background-color: #FCFCFC;"><li><code>async function logInOrder(urls) {</code><br /></li><li><code>// 并发读取远程URL</code><br /></li><li><code>const textPromises = urls.map(async url => {</code><br /></li><li><code>const response = await fetch(url);</code><br /></li><li><code>return response.text();</code><br /></li><li><code>});</code><br /></li><li><code>// 按次序输出</code><br /></li><li><code>for (const textPromise of textPromises) {</code><br /></li><li><code>console.log(await textPromise);</code><br /></li><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">顶层await</div><ol style="background-color: #FCFCFC;"><li><code>// awaiting.js</code></li><li><code>let output;</code></li><li><code>export default (async function main() {</code></li><li><code>const dynamic = await import(someMission);</code></li><li><code>const data = await fetch(url);</code></li><li><code>output = someProcess(dynamic.default, data);</code></li><li><code>})();</code></li><li><code>export { output };</code></li></ol><h4 id="uYklP" style="background-color: #FCFCFC;"><a name="t19"></a><a></a>21. Class 的基本语法</h4><div style="background-color: #FCFCFC;">原始:</div><ol style="background-color: #FCFCFC;"><li><code>function Point(x, y) {</code><br /></li><li><code>this.x = x;</code><br /></li><li><code>this.y = y;</code><br /></li><li><code>}</code><br /></li><li><code>Point.prototype.toString = function () {</code><br /></li><li><code>return '(' + this.x + ', ' + this.y + ')';</code><br /></li><li><code>};</code><br /></li><li><code>var p = new Point(1, 2);</code><br /></li></ol><div style="background-color: #FCFCFC;">es6改进后:</div><ol style="background-color: #FCFCFC;"><li><code>class Point {</code><br /></li><li><code>constructor(x, y) {</code><br /></li><li><code>this.x = x;</code><br /></li><li><code>this.y = y;</code><br /></li><li><code>}</code><br /></li><li><code>toString() {</code><br /></li><li><code>return '(' + this.x + ', ' + this.y + ')';</code><br /></li><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">上面代码定义了一个“类”,定义“类”的方法的时候,前面不需要加上<code>function</code>这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。</div><div style="background-color: #FCFCFC;">类的数据类型就是函数,类本身就指向构造函数。</div><ol style="background-color: #FCFCFC;"><li><code>class Bar {</code><br /></li><li><code>doStuff() {</code><br /></li><li><code>console.log('stuff');</code><br /></li><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>var b = new Bar();</code><br /></li><li><code>b.doStuff() // "stuff"</code><br /></li></ol><div style="background-color: #FCFCFC;">构造函数的<code>prototype</code>属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的<code>prototype</code>属性上面。</div><ol style="background-color: #FCFCFC;"><li><code>class Point {</code><br /></li><li><code>constructor() {</code><br /></li><li><code>// ...</code><br /></li><li><code>}</code><br /></li><li><code>toString() {</code><br /></li><li><code>// ...</code><br /></li><li><code>}</code><br /></li><li><code>toValue() {</code><br /></li><li><code>// ...</code><br /></li><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>// 等同于</code><br /></li><li><code>Point.prototype = {</code><br /></li><li><code>constructor() {},</code><br /></li><li><code>toString() {},</code><br /></li><li><code>toValue() {},</code><br /></li><li><code>};</code><br /></li></ol><div style="background-color: #FCFCFC;"><code>prototype</code>对象的<code>constructor</code>属性,直接指向“类”的本身</div><ol style="background-color: #FCFCFC;"><li><code>Point.prototype.constructor === Point // true</code></li></ol><div style="background-color: #FCFCFC;">类是有函数构造的q</div><ol style="background-color: #FCFCFC;"><li><code>//定义类</code><br /></li><li><code>class Point {</code><br /></li><li><code>constructor(x, y) {</code><br /></li><li><code>this.x = x;</code><br /></li><li><code>this.y = y;</code><br /></li><li><code>}</code><br /></li><li><code>toString() {</code><br /></li><li><code>return '(' + this.x + ', ' + this.y + ')';</code><br /></li><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>var point = new Point(2, 3);</code><br /></li><li><code>point.toString() // (2, 3)</code><br /></li><li><code>point.hasOwnProperty('x') // true</code><br /></li><li><code>point.hasOwnProperty('y') // true</code><br /></li><li><code>point.hasOwnProperty('toString') // false</code><br /></li><li><code>point.__proto__.hasOwnProperty('toString') // true</code><br /></li></ol><div style="background-color: #FCFCFC;">类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上<code>static</code>关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。</div><ol style="background-color: #FCFCFC;"><li><code>class Foo {</code><br /></li><li><code>static classMethod() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20'hello'%3B%60%22%2C%22id%22%3A%224vruQ%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>Foo.classMethod() // 'hello'</code><br /></li><li><code>var foo = new Foo();</code><br /></li><li><code>foo.classMethod()</code><br /></li><li><code>// TypeError: foo.classMethod is not a function</code><br /></li></ol><div style="background-color: #FCFCFC;"><code>Foo</code>类的<code>classMethod</code>方法前有<code>static</code>关键字,表明该方法是一个静态方法,可以直接在<code>Foo</code>类上调用(<code>Foo.classMethod()</code>),而不是在<code>Foo</code>类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。</div><ol style="background-color: #FCFCFC;"><li><code>class Foo {</code><br /></li><li><code>static bar() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.baz()%3B%60%22%2C%22id%22%3A%22c9PcP%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>static baz() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('hello')%3B%60%22%2C%22id%22%3A%22K1Vzs%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>baz() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('world')%3B%60%22%2C%22id%22%3A%22igqb7%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>Foo.bar() // hello</code><br />上面代码中,静态方法<code>bar</code>调用了<code>this.baz</code>,这里的<code>this</code>指的是<code>Foo</code>类,而不是<code>Foo</code>的实例,等同于调用<code>Foo.baz</code>。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。<br /></li><li><code>class Foo {</code><br /></li><li><code>static classMethod() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20'hello'%3B%60%22%2C%22id%22%3A%22RdspW%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class Bar extends Foo {</code><br /></li><li><code>}</code><br /></li><li><code>Bar.classMethod() // 'hello'</code><br /></li></ol><div style="background-color: #FCFCFC;">实例属性的新写法:可以不使用constructor, 而是直接写在顶层</div><ol style="background-color: #FCFCFC;"><li><code>class IncreasingCounter {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this._count%20%3D%200%3B%60%22%2C%22id%22%3A%22xSJzq%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>get value() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('Getting%20the%20current%20value!')%3B%60%22%2C%22id%22%3A%22Xa9kT%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20this._count%3B%60%22%2C%22id%22%3A%22Mzvol%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>increment() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this._count%2B%2B%3B%60%22%2C%22id%22%3A%22pkJb4%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class IncreasingCounter {</code><br /></li><li><code>_count = 0;</code><br /></li><li><code>get value() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('Getting%20the%20current%20value!')%3B%60%22%2C%22id%22%3A%228MGnp%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20this._count%3B%60%22%2C%22id%22%3A%22KfNx1%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>increment() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this._count%2B%2B%3B%60%22%2C%22id%22%3A%22R3llu%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">静态属性</div><ol style="background-color: #FCFCFC;"><li><code>class Foo {</code><br /></li><li><code>}</code><br /></li><li><code>Foo.prop = 1;</code><br /></li><li><code>Foo.prop // 1</code><br /></li><li><code>class MyClass {</code><br /></li><li><code>static myStaticProp = 42;</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(MyClass.myStaticProp)%3B%20%2F%2F%2042%60%22%2C%22id%22%3A%22fvvwR%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">可以在constructor定义属性的前面添加static 设置静态属性</div><div style="background-color: #FCFCFC;">私有属性和私有方法,外部不能访问</div><ol style="background-color: #FCFCFC;"><li><code>class Foo {</code></li><li><code>#a;</code></li><li><code>#b;</code></li><li><code>constructor(a, b) {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.%23a%20%3D%20a%3B%60%22%2C%22id%22%3A%22wMQbm%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.%23b%20%3D%20b%3B%60%22%2C%22id%22%3A%22MXqTl%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code></li><li><code>#sum() {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20%23a%20%2B%20%23b%3B%60%22%2C%22id%22%3A%22Nxl36%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code></li><li><code>printSum() {</code></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(this.%23sum())%3B%60%22%2C%22id%22%3A%22CxsPi%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code></li><li><code>}</code></li></ol><div style="background-color: #FCFCFC;"><code>new</code>是从构造函数生成实例对象的命令。ES6 为<code>new</code>命令引入了一个<code>new.target</code>属性,该属性一般用在构造函数之中,返回<code>new</code>命令作用于的那个构造函数。如果构造函数不是通过<code>new</code>命令或<code>Reflect.construct()</code>调用的,<code>new.target</code>会返回<code>undefined</code>,因此这个属性可以用来确定构造函数是怎么调用的。</div><ol style="background-color: #FCFCFC;"><li><code>function Person(name) {</code><br /></li><li><code>if (new.target !== undefined) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.name%20%3D%20name%3B%60%22%2C%22id%22%3A%22RjMGD%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>} else {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60throw%20new%20Error('%E5%BF%85%E9%A1%BB%E4%BD%BF%E7%94%A8%20new%20%E5%91%BD%E4%BB%A4%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B')%3B%60%22%2C%22id%22%3A%22X0oIi%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>// 另一种写法</code><br /></li><li><code>function Person(name) {</code><br /></li><li><code>if (new.target === Person) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.name%20%3D%20name%3B%60%22%2C%22id%22%3A%22FwDby%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>} else {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60throw%20new%20Error('%E5%BF%85%E9%A1%BB%E4%BD%BF%E7%94%A8%20new%20%E5%91%BD%E4%BB%A4%E7%94%9F%E6%88%90%E5%AE%9E%E4%BE%8B')%3B%60%22%2C%22id%22%3A%22raVOC%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>var person = new Person('张三'); // 正确</code><br /></li><li><code>var notAPerson = Person.call(person, '张三'); // 报错</code><br /></li></ol><div style="background-color: #FCFCFC;">new.target 是用来检测是否是由new构成的,区别于call构成</div><div style="background-color: #FCFCFC;"><code>new.target</code>会返回子类。</div><div style="background-color: #FCFCFC;">用法:</div><ol style="background-color: #FCFCFC;"><li><code>class Shape {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60if%20(new.target%20%3D%3D%3D%20Shape)%20%7B%60%22%2C%22id%22%3A%22SoWKe%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60throw%20new%20Error('%E6%9C%AC%E7%B1%BB%E4%B8%8D%E8%83%BD%E5%AE%9E%E4%BE%8B%E5%8C%96')%3B%60%22%2C%22id%22%3A%22VFJ9V%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%7D%60%22%2C%22id%22%3A%22UpgC5%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class Rectangle extends Shape {</code><br /></li><li><code>constructor(length, width) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super()%3B%60%22%2C%22id%22%3A%22Q3LHs%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60%2F%2F%20...%60%22%2C%22id%22%3A%22il4OC%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>var x = new Shape(); // 报错</code><br /></li><li><code>var y = new Rectangle(3, 4); // 正确</code><br /></li></ol><h4 id="W1zeS" style="background-color: #FCFCFC;"><a name="t20"></a><a></a>22. Class 的继承</h4><ol style="background-color: #FCFCFC;"><li><code>class Point {</code><br /></li><li><code>}</code><br /></li><li><code>class ColorPoint extends Point {</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">子类在constructor中必须使用super() 可以调用父类的constructor</div><ol style="background-color: #FCFCFC;"><li><code>class ColorPoint extends Point {</code><br /></li><li><code>constructor(x, y, color) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super(x%2C%20y)%3B%20%2F%2F%20%E8%B0%83%E7%94%A8%E7%88%B6%E7%B1%BB%E7%9A%84constructor(x%2C%20y)%60%22%2C%22id%22%3A%22XajiN%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.color%20%3D%20color%3B%60%22%2C%22id%22%3A%224nNet%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>toString() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%20this.color%20%2B%20'%20'%20%2B%20super.toString()%3B%20%2F%2F%20%E8%B0%83%E7%94%A8%E7%88%B6%E7%B1%BB%E7%9A%84toString()%60%22%2C%22id%22%3A%22TUCbS%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">如果子类没有定义constructor方法,这个方法会被默认添加;</div><ol style="background-color: #FCFCFC;"><li><code>class ColorPoint extends Point {</code><br /></li><li><code>}</code><br /></li><li><code>// 等同于</code><br /></li><li><code>class ColorPoint extends Point {</code><br /></li><li><code>constructor(...args) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super(...args)%3B%60%22%2C%22id%22%3A%22H0P0p%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li></ol><div style="background-color: #FCFCFC;">同时需要注意的是只有在使用super后才可以使用this关键字</div><ol style="background-color: #FCFCFC;"><li><code>let cp = new ColorPoint(25, 8, 'green');</code><br /></li><li><code>cp instanceof ColorPoint // true</code><br /></li><li><code>cp instanceof Point // true</code><br /></li></ol><div style="background-color: #FCFCFC;">实例对象<code>cp</code>同时是子类和父类<code>ColorPoint</code>和<code>Point</code>两个类的实例</div><div style="background-color: #FCFCFC;"><code>Object.getPrototypeOf</code>方法可以用来从子类上获取父类。</div><div style="background-color: #FCFCFC;">super() 只能放在 constructor中</div><ol style="background-color: #FCFCFC;"><li><code>class A {</code><br /></li><li><code>p() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60return%202%3B%60%22%2C%22id%22%3A%22SwO2k%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class B extends A {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super()%3B%60%22%2C%22id%22%3A%22tnulc%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(super.p())%3B%20%2F%2F%202%60%22%2C%22id%22%3A%22wnyWP%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>let b = new B();</code><br /></li></ol><div style="background-color: #FCFCFC;">上面代码中,子类<code>B</code>当中的<code>super.p()</code>,就是将<code>super</code>当作一个对象使用。这时,<code>super</code>在普通方法之中,指向<code>A.prototype</code>,所以<code>super.p()</code>就相当于<code>A.prototype.p()</code>。</div><ol style="background-color: #FCFCFC;"><li><code>class A {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.x%20%3D%201%3B%60%22%2C%22id%22%3A%220O0AB%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>print() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(this.x)%3B%60%22%2C%22id%22%3A%22aj2Q6%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class B extends A {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super()%3B%60%22%2C%22id%22%3A%22e3CUB%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.x%20%3D%202%3B%60%22%2C%22id%22%3A%22AQ4uW%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>m() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super.print()%3B%60%22%2C%22id%22%3A%22iuyMb%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>let b = new B();</code><br /></li><li><code>b.m() // 2</code><br /></li></ol><div style="background-color: #FCFCFC;">上面代码中,<code>super.print()</code>虽然调用的是<code>A.prototype.print()</code>,但是<code>A.prototype.print()</code>内部的<code>this</code>指向子类<code>B</code>的实例,导致输出的是<code>2</code>,而不是<code>1</code>。也就是说,实际上执行的是<code>super.print.call(this)</code>。</div><div style="background-color: #FCFCFC;">由于<code>this</code>指向子类实例,所以如果通过<code>super</code>对某个属性赋值,这时<code>super</code>就是<code>this</code>,赋值的属性会变成子类实例的属性。</div><ol style="background-color: #FCFCFC;"><li><code>class A {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.x%20%3D%201%3B%60%22%2C%22id%22%3A%22Qh3O3%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class B extends A {</code><br /></li><li><code>constructor() {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super()%3B%60%22%2C%22id%22%3A%22b5KTs%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60this.x%20%3D%202%3B%60%22%2C%22id%22%3A%226uQQc%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super.x%20%3D%203%3B%60%22%2C%22id%22%3A%22hHzlc%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(super.x)%3B%20%2F%2F%20undefined%60%22%2C%22id%22%3A%22UiILI%22%7D"></div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log(this.x)%3B%20%2F%2F%203%60%22%2C%22id%22%3A%22yhRus%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>let b = new B();</code><br /></li></ol><div style="background-color: #FCFCFC;">上面代码中,<code>super.x</code>赋值为<code>3</code>,这时等同于对<code>this.x</code>赋值为<code>3</code>。而当读取<code>super.x</code>的时候,读的是<code>A.prototype.x</code>,所以返回<code>undefined</code>。</div><div style="background-color: #FCFCFC;">如果<code>super</code>作为对象,用在静态方法之中,这时<code>super</code>将指向父类,而不是父类的原型对象。</div><ol style="background-color: #FCFCFC;"><li><code>class Parent {</code><br /></li><li><code>static myMethod(msg) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('static'%2C%20msg)%3B%60%22%2C%22id%22%3A%22ts30d%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>myMethod(msg) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60console.log('instance'%2C%20msg)%3B%60%22%2C%22id%22%3A%22Boqs0%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>class Child extends Parent {</code><br /></li><li><code>static myMethod(msg) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super.myMethod(msg)%3B%60%22%2C%22id%22%3A%22ffJ4X%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>myMethod(msg) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super.myMethod(msg)%3B%60%22%2C%22id%22%3A%22QACXL%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>Child.myMethod(1); // static 1</code><br /></li><li><code>var child = new Child();</code><br /></li><li><code>child.myMethod(2); // instance 2</code><br /></li></ol><div style="background-color: #FCFCFC;">(1)子类的<code>__proto__</code>属性,表示构造函数的继承,总是指向父类。</div><div style="background-color: #FCFCFC;">(2)子类<code>prototype</code>属性的<code>__proto__</code>属性,表示方法的继承,总是指向父类的<code>prototype</code>属性。</div><ol style="background-color: #FCFCFC;"><li><code>class A {</code><br /></li><li><code>}</code><br /></li><li><code>class B extends A {</code><br /></li><li><code>}</code><br /></li><li><code>B.__proto__ === A // true</code><br /></li><li><code>B.prototype.__proto__ === A.prototype // true</code><br /></li></ol><div style="background-color: #FCFCFC;">继承原生构造函数:</div><ul style="background-color: #FCFCFC;"><li>Boolean()</li><li>Number()</li><li>String()</li><li>Array()</li><li>Date()</li><li>Function()</li><li>RegExp()</li><li>Error()</li><li>Object()</li></ul><ol style="background-color: #FCFCFC;"><li><code>class MyArray extends Array {</code><br /></li><li><code>constructor(...args) {</code><br /></li></ol><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%60super(...args)%3B%60%22%2C%22id%22%3A%22QqWNs%22%7D"></div><ol style="background-color: #FCFCFC;"><li><code>}</code><br /></li><li><code>}</code><br /></li><li><code>var arr = new MyArray();</code><br /></li><li><code>arr[0] = 12;</code><br /></li><li><code>arr.length // 1</code><br /></li><li><code>arr.length = 0;</code><br /></li><li><code>arr[0] // undefined</code><br /></li></ol><div style="background-color: #FCFCFC;">Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。</div><ol style="background-color: #FCFCFC;"><li><code>const a = {</code></li><li><code>a: 'a'</code></li><li><code>};</code></li><li><code>const b = {</code></li><li><code>b: 'b'</code></li><li><code>};</code></li><li><code>const c = {...a, ...b}; // {a: 'a', b: 'b'}</code></li></ol><h4 id="EVuLz" style="background-color: #FCFCFC;"></h4><div>ES6</div>
ES6速通(下)https://developer.aliyun.com/article/1504141?spm=a2c6h.13148508.setting.18.36834f0eMJOehx