美文网首页
es6中Set、Map、WeakSet和WeakMap的区别

es6中Set、Map、WeakSet和WeakMap的区别

作者: 简单tao的简单 | 来源:发表于2024-01-24 17:58 被阅读0次

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 的作用
  1. 数组去重
  2. 实现并集(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 的区别?
  1. 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(…)
  1. WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
  2. WeakSet没有size属性,没有办法遍历它的成员。
为什么WeakSet没有size属性?为啥不能遍历它的成员?

WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。

WeakSet的作用
  1. 储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。因为WeakSet是弱引用。
  2. 保证了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 的区别
  1. Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应
Map 和 Set 的相同点

相同点:

  1. 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的作用
  1. 我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
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 的相同点
  1. WeakMap只接受对象作为键名(null除外),WeakSet的成员只能是对象
  2. WeakMap 和 WeakSet中的对象都是弱引用,不计入垃圾回收机制
WeakMap的作用

WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失,如果消失了,其所对应的WeakMap记录就会自动被移除,助于防止内存泄漏。

  1. 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
  1. 部署私有属性
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()

相关文章

网友评论

      本文标题:es6中Set、Map、WeakSet和WeakMap的区别

      本文链接:https://www.haomeiwen.com/subject/xwwuodtx.html