【ECMAScript6】Set与Map

一、集合Set

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯
一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进
行遍历,集合的属性和方法:

  • size 返回集合的元素个数
  • add 增加一个新元素,返回当前集合
  • delete 删除元素,返回 boolean 值
  • has 检测集合中是否包含某个元素,返回 boolean 值
  • clear 清空集合,返回 undefined
1.1 基本API
        let s = new Set()
        let s1 = new Set(['AAA', 'BBB', 'CCC', 'DDD'])
        console.log(s1.size)
        console.log(s1.add('XXX'))
        console.log(s1.delete('AAA'))
        console.log(s1.has('CCC'))

        s1.clear()
        console.log(s1)

        for(let v of s1) {
            console.log(v)
        }

在这里插入图片描述

1.2 集合的一些基本操作
        let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]

        // 1.数组去重
        let result = [...new Set(arr)]
        console.log(result)

        // 2. 求两个集合的交集
        let arr2 = [4, 5, 6, 5, 6]
        let result1 = [...new Set(arr)].filter(item => {
            let s2 = new Set(arr2)
            if(s2.has(item)){
                return true 
            }else {
                return false
            }
        })
        console.log(result1)

        let result2 = [...new Set(arr)].filter(item => {return new Set(arr2).has(item)})
        console.log(result2)

        // 3. 求两个集合的并集
        let union = [...new Set([...arr, ...arr2])]
        console.log(union)

        // 4. 求两个集合的差集
        // arr与arr2的差集:arr中有的元素而arr2中没有
        let diff1 = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
        console.log(diff1)
        // arr2与arr的差集:arr2中有的元素而arr中没有
        let diff2 = [...new Set(arr2)].filter(item => !(new Set(arr).has(item)))
        console.log(diff2)

在这里插入图片描述


二、弱集合WeakSet

2.1 基本API
  • WeakSet.prototype.add(value) :向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value) :清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value) :返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
  • WeakSet中的值任何时候都可能被销毁,所以没有必要提供迭代其值的能力,故没有迭代和clear的API。
2.2 Set与WeakSet的区别

1)WeakSet的成员只能为对象,不能为其它类型的值
2)WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说如果其他对象都不在引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。

        const ws = new WeakSet()
        ws.add({})
        console.log(ws)

上面add()方法初始化一个新对象,并将它作为WeakSet的值。由于没有指向这个对象的其它引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收。然后这个值就从弱集合中消失了,使其成为一个空集合。

        const ws = new WeakSet()
        const container = {
            val: {}
        }
        ws.add(container.val)

        function removeReference() {
            container.val = null
        }

container对象维护着一个对弱集合值的引用,所以container.val不会作为垃圾回收的目标,但是如果调用了removeReference后就会摧毁值对象的最后一个引用,垃圾回收就可以把这个值清理掉。

2.3 一个需要注意的‘bug’
        let obj = {name: 'Leozi'}
        let ws = new WeakSet()
        ws.add(obj)
        obj = null
        console.log(ws)

执行上面代码后理论上的结果是ws是一个空的弱集合,但是控制台展示如下:
在这里插入图片描述

原因:因为浏览器的垃圾回收机制是不受我们控制的,我们无法知道垃圾回收机制什么时候执行,之所以打印能出现内容,其实是因为打印的时候垃圾回收机制没有启动。所以我们可以认为,上图二中的打印在垃圾回收启动之后,它就是空的,下面的2.4节给出具体的验证。

2.4 验证WeakSet的弱引用

方法一:

        let obj = {name: 'Leozi'}
        let ws = new WeakSet()
        ws.add(obj)
        obj = null
        console.log(ws.has(obj))   // false

方法二:

        global.gc();  // global.gc() 强制节点运行垃圾回收
        console.log(process.memoryUsage())  // process.memoryUsage() Nodejs的内存占用情况 此时heapUsed ≈ 2M
        
        let obj = { name: 'LLL', age: new Array(5 * 1024 * 1024) } // 为了差距明显一点,这里多加一个值让整个对象占据内存更大
        let ws = new WeakSet();
        ws.add(obj);
        
        global.gc();
        console.log(process.memoryUsage())  // 此时heapUsed ≈ 4.5M,说明obj占用了内存且没有被回收

