天天看點

for...of,for...in,forEach和map的差別

1. for…of循環

具有

iterator

接口,就可以用

for...of

循環周遊它的成員(屬性值

value

)。

for...of

循環可以使用的範圍包括數組、

Set

Map

結構、某些類似數組的對象、

Generator

對象,以及字元串。

for...of

循環調用周遊器接口,數組的周遊器接口隻傳回具有數字索引的屬性。對于普通的對象,

for...of

結構不能直接使用,會報錯,必須部署了

Iterator

接口後才能使用。可以中斷循環。

一個對象如果要具備可被

for...of

循環調用的

Iterator

接口,就必須在其

Symbol.iterator

的屬性上部署周遊器生成方法(或者原型鍊上的對象具有該方法)

PS: 周遊器對象根本特征就是具有

next

方法。每次調用

next

方法,都會傳回一個代表目前成員的資訊對象,具有

value

done

兩個屬性。

普通對象是不具備周遊器接口,例如:

let obj = {
  name: "zl",
  age: 21,
  job: "coder"
};

for(let item of obj) {
  console.log(item);  //TypeError: obj is not iterable
}
           

為其部署周遊器生成方法:

let obj = {
  name: "zl",
  age: 21,
  job: "coder",
  [Symbol.iterator]() {
    const self = this;
    const keys = Object.keys(self);  // [ 'name', 'age', 'job' ]
    let index = 0;
    return {
      next() {
        if(index<keys.length) {
          return {
            value: self[keys[index++]],
            done: false
          }
        } else {
          return {value: undefined, done: true}
        }
      }
    }
  }
};

for(let item of obj) {
  console.log(item);  //zl 21 coder
}
           

PS:

Object.keys()

:傳回給定對象所有可枚舉屬性的字元串數組。

因為

generator

函數傳回的就是疊代器,疊代器具有

next()

方法,是以使用

Generator

函數(周遊器對象生成函數)簡寫

Symbol.iterator

方法,可以簡寫如下:

let obj = {
  name: "zl",
  age: 21,
  job: "coder",
  * [Symbol.iterator]() {
    const self = this;
    const keys = Object.keys(self);
    for(let index=0; index<keys.length; index++) {
      yield self[keys[index]]
    }
  }
};

for(let item of obj) {
  console.log(item);  // zl 21 coder
}
           

原生具備

Iterator

接口的資料結構有:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象
  • ES6 的數組、Set、Map 都部署了以下三個方法:

    entries()

    /

    keys()

    /

    values()

    ,調用後都傳回周遊器對象。

擴充:類數組及轉為數組的方法

兩者的差別:

⑴ 都擁有

length

屬性,其它屬性(索引)為非負整數(對象中的索引會被當做字元串來處理);

⑵ 類數組不具有數組所具有的方法;

⑶ 類數組是一個普通對象,而真實的數組是Array類型。

常見的類數組有: 函數的參數

arguments

, DOM 對象清單

Nodelist

(比如通過

document.querySelectorAll

得到的清單), jQuery 對象 (比如 $(“div”))。

類數組可以轉換為數組:

//第一種方法
Array.prototype.slice.call(arrayLike, start);
//第二種方法
[...arrayLike];
//第三種方法:
Array.from(arrayLike);
           

PS:任何定義了周遊器(

Iterator

)接口的對象,都可以用擴充運算符轉為真正的數組。

2. for…in循環

周遊對象自身的和繼承的可枚舉的屬性,也就是說會包括那些原型鍊上的屬性。如果想要僅疊代自身的屬性,那麼在使用

for...in

的同時還需要配合

getOwnPropertyNames()

hasOwnProperty()

。可以中斷循環。

3. forEach

隻能周遊數組,不能中斷,沒有傳回值(或認為傳回值是

undefined

,故無法鍊式調用)。傳遞的函數作為forEach()的第一個參數,然後forEach()使用三個參數調用該函數:數組元素、元素的索引和數組本身。看個例子:

//隻傳入了一個參數,數組元素的值。
var data = [1,2,3,4,5];
var sum = 0;
data.forEach(function (value) {
  sum += value;
});
console.log(sum); //15
console.log(data)  // [ 1, 2, 3, 4, 5 ]
           
// 傳入三個參數
var data = [1,2,3,4,5];
var sum = 0;
data.forEach(function (v, i, a) {
  a[i] = v + 1;
});
console.log(data); // [ 2, 3, 4, 5, 6 ]
           

由上面兩個例子可見對元素進行操作的話是不會改變原數組的,但是直接對數組進行操作的話是會改變原數組的。

當數組項是複雜資料類型時,操作元素也會改變原數組:

let arr = [
  {name: "zl"},
  {age: 21}
];
arr.forEach((item) => {
  item.job = "coder"
});
console.log(arr);  //[ { name: 'zl', job: 'coder' }, { age: 21, job: 'coder' } ]
           

不能通過

break

中斷循環,但是用

try{}catch(){}

可以通過抛出異常跳出循環。捕獲異常機制本身的功能就是在出現異常的時候跳出try的代碼塊到

catch

裡處理異常。我們可以用

throw

方法手動抛出一個異常,這樣就跳出了

forEach

循環。

let arr = [0, 1, 2, 3, 4, 5, 6]
try{
    arr.forEach((item) => {
        if (item === 3) {
            throw 'Jump out now!'//在這裡抛出異常
        }
        console.log(item)
    })
} catch (e) {
    console.log(e)
}
           

運作結果:

0
1
2
Jump out now!
           

4. map

隻能周遊數組,不能中斷,傳回值是修改後的數組。傳遞給

map

的函數的調用方式和傳遞給和

forEach()

的函數的調用方式是一樣的。但是傳遞給map()的函數應該有傳回值。注意,map()傳回的是新數組,對于原數組的改變與否參考

forEach()

。看一個簡單的例子:

var a = [1,2,3,4,5];
var b = a.map(function (x) {
  return x * x
});
console.log(b);
console.log(a);
           

參考:

《JavaScript權威指南》

眷你:https://juejin.im/post/5bcb249a6fb9a05d212ed038#heading-0

劉小夕:https://juejin.im/post/5cab0c45f265da2513734390#heading-4

繼續閱讀