如何使用for of 循環遍歷對象和Symbol.iterator


使用 for-of 遍歷對象是不是會輕松一點?

如何遍歷對象,一般來說都會想到 for-in

let obj = { a: 1, b: 2, c: 3, d: 4 } for( let k in obj ){ console.log(k, obj[k]); } // 輸出結果 // a 1 // b 2 // c 3 // d 4 

但是當有一些繼承關系的時候,就有些麻煩了,遍歷的時候會把繼承的屬性也遍歷出來,這就得加些判斷了

let newObj = Object.create(obj) newObj.e = 5; newObj.f = 6; for( let k in newObj ){ console.log(k, newObj[k]); } // 輸出結果 // e 5 // f 6 // a 1 // b 2 // c 3 // d 4  for( let k in newObj ){ if( newObj.hasOwnProperty(k) ){ console.log(k, newObj[k]); } } // 輸出結果 // e 5 // f 6 

當然多了一層縮進,有一點麻(蛋)煩(疼),所以可以省略一層,變成下面這樣

for( let k in newObj ) if( newObj.hasOwnProperty(k) ){    

  console.log(k,newObj[k]);

}

es6中有三類結構生來就具有Iterator接口:數組、類數組對象、Map和Set結構。

var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();

console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

至於對象沒有布置iterator接口的原因。是因為我們說的數組,Map等結構中的成員都是有順序的,即都是線性的結構,而對象,各成員並沒有一個確定的順序,所以遍歷時先遍歷誰后遍歷誰並不確定。所以,給一個對象部署iterator接口,其實就是對該對象做一種線性轉換。如果你有這種需要,就需要手動給你的對象部署iterator接口咯~

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
         };
       } else {
         return { value: undefined, done: true };
       }
      }
    };
  }
}

可以看到,Symbol.iterator會返回一個對象,這就是一個遍歷器對象,而作為遍歷器對象,其必須具備的特征就是必須具備next()方法。

我們還可以據此實現指針結構的數據結構。具體略~

  至於可以使用Array.from轉換成數組的類數組對象,部署iterator有一種很簡單的方法,即直接使用數組的[Symbol.iterator]接口。 

obj.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

當然,不知道你們看到next是否想到了es6的一個新玩意兒,即Generator函數。用Generator函數來實現Symbol.iterator接口,事半功倍。

 

其次,其它調用到遍歷器的操作還有解構賦值、擴展操作符、其它任何接受數組作為參數的場合,如:

  1. for...of
    Array.from()
    Map(), Set(), WeakMap(), WeakSet()(比如)
    Promise.all()
    Promise.race()

    遍歷器對象除了必須布置next方法以外,還有2個可選方法。return()和throw()。當一個解構在遍歷的時候異常提前退出(比如break,continue或者出錯)的時候,就會調用return方法,其次,return方法必須返回一個對象。

    至於throw方法,則是用於拋出錯誤,Generator.prototype.throw這里不展開講了,感興趣的可以搜索一下。

     for of循環有很多優點,比如不像for...in一樣只遍歷鍵名(甚至包括原型鏈上的鍵),而且不像foreach不能跳出循環。並且for...of為各種數據結構提供了一個統一的遍歷方法。

    foreach無法終止循環,當通過break來終止循環時會報錯 Illegal break statement。

復習就到此為止了,接下來來嘗試一些其它的方式

在 ES6 中提供了 for-of,可以很方便的遍歷數組和類數組,但是卻不能遍歷對象,這是為什么,與 for-in 僅僅相差一個單詞,用途也是遍歷,為什么卻不能使用在對象上?

查資料后得知,原來 ES6 中引入了 Iterator,只有提供了 Iterator 接口的數據類型才可以使用 for-of 來循環遍歷,而 Array、Set、Map、某些類數組如 arguments 等數據類型都默認提供了 Iterator 接口,所以它們可以使用 for-of 來進行遍歷

那么原因清楚了,該怎么解決呢?能不能為對象已經其它的一些數據類型提供 Iterator 接口呢

答案是可以的,ES6 同時提供了 Symbol.iterator 屬性,只要一個數據結構有這個屬性,就會被視為有 Iterator 接口,接着就是如何實現這個接口了,如下就是一個最簡實現:

newObj[Symbol.iterator] = function(){ let index = 0 , self = this , keys = Object.keys( self ) ; return { next(){ if( index < keys.length ){ return { value: self[keys[index++]] , done: false }; } else{ return { value: undefined , done: true } } } }; }; 

仔細看一下發現就會發現 Symbol.iterator 接口其實是一個 Generator 函數,那么就可以簡化代碼:

newObj[Symbol.iterator] = function* (){ let keys = Object.keys( this ) ; for(let i = 0, l = keys.length; i < l; i++){ yield this[keys[i]]; } } for(let v of newObj){ console.log( v ); } // 輸出結果 // 5 // 6 

值得注意的是 Object.keys 碰巧解決了之前 for-in 遇到的繼承問題

這樣滿足了我們的期望,使用 for-of 來遍歷對象,但是好像哪里不對,我們遍歷對象時一般都是期望同時輸出 key 和 value 的,這樣調整一下代碼

newObj[Symbol.iterator] = function* (){ let keys = Object.keys( this ) ; for(let i = 0, l = keys.length; i < l; i++){ yield { key: keys[i] , value: this[keys[i]] }; } } for(let v of newObj){ console.log( v ); } // 輸出結果 // {key: "e", value: 5} // {key: "f", value: 6} 

這樣返回了一個對象,似乎又很不舒服,我們能不能嘗試一些解構賦值呢。。。

for(let {key, value} of newObj){ console.log(key, value ); } // 輸出結果 // e 5 // f 6 

這樣似乎非常完美了。。。

但回過頭來看,你覺得輕松了一點么?

拓展: 在 class 中使用 Symbol.iterator

class User{ constructor(name, gender, lv){ this.name = name; this.gender = gender; this.lv = lv; } *[Symbol.iterator](){ let keys = Object.keys( this ) ; for(let i = 0, l = keys.length; i < l; i++){ yield { key: keys[i] , value: this[keys[i]] }; } } } let zhou = new User('zhou', 'male', 1); for(let {key, value} of zhou){ console.log(key, value); } // 輸出結果 // name zhou // gender male // lv 1


免責聲明!

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



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