上面的代码存在obj的外部引用,下面取消obj引用后看看最终的内存占用

        global.gc();  // global.gc() 强制节点运行垃圾回收
        console.log(process.memoryUsage())  // process.memoryUsage() Nodejs的内存占用情况 此时heapUsed ≈ 2M
        
        let obj = { name: 'LLL', age: new Array(5 * 1024 * 1024) } // 为了差距明显一点,这里多加一个值让整个对象占据内存更大
        let ws = new WeakSet();
        ws.add(obj);
        
        obj = null;  // 取消obj的外部引用
        global.gc();
        console.log(process.memoryUsage())  // 此时heapUsed ≈ 2M,说明obj没有占用了内存且已经被回收

创建完js文件之后,在命令行输入 node --expose-gc set.js 命令执行 set.js 中的代码,其中 --expose-gc 参数表示允许手动执行垃圾回收机制。

2.5 WeakSet的应用场景

储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏,下面用代码进行说明。

        let wrap = document.getElementById('wrap')
        let btn = document.getElementById('btn')

        // 假如想给这个btn加上"禁用"标签,就把它放在Set结构中
        const disableElements = new Set()
        disableElements.add(btn)
        btn.addEventListener('click', () => {
            wrap.removeChild(btn)
        })

假设我们需要给记录页面上的禁用标签,那么一个Set对象存放就可以了,这样写功能上没有问题,但如果写成这样,当点击事件发生后,button 的dom被移除,那么整份js中 disabledElements 这个对象因为是强引用,其中的值依然存在于内存中的,那么内存泄漏就造成了,于是我们可以换成 WeakSet 来存放。

        let wrap = document.getElementById('wrap')
        let btn = document.getElementById('btn')

        // 假如想给这个btn加上"禁用"标签,就把它放在Set结构中
        const disableElements = new WeakSet()
        disableElements.add(btn)
        btn.addEventListener('click', () => {
            wrap.removeChild(btn)
        })

这里当 button 被移除,disabledElements中的内容会因为是弱引用而直接变成空,也就是disabledElements被垃圾回收掉了其中的内存,避免了内存泄漏的产生。

三、映射Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”
的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了
iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属
性和方法:

  • size 返回 Map 的元素个数
  • set 增加一个新元素,返回当前 Map
  • get 返回键名对象的键值
  • delete 删除某个键值对
  • has 检测 Map 中是否包含某个元素,返回 boolean 值
  • clear 清空集合,返回 undefined
3.1 基本API
        let m = new Map()
        m.set('name', 'Leozi')
        m.set('change', function() {
            console.log('changed')
        })
        let key = {
            Leozi: 'Leozi'
        }
        m.set(key, ['AA', 'BB', 'CC'])

        console.log(m.size)

        m.delete('name')
        console.log(m)

        console.log(m.get('change'))
        console.log(m.get(key))

        // m.clear()

        for(let v of m) {
            console.log(v)
        }

在这里插入图片描述


四、弱映射WeakMap

4.1 基本API
  • set 增加一个新元素,返回当前 WeakMap
  • get 返回键名对象的键值
  • delete 删除某个键值对
  • has 检测 WeakMap 中是否包含某个元素,返回 boolean 值
  • WeakMap中的键值对任何时候都可能被销毁,所以没必要提供迭代其键值对的能力,故WeakMap不支持迭代,并且也没有clear和size接口。
        const wm1 = new WeakMap(),
              wm2 = new WeakMap(),
              wm3 = new WeakMap();
        const o1 = {},
              o2 = function(){},
              o3 = window;
        
        wm1.set(o1, 37);
        wm1.set(o2, "azerty");
        wm2.set(o1, o2); // value可以是任意值,包括一个对象或一个函数
        wm2.set(o3, undefined);
        wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象
        
        wm1.get(o2); // "azerty"
        wm2.get(o2); // undefined,wm2中没有o2这个键
        wm2.get(o3); // undefined,值就是undefined
        
        wm1.has(o2); // true
        wm2.has(o2); // false
        wm2.has(o3); // true (即使值是undefined)
        
        wm3.set(o1, 37);
        wm3.get(o1); // 37
        
        wm1.has(o1);   // true
        wm1.delete(o1);
        wm1.has(o1);   // false
