Set和Map数据结构
1. ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
Set 内部判断两个值是否不同,使用的算法叫做“Same-value equality”,它类似于精确相等运算符(===
),主要的区别是NaN
等于自身,而精确相等运算符认为NaN
不等于自身。
Set内部NaN等于NaN
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // {NaN} 只存在一个NaN, 说明Set内部的NaN是等于自身的
Set内部两个对象是不相等的
let set = new Set();
set.add({});
set.size // 1
set.add({})
set.size // 2
四个操作方法
add delete has clear
let set = new Set()
set.add(1) set.delete(1) set.has(1) set.clear()
Array.from
方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
Array.from(items) // [1, 2, 3, 4, 5]
数组去重
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 1, 1, 2, 2, 34, 5]) // [1, 2, 34, 5]
forEach(): 对数组中的每一项执行某方法
map(): 数组中的每一项执行某个函数并返回执行后结果的数组
2. 遍历
Set内部使用for...of...循环,所以Set可以使用扩展运算符展开
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
数组去重
let set = [1, 2, 3, 2, 1];
let unique = [...new Set(set)];
unique // [1, 2, 3]
并集
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
let union = new Set([...a, ...b])
union // Set(4) {1, 2, 3, 4}
[...union] // [1, 2, 3, 4]
交集
let intersect = new Set([...a].filter(x => b.has(x)))
intersect // {2, 3}
差集
let difference = new Set([...a].filter(x => !b.has(x)))
difference // {1}
3. WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。WeakSet 的成员只能是对象,而不能是其他类型的值。
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
ES6 规定 WeakSet 不可遍历。
WeakSet 是一个构造函数,可以使用new
命令,创建 WeakSet 数据结构。
const ws = new WeakSet();
作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
ws // [[1, 2], [3, 4]]
const b = [3, 4];
const ws = new WeakSet(b); // Invalid value used in weak set
上面报错是因为 数组逇成员是数组或类似数组的对象, 数组b的成员不是数组或类似数组的成员,而数组a的成员是数组或类似数组的对象。
WeakSet.prototype.add(value) 向WaekSet实例中添加一个新成员
WeakSet.prototype.delete(value) 从WaekSet删除一个指定成员
WeakSet.prototype.has(value) 返回一个布尔值, 判断一个值是否存在WeakSet实例中
WeakSet 没有size
属性,没有办法遍历它的成员。
WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了、
4. Map
const m = new Map();
const o = {p: 'Hello world'};
m.set(o, 'content');
m.get(o); // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
['name', '张三'],
['title', 'Author']
])
map.size // 2
map.has('name') // true
map.get('name') // 张三
只有对同一个对象的引用,Map 结构才将其视为同一个键。
const map = new Map();
map.set(['a'], '555');
map.get(['a']) // undefined
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
Map 结构的实例有以下属性和操作方法。
size属性: size
属性返回 Map 结构的成员总数。
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map // [['foo', true], ['bar', false]]
set(value, key)
map.set(1, 'one').set(2, 'second').set(3, 'third'); // 可以链式写
get(key)
get
方法读取key
对应的键值,如果找不到key
,返回undefined
。
const m = new Map();
const hello = function() {console.log('hello')}
m.set(hello, 'hello es6')
m.get(hello) // "hello es6"
has(key)
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('year') // false
delete(key)
delete
方法删除某个键,返回true
。如果删除失败,返回false
。
m.delete(262) // true
m.delete(262) // false 因为已经删除 262key 不存在 所以返回false
clear()
clear
方法清除所有成员,没有返回值。
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...
)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three']
])
[...map.keys()] // [1, 2, 3]
结合数组的map
方法、filter
方法,可以实现 Map 的遍历和过滤(Map 本身没有map
和filter
方法)。
与其他数据结构的互相转换
Map转为数组
Map 转为数组最方便的方法,就是使用扩展运算符(...
)。
const myMap = new Map();
myMap.set(true, 7).set({foo:3}, ['abc'])
[...myMap] // [[true, 7], [{foo:3}, ['abc']]]
数组转为Map
将数组传入 Map 构造函数,就可以转为 Map。
let map = [...myMap];
new Map(map) // 数组转为Map 作为参数传给Map就可以将数组转为Map
Map转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
console.log(obj, 'obj');
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
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false}); // {"yes" => true, "no" => false}
Map转为JSON(对象JSON)
Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap) // "{"yes":true,"no":false}"
Map转为JSON(数组JSON)
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap) // "[[true,7],[{"foo":3},["abc"]]]"
JSON转为Map
JSON 转为 Map,正常情况下,所有键名都是字符串
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}') // {yes: true, no: false}
5. WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合。
WeakMap
与Map
的区别有两点
①WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名。
②WeakMap
的键名所指向的对象,不计入垃圾回收机制。