es6學習筆記--Interator和Generator(以及for-of的用法)

這幾天學習了遍歷器和生成器,看着資料學,有點霧裏繚繞的感受,讓人忍不住放棄,還好多看了好幾遍,懟着資料裏的例子讓本身學會了Interator和Generator。
 
Interator,中文簡稱:遍歷器,是一種接口,爲具備遍歷結構的或者說有length長度的集合提供一個接口,從而進行遍歷操做。
Generator,中文簡稱:生成器,從語法上講是一種狀態機,經過遍歷操做,展現不一樣的狀態狀況。
 

Interator(遍歷器)git

Iterator 接口的目的,就是爲全部數據結構(集合),提供了一種統一的訪問機制,爲for-of這個遍歷方法提供接口,任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
ps: 數據結構=集合

在 JavaScript 中 迭代器是一個對象,它提供了一個next() 方法,(除了next()方法還有return和throw方法),用來返回序列中的下一項。這個方法返回包含兩個屬性:done和 value。done屬性是個布爾值,表明遍歷是否結束,便是否還有必要再一次調用next方法。value屬性表明當前成員的值。es6

迭代器對象一旦被建立,就能夠反覆調用next()。github

如下代碼是模擬迭代器:
function makeIterator(array){
    var nextIndex = 0;
    return {
    next: function(){
        return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {done: true};
    }
    };
}
let a = makeIterator(['apple','pear','orange'])
console.log(a.next())   // {value: "apple", done: false}
console.log(a.next())   // {value: "pear", done: false}
console.log(a.next())   // {value: "orange", done: false}
console.log(a.next())   // {done: true}
由上述例子可知:next方法返回的是一個對象,有value和done屬性,若是還有下一個next()可遍歷,那麼done爲false,若是done爲true,說明不可再次遍歷。
ps:遍歷器建立以後不會自動觸發,而是由next()觸發。
 
一種數據結構只要部署了 Iterator 接口,咱們就稱這種數據結構是「可遍歷的」(iterable),

ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性, Symbol.iterator屬性自己是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。編程

常見的具備Iterator接口的遍歷器:
  Array
  Map
  Set
  String
  TypedArray
  函數的 arguments 對象
  NodeList 對象
  生成器
ps:普通的對象不具備Iterator接口,緣由是對象的遍歷是沒有順序的,沒有相應的索引值可言,因此想要使普通的對象要有Iterator接口,須要給它加上Object方法變成有順序的對象便可。
Iterator遍歷器簡單來講,就是在具備Iterator的對象(Array,Map,Set,String,TypedArray,函數的 arguments 對象,NodeList 對象,生成器)上進行for-of的循環,而且用next()方法獲取遍歷的值.
 

學習一下新型的for-of循環。可在具備Iterator 接口的元素進行遍歷。數組

for-of循環和之前的之前的for循環和es5的forEach循環同樣,遍歷操做。
for...of語句在可迭代對象(包括 Array,Map,Set,String,TypedArray,arguments 對象等等)上建立一個迭代循環,調用自定義迭代鉤子,併爲每一個不一樣屬性的值執行語句。
let arr = [1,2,3];
for(let v of arr){
    v +=1;
    console.log(v)
} // 2  3  4
for-of替代了之前的全部的循環遍歷,融合了其優勢,摒棄了其缺點。
 
for-of與for,forEach,for-in相比的優缺點:
1 forEach循環不能用 break 中斷循環,不然會報錯.也不能使用 return 語句返回到外層函數
[1,2,3,4,5].forEach((i,v) => {
    console.log(v)
    if(i > 3){
        break;
    }
})  // Uncaught SyntaxError: Illegal break statement
2 for-in 遍歷,只獲取鍵名,獲取不到鍵值。以任意順序遍歷鍵名,只能遍歷帶有字符串的key。一般不推薦循環數組。
for(let v in ['a','b','c']){
    console.log(v)
}  // 0   1   2
3 for循環書寫太複雜,emmmm,只能這麼說。
 
for-of的優勢:
1 最簡潔、最直接的遍歷數組元素的語法
let arr = [1,2,3,4];
for(let v of arr){
    console.log(v)
}  // 1    2    3    4
上個方法避開了for-in循環的全部缺陷。
let a = ['a','b','c','d']
for(let v of a){
    console.log(v)
}   // a   b   c    d
上述代碼for-of能夠直接獲取鍵值,若是想要獲取其索引值能夠採用Object擴展方法keys()和entires()
2 與forEach()不一樣的是,它能夠正確響應break、continue和return語句
for(let v of [1,2,3,4,5]){
    console.log(v)
    if(v > 3){
        break
    }
} //  1   2   3    4     5

3 能夠遍歷其餘的全部集合(Nodelist,Set,Map),還有生成器數據結構

<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>


