Set()和Map()是ES6中新引入的两种数据结构,Set()同数学上的集合,其子元素不会重复。而Map()则是用来更好实现键值对映射的数据结构,解决了Object中键的类型只能为string的限制
一、基本用法
创建一个Set
和一个Map
的方法是类似的,都是用构造函数:
const s = new Set();
const m = new Map();
对于Set,可以通过给构造函数传一个数组,来初始化:
const s = new Set([1, 2, 2, 3, 3]);
[...s]; // [1, 2, 3]
Map则可以通过传一个具有Iterator接口,且成员都是双元素的数组,来初始化,如:
const sym = Symbol();
const arr = [
[1, 'Number'],
['Hello', 'String'],
[sym, 'Symbol']
];
const set = new Set([
[1, 'Number'],
['Hello', 'String'],
[sym, 'Symbol']
]);
const m1 = new Map(arr); // Map(3) {1 => "Number", "Hello" => "String", Symbol() => "Symbol"}
const m2 = new Map(set); // Map(3) {1 => "Number", "Hello" => "String", Symbol() => "Symbol"}
二、Set()
我们可以对一个Set进行增删查,方法如:
const s = new Set();
// 使用`add()`方法增加元素,可以保证唯一性,返回值为结构本身
// 在内部判断两个值是否相等,采取类似于===的同值相等策略,但NaN被认为是相等的
s.add(1).add(2);
s.add(NaN);
s.add(NaN);
s; // Set(3) {1, 2, NaN}
// 使用`delete()`可以删除元素,返回一个值表示删除成功与否
s.delete(1); // true
s.delete(1); // false
// 使用`has()`可以判断集合中是否包含有某一特定元素
s.has(NaN); // true
// 使用`clear()`可以清空集合中的所有元素
s.clear();
此外,还可以使用size
属性,得到一个集合包含的元素个数:
s.size;
对于Set结构,还具有一系列的遍历方法,可以用来遍历:
s.keys()
返回键名的迭代器s.values()
返回键值的迭代器,由于Set结构中没有键名,所以keys()和values()的返回是一致的s.entries()
返回键值对的迭代器s.forEach()
遍历每个成员
我们还可以直接使用for-of
来遍历一个Set,如:
const s = new Set([1,3,3,5,5,7]);
for (let elem of s) {
console.log(elem);
}
由于Set实现了iterator接口,所以可以使用展开运算符...
,如:
[...new Set([1,3,3,5,5,7])]; // 实现数组去重,输出:[1,3,5,7]
如果要将一个Set转为Array,除了可以用展开运算符外,还可以使用Array.from()
,如:
Array.from(new Set([1,3,3,5,5,7])); // [1, 3, 5, 7]
此外,可以实现集合的交集、并集、差集操作,如:
const s1 = new Set([1,2,3]);
const s2 = new Set([3,4,5]);
// s1并s2
new Set([...s1, ...s2]); // Set(5) {1,2,3,4,5}
// s1交s2
new Set([...s1].filter(x => s2.has(x))); // Set(1) {3}
// s1与s2的差集
new Set([...s1].filter(x => !s2.has(x))); // Set(2) {1,2}
三、Map
Map主要实现映射关系,虽然Object也可以实现映射,但是Object的限制为key必须要是string类型,map的基本用法为:
const m = new Map();
// 使用`set()`方法来新增或者修改一个键值对,返回值为结构对象本身
m.set(1, 'Number')
.set(1, 'Number One')
.set('Hello', 'String');
// 返回值 Map(1) {1 => 'Number One'}
// 使用`delete()`方法来删除一个键值对,返回值为删除成功与否的Boolean值
m.delete(1); // true
m.delete(1); // false
// 使用`get()`来获取一个键值,当获取的值不存在时,返回undefined
m.get(1); // undefined
m.get('Hello'); // 'String'
需要注意的是,set
和get
的时候,对于引用数据类型,是按引用地址作为键名的,并且对于NaN,是视为同一个键名的,所以有:
const m = new Map();
m.set(['a'], 'Hello');
m.set(['a'], 'World');
m; // Map(2) {Array(1) => "Hello", Array(1) => "World"}
m.get(['a']); // undefined
m.set(NaN, 'Not a number');
m.set(NaN, 'Not a Number');
m; // {Array(1) => "Hello", Array(1) => "World", NaN => "Not a Number"}
m.get(NaN); // 'Not a Number'
此外,还有如下的方法可供使用:
m.size
返回Map结构的成员总数m.has(key)
判断Map对象中是否含有键名为key
的成员m.clear()
清楚所有成员,没有返回值m.keys()
遍历方法,获取键名的迭代器m.values()
遍历方法,获取键值的迭代器m.entries()
遍历方法,获取键值对的迭代器(为一个双值数组)m.forEach()
遍历Map的所有成员
四、WeapSet和WeakMap
WeakSet和WeakMap的功能分别类似于Set和Map,不同之处在于:
1)它们都要求元素成员为引用类型
2)保存在结构里的元素,不会增加引用(即是弱引用的)
3)都不能获得size
,且不能遍历(因为是弱引用的,所以有可能遍历时还存在内存中,遍历完成后就消失了,所以干脆就直接不提供遍历方法)
1、WeakSet
只能添加引用类型,如果添加非引用类型,会报错:
const ws = new WeakSet();
ws.add(1); // 报错
ws.add({}); // 不报错
ws.add([]); // 不报错
基本用法:
1)构造函数可以接受初始化数据,但是数据成员必须是引用类型
const ws = new WeakSet([1, 2, 3, 4]); // 报错,因为1、2、3、4非引用
const ws2 = new WeakSet([
[1, 2],
[3, 4]
]);
// 不报错
2)WeakSet具有如下的方法:
WeakSet.prototype.add()
用于增加一个引用类型的元素,返回结构对象本身WeakSet.prototype.delete()
删除指定的元素,返回删除结果的Boolean值WeakSet.prototype.has()
判断元素是否在集合中,返回结果的Boolean值
2、WeakMap
WeakMap中,只接受引用类型作为键名(null除外),且WeakMap的键名所指向的对象,不计入垃圾回收机制:
const wm = new WeakMap();
wm.set({}, ''); // 不报错
wm.set([], ''); // 不报错
wm.set(1, ''); // 报错
同样的,也可以通过给构造函数传递初始化数据来新建一个WeakMap,但是仍然要保证键名为引用类型:
const wm = new WeakMap([
[1, 1]
]); // 报错
还需要注意的是,WeakMap只是键不计入垃圾回收机制,但是值还是计入的,即如果WeakMap里引用的值,会增加引用计数,如:
let keyObj = {};
let valueObj = {hint:'valueObj'};
const wm = new WeakMap();
wm.set(keyObj, valueObj);
valueObj = null;
wm.get(keyObj); // { hint: 'valueObj' }
此外,WeakMap仍然是不支持遍历的,所以它里面只有这些方法可用:get()
、set()
、has()
、delete()
3、应用
WeakSet()和WeakMap()的引入,主要是为了引用实例,但又不影响垃圾回收机制,从而降低内存泄漏(特别是DOM操作方面)。