ES6的Set和Map数据结构

Set

基本用法

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 本身是一个构造函数,用来生成 Set 数据结构。

const s = new Set();

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

const set = new Set([1, 2, 3, 4, 4]);
[...set]	// [1, 2, 3, 4]

Set 可以用来数组去重:

[...new Set(array)]

还能去除字符串里面的重复字符:

[...new Set('ababbc')].join('')
// "abc"
Set 实例的属性和方法

Set 结构的实例有以下属性。

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
const s = new Set();
s.add(1).add(2).add(2);
console.log(s.size);	// 2
console.log(s.has(1));	// true
console.log(s.has(2));	// true
console.log(s.has(3));	// false
s.delete(2);
console.log(s.has(2));	// false

Array.from 方法可以将 Set 结构转为数组,这是数组去重的第二种方法。

Array.from(new Set([1, 2, 3, 4, 4]));	// [1, 2, 3, 4]
遍历操作

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

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

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

keys()、values()、entries()

keys方法、values方法、entries方法返回的都是遍历器对象,keys方法和values方法返回值是相同的。

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法,所以遍历 Set 的 value 方法和直接遍历 Set 的结果是相同的。

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);
}
for(let item of set) {	// 在遍历时,可以省略value方法
	console.log(item);
}
for(let item of set.entries()) {
	console.log(item);
}
// ['red', 'red']
// ['green', 'green']
// ['blue', 'blue']
forEach()

和数组的forEach()方法类似,可以传入一个回调函数,接收三个参数(键值、键名、集合本身),第一个参数和第二个参数是相同的。

let set = new Set(['red', 'green', 'blue']);
set.forEach((value, key) => {
	console.log(value + ':' + key);
});
// red:red
// green:green
// blue:blue
遍历的应用

利用扩展运算符可以间接地使用数组的mapfilter方法

let set = new Set([2, 3, 4]);
set = [...set].map(item => item * 2);	// // 返回Set结构:{2, 4, 6}

因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);	// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));	// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));	// Set {1}

Map

含义和基本用法

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构)。Map 数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应。

Map 实例的属性和方法
  • size 属性:返回 Map 结构的成员总数
  • Map.prototype.set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
  • Map.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined
  • Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • Map.prototype.delete(key):删除某个键,返回true。如果删除失败,返回false
  • Map.prototype.clear():清除所有成员,没有返回值。
const map = new Map();
map.set('name', 'Tom');
map.set('age', 20);
console.log(map.size);	// 2
console.log(map.get('name'));	// Tom
console.log(map.has('age'));	// true
console.log(map.has('sex'));	// false
map.delete('age');
console.log(map.has('age'));	// false
map.clear();
console.log(map.size);	// 0
遍历方法

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

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

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

Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法,所以遍历 Map 的 entries 方法和直接遍历 Map 的结果是相同的。

const map = new Map([
	['foo', true],
	['bar', false]
]);
for(let key of map.keys()) {
	console.log(key);
}
// foo
// bar
for(let value of map.values()) {
	console.log(value);
}
// true
// false
for(let item of map.entries()) {
	console.log(`${item[0]} : ${item[1]}`);
}
// foo : true
// bar : false
for(let [key, value] of map.entries()) {
	console.log(`${key} : ${value}`);
}
// foo : true
// bar : false
for(let [key, value] of map) {		// 在遍历所有成员时,可以省略entries方法
	console.log(`${key} : ${value}`);
}
// foo : true
// bar : false

Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有mapfilter方法)。

const map0 = new Map([
	[1, 'a'],
	[2, 'b'],
	[3, 'c'],
]);
const map1 = new Map([...map0].filter(([k, v]) => k === 2));
// {2 => 'b'}
const map2 = new Map([...map0].map(([k, v]) => [k * 2, '_' + v]));
// {2 => '_a', 4 => '_b', 6 => '_c'}

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

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
map.forEach((key, value) => {
    console.log(`${key} : ${value}`);
});
// one : 1
// two : 2
// three : 3

参考资料:

Set 和 Map 数据结构