javascript闭包(Closure)初探

closure被翻译成“闭包”,感受这东西被包装的太学术化。下面参考书本和网上资源简单探讨一下(理解不当之处务请留意)。
一、什么是闭包
官方的回答:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分。
看了上面的定义,若是你不是高手,我坚信你会和我同样愤怒的质问:这tmd是人话吗?
要理解闭包,仍是代码最有说服力啊,上代码: javascript


function funcTest()
{
  
var tmpNum=100//私有变量

  
//在函数funcTest内定义另外的函数做为funcTest的方法函数
  function innerFuncTest(
  {
       alert(tmpNum); 
//引用外层函数funcTest的临时变量tmpNum
  }

  
return innerFuncTest; //返回内部函数
}

//调用函数
var myFuncTest=funcTest(); 
myFuncTest();
//弹出100

上面代码中,注释已经写的清清楚楚。如今咱们能够这么理解“闭包”:在函数体内定义另外的函数做为目标对象的方法函数(示例中就是在函数funcTest内定义另外的函数innerFuncTest做为funcTest的方法函数),而这个对象的方法函数反过来引用外层函数体中的临时变量(闭包是一种间接保持变量值的机制。示例中就是内部函数innerFuncTest引用外层函数funcTest的临时变量tmpNum,这里必须注意,临时变量能够包括外部函数中声明的全部局部变量参数和声明的其余内部函数)。当其中一个这样的内部函数在包含它们的外部函数以外被调用时,就会造成闭包(示例中,调用函数的时候,myFuncTest实际调用的是innerFuncTest函数,也就是说funcTest的一个内部函数innerFuncTest在funcTest以外被调用,这时就建立了一个闭包)。
二、两个利用闭包的例子
下面举两个例子,一个是由于闭包致使了问题,而另外一个则利用闭包巧妙地经过函数的做用域绑定参数。
这两个例子相关的HTML标记片段以下:
<a href="#" id="closureTest0">利用闭包的例子(1秒后会看到提示)</a><br />
<a href="#" id="closureTest1">因为闭包致使问题的例子1</a><br />
<a href="#" id="closureTest2">因为闭包致使问题的例子2</a><br />
<a href="#" id="closureTest3">因为闭包致使问题的例子3</a><br />
(1)、因闭包而致使问题
上面的HTML标记片段中有4个<a>元素,如今要给后三个指定事件处理程序,使它们在用户单击时报告本身在页面中的顺序,好比:当用户单击第2个连接时,报告“您单击的是第1个连接”。为此,若是编写下列为后三个连接添加事件处理程序的函数: java


function badClosureExample(){
    
for (var i = 1; i <4; i++) {
        
var element = document.getElementById('closureTest' + i);
        element .onclick 
= function(){
            alert(
'您单击的是第' + i + '个连接');
        }
    }
}

而后,在页面载入完成后(否则可能会报错)调用该函数:
window.onload = function(){
    badClosureExample();
}
看一下运行结果,此时单击后3个连接,会看到警告框中显示什么信息呢?——全都是“您单击的是第4个连接”。是否是令你感到十分意外?为何?
分析:由于在badClosureExample()函数中指定给element.onclick的事件处理程序,也就是onclick那个匿名函数是在badClosureExample()函数运行完成后(用户单击连接时)才被调用的。而调用时,须要对变量i求值,解析程序首先会在事件处理程序内部查找,但i没有定义。而后,又到 badClosureExample()函数中查找,此时有定义,但i的值是4(只有i大于4才会中止执行for循环)。所以,就会取得该值——这正是闭包(匿名函数)要使用其外部函(badClosureExample)做用域中变量的结果。并且,这也是因为匿名函数自己没法传递参数(故而没法维护本身的做用域)形成的。
那么这个例子的问题怎么解决呢?其实方法有不少(本身不妨写一下看看),我认为比较简单直接的代码: 闭包


function popNum(oNum){
    
return function(){
                    alert(
'您单击的是第'+oNum+'个连接');
   }
}
function badClosureExample(){
    
for (var i = 1; i <4; i++) {
        
var element = document.getElementById('closureTest' + i);
        element .onclick 
=new popNum(i);
        }
}

(2)、巧妙利用闭包绑定参数
 仍是上面的HTML片断,咱们要在用户单击第一个连接时延时弹出一个警告框,怎么实现?答案是使用setTimeout()函数,这个函数会在指定的毫秒数以后调用一个函数,如:
setTimeout(someFunc,1000);
但问题是,没法给其中的someFunc函数传递参数。而使用闭包则能够轻松解决这个问题: 函数

function  goodClosureExample(oMsg){
    
return   function (){
        alert(oMsg);
    };
}

函数goodClosureExample用来返回一个匿名函数(闭包)。而咱们能够经过为它传递参数来使返回的匿名函数绑定该参数,如:
var good = goodClosureExample('这个参数是经过闭包绑定的');
而此时,就能够将绑定了参数的good函数传递给setTimeout()实现延时警告了:
setTimeout(good,1000) //此时good中已经绑定了参数
最后,测试经过的完整代码: 测试


window.onload = function(){
    
var element = document.getElementById('closureTest0');
    
if (element) {
        
var good = goodClosureExample('这个参数是由闭包绑定的');
        element.onclick 
= function(){
            setTimeout(good, 
1000); //延迟1秒弹出提示
        }
    }
}

三、javascript的垃圾回收原理
(1)、在javascript中,若是一个对象再也不被引用,那么这个对象就会被GC回收;
(2)、若是两个对象互相引用,而再也不被第3者所引用,那么这两个互相引用的对象也会被回收。
在js中使用闭包,每每会给javascript的垃圾回收器制造难题。尤为是遇到对象间复杂的循环引用时,垃圾回收的判断逻辑很是复杂,搞很差就有内存泄漏的危险,因此,慎用闭包。ms貌似已经不建议使用闭包了。 spa

相关文章
相关标签/搜索