ES6小记(4)
ES6小计(4)
13. Iterator和for…of
13.1 Iterator遍历器
遍历器创建一个指针对象,通过不断调用指针对象的next方法移动指针指向,不断对对象成员进行遍历。拥有遍历器接口的对象,均可以使用for...of
进行遍历。数组、类数组、Set结构、Map结构自带遍历器接口,可直接使用for...of
进行遍历。
调用遍历器接口的场合:
数组和Set的结构赋值、扩展运算符、yield*
状态机、for...of
、Array.from()
、Map()
、Set()
、Promise.all()
、Promise.race()
字符串为类数组解构,也具有遍历器解构。
13.2 for…of循环
for...of
用于遍历所有数据结构的统一方法。只要一个数据结构有Symbol.iterator
属性,就可以用for...of
进行遍历。
var arr = ['a', 'b', 'c']
for (let a in arr) { console.log(a) } // 0 1 2
for (let a of arr) { console.log(a) } // a b c
for...in
用于遍历键名,for...of
用于遍历键值。如果要通过for...of
遍历键名,可以借助数组实例的entries()
和keys()
方法进行遍历。for...of
遍历数组时,只会遍历数字索引的属性,与for...in
有点不一致。
Set
和Map
的for...of
遍历:
var engines = new Set(['a', 'b', 'c', 'c'])
for (var e of engines) {
console.log(e)
}
// a
// b
// c
var es6 = new Map()
es6.set('a', 1)
es6.set('b', 2)
es6.set('c', 3)
for (var [key, value] of es6) {
console.log(key + ':' + value)
}
// a:1
// b:2
// c:3
遍历的顺序是按添加顺序进行的,Set遍历为一个值,Map遍历时一个键和值组成的数组。对于有的不含遍历接口的类数组{0: 'a', 1: 'b', length: 2}
,可以先用Array.from()
将其转换为数组后遍历。for...of
不能遍历普通对象。但可以用Object.keys()
将对象的键名生成数组后进行遍历。
13.3 遍历比较
for
循环:最原始,书写较麻烦。
数组的forEach
循环:arr.forEach(function(value) { console.log(value) })
,缺点是无法跳出循环。for...in
循环:不仅遍历数字键名,还遍历原型链上的键,这往往不是我们想要的,且遍历有时顺序随机。for...of
循环:没有上述的缺点。
14. Generator函数
14.1 简介
Generator函数是一种新的JS异步编程解决方案。可以把它理解为一个状态机,在这个状态机内,有多个需要依次去检测其状态的异步函数。Generator函数会返回一个遍历器对象,故其也是一个遍历器对象生成函数。
Generator函数有两个特征:一是在function
与函数名之间有一个*
号;而是内部用yield
定义不同的状态。
function *temp() {
yield 'a';
yield 'b';
return 'c'
}
let hw = temp()
hw.next() // {value: 'a', done: false}
hw.next() // {value: 'b', done: false}
hw.next() // {done: true}
Generator函数的调用方法与普通函数一样,但因Generator函数返回的是一个遍历器对象,故函数内容并没有执行,需要用遍历器对象的next
方法去遍历执行。每次调用next
方法,内部指针就从函数的头部或上一次停下来的地方开始执行,执行到下一个yield
(会执行完遇到的这个yield语句)或者return
语句为止。
从上述变现看,Generator函数是一个可以暂停执行的函数,yield
就是暂停语句。yield
暂停语句不能用在普通函数中,会报错。若yield
语句在表达式中用于计算时,需要在()
内使用。用于赋值表达式的又边时,可以不加括号。
14.2 next 方法参数
yield
语句本身没有返回值,但next()
方法可以带一个参数,携带的这个参数会被当做上一条yeild
语句的返回值。故可以通过next()
方法向函数内部注入控制值,但又有函数的上下文环境。
function *foo(x) {
var y = 2 * (yield (x + 1))
var z = yield (y / 3)
return x + y + z
}
var a = foo(5)
a.next() // {value: 6, done: false}
a.next() // {value: NaN, done: false} 注意yield无返回值,y = 2 * undefined == NaN
a.next() // {value: NaN, done: true}
var b = foo(5)
b.next() // {value: 6, done: false}
b.next(12) // {value: 8, done: false} // y = 2 * 12
b.next(13) // {value: 42, done: true} // x = 5, y = 24, z = 13
第一条next
语句不能有参数,因参数代表上一条语句的返回值。当然,可以在Generator函数外再包一层已有一个自执行的next()
方法的状态机函数就行了。
14.3 for...of
循环
for...of
可以自动遍历Generator函数,不需要使用next方法。当next
方法返回对象的done
属性为true
时,for...of
循环终止,并且不包含返回对象。
function *foo() {
yield 1
yield 2
yield 3
return 4
}
for (let v of foo()) {
console.log(v)
}
// 1 2 3
14.4 Generator.prototype.throw()
Generator函数返回的遍历器对象都有一个throw
方法,可以在函数体外抛出错误,但是错误的冒泡,会从函数体内开始。类似于错误注入到状态机内。注入的位置为yield
关键字位置,在后续语句执行前。若此处上下文没有进行错误捕获,错误则会冒泡到函数外部。类似于Promise
的reject
。
var g = function *() {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') throw e
console.log('内部捕获', e)
}
}
}
var i = g()
i.next()
try {
i.throw('a')
i.throw('b')
} catch (e) {
console.log('外部捕获', e)
}
// 内部捕获 a
// 外部捕获 b
这就意味着,将状态机内用try...catch
包装起来后,可以用防止状态机内错误外泄,且便于统一处理。若状态机内执行过程中抛出错误,就不会再执行了。若此后再调用next
方法,将返回{value: undefined, done: true}
对象。
14.5 Generator.prototype.return()
Generator函数返回的遍历器对象都有一个return
方法,可以返回给定的值,并终止遍历。
function *gen() {
yield 1
yield 2
yield 3
}
var g = gen()
g.next() // {value: 1, done: false}
g.return('foo') // {value: 'foo', done: true}
g.next() // {value: undefined, done: true}
当return()
不传值时,返回值value
为undefined
。
function *numbers() {
yield 1
try {
yield 2
yield 3
} finally {
yield 4
yield 5
}
yield 6
}
var g = numbers()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.return(7) // {value: 4, done: false}
g.next() // {value: 5, done: false}
g.next() // {value: 7, done: true}
注意当return
在try
代码块内返回时,要等到finally
结束后才会执行return
返回值。finally
内等同于try
外部,但是try
后会执行。
14.6 yield* 语句
如果在Generator函数内部调用另外一个Generator函数,默认情况下是没有效果的,因为Generator函数仅仅返回的是一个遍历器对象。但我们想要这个遍历器对象也生效时,就要用yield*
方法调用内部Generator函数了,类似于对于这个遍历器对象做for...of
遍历。其实yield*
语法完全可以用for...of
进行转换。
function *foo() { yield 'a'; yield 'b'; }
function *bar() { yield 'x'; foo(); yield 'y' }
function *set1() { yield 'x'; yield* foo(); yield 'y' }
function *set2() { yield 'x'; for (let v of bar()) { yield v }; yield 'y' }
for (let v of bar()) {
console.log(v)
}
// x
// y
for (let v of set1()) {
console.log(v)
}
// x
// a
// b
// y
for (let v of set2()) {
console.log(v)
}
上面例子最后set2输出同set1一致,不过set2中for…of循环需要自己做yield处理。并不是像yield 语句一样,将内部遍历器对象与外部遍历器对象合并了。准确的说,是`yield`将内部遍历器对象提取到外部来了。
任何数据结构,具有Iterator接口,就可以使用yield*
进行遍历,与for...of
表现一致。若内部Generator函数内有return
语句进行返回时,可以向外部Generator函数进行数据返回。如用yield*
进行处理内部状态机时,有两步:将内部状态机遍历器对象提取到外部,返回内部return的返回值。
function *g() {
yield 'a'
yield 'b'
return 'result:'
}
function *g2(send) {
let result = yield* send()
console.log(result)
}
console.log([...g2(g)])
// result:
// [ 'a', 'b' ]
故可以很方便地取出多重数组内部的值。
14.7 作为对象属性的Generator函数
let obj = {
gen: function *() {}
}
// 等同于
let obj = {
* gen() {}
}
14.8 Generator函数内的this
因为Generator函数返回的时一个遍历器对象,故在没有对遍历器遍历时,Generator内部逻辑是没有执行的,故内部的this
无效。但可以使用bind
显式地对内部this
进行绑定,并且对遍历器对象进行遍历后,this
才会有正确的指向。
function *g() {
this.a = 1
}
let obj = g()
obj.a // undefined
function *f() {
yield this.x = 2
yield this.y = 3
}
let obj = {}
let temp = f.bind(obj)()
temp.next() // {value: 2, done: false}
temp.next() // {value: 3, done: false}
temp.next() // {value: undefined, done: true}
obj // {x: 2, y: 3}
14.9 应用
Generator函数的暂停执行效果,意味着把异步操作写在yield
语句后,就可以通过next
方法进行异步调用,顺序执行。
TO BE CONTINUED!