【js】迭代器与生成器

迭代器(Iterator) 有时也称为遍历器 - 迭代器对象
作用

  1. 为各种数据结构提供统一的访问接口
  2. 使数据结构的成员按照某种次序排列
  3. 统一的迭代方式for...of循环
1.可迭代(iterable) iterable:拥有Symbol.iterator属性的数据结构是可迭代的
Symbol.iterator:值为迭代器生成函数
原生可迭代的数据结构:
  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. arguments
  7. NodeList
    这些数据结构均可以使用for...of来遍历
//以数组为例,判断是否有Symbol.iterator属性 const arr = [1, 2, 3, 4] Symbol.iterator in arr //true //for...of遍历数组 for(const i of arr){ console.log(i) //1 2 3 4 }

2.迭代器生成函数(Symbole.iterator) 【【js】迭代器与生成器】返回一个迭代器对象
const arr = [1,2,3,4] console.log(arr[Symbol.iterator]())

【js】迭代器与生成器
文章图片

迭代器中有一个 next函数
next()会返回一个成员对象,有value和done两个属性,value表示 当前成员的值,done表示 遍历是否终止
for...of每次的遍历都会调用next函数并取value
2.1next()方法
const arr = [1, 2, 3, 4] let iter = arr[Symbol.iterator]() //返回一个遍历器对象 console.log(iter.next()); //{value: 1, done: false} console.log(iter.next()); //{value: 2, done: false} console.log(iter.next()); //{value: 3, done: false} console.log(iter.next()); //{value: 4, done: false} console.log(iter.next()); //{value: undefined, done: true}

遍历器对象可以视为一个指针,每次调用上面的next方法都会移动指针
2.2实现一个迭代器生成函数
数组
//实现 function makeIterator(arr) { var nextIndex = 0; return { next() { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } //测试 const arr = [1] let iter = makeIterator(arr) console.log(iter.next()) //{value: 1, done: false} console.log(iter.next()); //{value: undefined, done: true}

  • 对于对象也可以去实现一个迭代器生成函数,思路大致一样
  • 但是对象上有丰富的方法间接生成一个数组,Object.values(obj)、keys、entries,这样就间接拥有了Symbol.iterator接口,可以使用for...of循环遍历
  • 对于对象这种非线性的数据结构,部署迭代器接口相当于将其转化为线性结构,不如直接使用Map
生成器(Generator)
  • 从语法和结构上看,封装了多个内部状态 - 状态机
  • 从返回结果看,返回了一个迭代器对象 - 迭代器生成函数
形式
  1. functon与函数名之间有一个星号*
  2. 内部使用yield语句定义不同的内部状态
    function* generator(){ yield 1; yield 2; return 3; }

1.next方法 1.1调用
调用生成器函数不会运行内部代码,会返回一个迭代器对象,上面有next方法
function* generator() { console.log("a"); yield 1; console.log("b"); return 2 console.log("c"); yield 3 } let iter = generator() console.log(iter);

【js】迭代器与生成器
文章图片

console.log(iter.next()); // "a" {value:1,done:false} console.log(iter.next()); // "b" {value:2,done:true} console.log(iter.next()); // {value:undefined,done:true}

第一次调用next会开始运行函数,从起始到第一个yield结束,返回yield后的状态
再调用next时会从上一个yield语句后开始执行,直到遇到下一个yield
当执行到return时,返回的对象done属性为true,且后续代码不再执行,下一调用返回{value:undefined,done:true}
yield不会终止只会中断且返回的done始终是false
1.2参数
next方法中可以添加参数,作为上一个yield的值,yield本身没有值
function* generator(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z) } let iter = generator(5) console.log(iter.next()); //{value: 6, done: false} console.log(iter.next(12)); //{value: 8, done: false} console.log(iter.next(13)); //{value: 42, done: true}

第一次返回结果为 x + 1 => 5 + 1
第二次的返回结果y由第一个yield计算而来,其值为第二个next()中的参数 2*12/3 = 8
z的值由第二个yield的值,其值为第三个next()中的参数 5 + 24 + 13 = 42
function* generator() { let val1 = yield 1 console.log(""val1); let val2 = yield 2 console.log(val2); } let iter = generator() iter.next("one") iter.next("two") //第一个yield的值:two iter.next("three")//第二个yield的值:three

