您的位置:首页 > Web前端 > JavaScript

JavaScript集合对象遍历方法总结

2017-08-30 16:34 633 查看
在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 入门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息