何为闭包?
在我的理解看来,闭包说白了就是一个函数,它可以引用函数外部的变量,当然这里的外部其实是一个作用域(也可以理解成为另一个函数)。如果这个作用域为函数作用域的话,即使外层函数已经执行完毕,但是里面被内层函数引用的变量依旧存储在内存中,除非内层函数已经执行完毕。
闭包原理
说到闭包,就不得聊一聊作用域的问题了,举个栗子:
function testClosure(propertyName){
return function(obj1,obj2){
var p1=obj1['propertyName'];
var p2=obj2['propertyName'];
if(p1>p2){
return 1;
}else if(p1<p2){
return -1;
}else{
return 0;
}
}
}
var result=testClosure('name');
result({name:'mickey'},{name:'bob'});
ok,这其实就是一个简单的闭包,要特别注意匿名函数中的 var p1=obj1['propertyName']和var p2=obj2['propertyName'];
仔细发现这里引用了外层函数的变量propertyName, 即使这个内部函数被返回了,而且是在被其他地方调用了,但他仍然可以访问变量propertyName,之所以可以访问这个变量,是因为内部函数的作用域链中包含testClosure()的作用域。关于作用域链,如下一张图:
用闭包解决实际问题
案例 1
function testClosure(){
var result=new Array();
for(var i=0;i<10;i++){
result[i]=function(){
return i;
};
}
return result;
}
如上,在执行testClosure函数的时候,result其实是一个函数数组,返回值是每项值为10的数组,而非想象中的那样返回值为1…10,为了解决以上问题,我们可以这样去实现
解决方案 1
function testClosure(){
var result=new Array();
for(var i=0;i<10;i++){
result[i]=function(num){
return function(){
return num;
}
}(i);
}
return result;
}
如上解决方法是利用立刻执行函数和闭包的特性将每一次for循环的变量i传入自执行的匿名函数中,在自执行函数中,利用闭包的特性,返回一个函数,这个函数就是我们result数组中的函数,然后在函数中通过return一个num来实现返回一个值,这样,每一个for循环在进行迭代的时候,i的值都被保存在自执行函数中,即使自执行函数执行完就销毁,但是里面的变量由于被内部函数引用,所以会一直停留在内存中。
解决方案 2
function testClosure(){
var result=new Array();
for(let i=0;i<10;i++){
result[i]=function(){
return i;
};
}
return result;
}
这里不仔细看的话,会发现和之前的源代码没有太大区别,但是在for循环里面,我们使用ES6中的let关键字取代了var,ES6中的let为JavaScript引入了块级作用域的概念,这就说明每一次for循环进行迭代的时候,i的值都只为每一个迭代中的{ }服务,相当于每一次迭代只维护一个{ },在C++或者JAVA中,如果直接在for循环里面声明i变量的话,它是默认具有块级作用域的。但是var不行,var即使在里面申请也会被默认看成是全局的(相对于一个函数作用域的全局)
案例 2
我们常常在为按钮绑定事件的时候,正常情况下,直接一个button.onclick=function(){ },其实就完事儿了,但是对于多个按钮呢?而且我还想实现点击的时候区别到底是哪一个按钮被触发了呢?如下:
<body>
<button class="button">按钮1</button>
<button class="button">按钮2</button>
<button class="button">按钮3</button>
<script type="text/javascript">
let buttons=document.getElementsByClassName("button");
for(var i=0;i<buttons.length;i++){
buttons[i].onclick=function(){
console.log(i+1);
};
}
</script>
</body>
这里很遗憾的是,当我们点击任何一个按钮,控制台都只会输出5。
解决方案
<body>
<button class="button">按钮1</button>
<button class="button">按钮2</button>
<button class="button">按钮3</button>
<script type="text/javascript">
let buttons=document.getElementsByClassName("button");
for(var i=0;i<buttons.length;i++){
buttons[i].onclick=function(num){
return function(){
console.log(num+1); }
}(i);
}
</script>
</body>
原理和上个案例类似,不在过多赘述,第二种解决方案也是使用let替换var.
案例 3
详情请点击JS中的闭包理解