闭包是什么?
引用 MDN 的描述:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
案例分析
闭包的形成
上面的话可以简单理解为 一个定义在函数内部的函数,且内部函数引用外部函数的值,我们来看一个简单的例子:
function count() {
var i = 0;
return function demo() {
i += 1;
console.log(i);
}
}
let fn = count();
fn(); // 1
从上面的例子可以得出,要形成一个闭包,只需要 内部函数引用了外部函数的变量
闭包的特点
闭包的特点除了显而易见的 变量私有化 外,还有最重要的一点就是 被内部函数引用的变量,不会在父函数调用结束后被回收机制回收,看下面的例子:
function count() {
var i = 0;
return {
add: function() {
i += 1;
},
show: function() {
console.log(i);
},
};
}
// 一般情况下 i 会随着父函数调用完成被回收
var Count = count();
Count.add();
Count.show(); // 1
Count.add();
Count.show(); // 2
多次调用后,i 被成功叠加,由此可以看出 i 一直被保存在内存中,并没有随着调用结束被回收
为什么会这样?因为 闭包函数会一直保存在内存中,而被依赖引用的变量也会一直保存在内存中 ,所以 i 不会在父函数调用完成后被回收机制自动回收
部分文章说到因为闭包会延长父函数变量的生命周期,所有会有内存泄漏风险,但是我认为应该是 不好的编码习惯导致存在内存泄漏的风险 ,不再使用的闭包应该及时手动清空
循环中的引用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>标题</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
let btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log(i); // 3
}
}
</script>
</body>
</html>
上面这个例子我相信已经不是第一次见到了,我们很清楚的知道 每次点击按钮,控制台打印的都是 3 。
因为 用 var 创建的 i 是一个全局变量,所以每一次循环就会覆盖原来的 i,导致打印出来的永远都是最后一个值。
答案显而易见,我们只需要一个 不会重复覆盖 i 的局部作用域 , 修改一下 for 循环的代码
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function(index) {
return function() {
console.log(index);
}
})(i);
}
总结
- 如何产生一个闭包:
- 嵌套函数
- 子函数引用父函数的变量
- 闭包的特点:
- 成员变量私有化,命名空间隔离
- 延长父函数变量的生命周期。只要闭包还在使用,父函数变量就不会被释放
- 闭包使用注意点:
- 由于会延长变量的生命周期,所以当不再使用时,需要
手动将闭包设置为 null释放内存,如showCount = null
- 由于会延长变量的生命周期,所以当不再使用时,需要