iterator迭代器
在ES6之前遍历数组的要领有以下四种:
// 第一种
for(var i = 0; i < array.length; i++){
console.log(array[i])
}
// 第二种
array.forEach(function(item,index){
console.log(item)
})
// 第三种
for(var index in array){
console.log(array[index])
}
// 第四种
for(var value of array){
console.log(value)
}
在上面的遍历体式格局中,第二种体式格局有一种小缺点,就是不能运用break
语句中缀实行,也不能运用return
语句返回到外层函数。它会一向遍历完数组的一切元素。第三种体式格局是一个更蹩脚的体式格局,在遍历历程当中,赋值给index的不是number范例,而是字符串范例,for-in
轮回除了遍历数组外,还会遍历自定义属性,以至是遍历出原型链上的属性。而且for-in的遍历递次不能获得保证。
第四种要领是ES6中新增的遍历数组的要领,它可以准确相应break、continue
和return
语句,for-in语句除了能遍历数组外,还能遍历类数组对象,如DOM的NodeList对象,arguments对象,也能遍历字符串、Set对象、Map对象。
for-of轮回语句可以遍历种种鸠合的。是因为这些对象都有一个迭代器的要领,迭代器(Iterator
)是一种接口,为种种差别的数据构造供应一致的接见机制,任何数据构造只需布置了iterator接口,就可以完成遍历操纵。比方下面这类状况:
let obj = {
data:['hello','world'],
[Symbol.iterator](){
const _self = this
let index = 0
return {
next(){
if(index < _self.data.length){
return {value:_self.data[index++],done:false}
}else{
return {value:undefined,done:true}
}
}
}
}
}
for(var value of obj){
console.log(value) // 输出 hello world
}
迭代器也可以直接运用Array默许的iterator。
// 也可以直接运用Array的迭代器
let newObj = {
0:'a',
1:'b',
2:'c',
[Symbol.iterator]:Array.prototype[Symbol.iterator]
}
for(var value of newObj){
console.log(value) // 输出 a b c
}
for-of轮回语句实际上是挪用遍历对象的[Symbol.iterator]要领,该要领返回一个iterator,内里有一个next要领,for轮回会不停挪用这个iterator.next要领来猎取下一个值,直到返回值中的done属性为ture时终了轮回。除了增加iterator外还可以运用yield完成轮回:
let obj = {
[Symbol.iterator]: function* (){
for(var i = 0; i < 100; i++){
yield i
}
}
}
for(var value of obj){
console.log(value)
}
iterator的遍历历程
- 建立一个指针对象,指向当前数据构造的肇端位置,也就是说,遍历器对象的实质就是一个指针对象。
- 第一次挪用指针对象的next要领,可以将指针指向数据构造的第一个成员。
- 不停挪用指针对象的next要领,直到它指向数据构造的终了位置。
当运用for-of轮回遍历某个数据构造时,该轮回会自动去寻觅iterator接口。只需一个对象含有iterator接口,那末该对象就可以被遍历。
运用iterator的场景
- 解构赋值
let arr = [1,2,3,5]
let [first,...second] = arr
- 扩大运算符
var str = 'hello'
[...str] // ['h','e','l','l','o']
- yield*
yield*背面跟的是一个可遍历的构造,它就会挪用该构造的遍历器接口。
let generator = function* (){
yield* [2,3,4]
}
- 其他场所
因为数组的遍历会挪用遍历器接口,所以任何接收数组作为参数的场所,实在都挪用了遍历器接口。比方for-of、Promise.all()、Promise.race()
天生器Generators
天生器函数与一般函数差别的点:一般函数运用function关键字声明,天生器函数运用function*声明,在天生器函数内部,有相似return语法的yeild关键字,与return差别的是,天生器函数可以yeild屡次,在函数实行历程当中,碰到yield表达式马上停息,后续可恢复实行状况。
function* question(name){
yield "你好" + name + '!'
yield "愿望你能喜好"
yield "下次再会"
return '拜拜'
}
var iter = question('小明')
iter.next() // {value: "你好小明!", done: false}
iter.next() // {value: "愿望你能喜好", done: false}
iter.next() // {value: "下次再会", done: false}
iter.next() // {value: '拜拜', done: true}
generator函数在挪用后,并不会马上实行,而是返回一个iterator对象,每次天生器实行到yield语句后,天生器函数的客栈构造(当地变量、参数、临时价、天生器内部当前的实行位置)被移除客栈。但是,天生器对象保留了对这个客栈构造的援用,所以稍后挪用.next()要领可以从新激活客栈构造而且继承实行。不过天生器不是线程,它依然处于JS单线程里。
假如运转到背面没有yield表达式,就会一向运转到函数终了,直到return语句为止,并将return表达式的值赋值给末了返回对象的value值,假如没有return语句,则返回对象的value值为undefined。
generator天生器是iterator的天生函数,实行generator函数,返回的就是iterator迭代器。
next要领的参数
function* gen(){
for(let i = 0; true; i++){
let reset = yield i
if(reset){
i = -1
}
}
}
let g = gen()
g.next() // {value:0,done:false}
g.next(true) // {value:0;done:false}
yield表达式自身没有返回值,或者说老是返回undefined,next要领可以带一个参数,该参数就会被作为上一个yeild表达式的值。generator从停息状况到恢复运转,它的上下文状况是稳定的,经由过程next要领传入参数,可以在generator函数最先运转今后,继承向函数内部注入值。
throw要领
function* gen(){
try {
yield '123'
}catch(e){
console.log('内部捕捉',e)
}
}
let g = gen()
g.next()
try {
g.throw('a')
g.throw('b')
}catch(e){
console.log('外部捕捉','b')
}
// 内部捕捉 a
// 外部捕捉 b
上述代码中,遍历器对象g一连抛出毛病,第一个毛病被generator函数体内的catch语句捕捉,第二次抛出毛病时,因为catch语句已实行过了,不会捕捉该毛病,所以这个毛病由函数体外的catch捕捉。假如函数内部没有try-catch语句,那末throw要领抛出的毛病将被外部的try-catch捕捉。遍历器对象g挪用throw要领后,抛出的异常被generator函数体捕捉今后,会附带实行下一条yield语句。一旦generator实行历程抛出毛病,且没有被内部捕捉,generator函数就不会实行下去了。假如今后再挪用next要领,将返回对象{value:undefined,done:true}
。
return要领
function* gen(){
yield 1;
yield 2;
yiled 3;
}
var g = gen()
g.next() // {value:1,done:false}
g.return('end') // {value:'end',done:true}
g.next() // {value:undefined,done:true}
假如generator函数内部有try-finally代码块,那末return要领会推晚到finally代码块实行终了再实行。
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 }
next()、throw()、return()这三个要领都是让generator函数恢复实行,而且运用差别的语句替代yield表达式,next()是将yeild表达式替代成一个值,throw()是将yeild替代成一个throw语句,return()是将yield语句替代成一个return语句。
yield*
在generator函数内部再挪用另一个generator函数,默许状况下是没有结果的,这个时刻就需要用到yield*表达式,用来在一个generator函数内实行另一个generator函数。