ES6 速通(中)

简介: ES6 速通(中)

ES6 速通(上)https://developer.aliyun.com/article/1504136?spm=a2c6h.13148508.setting.20.36834f0eMJOehx

17. Iterator 和 for … of 循环

扩展运算符

只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

  1. let arr = [...iterable];

yield

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

  1. let generator = function* () {
  2. yield 1;
  3. yield* [2,3,4];
  4. yield 5;
  5. };
  6. var iterator = generator();
  7. iterator.next() // { value: 1, done: false }
  8. iterator.next() // { value: 2, done: false }
  9. iterator.next() // { value: 3, done: false }
  10. iterator.next() // { value: 4, done: false }
  11. iterator.next() // { value: 5, done: false }
  12. 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 函数

  1. let myIterable = {
  2. [Symbol.iterator]: function* () {
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. }
  7. }
  8. [...myIterable] // [1, 2, 3]
  9. // 或者采用下面的简洁写法
  10. let obj = {
  11. * [Symbol.iterator]() {
  12. yield 'hello';
  13. yield 'world';
  14. }
  15. };
  16. for (let x of obj) {
  17. console.log(x);
  18. }
  19. // "hello"
  20. // "world"

遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

  1. function readLinesSync(file) {
  2. return {
`[Symbol.iterator]() {`
`return {`
`next() {`
`return { done: false };`
`},`
`return() {`
`file.close();`
`return { done: true };`
`}`
`};`
`},`
  1. };
  2. }
    上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行return方法。
  3. // 情况一
  4. for (let line of readLinesSync(fileName)) {
  5. console.log(line);
  6. break;
  7. }
  8. // 情况二
  9. for (let line of readLinesSync(fileName)) {
  10. console.log(line);
  11. throw new Error();
  12. }

18. Generator 函数

函数的写法如下:

  1. 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)的语法功能。

复制代码

  1. function* gen() {
  2. yield 123 + 456;
  3. }

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

yield表达式与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。

Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

  1. function* f() {
  2. console.log('执行了!')
  3. }
  4. var generator = f();
  5. setTimeout(function () {
  6. generator.next()
  7. }, 2000);

上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行。

另外需要注意,yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。

  1. var ticking = true;
  2. var clock = function() {
  3. if (ticking)
`console.log('Tick!');`
  1. else
`console.log('Tock!');`
  1. ticking = !ticking;
  2. }

上面代码的clock函数一共有两种状态(TickTock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。

  1. var clock = function* () {
  2. while (true) {
`console.log('Tick!');`
`yield;`
`console.log('Tock!');`
`yield;`
  1. }
  2. };

18. Generator 函数的语法 - 应用 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack

generator 控制流

  1. scheduler(longRunningTask(initialValue));
  2. function scheduler(task) {
  3. var taskObj = task.next(task.value);
  4. // 如果Generator函数未结束,就继续调用
  5. if (!taskObj.done) {
  6. task.value = taskObj.value
  7. scheduler(task);
  8. }
  9. }
  10. let steps = [step1Func, step2Func, step3Func];
  11. function* iterateSteps(steps){
  12. for (var i=0; i< steps.length; i++){
  13. var step = steps[i];
  14. yield step();
  15. }
  16. }

for … of 无法遍历return 对象

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. yield 5;
  7. return 6;
  8. }
  9. for (let v of foo()) {
  10. console.log(v);
  11. }
  12. // 1 2 3 4 5

上面代码使用for...of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为truefor...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

  1. function* numbers () {
  2. yield 1;
  3. try {
  4. yield 2;
  5. yield 3;
  6. } finally {
  7. yield 4;
  8. yield 5;
  9. }
  10. yield 6;
  11. }
  12. var g = numbers();
  13. g.next() // { value: 1, done: false }
  14. g.next() // { value: 2, done: false }
  15. g.return(7) // { value: 4, done: false }
  16. g.next() // { value: 5, done: false }
  17. g.next() // { value: 7, done: true }

上面代码中,调用return()方法后,就开始执行finally代码块,不执行try里面剩下的代码了,然后等到finally代码块执行完,再返回return()方法指定的返回值。

next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

next()是将yield表达式替换成一个值。

  1. const g = function* (x, y) {
  2. let result = yield x + y;
  3. return result;
  4. };
  5. const gen = g(1, 2);
  6. gen.next(); // Object {value: 3, done: false}
  7. gen.next(1); // Object {value: 1, done: true}
  8. // 相当于将 let result = yield x + y
  9. // 替换成 let result = 1;

上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined

throw()是将yield表达式替换成一个throw语句。

  1. gen.throw(new Error('出错了')); // Uncaught Error: 出错了
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = throw(new Error('出错了'));

return()是将yield表达式替换成一个return语句。

  1. gen.return(2); // Object {value: 2, done: true}
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = return 2;

yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

yield*命令可以很方便地取出嵌套数组的所有成员。

复制代码

  1. function* iterTree(tree) {
  2. if (Array.isArray(tree)) {
`for(let i=0; i < tree.length; i++) {`
`yield* iterTree(tree[i]);`
`}`
  1. } else {
`yield tree;`
  1. }
  2. }
  3. const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
  4. for(let x of iterTree(tree)) {
  5. console.log(x);
  6. }
  7. // a
  8. // b
  9. // c
  10. // d
  11. // e

