本文是 重溫基礎 系列文章的第十三篇。
今日感覺:每次自我年終總結,都會有各類情緒和收穫。html
系列目錄:前端
本章節複習的是JS中的迭代器和生成器,經常用來處理集合。 node
前置知識:
JavaScrip已經提供多個迭代集合的方法,從簡單的for
循環到map()
和filter()
。
迭代器和生成器將迭代的概念直接帶入核心語言,並提供一種機制來自定義for...of
循環的行爲。 git
本文會將知識點分爲兩大部分,簡單介紹和詳細介紹:
簡單介紹,適合基礎入門會使用的目標;
詳細介紹,會更加深刻的作介紹,適合理解原理;github
當咱們使用循環語句迭代數據時,需初始化一個變量來記錄每一次迭代在數據集合中的位置:正則表達式
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)。json
迭代器是一種特殊對象,它具備一些專門爲迭代過程設計的專有接口,全部的迭代器對象都有一個next()
方法,每次調用都會返回一個結果對象。
這個結果對象,有兩個屬性:segmentfault
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新增的生成器,可使得建立迭代器對象的過程更加簡單。
生成器是一種返回迭代器的函數,經過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}"
生成器的適用返回很廣,能夠將它用於全部支持函數使用的地方。
Iterator是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成迭代操做(即依次處理該數據結構的全部成員)。
Iterator三個做用:
for...of
消費;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 }
若數據可迭代,即一種數據部署了Iterator接口。
ES6中默認的Iterator接口部署在數據結構的Symbol.iterator
屬性,即若是一個數據結構具備Symbol.iterator
屬性,就能夠認爲是可迭代。 Symbol.iterator
屬性自己是函數,是當前數據結構默認的迭代器生成函數。執行這個函數,就會返回一個迭代器。至於屬性名Symbol.iterator
,它是一個表達式,返回Symbol
對象的iterator
屬性,這是一個預約義好的、類型爲 Symbol 的特殊值,因此要放在方括號內(參見《Symbol》一章)。
原生具備Iterator接口的數據結構有:
對數組和 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']
擴展運算符(...
)也會調用默認的 Iterator 接口。
let a = 'hello'; [...a]; // ['h','e','l','l','o'] let a = ['b', 'c']; ['a', ...a, 'd']; // ['a', 'b', 'c', 'd']
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 }
因爲數組的迭代會調用迭代器接口,因此任何接受數組做爲參數的場合,其實都調用了迭代器接口。下面是一些例子。
new Map([['a',1],['b',2]])
)只要數據結構部署了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
可使用數組做爲變量,如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)}; // TypeError
使用break
來實現。
for (let k of a){ if(k>100) break; console.log(k); }
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}
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 }
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 13
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 沒有 5
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 // 外部捕獲 b
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}
相同點就是都是用來恢復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;
用於在一個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 // 4
解決回調地獄:
// 使用前 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 |
歡迎關注微信公衆號【前端自習課】天天早晨,與您一塊兒學習一篇優秀的前端技術博文 .