新增数据结构Set、Weak Set、Map、Weak Map

186 阅读6分钟

Set / Map 是两种存储结构

Set:

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

Set是一个构造函数,可以传入一个数组初始化默认值。

let sun = new Set([1,2,3,4,56,1,2,3,2]);
console.log(sun) //Set(5) {1, 2, 3, 4, 56}

Set内部还提供了一些内置属性和方法

set.size //Set实例的成员个数(数组的长度)
sun.size //5

set.add(value)//为Set的实例添加值
sun.add(7)//Set(6) {1, 2, 3, 4, 56, 7}

set.delete(value)//删除Set实例的值 如果存在value,返回true,反之,返回false
sun.delete(1)//true
sun //Set(5) {2, 3, 4, 56, 7}

set.has(value) //判断传入的参数是否为set的成员 如果存在value,返回true,反之,返回false
sun.has(2) //true

set.clear() //清除set中的所有成员
sun.clear() //Set(0) {}

set.entries() //方法返回一个新的迭代器对象 ,这个对象的元素是类似 [value, value] 形式的数组

value 是集合对象中的每个元素,迭代器对象元素的顺序即集合对象中元素插入的顺序。由于集合对象不像 Map 对象那样拥有 key,然而,为了与 Map 对象的 API 形式保持一致,故使得每一个 entry 的 key 和 value 都拥有相同的值,因而最终返回一个 [value, value] 形式的数组。

var mySet = new Set();
mySet.add("foobar");
mySet.add(1);
mySet.add("baz");

var setIter = mySet.entries();

console.log(setIter.next().value); // ["foobar", "foobar"]
console.log(setIter.next().value); // [1, 1]
console.log(setIter.next().value); // ["baz", "baz"]

遍历方法 与entries()一样,其他的遍历也是关于键值对的

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

由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

const set = new Set(['a', 'b', 'c'])

for (let item of set.keys()) {
  console.log(item)
}
// a
// b
// c

for (let item of set.values()) {
  console.log(item)
}
// a
// b
// c

for (let item of set.entries()) {
  console.log(item)
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]

// 直接遍历set实例,等同于遍历set实例的values方法
for (let i of set) {
  console.log(i)
}
// a
// b
// c

set.forEach((value, key) => console.log(key + ' : ' + value))

// a: a
// b: b
// c: c

Set中的特殊值

Set内部使用Object.is()方法来判断两个数据项是否相等,所以对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

+0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复

undefined 与 undefined 是恒等的,所以不重复

NaN 与 NaN 是不恒等的,但是在 Set 中认为NaN与NaN相等,所有只能存在一个,不重复。

Weak Set

WeakSet结构与Set类似,也是不重复的值的集合。

WeakSet是一个构造函数,可以传入一个类数组初始化默认值。

WeakSet的成员只能是对象,而不能是其他类型的值。

WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用

let set = new Set();
let key={};
let key2 = {};
set.add(key);
set.add(key2);
console.log(set.size); //2

key=null;
console.log(set.size); //2

可以看出就算对象key置为null,但是由于是强引用的方式,Set实例还存在,对象key依然不会被回收。

如果想让对象key正常释放的话,可以使用Weak Set,此时,存放的是对象的弱引用,当对象只被Set弱引用的话,并不会阻止对象实例被回收。

WeakSet是不可遍历的

Weka Set同Set的用法几乎一致。可以使用add()方法增加数据项,使用has()方法检查Weak Set中是否包含某项,以及使用delete()方法删除某一项。

let set = new WeakSet();
let key = {};   
set.add(key);
console.log(set.has(key)); //true
set.delete(key);
console.log(set.has(key)); //false

对于Weak Set和Set之间的重要差异:

  1. 对于Weak Set实例,若调用了add()方法时传入了非对象的参数,则会抛出错误。如果在has()或者delete()方法中传入了非对象的参数则会返回false;

  2. Weak Set不可迭代,因此不能用于for-of循环;

  3. Weak Set 无法暴露出任何迭代器(例如 keys() 与 values() 方法) ,因此没有任何编程手段可用于判断 Weak Set 的内容;

  4. Weak Set没有forEach()方法;

  5. Weak Set没有size属性;

Map

刚才Set是针对数组的,那么Map则是针对对象的

ES6中提供了Map数据结构,能够存放键值对,同Set一样,键的去重是通过Object.is()方法进行比较,键的数据类型可以是基本类型数据也可以是对象,而值也可以是任意类型数据。

 let map = new Map();
 map.set('title','hello world');
 map.set('year','2018');

 console.log(map.size); //2
 

通过set()方法往Map中增加了两个键值对后,可以看到Map的大小就为2;

通过get()方法可以从Map中提取值

 let map = new Map();
 map.set('title','hello world');
 map.set('year','2018');

 console.log(map.get('title')); // hello world

has(),delete()以及clear()方法

为了和Set的操作保持一致,Map中同样有has()方法,用来检查某个数据项是否存在于Map中,使用delete方法可以从Map中删除一个数据项,使用clear方法可以删除Map中所有的数据项

let map = new Map();
map.set('title','hello world');
map.set('year','2018');

console.log(map.has('year')); //true
map.delete('title');
console.log(map.has('title')); //false
map.clear();
console.log(map.size); //0

与Set的初始化一样,Map也可以用数组来初始化Map,该数组中的每一个数据项也是数组,数组的第一个数据项代表键值对的键,第二个数据项是键值对的值:

//使用数组来创建Map
let map = new Map([['title','hello world'],['year','2018']]);
console.log(map.has('title')); //true
console.log(map.has('year')); //true
console.log(map.size); //2

与Set一样,Map也拥有forEach方法,该方法也接收一个回调函数,该回调函数有三个参数:

键值对的键;

键值对的值;

当前Map对象引用;

 let map = new Map([['title','hello world'],['year','2018']]);
 map.forEach((value,key,ownerMap)=>{
     console.log(value);
     console.log(key);
 });

 hello world
 title
 2018
 year
 

与Set的forEach一样,可以在回调函数中传入this引用。

** Weak Map**

Weak Map的初始化

Weak Map的键必须是对象,值可以是任意类型,初始化同Map一样,也可是使用数组来创建一个 Weak Map :

//使用数组来创建一个Weak Map
let key = {};
let key2 = {};
let map = new WeakMap([[key,'hello'],[key2,'world']]);
console.log(map.get(key)); //hello
console.log(map.get(key2)); //world

has方法以及delete方法

与Map一样,可以使用has()方法来检查Weak Map中是否存在某一个键值对,使用delete()方法可以删除一个键值对。clear() 方法不存在,这是因为没必要对键进行枚举,并且枚举 Weak Map 也是不可能的,这与 Weak Set 相同:

let key = {};
let key2 = {};
let map = new WeakMap([[key,'hello'],[key2,'world']]);

map.delete(key);
console.log(map.has(key)); //false

Weak Map 的用法与局限性

当决定是要使用 Weak Map 还是使用正规 Map 时,首要考虑因素在于你是否只想使用对象类型的键。如果你打算这么做,那么最好的选择就是 Weak Map 。因为它能确保额外数据在不再可用后被销毁,从而能优化内存使用并规避内存泄漏。

要记住 Weak Map 只为它们的内容提供了很小的可见度,因此你不能使用 forEach() 方法、size 属性或 clear() 方法来管理其中的项。如果你确实需要一些检测功能,那么正规 Map会是更好的选择,只是一定要确保留意内存的使用。