由于扩展运算符...默认调用 Iterator 接口,所以上面这个函数也可以用于嵌套数组的平铺。

  1. [...iterTree(tree)] // ["a", "b", "c", "d", "e"]

this 结合 generator :

  1. function* F() {
  2. this.a = 1;
  3. yield this.b = 2;
  4. yield this.c = 3;
  5. }
  6. var f = F.call(F.prototype);
  7. f.next(); // Object {value: 2, done: false}
  8. f.next(); // Object {value: 3, done: false}
  9. f.next(); // Object {value: undefined, done: true}
  10. f.a // 1
  11. f.b // 2
  12. f.c // 3

19. Generator 函数的异步应用

Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

下面看看如何使用 Generator 函数,执行一个真实的异步任务。

  1. var fetch = require('node-fetch');
  2. function* gen(){
  3. var url = 'https://api.github.com/users/github';
  4. var result = yield fetch(url);
  5. console.log(result.bio);
  6. }

上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。

执行这段代码的方法如下。

  1. var g = gen();
  2. var result = g.next();
  3. result.value.then(function(data){
  4. return data.json();
  5. }).then(function(data){
  6. g.next(data);
  7. });

上面代码中,首先执行 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 执行器。

  1. function run(fn) {
  2. var gen = fn();
  3. function next(err, data) {
`var result = gen.next(data);`
`if (result.done) return;`
`result.value(next);`
  1. }
  2. next();
  3. }
  4. function* g() {
  5. // ...
  6. }
  7. run(g);

上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。

下面是一个 Generator 函数,用于依次读取两个文件。

  1. var gen = function* () {
  2. var f1 = yield readFile('/etc/fstab');
  3. var f2 = yield readFile('/etc/shells');
  4. console.log(f1.toString());
  5. console.log(f2.toString());
  6. };

co 模块可以让你不用编写 Generator 函数的执行器。

  1. var co = require('co');
  2. co(gen);

co就是把对象转化为promise对象如何层层then

20. async 函数

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

前文有一个 Generator 函数,依次读取两个文件。

  1. const fs = require('fs');
  2. const readFile = function (fileName) {
  3. return new Promise(function (resolve, reject) {
`fs.readFile(fileName, function(error, data) {`
`if (error) return reject(error);`
`resolve(data);`
`});`
  1. });
  2. };
  3. const gen = function* () {
  4. const f1 = yield readFile('/etc/fstab');
  5. const f2 = yield readFile('/etc/shells');
  6. console.log(f1.toString());
  7. console.log(f2.toString());
  8. };

上面代码的函数gen可以写成async函数,就是下面这样。

  1. const asyncReadFile = async function () {
  2. const f1 = await readFile('/etc/fstab');
  3. const f2 = await readFile('/etc/shells');
  4. console.log(f1.toString());
  5. console.log(f2.toString());
  6. };

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

ad

async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

  1. asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

asyncawait,比起星号和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 函数有多种使用形式。

  1. // 函数声明
  2. async function foo() {}
  3. // 函数表达式
  4. const foo = async function () {};
  5. // 对象的方法
  6. let obj = { async foo() {} };
  7. obj.foo().then(...)
  8. // Class 的方法
  9. class Storage {
  10. constructor() {
  11. this.cachePromise = caches.open('avatars');
  12. }
  13. async getAvatar(name) {
  14. const cache = await this.cachePromise;
  15. return cache.match(`/avatars/${name}.jpg`);
  16. }
  17. }
  18. const storage = new Storage();
  19. storage.getAvatar('jake').then(…);
  20. // 箭头函数
  21. const foo = async () => {};

Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

下面是一个例子。

  1. async function getTitle(url) {
  2. let response = await fetch(url);
  3. let html = await response.text();
  4. 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

目录
相关文章
|
7月前
|
JSON 前端开发 JavaScript
ES6 速通(上)
ES6 速通(上)
46 1
|
7月前
|
编解码 JavaScript 前端开发
ES6 速通(下)
ES6 速通(下)
58 1
|
缓存 JavaScript 算法
每天3分钟,重学ES6-ES12(十八) CJS
每天3分钟,重学ES6-ES12(十八) CJS
93 0
|
前端开发 JavaScript
每天3分钟,重学ES6-ES12系列文章汇总
每天3分钟,重学ES6-ES12系列文章汇总
67 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(二)
每天3分钟,重学ES6-ES12(十八)ES Module
82 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(一)
每天3分钟,重学ES6-ES12(十八)ES Module
85 0
|
前端开发 JavaScript 小程序
每天3分钟,重学ES6-ES12(十七)模块化历史
每天3分钟,重学ES6-ES12(十七)模块化历史
91 0
|
JavaScript 前端开发 数据库
再不学ES6你就out了 —— 一文搞懂ES6
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版 虽然15年就有正式版本了,但是国内普遍商用是在2018年之后去了,甚至到现在有很多前端仍然搞不懂ES6
175 0
再不学ES6你就out了 —— 一文搞懂ES6
|
前端开发 容器
一文看透 Module Federation 上
一文看透 Module Federation
1420 2
一文看透 Module Federation 上
|
前端开发 JavaScript 开发工具
一文看透 Module Federation 下
一文看透 Module Federation
1131 1
一文看透 Module Federation 下