ES6 WeakSet和WeakMap

WeakSet和WeakMap

(1)WeakSet和WeakMap一些相似的特点

1.WeakSet的成员只能是对象,WeakMap只接受对象(null除外)作为键名
    let weakset = new WeakSet([1, 2, 3]);
    //TypeError: Invalid value used in weak set

    let weakmap=new WeakMap([[0,2],[0,3]]);
    //TypeError: Invalid value used as weak map
    //这里的0为数字而不是对象

上面的代码均会报错。

   let weakmap=new WeakMap();

    let key={};
    weakmap.set(key,1);

    console.log(weakmap);
    //WeakMap { {}=>1 }

WeakMap只接受对象(null除外)作为键名,其他类型的值都可以作为键值。

2.不计入垃圾回收机制

WeakSet中的对象不计入垃圾回收机制,WeakMap中键名指向的对象不计入垃圾回收机制。

常见垃圾收集方式——标记清除

我们先来复习一下垃圾回收机制:在编写javascript程序时,开发人员不用关心内存的使用问题,所需内存的分配以及无用内存的回收实现了自动管理,垃圾收集器会按照固定的时间间隔,找出那些不再继续使用的变量,然后释放其占用的内存。
javascript中通常有两种垃圾收集方式:
1.标记清除(Mark-and-Sweep)
2.引用计数(Reference-counting)
大多数浏览器使用的是标记清除策略,因为引用计数策略无法解决因为循环引用而造成内存泄露问题。
标记清除指的是:
当变量进入执行环境,就将这个变量标记为“进入环境”,当变量离开环境时,将其标记为“离开环境”。垃圾收集器在运行的时候会给储存在内存中的所有变量加上标记(不同的浏览器可能使用不同的标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后仍然被加上标记的变量将被视为准备删除的变量,之后垃圾收集器运行的时候(不同浏览器垃圾收集器运行的时间间隔互有不同)销毁带标记的值并回收它们占用的内存。

3.WeakSet和WeakMap没有遍历操作,没有size属性,没有clear方法

WeakSet中的对象是弱引用,WeakMap的键名所引用的对象也是弱引用。垃圾回收机制不将它们的引用考虑在内。一旦不再需要,垃圾回收机制会自动将它们回收,不用手动删除,避免了内存的泄露。

因为WeakSet的成员和WeakMap引用的键名可能会随时消失,所以WeakSet和WeakMap没有遍历操作,也没有size属性,并且两者都没有clear方法。

对于WeakMap来说,弱引用的只是键名而不是键值
    let weakmap = new WeakMap();

    let key = {};
    let obj = {"foo": 1};
    weakmap.set(key, obj);

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

    obj = null;

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

键值obj是正常引用的。所以,即使在WeakMap外部消除了obj的引用,WeakMap内部的引用依然存在。

    let key = {};
    let obj = {"foo": 1};
    let weakmap = new WeakMap([[key,obj]]);

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

    key = {"rrr":1};
    obj = {"foo": 2};

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

即使在WeakMap外部改变了键名key和键值obj的外部引用,但在WeakMap内部依然引用的是构造时的引用值。

(2)WeakSet

WeakSet结构与Set类似,也是不重复的值的集合。

1.基本语法
WeakSet构造函数

任何具有iterable接口的对象都可以成为WeakSet构造函数的参数。以数组为例,数组的所有成员都会自动成为WeakSet实例对象的成员,而数组的成员只能是对象。

    let weakset = new WeakSet([[1, 2], [2, 3]]);
    console.log(weakset);
    // WeakSet [[1,2],[2,3]]
    // WeakSet [[2,3],[1,2]]
    //都有可能出现

    let set=new Set([[1, 2], [2, 3]]);
    console.log(set);
    //Set [[1,2],[2,3]]

以上代码可以看到WeakSet中的元素顺序和Set中的元素顺序是不同的。
WeakSet中元素顺序并不是确定的,当使用add()后,元素的顺序还会发生改变:

    let weakset = new WeakSet([[1, 2], [2, 3]]);
    console.log(weakset);
    // WeakSet [[4,5],[1,2],[2,3]]
    // WeakSet [[2,3],[4,5],[1,2]]
    //等等各种顺序都有可能出现
    weakset.add([4,5]);
    console.log(weakset);
    // WeakSet [[4,5],[1,2],[2,3]]
    // WeakSet [[2,3],[4,5],[1,2]]
    //等等各种顺序都有可能出现

两次运行相同的代码,在WeakSet中元素的顺序有可能是不同的。

2.操作方法

WeakSet没有遍历操作,没有size属性,没有clear方法。

add(value)

向WeakSet实例添加一个新成员。

delete(value)

清除WeakSet实例的指定成员。

has(value)

返回一个布尔值,表示某个值是否在WeakSet实例中。

3.使用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,也不会出现内存泄漏。

(3)WeakMap

WeakMap结构与Map结构类似也用于生成键值对的集合。

1.基本语法
WeakMap构造函数

WeakMap可以接受一个数组,作为构造函数的参数:

    let k1 = [1, 2];
    let k2 = [2, 3];

    let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123 }
    //WeakMap { [1,2]=>123,[2,3]=>234 }
    //都有可能出现
    
   let map=new Map([[k1, 123], [k2, 234]]);
    console.log(map);
    //WeakMap { [2,3]=>234,[1,2]=>123 }

以上代码可以看到WeakMap中的元素顺序和Map中的元素顺序是不同的。
WeakMap中元素顺序并不是确定的,当使用set()后,元素的顺序还会发生改变:

    let k1 = [1, 2];
    let k2 = [2, 3];

    let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
    //WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
    //WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
    //等等各种顺序都有可能出现
    weakmap.set([4,5],456);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
    //WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
    //WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
    //等等各种顺序都有可能出现

两次运行相同的代码,在WeakMap中元素的顺序有可能是不同的。

2.操作方法

WeakMap没有遍历操作,没有size属性,没有clear方法。
WeakMap只有以下四种方法:

get()
set()
has()
delete()
3.使用WeakMap的例子
以DOM作为键名的场景
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 节点删除,该状态就会自动消失,不存在内存泄漏风险。

实现注册监听事件的listener对象
    const listener = new WeakMap();
    
    listener.set(element1, handler1);
    listener.set(element2, handler2);
    
    element1.addEventListener("click",listener.get(element1),false);
    element2.addEventListener("click",listener.get(element2),false);

上面的代码中,监听函数放在WeakMap里面。一旦DOM对象消失,与它绑定的监听函数也会自动消失。

部署私有属性
const _counter = new WeakMap();
const _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)();
    }
  }
}

const c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()
// DONE

上面代码中,Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

参考文献:《ECMAScript 6 入门》阮一峰

猜你喜欢

转载自blog.csdn.net/weixin_42695446/article/details/84439113