
从JavaScript中的for...of说起(上)
先来看一段很常见的代码:
上面的代码中,用for...of来遍历一个数组。其实这里说遍历不太准确,应该是说:for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
iterator
ECMAScript 2015规定了关于迭代的协议,这些协议可以被任何遵循某些约定的对象来实现。如果一个js对象想要能被迭代,那么这个对象或者其原型链对象必须要有一个Symbol.iterator的属性,这个属性的值是一个无参函数,返回一个符合迭代器协议的对象。这样的对象被称为符合【可迭代协议】。
typeof Array.prototype[Symbol.iterator] === 'function'; // true
typeof Array.prototype[Symbol.iterator]() === 'object'; // true
数组之所以可以被for...of迭代,就是因为数组的原型对象上拥有Symbol.iterator属性,这个属性返回了一个符合【迭代器协议】的对象。
一个符合【迭代器协议】的对象必须要有一个next属性,next属性也是一个无参函数,返回一个对象,这个对象至少需要有两个属性:done, value, 大概长成下面这样:
依旧来看一下数组:
不光for...of会使用对象的iterator接口,下面这些用法也会默认使用对象的iteretor接口。
(1) 解构赋值 (2) 扩展运算符 (3) yield*
generator
生成器对象和生成器函数
generator表示一个生成器对象。这个对象符合【可迭代协议】和【迭代器协议】,是由生成器函数(generator function)返回的。
什么是生成器函数呢?MDN上的描述如下:
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 (iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。next()方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。
看下面的例子:
因为生成器对象符合可迭代协议和迭代器协议,我们可以用for...of来进行迭代。for…of会拿到迭代器返回值的value,也就是说,在迭代generator时,for…of拿到的是yield后面紧跟的那个值。
如果我们使用for...of来遍历上述的生成器对象,由于for…of拿到的是迭代器返回值的value,所以会得到以下的结果:
下面是一个使用generator和for...of输出斐波拉契数列的经典例子:
稍微总结一下,generator给了我们控制暂停代码执行的能力,我们可以自己来控制代码执行。那是否可以用generator来写异步操作呢 ?
iterator,generator与异步操作
一个很常见的场景: 页面发起一个ajax请求,请求返回后,执行一个回调函数。在这个回调函数里,我们使用第一个请求返回的url,再次发起一个ajax请求。(这里先不考虑使用Promise)
// 我们先定义发起ajax的函数,这里用setTimeout模拟一下
我们尝试用generator的写法来实现上面的需求.
// 先把ajax函数改造一下, 把url提出来作为一个参数,然后返回一个只接受回调函数作为参数的newAjax函数
// 这种只接受回调函数作为参数的函数被称为thunk函数。
在上面的代码中,我们使用generator实现了依次执行两个异步操作。上面的代码看起来是比较复杂的。整个的逻辑在gen这个generator function里,然后我们手动执行完了g这个generator。按照上面的代码,如果我们想再加入一个ajax请求,需要先修改generator function,然后修改generator的执行逻辑。我们来实现一个自动的流程,只需要定义好generator,让它自动执行。
这下,我们就可以专注于generator function的逻辑了。
// 自动执行
autoRun(gen);
著名的co就是一个自动执行generator的库。
上面的代码中,gen函数体内,我们用同步代码的写法,实现了异步操作。可以看到,用gererator来执行异步操作,在代码可读性、可扩展性上面,是很有优势的。如今,我们或许会像下面这样来写上面的逻辑:
写在后面
本文从for..of入手,梳理了javascript中的两个重要概念:iterator和generator。并且介绍了两者在异步操作中的应用。符合预期。下一篇文章中,将介绍async、await,任务队列的相关内容,希望能对js中的异步代码及其写法有一个更深入,全面的认识。
