我正试图用Javascript来解决问题。
以下是教程中的示例:
function greeter(name, age) {
var message = name + ", who is " + age + " years old, says hi!";
return function greet() {
console.log(message);
};
}
// Generate the closure
var bobGreeter = greeter("Bob", 47);
// Use the closure
bobGreeter();
作者说这是使用闭包来制作私有变量的有效方法,但我不明白这一点。
有人可以像这样启发编码的好处吗?
答案 0 :(得分:23)
闭包是一对 a function 和环境,在其中定义了它(假设lexical scoping,哪个JavaScript使用)。因此,闭包函数可以访问其环境中的变量;如果没有其他函数可以访问该环境,那么其中的所有变量都是私有的,只能通过闭包函数访问。
您提供的示例可以很好地证明这一点。我添加了内联注释来解释环境。
// Outside, we begin in the global environment.
function greeter(name, age) {
// When greeter is *invoked* and we're running the code here, a new
// environment is created. Within this environment, the function's arguments
// are bound to the variables `name' and `age'.
// Within this environment, another new variable called `message' is created.
var message = name + ", who is " + age + " years old, says hi!";
// Within the same environment (the one we're currently executing in), a
// function is defined, which creates a new closure that references this
// environment. Thus, this function can access the variables `message', `name',
// and `age' within this environment, as well as all variables within any
// parent environments (which is just the global environment in this example).
return function greet() { console.log(message); };
}
运行var bobGreeter = greeter("Bob", 47);
时,会创建一个新的闭包;也就是说,您现在已经有了一个新的函数实例以及创建它的环境。因此,您的新函数在所述环境中引用了“message”变量,尽管没有其他人这样做。
额外阅读:SICP Ch 3.2。虽然它侧重于Scheme,但这些想法是相同的。如果您对本章有所了解,那么您将对环境和词法范围的工作方式有一个良好的基础。
Mozilla还有一个专门针对explaining closures的页面。
答案 1 :(得分:8)
闭包的目的是使你在给定函数中使用的变量保证“闭合”,这意味着它们不依赖于外部变量 - 它们只依赖于并使用它们的参数。这使得您的Javascript方法更接近pure function,即为相同的给定参数返回相同值的方法。
不使用封口,您的功能就像瑞士奶酪,它们会有洞。闭包会堵塞这些洞,因此该方法不依赖于范围链中较高的变量。
现在,到目前为止,我的回答仅仅是组织代码和样式。举个简单的例子吧。在注释的行中,我调用一个函数,并捕获变量a
的值以供将来使用。
var a = "before";
var f = function(value) {
return function()
{
alert(value);
}
} (a); //here I am creating a closure, which makes my inner function no longer depend on this global variable
a = "after";
f(); //prints "before"
现在,你为什么需要这样做?嗯,这是一个实际的例子。请考虑以下使用jQuery添加5个文档链接的代码。当您单击某个链接时,您会希望它与该链接关联的数字为alert
,因此单击您认为的第一个会提醒0,依此类推。 但是,情况并非如此,每个链接都会alert
的值为5.这是因为我定义的函数取决于变量i
,该变量在功能的上下文。我传入bind
的功能是瑞士奶酪功能。
for (var i = 0; i < 5; i++)
{
var a = $('<a>test link</a>').bind('click', function(){
alert(i);
});
$(a).appendTo('body');
}
现在,让我们通过创建一个闭包来解决这个问题,这样每个链接都会alert
正确的数字。
for (var i = 0; i < 5; i++)
{
var fn = function (value) {
return function() {
alert(value);
};
} (i); //boom, closure
var a = $('<a>test link</a>').bind('click', fn);
$(a).appendTo('body');
}
答案 2 :(得分:4)
我不认为这是私有变量的一个很好的例子,因为没有真正的变量。闭包部分是函数greet
可以看到message
(外部不可见,因此私有),但它(或其他任何人)没有改变它,所以它更像是一个恒定。
以下示例怎么样?
function make_counter(){
var i =0;
return function(){
return ++i;
}
}
var a = make_counter();
console.log(a()); // 1
console.log(a()); // 2
var b = make_counter();
console.log(b()); // 1
console.log(a()); // 3
答案 3 :(得分:2)
更好的例子可能是
function add(start, increment) {
return function() {
return start += increment;
}
}
var add1 = add(10, 1);
alert(add1()); // 11
alert(add1()); // 12
这里,每次调用返回的函数时,都会添加1.内部是封装的。
返回的函数仍然可以访问其父变量(在本例中为start
和increment
)。
在较低层次的思考中,我认为这意味着函数的堆栈在返回时不会被销毁。
答案 4 :(得分:2)
一旦你“得到它”,你会想知道为什么花了这么长时间来理解它。这就是我感觉的方式。
我认为Javascript中的函数范围可以用相当简洁的方式表达。
函数体可以访问在函数声明的词法环境中可见的任何变量,也可以访问通过函数调用创建的任何变量 - 即,在本地声明的任何变量,作为参数传递或以其他方式提供按语言(例如
this
或arguments
)。
答案 5 :(得分:2)
它被称为“闭包”,因为它们围绕自由变量“关闭”,并且有更多的方法可以使用它然后只隐藏状态。例如,在闭包来自的函数式编程中,它们通常用于减少参数数量或为函数设置一些常量。假设您需要函数goodEnough()
来测试某些结果是否比某个阈值更好。您可以使用2个变量的函数 - 结果和阈值。但是你也可以“封闭”你的常数内部函数:
function makeThresholdFunction(threshold) {
return function(param) {
return (param > threshold);
}
}
var goodEnough = makeThresholdFunction(0.5);
...
if (goodEnough(calculatedPrecision)) {
...
}
使用闭包你也可以使用所有技巧,例如它们的组成:
function compose(f1, f2) {
return function(arg) {
return f1(f2(arg));
}
}
var squareIncremented = compose(square, inc);
squareIncremented(5); // 36
有关封闭设计和使用的更多信息,请访问SICP。
答案 6 :(得分:1)
我发现这篇文章非常有用。
答案 7 :(得分:1)
//Lets start with a basic Javascript snippet
function generateCash() {
var denomination = [];
for (var i = 10; i < 40; i += 10) {
denomination.push(i);
}
return denomination;
}
这是Javascript中的一个基本函数语句,它返回一个[10,20,30]
的数组//--Lets go a step further
function generateCash() {
var denomination = [];
for (var i = 10; i < 40; i += 10) {
denomination.push(console.log(i));
}
return denomination;
}
当循环迭代时,这将打印10,20,30顺序,但将返回[未定义,未定义,未定义]的数组,主要原因是我们没有推动i的实际值,我们只是打印它out,因此在每次迭代时,javascript引擎都会将其设置为undefined。
//--Lets dive into closures
function generateCash() {
var denomination = [];
for (var i = 10; i < 40; i += 10) {
denomination.push(function() {
console.log(i)
});
}
return denomination;
}
var dn = generateCash();
console.log(dn[0]());
console.log(dn[1]());
console.log(dn[2]());
这有点棘手,你期望输出是什么,它会是[10,20,30]?答案是否定的,让我们看看这是怎么回事。首先,在创建dn时创建全局执行上下文,我们还有generatecash()函数。现在我们看到,当for循环迭代时,它会创建三个匿名函数对象,可能很容易认为push函数中的console.log也被触发了,但实际上并非如此。我们已经调用了generateCash(),因此push函数只是创建了三个匿名函数对象,它不会触发函数。在迭代结束时,当前本地上下文从执行堆栈弹出,它离开状态i:40和arr:[functionobj0(),functionob1(),functionobj2()]。
因此,当我们开始执行最后三个语句时,它们都输出40,因为它无法从当前范围获取i的值,它会向上移动范围链并发现i的值已经所有这些都将触发40的原因是由于dn的每个组件都位于相同的执行上下文中,并且所有这些组件都无法在当前范围内找到i的值,因此将在范围链上运行找到我设置为40并分别输出