Iterator
迭代器
迭代器:迭代器是一种接口;其本质就是一个指针对象
Iterator 的作用:
- 为各种数据结构,提供一个简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- 主要供
for … of
使用
逻辑过程:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第 1 次调用指针对象的
next()
,指针就指向数据结构的第 1 个元素 - 第 2 次调用指针对象的
next()
,指针就指向数据结构的第 2 个元素 - 不断调用指针对象的
next()
,直到它指向数据结构的结束位置
具有 Iterator 接口的数据结构:
① Array、② Map、③ Set、④ String、⑤ 函数的 arguments 类数组对象、⑥ DOM 中的 NodeList 类数组对象
数组的
Symbol.iterator
属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 返回一个迭代器对象
console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: 'c', done: false }
console.log(iter.next()); // { value: undefined, done: true }
类数组的对象调用数组的迭代器
Symbol.iterator
类数组:下标作为属性名,有 length
属性的对象
let iterable = {
0: 'a', // 属性名与下标一样
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // a b c
}
注意,普通对象部署数组的迭代器 Symbol.iterator
,无效果
let iterable = {
a: 'a', // 属性名与下标是不一样的
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined undefined undefined
}
字符串的包装类是一个类数组的对象,也具有 Iterator 接口
let str = "hi";
console.log(typeof str[Symbol.iterator]); // function
let iterator = str[Symbol.iterator](); // 返回一个迭代器对象
console.log(iterator.next()); // {value: "h", done: false}
console.log(iterator.next()); // {value: "i", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
封装迭代器
let arr = [1, 2];
arr.myIterator = function () {
let i = 0;
return {
next: () => { // 这里使用箭头函数,才能使 this 指向 arr
let done = i >= this.length;
let value = done ? undefined : this[i++];
return {
value,
done
};
};
};
};
let obj = arr.myIterator();
console.log(obj.next()); // {value: 1, done: false}
console.log(obj.next()); // {value: 2, done: false}
console.log(obj.next()); // {value: undefined, done: true}
Generator
生成器
定义生成器
- 关键字
function
与函数名之间有一个星号*
- 可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决⽅案
Generator
函数不能和new
一起使用,会报错- 不用写
return
语句
function* test() {
console.log("first");
let a = yield 1;
console.log("second");
console.log("a", a);
let b = yield 2;
console.log("third");
console.log("b", b);
}
- 也可以使用函数表达式的方式定义生成器:
此时是匿名函数表达式,所以*
在function
关键字和小括号之间
let test = function* () {}
- 注意:不能通过箭头函数创建生成器,毕竟
*
都不知该写哪里了是吧
- 作为对象的方法:
let obj = {
createIterator: function* (arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i];
}
}
};
也可以用 ES6 对象方法的简写方式来创建生成器。此时需要在函数名前添加一个星号 *
:
let obj = {
* createIterator(arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i];
}
}
};
调用生成器
- 该函数和普通函数不同,在执行的时候函数并不会运行,会返回⼀个分步执行对象
该对象存在next
方法,⽤来让程序继续执行,当程序遇到yield
关键字时会停顿 next
返回的对象中包含value
和done
两个属性:value
代表上⼀个yield
返回的结果,done
代表程序是否执行完
// 获取分步执⾏对象
let generator = test();
console.log("generator", generator); // generator test {<suspended>}
// 步骤1 该程序从起点执⾏到第⼀个 yield 关键字后,step1 的 value 是 yield 右侧的结果 1
let step1 = generator.next(); // first
console.log(step1); // {value: 1, done: false}
//步骤2 该程序从 var a 开始执⾏到第 2 个 yield 后,step2 的 value 是 yield 右侧的结 2
let step2 = generator.next(); // second
console.log(step2); // a undefined
// 由于没有 yield 该程序从 var b 开始执⾏到结束
let step3 = generator.next(); // third b undefined
console.log(step3); // {value: undefined, done: true}
上例中的 next
函数没有接收参数,所以 a b 的值为 undefined
这是因为在分步执行过程中,我们是可以在程序中对运行的结果进行人为干预的
也就是说 yield
返回的结果和他左侧变量的值都是可以干预的
// 获取分步执⾏对象
let generator = test();
console.log("generator", generator); // generator test {<suspended>}
// 步骤1 该程序从起点执⾏到第⼀个 yield 关键字后,step1 的 value 是 yield 右侧的结果 1
let step1 = generator.next(); // first
console.log(step1); // {value: 1, done: false}
//步骤2 该程序从 var a 开始执⾏到第 2 个 yield 后,step2 的 value 是 yield 右侧的结 2
let step2 = generator.next(step1.value); // second
console.log(step2); // a 1
// 由于没有 yield 该程序从 var b 开始执⾏到结束
let step3 = generator.next(step2.value); // third b 2
console.log(step3); // {value: undefined, done: true}
给 next
方法传入参数 stepX.value
之后,a b 就能获取到正确的值了
return
语句
- 可以写上
return
,但return
后面的语句也可以通过next()
获取
function* show() {
yield "a";
yield "b";
return 'c';
}
let g1 = show(); // 获取获取分步执⾏对象
let step1 = g1.next();
console.log("step1", step1); // step1 {value: 'a', done: false}
let step2 = g1.next();
console.log("step2", step2); // step2 {value: 'b', done: false}
let step3 = g1.next();
console.log("step3", step3); // step3 {value: 'c', done: true}
let step4 = g1.next();
console.log("step4", step4); // step4 {value: undefined, done: true}
批量处理
yield
语句
- 如果传入数组参数,可以通过
for
循环,批量处理yield
语句
function* show(arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i];
}
}
let g = show([1, 2, 3]);
console.log(g); // show {<suspended>}
console.log(g.next()); // {value: 1, done: false}
console.log(g.next()); // {value: 2, done: false}
console.log(g.next()); // {value: 3, done: false}
console.log(g.next()); // {value: undefined, done: true}
console.log(g); // show {<closed>}
- 注意:
yield
关键字只能在生成器generator
函数的子作用域中使用,否则会报错
function* show(arr) {
arr.forEach(function (item, index) {
yield item; // SyntaxError: Unexpected identifier
});
}
generator 的各种搭配
for … of
循环
function* show() {
yield "a";
yield "b";
}
let g1 = show();
for (let val of g1) {
console.log(val); // a b
}
- 解构赋值:
function* show() {
yield "a";
yield "b";
};
let [g1, g2, g3] = show();
console.log(g1, g2, g3); // a b undefined
- 扩展运算符:
function* show() {
yield "a";
yield "b";
};
let [...g1] = show();
console.log(g1); // ["a", "b"]
Array.from()
:(Array.from
用于把类数组对象转成数组 )
function* show() {
yield "a";
yield "b";
};
let g1 = Array.from(show());
console.log(g1); // ["a", "b"]
可迭代对象 & for … of
- 具有
Symbol.iterator
方法的对象,如字符串、数组、Set、Map,都默认具有迭代器 - 通过生成器创建的,都是迭代对象
- 而
for … of
循环的本质就是通过每次调用迭代对象的next()
,将迭代器返回的结果对象中的value
属性值,存到一个变量中,循环执行直到结果对象中done
属性值为true
为止
let arr = [10, 20, 30];
for (let num of arr) {
console.log(num); // 10 20 30
}
异步处理
function* test() {
let a = yield 1; // 普通数据
console.log("a", a);
let res = yield setTimeout(function () { // 定时器
return 123;
}, 1000);
console.log("res", res);
let res1 = yield new Promise(function (resolve) { // Promise 实例
setTimeout(function () {
resolve(456)
}, 1000);
});
console.log("res1", res1);
}
let generator = test()
console.log("generator", generator); // generator test {<suspended>}
let step1 = generator.next();
console.log("step1", step1); // step1 {value: 1, done: false}
let step2 = generator.next(step1.value); // a 1
console.log("step2", step2); // step2 {value: 3, done: false}
let step3 = generator.next(step2.value); // res 3
console.log("step3", step3); // step3 {value: Promise, done: false} (这里得到的 value 值是 Promise 实例)
let step4 = generator.next(step3.value); // res1 Promise {<pending>}
console.log("step4", step4); // step4 {value: undefined, done: true}
我们展开查看 Promise 对象:
value: Promise
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 456
可以发现:生成器 Generator 可以获取到普通数据、Promise 实例;但获取不了定时器内的数据
实现用 Generator 将 Promise 的异步流程同步化
我们可以通过递归调⽤的⽅式,来动态的去执⾏⼀个Generator函数,以done属性作为是否结束 的依据,通过next来推动函数执⾏,如果过程中遇到了Promise对象我们就等待Promise对象执⾏完毕再进⼊下⼀ 步,我们这⾥排除异常和对象reject的情况,封装⼀个动态执⾏的函数如下
function generatorFunctionRunner(fn) {
// 定义分步对象
let generator = fn();
// 执⾏到第⼀个 yield
let step = generator.next();
// 定义递归函数
function loop(stepArg, generator) {
// 获取本次的 yield 右侧的结果
let value = stepArg.value;
// 判断结果是不是 Promise 对象
if (value instanceof Promise) {
// 如果是 Promise 对象就在 then 函数的回调中获取本次程序结果
// 并且等待回调执⾏的时候进⼊下⼀次递归
value.then(promiseValue => {
if (stepArg.done == false) {
loop(generator.next(promiseValue), generator);
}
});
} else {
// 判断程序没有执⾏完就将本次的结果传⼊下⼀步进⼊下⼀次递归
if (stepArg.done == false) {
loop(generator.next(stepArg.value), generator);
}
}
}
// 执⾏动态调⽤
loop(step, generator);
}
有了这个函数之后我们就可以调用这个函数来执行 Generator 方法了
function* test() {
let res1 = yield new Promise(function (resolve) {
setTimeout(function () {
resolve('第⼀秒运⾏');
}, 1000);
});
console.log(res1);
let res2 = yield new Promise(function (resolve) {
setTimeout(function () {
resolve('第⼆秒运⾏');
}, 1000);
});
console.log(res2);
let res3 = yield new Promise(function (resolve) {
setTimeout(function () {
resolve('第三秒运⾏');
}, 1000);
});
console.log(res3);
}
generatorFunctionRunner(test);