Iterator迭代器原理
ECMAScript 6 入门-Iterator 和 for...of 循环
JavaScript 原有的表示“集合”的数据结构,主要是数组(
Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:
- 一是为各种数据结构,提供一个统一的、简便的访问接口;
- 二是使得数据结构的成员能够按某种次序排列;
- 三是 ES6 创造了一种新的遍历命令
for...of循环,Iterator 接口主要供for...of消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的
next方法,可以将指针指向数据结构的第一个成员。(3)第二次调用指针对象的
next方法,指针就指向数据结构的第二个成员。(4)不断调用指针对象的
next方法,直到它指向数据结构的结束位置。
每一次调用
next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
Iterator仅仅是一种机制,这种机制有如下规定:
- 拥有
next方法用于依次遍历数据结构的成员 - 每一次遍历返回的结果是一个对象
{done:false,value:xxx}done:记录是否遍历完成value:当前遍历的结果
例如我们想通过这套机制遍历数组,我们可以自己实现这套机制:
- 传入集合
- 执行一次
next,就能拿到集合中的每一项
例如以下效果:
let itor = new Iterator([10, 20, 30, 40]);
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.next()); //->{value:20,done:false}
console.log(itor.next()); //->{value:30,done:false}
console.log(itor.next()); //->{value:40,done:false}
console.log(itor.next()); //->{value:undefined,done:true}
//后面再执行,就是{value:undefined,done:true}
class Iterator {
constructor(assemble) {//传入集合
let self = this;
self.assemble = assemble;
self.index = 0;
}
next() {
//执行一次next就能拿到集合中的每一项
let self = this,
assemble = self.assemble;
if (self.index > assemble.length - 1) {
return {
done: true,
value: undefined
};
}
return {
done: false,
value: assemble[self.index++]
};
}
}
以上都是通过js自己创造了一套数组的迭代器规范。通过 new Iterator 创造出来的实例对象就是遍历器,因为它实现了遍历器所规定的规范。
以上是我们自己实现的遍历器机制,ES6当中有很多默认的数据结构就具备这套机制。所有拥有 Symbol.iterator 属性的数据结构(值),被称为可被遍历的,可以基于 for of 循环处理。 for of 是按照iterator规范进行迭代的
- 数组
- 部分类数组:arguments/NodeList/HTMLCollection...
- String
- Set
- Map
- generator object
- ...
对象默认不具备 Symbol.iterator ,属于不可被遍历的数据结构
数组的 for of 运行原理的举例:
let arr = [10, 20, 30, 40];
//arr[Symbol.iterator]=function...
//arr[Symbol.iterator]() -> 返回一个具备iterator规范的对象「拥有next方法的对象」 itor
for (let item of arr) {
// 每一轮循环都执行一次next
// itor.next() item->value done如果是false则进行下一轮循环
// ...
// done成为true,则整个循环结束
console.log(item);
}
- 数组有一个内置对象
arr[Symbol.iterator]=function... arr[Symbol.iterator]()-> 返回一个具备iterator规范的对象,即拥有next方法的遍历器对象- 每一轮循环都执行一次
next,循环的值item就是返回值的value,done如果是false则进行下一轮循环 done成为true,则整个循环结束
手写一下 arr[Symbol.iterator] 实现:
let arr = [10, 20, 30, 40];
arr[Symbol.iterator] = function () {
let assemble = this,
index = 0;
return {
next() {
if (index > assemble.length - 1) {
return {
done: true,
value: undefined
};
}
return {
done: false,
value: assemble[index++]
};
}
};
};
for (let item of arr) {
console.log(item);
}
或者我们刚实现了一个Iterator类
arr[Symbol.iterator] = function () {
return new Iterator(this);
}
我们可以重构 Symbol.iterator 来实现自己想实现的遍历,例如在返回值里面乘以 10
实现对象遍历器
实现对象遍历器,使其可以用for of 迭代
let obj = {
name: 'xxx',
age: 12
};
for (let value of obj) {
console.log(value);
}
for of 无法迭代对象
let obj = {
name: 'xxx',
age: 12
};
Object.prototype[Symbol.iterator] = function () {
let assemble = this,
keys = Object.keys(assemble).concat(Object.getOwnPropertySymbols(assemble)),
index = 0;
return {
next() {
if (index > keys.length - 1) {
return {
done: true,
value: undefined
};
}
return {
done: false,
value: assemble[keys[index++]]
};
}
};
};
for (let value of obj) {
console.log(value);
}
自己构建的类数组对象,也可以利用数组的迭代器迭代类数组对象。
let obj = {
0: 10,
1: 20,
2: 30,
length: 3
};
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
for (let value of obj) {
console.log(value);
}
Generator函数
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,
function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
Generator函数与普通函数的区别
普通函数
function fn() {
console.log(this);//window
}
let gen = fn();
console.log(gen);//undefined
console.log(typeof fn); //->"function"
console.log(fn instanceof Function); //->true
Generator 函数:
function* fn() {
console.log(this);
}
let gen = fn(); //->虽然看上去fn后面加小括号了,但是和我们理解的fn执行是不一样的「函数体中代码并没有执行」
console.log(gen); //->返回的结果不是undefined,而是具备迭代器规范的一个对象「生成器函数执行返回一个迭代器」Generator类的一个实例
console.log(typeof fn); //->"function"
console.log(fn instanceof Function); //->true
Object.prototype.toString.call(gen)//"[object Generator]"
Generator函数和普通函数区别:
-
function关键字后面加一个* -
如上例,
console.log(gen),虽然看上去fn后面加小括号执行了,但是和普通函数的fn执行是不一样。函数执行了,但是函数体中代码并没有执行,没有打印出this。函数执行返回的结果不是undefined,而是是具备迭代器规范的对象,即生成器函数执行返回一个迭代器。(Generator类在浏览器不可主动访问)。函数执行返回的实例,原型链如下:gen.__proto__->fn.prototype->GeneratorFunction.prototype(其有next/return/throw/Symbol.toStringTag这些属性)->xxx.prototype(其有Symbol.iterator属性,说明有迭代器相关的特点) ->Object.prototype -
Generator函数的返回值是当前类(函数)的一个实例(并没有
new)。看上去当做普通函数执行,其实返回的是当前类的实例。测试:
function* fn() { } fn.prototype.query = function () {}; let gen = fn(); console.log(gen)给原型上添加一个属性,结果返回的实例也有这个属性。
-
不可以
new执行
Generator函数的使用
如何让生成器函数里的代码进行执行呢?
执行方式:
- Generator函数会返回一个迭代器对象,每一次执行
next方法都会去函数体中执行代码 - 从开始或者上一次
yeild结束的位置继续向下执行,直到遇到新的yeild结束 - 每次返回的对象
value的值是yeild后面的值或return后面的值(最后一次)done的值是false/true(只有遇到return是true,因为是最后一次)
举例:
如果 return undefined
function* generator(x) {
console.log('x',x)
}
let itor = generator(100);
console.log('next',itor.next());
//-> 输出x:100 ,next:{value:undefined,done:true}
如果有 return 其他
function* generator(x) {
console.log(x);
return x * 10;
}
let itor = generator(100);
console.log('next',itor.next());
//next:{value:1000,done:true}
执行一次 next ,函数体中的代码就执行了
function* generator(x) {
return x * 10;
}
let itor = generator(100);
console.log('next',itor.next());
console.log('next',itor.next());
function* generator() {
console.log('A');
yield 10;
console.log('B');
yield 20;
console.log('C');
yield 30;
console.log('D');
return 100;
}
let itor = generator();
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.next()); //->{value:20,done:false}
console.log(itor.next()); //->{value:30,done:false}
console.log(itor.next()); //->{value:100,done:true}
console.log(itor.next()); //->{value:undefined,done:true}
所以生成器函数返回的对象,通过迭代器规范,可以控制函数体当中代码执行的步骤
itor.return()
itor.return() 的作用:直接使遍历结束
function* generator() {
console.log('A');
yield 10;
console.log('B');
yield 20;
console.log('C');
yield 30;
console.log('D');
return 100;
}
let itor = generator();
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.return('@return')); //->{value:'@return',done:true}
console.log(itor.next());
console.log(itor.next());
console.log(itor.next());
itor.throw()
itor.throw() 的作用:直接抛出异常信息,没有返回结果,下面代码也不会再执行了
function* generator() {
console.log('A');
yield 10;
console.log('B');
yield 20;
console.log('C');
return 30;
}
let itor = generator();
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.return('@return')); //->{value:"@return",done:true} 直接控制遍历结束
console.log(itor.throw('@throw')); //直接抛出异常信息,没有返回结果,下面代码也不会再执行了
console.log(itor.next()); //->{value:undefined,done:true}
itor.next()
如果 itor.next() 传值:
- 第一次传递的值没有用
- 之后,每一次执行
next的传递的值,作为上一次yeild的返回值处理
function* generator() {
let x1 = yield 10;
console.log(x1);
let x2 = yield 20;
console.log(x2);
return 30;
}
let itor = generator();
itor.next('@1'); //第一次传递的值没有用
itor.next('@2'); //每一次执行next的传递的值,是作为上一次yeild的返回值处理的
itor.next('@3');
Generator函数嵌套
yield 不加 * :
function* generator1() {
yield 10;
yield 20;
}
function* generator2() {
yield 10;
yield generator1();
yield 20;
}
let itor = generator2();
console.log(itor.next()); //value:10 done:false
console.log(itor.next()); //value:itor->generator1 done:false
console.log(itor.next()); //value:20 done:false
console.log(itor.next()); //value:undefined done:true
yeild 后面加 * :如果 后面跟着一个新的 itor ,后期执行到这的时候,会进入到新的generator中执行
function* generator1() {
yield 10;
yield 20;
}
function* generator2() {
yield 10;
yield* generator1(); //yeild* 后面跟着一个新的itor,后期执行到这的时候,会进入到新的generator中执行
yield 20;
}
let itor = generator2();
console.log(itor.next()); //value:10 done:false
console.log(itor.next()); //value:10 done:false
console.log(itor.next()); //value:20 done:false
console.log(itor.next()); //value:20 done:false
console.log(itor.next()); //value:undefined done:true
async / await 原理
需求:
const query = interval => {
return new Promise(resolve => {
setTimeout(() => {
resolve(interval);
}, interval);
});
};
需求:串行请求。有三个请求,所用时间分别是1000/2000/3000,而且实现的需要时“串行”(第一个请求成功,再发第二个请求,第二个请求成功,再发第三个请求 ->都成功需要的总时间是6000ms)
使用Promise的 then 链机制:
query(1000).then(result => {
console.log( ` 第一个请求成功,结果是:${result} ` );
return query(2000);
}).then(result => {
console.log( ` 第二个请求成功,结果是:${result} ` );
return query(3000);
}).then(result => {
console.log( ` 第三个请求成功,结果是:${result} ` );
});
另一个思路:使用Generator函数实现:
function* generator() {
let result;
result = yield query(1000);
console.log( ` 第一个请求成功,结果是:${result} ` );
result = yield query(2000);
console.log( ` 第二个请求成功,结果是:${result} ` );
result = yield query(3000);
console.log( ` 第三个请求成功,结果是:${result} ` );
}
let itor = generator();
// console.log(itor.next()); //value:promise done:false
itor.next().value.then(result => {
itor.next(result).value.then(result => {
itor.next(result).value.then(result => {
itor.next(result);
});
});
});
把上次一的Promise的实例的返回结果拿到,然后传入 next ,最后成为函数体中的上一个 yelid 的返回值
上面展现了一下Generator函数实现串行原理,这样的好处是Generator函数体中的串行代码比较清晰。但是我们现实写代码完成业务当中,不知道到底要执行多少次 next ,所以我们需要一个特殊的循环逻辑来进行处理。这样我们只需要只写函数体中的代码,即可实现串行。下面我们来实现一下
原理:创建一个asyncfunction,传入Generator函数,然后在asyncfunction中得到Generator函数返回的迭代器,然后利用迭代器机制,循环执行 next (或者递归执行),直到全部执行完 next 为止。
Generator函数+递归实现:
const query = interval => {
return new Promise(resolve => {
setTimeout(() => {
resolve(interval);
}, interval);
});
};
function isPromise(x) {
if (x == null) return false;
if (/^(object|function)$/i.test(typeof x)) {
if (typeof x.then === "function") {
return true;
}
}
return false;
}
// 依次去执行生成器函数中每一个操作:itor.next() co.js
// + generator:要处理的生成器函数
// + params:存储给生成器函数传递的实参信息
function AsyncFunction(generator, ...params) {
let itor = generator(...params);
const next = x => {
let {value,done} = itor.next(x);
if (done) {
return;
}
if (!isPromise(value)) value = Promise.resolve(value);//让其可以支持yield 1000这种类型的语句
value.then(result => next(result))//成功之后让他走下一个迭代器的next方法,并且把值传给上一个yield
.catch(reason => itor.throw(reason));//如果某个请求失败,不往下走,抛出异常即可
};
next();
}
AsyncFunction(function* generator(x, y) {
console.log(x, y);
let result = yield query(1000);
console.log( ` 第一个请求成功,结果是:${result} ` );
result = yield query(2000);
console.log( ` 第二个请求成功,结果是:${result} ` );
result = yield query(3000);
console.log( ` 第三个请求成功,结果是:${result} ` );
})
这样处理之后就不需要手动一个个去写 next 了,不管有多少个 yield ,都可以执行
如果我想当整个串行流程都结束,AsyncFunction也返回一个Promise实例,用来表示流程结束:Generator函数+promise实现
function isPromise(x) {
if (x == null) return false;
if (/^(object|function)$/i.test(typeof x)) {
if (typeof x.then === "function") {
return true;
}
}
return false;
}
function AsyncFunction(generator, ...params) {
return new Promise(resolve => {
let itor = generator(...params);
const next = x => {
let {value,done} = itor.next(x);
if (done) {
resolve(value);//把最后一次value值传给他
return;
}
if (!isPromise(value)) value = Promise.resolve(value);
value.then(result => next(result))
.catch(reason => itor.throw(reason));
};
next();
});
}
AsyncFunction(function* generator(x, y) {
console.log(x, y);
let result = yield query(1000);
console.log( ` 第一个请求成功,结果是:${result} ` );
result = yield query(2000);
console.log( ` 第二个请求成功,结果是:${result} ` );
result = yield query(3000);
console.log( ` 第三个请求成功,结果是:${result} ` );
}, 100, 200).then(() => {
// generator处理完成,执行这个操作
console.log('全部处理完成');
});
以上借鉴了co.js库的实现原理,co.js是基于es6的Generator函数实现的,相当于一Generator函数的自动执行器
上面就是 async / await 的实现原理, async / await 是 Generator函数+promise 的语法糖。
使用 async / await 书写如下:
(async () => {
let result = await query(1000);
console.log( ` 第一个请求成功,结果是:${result} ` );
result = await query(2000);
console.log( ` 第二个请求成功,结果是:${result} ` );
result = await query(3000);
console.log( ` 第三个请求成功,结果是:${result} ` );
})();