美文网首页
温故知新之ES6(五)

温故知新之ES6(五)

作者: simuty | 来源:发表于2018-09-11 11:43 被阅读17次

    1. Iterator 和 for...of 循环

    ES6 中有四种数据集合:数组( Array )、对象( Object )、Map 和 Set 。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
    遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

    1.1 Iteration作用:

    1. 为各种数据结构,提供一个统一的、简便的访问接口;
    2. 使得数据结构的成员能够按某种次序排列;
    3. ES6创造了一种新的遍历命令 for...of 循环,Iterator接口主要供 for...of 消费。
    

    1.2 Iterator 的遍历过程

    (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

    (2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。

    (3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。

    (4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

    ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。

    原生具备 Iterator 接口的数据结构如下。

    Array
    Map
    Set
    String
    TypedArray
    函数的 arguments 对象
    NodeList 对象
    

    1.3 调用 Iterator 接口的场合

    1)解构赋值

    对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。

    let set = new Set().add('a').add('b').add('c');
    let [x,y] = set;
    // x='a'; y='b'
    
    let [first, ...rest] = set;
    // first='a'; rest=['b','c'];
    
    2)扩展运算符

    扩展运算符(...)也会调用默认的 Iterator 接口。

    // 例一
    var str = 'hello';
    [...str] //  ['h','e','l','l','o']
    
    // 例二
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']
    // ['a', 'b', 'c', 'd']
    

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

    3)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 }
    

    4)其他场合

    由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

    for...of
    Array.from()
    Map(), 
    Set(), 
    WeakMap(), 
    WeakSet()(比如 new Map([['a',1],['b',2]]) )
    Promise.all()
    Promise.race()
    
    

    2 Generator

    Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

    Generator

    1. 语法上: 
        Generator 函数是一个状态机,封装了多个内部状态。Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
    2. 形式上: 
        Generator 函数是一个普通函数,但是有两个特征:
        一是,function关键字与函数名之间有一个星号;
        二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
    

    可以参看阮一峰老师的--Generator 函数的语法

    3. async 函数

    用过的人都说好

    ES2017 标准引入了 async 函数,使得异步操作变得更加方便

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

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

    3.1 async 的改进

    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 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

    4. 返回值是 Promise。

    async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。

    进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖

    1_ko3KtcVSlzpe3RnTRgJaHw (1).jpeg

    3.2 基本语法

    1. 例子
    1. async 函数返回一个 Promise 对象。
    2. await 命令,正常情况下, await 命令后面是一个 Promise 对象。如果不是,会被转成一个立即 resolve 的 Promise 对象。

    // 如下代码,三种情况加上捕获异常,应该很容易理解

    asyncDemo();
    async function asyncDemo() {
        async function sleep(para) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    resolve(para * para);
                }, 1000);
            });
        }
    
        try {
            const result = await sleep(2);
            console.log(result);
    
            // async使用 返回一个promise
            async function asyncSleep(para) {
                // 等待得到最终状态后返回
                return await sleep(para);
            }
            // await 使用
            const result_1 = await asyncSleep(3);
            console.log(result_1);
    
            asyncSleep(4).then(function(result_2) {
                console.log(result_2);
                return Promise.reject("异常")
            });
        } catch (err) {
            // 捕获异常
            console.log(err);
    
        }
    }
    
    

    答案:

    4
    9
    16
    (node:20766) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 异常
    

    3.3 使用注意点

    1. await 命令后面的 Promise 对象,运行结果可能是 rejected ,所以最好把 await 命令放在 try...catch 代码块中。
    2. 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
    
    let foo = await getFoo();
    let bar = await getBar();
    
    // 写法 1
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法 1.1 
    Promise.all([
        new Promise<any>(async (v, e) => v(await test_1().catch(err => e(err)))),
        new Promise<any>(async (v, e) => v(await test_2().catch(err => e(err)))),
    
    ])
    
    
    // 写法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    
    
    1. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

    3.4 async 函数的实现原理

    async 函数的实现原理,就是将** Generator 函数和自动执行器,包装在一个函数里。**

    async function fn(args) {
      // ...
    }
    // 等同于
    function fn(args) {
      return spawn(function* () {
        // ...
      });
    }
    
    

    所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

    下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

    function spawn(genF) {
      return new Promise(function(resolve, reject) {
        var gen = genF();
        function step(nextF) {
          try {
            var next = nextF();
          } catch(e) {
            return reject(e);
          }
          if(next.done) {
            return resolve(next.value);
          }
    Promise.resolve(next.value).then(function(v) {
            step(function() { return gen.next(v); });
          }, function(e) {
            step(function() { return gen.throw(e); });
          });
        }
        step(function() { return gen.next(undefined); });
      });
    }
    

    更多使用方法,根据业务场景具体分析吧
    比如:错误处理、并行执行

    相关文章

      网友评论

          本文标题:温故知新之ES6(五)

          本文链接:https://www.haomeiwen.com/subject/tgaygftx.html