天天看點

for...of循環的使用

for...of循環的使用

for…of語句在可疊代對象(包括 Array,Map,Set,String,TypedArray,arguments 對象等等)上建立一個疊代循環,調用自定義疊代鈎子,并為每個不同屬性的值執行語句。——來源MDN

基本使用

for…of的基本使用比較簡單:

// 周遊數組
let array = ['a', 'b', 'c'];


for (let value of array) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}


// 周遊字元串
let str = "abc";


for (let value of str) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}


// 周遊Map
let map = new Map([["a", 1], ["b", 2], ["c", 3]]);


for (let value of map) {
  console.log(value); // 分别列印 ["a", 1] ["b", 2] ["c", 3]
}


// 周遊Set
let set = new Set(['a', 'a', 'b', 'c', 'b', 'c']);


for (let value of set) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}


// 周遊arguments
(function() {
  for (let argument of arguments) {
    console.log(argument); // 分别列印 'a' 'b' 'c'
  }
})('a', 'b', 'c');      

可疊代對象

for…of的文法比較簡單,上面我們周遊了這麼多資料,現在我們使用for…of周遊一下對象:

let object = {
  a: 1,
  b: 2,
  c: 3,
}
for (let value of object) {
  console.log(value); // 報錯:Uncaught TypeError: object is not iterable
}      

結果很不幸,使用for…of周遊對象報錯了。為什麼報錯了,報錯的錯誤提示寫的很清楚,因為object對象不是可疊代的,也就是說它不是可疊代對象。

這裡遇到一個新的名詞,什麼是可疊代對象呢?

要成為可疊代對象, 這個對象必須實作@@iterator方法,并且該方法傳回一個符合疊代器協定的對象。

這裡有2個問題,第一怎麼去實作一個@@iterator方法?看到@@xxx這樣的方法,想都不用想就是指[Symbol.xxx]方法,這裡也就是一個方法的key是[Symbol.iterator]就可以了,比如:

let object = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator]: function() {}
}      

第二個問題什麼是符合疊代器協定的對象?首先疊代器協定的對象是一個對象,這個對象有一個next方法,這個next方法每次調用有會傳回一個對象,這個傳回的對象又有一個done屬性和一個value屬性。

其中done屬性表示是否完成,如果是true則表示完成,false或者不寫則表示沒有完成;value表示值,也就是for…of循環時每次使用的值,如果done為true時候則可以不寫。舉個可疊代對象的例子:

let loop10 = {
  [Symbol.iterator]: function() {
    let i = 0


    return {
      next: function() {
        return {
          value: i++,
          done: i > 10
        }
      }
    }
  }
}


for (let value of loop10) {
  console.log(value); // 分别列印 0 1 2 3 4 5 6 7 8 9
}      

疊代器協定的對象也可以自己調用着玩玩:

let iterator = loop10[Symbol.iterator]();
iterator.next(); // 傳回 {value: 0, done: false}
iterator.next(); // 傳回 {value: 1, done: false}
iterator.next(); // 傳回 {value: 2, done: false}      

當然疊代器協定的對象不僅僅隻能用在for-of循環中,也可以用在數組的解構上:

let arr = [...loop10]; // arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]      

可疊代對象與generator函數​

當我們看到一個個可疊代對象的next方法,再看看一個個的{value: 0, done: false}這種符合疊代器協定的對象,這時不想跟generator沒點關系都不行了,沒錯generator函數傳回的正是可疊代對象。

我們先使用正常方法實作一下對象的for…of周遊。

let object = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator]: function() {
    let keys = Object.keys(this);
    let i = 0


    return {
      next: function() {
        return {
          value: keys[i++],
          done: i > keys.length
        }
      }
    }
  }
}
for (let value of object) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}      

使用generator函數可以簡化上述步驟:

let object = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator]: function *() {
    let keys = Object.keys(this)
    for (let i = 0; i < keys.length; i++) {
      yield keys[i]
    }
  }
}
for (let value of object) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}      

是不是很友善?這裡偷偷告訴你一個小秘密:generator函數調用後的對象也可以用在for…of上。

let loop10Gen = function *() {
  for (let i = 0; i < 10; i++) {
    yield i
  }
}


// 注意這裡是loop10Gen() 而不是loop10Gen
for (const value of loop10Gen()) {
  console.log(value); // 分别列印 0 1 2 3 4 5 6 7 8 9
}      

上面不是說了,可疊代對象要實作一個@@iterator方法,這裡實作了嗎?沒錯,這裡還真實作了!你可以試試:

let itarator = loop10Gen();
itarator[Symbol.iterator]() === itarator; // 傳回true      

于是我們就得到一個比較繞的真理:generator調用後的對象,既是可疊代對象,也是符合疊代器協定的對象。

for…of與for…in的差別

for…in周遊的是對象的可枚舉屬性,而for…of語句周遊的是可疊代對象所定義要疊代的資料。

由于for…in周遊的是對象的可枚舉屬性,是以對于數組來說列印的是鍵,而不是值:

let array = ['a', 'b', 'c'];


for (const value in array) {
  console.log(value); // 分别列印 '0' '1' '2'
}


for (const value of array) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}      

for…in會周遊對象原型和原型鍊上的可枚舉的屬性。

let array = ['a', 'b', 'c'];


Object.prototype.formObject = true;
Array.prototype.formArray = true;
array.hello = 'world'




for (const value in array) {
  console.log(value); // 分别列印 0 1 2 hello formArray formObject
}


for (const value of array) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}      

通常為了避免for…in周遊原型和原型鍊上無關的可枚舉屬性,使用Object.hasOwnProperty()方法來判斷:

let array = ['a', 'b', 'c'];


Object.prototype.formObject = true;
Array.prototype.formArray = true;
array.hello = 'world'


for (const value in array) {
  if (Object.hasOwnProperty.call(array, value)) {
    console.log(value); // 分别列印 0 1 2 hello
  }
}


for (const value of array) {
  console.log(value); // 分别列印 'a' 'b' 'c'
}      

可疊代對象的return方法

可疊代對象除了next方法外還有return方法,主要用在循環中斷的時候會調用,比如是用break關鍵字、或者抛出一個Error:

let loop10 = {
  [Symbol.iterator]: function() {
    let i = 0


    return {
      next: function() {
        return {
          value: i++,
          done: i > 10
        }
      },
      return: function() {
        console.log('return調用了~~')
        return { done: true };
      }
    }
  }
}


for (let value of loop10) {
  console.log(value); // 分别列印 0 1 2 3
  if (value === 3) {
    break; // 列印 'return調用了~~'
  }
}