let doms = document.querySelectorAll('div')
for(let v of doms){
    console.log(v.innerHTML)
}    // 1   2   3   4
for-of能夠遍歷dom元素,而且進行相應的操做
let set = new Set([1,2,3,4,5])
for(let v of set){
    console.log(v)
}   //  1   2  3  4  5
let map = new Map().set('a',1).set('b',2)
for(let v of map){
    console.log(v)
}   
// ["a", 1]
// ["b", 2] 
Set和Map自己具備 Iterator 接口,因此用for-of循環,set返回的是一個值,而map返回的是一個數組。
ps:for-of不能遍歷普通的對象,會報xxx is not iterable,須要把普通對象轉化成具備Interator接口的便可。Object.keys(),Object.values()和Obejct.entries()也能夠獲取
let obj = {name:'peter',age:25}
for(let v of obj){
    console.log(v)
} // Uncaught TypeError: obj is not iterable
let obj = {name:'peter',age:25}
for(let v of Object.keys(obj)){
    console.log(v)
} // name  age

4 另外,for-of能夠適用於字符串app

let str = 'hello';
for(let v of str) {
    console.log(v)
}   // h  e  l  l  o

Interator不是很難懂,只要明白了哪些是數據集合,就說明具備Interator接口,天然就能夠看成遍歷器,從而使用for-of循環和使用next方法獲取想要的數據。dom

 

Generator(生成器)異步

經過對Interator的理解,生成器也是具備Interator接口的對象,它自己帶有遍歷特性,返回一個遍歷器對象。async

從寫法上看,它和普通函數差異不大,就多了兩個特性:
  1 在函數聲明前加上星號(*),
  2 函數內部多了一個關鍵字yield。

既然生成器是遍歷器,那麼可使用遍歷器的方法(自己函數不會實行,必須經過next()方法才能調用或者使用for-of返回)

function *fn(){
    yield 'peter';
    yield 1;
    yield {name:'peter'};
    yield [1,2,3,4];
    yield function *foo(){
        yield 123;
    }
}
let a = fn()
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
{value: "peter", done: false}
{value: 1, done: false}
{value: {…}, done: false}
{value: Array(4), done: false}
{value: ƒ, done: false}
{value: undefined, done: true}
從上述例子可知:每個next()方法,就返回一個數據,這說明yield表達式表明一個進程,返回其後面的表達式。

學習一下yield:

1 因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。因此yield表達式就是暫停標誌。
2 yield 是關鍵字,其後面能夠跟變量,常量,表達式,可是必須有next方法才能調用或者for-of返回,自己不是返回值
function *fn(){
    yield 'a';
    let b = yield 'b' + 'c';
    yield 'd'
}
let foo = fn()
for(let v of foo){
    console.log(v)
}  //  a   bc    d
由上述例子可知:yield返回的是後面的表達式,不影響前面的聲明
3 遇到yield表達式時,進程暫停,next()方法纔會調用yield後面的表達式。
function *fn(){
    yield '1'
    console.log('start')
    yield '2'
}
let a = fn()
console.log(a.next())   
{value: "1", done: false}
4 做用和return差很少,但也有區別,return只能執行一回,yield能執行屢次,每次遇到yield時就先暫停,而後下一次運行從暫停的位置開始。
5 yield只能在Genterator函數裏才能運用,在其餘地方運用會報錯。
function fn(){
    yield 'a'
}
fn()   // Uncaught SyntaxError: Unexpected string
6 yield和return同時在一個函數裏時,按照代碼同步順序執行的結果,到了return就直接返回,不繼續執行下一步。
function *fn(){
    yield '1';
    yield '2';
    return '3';
    yield '4'
}
let a = fn()
console.log(a.next());  // {value: "1", done: false}
console.log(a.next());  // {value: "2", done: false}
console.log(a.next());  // {value: "3", done: true}
console.log(a.next());  // {value: undefined, done: true}
console.log(a.next());  // {value: undefined, done: true}
7 yield 也能夠跟星號,表明在一個 Generator 函數裏面執行另外一個 Generator 函數。
在生成器函數裏是沒法進行另外一個Generator函數的,沒有效果,若單單使用yield,返回的是另外一個生成器的對象
function *foo(){
    yield 'f'
}
function *fn(){
    yield 'a';
    yield foo()
    yield 'b'
}
for( let v of fn()){
    console.log(v)   
}   // a   foo {<suspended>}     b
因此在yield* 能夠用來進行另外一個Generator函數(只要在一個Generator函數運行另外一個Generator函數就能夠直接yield*)
function *foo(){
    yield 1;
    yield 2
}
function *fn(){
    yield 'a';
    yield *foo()
    yield 'b'
}
for( let v of fn()){
    console.log(v)    // a   1  2    b
}

上面已經說到生成器也是具備Interator接口的對象,不可置否的,生成器自己帶有Symbol.iterator,能夠說生成器是遍歷器的一種,因此可遍歷,可使用for-of來循環數據。

for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,且此時再也不須要調用next方法。

簡單來講,for-of裏返回的數據和next()中value同樣.區別在於for-of循環完後不循環return後面的表達式。而next()則會。
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
for(let v of a){
    console.log(v)  // a   b   c    d   
}
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
console.log(a.next());   // {value: "a", done: false}
console.log(a.next());   // {value: "b", done: false}
console.log(a.next());   // {value: "c", done: false}
console.log(a.next());   // {value: "d", done: false}
console.log(a.next());   // {value: "end", done: true}
console.log(a.next());   // {value: undefined, done: true}
擴展運算符也支持生成器的遍歷
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
console.log([...a])   //  ["a", "b", "c", "d"]
因而可知,遍歷出來的依然不包含return的表達式

