一、概念
JavaScript 中有作用域和作用域链的概念,变量的访问只能在作用域内,如果该作用域内找不到,就向上查找父作用域,直到找到为止。那么如何在外层也能访问到作用域内的变量,就是今天的登场主角“闭包”。闭包可以通过函数作用域内返回另一个函数来达到此目的。在一般的开发项目中,可能闭包用得并不多,但是在框架工具源码里,闭包的使用遍地可见。
二、特点
- 由于闭包内部保存着外部引用的变量,不会被垃圾回收机制回收,所以会一直保存在内存中,从而消耗内存。在 IE 中可能导致内存泄漏,所以在退出函数前,将不使用的变量全部删除。
- 使用闭包模拟私有属性或方法时,不能随意改变父函数作用域内变量的值。
- 使用闭包时返回函数不要引用任何循环变量,或者在循环中会发生变化的变量。可以创建一闭包,用函数的参数值绑定循环变量当前的值,这样,无论循环变量如何改变,绑定到函数的参数不会变。
三、应用
- 在外层访问或改变函数作用域内变量;
function person() {
var x = 3;
return function () {
x++;
return x;
}
};
var m= person();
console.log(m());//4
console.log(m());//5
console.log(m());//6
- 将函数作为参数调用匿名函数;
(function person(f) {
var x= 10;
f(x);
})(person);
function person(n){
console.log(n)//10
};
- 用闭包模拟私有方法
var person = (function(){
var index = 0;
var increase = function(){
index++;
};
return {
get:function(){
return index;
},
increase:function(){
increase ();
}
}
})();
console.log(person.get());//0
person.increase();
console.log(person.get());//1
- 五种方法解决循环引用问题
本文只列出方法,如果想要了解每个方法的解析原理,可参考文章《点击每个兄弟节点获取对应节点下标的五种方案详解》。
//要求:点击每个 li 打印出对应的下标。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var lis=document.getElementsByTagName('li');
for(var i=0;i<=lis.length-1;i++){
lis[i].onclick = function () {
console.log(i)
};
}
//现象:不论点击哪一个 li 都打印出 lis.length(有几个li就打印出几)。
</script>
//解决方案一:使用 let,将以上循环脚本中的 var 改成 let 即可
for(let i=0;i<=lis.length-1;i++){
lis[i].onclick = function () {
console.log(i)
};
}
//解决方案二:给 DOM 属性赋值。
for(var i=0;i<=lis.length-1;i++){
lis[i].id = i;
lis[i].onclick = function () {
console.log(this.id)
};
}
//解决方案三:使用传参闭包1。
for(var i=0;i<=lis.length-1;i++){
lis[i].onclick = (function (m) {
return function(){
console.log(m)
}
})(i);
}
//解决方案四:使用传参闭包2。
for(var i=0;i<=lis.length-1;i++){
(function(m){
lis[i].onclick = function () {
console.log(m)
};
})(i)
}
//解决方案五:使用不传参闭包+变量关联。
for(var i=0;i<=lis.length-1;i++){
(function(){
var id = i;
lis[i].onclick = function () {
console.log(id)
};
})()
}
四、意义
- 可在外层访问或改变作用域内的变量;
- 可使用闭包模拟私有属性或方法;
- 可使用闭包解决循环引用的问题。