阮一峰es6要点总结——Set与Map数据结构
笔记,完全摘录自阮一峰
set数据结构
ES6提供的,新的数据结构,类似于数组。
特点:成员值唯一,没有重复的值
-
生成set数据结构
使用Set()
构造函数,可接受一个数组,类数组对象作为参数。 -
Set实例的方法和属性
属性 -
Set.prototype.constructor
:指向构造函数Set -
Set.prototype.size
:返回set
实例的成员总数
方法——操作方法(操作数据)/遍历方法(遍历成员)
- 操作方法
-
add(value)
:添加value,并返回Set结构本身 -
delete(value)
:删除value,返回布尔值,表示是否删除成功 -
has(value)
:返回布尔值,表示value是否为set
的成员 -
clear()
:清除所有成员
-
- 遍历操作
-
key()
:返回键名的遍历器 -
values()
:返回键值的遍历器 -
entries()
:返回键值对的遍历器 -
forEach()
:使用回调函数遍历每个成员
Set
的遍历顺序就是插入顺序
由于 Set
结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和values
方法的行为完全一致。
Set
结构的实例默认可遍历,它的默认遍历器生成函数就是它的values
方法。
-
Set
内部的值相等判断
使用“Same-value equality”算法,类似全等===
,区别是,NaN
等于自身。 -
应用
1.利用Set
结构的独特特性,实现数组去重
// 去除数组的重复成员
[...new Set(array)]
2.实现并集、交集、差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
3.在遍历中,改变原Set结构
// 方法一,利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二,利用`Array.from()`
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
WeakSet
特点:
-
成员唯一,且必须是对象。对象均为弱引用
-
弱引用性:如果没有其他对象引用WeakSet中的对象,那么垃圾回收机制会自动回收该对象占用的内存。
-
由于弱引用性,导致WeakSet的成员不可遍历(因为无法确保遍历成员的存在)。
用法:
构造函数WeakSet()
,可传入数组,或类数组等具有iterable接口的对象。
注意:成为WeakSet的成员的是参数的成员,而不是参数本身,所以
var b = [3, 4];
var ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
数组b的成员是数字,不符合WeakSet的特性,会报错。其他情况的参数也一样。
方法
-
WeakSet.prototype.add(value)
:添加新成员 -
WeakSet.prototype.delete(value)
:清除指定成员 -
WeakSet.prototype.has(value)
:返回布尔值,表示某个值是否存在。
应用
存储dom节点,不用担心,这些节点从文档移除是,造成内存泄漏
举例:闭包保存下一个dom节点,当dom节点删除时候,由于还存在引用,无法被回收。使用WeakSet就可以解决这个问题。
另一个例子
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。
Map
Object
,Map
均为Hash
结构(键值对集合)
区别:
Object
键名只能是字符串形式,Map
的键名可以是任意类型(包括对象)。
总的来说,Map
是一种更完善的Hash
结构的实现。如果你需要“键值对”的数据结构,Map比Object更合适。
构造函数
可传入一个数组参数。
var items = [
['name', '张三'],
['title', 'Author']
];
var map = new Map(items) //等价于下边的
var map = new Map();
items.forEach(([key, value]) => map.set(key, value));
特性
-
Map
键名可以是任意类型 -
Map
键名为对象类型时,与内存地址绑定,如果内存地址不同,就视为两个键
var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
var map = new Map();
var k1 = ['a'];
var k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
-
Map
键名为简单类型(数值,字符串,布尔值),只要两个值严格相等(===),就认为是一个键。 -
NaN
被视为同一个键,0
和-0
被视为同一个键 - 相同键多次赋值,后覆盖前
实例的属性和方法
-
size属性
返回Map成员总数 -
set(key, value)
设置键值对,返回Map本身,所以可以链式 -
get(key)
读取key
对应的值,找不到返回undefined -
has(key)
返回布尔值,表示是否存在 -
delete(key)
删除某键,返回布尔值,表示是否成功删除 -
clear()
清空所有成员,无返回值。 -
遍历方法
-
keys()
-
values()
-
entries()
-
forEach()
,接受第二个参数,绑定回调函数的this
需要特别注意的是,Map的遍历顺序就是插入顺序。
基本同Set
Map
结构的默认遍历器接口(Symbol.iterator
属性),就是entries
方法。
与其他数据结构的互相转换
-
Map
-->Array
使用扩展运算符(...)
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
-
Array
-->Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
-
Map
-->Object
键名转成字符串就行
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
-
Object
-->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
两种情况
一.键名全是字符串的,调用上边的,Map
-->Object
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
-->Array
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
一.正常情况
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}
二.特殊情况,整个json就是一个数组,每项都是两成员数组
unction jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
WeakMap
WeakMap
结构与Map
结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。
WeakMap
的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap
自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap
结构,当某个DOM元素被清除,其所对应的WeakMap
记录就会自动被移除。基本上,WeakMap
的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap
结构有助于防止内存泄漏。
一个例子
var wm = new WeakMap();
var element = document.querySelector(".element");
wm.set(element, "Original");
wm.get(element) // "Original"
element.parentNode.removeChild(element);
element = null;
wm.get(element) // undefined
与Map的区别
- 没有遍历操作
- 没有size
- 无法清空
这些都是因为WeakMap的键不被计入引用,被垃圾回收机制忽略。
WeakMap的方法
- get
- set
- has
- delete
WeakMap应用的典型例子
例子一
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
上面代码中,myElement
是一个 DOM 节点,每当发生click
事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap
里,对应的键名就是myElement
。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
例子二
部署私有属性
let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
let c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
上面代码中,Countdown
类的两个内部属性_counter
和_action
,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。