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


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

Interator(遍歷器)

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

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

迭代器對象一旦被創建,就可以反復調用next()。

以下代碼是模擬迭代器:
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可以適用於字符串

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

Interator不是很難懂,只要明白了哪些是數據集合,就說明具有Interator接口,自然就可以當作遍歷器,從而使用for-of循環和使用next方法獲取想要的數據。

 

Generator(生成器)

通過對Interator的理解,生成器也是具有Interator接口的對象,它本身帶有遍歷特性,返回一個遍歷器對象。

從寫法上看,它和普通函數差別不大,就多了兩個特性:
  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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM