JS:yield、Iterator遍历器 与 Generator生成器函数
为了便于书写,下文Iterator遍历器(迭代器)简称“I接口/I对象”;Generator生成器函数简称“G函数”。
Iterator遍历器 & for…of
作用:为不同数据类型提供统一接口,以便执行遍历。
当使用for…of循环遍历某种数据结构时,会自动寻找I接口。
本质上是一个指针对象。
对象可遍历条件:包含Symbol.iterator属性,值为一个返回I对象的方法,该I对象具有next方法,返回当前成员信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//给Object变量部署I接口
var obj={
[Symbol.iterator]:function(){
var obj2={
next:function(){
...
return {value:...,done:...};//值 状态
}
}
return obj2;//I对象
}
};
//执行Array变量的I接口
var arr=['a','b'];
var iter=arr[Symbol.iterator];
iter.next();//{value:'a',done:false}
iter.next();//{value:'b',done:false}
iter.next();//{value:undefined,done:true}原生具备I接口的数据类型:Array,Map,Set,String,TypedArray,函数的arguments对象,DOM NodeList对象,G函数对象,其它类似Array的对象。其他数据类型只要按照上一行设置属性也可遍历,即可以使用for…of/while循环遍历。(Object没有I接口是因为对象属性顺序不确定)
1
2
3
4
5
6
7
8
9
10
11//类似Array的Object变量部署Array的I接口的例子:
let obj={
0:'a',
1:'b',
2:'c',
[Symbol.iterator]:Array.prototype[Symbol.iterator]
};
for(let item of obj){
console.log(item);//a b c
}
//普通Object变量部署Array的I接口没有用ES6与C++,Java,C#,Python同步,使for…of作为遍历所有数据结构的统一方法。
for…in获得键,for…of获得值。1
2
3
4
5
6
7
8
9
10
11
12
13
14for(let temp in ['a','b']){...}//temp:0 1
for(let temp of ['a','b']){...}//temp:'a' 'b'
for(let temp in ['a','b'].keys()){...}//temp:0 1
for(let temp in ['a','b'].values()){...}//temp:'a' 'b'
for(let temp in ['a','b'].entries()){...}//temp:[0,'a'] [1,'b']
let arr=['a','b'];
arr.name='c';
for(let temp in arr){...}//temp:0 1 'c'
for(let temp of arr){...}//temp:'a' 'b'默认调用I接口的场合:(1)Array和Set的解构赋值;(2)扩展运算符(…);(3)yield*后跟可遍历结构;(4)接收数组作为参数的其它场合
自己写I对象,next方法必写,return和throw可选。return用于提前结束,必须返回一个Object;throw主要配合G函数使用,一般不用。
Map和Set结构:(1)for…of循环Map内的临时变量为[key,value]得到键和值,而Set只能得到值,可用上述keys(),values(),entries()方法拓展;(2)遍历顺序为成员被添加进去的顺序。
for…of循环字符串可以自动识别32位UTF-16字符。
将类似数组的对象通过Array.from()转为数组可以方法遍历。
普通Object可用for…in遍历属性名,用for…of遍历会报错。
解决方法:(1)用Object.keys()生成键名数组,可以遍历这个数组;(2)结合Object.keys用G函数重新包装。1
2
3
4
5
6let obj={...};
function* genObj(obj){
for(let key of Object.keys(obj)){
yield [key, obj[key]];
}
}各种for循环的比较
(1)原始的for循环for(var i=0;i<10;i++)
:写法麻烦;
(2)forEach:无法中途跳出循环;
(3)for…in:会把数组的键名转为字符串,会包含手动添加的键,遍历顺序不定;
(4)for…of:没有for…in的缺点,是遍历所有数据结构的统一接口。
Generator生成器函数
- ES6引入的特殊函数。
- 是异步编程解决方法之一。
- G函数写法:
1
2
3
4function* myGen(){//function与函数名间有没有空格不限
yield 'hello';
yield 'world';
} - G函数有两个作用:
(1)一个封装了多个内部状态的状态机;
(2)调用G函数不会执行该函数,而是返回一个I对象,I对象可遍历G函数内部的每个状态(指向内部状态的指针对象)。G函数是I对象最简单的实现。每次调用next方法,指针指向下一条yield语句或return语句,直到没有下一条则永远返回1
2
3
4
5
6
7let obj={
* [Symbol.iterator](){
yield 'a';
yield 'b';
}
};//简洁写法,此时obj可遍历
obj.next();//{value:'a',done:false}{value:undefined,done:true}
。 - G函数不使用yield语句,则变为一次暂缓执行函数
G函数执行时只生成一个变量obj,obj执行next方法时执行G函数内语句。 - G函数执行后返回I对象,该对象执行
[Symbol.iterator]()
方法,返回I对象自身。 - next方法的参数由于i用var修饰,是一个全局变量;
1
2
3
4
5
6
7
8
9
10function* f(){
for(var i=0;i<5;i++){
var check=yield i;
if(check) i=-1;
}
}
let g=f();
g.next();//{value:0,done:false}
g.next();//{value:1,done:false}
g.next(true);//{value:0,done:false}
next内传入的参数会作为yield表达式的返回值;
每次yield暂停G函数时,G函数原本的上下文保持不变,
以上述函数为例,第一次跑next时,第一轮循环走到yield i暂停,
第二次跑next时,仍在第一轮循环,由于第一次跑next没有参数,check=undefined,往下跑到第二轮循环,此时i=1,第二个next依然没有给参数,还是跑到yield i暂停,
第三次跑next时,传入的参数值为true,此时仍在第二轮循环,赋给check,进入if判断,i赋值为-1,继续走第三轮循环,先走i++,使i=0,跑到yield i暂停,next方法返回一个对象{value:0,done:false}
。
而G函数也可接收参数,可作为影响整个G函数的外部传入值。
1 | function* m(n=0){ |
- throw方法:既可以在G函数内部抛出异常,也可以在G函数外部抛出异常
- return方法:终止G函数。
- G函数可以不完全实现“协程”。(看不懂)
- G函数可用于控制流管理。(不了解应用场景,大概是每次执行一步,每个步骤必须是同步操作)
- 异步编程
异步编程的方法:回调函数,事件监听,发布/订阅,Promise对象,G函数(ES6)。
yield & yield*
yield
yield译为“产出”,在代码里有“生成”的意思。
yield表达式只能在G函数内使用,在G函数嵌套的普通函数内使用也会报错。
yield表达式如果放在另一表达式之中,必须套上圆括号,例如:console.log('aaa',(yield 'bbb'));
;
yield表达式如果作为其他函数的参数或放在表达式的右边,可以不加括号。getData(yield 'a'); let name=yield 'b';
yield表达式是函数可以暂停执行的标志,提供了可以手动“惰性求值”的语法。yield*
yield*语句可用于在G函数中,执行另一个G函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function* m(){
yield 'a';
yield* n();//没有yield*则无法执行
yield 'd';
}
function* n(){
yield 'b';
yield 'c'
}
for(let v of m()){
console.log(v);
}
//'a' 'b' 'c' 'd'
//内部的yield会全部执行,按照上下文的顺序yield相当于for of的简写形式。yield后跟字符串、数组等含I接口的都会遍历。
G函数的异步应用
JS使用回调函数(重新调用:callback)实现异步编程。
异步即两步,第一步先顺序执行,等拿到了第一步的返回值才会进入第二步,第二步的任务直接单独放在一个函数里,第一步抛出的错误也在第二步作为参数捕捉。
异步回调函数的缺点是多重嵌套情况,就会逻辑混乱。(回调地狱)
Promise对象就是用来解决这个缺点。(.then.then.catch写法)
Promise的缺点是代码冗余。(个人觉得挺清晰的- -)
G函数用来解决这个缺点。
G函数的最大特点是能交出执行权,使当前异步任务暂停。
调用G函数返回一个指针(即遍历器I),G函数内的yield和I接口的next方法的组合技就是恢复执行。(next->执行当前yield->到下一yield暂停)
G函数的缺点是不方便流程管理,难以确认什么时候执行下一阶段。
Thunk函数(T函数)用于解决G函数的缺点。
T函数实现“传名调用”(而JS是传值调用),用来替换作为函数参数的表达式。
语法糖:async函数就是将 G函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数的返回值是Promise对象。
End.🐧