澄清JavaScript变量范围

时间:2012-04-15 19:17:13

标签: javascript variables scope

我已经知道如何使这段代码正常工作,但我的问题更多的是为什么它会像这样工作,以及我做的正确。

我可以用来展示我的问题的最简单的例子是:

假设我有一个函数,只需按一下按钮就可以将输入字段的值增加10。

var scopeTest = {

    parseValue : function( element, value ) {
        value = parseInt( element.val(), 10 );
        //Why does this not return the value?
        return value;
    },


    incrementValue : function( element, button, value ) {
        button.on('mousedown', function (e) {
            //Execute the parseValue function and get the value
            scopeTest.parseValue( element, value ); 
            //Use the parsed value      
            element.val( value + 10 );    
            e.preventDefault(); 
        });               
    },


    init : function () { 
        var element = $('#test-input'),
            button = $('#test-button'),
            value = ''; 

        this.incrementValue( element, button, value );
    }

};

scopeTest.init();

上面的代码不起作用,因为parseValue方法在value方法中执行时没有正确返回incrementValue var。

要解决这个问题,我必须将scopeTest.parseValue( element, value );参数设置为value变量,如下所示:

value = scopeTest.parseValue( element, value );

比代码有效。

但我的问题是为什么?为什么这个额外的变量赋值步骤是必要的,为什么return语句不够?我也在用我的函数/方法做正确的事,或者这只是JavaScript的工作方式?

这里的工作示例=> http://jsfiddle.net/Husar/zfh9Q/11/

5 个答案:

答案 0 :(得分:2)

因为 parseValue 参数只是一个引用。是的,您可以更改对象,因为您有一个引用,但是如果您分配给引用它现在指向另一个对象。

原始版本未更改。是的,返回“足够”,但是您将新对象保存在变量中,其生命周期在下一行代码处结束。

人们说JavaScript通过引用传递对象,但是从字面上理解这可能会令人困惑。 JavaScript中的所有对象句柄都是引用。此引用本身不是通过引用传递的,也就是说,您没有获得双间接指针。因此,您可以通过形式参数更改对象本身,但不能更改调用站点的引用本身。

答案 1 :(得分:1)

这不是一个范围问题,它是传递引用和传值之间的混淆。

在JavaScript中,所有数字都按值传递,这意味着:

var value = 10;
scopeTest.parseValue( element, value );
// value still == 10

对象和数组通过引用传递,意思是:

function foo( obj ){
    obj.val = 20;
}

var value = { val: 10 }
foo( value );
// value.val == 20;

答案 2 :(得分:1)

正如其他人所说的那样传递参考 vs 传递val

鉴于: function foo (){return 3+10;} foo();
怎么了?执行操作,但您没有在任何地方设置该值。

鉴于: result = foo();
该操作执行但您已存储该值以供将来使用。


这只是范围问题

  • var param = 0;  
    function foo( param ) { 
       param = 1; 
    } 
    foo(param);
    console.log(param);                  // still retains value of 0
    

为什么?

param是全局的,但在函数内部,参数的名称称为param,因此该函数不会使用全局。相反,param仅应用本地实例(this.param)。如果你这样做,那就完全不同了:

  • var param = 0;  
    function foo() {                     // notice no variable 
       param = 1;                        // references global
    } 
    foo(param);
    console.log(param);                  // new value of 1
    

这里没有名为param的局部变量,所以它使用全局。

答案 3 :(得分:1)

这主要是范围问题。 pass-by- *问题很难讨论,因为sender变量和被调用函数变量具有相同的名称。无论如何我都会尝试。

变量具有可见范围。您可以将其视为存储内容的位置。此范围由您的函数的位置定义。意味着它在源代码中的位置(在全局范围内或在函数范围内)。它是在您编写源代码时定义的,而不是之后调用函数的方式。

范围可以嵌套。在您的示例中,有四个范围。全局范围和每个函数都有一个范围。函数的作用域都具有全局作用域作为父作用域。父范围意味着每当您尝试访问名称/变量时,首先在函数范围中搜索它,如果未找到,则搜索将继续到父范围,直到找到名称/变量或已达到全局范围(在这种情况下,你会收到一个无法找到的错误。)

允许多次定义相同的名称。我认为这是你困惑的根源。您眼睛的名称“值”始终相同,但在脚本中存在三次。每个函数都定义了它:parseValue和incrementValue作为参数,init作为local var。这种效果称为阴影。这意味着名称为'value'的所有变量始终存在,但如果您查找名称,则会先找到一个名称,从而使另一个变得不可见/阴影。

在这种情况下,“value”在所有三个函数中都被视为相似,因为局部var和参数的范围是相同的。这意味着只要您输入其中一个方法,就可以输入功能范围。通过输入范围,名称“value”将添加到范围链中,并在执行函数时首先找到。反之亦然。如果保留函数作用域,则会从作用域链中删除“value”并使其不可见并被丢弃。

这里非常令人困惑,因为你调用的函数带有一个名为“value”的参数“value”,但它们仍然意味着不同的东西。不同的是需要将值从一个“值”传递到另一个“值”。会发生什么是外部“值”的值被复制到内部“值”。这意味着传递价值。被复制的值可以是对象的引用,这是大多数人认为它是通过引用传递的。我很抱歉,如果这听起来令人困惑,但这里有太多的价值命名。

该值从外部函数复制到被调用函数,并且只在被调用函数内部存在。如果函数结束,您对其所做的每个更改都将被丢弃。唯一的可能性是返回你的“副作用”。这意味着您的工作将在函数被丢弃之前不久被复制回变量

对于其他替代方案确实是留下参数并与范围链一起工作,例如全球范围。但我强烈建议你不要这样做。它似乎很容易使用,但它产生了许多微妙的错误,这将使你的生活更加艰难。最好的办法是确保变量具有最窄的范围(使用它们的位置)并传递每个函数参数的值和返回值。

答案 4 :(得分:-1)