JavaScript 生成器之精髓
迭代器/生成器是一组用以做自动化流程控制的语法对象。其核心价值点有二: 可自动化的流程设计与控制 、 可暂停特性 。其他所谓价值都是纸老虎。
迭代器 - iterator
迭代器和生成器是你天天在用但又感知不到的东西。生成器是用来生成迭代器的东西,其基础还是迭代器。让我们先从迭代器入手。
迭代器是一类 特殊对象 ,它具有用来 访问迭代过程 的 专用接口 。迭代者,循环也。可以这样理解,它是专门用来处理循环过程的一类接口,最终达到简化循环的目的。任何具备这类接口特征的对象,都认为具有了迭代器的特征。先来看看手写循环过程的痛点在哪里:
const colors = ['blue', 'green', 'red']
for (let i = 0; i < colors.length; i++) {
doSomething(colors[i])
}
这可能是你每天都会写的代码。但看完这篇文章后,希望这样的代码不要再出现于你的手下。2018 年,不应该再用这么命令式的方式写代码了。命令式有什么毛病呢?
- 对于每类数组,其循环结构的模板代码都是重复的。代码量较大,重复多,不优雅
- 需要手动管理索引,以获得对应元素的值
- 对于多重循环,不同层之间的索引值可能相互引用修改,难以调试
相反,我们至少应该以更加「声明式」一些的思路来写。比如,以下的方式:
for (color of colors) {
doSomething(color)
}
这是 ES6 提供的 for..of
语法。当然,你可以说它就是把循环过程封装在关键字下,但试想一下,如果它处理的不是数组呢?你就不能以索引的方式来访问元素了,对不?你也就不能用 length
的方式来判断循环结束了,对不?于是我们想到,为了使我们对命令式循环的处理流程更加通用,我们需要为每一类所谓的「可迭代对象」编写一个具有统一接口的迭代器,然后就能在 for..of
底下基于一套相同的迭代器接口来访问它们了。
一个典型的迭代器,主要就是要提供一个 next
接口,这个接口需要回答两个问题:下一个元素的值是啥,以及是否还有下一个元素。
function createArrayIterator(items) {
let index = 0
return {
next() {
const done = index >= items.length
const value = done ? undefined : items[index++]
return { value, done }
},
}
}
在 ES6 中,这个迭代器函数被保存到原型上的 [Symbol.iterator]
键上。 也即是说,所有提供了这个键的对象,都能被当成可迭代对象被访问 (如 for..of
运算符、展开运算符 ...object
等)。JS 中内建类型的可迭代对象分别有:
内建类型 | 迭代器 |
---|---|
Array.prototype[Symbol.iterator] | [Function: values] |
Set.prototype[Symbol.iterator] | [Function: values] |
Map.prototype[Symbol.iterator] | [Function: entries] |
String.prototype[Symbol.iterator] | [Function: [Symbol.iterator]] |
然而呢,到目前为止,这个东西看起来还没什么价值。除了,它本身是一种十分常见的编程模式;除了,可以为你自定义的对象定义迭代器……好像就没啥价值了。
生成器 - generator
生成器函数。生成什么的函数呢?是生成迭代器的一个函数,它会返回一个迭代器,只不过用它生成的迭代器,除了可以 next()
,还可以 throw()
。一方面,可以用它来方便地生成一些迭代器;但它最大的价值,还在于 yield
语句提供了暂停执行的能力。只有在被 next()
的时候,它才会接着往下执行。
这里有两个重点:一个是,被谁 next()
?一个是,这种暂停执行的能力有什么价值?
不管是被谁 next()
,它都应该是一个 自动化的程序 。抽象出迭代器接口,以及自动化创建迭代器的生成器函数,目标无非是为了更灵活、更自动化的流程控制。因此,只有当这个「流程控制」具备自动化的能力时,生成器和迭代器函数才显出其价值。在语言规范中,称这两者为 Control Abstraction(流程)控制抽象层,也是这个道理。这是学习生成器质问其价值的一个重要回答。也就是说,很多其他(比如知乎上讲的)作用都不是非生成器不可的价值。
其次是,这种暂停执行的能力有什么价值?目前看到的就是实现异步流程的同步化( async
/ await
),以及 redux-saga
这样的应用。其他还没看到什么特别的价值。有些人说,支持「懒求值」,这个事情我看来好像就是瞎扯,因为如果 generator 函数最终被自动化,你哪里有手动「懒」一把的地方?还不是一把全执行完。当然,可能是我没有理解到它的场景。
总结起来,什么时候可以考虑用生成器技术呢,两个:
- 需要实现一套 可自动化 的 流程控制 程序时
- 需要利用 暂停执行 的能力时
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: @Output 装饰器的作用是什么?
下一篇: ES6 改进的数组功能
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论