愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

《ECMAScript6入门》学习笔记之Set和Map

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'

需要注意的是,setget的时候,对于引用数据类型,是按引用地址作为键名的,并且对于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操作方面)。