ES6之迭代器与生成器

迭代器

之前写过一篇文章关于可迭代对象-谈谈遍历与迭代协议, 并讲述了可迭代协议(iterable protocal);

还遗留了一个迭代器协议(iterator)没有讲述,这次我们就来说一说迭代器协议;

迭代器协议

MDN上是这样定义iterator protocal的:

The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite).

An object is an iterator when it implements a next() method with the following semantics:

Property Value
next A zero arguments function that returns an object with two properties:
  • done (boolean)
    • Has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator. The return values are explained here.
    • Has the value false if the iterator was able to produce the next value in the sequence. This is equivalent of not specifying the done property altogether.
  • value - any JavaScript value returned by the iterator. Can be omitted when done is true.

The next method always has to return an object with appropriate properties including done and value. If a non-object value gets returned (such as false or undefined), a TypeError (“iterator.next() returned a non-object value”) will be thrown. |

其实就是迭代器拥有一个next方法,每次调用返回含有value和done两个属性的对象,value表示下一个将要返回的值;done是一个布尔值,当没有更多返回数据时返回true;

1
2
3
4
5
6
7
8
var someArray = [1, 5];
var someArrayEntries = someArray.entries();
someArrayEntries.toString(); // "[object Array Iterator]"
someArrayEntries === someArrayEntries[Symbol.iterator](); // true
someArrayEntries.next(); // {value: Array(2), done: false}
someArrayEntries.next(); // {value: Array(2), done: false}
someArrayEntries.next(); // {value: undefined, done: true}

上个例子中someArray.entries()生成了一个迭代器,每次调用next返回一个对象;当迭代到最后,返回{value: undefined, done: true};并且特殊的是someArrayEntries也是一个符合iterable协议的可迭代对象,当调用someArrayEntries[Symbol.iterator])的时候返回一个迭代器对象就是其本身;

迭代器与可迭代对象的联系

1
2
3
4
5
6
7
iterableObj[Symbol.iterator]() ===> iteratorObj;
for (let v of iterableObj){
console.log(v);
}
iteratorObj.next(); //{value: v, done: false}
...
iteratorObj.next(); //{value: undefined, done: true}

以上介绍生成迭代器的方法还是针对具有Symbol.iterator属性的常见对象(如数组,map等)来的,怎么更通用的生成一个迭代器呢?

内建迭代器

1.集合对象迭代器

ES6中有三种类型集合对象: 数组、Map集合与Set集合。为了更好的访问对象中的内容,都内建了以下三种迭代器:

  • entries() 返回一个迭代器,其值为多个键值对
  • values() 返回一个迭代器,其值为集合中的值
  • keys() 返回一个迭代器,其值为集合中的所有键名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let colors = [ "red", "green", "blue" ];
for (let entry of colors.entries()) {
console.log(entry);
}
//[0, "red"]
//[1, "green"]
//[2, "blue"]
let tracking = new Set([1234, 5678, 9012]);
for (let value of tracking.values()) {
console.log(value);
}
// 1234
// 5678
// 9012
let data = new Map();
for (let key of data.keys()) {
console.log(key);
}
// "title"
// "format"

在for-of循环中,如果没有显式指定则使用默认的迭代器。数组和Set集合的默认迭代器是values()方法,Map默认迭代器是entries方法。

2.字符串迭代器和NodeList迭代器

1
2
3
4
var message = "A 𠮷 B";
for (let c of message) {
console.log(c);
}

NodeList迭代器:

1
2
3
4
5
var divs = document.getElementsByTagName("div");
for (let div of divs) {
console.log(div.id);
}
}

生成器

上面的例子中生成迭代器的方法还是针对具有Symbol.iterator属性的常见对象(如数组,map等)来的,怎么更通用的生成一个迭代器呢?

ES6中引入了生成器;生成器是一种返回迭代器的函数,通过function关键字后的*号来表示,函数中会用到新的关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格,如:

1
2
3
4
5
6
7
8
9
10
11
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// generators are called like regular functions but return an iterator
let iterator = createIterator();
console.log(iterator); // [object Generator]
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

