javascript
批量生成数组_JavaScript【重温基础】13.迭代器和生成器
本文是 重溫基礎 系列文章的第十三篇。
今日感受:每次自我年終總結,都會有各種情緒和收獲。
本章節復習的是JS中的迭代器和生成器,常常用來處理集合。
前置知識:
JavaScrip已經提供多個迭代集合的方法,從簡單的for循環到map()和filter()。
迭代器和生成器將迭代的概念直接帶入核心語言,并提供一種機制來自定義for...of循環的行為。
本文會將知識點分為兩大部分,簡單介紹和詳細介紹:
簡單介紹,適合基礎入門會使用的目標;
詳細介紹,會更加深入的做介紹,適合理解原理;
1. 概述
當我們使用循環語句迭代數據時,需初始化一個變量來記錄每一次迭代在數據集合中的位置:
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關鍵字可以返回任何值和表達式,因為可以通過生成器函數批量給迭代器添加元素:
生成器的適用返回很廣,可以將它用于所有支持函數使用的地方。
4. 迭代器(詳細介紹)
4.1 Iterator迭代器概念
Iterator是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就可以完成迭代操作(即依次處理該數據結構的所有成員)。Iterator三個作用: 為各種數據結構,提供一個統一的、簡便的訪問接口;
使得數據結構的成員能夠按某種次序排列;
* Iterator 接口主要供ES6新增的for...of消費;
4.2 Iterator迭代過程
每一次調用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方法。
- (2)擴展運算符
擴展運算符(...)也會調用默認的 Iterator 接口。
- (2)yield*yield*后面跟的是一個可迭代的結構,它會調用該結構的迭代器接口。
- (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方法。
與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){...}。
- 類數組對象
- 對象
普通對象不能直接使用for...of會報錯,要部署Iterator才能使用。
4.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方法的運行邏輯如下:
1. 遇到yield就暫停執行,將這個yield后的表達式的值,作為返回對象的value屬性值。
2. 下次調用next往下執行,直到遇到下一個yield。
3. 直到函數結束或者return為止,并返回return語句后面表達式的值,作為返回對象的value屬性值。
4. 如果該函數沒有return語句,則返回對象的value為undefined 。
注意:
* yield只能用在Generator函數里使用,其他地方使用會報錯。
- yield表達式如果用于另一個表達式之中,必須放在圓括號內。
- yield表達式用做函數參數或放在表達式右邊,可以不加括號。
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返回的是對象 和數字計算會NaNlet 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表達式替換成一個值。
- throw()將yield表達式替換成一個throw語句。
- next()將yield表達式替換成一個return語句。
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 應用場景
解決回調地獄:
參考資料
1.MDN 迭代器和生成器
2.ES6中的迭代器(Iterator)和生成器(Generator)
本部分內容到這結束
歡迎關注我的微信公眾號【前端自習課】
總結
以上是生活随笔為你收集整理的批量生成数组_JavaScript【重温基础】13.迭代器和生成器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全局对象_C++全局变量初始化
- 下一篇: gradle idea java ssm