Iterator
遍历器(Iterator)是一种机制(接口):为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署Iterator接口,就可以完成遍历操作【for of 循环本身就是基于这种机制进行迭代的】,依次处理该数据结构的所有成员
- 拥有next方法用于依次遍历数据结构的成员
- 每一次遍历返回的结果是 一个对象{done:false,value:xxx} ;done:记录是否遍历完成,value:当前遍历的结果
根据这两个特点实现一个Iterator类
// 简单实现一个Iterator
class Iterator{
constructor(assemble){
let self = this;
self.assemble=assemble;
self.index=0;
};
next() {
let self = this,
assemble=self.assemble;
if(self.index>assemble.length-1){
return{
value:undefined,
done:true,
}
}
return {
value:assemble[self.index++],
done:false,
};
};
}
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}
拥有Symbol.iterator属性的数据结构(值),被称为可被遍历的,可以基于for of 循环处理
虽然不具备Iterator内置类,但是一些内置类型拥有默认的迭代器行为它提供了了Symbol.iterator属性方法,这个方法具备迭代器规范,基于这个方法可以依次迭代数据中的每一项
mdn搜索如下:
\
- 数组 Array.prototype[Symbol.iterator]
let arr = [10,20,30];
let itor = arr[Symbol.iterator]();
// 1.先去找arr[Symbol.iterator]执行(如果数据集合不具备这个属性,就会报错【Uncaught TypeError: {} is not iterable】),否则返回一个迭代器对象itor
// 2.每一轮循环都会执行一次itor.next()方法,把结果中的value赋值给循环中的value;当done 为true时,则结束整个循环
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: undefined, done: true}
// 数组是一个可以被迭代的数据,但并不是一个迭代器,它的原型上有Symbol.iteratorlet arr=[10,20,30];
arr.next();// arr.next is not a function
// 所以先变成迭代器
let arr = [10,20,30];
let itor = arr[Symbol.iterator]();
console.log(itor)
\
\
- 部分类数组:arguments/NodeList/HTMLCollection ...
- String
var string = 'A\uD835\uDC68';
var strIter = string[Symbol.iterator]();//返回一个新的 Iterator 对象。
console.log(strIter.next().value); // "A"
console.log(strIter.next().value); // "\uD835\uDC68"
- Set
- Map
- generator object
普通对象默认不具备Symbol.iterator ,属于不可被遍历的数据结构\
如果每次迭代,都需要一直next,所以for of 就是基于这个机制去迭代的(这也就是for of 的原理)
// 普通对象是不具备这个属性的(Object.prototype上不具备Symbol.iterator),所以用for of 循环会直接报错 【Uncaught TypeError: obj is not iterable】
let obj={name:'jiang',age:14}
for(value of obj){
console.log(value)
}
如果想让对象也可迭代,那就给对象也加一个Symbol.iterator属性
let obj={name:'jiang',age:14};
obj[Symbol.iterator]=function(){
let self = this,
keys=Reflect.ownKeys(self),// ["name", "age"]由目标对象的自身属性键组成的 Array
index=0;
return{
next(){
if(index>keys.length-1){
return{
value:undefined,
done:true,
}
}
return{
value:self[keys[index++]],
done:false,
}
}
}
}
console.log(obj)for(value of obj){
console.log(value)
}
\
\
如果想让所有对象都能用,加在原型上,那所有的对象都可以用for of 来循环了 \
Object.prototype[Symbol.iterator]=function(){
let self = this,
keys=Reflect.ownKeys(self),
index=0;
return{
next(){
if(index>keys.length-1){
return{
value:undefined,
done:true,
}
}
return{
value:self[keys[index++]],
done:false,
}
}
}
}
let obj={name:'jiang',age:14};
for(value of obj){
console.log(value)
}
类数组和数组非常相似,可以把数组上的属性借用给类对象,也能用for of\
let obj={
0:10,
1:20,
2:30,
length:3,
}
obj[Symbol.iterator]= Array.prototype[Symbol.iterator];
for(value of obj){
console.log(value)
}
扩展:js中的常用的循环,以及它们的性能对比
for循环、while循环、Array.prototype.forEach可以迭代数组、for of循环、for in 循环
let arr=new Array(9999999).fill(null);
console.time('FOR')
for(var i=0;i<arr.length;i++){
}
console.timeEnd('FOR');
console.time('while')
let k=0;
while(k<arr.length){
k++;
}
console.timeEnd('while');
console.time('FOREACH')
arr.forEach(()=>{})
console.timeEnd('FOREACH');
console.time('FOROF')
for(let item of arr){}
console.timeEnd('FOROF');
console.time('FORIN')
for(let item in arr){}
console.timeEnd('FORIN');
\
性能从高到低:
for 循环&&while循环->forEach循环(内部封装的方法,肯定做了一些逻辑判断,稍微慢了点)->for/of循环->for/in 循环(会依次查找私有和原型链上的所有可枚举属性,所以比其他循环要慢)
Generator
\
Generator 函数是 ES6 提供的一种异步编程解决方案,它的特点\
-
一是,
function关键字与函数名之间有一个星号\ -
二是,函数体内部使用
yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw)
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。
不同的是,调用 Generator 函数后,该函数并不执行,如果传有实参,实参值会预先传进去,返回的也不是函数运行结果,返回一个具备迭代器规范的对象\
\
通过原型链指向一层一层往上找,找到GeneratorFunction,里面有next/return/throw方法
(1)Generator.prototype.next()
next() 方法返回一个包含属性 done 和 value 的对象。该方法也可以通过接受一个参数用以向生成器传值。
-
value:可选,向生成器传递的值.(如果传递了该参数,那么这个参数会传给上一条执行的 yield语句左边的变量) -
返回值:返回的
对象包含两个属性:-
done(布尔类型)- 如果迭代器超过迭代序列的末尾,则值为
true。 在这种情况下,value可选地指定迭代器的返回值。 - 如果迭代器能够生成序列中的下一个值,则值为
false。 这相当于没有完全指定done属性。
- 如果迭代器超过迭代序列的末尾,则值为
-
value迭代器返回的任意的Javascript值。当done的值为true时可以忽略该值。
-
function* generator() {
let a = yield 1;
let b = yield 2;
let c = yield 3;
console.log(`a = ${a}, b = ${b}, c = ${c}`)
}
let gen = generator(); // "Generator { }"
console.log(gen.next(10)); // {value: 1, done: false}
console.log(gen.next(9)); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next(8));
// a = 9, b = undefined, c = 8
// {value: undefined, done: true}
(2)Generator.prototype.return()
return() 方法返回给定的值并结束生成器。
gen.return(value)
value: 需要返回的值
- 返回值:返回该函数参数中给定的值.
(3)Generator.prototype.throw()
throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。\
gen.throw(exception)
-
exception:用于抛出的异常。 -
返回值:带有两个属性的`对象
-
done(boolean)- 如果迭代器已经返回了迭代序列的末尾,则值为
true。在这种情况下,可以指定迭代器value的返回值。 - 如果迭代能够继续生产在序列中的下一个值,则值为
false。 这相当与不指定 done 属性的值。
- 如果迭代器已经返回了迭代序列的末尾,则值为
-
value- 迭代器返回的任何 JavaScript 值。当 done 是 true 的时候可以省略。
-
function* gen() {
while(true) {
try {
yield 42;
} catch(e) {
console.log("Error caught!");
}
}
}
var g = gen();
console.log(g.next()); // { value: 42, done: false }
console.log(g.throw(new Error("Something went wrong"))); // "Error caught!" { value: 42, done: false }
console.log(g.next()); // { value: 42, done: false }
从上述代码中可以看出,关于throw()方法需要注意的是,throw()方法并\color{red}{不会结束生成器}不会结束生成器。(如果不通过try catch 捕获,直接就会结束生成器)
具体执行方式:\
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw.next());//{value: 'hello', done: false}
console.log(hw.next());//{value: 'world', done: false}
console.log(hw.next());//{value: 'ending', done: true}
console.log(hw.next());//{value: undefined, done: true}
第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。\
第二次调用如上,
第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)\
第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。\
注意:
// ES6 没有规定,function关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
function * foo(x, y) { }
function *foo(x, y) { }
function* foo(x, y) { }
function*foo(x, y) { }
Iterator与Generator关系:
iterator迭代对象和generator实例一样都使用next方法迭代 ,并且这个 yield 语句就是用来中断代码的执行的,也就是说,配合 next() 方法,每次只会执行一个 yield 语句
基于生成器函数执行的返回结果就是一个迭代器\
function * gen(){
}
const f=gen();
console.log(f)
f.next();
之前给Object原型上加的Symbol.iterator的方法,可以用生成器来实现(生成器函数执行就是迭代器对象)
Object.prototype[Symbol.iterator] = function*() {
for (const [key, value] of Object.entries(this)) {
yield { key, value };
}
};
// 具备这个属性,就可以用for of 来循环对象
for (const { key, value } of { a: 1, b: 2, c: 3 }) {
console.log(key, value);
}
generator的应用
因为有这个暂停特点(把函数执行打断,看上去是同步的,实际是异步的),在真实项目中就可以来管理异步编程:
使用generator,让异步代码像同步代码一样执行
// 串行发送三个请求,请求时间分别是1000、2000、3000
function query(interval){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(interval);
},1000)
})
}
// Promise的出现,让程序员告别了痛苦的回调地狱,书写异步代码像这样:
query(1000).then(value=>{
console.log('第1个成功,',value);
return query(2000);
}).then(value=>{
console.log('第2个成功,',value);
return query(3000);
}).then(value=>{
console.log('第3个成功,',value);
})
// 用Generator实现,手动执行next
function * generator(){
let value=yield query(1000);
console.log('第1个成功,',value);
value=yield query(2000);
console.log('第2个成功,',value);
value=yield query(3000);
console.log('第3个成功,',value);
}
let itor=generator();
// itor.next(); // value:第一次请求的promise实例 {value: Promise, done: false},接下来需要等待本次请求成功,获取到结果
itor.next().value.then(val=>{
// val:第一次请求成功的结果
itor.next(val).value.then(val=>{
//val:第二次请求成功的结果
itor.next(val).value.then(val=>{
//val:第三次请求成功的结果
itor.next(val);
})
})
})
// 用Generator实现,自动执行next function query(interval){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(interval);
},1000)
})
}
function AsyncFunction(generator,...params){
let itor=generator(...params);
const next=(val)=>{
let {value,done}=itor.next(val);
if(done) return; // done为true结束
value.then(val=>next(val))
.catch(reason=>itor.throw(reason));
}
next();
}
AsyncFunction(function * (){
let value=yield query(1000);
console.log('第1个成功,',value);
value=yield query(2000);
console.log('第2个成功,',value);
value=yield query(3000);
console.log('第3个成功,',value);
})
// 有个co模块就是用类似这种方式
// async await是generator+promise的‘语法糖’,就是将generator函数的*换成async,用await替代yeild关键词。不用再考虑generator实例如何调用next方法了
function query(interval){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(interval);
},1000)
})
}
(async()=>{
let result=await query(1000);
console.log('第1个成功,',result);
result=await query(2000);
console.log('第2个成功,',result);
result=await query(3000);
console.log('第3个成功,',result);
})();
参考:
[迭代协议]developer.mozilla.org/zh-CN/docs/…
[Generator]developer.mozilla.org/zh-CN/docs/…
es6.ruanyifeng.com/#docs/gener…
\