JavaScript闭包返回的函数的范围和执行上下文

时间:2013-12-01 16:14:25

标签: javascript oop closures

如果没有具体的闭包知识,可以理解该片段:

function closure(){
    var i = 1;
    i++
    var ofunc = function(){return i;};
    return ofunc;
}

var x = closure()
x()

将返回:

=> 2

以下代码段非常违反直觉:

function closure(){
    var i=1;
    i++
    var ofunc = function(){return i;};
    i++;
    return ofunc;
}

var x = closure()
x()

因为它将返回:

=> 3

尽管第二次增量操作(i++;)在ofunc之后,当ofunc通过xx()调用时,其范围是closure {1}}。 closure被执行,i计算结果为3,因此结果。

一个明显的解决方案是:

function closure(){
    var i = 1;
    i++
    var ofunc = function(){return i;}(); //note the parenthesis 
    i++;
    return ofunc;
}

var y = closure()
y //note the missing parenthesis 

再次回归更令人欣慰:

=> 2

在这种情况下,ofunc在声明期间执行,因为最终尾随(),使用当前值i,2。因此y没有' t返回指向ofunc的指针,以y()执行,但执行ofunc的结果。

不幸的是,这会摧毁所有关闭的力量,或者至少在我看来。这是不能立即执行对象函数并在稍后执行时传递参数(到其指针)的可能性。实际上,如果我们向ofunc添加参数:

function closure(){
    var i = 1;
    i++
    var ofunc = function(j){return i+j;};
    return ofunc;
}

我们现在可以调用链接的函数,如:

var x = closure()
x(10)

=> 12

此外,如果我们也向closure添加参数,我们就可以创建一个参数ofunc - 函数类,其中闭包的参数是ofunc的参数。例如,如果ofunc定义了一个对数,则闭包的参数可以设置为其基数,每个参数创建不同的基本对数运算,如:log10 = closure(10)

所以在我看来,() - 形式实际上并不有趣。相反,最好通过获取闭包返回的函数的源来进行审计/调试,并使用外部参数评估。不幸的是:

 x.toSource()

我们只获得未评估的表格:

 => '(function (j){return i+j;})'

如何在执行i之后(但在执行closure之前)获取x()的实际值?那就是:

 => '(function (j){return 2+j;})'

2 个答案:

答案 0 :(得分:1)

只需将您的变量复制到具有所需值的新变量:

function closure(){
    var i = 1;
    i++;
    var i2 = i; /* Now i2 has desired value, even if you change i */ 
    var ofunc = function(j){return i2+j;};
    i++;
    return ofunc;
}
var x = closure()
x(5);

另一种可能性:将您想要的i作为参数传递给自动执行函数,该函数返回另一个函数:

function closure(){
    var i = 1;
    i++;
    var ofunc = (function(i){ return function(j){return i+j;}; })(i);
    i++;
    return ofunc;
}
var x = closure()
x(5);

答案 1 :(得分:1)

理解上有一点差距。我将尝试解释这里发生了什么。

javascript关闭关闭结束/捕获引用到变量。这就是您在示例中遇到的情况。

function closure(){
    var i=1;
    i++
    var ofunc = function(){
        //JS Interpreter: Ohh, there's a variable called *i*, let me hold that reference.
        return i;
    };
    ofunc(); //JS: Let me call that function.
             //While executing: Hey, I have the reference to i, what's the value?
             //Returns 2 here.
    i++;
    return ofunc;
}

closure()(); //JS: Let me call that function.
             //While executing: Hey, I have the reference to i, what's the value?
             //Value retriever(or something): The **latest** value is 3.
             //Returns 3 here.

这是因为,闭包是捕获引用而不是值。如果需要捕获值,则需要以某种方式使闭包捕获变量的状态。 @Oriol描述了一种方式。另一种方法是将其包装在另一个闭包内,以便捕获它的参数。一个例子:

function closure(){
    var i=1;
    i++
    var ofunc = function(x){return function(){return x;};}(i);
    i++;
    return ofunc;
}

closure()(); //returns 2, not 3! :-)

让我们剖析这一行:var ofunc = function(x){return function(){return x};}(i);

var ofunc = function(x){ // ** (i)
    func = function(){
        return x; //Reference to parameter passed in outer function.
    };
}
result = ofunc(i); //is a function.
return result;

当您致电ofunc()时,您会将参数传递给它。该参数表示为(i)。在函数体中,您创建新函数。这个函数是另一个闭包。这样做是因为它保留了对x(来自参数)的引用。由于此x是变量的状态,因此您实际执行的操作是:“创建捕获对外部函数中传递的参数的引用的函数”。传递的参数是变量的状态。因此,在调用ofunc时,您创建一个 kinda 保存变量状态的函数。


为了理解实际的好处,让我们考虑以下用例:创建一个包含10个函数的数组,其中索引i的每个函数都将获取一个输入参数,并在该输入参数乘以时返回结果i

案例#1 :(错误的做法!)

result = [];
for (var i=0; i<10; i++) {
    func = function(x) {
        return x*i;
    };
    result.push(func);
}

result[5](6); //Invoke the i-th function, expected result: 30, Actual result: 60!

案例#2 :(正确的方式)

result = [];
for (var i=0; i<10; i++) {
    func = function(i_) {
        return function(x){ return x*i_; };
    };
    result.push(func(i));
}
result[5](6); //result: 30