迭代、迭代器、生成器的前世今生
什么是迭代
類似于遍歷 遍歷:有多個(gè)數(shù)據(jù)組成的集合數(shù)據(jù)結(jié)構(gòu)(map、set、array等其他類數(shù)組),需要從該結(jié)構(gòu)中依次取出數(shù)據(jù)進(jìn)行某種處理。 迭代:按照某種邏輯,依次取出下一個(gè)數(shù)據(jù)進(jìn)行處理。
什么是迭代器 iterator
JS語(yǔ)言規(guī)定,如果一個(gè)對(duì)象具有next方法,并且next方法滿足一定的約束,則該對(duì)象是一個(gè)迭代器(iterator)。 next方法的約束:該方法必須返回一個(gè)對(duì)象,該對(duì)象至少具有兩個(gè)屬性:
- value:any類型,下一個(gè)數(shù)據(jù)的值,如果done屬性為true,通常,會(huì)將value設(shè)置為undefined
- done:bool類型,是否已經(jīng)迭代完成
- 通過迭代器的next方法,可以依次取出數(shù)據(jù),并可以根據(jù)返回的done屬性,判定是否迭代結(jié)束。
案例如下:
/** * 迭代器 * 必須有個(gè)next函數(shù) * 該函數(shù)必須返回一個(gè)對(duì)象 包括 value 屬性 和 done屬性 * value:本次迭代的返回值 * done:true 或者 false 本次迭代是否結(jié)束 * */ const iterator = { total:3, index:1, next(){ const obj = { value:this.index > this.total?undefined:Math.random(), done:this.index > this.total } this.index++; return obj; } } //一個(gè)一個(gè)迭代直到不能迭代為止 let result = iterator.next(); while(!result.done){ console.log(result); result = iterator.next(); } //輸出斐波拉數(shù)列的數(shù)據(jù) //1 1 2 3 5 8 13 21 const sequenceItereator = { a:1, b:1, curIndex:1, //當(dāng)前從1開始算 next(){ if(this.curIndex == 1 || this.curIndex == 2){ this.curIndex++; return { value:1, done:false } } var c = this.a + this.b; this.curIndex++; this.a = this.b; this.b = c; return { value:c, done:false } } } for(var i = 0;i<50;i++){ console.log(sequenceItereator.next()); }
什么是迭代器創(chuàng)建函數(shù)
它是指一個(gè)函數(shù),調(diào)用該函數(shù)后,返回一個(gè)迭代器,則該函數(shù)稱之為迭代器創(chuàng)建函數(shù),可以簡(jiǎn)稱位迭代器函數(shù)。
案例如下:
//迭代器創(chuàng)建函數(shù) function createIterator(arr){ var i = 0; return { next(){ var obj = { value:arr[i], done:i>arr.length - 1 } i++; return obj; } } } var iterator = createIterator([1,3,5,7,9]); console.log(iterator);
什么是可迭代協(xié)議
ES6中出現(xiàn)了for-of循環(huán),該循環(huán)就是用于迭代某個(gè)對(duì)象的,因此,for-of循環(huán)要求對(duì)象必須是可迭代的(對(duì)象必須滿足可迭代協(xié)議) 可迭代協(xié)議是用于約束一個(gè)對(duì)象的,如果一個(gè)對(duì)象滿足下面的規(guī)范,則該對(duì)象滿足可迭代協(xié)議,也稱之為該對(duì)象是可以被迭代的。 可迭代協(xié)議的約束如下:
- 對(duì)象必須有一個(gè)知名符號(hào)屬性(Symbol.iterator)
- done:bool類型,是否已經(jīng)迭代完成
- 該屬性必須是一個(gè)無參的迭代器創(chuàng)建函數(shù)
案例如下:
//可迭代協(xié)議 var robj = { [Symbol.iterator]:function(){ var total = 3; var i = 1; return { next(){ var oop = { value:i>total?undefined:Math.random(), done:i>total } i++; return oop; } } } }; for (const element of robj) { console.log(element); }
什么是生成器
生成器:由構(gòu)造函數(shù)Generator創(chuàng)建的對(duì)象,該對(duì)象既是一個(gè)迭代器,同時(shí),又是一個(gè)可迭代對(duì)象(滿足可迭代協(xié)議的對(duì)象)
**注意:Generator構(gòu)造函數(shù),不提供給開發(fā)者使用,僅作為JS引擎內(nèi)部使用**
生成器函數(shù)(生成器創(chuàng)建函數(shù)):該函數(shù)用于創(chuàng)建一個(gè)生成器。
ES6新增了一個(gè)特殊的函數(shù),叫做生成器函數(shù),只要在函數(shù)名與function關(guān)鍵字之間加上一個(gè)*號(hào),則該函數(shù)會(huì)自動(dòng)返回一個(gè)生成器
生成器函數(shù)的特點(diǎn):
- 調(diào)用生成器函數(shù),會(huì)返回一個(gè)生成器,而不是執(zhí)行函數(shù)體(因?yàn)椋善骱瘮?shù)的函數(shù)體執(zhí)行,收到生成器控制)
- 每當(dāng)調(diào)用了生成器的next方法,生成器的函數(shù)體會(huì)從上一次yield的位置(或開始位置)運(yùn)行到下一個(gè)yield
- yield關(guān)鍵字只能在生成器內(nèi)部使用,不可以在普通函數(shù)內(nèi)部使用
- 它表示暫停,并返回一個(gè)當(dāng)前迭代的數(shù)據(jù)
- 如果沒有下一個(gè)yield,到了函數(shù)結(jié)束,則生成器的next方法得到的結(jié)果中的done為true
- yield關(guān)鍵字后面的表達(dá)式返回的數(shù)據(jù),會(huì)作為當(dāng)前迭代的數(shù)據(jù)
- 生成器函數(shù)的返回值會(huì)作最終的value的值 但是當(dāng)在進(jìn)行next時(shí) value是undefined
- 生成器在調(diào)用next的時(shí)候可以傳遞參數(shù),該參數(shù)會(huì)作為上一次yield整個(gè)表達(dá)式的返回結(jié)果
案列如下:
//生成器函數(shù) 調(diào)用該函數(shù)返回一個(gè)生成器 該生成器即使是一個(gè)迭代器 又是一個(gè)可迭代對(duì)象(滿足可迭代協(xié)議) function* createGenerator(){ console.log('函數(shù)體執(zhí)行 - 開始'); yield 1; //會(huì)作為本次迭代的value值 {value:1,done:false} console.log("函數(shù)體執(zhí)行 - 1") yield 2;//會(huì)作為本次迭代的value值 {value:2,done:false} console.log('函數(shù)體執(zhí)行 - 2'); yield 3;//會(huì)作為本次迭代的value值 {value:3,done:false} console.log("函數(shù)體執(zhí)行 - 3"); return "結(jié)束"//會(huì)作為本次迭代的value值 {value:"結(jié)束",done:true} } //掉用只會(huì)返回一個(gè)生成器 不會(huì)執(zhí)行函數(shù)體 var generator = createGenerator(); //當(dāng)調(diào)用next的時(shí)候會(huì)從開始位置到第一個(gè)yield處執(zhí)行 執(zhí)行到y(tǒng)ield位置就會(huì)卡住(不會(huì)繼續(xù)執(zhí)行), 等到下一次next的時(shí)候 //生成器的函數(shù)體會(huì)從上一次yield的位置(或開始位置)運(yùn)行到下一個(gè)yield console.log(generator.next); const iterator = generator[Symbol.iterator](); function* createArrayIterator(array){ for (let index = 0; index < array.length; index++) { const item = array[index]; console.log(`第${index}次迭代`); yield item; } } const arrayIterator = createArrayIterator([1,2,3,4,5,6]);
//生成器函數(shù) 調(diào)用該函數(shù)返回一個(gè)生成器 該生成器即使是一個(gè)迭代器 又是一個(gè)可迭代對(duì)象(滿足可迭代協(xié)議) function* createGenerator(){ console.log('函數(shù)體執(zhí)行 - 開始'); let result = yield 1; //會(huì)作為本次迭代的value值 {value:1,done:false} console.log("函數(shù)體執(zhí)行 - 1",result) result = yield 2;//會(huì)作為本次迭代的value值 {value:2,done:false} console.log('函數(shù)體執(zhí)行 - 2',result); result = yield 3;//會(huì)作為本次迭代的value值 {value:3,done:false} console.log("函數(shù)體執(zhí)行 - 3",result); return "結(jié)束"//會(huì)作為本次迭代的value值 {value:"結(jié)束",done:true} } let itereator = createGenerator(); let res = itereator.next(); function run(){ if(!res.done){ //如果在調(diào)用next的時(shí)候 給 next傳遞參數(shù) 該參數(shù)會(huì)作為 yield 整個(gè)表達(dá)式的值返回 //執(zhí)行步驟 //第一次調(diào)用就不care了 第一次調(diào)用碰到y(tǒng)ield 1; 就會(huì)卡住不會(huì)往下執(zhí)行 這個(gè)時(shí)候還不執(zhí)行賦值操作 //第二次調(diào)用next 傳遞上一次迭代的值作為參數(shù)傳遞 這個(gè)時(shí)候 就會(huì)從上一次 yield的位置 運(yùn)行到下一個(gè)yield (這個(gè)時(shí)候就會(huì)進(jìn)行賦值操作) //整個(gè)yield 表達(dá)式的返回值 就是給next函數(shù)傳遞的參數(shù) //依次類推 console.log(res); res = itereator.next("張三:"+Math.random()); run(); } } run();
var i = 0; function asyncData(){ return new Promise((resolve,reject)=>{ setTimeout(() => { i++; //3秒后完成 完成的數(shù)據(jù) resolve('完成'+i); }, 10000); }) } //調(diào)用next()方法時(shí)傳入的值會(huì)作為上一個(gè)yield表達(dá)式的返回值 //創(chuàng)建一個(gè)生成器函數(shù) 調(diào)用時(shí)返回一個(gè)生成器 function* task(){ console.log("開始獲取數(shù)據(jù)"); let data = yield asyncData(); console.log('獲取到的數(shù)據(jù)',data); data = yield asyncData(); console.log("又獲取到了數(shù)據(jù)",data); data = yield 1; console.log('又獲取到了數(shù)據(jù)',data); return '結(jié)束'; } //沒封裝之前的寫法 /*function run(createGenerator){ const generator = createGenerator(); let res = generator.next(); function next(){ if(!res.done){ console.log(res); const value = res.value; if(typeof value.then === 'function'){ value.then((data)=>{ res = generator.next(data); next(); }); }else{ res = generator.next(value); next(); } } } next(); }*/ //封裝后的寫法 function run(createGenerator){ const generator = createGenerator(); console.log(generator); next(); function next(nextValue){ const res = generator.next(nextValue); if(res.done){ console.log('生成器迭代結(jié)束'); return; } const value = res.value; if(typeof value.then === 'function'){ //如果返回的是promise 將promise完成時(shí)的數(shù)據(jù)作為參數(shù) //作為上一次yield表達(dá)式的返回值 value.then((data)=>{ return next(data) }) }else{ console.log('走這里了') //將上一次迭代獲取到的value的值作為參數(shù) 傳遞給上一次yield表達(dá)式的返回值 next(res.value); } } } run(task);
function* g2(){ console.log('g2函數(shù)體-運(yùn)行'); let res = yield 'g1'; console.log('g1運(yùn)行'); res = yield 'g2'; console.log('g2運(yùn)行') return 123; } function* createGenerator(){ console.log('函數(shù)體-開始') let res = yield 1; //1作為 本次迭代的值 {value:1,done:false} console.log('函數(shù)體-運(yùn)行1',res); res = yield* g2(); console.log('函數(shù)體-g2',res); res = yield 2; console.log('函數(shù)體-運(yùn)行2',res); res = yield 3; console.log('函數(shù)體-運(yùn)行3',res); return '結(jié)束' } var generator = createGenerator();

總結(jié):
生成器的核心價(jià)值在于其?延遲執(zhí)行與狀態(tài)保持能力?,適用于:
- 需要按需生成數(shù)據(jù)的迭代場(chǎng)景(如分頁(yè)、樹遍歷)
- 資源敏感型任務(wù)(如大文件處理、流式傳輸)
- 復(fù)雜流程控制(如多步驟交互、狀態(tài)機(jī))
- 盡管 async/await 更常用于異步編程,但生成器在定制化迭代器協(xié)議、性能優(yōu)化框架中仍不可替代

浙公網(wǎng)安備 33010602011771號(hào)