提示
: 本文是 github 上《Understanding ECMAScript 6》 的笔记整理,代码示例也来源于此。你们有时间能够直接读这本书。虽是英文,但通俗易懂,很是推荐。node
以前面试时有被问到为何会有 Generator, 还好没懵逼。想知道的吗,往下翻。git
原文连接
es6
这里主要介绍 generator
的由来和一些基本概念,已经了解了或者想了解 generator
高级用法的能够看这篇 怎么往 Generator 里抛个错?github
ES5
里遍历一个数组须要这样:面试
const colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
console.log(colors[i]);
}
复制代码
能够看出:chrome
这段代码逻辑简单, 但写法复杂而枯燥。并且很经常使用,因此很容易因手抖而出bug。为了简化写法,下降出错机率,ES6
引入了一些新的语法,其中一个是 iterator
。数组
iterator
也是一种对象,不过它有着专为迭代而设计的接口。它有next
方法,该方法返回一个包含 value
和 done
两个属性的对象 (下称 result
)。前者是迭代的值,后者是代表迭代是否完成的标志 -- 布尔值: true
表示迭代完成,false
表示没有。iterator
内部有指向迭代位置的指针,每次调用next
, 自动移动指针并返回相应的 result
。bash
下面是一个自定义的 iterator
:函数
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined; return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 后续的全部调用返回的结果都同样
console.log(iterator.next()); // "{ value: undefined, done: true }"
复制代码
能够看出,只须要咱们不断执行next
就能够,不须要手动追踪迭代的位置。比开篇的循环简单了些。post
注意: 最后一次迭代返回的
value
, 并非集合(如上例子中的items
)里的值,而是undedined
或者函数返回值。 查看这里,能够了解返回值与result的关系
上面虽然是比 for
循环简单了些,但手动写个 iterator
太麻烦了,因此ES6
推出 generator
,方便建立 iterator
。也就是说,generator
就是一个返回值为 iterator
的函数。
其语法以下:
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
// generators能够像正常函数同样被调用,不一样的是会返回一个 iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
复制代码
例子很明了,*
标明这是个 generators
, yield
用来在调用 next
时返回 value
。
方法里的 generator
, 能够简写:
let o = {
createIterator: function* (items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
// 等同于
let o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
复制代码
能够看到,建立 generator
函数有三种方法
函数声明
function* createIterator() {...}
复制代码
函数表达式
const createIterator = function* () {...}
复制代码
对象里的简写方式
let o = {
*createIterator(items) {
...
}
};
复制代码
能够认为,就是在函数关键字 function
和函数名之间加 *
, 只不过,不一样场景下关键和函数名能够省略罢了
[function] * [name]() {}
// * 能够靠近关键字也能够靠近函数名,或两不靠近,均可以
复制代码
注意点:
须要注意的是,yield
不能跨函数:
function* createIterator(items) {
items.forEach(function (item) {
// 语法错误
yield item + 1;
});
}
复制代码
箭头函数不能用作 generator
和 iterator
紧密是相关的,有个叫 iterable
的对象。它有个 Symbol.iterator
属性,其值是个 generator
函数。
用代码描述的话,iterable
长这样:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
复制代码
ES6
里的集合如数组、set、map甚至字符串,都是 iterable
。generator
建立的 iterator
都被默认添加了 Symbol.iterator
,因此,它们也是 iterable
。
全部的 iterable
, 也均可以使用 for-of
循环。
虽然有了 iterator
,只要调用它的next
方法,就能够迭代了。可是,每次手动调用也太麻烦了,因此 ES6
推出了 for-of
循环:
const colors = ["red", "green", "blue"];
for (let color of colors) {
console.log(color);
}
复制代码
对比开篇的 for
循环:
const colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
console.log(colors[i]);
}
复制代码
能够看出,for-of
循环
只须要声明变量活动每次迭代的值便可,简洁明了。
注意:for-of
只能用在 iterable
上,用其余对象上会报错。
iterator
是 ES6
里一个重要的部分,一些内置的数据类型都内置了 iterator
,方便开发。
这里简要介绍下有 iterator
的数据类型。虽然简单,但不是不重要。
ES6
里的集合有三类:
他们下面的 iterator
有:
entries():
返回迭代结果为键值对的 iterator
; values():
返回迭代结果为集合里的值的 iterator
; keys():
返回迭代结果为集合里的 key
的 iterator
;
下面以 map
为例:
let tracking = new Map([
['name', 'jeyvie'],
['pro', 'fd'],
['hobby', 'programming']
]);
for (let entry of tracking.entries()) {
console.log(entry);
}
// ["name", "jeyvie"]
// ["pro", "fd"]
// ["hobby", "programming"]
for (let key of tracking.keys()) {
console.log(key);
}
// name
// pro
// hobby
for (let value of tracking.values()) {
console.log(value);
}
// jeyvie
// fd
// programming
复制代码
其中 set
里的 key
和 value
相等,都是 set
里的值。数组的 key
是其下标 index
, value
是里面的值。
另外,各集合都有默认的 iterator
供 for-of 调用。 其执行结果,反应的是集合如何被初始化的。
如上例子 tracking
:
for (let value of tracking) {
console.log(value);
}
// ["name", "jeyvie"]
// ["pro", "fd"]
// ["hobby", "programming"]
复制代码
对应这 Map
实例化是出入的子项 -- 有两个元素的数组。
其余的set
、 数组与之相似,不赘述了。
须要留意,经验证
chrome v65
和node v8.0
里数组都没有values()
。也许是由于对数组而言,它比较冗余吧。
字符串里 iterator
,理解一句话就能够: 是基于 code point
而不是code unit
迭代的。比较一下两个例子:
例子1:
基于 code unit
var message = "A 𠮷 B";
for (let i=0; i < message.length; i++) {
console.log(message[i]);
}
// A
// (空)
// �
// �
// (空)
// B
复制代码
例子2:
基于 code point
var message = "A 𠮷 B";
for (let c of message) {
console.log(c);
}
// A
// (空)
// 𠮷
// (空)
// B
复制代码
基于code point
能够理解为基于字符, 关于它是什么,能够查看字符串那章。
之前要迭代 NodeList
(元素集合),须要用 for
循环:
// 能够这样
for (var i=0, len=NodeList; i<len; i++) {
var el = NodeList[i]
// ....
}
复制代码
ES6
能够直接这样:
for (let el of NodeList) {
// el 就是集合里的每一个元素
}
复制代码
另外,如今
NodeList
也有forEach
方法了
spread操做符
...
和 iterable展开操做符能够用在全部的 iterable
上,并根据该 iterable
的默认 iterator
决定取哪一个值。
好比咱们能够将字符串转为数组:
[...'A𠮷B']
// ['A', '𠮷', 'B']
复制代码
这里以 for
循环的问题开始, 讲述 ES6
里怎么解决这个问题,由此引出 generator
, 而后带出了 iterator
和 iterable
, 以及操做 iterable
的 for-of
。 最后讲了 ES6
里结合 for-of
, 能使内置集合
的操做起来更加方便。
固然了,for
循环问题也许并非最初 generator
产生的缘由,generator
也不仅是解决了 for
循环这一个问题,它还有更多的高级功能,在实际中的做用更大,后面我会继续发表出来。
那我为何要写这篇文章,还成了个“标题党”呢?由于我好奇,我不仅是想知道一项技术当前到底怎么用,仍是知道它为什么出现,想知道其前因后果。这样,一方面能更了解了技术,另外一反面,也更能了解技术发展的趋势,这样,才能可能看得清前方啊,不至于东奔西撞,走那么多弯路。
最后,一切都是抛砖引玉,欢迎你们批评指正!
另外,generator
高级用法篇 怎么往 Generator 里抛个错? 已经写完,欢迎方家指正。