本文是 重溫基礎 系列文章的第十三篇。
今日感覺:每次自我年終總結,都會有各類情緒和收穫。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()
方法,每次調用都會返回一個結果對象。
這個結果對象,有兩個屬性:數組
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 }
複製代碼
(4)其餘場合
因爲數組的迭代會調用迭代器接口,因此任何接受數組做爲參數的場合,其實都調用了迭代器接口。下面是一些例子。
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]])
)
Promise.all()
Promise.race()
只要數據結構部署了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 |
每日文章推薦 | github.com/pingan8787/… |
ES小冊 | js.pingan8787.com |
歡迎關注個人微信公衆號【前端自習課】