迭代器与生成器
JavaScript 迭代器与生成器
生成器是一种返回迭代器的函数,,使用
迭代器
Generator
普通的函数有一个入口,有一个返回值;当函数被调用时,从入口开始执行,结束时返回相应的返回值。生成器定义的函数,有多个入口和多个返回值;对生成器执行
yield
与 next
在生成器中扮演者非常重要的角色,前者是操作符,后者则是生成器上的属性函数,二者满足如下特性:
- 生成器的语句会在外部调用
next
函数时执行,即我们可以在生成器之外控制其内部操作的执行过程。 - 当生成器执行到
yield
操作符时会立即执行yield
之后的语句并且暂停,语句的值作为上一步next
函数的返回值,其是形如{done:false, value:x}
的对象。 - 继续调用
next
函数会使生成器继续执行,此处next
函数的参数值会作为整个yield
语句的值;生成器继续执行直到再次遇到yield
,或是遇到return
/ throw
生成器就退出。 next
函数的返回值具有三种情况:- 如果再次遇到
yield
,next
返回值中的value
属性是紧接在这条yield
之后的语句执行之后的返回值; - 如果遇到的是
return
,那么返回对象{done:true, value}
则是return
的返回值; - 其他情况下,返回对象
{done:false, value:undefined}
;
- 如果再次遇到
我们可以通过简单的例子来验证上述流程描述:
const iter = (function* gen() {
console.log(`yield ${yield "a" + 0}`);
console.log(`yield ${yield "b" + 1}`);
return "c" + 2;
})();
console.log(`next:${iter.next(0).value}`); //输出 next:a0
console.log(`next:${iter.next(1).value}`); //输出 yield 1 next:b1
console.log(`next:${iter.next(2).value}`); //输出 yield 2 next:c2
第一个 next
触发生成器执行到第一个 yield
,并立即执行'a' + 0 = 'a0'
a0
next
的返回值;第二个带参数为 1
的 next
触发生成器继续执行,此时 第一个 yield
才返回 1
,然后执行到 第二个 yield
并立即立即这条 yield
后面的 'b' + 1 = 'b1'
,b1
作为这次 next
的返回;第三个
const f = (function* fibonacci() {
let [a, b] = [0, 1];
for (;;) {
yield a;
[a, b] = [b, a + b];
}
})();
//执行三次,得到三个对象,其value值分别是0,1,1
for (let i of Array(3).keys()) {
console.log(f.next());
}
迭代器| Iterator
可迭代对象
可迭代对象,都有一个
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
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}"
在这段代码中,通过
用
function isIterator(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new Map())); // true
console.log(isIterable("Hello")); // true
当我们在创建对象时,给
let collection = {
items: [],
*[Symbol.iterator]() {
// 将生成器赋值给对象的Symbol.iterator属性来创建默认的迭代器
for (let item of this.items) {
yield item;
}
},
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
内建迭代器
每个集合类型都有一个默认的迭代器,在
let colors = ["red", "green", "blue"];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "print");
// 与调用 colors.values() 方法相同
for (let value of colors) {
console.log(value);
}
// 与调用 tracking.values() 方法相同
for (let num of tracking) {
console.log(num);
}
// 与调用 data.entries() 方法相同
for (let entry of data) {
console.log(entry);
}
展开运算符可以操作所有的可迭代对象,并根据默认迭代器来选取要引用的值,从迭代器读取所有值。然后按返回顺序将它们依次插入到数组中。因此如果想将可迭代对象转换为数组,用展开运算符是最简单的方法。
let set = new Set([1, 2, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
迭代器的异常处理
除了给迭代器传递数据外,还可以给它传递错误条件。通过
function* createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 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"))); // 从生成器中抛出错误
这个示例中,前两个表达式正常求值,而调用
function* createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2; // yield 4 + 2, 然后抛出错误
} catch (e) {
second = 6;
}
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"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
这里有个有趣的现象:调用