JavaScript集合對象遍歷方法總結

在JavaScript中能表示集合的數據結構是對象,如數組、普通對象和ES2015中新增的Set和Map等。當然不同對象的表現形式和功能不一樣。如對於集合{1,2,3,4,5,1},數組的表現形式可以爲[1,2,3,4,5,1],普通對象的表現形式可以是{"1":2,"2":1,"3":1,"4":1,"5":1},Set的表現形式是{1,2,3,4,5},Map的表現形式爲{"1" => 2, "2" => 1, "3" => 1, "4" => 1, "5" => 1}。當然set的主要作用是去重,map的主要作用是統計。新增的數據結構和新增的方法也使得集合的遍歷變得很容易。針對這樣的對象,我們經常使用的操作就是遍歷,本文將總結集合對象遍歷的幾種方式。

1. 數組的遍歷

首先來看一下針對數組的遍歷方法,如下:

var arr=[1,2,3,4,5,1];
//1.簡單的for循環
for(var i=0,l=arr.length;i<l;i++){
    console.log(`${i}:${arr[i]}`);
}
//2.forEach循環
arr.forEach((item,index)=>console.log(`${item}:${arr[index]}`));
//3.for...in循環
for(var i in arr){
    console.log(`${i}:${arr[i]}`)
}
//4.for...of循環
 //只輸出值
for(var i of arr){
    console.log(`${i}`)
}
 //輸出索引和值
for(var [key,value] of arr.entries()){
    console.log(`${key}:${value}`)
}
//outputs:
0:1
1:2
2:3
3:4
4:5
5:1

其實對數組來說,最有效率的方式就是第一種簡單for循環了。另外幾種雖然可以,但是效率就大打折扣了。for…of循環是ES6新增的方法,用在這裏有點大材小用了,不過在這裏主要說明的是ES6的數組、Set和Map都擁有entries()方法,它返回一個遍歷器對象,用來遍歷[鍵名, 鍵值]組成的數組。這點可以參見阮一峯老師的《ECMAScript 6 入門》,引用如下:

有些數據結構是在現有數據結構的基礎上,計算生成的。比如,ES6的數組、Set、Map 都部署了以下三個方法,調用後都返回遍歷器對象。

  • entries() 返回一個遍歷器對象,用來遍歷[鍵名, 鍵值]組成的數組。對於數組,鍵名就是索引值;對於 Set,鍵名與鍵值相同。Map 結構的 Iterator 接口,默認就是調用entries方法。
  • keys() 返回一個遍歷器對象,用來遍歷所有的鍵名。
  • values() 返回一個遍歷器對象,用來遍歷所有的鍵值。

2. 對象的遍歷

var obj={"1":2,"2":1,"3":1,"4":1,"5":1};
//1. for...in循環
for(var i in obj){
    console.log(`${i}:${obj[i]}`)
}
//2. 利用Object.keys()+forEach
Object.keys(obj).forEach(i=>console.log(`${i}:${obj[i]}`));
//outputs:
1:2
2:1
3:1
4:1
5:1

對象的遍歷可以通過for…in循環得到。如果只想得到對象的屬性值,可以用Object提供的靜態方法Object.keys()得到。不過這裏要說一下for…in循環。for…in循環會返回所有能夠通過對象訪問的、可枚舉(enumerated)的屬性,其中既包括存在於實例中的屬性,也包括存在與原型中的屬性。它準確來說是用來找屬性的方法,用在集合遍歷(屬性值的數據類型通常一樣)顯得有些大材小用了。不過因爲這是一個很強大的方法,強大也意味着容易讓人犯錯。比如你想造一個類數組對象(對象中有length屬性)寫了如下語句obj.length=5。當你再用for…in循環是就會鬱悶的發現輸出多了一個length:5。或許你也許會問數組也是對象,爲什麼for…in循環的結果沒有length呢?

