Set集合和Map集合

Set集合是一种无重复元素的列表。Map集合内含多组键值对,集合中每个元素分别存放着可访问的键名和它对应的值。

Set集合

创建

调用new Set()创建Set集合,调用add方法向集合中添加元素,访问size属性可以获取集合中目前的元素数量。

1
2
3
4
5
6
7
8
let set = new Set(),
key1 = {},
key2 = {};
set.add(5);
set.add('5');
set.add(key1);
set.add(key2);
console.log(set.size); //4

Set集合中,不会对所存值进行强制的类型转换,数字5和字符串”5”是作为两个独立元素存在。由于key1key2不会转换成字符串,因而它们再Set集合中是两个独立的元素。

如果多次调用add方法,并传入相同的值作为参数,那么后续的调用实际上会被忽略。

1
2
3
4
5
let set = new Set();
set.add(5);
set.add('5');
set.add(5);
console.log(set.size); //2

可以用数组来初始化Set集合,Set构造函数会过滤掉重复的值以保证集合中的元素各自唯一。可以通过has方法检测Set集合中是否存在某个值。

1
2
3
4
let set = new Set([2,4,5,6,4,7,8,5,7]);
console.log(set.size); //6
console.log(set.has(5)); //true
console.log(set.has(9)); //false

Set构造函数可以接受所有可迭代对象作为参数,构造函数通过迭代器从参数中取值。

移除

调用delete()方法可以移除Set集合中的某一个元素,调用clear()方法会移除集合中所有元素。

1
2
3
4
5
6
7
8
9
10
11
let set = new Set();
set.add(5);
set.add('5');
console.log(set.has(5)); //true
set.delete(5);
console.log(set.has(5)); //false
console.log(set.size); //1
set.clear();
console.log(set.has('5')); //false
console.log(set.size); //0

Set集合的forEach方法

forEach方法的回调函数接受3个参数

  • Set集合中元素的值
  • 与第一个参数一样的值
  • 被遍历Set集合本身

Set集合没有键名,第一二个参数相等,保证其他forEach方法的一致性。

1
2
3
4
5
6
let set = new Set([4,7]);
set.forEach(function(value, key, ownerSet){
console.log(value,key);
console.log(set === ownerSet);
})
//4 4 true 7 7 true

Set集合中的forEach方法中,第二个参数也与数组的一样,传递到回调函数中来指代this值。

Set集合转换为数组

使用展开运算符(...)可以将Set集合(可迭代对象)转换为数组

1
2
3
let set = new Set([1,2,4,3,2,4,5,7,5]),
arr = [...set];
console.log(arr); //[ 1, 2, 4, 3, 5, 7 ]

Weak Set集合

将对象存储在Set的实例与存储在变量中完全一样,只要Set实例中的引用存在,垃圾回收机制就不能释放该对象的内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
let set = new Set(),
key = {};
set.add(key);
console.log(set.size); //1
//移除原始引用
key = null;
console.log(set.size); //1
//重新取回原始引用
key = [...set][0]

示例中将变量key设置为null,清除了对初始对象的引用,但Set集合却保留了这个引用。有时候可能需要当其他所有引用不再存在时,Set集合中的这些引用随之消失。

ES6引入了另外一个类型,Weak Set集合(弱引用Set集合)。Weak Set集合只会存储对象的弱引用,不可以存储原始值,集合中弱引用是对象的唯一的引用,则会被回收并释放相应内存。

  • 创建
    WeakSet构造函数可以创建Weak Set集合,也向构造函数可以传入一个可迭代对象来创建Weak Set集合。集合支持3个方法addhasdelete
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let set = new WeakSet(),
    key = {};
    set.add(key);
    console.log(set.has(key));
    //移除对象key的最后一个强引用,Weak Set中的引用也自动移除
    key = null;
    let key1 = {},
    key2 = {},
    set1 = new WeakSet([key1, key2]);

WeakSet构造函数中,不接受任何原始值,否则会抛出错误。

两种Set类型的主要区别

  • WeakSet实例中,如果向addhasdelete这3个方法中传入非对象参数都会导致程序报错。
  • Weak Set集合不可迭代,所以不能用于for-of循环。
  • Weak Set不暴露任何迭代器(例如keysvalues方法)所以无法通过程序本身来检测其中内容。
  • Weak Set不支持forEach方法,不支持size属性。

Map集合

Map类型是一种储存着键值对的有序列表,其中键名和对应的值支持所有数据类型。键名不会被强制转换成其他形式,其的等价判断通过Object.is方法实现。

创建

