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