4.2 Map与WeakMap的区别
  • Map 对象的键可以是任何类型,但 WeakMap 对象中的键只能是对象引用;
  • WeakMap 不能包含无引用的对象,否则会被自动清除出集合(垃圾回收机制);
  • WeakMap 对象是不可枚举的,无法获取集合的大小。WeakMap不像 Map,
    一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size 属性,也不支持clear方法,所以WeakMap只有四个方法可用get()、set()、has()、delete()

Map的缺点:
在JavaScript里,Map API可以通过共用两个数组(一个存放键,一个存放值)来实现。给这种 Map设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该Map取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。但这样的实现会有两个很大的缺点,首先赋值和搜索操作都是O(n)的时间复杂度(n是键值
对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
相比之下,原生的WeakMap持有的是每个键对象的 “弱引用”,这意味着在没有其他引用存在时
垃圾回收能正确进行
。原生WeakMap的结构是特殊且有效的,其用于映射的key只有在其没有被
回收时才是有效的。

为什么WeakSet和WeakMap不支持迭代?
因为for…of迭代是通过引用(强引用)的方式进行的,而WeakSet和WeakMap维护的是弱引用,也就是这些引用随时都可能被垃圾回收机制回收,如果可以迭代的话,则列表将会受到垃圾回收机制的影响,从而得不到不确定的结果。

4.3 对于弱引用的理解

定义:在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
在JavaScript中,一般创建一个对象var obj = new Object(),都是建立一个强引用,只有当
我们手动设置 obj = null 的时候,才有可能回收 obj 所引用的对象。
而如果我们能创建一个弱引用的对象,假如为var obj = new WeakObject(),
我们什么都不用做,只用静静的等待垃圾回收机制执行,obj 所引用的对象就会被回收。
例子

        const key = new Array(5 * 1024 * 1024);
        const arr = [
          [key, 1]
        ];

使用这种方式,我们其实建立了arr对key所引用的对象(我们假设这个真正的对象叫Obj)的强引
用。所以当你设置key = null时,只是去掉了key对Obj的强引用,并没有去除arr对Obj的强引用,所以Obj还是不会被回收掉。
Map类型也是类似:

        let map = new Map();
        let key = new Array(5 * 1024 * 1024);
        
        // 建立了 map 对 key 所引用对象的强引用
        map.set(key, 1);
        // key = null 不会导致 key 的原引用对象被回收
        key = null;

通过Node进行证明:

        // 允许手动执行垃圾回收机制  node --expose-gc
        
        global.gc();
        // 返回 Nodejs 的内存占用情况,单位是 bytes
        process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4M
        
        let map = new Map();
        let key = new Array(5 * 1024 * 1024);
        map.set(key, 1);
        global.gc();
        process.memoryUsage(); // heapUsed: 46751472 注意这里大约是 44.6M
        
        key = null;
        global.gc();
        process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M
        
        // 这句话其实是无用的,因为 key 已经是 null 了
        map.delete(key);
        global.gc();
        process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M

如果你想要让Obj被回收掉,你需要先delete(key)然后再key = null:

        let map = new Map();
        let key = new Array(5 * 1024 * 1024);
        map.set(key, 1);
        map.delete(key);
        key = null;

通过Node进行证明:

        // node --expose-gc
        
        global.gc();
        process.memoryUsage(); // heapUsed: 4638376 ≈ 4.4M
        
        let map = new Map();
        let key = new Array(5 * 1024 * 1024);
        map.set(key, 1);
        global.gc();
        process.memoryUsage(); // heapUsed: 46727816 ≈ 44.6M
        
        map.delete(key);
        global.gc();
        process.memoryUsage(); // heapUsed: 46748352 ≈ 44.6M
        
        key = null;
        global.gc();
        process.memoryUsage(); // heapUsed: 4808064 ≈ 4.6M
4.4 WeakMap中弱引用的证明
        const wm = new WeakMap();
        let key = new Array(5 * 1024 * 1024);
        wm.set(key, 1);
        key = null;

当我们设置 wm.set(key, 1) 时,其实建立了wm对key所引用的对象的弱引用,但因为let key = new Array(5 * 1024 * 1024)建立了key对所引用对象的强引用,被引用的对象并不会被回收,
但是当我们设置 key = null 的时候,就只有wm对所引用对象的弱引用,下次垃圾回收机制执
行的时候,该引用对象就会被回收掉。
通过Node进行证明:

        // node --expose-gc
        
        global.gc();
        process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4M
        
        const wm = new WeakMap();
        let key = new Array(5 * 1024 * 1024);
        wm.set(key, 1);
        global.gc();
        process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M
        
        key = null;
        global.gc();
        process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M

总结:弱引用的特性就是WeakMaps保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。也正是因为这样的特性,WeakMap内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6规定WeakMap不可遍历。

4.5 WeakMap的应用场景

1)在DOM对象上保存相关数据

        let wm = new WeakMap(), element = document.querySelector(".element");
        wm.set(element, "data");
        
        let value = wm.get(elemet);
        console.log(value); // data
        
        element.parentNode.removeChild(element);
        element = null;

传统使用jQuery的时候,我们会通过 . d a t a ( ) 方 法 在 D O M 对 象 上 储 存 相 关 信 息 ( 就 比 如 在 删 除 按 钮 元 素 上 储 存 帖 子 的 I D 信 息 ) , j Q u e r y 内 部 会 使 用 一 个 对 象 管 理 D O M 和 对 应 的 数 据 , 当 你 将 D O M 元 素 删 除 , D O M 对 象 置 为 空 的 时 候 , 相 关 联 的 数 据 并 不 会 被 删 除 , 你 必 须 手 动 执 行 .data()方法在DOM对象上储存相关信息(就比如在删除按钮元素上储存帖子的ID信息),jQuery内部会使用一个对象管理DOM和对应的数据,当你将DOM元素删除,DOM对象置为空的时候,相关联的数据并不会被删除,你必须手动执行 .data()DOM(ID)jQuery使DOMDOMDOM.removeData()方法才能删除掉相关联的数据,WeakMap就可以简化这样的操作。

2)数据缓存
使用WeakMap可以将先前计算的结果与对象相关联,而不必担心内存管理。以下功能 countOwnKeys()是一个示例:它将以前的结果缓存在WeakMap中cache。

        const cache = new WeakMap();
        
        function countOwnKeys(obj) {
          if (cache.has(obj)) {
            return [cache.get(obj), 'cached'];
          } else {
            const count = Object.keys(obj).length;
            cache.set(obj, count);
            return [count, 'computed'];
          }
        }

测试:

        let obj = { name: "kakuqo", age: 30 };
        console.log(countOwnKeys(obj));
        // [2, 'computed']
        console.log(countOwnKeys(obj));
        // [2, 'cached']
        obj = null; // 当对象不在使用时,设置为null

3)私有属性
示例1:在以下代码中,WeakMap _counter和_action用于存储以下实例的虚拟属性的值

        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);
            counter--;
            _counter.set(this, counter);
            if (counter === 0) {
              _action.get(this)();
            }
          }
        }

测试:

        let invoked = false;
        
        const countDown = new Countdown(3, () => invoked = true);
        countDown.dec();
        countDown.dec();
        countDown.dec();
        
        console.log(`invoked status: ${invoked}`)

示例2:

        const privateData = new WeakMap();
        
        class Person {
            constructor(name, age) {
                privateData.set(this, { name: name, age: age });
            }
        
            getName() {
                return privateData.get(this).name;
            }
        
            getAge() {
                return privateData.get(this).age;
            }
        }
        
        export default Person;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值