概念: 迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象[1][2][3],设计人员使用此接口无需关心容器对象的内存分配的实现细节。
JS中的迭代器
其对象返回一个next函数
调用next函数返回一个对象,其对象中包含两个属性done
(完成),它的值为布尔类型,也就是true/false
。
{done:false}
{done:true}
value
(值),它可以返回js中的任何值,TS中表示可为:value:any
类型思考以下代码:
let index = 0 const bears = ['ice', 'panda', 'grizzly'] let iterator = { next() { if (index < bears.length) { return { done: false, value: bears[index++] } } return { done: true, value: undefined } } } console.log(iterator.next()) //{ done: false, value: 'ice' } console.log(iterator.next()) //{ done: false, value: 'panda' } console.log(iterator.next()) //{ done: false, value: 'grizzly' } console.log(iterator.next()) //{ done: true, value: undefined }
next
方法,next
方法返回了一个对象,有done
属性和value
属性,且key
的值类型也为boolean
或any
,符合迭代器协议,是一个妥妥的迭代器没跑了。index
和iterator
对象是属于一个整体,我却使用了全局变量,从V8引擎的GC,可达性(也就是标记清除)来看,如果bears = null
,不手动设置为null很有可能会造成内存泄漏,并且内聚性低。very beautiful
,very 优雅。
思考一下代码:
const bears = ['ice', 'panda', 'grizzly'] function createArrIterator(arr) { let index = 0 let _iterator = { next() { if (index < arr.length) { return { done: false, value: arr[index++] } } return { done: true, value: undefined } } } return _iterator } let iter = createArrIterator(bears) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())
迭代器对象和可迭代对象是一个不同的东西,虽然它们存在关联,而且面试的时候经常面这些概念,废话不多说,我们直接进入主题。
实现了[Symbol.iterator]为key的方法,且这个方法返回了一个迭代器对象
for of 的时候,其本质就是调用的这个函数,也就是[Symbol.iterator]为key的方法
let str = 'The Three Bears' const bears = ['ice', 'panda', 'grizzly'] for( let text of str) { console.log(text) //字符串每个遍历打印 } for( let bear of bears) { console.log(bear) } //ice panda grizzly
const bears = ['ice', 'panda', 'grizzly'] //数组的Symbol.iterator方法 const iter = bears[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) const nickName = 'ice' //字符串的Symbol.iterator方法 const strIter = nickName[Symbol.iterator]() console.log(strIter.next()) console.log(strIter.next()) console.log(strIter.next()) console.log(strIter.next())
let info = { bears: ['ice', 'panda', 'grizzly'], [Symbol.iterator]: function() { let index = 0 let _iterator = { //这里一定要箭头函数,或者手动保存上层作用域的this next: () => { if (index < this.bears.length) { return { done: false, value: this.bears[index++] } } return { done: true, value: undefined } } } return _iterator } } let iter = info[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) //符合可迭代对象协议 就可以利用 for of 遍历 for (let bear of info) { console.log(bear) } //ice panda grizzly
[Symbol.iterator]
方法,并且这个方法返回了一个迭代器对象。class myInfo { constructor(name, age, friends) { this.name = name this.age = age this.friends = friends } [Symbol.iterator]() { let index = 0 let _iterator = { next: () => { const friends = this.friends if (index < friends.length) { return {done: false, value: friends[index++]} } return {done: true, value: undefined} } } return _iterator } } const info = new myInfo('ice', 22, ['panda','grizzly']) for (let bear of info) { console.log(bear) } //panda //grizzly
friends
进行了迭代,你也可以迭代你想要的一切东西...生成器是ES6新增的一种可以对函数控制的方案,能灵活的控制函数的暂停执行,继续执行等。
生成器函数和普通函数的不同
function
定义,生成器函数function*
,要在后面加*
yield
来控制函数的执行function* bar() { console.log('fn run') } bar()
function* bar() { console.log('fn run') } const generator = bar() console.log(generator.next()) //fn run //{ value: undefined, done: true }
function* bar() { console.log('fn run start') yield 100 console.log('fn run...') yield 200 console.log('fn run end') return 300 } const generator = bar() //1. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中 console.log(generator.next()) //2. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中 console.log(generator.next()) //3. 执行剩余代码 console.log(generator.next()) //打印结果: //fn run start //{done:false, value: 100} //fn run... //{done:false, value: 200} //fn run end //{done:true, value: 300}
yield x
,后就会暂停,等待下一次调用next继续往下执行,周而复始,没有了yield
关键字,进行最后一次next调用返回done:true
。我有一个需求,既然生成器能控制函数分段执行,我要你实现一个分段传参。
思考以下代码:
function* bar(nickName) { const str1 = yield nickName const str2 = yield str1 + nickName return str2 + str1 + nickName } const generator = bar('ice') console.log(generator.next()) console.log(generator.next('panda ')) console.log(generator.next('grizzly ')) console.log(generator.next()) // { value: 'ice', done: false } // { value: 'panda ice', done: false } // { value: 'grizzly panda ice', done: true } // { value: undefined, done: true }
前面我们讲到,生成器是一个特殊的迭代器,那生成器必定是可以代替迭代器对象的,思考以下代码。
let bears = ['ice','panda','grizzly'] function* createArrIterator(bears) { for (let bear of bears) { yield bear } } const generator = createArrIterator(bears) console.log(generator.next()) console.log(generator.next()) console.log(generator.next()) console.log(generator.next())
其实这里还有一种语法糖的写法yield*
思考以下代码:
let bears = ['ice','panda','grizzly'] function* createArrIterator(bears) { yield* bears } const generator = createArrIterator(bears) console.log(generator.next()) console.log(generator.next()) console.log(generator.next()) console.log(generator.next())
class myInfo { constructor(name, age, friends) { this.name = name this.age = age this.friends = friends } *[Symbol.iterator]() { yield* this.friends } } const info = new myInfo('ice', 22, ['panda','grizzly']) for (let bear of info) { console.log(bear) } //panda //grizzly
{done:boolean, value:any}
[Symbol.iterator]
方法,并且调用这个方法返回一个迭代器function* bar() {}
这种形式定义yield
关键字可以控制函数分段执行