循环问题
在一般循环结构中,如果需要跟踪多个变量,情况将变得复杂,容易出错。
迭代器
用于迭代对象的对象,一般提供 next()
方法用于迭代对象中的元素,该方法返回两个变量,value 表示返回的值,done 表示是否还存在没有遍历的元素。
function create(items) {
var i = 0;
return {
next: function() {
var done = i >= items.length;
var value = !done ? items[i++] : undefined;
return {
value: value,
done: done
};
}
};
}
var ite = create([1,3,2]);
ite.next(); // {value: 1, done : false}
ite.next(); // {value: 3, done : false}
ite.next(); // {value: 2, done : false}
ite.next(); // {value: undefined, done : true}
生成器
生成器是能够返回迭代器的函数,function 关键字和函数标识符之间插入一个 '*' 表示该函数是生成器。在生成器函数内部,可以使用 yield 关键字,当生成器返回的迭代器使用 next()
方法时,生成器会执行到 yield 语句处,然后停止执行。再次执行 next()
方法时,从上一次停止的位置开始执行,依然是执行到 yield 语句处停止。
yield 语句只能在生成器内部作用域使用,不能在生成器内部函数中使用。
function *generator() {
yield 1;
yield 2;
}
可以使用函数表达式创建生成器,但是不能使用箭头函数创建。
let gene = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
生成器可以作为对象的属性
var obj = {
*create() {
yield 1;
yield 2;
}
};
let iterator = obj.create();
可迭代对象和 for-of 循环
具有 Symbol.iterator 属性的对象是可迭代对象,这个属性定义了一个函数,用于返回与对象关联的迭代器。集合对象(数组、Set、Map)和字符串都是可迭代对象,可迭代对象和 for-of 配合使用。生成器创建的迭代器是可迭代对象。
使用 for-of 循环时,被迭代对象的 Symbol.iterator 属性定义的方法返回迭代器。在每次迭代时,调用迭代器的 next() 方法,将该方法返回的 value 值赋给变量,当返回的 done 值为 true 时停止迭代。
不可迭代对象、null、undefined 使用 for-of 循环会抛出错误。
可以直接访问 Symbol.iterator 属性得到迭代器或者判断对象是否为可迭代对象。
let arr = [1,2,3];
let iterator = arr[Symbol.iterator](); // 返回迭代器
typeof arr[Symbol.iterator] === "function" // 判断该属性是否存储了函数
自定义对象默认是不可迭代的,但可以为 Symbol.iterator 属性定义一个生成器使得对象可迭代。
let collect = {
items: [],
*[Symbol.iterator]() {
for (e of items) {
yield e;
}
}
}
collect.items.push(1);
collect.items.push(2);
collect.items.push(3);
for (let e of collect)
内置迭代器
集合对象类型有三种:数组、Set 和 Map。每一种都有 3 个方法返回使用目的不同的迭代器:entries()
、values()
和 keys()
方法。
entries()
方法返回的迭代器迭代的元素是键值对,使用大小为 2 的数组表示,第一个元素为键,第二个元素为值。对于数组对象,键值对是索引和对应的值;对于 Set,键和值相等;对于 Map,与存储时的键值对元素一致。values()
方法返回的迭代器迭代的元素是值。对于 Map,只包含值,没有对应的键。keys()
方法返回的迭代器迭代的元素是键。对于数组,是索引;对于 Set,是值,因为 Set 中键值是一样的。
数组和 Set 的默认迭代器是 values()
,Map 的默认迭代器是 entries()
。
Map 迭代时可以使用数组解构
let m = new Map();
m.set("1", "v");
for (let [key, value] of m)
字符串使用下标访问字符时,使用的是码元,如果某个字符使用两个码元表示,使用下标访问会得到意料之外的结果。字符串的默认迭代器迭代的是单个字符而不是码元,使用 for-in 循环会使用默认迭代器遍历字符串数组。
DOM 中的 NodeList 类型表示页面文档中元素的集合。该类型的默认迭代器与数组类型的一致。
扩展运算符和非数组可迭代对象
扩展运算符作用于可迭代对象时,调用该对象的默认迭代器返回迭代的值。不同的可迭代对象,其默认迭代器不同,返回的值不同。在数组中可以使用任意多次扩展运算符。
let a = [1,2,3],
b = [9,8,7];
let c = [...a, ...b]; // [1,2,3,9,8,7]
迭代器高级
生成器返回的迭代器调用 next()
方法时,如果向这个方法传入一个参数,则这个参数会取代上一次调用 next()
方法时的 yield 语句。
function *af() {
let a = yield 1;
let b = yield a + 2;
yield b + 3;
}
let i = af();
i.next(); // value: 1, done: false
i.next(3); // value: 5, done: false 相当于使用 3 取代了 yield 1 变成了 let a = 3;
i.next(7); // value: 10, done: false 7 取代了 yield a + 2 变成了 let b = 7
i.next(); // value: undefined, done: true
可以向迭代器可选的 throw()
方法中传入一个错误,这个方法执行时,类似于 next()
,会从上一次 yield 语句处恢复执行,然后执行到下一个 yield 语句为止。区别是在恢复执行时,会先抛出传入的错误对象,再执行。
function *ci() {
let a = yield 1;
let b;
try {
b = yield a + 2;
} catch(ex) {
b = 6;
}
yield b + 3;
}
let i = ci();
// 执行 yield 1, 得到 value: 1 done: false
i.next();
// 从 yield 1 处恢复,4 赋值给 a,最终执行 yield a + 2,得到 value: 6 done: false
i.next(4);
// 从 yield a + 2 处恢复,抛出错误,被捕获, 继续执行得到 value: 9 done: false
i.throw(new Error("error"));
i.next(); // value: undefined done: true
生成器中使用 return 语句时,表示生成器已经执行结束,即使后面仍然存在 yield 语句。生成器执行到 return 语句时,会将 done 设为 true,如果 return 语句有返回值,则该值设为 value 的值。
扩展运算符和 for-of 循环使用生成器时,如果发现 done 为 true 时,即使此时 value 中有值也不会使用该值,忽略该值。
可以在一个生成器中以 yield * generator()
的形式使用另一个生成器,这种方式可以将多个不同的生成器组合在一起使用。
function *a() {
yield 1;
yield 2;
return 3;
}
function *b(count) {
for (let i = 0; i < count; i++) {
yield "b";
}
}
function *c() {
let r = yield *a();
yield *b(r);
}
let i = c();
/*
先执行 a(),最终返回 3 赋值给 r,然后传递给 b 并执行。
value: 1 2 "b" "b" "b" undefined
done: false false false false false true
*/
i.next();
像 yield * 'string'
这样直接在字符串上使用 yield 会调用字符串的默认迭代器。
异步任务
使用生成器和迭代器可以执行异步操作。如果有几个任务是连续执行的,并且这些任务执行不需要上一个任务的输出数据,此时可以在一个生成器中定义这些任务,并使用 'yield' 分隔这些任务。然后使用生成器返回的迭代器调用 next()
方法执行。此时每次调用 next()
方法后就执行完了一个任务。
当一个任务的执行需要上一个任务的输出作为输入的时候,此时需要将每个任务执行之后生成的数据保存在 value 中返回,迭代器调用 next()
方法时将其作为参数传入,让下一个任务可以使用该数据。
如果一个任务执行完毕后,返回的是一个参数为回调函数的函数时,这个回调函数的正常执行结果可以作为下一个任务执行时的输入数据;若返回的不是函数,则直接作为下一个任务执行的输入。
function run(generator) {
let iterator = generator();
// 执行第一个任务
let result = iterator.next();
function nextStep() {
if (!result.done) { // 还有任务未执行
if (typeof result.value === "function") {
result.value(function (err, data) {
if (err) {
result = iterator.throw(err);
return ;
}
result = iterator.next(data);
nextStep();
});
} else {
result = iterator.next(result.value);
nextStep();
}
}
}
nextStep();
}
参考
[1] Zakas, Understanding ECMAScript 6, 2017.
标签:迭代,08,生成器,yield,next,let,value,UES From: https://www.cnblogs.com/xdreamc/p/16548989.html