2.yield语句
  • 普通的yield语句可以中断执行,并且返回转态
  • yield* 后可以跟随一个迭代器对象,相当于在生成器内调用另一个生成器
    function* generator1() { yield 1 yield 2 } function* generator2() { yield 'a' yield* generator1() yield 'b' return 'c' } let gene2 = generator2() for (const i of gene2) { console.log(i)// 'a' 1 2 'b' }

    在一个生产器yield* 执行另一个生成器,相当于将它的语句插入到该生成器中
    return返回的done是true,for...of在遍历时不会返回其值
  • 如果是yield后执行一个生成器,next返回的value是一个迭代器对象
function* generator1() { yield 1 yield 2 } function* generator2() { yield 'a' yield generator1() } let gene2 = generator2() console.log(gene2.next()); console.log(gene2.next());

【js】迭代器与生成器
文章图片

3.Generator.prototype.throw() 3.1作用
外部抛出错误,在内部捕获
function* generator() { try { yield 1 yield 2 yield 3 } catch (e) { console.log("内部捕获:" + e); } } let gene = generator() try { console.log(gene.next()); gene.throw("第一次错误"); console.log(gene.next()); gene.throw("第二次错误"); console.log(gene.next()); gene.throw("第三次错误"); console.log(gene.next()); } catch (e) { console.log("外部捕获:" + e); }

【js】迭代器与生成器
文章图片

第一次调用throw会在内部的catch中捕获,try中的语句不再执行
出现错误后再调用next为终止状态的对象,说明生成器被终止了
由于生成器被终止,第二次错误会在 外部捕获,生成器 彻底终止,再调用上面的方法不再生效
3.2返回值
throw返回下一个next的状态
function* generator() { try { console.log(1); yield "in11" console.log(2); yield "in12" } catch (e) { console.log("内部捕获1"); }console.log(3); yield "out1"try { console.log(4); yield "in21" console.log(5); yield "in22" } catch (e) { console.log("内部捕获2"); }console.log(6); yield "out2" yield "out3" } let gene = generator() try { console.log(gene.next()); //1 {value: 'in11', done: false} console.log(gene.throw()); //内部捕获1 3 {value: 'out1', done: false} console.log(gene.next()); //4 {value: 'in21', done: false} console.log(gene.throw()); //内部捕获2 6 {value: 'out2', done: false} console.log(gene.next()); //7 {value: 'out3', done: false} console.log(gene.throw()); //外部捕获 console.log(gene.next()); } catch (e) { console.log("外部捕获"); }

上面的代码中有多个try catch
第一个next会进入第一个try中
第一个throw的调用环境在有catch,跳出try执行至下一个yield
下一个yield有catch可继续throw,若没有catch则在外部捕获,生成器终止
throw在错误被捕获再去返回下一个next的返回值
4.Generator.prototype.return() 返回给定的值,终止生成器
function* generator(){ yield 1 yield 2 yield 3 } let gene = generator() gene.next() // {value:1,done:false} gene.return("foo")// {value:"foo",done:false} gene.next() // {value:undefined,done:false}

  • 如果在try..finally中,return会在finally后运行
    function* generator() { try { yield 1 yield 2 } finally { yield 3 yield 4 } yield 5 } let gene = generator() console.log(gene.next()); //{value: 1, done: false} console.log(gene.return(6)); //{value: 3, done: false} console.log(gene.next()); //{value: 4, done: false} console.log(gene.next()); //{value: 6, done: true} console.log(gene.next()); //{value: undefined, done: true}

    第一个next进入try中,如果直接return会直接结束
    在try中执行return,会进入finally中,执行至第一个yield,返回状态,并将return添加到finally的结尾
    在finally中执行return会直接结束
5.部署一个Iterator接口 对象
function* iterEntries(obj) { let keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]] } } let objEntr = iterEntries({ a: 1, b: 2, c: 3 }) for (const [key, val] of objEntr) { console.log(key, val); }

'a' 1 'b' 2 'c' 3

    推荐阅读