JavaScript 闭包






4.95/5 (12投票s)
一篇简明易懂的文章,讲解 JavaScript 中的闭包
引言
如果您已经学习了 函数 和 模块,那么学习闭包对您来说应该不是难事。如果您碰巧在此处登陆,并且尚未学习这两个主题,那也完全没关系。闭包属于 JavaScript 高级概念的范畴,但请相信我,如果您了解并理解内部函数的工作原理,那么理解闭包将易如反掌。
对于这里所有勤奋好学、不走捷径、喜欢深入研究的读者,我建议您在继续学习之前,先阅读关于 函数 的帖子。而对于那些懒惰、喜欢两分钟速食面的朋友来说,这篇文章将确保您在开始“主菜”(闭包)之前,就能将所需的基础概念煮好并端到您的盘子里。
内部函数
内部函数本质上是“函数中的函数”。JavaScript 支持嵌套函数,这意味着您可以在一个函数内创建一对或多个函数。内部函数还可以包含函数,这种嵌套可以更深。让我们通过一个简单的例子来看看。
function innerOuterDemo(){
console.log("The outer function");
function innerFunction(){
console.log("The inner function");
}
return innerFunction;
}
函数 innerOuterDemo
包含一个名为 innerFunction
的内部函数,它只是将“The inner function”写入控制台。调用 innerOuterDemo
将把“The outer function”写入控制台,并返回函数 innerFunction
。
var doSomething = innerOuterDemo(); // Logs "The outer function" and returns innerFunction
doSomething(); // Logs "The inner function"
非常简单明了。但请记住,一旦函数完成执行并返回,它就不再处于作用域中。这意味着当 innerOuterDemo
执行并返回 innerFunction
时,innerOuterDemo
就不再处于作用域中了。
现在我们已经涵盖了函数嵌套和作用域的基础知识,让我们直接进入闭包。请看下面的例子
function foodAndMore(){
var myFood = "Pizza and Pasta";
function getMyFood(){
return myFood;
}
return getMyFood;
}
var foodILove = foodAndMore();
console.log("I love " + foodILove());
这与第一个图例非常相似,除了我们有一些数据(myFood
变量)在操作。myFood
变量属于外部函数 foodAndMore
,但在 return
语句的内部函数 getMyFood
中使用。调用 foodAndMore
函数会返回 getMyFood
函数,该函数被赋给变量 foodILove
。当您运行上面的代码片段时,它会将文本“I love Pizza and Pasta
”写入控制台。
这里有什么特别之处?还记得我说的,当一个函数完成执行后,它就脱离了作用域吗?如果是这样,那么包含“Pizza and Pasta
”值的 myFood
变量在 foodAndMore
函数执行完毕返回后,难道不应该脱离作用域吗?但是,如果您在控制台中看到了输出“I love Pizza and Pasta
”,那么情况显然并非如此。
闭包
闭包是内部函数与定义在外部作用域但可被内部函数访问的变量的组合。换句话说,闭包是一个可以访问外部函数变量的内部函数。现在,让我们饿了,再回顾一下我们的美食示例。正如我们已经观察到的,内部函数 getMyFood
即使在外部函数执行完毕并脱离作用域后,仍然可以访问外部函数的变量 myFood
。因此,函数 getMyFood
和变量 myFood
的组合就是一个闭包的例子。我们可以随时通过变量引用(例如 foodILove
)调用 getMyFood
函数,并获取 myFood
变量的值,即“Pizza and Pasta
”。
在 JavaScript 中,内部函数不仅可以访问外部函数的变量,还可以访问其参数。下面的例子将证明这一点。
function getMultiplier(multiplyBy){
function multiply(num){
return multiplyBy * num;
}
return multiply;
}
var multiplyByTwo = getMultiplier(2);
var multiplyByTen = getMultiplier(10);
var twoIntoFive = multiplyByTwo(5); // 10
var tenIntoSix = multiplyByTen(6); // 60
因此,内部函数 multiply
可以访问外部函数的参数 multiplyBy
。外部函数 getMultiplier
返回一个可以用作乘法函数的函数。我们从中创建了两个函数 multiplyByTwo
和 multiplyByTen
。在调用这两个函数中的任何一个时,JavaScript 运行时都会记住执行环境(getMultiplier
函数的参数),并在数字之间执行乘法。
闭包在 JavaScript 以及基于 JavaScript 的库和框架中得到广泛应用。例如,在事件处理和 AJAX 调用中,当您将内联函数作为事件处理程序或回调函数时。这些内联函数可以访问主(外部)函数的变量和参数。
以下是 jQuery 中按钮点击事件处理函数(闭包)的一个示例。即使外部函数未执行,该函数也可以访问 count
变量及其状态。
$(function(){
var count = 1;
$("#counterBtn").Click(function(){
alert("Your click count is: " + count++);
});
});
闭包在 JavaScript 中流行的 模块模式 中定义可以访问 私有
函数和变量的 公共
函数时也很有用。下面是一个简单的图例。
var mathUtility = (function(){
var count = 1;
function increment(number){
return number + count;
}
function decrement(number){
return number - count;
}
return {
nextOf: increment,
previousOf: decrement
};
}());
var numberAfterFive = mathUtility.nextOf(5); // 6
var numberBeforeTen = mathUtility.previousOf(10); // 9
我希望上面的例子不言自明。我们创建了一个模块 mathUtility
,其中包含两个 私有
函数 increment
和 decrement
。两个 私有
函数都可以访问外部匿名函数中的 count
变量,从而形成闭包。我们返回一个对象,实际上是一个名为 mathUtility
的模块,其中包含两个 公共
函数 nextOf
和 previousOf
,可用于查找任何给定数字的下一个或上一个数字。
结束语
我不想让您的滚动条过长,就此打住。只要记住,JavaScript 函数会记住它们的执行环境。JavaScript 运行时会确保外部函数中在作用域内且可供内部函数访问的变量和参数将始终可供内部函数访问。
我试图让这篇文章简洁、切题且易于理解。如果您仍有疑问,或者发现可以改进的地方,请留下您的评论。感谢阅读。
你可能还对以下内容感兴趣