先看一段简单的代码:
<!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里面的元素会自然清除掉