作为 ES2015 的新增特性,SetMap 对象大家应该很熟悉了,例如 Set 在数组去重等场景中经常会用到:

function unique(array = []) {
	return Array.from(new Set(array));
}

但是一般我们都是只在需要这种数据结构的时候才去创建它,在用完之后就转回数组。大家可能都认为,相比 SetMap 对象,还是数组操作更熟悉一些。但实际上它们本身也提供了一些遍历方法,下面我们一起来看下。

Set 对象遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员:

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

需要特别指出的是,Set 的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。

与数组类似,Set 对象也提供 keysvaluesentries 方法返回一个迭代器。与数组不同的是,Set 对象 没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

下面的代码中,entries 方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等:

let set = new Set(['red', 'green', 'blue']);

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的 values 方法。

Set.prototype[Symbol.iterator] === Set.prototype.values
// true

一个对象只要实现了 Symbol.iterator 接口,就是一个可迭代对象,可以使用 for...of 遍历,或者使用扩展运算符展开

这意味着,可以省略 values 方法,直接用 for...of 循环遍历 Set

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

数组扩展运算符内部使用 for...of 循环,所以也可用于 Set 结构

Set 结构的实例与数组一样,也拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值。

et set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

上面代码说明,forEach 方法的参数就是一个处理函数。该函数的参数与数组的 forEach 一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set 结构的 键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。

这里可以得出一个结论,Set 对象 forEach 的回调只需要接受一个形参即可

Map 对象遍历操作

Set 对象大家都知道使用扩展运算符转为数组去遍历,这样其实也没啥问题,但是 Map 对象的遍历就五花八门了,有的用扩展运算符转数组遍历,还有的用 Object.fromEntries 转为对象去遍历:

const map = new Map([["name", 1], ["age", 2]]);

// 方法一:转为数组
[...map].forEach(([key, val]) => {
	// ...
})

// 方法二:转为对象
const obj = Object.fromEntries(map);
for (let key in obj) {
	if (Object.prototype.hasOwnProperty.call(obj, key)) {
		// ...
	}
}

这边推荐使用第一种方法,直接遍历数组简单直接,不用担心遍历到对象原型上

实际上 Map 对象也提供了一些用于遍历的方法,下面我们来看下。

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

需要特别注意的是,Map 的遍历顺序就是插入顺序。

使用 keysvalues

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

使用 entries

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

Map 结构的默认遍历器接口(Symbol.iterator 属性)是 entries 方法。

map[Symbol.iterator] === map.entries
// true

这意味着 Map 结构可以直接使用 for...of 遍历:

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

顺便提一下,用扩展运算符将 Map 结构转为数组,实际上就是 for...of 调用 entries 方法返回的迭代器:

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

此外,Map 还有一个 forEach 方法,与数组的 forEach 方法类似,也可以实现遍历。

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

参考

Set 和 Map 数据结构 - 阮一峰 ES6 教程