概要總結;只要具備Symbol.iterator屬性的,就能夠遍歷yield表達式

Generator 函數也不能跟new命令一塊兒用,會報錯
function* f() {
    yield 2;
    yield 3;
}
new f()  // TypeError: F is not a constructor

Generator 函數的方法:

next() 返回 Generator 函數對象中yield後面的表達式,上面已經用到了next方法。yield表達式自己沒有返回值,老是返回undefined

當next()有參數時,該參數就會被看成上一個yield表達式的返回值。
function *fn(x){
    let a = yield x;
    let b = yield 2 + a;
}
let a = fn(2);
console.log(a.next(5));   // {value: 2, done: false}
console.log(a.next(10));  // {value: 12, done: false}
console.log(a.next(20));  // {value: undefined, done: true}
因爲next方法的參數表示上一個yield表達式的返回值,因此在第一次使用next方法時,傳遞參數是無效的。
因此第一個next方法用來啓動遍歷器對象,因此不用帶有參數。
      第二個next()方法把第二個yield後面的2替換成了10,因此10+2=12
      第三個next()方法由於沒有yield,返回undefined,參數無效
ps:若是一開始傳了參數,第二個next沒有傳參數,則是undefined
function *fn(x){
    let a = yield x;
    let b = yield 2 + a;
}
let a = fn(2);
console.log(a.next());   // {value: 2, done: false}
console.log(a.next());  // {value: NaN, done: false}
console.log(a.next());  // {value: undefined, done: true}
第二個傳參爲空致使undefined+2等於NaN
next()方法的意義:Generator 函數從暫停狀態到恢復運行,它的上下文狀態(context)是不變的。經過next方法的參數,就有辦法在 Generator 函數開始運行以後,繼續向函數體內部注入值。也就是說,能夠在 Generator 函數運行的不一樣階段,從外部向內部注入不一樣的值,從而調整函數行爲。

throw() 在函數體外拋出錯誤,而後在 Generator 函數體內捕獲。

let a = function* () {
    try {
        yield ;
    } catch ( e ){
        console.log(e);
    }
};
var i = a();
console.log(i.throw())  // Uncaught undefined
生成器的聲明方式和普通的同樣。    yield後面沒有表達式,爲undefined。
throw方法能夠接受一個參數,該參數會被catch語句接收,建議拋出Error對象的實例。
let g = function* () {
    try {
        yield 1;
    } catch (e) {
        console.log(e);
    }
    yield 'a';
    yield 'b'
};

let i = g();
console.log(i.next())  // 1
i.throw(new Error('出錯了!'));  // Error: 出錯了!(…)   附帶執行了一次yield ‘a’
console.log(i.next())  // b

throw()方法的做用就是捕獲異常,而且繼續執行下去,不由於異常而中斷。throw方法被捕獲之後,會附帶執行下一條yield表達式。也就是說,會附帶執行一次next方法。

ps:不要混淆遍歷器對象的throw方法和全局的throw命令。上面的異常是用遍歷器對象的throw方法拋出的,而不是用throw命令拋出的。後者只能被函數體外的catch語句捕獲。

throw()的意義:大大方便了對錯誤的處理。多個yield表達式,能夠只用一個try...catch代碼塊來捕獲錯誤。若是使用回調函數的寫法,想要捕獲多個錯誤,就不得不爲每一個函數內部寫一個錯誤處理語句,如今只在 Generator 函數內部寫一次catch語句就能夠了。

return() 返回給定的值,而且終結遍歷 Generator 函數。

當return()不傳參數時,默認是undefined,就至關於最後一步,done即爲true時的操做,value值爲undefined
function *fn(){
    yield 1;
    yield 2;
    return 3;
}
let a = fn();
console.log(a.next())   // {value: 1, done: false}
console.log(a.return());   // {value: undefined, done: true}
console.log(a.next())   // {value: undefined, done: true}
當return()傳參數時,value值爲傳的參數
function *fn(){
    yield 1;
    yield 2;
    return 3;
}
let a = fn();
console.log(a.next())   // {value: 1, done: false}
console.log(a.return(100));   // {value: 100, done: true}
console.log(a.next())   // {value: undefined, done: true}

return()的意義:一般在生成器異步操做時須要在某個時段跳出來。

Generator生成器是異步編程提供了方便。

 

對於Interator和Generator,在平時使用時不多用到,只有那個for-of能夠替代for循環使用,主要用於異步編程async當中。學習這個感受沒學全,事後我會再仔細學一遍。知識點我放到github裏了,有須要能夠去下載一塊兒學習。

仍是那句話。有什麼問題或錯誤請私信或者下方評論,一塊兒討論進步。

 

參考資料:

阮一峯es6入門 http://es6.ruanyifeng.com/

相關文章
相關標籤/搜索