嗨,我仍然不确定在 javascript 中使用闭包的确切用法。我对闭包有所了解“闭包是一个内部函数,它可以访问外部(封闭)函数的变量 - 范围链”。但我不知道知道为什么我们在 javascript 中使用闭包。
5 回答
闭包是一种强大的结构,用于在 JavaScript 中实现许多附加功能。例如,可以使用闭包来公开私有状态,如下所示:
function getCounter() {
var count = 0;
return function () {
return ++count;
};
}
var counter = getCounter();
alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3
在上面的示例中,当我们调用时,getCounter
我们创建了一个私有变量count
。然后我们返回一个返回count
递增的函数。因此,我们返回的函数是一个闭包,因为它关闭了变量并允许您在超出范围count
后访问它。count
那是在几行中塞满的大量信息。让我们分解一下?
好的,所以变量就像人一样有生命周期。他们出生,他们生活,他们死去。范围的开始标志着变量的诞生,范围的结束标志着变量的死亡。
JavaScript 只有函数作用域。因此,当您在函数内声明变量时,它会被提升到函数的开头(它诞生的地方)。
当您尝试访问未声明的变量时,您会得到一个ReferenceError
. 但是,当您尝试访问稍后声明的变量时,您会得到undefined
. 这是因为 JavaScript 中的声明被提升了。
function undeclared_variable() {
alert(x);
}
undeclared_variable();
当您尝试访问未声明的变量时,您会得到一个ReferenceError
.
function undefined_variable() {
alert(x);
var x = "Hello World!";
}
undefined_variable();
当您尝试访问稍后在函数中声明的变量时,您会得到undefined
因为只有声明被提升。定义稍后出现。
回到作用域,当变量超出作用域时(通常是在声明变量的函数结束时),它就会死掉。
例如,下面的程序将给出一个ReferenceError
因为x
未在全局范围内声明。
function helloworld() {
var x = "Hello World!";
}
helloworld();
alert(x);
闭包很有趣,因为它们允许您访问变量,即使在声明变量的函数结束时也是如此。例如:
function getCounter() {
var count = 0;
return function () {
return ++count;
};
}
var counter = getCounter();
alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3
在上面的程序中,变量count
是在函数中定义的getCounter
。因此,当调用getCounter
结束时,变量count
也应该死亡。
然而事实并非如此。这是因为getCounter
返回一个访问count
. 因此,只要该函数 ( counter
) 存在,该变量count
也将保持存在。
在这种情况下,返回的函数 ( counter
) 称为闭包,因为它关闭了count
称为upvalue
of的变量counter
。
用途
解释就够了。为什么我们仍然需要闭包?
正如我之前已经提到的,闭包的主要用途是像getCounter
函数一样暴露私有状态。
闭包的另一个常见用例是部分应用。例如:
function applyRight(func) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var rest = Array.prototype.slice.call(arguments);
return func.apply(this, rest.concat(args));
};
}
function subtract(a, b) {
return a - b;
}
var decrement = applyRight(subtract, 1);
alert(decrement(1)); // 0
在上面的程序中,我们有一个名为subtract
. 我们使用部分应用程序创建了另一个decrement
从此函数调用的subtract
函数。
如您所见,该decrement
函数实际上是一个闭包,它关闭了变量func
和args
.
这就是你需要知道的关于闭包的全部内容。
它允许您简洁地表达逻辑,而无需重复自己或为回调函数提供大量参数和参数。
此处提供了更多信息:javascript 闭包优势?
一般来说,闭包的主要用途是创建一个从其上下文中捕获状态的函数。考虑该函数具有捕获的变量,但它们没有作为参数传递。
所以,你可以想办法创建函数族。例如,如果您需要一系列仅在一个值上有所不同的函数,但您不能将该值作为参数传递,则可以使用闭包创建它们。
Mozilla 开发者网络对闭包有很好的介绍。它显示了以下示例:
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
在这种情况下,该函数displayName
已捕获变量name
。就目前而言,这个例子不是很有用,但您可以考虑返回函数的情况:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
这里函数makeFunc
返回displayName
捕获变量的函数name
。现在可以通过将函数分配给变量来调用外部函数myFunc
。
为了继续这个例子,现在考虑捕获的变量name
是否实际上是一个参数:
function makeFunc(name) {
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc("Mozilla");
myFunc();
在这里,您可以看到makeFunc
创建了一个函数,该函数显示一条消息,其中包含作为参数传递的文本。因此它可以创建一个仅根据该变量的值("Mozilla"
在示例中)而变化的整个函数系列。使用此功能,我们可以多次显示消息。
这里重要的是,将在按摩中显示的值已被封装。我们以类似的方式保护这个值,一个类的私有字段在其他语言中隐藏一个值。
例如,这允许您创建一个向上计数的函数:
function makeFunc(value) {
function displayName() {
alert(value);
value++;
}
return displayName;
}
var myFunc = makeFunc(0);
myFunc();
在这种情况下,每次调用存储在 myFunc 中的函数时,都会得到下一个数字,第一个 0,下一个 1,2……等等。
一个更高级的例子是同样来自 Mozilla Developer Network 的“Counter”“类”。它演示了模块模式:
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */
在这里,您可以看到这Counter
是一个对象,它有一个increment
增加privateCounter
变量的方法,以及一个decrement
减少变量的方法。可以通过调用方法来查询这个变量的值value
。
存档的方式是自动调用匿名函数,该函数创建一个隐藏范围,其中privateCounter
声明了变量。现在这个变量只能从捕获它的值的函数中访问。
这是因为信息隐藏。
var myModule = (function (){
var privateClass = function (){};
privateClass.prototype = {
help: function (){}
};
var publicClass = function (){
this._helper = new privateClass();
};
publicClass.prototype = {
doSomething: function (){
this._helper.help();
}
};
return {
publicClass: publicClass
};
})();
var instance = new myModule.publicClass();
instance.doSomething();
在 javascript 中,您没有具有 ppp(公共、受保护、私有)属性的类,因此您必须使用闭包来隐藏信息。在类级别上这将是非常昂贵的,所以为了更好的代码质量,你唯一能做的就是在模块级别使用闭包,并在该闭包中使用私有帮助类。所以闭包是为了隐藏信息。它并不便宜,它消耗资源(内存、cpu 等),所以你必须在适当的地方使用闭包。所以永远不要在类级别上使用它来隐藏信息,因为它太昂贵了。
通过使用回调将方法绑定到实例的另一种用法。
Function.prototype.bind = function (context){
var callback = this;
return function (){
return callback.apply(context, arguments);
};
};
绑定函数的典型用法是异步调用:defer、ajax、事件监听器等...
var myClass = function (){
this.x = 10;
};
myClass.prototype.displayX = function (){
alert(this.x);
};
var instance = new myClass();
setTimeout(instance.displayX.bind(instance), 1000); //alerts "x" after 1 sec
想象一下,如果不是
alert("Two plus one equals" + (2+1) );
您将被迫为您曾经使用的每个常量声明一个变量。
var Two = 2;
var One = 1;
var myString = "Two plus one equals";
alert(myAlert + (Two + One) );
如果您必须在使用它之前为每个常量定义一个变量,您会发疯的。
在闭包的情况下访问局部变量是一个优势,但主要作用 - 有用性 - 是使用函数作为主要表达式,一个常量。
拿
function Div1OnClick()
{
Counter.clickCount ++;
}
$('#Div1').click(Div1OnClick);
相对
$('#Div1').click(function(){ Counter.clickCount++; });
您不会为了使用一次而在“above”命名空间中创建新的函数名称。实际活动就在使用它的地方——你不需要在代码中追逐它到它被编写的地方。您可以将实际函数用作常量,而不是首先定义和命名它,然后按名称调用它,虽然有无数与闭包相关的警告、优势和技巧,但这是销售它们的一个属性。