我來解釋一下,先來看一下上面的黑體字,返回可枚舉的屬性。這意味着只有可枚舉的屬性纔會暴露出來。那怎麼看是不是可枚舉呢?要查看對象屬性的特性,可以用Object.getOwnPropertyDescriptor()。比如我們查看length屬性在數組中是否可枚舉,可使用如下代碼:Object.getOwnPropertyDescriptor(arr,'length')。它在Chrome中的返回爲Object {value: 6, writable: true, enumerable: false, configurable: false}。裏面的enumerable值爲false,意味着不可枚舉。configurable爲false說明不可刪除。所以length屬性在數組中默認是不可枚舉,也不能用delete刪除的。那有沒有辦法修改哪?要修改屬性默認的特性,可以使用Object.defineProperty()。但數組中的length屬性默認是不可修改的。說了這麼多,我們給對象新添加的length屬性返回什麼呢?試一下,就會發現它返回Object {value: 4, writable: true, enumerable: true, configurable: true}。因爲configurable值爲true,所以可被枚舉到。不過針對我們自己創建的對象,可以修改屬性類型。使用Object.defineProperty(obj,'length',{enumerable: false})設置可枚舉爲false之後,發現再利用for…in循環就不會再遍歷到length屬性了。這裏的屬性類型的具體內容可以參見JavaScript高級程序設計6.1節。

另外針對Object.keys()要注意,若傳入的參數爲非對象,ES6工作方式同ES5不一樣。ES5會報錯,ES6會將其強制轉換爲對象,若轉換不成功報錯。

Object.keys('abcd')
//es5:TypeError...
//es6:["0", "1", "2", "3"]

3. Set的遍歷

//Set作爲構造函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作爲參數
var set=new Set([1,2,3,4,5,1])
//Set {1, 2, 3, 4, 5}
//也可以使用add方法添加成員
var set=new Set();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
set.add(1);
//Set {1, 2, 3, 4, 5}
//1.for...of遍歷值
for(var i of set){
    console.log(`${i}`)
}
for(var [key,value] of set.entries()){
    console.log(`${key}:${value}`)
}
//2.forEach遍歷鍵值對
set.forEach((value, key) => console.log(`${key}:${value}`) )

Set和Map的加入可謂是呼聲已久。因爲很長時間我們都只能依靠普通對象來實現Set和Map的功能。所以如果JS原生支持的話,那再好不過了。Set的遍歷可以用for…of或forEach來實現。如果使用set.entries(),返回的數組鍵名和鍵值相等。set.keys()set.values()也會得到相同的集合。
得益於ES6的方法,數組和Set可以迸發出很強大能量。比如數組轉Set,直接向Set的構造函數中傳遞數組就會得到一個去重的Set;Set轉數組,同樣很簡單,直接使用Array.from或擴展運算符就Ok了。(擴展運算符可以將任何實現了 Iterator 接口的對象轉化爲數組,Set實現了 Iterator 接口。)二者簡直天作之合。比如有些時候需要對數組去重而後排序,二者合作簡直不能太美。

var arr=[5,3,1,2,3,4,5,1];
var set=new Set(arr);
//Set {5,3,1,2,4}
arr=Array.from(set);
//也可以使用擴展運算符arr=[...set];
arr.sort((a,b)=>a-b);
//output:[1, 2, 3, 4, 5]

4. Map的遍歷

//Map作爲構造函數可以接受一個數組作爲參數。該數組的成員是一個個表示鍵值對的數組
var map=new Map([["小明",2],["小紅",2]])
//Map {"小明" => 2, "小紅" => 2}
//也可以使用set設置鍵值對
var map=new Map();
map.set(1,2);
map.set(2,1);
map.set(3,1);
map.set(4,1);
map.set(5,1);
//Map {1 => 2, 2 => 1, 3 => 1, 4 => 1, 5 => 1}
//1.for...of遍歷
    //遍歷鍵值對
for(var [key,value] of map){
    console.log(`${key}:${value}`)
}
    //遍歷值
for(var value of map.values()){
    console.log(`${value}`)
}
    //遍歷鍵
for(var key of map.keys()){
    console.log(`${key}`)
}
//2.forEach遍歷鍵值對
map.forEach((value, key) => console.log(`${key}:${value}`) )

for…of做map的遍歷時,默認方式是返回鍵值對,同使用map.entries()相同,不過可以通過使用map.keys()map.values()只返回鍵名或鍵值。Map和Set都提供forEach接口,和數組使用方式相同。
Map和數組的轉換同樣簡單:

//數組轉map
var arr=[["小明",2],["小紅",2]];
var map=new Map(arr)
//{"小明" => 2, "小紅" => 2}
//map轉數組
arr=[...map];
//[Array[2], Array[2]]

好了,這就是本文的內容。不過,另外說一下只有提供Iterator 接口的數據結構纔可以使用for…of循環,纔可以使用擴展操作符轉化爲數組。原生具備該接口的有:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

對於Set和Map更多的用法請自行查看文末的參考資料。

如有錯誤,請不吝指正;如有問題,請寫下評論。

參考資料:
1. ECMAScript 6 入門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章