原文地址:https://developer.mozilla.org/en/JavaScript/Guide/Closures
闭包
闭包通常被认为是JS的高阶特性,不过想要精通JS,理解闭包是必须的。
看下面的代码:
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
init()方法创建了name局部变量和displayName方法。displayName在init方法内部被声明,是一个内部函数,他只在init方法内部才可以生效。displayName方法没有属于自己的局部变量,但它重用了方法外的name变量。
下面是个函数作用域的例子。JS中,变量的作用域取决于它在代码中被声明的位置,内嵌的方法可以访问处于在其域外(嵌套该方法)声明的变量,下面是一个例子:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
如果你运行上面的代码,你会发现它的效果和之前的init方法完全相同:浏览器会弹出一个带有“Mozilla”字样的alert弹窗。不同之处在于,名为displayName的内部方法在被执行之前就被它外层的方法作为结果返回了。
直观上我们会认为上面的代码应该不能执行。一般我们认为,在一个方法中声明的局部变量只存在于这个方法的执行期,当makeFunc这个方法完成执行之后,认为name这个变量不再有作用是可以理解的。不过从代码执行的结果来看,并不是这样。
这里还有个更有趣的例子:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
print(add5(2)); // 7
print(add10(2)); // 12
在这个例子里,我们定义了有一个参数x,并且会返回另一个方法的makeAdder(x)方法,返回的这个方法有一个参数y,并且会返回x和y的和。
事实上,makeAdder是一个工厂方法——它能创建一个给能参数赋予一个固定值的方法。上面的例子里,我们使用工厂方法创建了两个新的方法,一个的固定参数值是5,另一个是10。
闭包实用
这是闭包的理论使用,不过闭包真的有用吗?让我们看看它的实际运用吧。闭包能够让你将一些数据(及其环境)同操作这些数据的方法联系起来。这和OOP有明显的相似之处,对象允许我们将一些数据同一个或多个方法联系起来。
因此,你能在平时可能只将一个对象同一个方法联系起来的任何地方使用闭包。
你想处理的情况可能在网络上非常常见。我们写的大多数的JS代码是事件驱动的,我们定义一些操作行为,然后把它们用户触发的事件关联(比如点击或者是按键)。我们的代码通常作为一个回调函数——作为对这个事件的响应,进行执行。
下面是个实际例子:我们想在页面上加上一些按钮去调整文字大小。一种解决方案是:设定body元素的font-size样式为确定的像素值,然后为这个页面上的其他元素设定相对尺寸的em样式:
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
我们的按钮能够改变body元素的font-size属性,由于相对单位的原因,这种改变会作用到页面上的其他元素。
下面是JavaScript代码:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
size12,size14,size16方法分别可以将body的字体大小调整到12,14,16像素。我们可以把他们附在按钮上。
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
Java等一些语言可以声明私有方法,私有意味着该方法只能被同一个类中的其他方法所调用。
JavaScript原生没有提供方法去实现私有方法,但是通过闭包能够模拟实现。私有方法不仅是在限制代码访问时有用,同时也提供了一种强大的管理全局命名空间,保持公共接口的里没有非必需方法的手段。
下面是如何定义一些公共的方法,可以访问私有函数和变量,使用闭包,它也被称为模块模式。
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */