2010-10-11 108 views
7

我正试图用Javascript封闭我的头部。了解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

[“var functionName = function(){}”与“function functionName(){}”in Javascript]中的可能重复(http://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname-in-javascript) – BrunoLM 2010-10-11 01:56:35

+2

@BrunoLM:我认为这是一个不同的问题。 – Thilo 2010-10-11 02:01:54

+0

@Thilo:同样的答案适用。 – BrunoLM 2010-10-11 02:02:19

回答

22

闭合是一对函数在它被定义(假设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

+0

+1非常详细的答案 – alex 2010-10-11 02:11:39

+1

+1链接到[Mozilla的封闭页面](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures) - 这是对封闭的最好,最简洁的解释基础知识和实际应用程序。强烈推荐 – mjmoody383 2013-03-09 03:16:47

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 
+0

很好的例子。现在我明白什么是闭包。关闭(隐藏)外部函数的变量。这是对的? – 2010-10-11 02:05:55

+0

很抱歉,如果我的示例与您的示例类似,则在我发布并未见到您之前,我先打字并测试它。 – alex 2010-10-11 02:07:21

+0

要关闭外部范围的变量,是的。并不意味着隐藏。意味着您的范围得到扩展以包含它所引用的所有内容。我猜这个术语来自诸如数学中的“传递闭包”之类的东西。 – Thilo 2010-10-11 02:07:31

2

一个更好的例子可能是

function add(start, increment) { 
    return function() { 
     return start += increment; 
    } 
} 

var add1 = add(10, 1); 

alert(add1()); // 11 
alert(add1()); // 12 

在这里,每次调用返回的函数时,你加1的内部封装。

返回的函数仍然可以访问其父变量(在本例中为startincrement)。

在较低级别的思考中,我认为这意味着函数的堆栈在返回时不会被销毁。

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'); 
} 
+1

提供REAL-WORLD示例的荣誉......我见过的第一个 – Yarin 2010-11-17 17:17:23

+0

我不是JS的专家,并且想了解为什么在下面的函数中不需要Closure ...它提醒我如何期待它。 (var i = 0; i <5; i ++) varα= function(){ alert(i); }(); } – 2015-04-18 03:28:56

2

一旦你“明白了”,你会想知道为什么花了很长时间才能理解它。这就是我感受到的方式。

我觉得Javascript中的函数范围可以相当简洁地表达。

函数体将有机会获得时可以看到在函数声明的词法环境,也可通过函数的调用创建的任何变量的变量 - 也就是说,本地声明的任何变量,穿过作为参数或以其他语言提供(例如thisarguments)。

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

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 sequentialy作为循环迭代,但会返回一个[undefined,undefined,undefined]的数组,主要原因是我们没有推送i的实际值,我们只是将其打印出来,因此在每次迭代时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.他们之所以会激发40的原因是因为dn的每个组件都在同一个执行上下文中,并且他们都无法在当前范围中找到i的值,所以它们会上升到范围链并发现我设置为40,并分别输出它

+0

您应该格式化您的代码,并将广泛的注释引入它自己的段落中。 – 2015-12-28 22:04:37

+0

完成,谢谢提醒 – 2015-12-30 18:03:48