细说WeakMap

作者: 姜治宇 | 来源:发表于2020-04-19 11:14 被阅读0次

    先看一段简单的代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>二维数组持有dom</title>
    </head>
    <body>
    <div>
    
        <input type="text" id="kw"/>
        <button id="search">搜索</button>
    
        <button id="release">释放</button>
    </div>
    </body>
    </html>
    <script>
    
        let e2 = document.getElementById('search');
        let arr = [[e2, {clickTimes: 0}]];//dom节点与点击次数是一一对应的
    
        e2.onclick = function () {
            let e1 = document.getElementById('kw');
            arr[0][1].clickTimes++
            e1.value = arr[0][1].clickTimes
        }
        document.getElementById('release').onclick = function () {
            e2.onclick = null
    
            e2 = null;
            console.log(arr)
        }
    </script>
    

    e2是一个dom对象,arr数组对这个对象形成了引用关系。
    当e2使用完毕后,点击释放按钮,发现arr仍旧持有e2,也就是说,e2仍在内存中,没有被释放掉。
    为了保证内存不会泄露,我们只能手动释放:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>二维数组持有dom</title>
    </head>
    <body>
    <div>
    
        <input type="text" id="kw"/>
        <button id="search">搜索</button>
    
        <button id="release">释放</button>
    </div>
    </body>
    </html>
    <script>
    
        let e2 = document.getElementById('search');
        let arr = [[e2, {clickTimes: 0}]];//dom节点与点击次数是一一对应的
    
        e2.onclick = function () {
            let e1 = document.getElementById('kw');
            arr[0][1].clickTimes++
            e1.value = arr[0][1].clickTimes
        }
        document.getElementById('release').onclick = function () {
            e2.onclick = null
    
            e2 = null;
            arr[0] = null; //手动释放
            console.log(arr)
        }
    </script>        
    

    这样写代码很烦,因为你会把注意力过多的放在内存释放上,这样就无法专心逻辑方面的思考了。
    怎么办呢?
    es6提供了另一个好用的工具—— WeakMap。
    WeakMap的键只能是对象,而且所引用的对象都是弱引用,弱引用的意思就是垃圾回收机制不会标记它。也就是说,只要键引用的对象释放了,WeakMap里面的元素就会自动跟着释放,并不需手动清除。
    因为浏览器端无法手动调用gc垃圾回收,我们只能在node端玩了,看下面一段代码:
    test.js:

    let e1 = new Array(5 * 1024 * 1024)
    let e2 = new Array(5 * 1024 * 1024)//开辟大块内存空间
    
    let wm = new WeakMap([[e1,'内存1'], [e2,'内存2']]) //对e1和e2进行了持有
    
    global.gc()//手动调用垃圾清除,必须使用命令: node --expose-gc test.js来执行
    
    console.log(process.memoryUsage())
    /*e1和e2清除前:
    *{ rss: 105156608,
      heapTotal: 93691904,
      heapUsed: 88018144,
      external: 8224 }
    * */
    console.log('---------')
    e1 = null
    e2 = null
    global.gc()
    
    console.log(process.memoryUsage())
    /*e1和e2清除后:
    * { rss: 63799296,
      heapTotal: 52260864,
      heapUsed: 46349472,
      external: 8224 }
    * */
    

    这段代码大家必须用以下命令来执行,否则垃圾回收代码会报错:

    node --expose-gc test.js
    

    我们主要看heapUsed一项,这个表示正在使用的堆内存。
    从e1和e2释放前后可以看出,前后堆内存缩小了一半,由此可见,WeakMap对e1和e2的持有,并未影响垃圾的回收。
    我们对比下原来的二维数组:

    let e1 = new Array(5 * 1024 * 1024)
    let e2 = new Array(5 * 1024 * 1024)//开辟大块内存空间
    
    let arr = [[e1,'内存1'], [e2,'内存2']] //对e1和e2进行了持有
    
    global.gc()//手动调用垃圾清除,必须使用命令: node --expose-gc test.js来执行
    
    console.log(process.memoryUsage())
    /*e1和e2清除前:
    *{ rss: 105107456,
      heapTotal: 93691904,
      heapUsed: 88017976,
      external: 8224 }
    * */
    console.log('---------')
    e1 = null
    e2 = null
    global.gc()
    
    console.log(process.memoryUsage())
    /*e1和e2清除后:
    * { rss: 105865216,
      heapTotal: 94216192,
      heapUsed: 88292424,
      external: 8224 }
    * */
    

    可以看出,二维数组前后并未释放heapUsed空间。
    有了WeakMap,前面的问题就容易解决了。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>dom填入WeakMap</title>
    </head>
    <body>
    <div>
    
        <input type="text" id="kw"/>
        <button id="search">搜索</button>
    
        <button id="release">释放</button>
    </div>
    </body>
    </html>
    <script>
    
        let e2 = document.getElementById('search');
        let wm = new WeakMap()
        wm.set(e2, {clickTimes: 0}) //回收时,只需保证e2销毁掉即可
        e2.onclick = function () {
            let e1 = document.getElementById('kw');
            let times = wm.get(e2)
            times.clickTimes++
            e1.value = times.clickTimes
        }
        document.getElementById('release').onclick = function () {
            e2.onclick = null
            e2 = null;
    
            console.log(wm.get(e2))//e2已经没了,不会造成内存泄露
        }
    
    </script>
    

    还有一个妙用就是用于对象私有属性。

    const counterWm = new WeakMap();//动作名称
    const actionWm = new WeakMap();//回调函数
    
    class CountDo {
        //初始化
        constructor(counter, action) {
            counterWm.set(this, counter);
            actionWm.set(this, action);
        }
        doIt() {
            if(counterWm.get(this)){
                actionWm.get(this)()
            }
        }
    }
    
    let c = new CountDo('do1', () => console.log('做第一件事'))
    
    c.doIt()
    console.log(counterWm.get(c))
    console.log(actionWm.get(c))
    c = null //当c销毁后,两个WeakMap里面的元素会自然清除掉
    

    相关文章

      网友评论

        本文标题:细说WeakMap

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