上面的例子createIterator就是一个生成器,生成器执行返回的是一个生成器对象,生成器对象既是一个迭代器也是一个可迭代对象。

yield

生成器函数最有趣的部分是:每当执行完一条yield语句后函数就会自动停止执行。

**yield关键字只可以在生成器内部使用,在其他地方使用会导致程序抛出语法错误,即便在生成器内部的函数里使用也是如此:

1
2
3
4
5
6
function *createIterator(items) {
items.forEach(function(item) {
// syntax error
yield item + 1;
});
}

从字面量上看,yield关键字确实在createIterator函数内部,但是它与return关键字一样,二者都不能穿透函数边界。嵌套函数中的return语句不能用做外部函数的返回语句,而此处嵌套函数中的yield语句会导致程序抛出语法错误。

生成器函数表达式

1
2
3
4
5
6
7
8
9
10
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let 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 }"

不能用箭头函数来创建生成器

生成器对象的方法

由于生成器对象本身就是函数,因而可以将她们添加到对象中。例如

1
2
3
4
5
6
7
let o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
} };
let iterator = o.createIterator([1, 2, 3]);

也可以用ES6对象方法的简写方式来创建生成器;

1
2
3
4
5
6
7
let o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
} };
let iterator = o.createIterator([1, 2, 3]);

高级生成器

生成器根据需求计算它们的产出值,这使得它们能够有效地表示计算成本高的序列,或者甚至如上所述的无限序列。

给迭代器传递参数

The next() 方法也接受可用于修改生成器内部状态的值。传递给next()的值将被视为暂停生成器的最后一个yield表达式的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function* fibonacci() {
var fn1 = 0;
var fn2 = 1;
while (true) {
var current = fn1;
fn1 = fn2;
fn2 = current + fn1;
var reset = yield current;
if (reset) {
fn1 = 0;
fn2 = 1;
}
}
}
var sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2

在迭代器中抛出错误

1
2
3
4
5
6
7
8
9
function *createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next(4)); // {value: 6, done: false}
console.log(iterator.throw(new Error("Boom")));

生成器返回语句

1
2
3
4
5
6
7
8
function *createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

上面例子中return语句返回一个值,该值被赋值给返回对象的value属性,本来第二条是返回{ value: undefined, done: true }的最终返回{ value: 42, done: true }

委托生成器

语法:

1
2
3
yield* [[expression]];
expression
返回一个可迭代对象的表达式。

yield* 表达式迭代操作数,并产生它返回的每个值。yield* 表达式本身的值是当迭代器关闭时返回的值(即done为true时);

1.委托给其他生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var iterator = g2();
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: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

2.委托给其他可迭代对象

除了生成器对象这一种可迭代对象,yield* 还可以 yield 其它任意的可迭代对象,比如说数组、字符串、arguments 对象等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* g3() {
yield* [1, 2];
yield* "34";
yield* arguments;
}
var iterator = g3(5, 6);
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: "4", done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: 6, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

3.yield* 表达式的值

yield* 是一个表达式,不是语句,所以它会有自己的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* g4() {
yield* [1, 2, 3];
return "foo";
}
var result;
function* g5() {
result = yield* g4();
}
var iterator = g5();
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 }, 此时 g4() 返回了 { value: "foo", done: true }
console.log(result); // "foo"

koa1源码中就有用到委托生成器语法;

可迭代对象

ES6中,所有的集合对象(数组、Set集合、Map集合)和字符串都是可迭代对象,这些对象中都有默认的迭代器;

之前的文章谈过可迭代对象,这里就总结一下:

for-of循环

可迭代对象可用于for-of循环:

1
2
3
4
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}

spread syntax (…)

可迭代对象可用于…语法:

[...'abc']; // ["a", "b", "c"]

用于yield *

1
2
3
function* gen() {
yield* ['a', 'b', 'c'];
}

用于解构

1
let [a, b, c] = new Set(['a', 'b', 'c']);