摘要:迭代器和生成器將迭代的概念直接帶入核心語言,并提供一種機制來自定義循環的行為。本文主要會介紹中新增的迭代器和生成器。屬性本身是函數,是當前數據結構默認的迭代器生成函數。
本文是 重溫基礎 系列文章的第十三篇。
今日感受:每次自我年終總結,都會有各種情緒和收獲。
系列目錄:
【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理)
【重溫基礎】1.語法和數據類型
【重溫基礎】2.流程控制和錯誤處理
【重溫基礎】3.循環和迭代
【重溫基礎】4.函數
【重溫基礎】5.表達式和運算符
【重溫基礎】6.數字
【重溫基礎】7.時間對象
【重溫基礎】8.字符串
【重溫基礎】9.正則表達式
【重溫基礎】10.數組
【重溫基礎】11.Map和Set對象
【重溫基礎】12.使用對象
本章節復習的是JS中的迭代器和生成器,常常用來處理集合。
前置知識:
JavaScrip已經提供多個迭代集合的方法,從簡單的for循環到map()和filter()。
迭代器和生成器將迭代的概念直接帶入核心語言,并提供一種機制來自定義for...of循環的行為。
本文會將知識點分為兩大部分,簡單介紹和詳細介紹:
簡單介紹,適合基礎入門會使用的目標;
詳細介紹,會更加深入的做介紹,適合理解原理;
當我們使用循環語句迭代數據時,需初始化一個變量來記錄每一次迭代在數據集合中的位置:
let a = ["aaa","bbb","ccc"]; for (let i = 0; i< a.length; i++){ console.log(a[i]); }
這邊的i就是我們用來記錄迭代位置的變量,但是在ES6開始,JavaScrip引入了迭代器這個特性,并且新的數組方法和新的集合類型(如Set集合與Map集合)都依賴迭代器的實現,這個新特性對于高效的數據處理而言是不可或缺的,在語言的其他特性中也都有迭代器的身影:新的for-of循環、展開運算符(...),甚至連異步編程都可以使用迭代器。
本文主要會介紹ES6中新增的迭代器(Iterator)和生成器(Generator)。
2. 迭代器(簡單介紹)迭代器是一種特殊對象,它具有一些專門為迭代過程設計的專有接口,所有的迭代器對象都有一個next()方法,每次調用都會返回一個結果對象。
這個結果對象,有兩個屬性:
value: 表示下一個將要返回的值。
done: 一個布爾值,若沒有更多可返回的數據時,值為true,否則false。
如果最后一個值返回后,再調用next(),則返回的對象的done值為true,而value值如果沒有值的話,返回的為undefined。
ES5實現一個迭代器:
function myIterator(list){ var i = 0; return { next: function(){ var done = i >= list.length; var value = !done ? list[i++] : undefined; return { done : done, value : value } } } } var iterator = myIterator([1,2,3]); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"
從上面代碼可以看出,ES5的實現還是比較麻煩,而ES6新增的生成器,可以使得創建迭代器對象的過程更加簡單。
3. 生成器(簡單介紹)生成器是一種返回迭代器的函數,通過function關鍵字后的星號(*)來表示,函數中會用到新的關鍵字yield。星號可以緊挨著function關鍵字,也可以在中間添加一個空格。
function *myIterator(){ yield 1; yield 2; yield 3; } let iterator = myIterator(); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"
生成器函數最有趣的部分是,每當執行完一條yield語句后函數就會自動停止執行,比如上面代碼,當yield 1;執行完后,便不會執行任何語句,而是等到再調用迭代器的next()方法才會執行下一個語句,即yield 2;.
使用yield關鍵字可以返回任何值和表達式,因為可以通過生成器函數批量給迭代器添加元素:
function *myIterator(list){ for(let i = 0; i< list.length ; i ++){ yield list[i]; } } var iterator = myIterator([1,2,3]); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"
生成器的適用返回很廣,可以將它用于所有支持函數使用的地方。
4. 迭代器(詳細介紹) 4.1 Iterator迭代器概念Iterator是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就可以完成迭代操作(即依次處理該數據結構的所有成員)。
Iterator三個作用:
為各種數據結構,提供一個統一的、簡便的訪問接口;
使得數據結構的成員能夠按某種次序排列;
Iterator 接口主要供ES6新增的for...of消費;
4.2 Iterator迭代過程創建一個指針對象,指向當前數據結構的起始位置。也就是說,迭代器對象本質上,就是一個指針對象。
第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。
第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
不斷調用指針對象的next方法,直到它指向數據結構的結束位置。
每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含value和done兩個屬性的對象。
value屬性是當前成員的值;
done屬性是一個布爾值,表示迭代是否結束;
模擬next方法返回值:
let f = function (arr){ var nextIndex = 0; return { next:function(){ return nextIndex < arr.length ? {value: arr[nextIndex++], done: false}: {value: undefined, done: true} } } } let a = f(["a", "b"]); a.next(); // { value: "a", done: false } a.next(); // { value: "b", done: false } a.next(); // { value: undefined, done: true }4.3 默認Iterator接口
若數據可迭代,即一種數據部署了Iterator接口。
ES6中默認的Iterator接口部署在數據結構的Symbol.iterator屬性,即如果一個數據結構具有Symbol.iterator屬性,就可以認為是可迭代。
Symbol.iterator屬性本身是函數,是當前數據結構默認的迭代器生成函數。執行這個函數,就會返回一個迭代器。至于屬性名Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預定義好的、類型為 Symbol 的特殊值,所以要放在方括號內(參見《Symbol》一章)。
原生具有Iterator接口的數據結構有:
Array
Map
Set
String
TypedArray
函數的 arguments 對象
NodeList 對象
4.4 Iterator使用場景(1)解構賦值
對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator方法。
let a = new Set().add("a").add("b").add("c"); let [x, y] = a; // x = "a" y = "b" let [a1, ...a2] = a; // a1 = "a" a2 = ["b","c"]
(2)擴展運算符
擴展運算符(...)也會調用默認的 Iterator 接口。
let a = "hello"; [...a]; // ["h","e","l","l","o"] let a = ["b", "c"]; ["a", ...a, "d"]; // ["a", "b", "c", "d"]
(2)yield*
yield*后面跟的是一個可迭代的結構,它會調用該結構的迭代器接口。
let a = function*(){ yield 1; yield* [2,3,4]; yield 5; } let b = a(); b.next() // { value: 1, done: false } b.next() // { value: 2, done: false } b.next() // { value: 3, done: false } b.next() // { value: 4, done: false } b.next() // { value: 5, done: false } b.next() // { value: undefined, done: true }
(4)其他場合
由于數組的迭代會調用迭代器接口,所以任何接受數組作為參數的場合,其實都調用了迭代器接口。下面是一些例子。
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([["a",1],["b",2]]))
Promise.all()
Promise.race()
4.5 for...of循環只要數據結構部署了Symbol.iterator屬性,即具有 iterator 接口,可以用for...of循環迭代它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterato方法。
使用場景:
for...of可以使用在數組,Set和Map結構,類數組對象,Genetator對象和字符串。
數組
for...of循環可以代替數組實例的forEach方法。
let a = ["a", "b", "c"]; for (let k of a){console.log(k)}; // a b c a.forEach((ele, index)=>{ console.log(ele); // a b c console.log(index); // 0 1 2 })
與for...in對比,for...in只能獲取對象鍵名,不能直接獲取鍵值,而for...of允許直接獲取鍵值。
let a = ["a", "b", "c"]; for (let k of a){console.log(k)}; // a b c for (let k in a){console.log(k)}; // 0 1 2
Set和Map
可以使用數組作為變量,如for (let [k,v] of b){...}。
let a = new Set(["a", "b", "c"]); for (let k of a){console.log(k)}; // a b c let b = new Map(); b.set("name","leo"); b.set("age", 18); b.set("aaa","bbb"); for (let [k,v] of b){console.log(k + ":" + v)}; // name:leo // age:18 // aaa:bbb
類數組對象
// 字符串 let a = "hello"; for (let k of a ){console.log(k)}; // h e l l o // DOM NodeList對象 let b = document.querySelectorAll("p"); for (let k of b ){ k.classList.add("test"); } // arguments對象 function f(){ for (let k of arguments){ console.log(k); } } f("a","b"); // a b
對象
普通對象不能直接使用for...of會報錯,要部署Iterator才能使用。
let a = {a:"aa",b:"bb",c:"cc"}; for (let k in a){console.log(k)}; // a b c for (let k of a){console>log(k)}; // TypeError4.6 跳出for...of
使用break來實現。
for (let k of a){ if(k>100) break; console.log(k); }5. 生成器(詳細介紹) 5.1 基本概念
Generator生成器函數是一種異步編程解決方案。
原理:
執行Genenrator函數會返回一個遍歷器對象,依次遍歷Generator函數內部的每一個狀態。
Generator函數是一個普通函數,有以下兩個特征:
function關鍵字與函數名之間有個星號;
函數體內使用yield表達式,定義不同狀態;
通過調用next方法,將指針移向下一個狀態,直到遇到下一個yield表達式(或return語句)為止。簡單理解,Generator函數分段執行,yield表達式是暫停執行的標記,而next恢復執行。
function * f (){ yield "hi"; yield "leo"; return "ending"; } let a = f(); a.next(); // {value: "hi", done : false} a.next(); // {value: "leo", done : false} a.next(); // {value: "ending", done : true} a.next(); // {value: undefined, done : false}5.2 yield表達式
yield表達式是暫停標志,遍歷器對象的next方法的運行邏輯如下:
遇到yield就暫停執行,將這個yield后的表達式的值,作為返回對象的value屬性值。
下次調用next往下執行,直到遇到下一個yield。
直到函數結束或者return為止,并返回return語句后面表達式的值,作為返回對象的value屬性值。
如果該函數沒有return語句,則返回對象的value為undefined 。
注意:
yield只能用在Generator函數里使用,其他地方使用會報錯。
// 錯誤1 (function(){ yiled 1; // SyntaxError: Unexpected number })() // 錯誤2 forEach參數是個普通函數 let a = [1, [[2, 3], 4], [5, 6]]; let f = function * (i){ i.forEach(function(m){ if(typeof m !== "number"){ yield * f (m); }else{ yield m; } }) } for (let k of f(a)){ console.log(k) }
yield表達式如果用于另一個表達式之中,必須放在圓括號內。
function * a (){ console.log("a" + yield); // SyntaxErro console.log("a" + yield 123); // SyntaxErro console.log("a" + (yield)); // ok console.log("a" + (yield 123)); // ok }
yield表達式用做函數參數或放在表達式右邊,可以不加括號。
function * a (){ f(yield "a", yield "b"); // ok lei i = yield; // ok }5.3 next方法
yield本身沒有返回值,或者是總返回undefined,next方法可帶一個參數,作為上一個yield表達式的返回值。
function * f (){ for (let k = 0; true; k++){ let a = yield k; if(a){k = -1}; } } let g =f(); g.next(); // {value: 0, done: false} g.next(); // {value: 1, done: false} g.next(true); // {value: 0, done: false}
這一特點,可以讓Generator函數開始執行之后,可以從外部向內部注入不同值,從而調整函數行為。
function * f(x){ let y = 2 * (yield (x+1)); let z = yield (y/3); return (x + y + z); } let a = f(5); a.next(); // {value : 6 ,done : false} a.next(); // {value : NaN ,done : false} a.next(); // {value : NaN ,done : true} // NaN因為yeild返回的是對象 和數字計算會NaN let b = f(5); b.next(); // {value : 6 ,done : false} b.next(12); // {value : 8 ,done : false} b.next(13); // {value : 42 ,done : false} // x 5 y 24 z 135.4 for...of循環
for...of循環會自動遍歷,不用調用next方法,需要注意的是,for...of遇到next返回值的done屬性為true就會終止,return返回的不包括在for...of循環中。
function * f(){ yield 1; yield 2; yield 3; yield 4; return 5; } for (let k of f()){ console.log(k); } // 1 2 3 4 沒有 55.5 Generator.prototype.throw()
throw方法用來向函數外拋出錯誤,并且在Generator函數體內捕獲。
let f = function * (){ try { yield } catch (e) { console.log("內部捕獲", e) } } let a = f(); a.next(); try{ a.throw("a"); a.throw("b"); }catch(e){ console.log("外部捕獲",e); } // 內部捕獲 a // 外部捕獲 b5.6 Generator.prototype.return()
return方法用來返回給定的值,并結束遍歷Generator函數,如果return方法沒有參數,則返回值的value屬性為undefined。
function * f(){ yield 1; yield 2; yield 3; } let g = f(); g.next(); // {value : 1, done : false} g.return("leo"); // {value : "leo", done " true} g.next(); // {value : undefined, done : true}5.7 next()/throw()/return()共同點
相同點就是都是用來恢復Generator函數的執行,并且使用不同語句替換yield表達式。
next()將yield表達式替換成一個值。
let f = function * (x,y){ let r = yield x + y; return r; } let g = f(1, 2); g.next(); // {value : 3, done : false} g.next(1); // {value : 1, done : true} // 相當于把 let r = yield x + y; // 替換成 let r = 1;
throw()將yield表達式替換成一個throw語句。
g.throw(new Error("報錯")); // Uncaught Error:報錯 // 相當于將 let r = yield x + y // 替換成 let r = throw(new Error("報錯"));
next()將yield表達式替換成一個return語句。
g.return(2); // {value: 2, done: true} // 相當于將 let r = yield x + y // 替換成 let r = return 2;5.8 yield* 表達式
用于在一個Generator中執行另一個Generator函數,如果沒有使用yield*會沒有效果。
function * a(){ yield 1; yield 2; } function * b(){ yield 3; yield * a(); yield 4; } // 等同于 function * b(){ yield 3; yield 1; yield 2; yield 4; } for(let k of b()){console.log(k)} // 3 // 1 // 2 // 45.9 應用場景
控制流管理
解決回調地獄:
// 使用前 f1(function(v1){ f2(function(v2){ f3(function(v3){ // ... more and more }) }) }) // 使用Promise Promise.resolve(f1) .then(f2) .then(f3) .then(function(v4){ // ... },function (err){ // ... }).done(); // 使用Generator function * f (v1){ try{ let v2 = yield f1(v1); let v3 = yield f1(v2); let v4 = yield f1(v3); // ... }catch(err){ // console.log(err) } } function g (task){ let obj = task.next(task.value); // 如果Generator函數未結束,就繼續調用 if(!obj.done){ task.value = obj.value; g(task); } } g( f(initValue) );
異步編程的使用
在真實的異步任務封裝的情況:
let fetch = require("node-fetch"); function * f(){ let url = "http://www.baidu.com"; let res = yield fetch(url); console.log(res.bio); } // 執行該函數 let g = f(); let result = g.next(); // 由于fetch返回的是Promise對象,所以用then result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); })參考資料
1.MDN 迭代器和生成器
2.ES6中的迭代器(Iterator)和生成器(Generator)
本部分內容到這結束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
JS小冊 | js.pingan8787.com |
歡迎關注微信公眾號【前端自習課】每天早晨,與您一起學習一篇優秀的前端技術博文 .
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100747.html
摘要:本文是重溫基礎系列文章的第十四篇。元,是指程序本身。有理解不到位,還請指點,具體詳細的介紹,可以查看維基百科元編程。攔截,返回一個布爾值。 本文是 重溫基礎 系列文章的第十四篇。 這是第一個基礎系列的最后一篇,后面會開始復習一些中級的知識了,歡迎持續關注呀! 接下來會統一整理到我的【Cute-JavaScript】的JavaScript基礎系列中。 今日感受:獨樂樂不如眾樂樂...
摘要:構造函數通常首字母大寫,用于區分普通函數。這種關系常被稱為原型鏈,它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。中所有的對象,都有一個屬性,指向實例對象的構造函數原型由于是個非標準屬性,因此只有和兩個瀏覽器支持,標準方法是。 從這篇文章開始,復習 MDN 中級教程 的內容了,在初級教程中,我和大家分享了一些比較簡單基礎的知識點,并放在我的 【Cute-JavaScript】系...
摘要:本文是重溫基礎系列文章的第十篇。返回一個由回調函數的返回值組成的新數組。返回一個數組迭代器對象,該迭代器會包含所有數組元素的鍵值對。回調函數接收三個參數,當前值當前位置和原數組。 本文是 重溫基礎 系列文章的第十篇。 今日感受:平安夜,多棒。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎】1.語法和數據類型 【重溫基礎】2.流程控制和錯誤...
摘要:本文是重溫基礎系列文章的第三篇,今天想起鬼腳七的一句話人不一定自由,但思想一定是自由的。系列目錄復習資料資料整理個人整理重溫基礎語法和數據類型重溫基礎流程控制和錯誤處理本章節復習的是中的循環語句,讓我們能更快速且簡單的完成一些需求。 本文是 重溫基礎 系列文章的第三篇,今天想起鬼腳七的一句話:人不一定自由,但思想一定是自由的。思想沒有對和錯,也沒有高和低,只有不同。了解一個人可以去了解...
摘要:來說說迭代器和生成器,還有可迭代對象和生成器表達式。有點繞是不是,其實,一般只要知道可迭代對象以及它是如何實現的就行了,中常常用生成器來代替迭代器,可以說,生成器就是迭代器。 來說說迭代器和生成器,還有可迭代對象和生成器表達式。 之前簡單的提到過,一個對象是可迭代的可以理解為能夠使用for循環。這樣說其實不太準確,某個對象可迭代是因為它內部實現了$__iter__$這個特殊方法。比如在...
閱讀 2801·2023-04-25 22:51
閱讀 2026·2021-10-11 10:58
閱讀 3308·2019-08-30 10:49
閱讀 1870·2019-08-29 17:09
閱讀 3136·2019-08-29 10:55
閱讀 839·2019-08-26 10:34
閱讀 3467·2019-08-23 17:54
閱讀 980·2019-08-23 16:06