关注前端小讴,浏览更多原创技术文章
迭代器模式
- ES6 新增了 2 个高级个性:迭代器和生成器
迭代办法 | 优/毛病 |
---|---|
for | ① 需晓得如何应用数据结构 ② 遍历程序不是固有的 |
forEach() | ① 无奈标识迭代终止 ② 只实用数组 ③ 回调构造较蠢笨 |
Iterator 接口 | 无需理解可迭代对象的构造,只需晓得如何获得间断的值 |
相干代码 →
可迭代协定
- 实现 Iterable 接口要求同时具备:① 反对迭代的自我辨认 ② 创立实现 Iterator 接口的对象
- 必须裸露一个属性作为默认迭代器,属性应用Symbol.iterator作为键
- 默认迭代属性必须援用一个迭代器工厂函数,调用工厂函数返回一个新迭代器
- 实现了 Iterable 接口的内置类型:字符串、数组、映射、汇合、arguments 对象、NodeList 等 DOM 汇合类型
// 未实现迭代器工厂函数let num = 1let obj = {}console.log(num[Symbol.iterator]) // undefinedconsole.log(obj[Symbol.iterator]) // undefined// 实现了迭代器工厂函数let str = 'abc'let arr = ['a', 'b', 'c']let map = new Map().set('a', 1).set('b', 2)let set = new Set().add('a').add('b')console.log(str[Symbol.iterator]) // ƒ [Symbol.iterator]() { [native code] }console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }console.log(map[Symbol.iterator]) // ƒ entries() { [native code] }console.log(set[Symbol.iterator]) // ƒ values() { [native code] }// 调用迭代器工厂函数,生成新的迭代器console.log(str[Symbol.iterator]()) // StringIterator {}console.log(arr[Symbol.iterator]()) // ArrayIterator {}console.log(map[Symbol.iterator]()) // MapIterator { "a" => 1, "b" => 2 }console.log(set[Symbol.iterator]()) // StringIterator { "a", "b" }
- 不须要显示调用迭代器工厂函数,实现可迭代协定的所有类型都主动兼容承受可迭代对象的任何语言个性
承受可迭代对象的原生语言个性包含:
for-of
循环- 数组解构
- 扩大操作符
Array.from()
- 创立汇合
- 创立映射
Promise.all()
接管由期约组成的可迭代对象Promise.race()
接管由期约组成的可迭代对象yield*
操作符,在生成器中应用
for (let el of arr) { console.log(el) // for-of循环 /* 'a' 'b' 'c' */}let [a, b, c] = arr // 数组解构console.log(a, b, c) // 'a' 'b' 'c'let arr2 = [...arr] // 扩大操作符console.log(arr2) // [ 'a', 'b', 'c' ]let arr3 = Array.from(arr) // Array.from()console.log(arr3) // [ 'a', 'b', 'c' ]let set2 = new Set(arr)console.log(set2) // Set(3) { 'a', 'b', 'c' }let pairs = arr.map((x, i) => [x, i])console.log(pairs) // [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ] ]let map2 = new Map(pairs)console.log(map2) // Map(3) { 'a' => 0, 'b' => 1, 'c' => 2 }
- 如果对象原型链上的父类实现了 Iterabe 接口,该对象也实现了这个接口
class FooArray extends Array {}let fooArr = new FooArray('foo', 'bar', 'baz')for (let el of fooArr) { console.log(el) /* foo bar baz */}
迭代器协定
迭代器 API 应用
next()
办法在可迭代对象中遍历数据,每次胜利调用next()
都返回一个IteratorResult
对象:IteratorResult
蕴含 2 个属性done和valuedone
是一个布尔值,false 为未耗尽,true 为耗尽value
蕴含可迭代对象的下一个值(若 done 为 true 则 value 为 undefined)- 迭代器达到
done:true
状态,后续再调用next()
始终返回{done:true,value:undefined}
let arr4 = ['foo', 'bar'] // 可迭代对象console.log(arr4[Symbol.iterator]) // ƒ values() { [native code] },迭代器工厂函数let iter = arr4[Symbol.iterator]() // 迭代器console.log(iter) // ArrayIterator {}console.log(iter.next()) // { value: 'foo', done: false },执行迭代console.log(iter.next()) // { value: 'foo', done: false },执行迭代console.log(iter.next()) // { value: undefined, done: true },执行迭代console.log(iter.next()) // { value: undefined, done: true },执行迭代
- 同一个可迭代对象的不同迭代器实例之间没有分割,每个迭代器都独立地遍历可迭代对象
let iter2 = arr4[Symbol.iterator]() // 迭代器iter2,迭代可迭代对象arr4let iter3 = arr4[Symbol.iterator]() // 迭代器iter2,迭代可迭代对象arr4console.log(iter2.next()) // { value: 'foo', done: false }console.log(iter3.next()) // { value: 'foo', done: false }console.log(iter3.next()) // { value: 'bar', done: false }console.log(iter2.next()) // { value: 'bar', done: false }
- 可迭代对象在迭代期间被批改,迭代器反映相应变动;迭代器保护的是指向可迭代对象的援用,会阻止垃圾回收程序回收可迭代对象
let arr5 = ['foo', 'baz']let iter4 = arr5[Symbol.iterator]()console.log(iter4.next()) // { value: 'foo', done: false }arr5.splice(1, 0, 'bar') // 迭代期间,可迭代对象被批改console.log(iter4.next()) // { value: 'bar', done: false }console.log(iter4.next()) // { value: 'baz', done: false }console.log(iter4.next()) // { value: undefined, done: true }
自定义迭代器
- 任何实现了 Iterable 接口(具备 Symbol.iterator 属性)的对象都能够作为迭代器应用
class Counter { // 构造函数 constructor(limit) { this.count = 1 this.limit = limit } // Iterable 接口,实现自定义迭代 [Symbol.iterator]() { return this } // 原型上的迭代办法 next() { if (this.count <= this.limit) { return { value: this.count++, done: false } } else { return { value: undefined, done: true } } }}let counter = new Counter(5)console.log(counter) // Counter { count: 1, limit: 5 },构造函数console.log(counter[Symbol.iterator]) // ƒ [Symbol.iterator]() { return this },迭代器工厂函数for (let i of counter) { console.log(i) /* 实现了自定义迭代器: 1 2 3 4 5 */}for (let i of counter) { console.log(i) /* 只能迭代1次 nothing logged */}
- 将计数器变量放到闭包里,实现同一个可迭代对象可能创立多个迭代器
class Counter2 { constructor(limit) { this.limit = limit } [Symbol.iterator]() { let count = 1 // 计数器变量放到闭包中 let limit = this.limit return { next() { if (count <= limit) { return { value: count++, done: false } } else { return { value: undefined, done: true } } }, } }}let counter2 = new Counter2(3)for (let i of counter2) { console.log(i) /* 1 2 3 */}for (let i of counter2) { console.log(i) /* 同一个可迭代对象,可能创立多个迭代器: 1 2 3 */}
- 任何迭代器,调用 Symbol.iterator 属性援用的工厂函数后,返回与原迭代器雷同的迭代器
let arr6 = ['foo', 'bar', 'baz']let iter5 = arr6[Symbol.iterator]()console.log(iter5) // ArrayIterator {}let iter6 = iter5[Symbol.iterator]() // 迭代器再次调用工厂函数,生成新的迭代器console.log(iter6) // // ArrayIterator {}console.log(iter5 === iter6) // truelet iter7 = iter6[Symbol.iterator]() // 迭代器再次调用工厂函数,生成新的迭代器console.log(iter7) // // ArrayIterator {}console.log(iter6 === iter7) // truefor (let i of iter7) { console.log(i) /* 'foo' 'bar' 'baz' */}
提前终止迭代器
可选的
return()
办法可在迭代器未耗尽时,指定迭代器提前敞开时执行的逻辑,迭代器提前敞开的可能状况包含:for-of
循环通过break
、continue
、return
或throw
提前退出解构操作
未生产所有值
class Counter3 { constructor(limit) { this.limit = limit } [Symbol.iterator]() { let count = 1 // 计数器变量放到闭包中 let limit = this.limit return { next() { if (count <= limit) { return { value: count++, done: false } } else { return { value: undefined, done: true } } }, // 提前终止迭代器的办法 return() { console.log('Exiting early') return { done: true } }, } }}let counter3 = new Counter3(5)for (let i of counter3) { if (i > 2) { break // 提前终止迭代器,调用迭代器的return()办法 } console.log(i) /* 1 2 'Exiting early' */}try { for (let i of counter3) { if (i > 2) { throw 'err' // 提前终止迭代器,调用迭代器的return()办法 } console.log(i) /* 1 2 'Exiting early' */ }} catch (e) {}let [a2, b2, c2, d2, e2, f2] = counter3 // 解构操作,生产所有值console.log(a2, b2, c2, d2, e2, f2) // 1 2 3 4 5 undefinedlet [a3, b3, c3] = counter3 // 'Exiting early',解构操作,未生产所有值
- 若迭代器未敞开,提前终止后能够从上次来到的中央持续迭代,数组的迭代器就是不能敞开的
let arr7 = [1, 2, 3, 4, 5]let iter8 = arr7[Symbol.iterator]()for (let i of iter8) { console.log(i) if (i > 2) { break // 提前退出迭代器,但不敞开 } /* 1 2 3 */}for (let i of iter8) { console.log(i) /* 持续迭代 4 5 */}
迭代器实例的
return
属性是否为函数对象,决定迭代器是否可敞开- 给不可敞开的迭代器减少
return()
办法不能让该迭代器变得可敞开,但提前终止会调用新增的return()
办法
- 给不可敞开的迭代器减少
let arr8 = [1, 2, 3, 4, 5]let iter9 = arr8[Symbol.iterator]()console.log(iter9.return) // undefined,迭代器不可敞开iter9.return = function () { // 追加return办法,但无奈让迭代器变得可敞开 console.log('Exiting early') return { done: true }}for (let i of iter9) { console.log(i) if (i > 2) { break // 提前退出迭代器 } /* 1 2 3 'Exiting early',提前终止迭代器仍会调用return()办法 */}for (let i of iter9) { console.log(i) /* 持续迭代 4 5 */}
总结 & 问点
- 实现迭代有哪些形式?其优、毛病别离是什么?默认迭代器必须应用什么属性作为键?
- 如何检测数据类型是否实现了迭代器工厂函数?调用这个工厂函数会生成什么?
- 迭代器 API 调用哪个办法遍历数据?其返回值是什么?同一个可迭代对象的不同迭代器实例之间有分割么?
- 迭代器真正保护的是什么?其对垃圾回收程序有什么影响?可迭代对象在迭代期间被批改,迭代器受影响么?
- 写一段代码创立一个自定义迭代器,实现“同一个可迭代对象可能创立多个迭代器”
- 迭代器调用 Symbol.iterator 属性援用的工厂函数会生成什么?
- 如何指定迭代器提前敞开时执行的逻辑?哪些状况下迭代器会提前终止?
- 提前终止的迭代器若未敞开,是否可持续迭代?如何判断迭代器是否可敞开?
- 不可敞开的迭代器是否变成可敞开的?请用代码证实