Set
ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值Set(4) {1, 2, 3, 4}
Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。
// 例一
var set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs() {
return [...document.querySelectorAll('div')];
}
var set = new Set(divs());
set.size // 56
// 类似于
divs().forEach(div => set.add(div));
set.size // 56
Set 认为NaN等于自身,而精确相等运算符认为NaN不等于自身。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
两个对象总是不相等的。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
console.log(set) //Set(2) {{…}, {…}}
Set实例的属性和方法
Set结构的实例有以下属性。
- set.constructor:构造函数,默认就是Set函数。
- set.size:返回Set实例的成员总数。
Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
- set.add(value):添加某个值,返回Set结构本身。
- set.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- set.has(value):返回一个布尔值,表示该值是否为Set的成员。
- set.clear():清除所有成员,没有返回值。
Set结构的实例有四个遍历方法,可以用于遍历成员。
- set.keys():返回键名的遍历器
- set.values():返回键值的遍历器
- set.entries():返回键值对的遍历器
- set.forEach():使用回调函数遍历每个成员
如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;另一种是利用Array.from方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
Set 的作用
- 数组去重
- 实现并集(Union)、交集(Intersect)和差集(Difference)
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}
WeakSet
WeakSet结构与Set类似,也是不重复的值的集合
Set 和 WeakSet 的区别?
- WeakSet的成员只能是对象,而不能是其他类型的值
var ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
a是一个数组,它有两个成员,也都是数组。将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。
var a = [[1,2], [3,4]];
var ws = new WeakSet(a);
数组b的成员不是对象,加入WeaKSet就会报错
var b = [3, 4];
var ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
- WeakSet没有size属性,没有办法遍历它的成员。
为什么WeakSet没有size属性?为啥不能遍历它的成员?
WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。
WeakSet的作用
- 储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。因为WeakSet是弱引用。
- 保证了Foo的实例方法,只能在Foo的实例上调用
const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。
WeakSet实例的属性和方法
- weakSet.add(value):向WeakSet实例添加一个新成员。
- weakSet.delete(value):清除WeakSet实例的指定成员。
- weakSet.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
Map
Map 也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
var m = new Map();
var 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也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
var map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
var items = [
['name', '张三'],
['title', 'Author']
];
var map = new Map();
items.forEach(([key, value]) => map.set(key, value));
Map实例的属性和方法
- map.size 属性返回Map结构的成员总数。
- map.set(key, value) set方法设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
- map.get(key) get方法读取key对应的键值,如果找不到key,返回undefined。
- map.has(key) has方法返回一个布尔值,表示某个键是否在Map数据结构中。
- map.delete(key) delete方法删除某个键,返回true。如果删除失败,返回false。
- map.clear() clear方法清除所有成员,没有返回值。
Map原生提供三个遍历器生成函数和一个遍历方法。
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
- forEach():遍历Map的所有成员。
Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...)。
let 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 和 Object 的区别
- Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应
Map 和 Set 的相同点
相同点:
- Set 和Map都认为NaN等于自身,而精确相等运算符认为NaN不等于自身。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
console.log(set) // Set {NaN}
let map = new Map();
map.set(NaN, 111);
map.set(NaN, 222)
console.log(map) //Map(1) {NaN => 222}
Map的作用
- 我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
var map = new Map();
map.set(['a'], 555);
map.set(['a'],666)
console.log(map) //Map(2) {Array(1) => 555, Array(1) => 666}
Map与其他数据结构的互相转换
(1)Map转为数组
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2)数组转为Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
(3)Map转为对象
如果所有Map的键都是字符串,它可以转为对象。
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 }
(4)对象转为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 ] ]
(5)Map转为JSON
Map转为JSON要区分两种情况。一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON。
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。
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"]]]'
(6)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}')
// Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个JSON就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为JSON的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
WeakMap
WeakMap 和 WeakSet 的相同点
- WeakMap只接受对象作为键名(null除外),WeakSet的成员只能是对象
- WeakMap 和 WeakSet中的对象都是弱引用,不计入垃圾回收机制
WeakMap的作用
WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失,如果消失了,其所对应的WeakMap记录就会自动被移除,助于防止内存泄漏。
- DOM节点作为键名,而不用担心这些节点从文档移除时,会引发内存泄漏。因为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
- 部署私有属性
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,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。
WeakMap实例的属性和方法
- wm.get()
- wm.set()
- wm.has()
- wm.delete()
网友评论