摘要:當(dāng)這個(gè)迭代器的方法被首次后續(xù)調(diào)用時(shí),其內(nèi)的語句會(huì)執(zhí)行到第一個(gè)后續(xù)出現(xiàn)的位置為止,后緊跟迭代器要返回的值。在這個(gè)回調(diào)函數(shù)里,我們使用第一個(gè)請(qǐng)求返回的,再次發(fā)起一個(gè)請(qǐng)求。
寫在前面
本文首發(fā)于公眾號(hào):符合預(yù)期的CoyPan
后續(xù)文章:【JS基礎(chǔ)】從JavaScript中的for...of說起(下) - async和await
先來看一段很常見的代碼:
const arr = [1, 2, 3]; for(const i of arr) { console.log(i); // 1,2,3 }
上面的代碼中,用for...of來遍歷一個(gè)數(shù)組。其實(shí)這里說遍歷不太準(zhǔn)確,應(yīng)該是說:for...of語句在可迭代對(duì)象(包括 Array,Map,Set,String,TypedArray,arguments 對(duì)象等等)上創(chuàng)建一個(gè)迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個(gè)不同屬性的值執(zhí)行語句。
iteratorECMAScript 2015規(guī)定了關(guān)于迭代的協(xié)議,這些協(xié)議可以被任何遵循某些約定的對(duì)象來實(shí)現(xiàn)。如果一個(gè)js對(duì)象想要能被迭代,那么這個(gè)對(duì)象或者其原型鏈對(duì)象必須要有一個(gè)Symbol.iterator的屬性,這個(gè)屬性的值是一個(gè)無參函數(shù),返回一個(gè)符合迭代器協(xié)議的對(duì)象。這樣的對(duì)象被稱為符合【可迭代協(xié)議】。
typeof Array.prototype[Symbol.iterator] === "function"; // true typeof Array.prototype[Symbol.iterator]() === "object"; // true
數(shù)組之所以可以被for...of迭代,就是因?yàn)閿?shù)組的原型對(duì)象上擁有Symbol.iterator屬性,這個(gè)屬性返回了一個(gè)符合【迭代器協(xié)議】的對(duì)象。
一個(gè)符合【迭代器協(xié)議】的對(duì)象必須要有一個(gè)next屬性,next屬性也是一個(gè)無參函數(shù),返回一個(gè)對(duì)象,這個(gè)對(duì)象至少需要有兩個(gè)屬性:done, value, 大概長(zhǎng)成下面這樣:
{ next: function(){ return { done: boolean, // 布爾值,表示迭代是否完成,如果沒有這個(gè)屬性,則默認(rèn)為false value: any // 迭代器返回的任何javascript值。如果迭代已經(jīng)完成,value屬性可以被省略 } } }
依舊來看一下數(shù)組:
typeof Array.prototype[Symbol.iterator]().next === "function" // true Array.prototype[Symbol.iterator]().next() // {value: undefined, done: true} const iteratorObj = [1,2,3][Symbol.iterator](); iteratorObj.next(); // { value: 1, done: false } iteratorObj.next(); // { value: 2, done: false } iteratorObj.next(); // { value: 3, done: false } iteratorObj.next(); // { value: undefined, done: true }
我們自己來實(shí)現(xiàn)一個(gè)可以迭代的對(duì)象。
const myIterator = { [Symbol.iterator]: function() { return { i: 0, next: function() { if(this.i < 2) { return { value: this.i++ , done: false }; } else { return { done: true }; } } } } } for(const item of myIterator) { console.log(item); } // 0 // 1
不光for...of會(huì)使用對(duì)象的iterator接口,下面這些用法也會(huì)默認(rèn)使用對(duì)象的iteretor接口。
(1) 解構(gòu)賦值 (2) 擴(kuò)展運(yùn)算符 (3) yield*
generator表示一個(gè)生成器對(duì)象。這個(gè)對(duì)象符合【可迭代協(xié)議】和【迭代器協(xié)議】,是由生成器函數(shù)(generator function)返回的。
什么是生成器函數(shù)呢?MDN上的描述如下:
生成器函數(shù)在執(zhí)行時(shí)能暫停,后面又能從暫停處繼續(xù)執(zhí)行。
調(diào)用一個(gè)生成器函數(shù)并不會(huì)馬上執(zhí)行它里面的語句,而是返回一個(gè)這個(gè)生成器的 迭代器 (iterator )對(duì)象。當(dāng)這個(gè)迭代器的 next() 方法被首次(后續(xù))調(diào)用時(shí),其內(nèi)的語句會(huì)執(zhí)行到第一個(gè)(后續(xù))出現(xiàn)yield的位置為止,yield 后緊跟迭代器要返回的值。或者如果用的是 yield*(多了個(gè)星號(hào)),則表示將執(zhí)行權(quán)移交給另一個(gè)生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行)。next()方法返回一個(gè)對(duì)象,這個(gè)對(duì)象包含兩個(gè)屬性:value 和 done,value 屬性表示本次 yield 表達(dá)式的返回值,done 屬性為布爾類型,表示生成器后續(xù)是否還有 yield 語句,即生成器函數(shù)是否已經(jīng)執(zhí)行完畢并返回。
看下面的例子:
function* gen() { // gen一個(gè)生成器函數(shù) yield 1; yield 2; yield 3; } const g = gen(); // g是一個(gè)生成器對(duì)象,是可迭代的 Object.prototype.toString.call(g) === "[object Generator]" // true g.next(); // { value: 1, done: false } g.next(); // { value: 2, done: false } g.next(); // { value: 3, done: false } g.next(); // { value: undefined, done: true }
因?yàn)樯善鲗?duì)象符合可迭代協(xié)議和迭代器協(xié)議,我們可以用for...of來進(jìn)行迭代。for…of會(huì)拿到迭代器返回值的value,也就是說,在迭代generator時(shí),for…of拿到的是yield后面緊跟的那個(gè)值。
function* gen2() { yield "a"; yield "b"; yield "c"; } const g2 = gen2(); for(const i of g2) { console.log(i); } // a // b // c生成器函數(shù)的"嵌套"
function *gen1(i) { yield i+1; yield i+2; yield *gen2(i+2); // 將執(zhí)行權(quán)移交給gen2 yield i+3; } function *gen2(i) { yield i*2; } const g = gen1(0); g.next(); // { value: 1, done: false } g.next(); // { value: 2, done: false } g.next(); // { value: 4, done: false } g.next(); // { value: 3, done: false } g.next(); // { value: undefined, done: true }生成器函數(shù)里的參數(shù)傳遞
function* gen3() { let a = yield 1; console.log("a:", a); let b = yield a + 1; yield b + 10; } const g = gen3(); g.next(); // { value: 1, done: false } 這個(gè)時(shí)候,代碼執(zhí)行到gen3里第一行等號(hào)右邊 g.next(100); // a: 100 , { value: 101, done: false }。代碼執(zhí)行第一行等號(hào)的左邊,我們傳入了100,這個(gè)100會(huì)作為a的值,接著執(zhí)行第二行的log, 然后執(zhí)行到第三行等號(hào)的右邊。 g.next(); // { value: NaN, done: false }。代碼執(zhí)行第三行等號(hào)的左半部分,由于我們沒有傳值,b就是undefined, undefined + 10 就是NaN了。 g.next(); // { value: undefined, done: true }
如果我們使用for...of來遍歷上述的生成器對(duì)象,由于for…of拿到的是迭代器返回值的value,所以會(huì)得到以下的結(jié)果:
function* gen4() { let a = yield 1; let b = yield a + 1; yield b + 10; } const g4 = gen4(); for(const i of g4) { console.log(i); } // 1 // NaN // NaN
下面是一個(gè)使用generator和for...of輸出斐波拉契數(shù)列的經(jīng)典例子:
function* fibonacci() { let [prev, curr] = [0, 1]; while(1){ [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { if (n > 100) { break } console.log(n); }
稍微總結(jié)一下,generator給了我們控制暫停代碼執(zhí)行的能力,我們可以自己來控制代碼執(zhí)行。那是否可以用generator來寫異步操作呢 ?
iterator,generator與異步操作一個(gè)很常見的場(chǎng)景: 頁面發(fā)起一個(gè)ajax請(qǐng)求,請(qǐng)求返回后,執(zhí)行一個(gè)回調(diào)函數(shù)。在這個(gè)回調(diào)函數(shù)里,我們使用第一個(gè)請(qǐng)求返回的url,再次發(fā)起一個(gè)ajax請(qǐng)求。(這里先不考慮使用Promise)
// 我們先定義發(fā)起ajax的函數(shù),這里用setTimeout模擬一下 function myAjax(url, cb) { setTimeout(function(){ const data = "ajax返回了"; cb && cb(resData); }, 1000); } // 一般情況下,要實(shí)現(xiàn)需求,一般可以這樣寫 myAjax("https://xxxx", function(url){ myAjax(url, function(data){ console.log(data); }); });
我們嘗試用generator的寫法來實(shí)現(xiàn)上面的需求.
// 先把a(bǔ)jax函數(shù)改造一下, 把url提出來作為一個(gè)參數(shù),然后返回一個(gè)只接受回調(diào)函數(shù)作為參數(shù)的newAjax函數(shù) // 這種只接受回調(diào)函數(shù)作為參數(shù)的函數(shù)被稱為thunk函數(shù)。 function thunkAjax(url) { return function newAjax(cb){ myAjax(url, cb); } } // 我們定義一個(gè)generator function function* gen() { const res1 = yield thunkAjax("http://url1.xxxx"); console.log("res1", res1); const res2 = yield thunkAjax(res1); console.log("res2", res2); } // 實(shí)現(xiàn)需求。 const g = gen(); const y1 = g.next(); // y1 = { value: ?, done: false }. 這里的value,就是一個(gè)newAjax函數(shù),接受一個(gè)回調(diào)函數(shù)作為參數(shù) y1.value(url => { // 執(zhí)行y1.value這個(gè)函數(shù),并且傳入了一個(gè)回調(diào)函數(shù)作為參數(shù) const y2 = g.next(url); // 傳入url作為參數(shù),最終會(huì)賦值給上面代碼中的res1。 y2 = { value: f, done: false } y2.value(data => { g.next(data); // 傳入data作為參數(shù),會(huì)賦值給上面代碼中的res2。至此,迭代也完成了。 }); }); // 最終的輸出為: // 1s后輸出:res1 ajax返回了 // 1s后輸出:res2 ajax返回了
在上面的代碼中,我們使用generator實(shí)現(xiàn)了依次執(zhí)行兩個(gè)異步操作。上面的代碼看起來是比較復(fù)雜的。整個(gè)的邏輯在gen這個(gè)generator function里,然后我們手動(dòng)執(zhí)行完了g這個(gè)generator。按照上面的代碼,如果我們想再加入一個(gè)ajax請(qǐng)求,需要先修改generator function,然后修改generator的執(zhí)行邏輯。我們來實(shí)現(xiàn)一個(gè)自動(dòng)的流程,只需要定義好generator,讓它自動(dòng)執(zhí)行。
function autoRun(generatorFun) { const generator = generatorFun(); const run = function(data){ const res = generator.next(data); if(res.done) { return; } return res.value(run); } run(); }
這下,我們就可以專注于generator function的邏輯了。
function* gen() { const res1 = yield thunkAjax("http://url1.xxxx"); console.log("res1", res1); const res2 = yield thunkAjax(res1); console.log("res2", res2); const res3 = yield thunkAjax(res2); console.log("res3", res3); ... } // 自動(dòng)執(zhí)行 autoRun(gen);
著名的co就是一個(gè)自動(dòng)執(zhí)行g(shù)enerator的庫。
上面的代碼中,gen函數(shù)體內(nèi),我們用同步代碼的寫法,實(shí)現(xiàn)了異步操作。可以看到,用gererator來執(zhí)行異步操作,在代碼可讀性、可擴(kuò)展性上面,是很有優(yōu)勢(shì)的。如今,我們或許會(huì)像下面這樣來寫上面的邏輯:
const fn = async function(){ const res1 = await func1; console.log(res1); const res2 = await func2; console.log(res2); ... } fn();寫在后面
本文從for..of入手,梳理了javascript中的兩個(gè)重要概念:iterator和generator。并且介紹了兩者在異步操作中的應(yīng)用。符合預(yù)期。下一篇文章中,將介紹async、await,任務(wù)隊(duì)列的相關(guān)內(nèi)容,希望能對(duì)js中的異步代碼及其寫法有一個(gè)更深入,全面的認(rèn)識(shí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/103309.html
摘要:基礎(chǔ)從中的說起上和在異步操作中使用和是一件比較費(fèi)勁的事情,而給我們提供了更為簡(jiǎn)便的和。表達(dá)式會(huì)暫停當(dāng)前的執(zhí)行,等待處理完成。若正常處理,其回調(diào)的函數(shù)參數(shù)作為表達(dá)式的值,繼續(xù)執(zhí)行。若處理異常,表達(dá)式會(huì)把的異常原因拋出。 寫在前面 本文首發(fā)于公眾號(hào):【符合預(yù)期的CoyPan】 在上一篇文章中,梳理了javascript中的兩個(gè)重要概念:iterator和generator,并且介紹了兩者在...
摘要:引用自可迭代對(duì)象和迭代器不以規(guī)矩,不成方圓為了使某個(gè)對(duì)象成為可迭代對(duì)象象,它必須實(shí)現(xiàn)方法,也就是說,它得有一個(gè)是的屬性。的遍歷,絕對(duì)應(yīng)該用。 pseudo 英 [sju:d??] 美 [su:do?]adj.假的,虛偽的n.[口]假冒的人,偽君子 pseudo-array 英 [sju:d???re?] 美 [sju:d???re?][計(jì)] 偽數(shù)組 jQuery 對(duì)象是偽數(shù)組 兩個(gè)...
摘要:不幸的是,迭代器不能用來表示這樣的數(shù)據(jù)源。即使是的迭代器也是不夠的,因?yàn)樗氖钱惒降模堑餍枰酱_定狀態(tài)。異步迭代器一個(gè)異步迭代器就像一個(gè)迭代器,除了它的方法返回一個(gè)的。 ES2018 新特性 異步迭代器(本文) 正則表達(dá)式反向(lookbehind)斷言 正則表達(dá)式 Unicode 轉(zhuǎn)義 非轉(zhuǎn)義序列的模板字符串 正則表達(dá)式 s/dotAll 模式 正則表達(dá)式命名捕獲組 對(duì)...
摘要:我關(guān)注的賀老賀師俊前輩最近發(fā)表個(gè)這樣一條微博雖然這條微博沒有引起大范圍的關(guān)注和討論,但是作為新人,我陷入了思考。通過賀老的微博,對(duì)一個(gè)問題進(jìn)行探究,最終找到核心成員的一文,進(jìn)行參考并翻譯。 我關(guān)注的賀老—賀師俊前輩@johnhax 最近發(fā)表個(gè)這樣一條微博: showImg(https://segmentfault.com/img/remote/1460000010452807); 雖然...
摘要:的精髓在于,用維護(hù)狀態(tài)傳遞狀態(tài)的方式使得回調(diào)函數(shù)能夠及時(shí)調(diào)用,比傳遞要簡(jiǎn)單靈活的其他方法用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù),等同于部分和的區(qū)別在發(fā)生異常,在中捕獲不到能夠捕獲異常。 ES6是個(gè)啥 ECMAScript是國(guó)際通過的標(biāo)準(zhǔn)化腳本語言JavaScript由ES,BOM,DOM組成ES是JavaScript的語言規(guī)范,同時(shí)JavaScript是ES的實(shí)現(xiàn)和擴(kuò)展6就是JavaScript...
閱讀 1201·2021-11-15 18:00
閱讀 1797·2021-10-08 10:15
閱讀 763·2021-09-04 16:48
閱讀 2387·2021-09-04 16:48
閱讀 1321·2019-08-29 18:40
閱讀 974·2019-08-29 13:08
閱讀 2994·2019-08-26 14:06
閱讀 1118·2019-08-26 13:35