在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接口,就能够完成遍历操做。好比下面这种状况:this
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。prototype
// 也能够直接使用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) }
当使用for-of循环遍历某个数据结构时,该循环会自动去寻找iterator接口。只要一个对象含有iterator接口,那么该对象就能够被遍历。指针
let arr = [1,2,3,5] let [first,...second] = arr
var str = 'hello' [...str] // ['h','e','l','l','o']
yield*后面跟的是一个可遍历的结构,它就会调用该结构的遍历器接口。code
let generator = function* (){ yield* [2,3,4] }
因为数组的遍历会调用遍历器接口,因此任何接受数组做为参数的场合,其实都调用了遍历器接口。好比for-of、Promise.all()、Promise.race()对象
生成器函数与普通函数不一样的点:普通函数使用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迭代器。
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函数开始运行以后,继续向函数内部注入值。
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}
。
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语句。
在generator函数内部再调用另外一个generator函数,默认状况下是没有效果的,这个时候就须要用到yield*表达式,用来在一个generator函数内执行另外一个generator函数。