闭包是什么?它是怎样工作的?
简单地说,闭包是一个函数在它创建时允许该自身函数访问并操作该自身函数以外的变量时所创建的作用域。换句话说,闭包可以让函数访问所有的变量和函数,只要这些函数和变量存在于该函数声明时的作用域内就行。
有趣的事,内部函数可以比外部函数看得远 :
内部函数的参数是包含在闭包内的
作用域以外的变量,即便是函数声明之后的那些声明,也都包含在闭包中
相同的作用域内,尚未声明的变量不能被提前引用
如何使用闭包?
-私有变量
闭包的一种常见用法是封装一些信息作为"私有变量"。
function Ninja(){ var feints = 0; this.getFeints = function(){ return feints; }; this.feint = function(){ feints++; }; }; var ninja = new Ninja(); ninja.feint(); console.assert(ninja.getFeints()==1, "We're able to access the internal feint count."); console.assert(ninja.feints===undefined, "And the private data is inaccessible to us.");
在上述代码中,我们创建了一个函数构造器,在函数上使用new关键字时,就会创建一个新对象实例,该函数会被调用,并将新对象作为它的上下文,函数会作为该对象的构造器。所以函数内的this就是新实例化的对象。在构造器内,我们定义了一个变量feints用于保存状态。JavaScript的作用域规则显示了它的可访问性只能在构造器内部。要让外部的代码可以访问到该内部变量,我们定义了一个存取方法getFeints(),该方法只能对内部变量进行读取,但不能写入。
接下来,创建实现方法feint(),以便在一个受控制的方法内控制变量的值。
建立了构造器之后,使用new操作符进行调用,然后再调用feint()方法。
-回调与计时器
另一个使用闭包最常见的情形就是在处理回调或使用计时器的时候。在这两种情况下,函数都是在后期未指定的时间进行异步调用,在这种函数内部,我们经常需要访问外部数据。闭包可以作为一种访问这些数据的很直观的方式,特别是在避免创建全局变量来存储信息时。例如:
<div id = 'testSubject'></div> <button type = 'button' id = 'testButton'>Go!</button> <script> jQuery('#testButton').click(function(){ var elem$ = jQuery('#testSubject'); elem$.html("Loading..."); jQuery.ajax({ url:'test.html', success: function(html){ console.assert(elem$,"We can see elem$,via the closure for this callback."); elem$.html(html); } }) }) </script>
在计时器间隔回调中使用闭包:
<div id="box"></div> <script> function animateIt(elementId){ var elem = document.getElementById(elementId); var tick = 0; var timer = setInterval(function(){ if(tick < 100){ elem.style.left = elem.style.top = tick + 'px'; tick++; }else { clearInterval(timer); console.assert(tick == 100, "Tick accessed via a closure."); console.assert(elem, "Element also accessed via a closure."); console.assert(timer, "Timer reference also obtained via a closure."); } },10); };animateIt('box'); </script>
以上代码的重要作用是:使用一个独立的匿名函数完成特定元素的动画效果。通过闭包,该函数能使用三个变量控制动画的过程。