调用new Map()创建Map集合,调用set方法并传入键名和对应值作为两个参数。调用get方法传入键名可以获取对应的值,如果传入的键名不存在则会返回undefined。也可以向Map构造函数传入数组来创建一个Map集合,数组中每一个元素都是一个子数组,子数组中包含一个键值对的键名与值两个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let map1 = new Map();
let key = {};
map1.set('title', 'Understanding');
map1.set('year', 2000);
map1.set(key, 'lala');
console.log(map1.get('title')); //'Understanding'
console.log(map1.get('year')); //2000
console.log(map1.get(key)); //'lala'
console.log(map1.get('name')); //undefined
let map2 = new Map([['name', 'Nicholas'], ['age', 25]]);
console.log(map2.get('name')); //'Nicholas'
console.log(map2.get('age')); //25

Map集合支持的方法

MapSet集合有3个通用的方法

  • has检测指定键名在Map集合中是否存在
  • deleteMap集合中删除指定键名及其对应的值
  • clear移除Map集合中的所有键值对

Map集合也支持size属性,代表当前集合中包含的键值对数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let map = new Map();
map.set('name', 'Nicholas');
map.set('age', 25);
console.log(map.size); //2
console.log(map.has('name')); //true
map.delete('name');
console.log(map.has('name')); //false
console.log(map.size); //1
map.clear();
console.log(map.size); //0
console.log(map.has('age')); //false

Map集合的forEach方法

Map集合中forEach方法和Set集合的forEach方法类似,其回调函数接受3个参数

  • Map集合中元素的值
  • 值对应的键名
  • Map集合本身
1
2
3
4
5
6
7
8
9
let map = new Map([['name', 'Nicholas'], ['age', 20]]);
map.forEach((value, key, ownerMap) => {
console.log(`${key} ${value}`);
console.log(ownerMap === map);
})
//'name' 'Nicholas'
//true
//'age' 20
//true

也可以指定forEach函数的第二个参数作为回调的this

Weak Map集合

Weak Map类型是一种存储键值对的无序列表,键名必须是非null类型对象,键名对应的值可以是任意类型。

Weak Set集合类似,Weak Map是弱引用Map集合,用于存储对象的弱引用,集合中保存的是对象的弱引用,如果弱引用之外不存在其他的强引用,引擎垃圾回收机制会自动回收这个对象,同时也会移除Weak Map集合中的键值对。但只有集合中的键名遵从这个规则,键名对应的值如果是一个对象,则保存的是对象的强引用,不会触发垃圾回收机制。

  • 初始化
    调用WeakMap构造函数创建一个Weak Map,通过set方法添加数据,通过get方法获取数据。也可以通过传入一个数组,数组内的元素为一个数组,该数组有两个元素构成,第一个是键名,传入的值必须是非null对象,第二个是键名对应的值,可以是任意值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let map1 = new WeakMap(),
    ele = document.querySelector('.ele');
    map1.set(ele, 'original');
    let value = map.get(ele);
    console.log(value); //'original'
    //移除ele元素
    ele.parentNode.removeChild(ele);
    ele = null;
    // Weak Map 集合为空
    let key1 = {},
    key2 = {},
    map2 = new WeakMap([[key1, 'hello'], [key2, 'world']]);
    console.log(map2.has(key1)); //true
    console.log(map2.get(key1)); //'hello'
  • 支持的方法
    Weak Map集合只支持两个可以操作键值对的方法:hasdelete方法,has方法用于检测给定键在集合中是否存在,delete方法可以移除指定的键值对。和Weak Set一样,二者都不支持键名枚举,也不支持clear方法。

  • 私有对象数据
    Weak Map集合一般用于储存DOM元素,还有一个实际的应用是存储对象实例的私有数据

在ES5中,可以通过下面这种模式创建一个接近真正私有数据的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Person = (function(){
var privateData = {},
privateId = 0;
function Person(name){
Object.defineProperty(this, '_id', {
value: privateId++
})
privateData[this._id] = {
name: name
}
}
Person.prototype.getName = function(){
return privateData[this._id].name
}
return Person
}())

Person又一个立即调用函数表达式生成,包括两个私有变量:privateDataprivateIdprivateData对象保存的是每个实例的私有信息,privateId则为每个实例生成一个独立的ID。当调用Person构造函数时,属性_id的值会加1,这个属性不可枚举,不可写,不可配置。

privateData对象保存了所有实例对应的名称,调用getName函数,即可通过this._id获取当前示例的ID,并以此从privateData对象提取实例名称。

这种方法最大的问题是,如果不主动管理,由于无法获知对象实例何时被销毁,因此privateData中的数据永远都不会消失。使用Weak Map集合就可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
var Person = (function(){
let privateData = new WeakMap();
function Person(name){
privateData.set(this, {name: name})
}
Person.prototype.getName = function(){
return privateData.get(this).name
}
return Person
}())

由于Person对象实例可以直接作为集合的键使用,无需维护一套ID体系来跟踪数据。这样只要对象实例被销毁,相关信息也会被销毁,保证了信息的私有性。