ES6中的Set、WeakSet、Map、WeakMap详解
1.Set
基本用法
ES6提供了新的数据结构Set。它是一个构造函数,通过new生成的Set数据结构类似于数组,允许存储任何类型的值,无论是原始值或者对象引用,但是成员的值是唯一的,没有重复的值。
let s = new Set([1,2,3,1,3,4]) //=> {1, 2, 3, 4}
语法:new Set([val]);
参数:传递一个具有iterable接口的数据结构用来初始化,把它的所有元素不重复的添加到新的Set中。如果不指定参数或者参数为null,则新的Set为空。
返回值:一个新的Set对象。
具有iterable数据结构的数据类型有:Array、String、Map、Set、TypedArray(类数组)、函数的arguments对象、NodeList对象
let set1 = new Set('dafdv'); //=> {"d", "a", "f", "v"}
let set2 = new Set([1,2,3,4,1,2,3]); //=>{1, 2, 3, 4}
可以利用Set成员没有重复的特点进行数组去重,同样也可以对字符串进行去重
let ary = [1,2,3,1,3,4];
ary = [...new Set(ary)];
console.log(ary); //=>[1,2,3,4]
let str = 'ddewcads';
str = [...new Set(str)].join('');
console.log(str); //=>'dewcas'
Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),但是在Set中NaN等于NaN,而精确相等运算符认为NaN不等于自身。
LET set = new Set([NaN,NaN,,undefined]) //=>{NaN, undefined}
//ES6对数组的空位有了统一的处理,明确规定将空位转换为undefined
Set实例的属性和方法
Set 结构的实例有以下属性
- Set.prototype.constructor:默认是Set构造函数
- Set.prototype.size:返回Set实例的成员总数
let set = new Set('dexs');
console.log(set.size); //=>4
Set实例的方法可以分为两大类:操作方法和遍历方法
操作方法
- Set.prototype.add(val):增加某个值,返回Set结构本身
- Set.prototype.delete(val):删除某个值,返回一个布尔值,表示删除是否成功
- Set.prototype.has(val):返回一个布尔值,白女士该值是否为Set的成员
- Set.prototype.clear():清除所有成员,没有返回值
举例如下
let s = new Set();
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
遍历操作
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- Set.prototype.forEach():使用回调函数遍历每个成员
其中keys(),values(),entries()三个方法一般结合for…of 循环使用。因为这三个方法返回的是Iterator对象。由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为一样。
let set = new Set(['aaa', 'bbb', 'ccc']);
for (let item of set.keys()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.values()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.entries()) {
console.log(item);
}
// ["aaa", "aaa"]
// ["bbb", "bbb"]
// ["ccc", "ccc"]
Set 结构的实例和数组一样,也有forEach方法,可以遍历每个成员,没有返回值
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key,value));
// 1 1
// 4 4
// 9 9
2.WeakSet
WeakSet 结构与Set 类似,也是不重复的值的集合。但是,它与Set 有两个区别。
- WeakSet的成员只能是对象,而不能是其它类型的值
let ws = new WeakSet();
ws.add(123)
// TypeError: Invalid value used in weak set
ws.add(Symbol('sdfda'))
// TypeError: invalid value used in weak set
试图向 WeakSet 添加基本值,结构都是报错,因为WeakSet 只能放置对象。
- WeakSet 中的对象都是弱引用,即浏览器的垃圾回收机制不考虑 WeakSet 对该对象的引用。也可以理解为该对象只要其它东西不在引用,只有 WeakSet 引用,垃圾回收机制会销毁该对象。
语法
WeakSet是一个构造函数,可以使用new 命令,创建 WeakSet 数据结构
let ws = new WeakSet();
WeakSet 可以接受具有Iterable 接口的对象作为参数,并且该对象必须是二维及二维以上的
let a = [[1, 2], [3, 4]];
let ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
以上示例组数成员必须是引用数据类型
WeakSet 实例的方法
- WeakSet.prototype.add(value):向WeakSet 实例添加一个新的成员
- WeakSet.prototype.delete(value):清除 WeakSet 实例的只当成员
- WeakSet.prototype.has(value):返回一个布尔值,表示 WeakSet 中是否存在某个值
举例如下:
let ws = new WeakSet();
let obj = {};
let foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet 和Set不同,没有 Size 属性,没有办法遍历它的成员
let ws = new WeakSet();
ws.size // undefined
ws.forEach(item=>{
console.log(item);
}) // Uncaught TypeError: ws.forEach is not a function
WeakSet 不能遍历,是因为它的每个成员都是弱引用,垃圾回收机制不考虑它的引用,随时都有可能消失,所以在遍历的时候,可能刚刚遍历完,就不存在了。
3.Map
基本用法
在JS中,普通对象,本质上是键值对的集合(Hash结构),但是传统上只能把字符串当作键。
为了解决这个问题,ES6 提供了Map 数据结构,它类似于对象,也是键值对的集合,但是各种类型的值包括对象都可当作键,是一种比较完善的 Hash 结构。
let map = new Map([
['name', '张三'],
['title', 'Author']
]);
console.log(map); // {"name" => "张三", "title" => "Author"}
map.set('name','李四');
map.set('name','王五');
map.get('name') // 王五
map.get('age'); // undefined
console.log(map); // {"name" => "王五", "title" => "Author"}
上面代码中,使用 Map 结构中的 set 和 get 方法设置和读取 name 键,并且一个键多次设置值后面会覆盖前面的,如果读取一个未知的键,返回undefined。
// 例一
let map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
// 例二
let map = new Map();
let k1 = ['a'];
let k2 = ['a'];
map.set(k1, 111)
map.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
该代码例一, set 和 get 方法,表面是针对同一个键,但是实际上这是两个数组不同的实例,内存地址不一样,因此两者操作的并非同意换个键,所以 get 返回的undefined。同样例二操作的两两个键看起来虽然一样,但是最后却可以获取到两个值,因为 k1 === k2 等于false。由此可以看出,Map的键如果是对象,则该键和内存地址绑定,只要内存地址不同,就是两个不同的键。
let map = new Map();
map.set(-0,1);
map.get(+0); // 1
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.ste(NaN,123);
map.get(NaN); // 123
该例子可以看出,在Map中 -0 等于 +0,是一个键。布尔值 true 和字符串 ‘true’则是两个不同的键;undefined 和 null 也是两个不同的键; NaN 虽然不等于自身,但是 Map 将其是为一个键。
Map实例的属性和操作方法
属性
- size属性:返回 Map 结构的成员总数
- constructor属性:等于 Map 构造函数
操作方法
- Map.prototype.set(key,value)
设置 key 对应得值 value,然后返回整个 Map 结构。如果设置得时候 key 已经存在,则是修改值,否则就是设置新的值。
let map = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
// {1 => "a", 2 => "b", 3 => "c"}
因为每次设置返回的都是 Map 结构,所以可以使用链式写法
- Map.prototype.get(key)
获取的时候读取 key 对应的值,如果没有返回undefined。
let m = new Map();
m.set('hello', 'Hello ES6!')
m.get('hello') // Hello ES6!
m.get('sss') // undefined
- Map.prototype.has(key)
has 方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
let m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
-
Map.prototype.delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。 -
Map.prototype.clear()
clear方法清除所有成员,没有返回值。
遍历方法
- Map.prototype.keys():返回键名的遍历器。
- Map.prototype.values():返回键值的遍历器。
- Map.prototype.entries():返回所有成员的遍历器。
- Map.prototype.forEach():遍历 Map 的所有成员。
需要注意的是,Map 的遍历顺序就是插入顺序。
用法和 Set 一样
let map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
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结构与其它数据结构相互转换
-
转换为数组
前面已经说过了,一般基于扩展运算符转换 -
数组转换为 Map
将数组传入 Map 构造函数,就可以转换为 Map。
let map = new Map([
[true, 7],
[{foo: 3}, ['abc']]
]);
// {true => 7,Object {foo: 3} => ['abc']}
- Map 转换为对象
如果所有 Map 的键都是字符串,它也可以无损的转换为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
- 对象转换为 Map
对象转换为 Map 可以借用 Object.entries()。
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
4.WeakMap
WeakMap 与 Map 的区别有两点
- WeakMap 只接受对象作为键名,不接受其它类型的值作为键名
- WeakMap的键名对对象的引用不计入垃圾回收机制。
let map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
WeakMap 的属性和方法
WeakMap 和 WeakSet 一样没有遍历操作,即没有 keys()、values()、entries() 方法,也没有size属性。
WeakMap 无法清空,即不支持 clear方法。
WeakMap 只有四个方法可以使用:get()、set()、has()、delete()。
const wm = new WeakMap();
// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined