【问题标题】:Surprised that global variable has undefined value in JavaScript惊讶于全局变量在 JavaScript 中具有未定义的值
【发布时间】:2012-02-23 13:03:07
【问题描述】:

今天,当我看到一个全局变量在某种情况下具有undefined 值时,我感到非常惊讶。

例子:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

输出为

undefined
20

这里,为什么JavaScript引擎将全局值视为undefined?我知道 JavaScript 是一种解释型语言。它是如何考虑函数中的变量的?

这是 JavaScript 引擎的一个陷阱吗?

【问题讨论】:

    标签: javascript scope


    【解决方案1】:

    这种现象被称为:JavaScript 变量提升

    您在任何时候都不会访问函数中的全局变量;您只能访问本地的 value 变量。

    您的代码相当于以下内容:

    var value = 10;
    
    function test() {
        var value;
        console.log(value);
    
        value = 20;
        console.log(value);
    }
    
    test();
    

    您仍然对收到 undefined 感到惊讶吗?


    解释:

    这是每个 JavaScript 程序员迟早都会遇到的问题。简而言之,您声明的任何变量总是提升到本地闭包的顶部。因此,即使您在第一次 console.log 调用之后声明了变量,它仍然被认为是您在此之前声明了它。
    但是,只有声明部分被吊起;另一方面,分配不是。

    所以,当您第一次调用console.log(value) 时,您正在引用本地声明的变量,该变量尚未分配任何内容;因此undefined

    这里是another example

    var test = 'start';
    
    function end() {
        test = 'end';
        var test = 'local';
    }
    
    end();
    alert(test);
    

    您认为这会引起什么警报?不,不要只是继续阅读,想想吧。 test的值是多少?

    如果你说的不是start,那你就错了。上面的代码等价于:

    var test = 'start';
    
    function end() {
        var test;
        test = 'end';
        test = 'local';
    }
    
    end();
    alert(test);
    

    这样全局变量就不会受到影响。

    如您所见,无论您将变量声明放在何处,它始终提升到本地闭包的顶部。


    旁注:

    这也适用于函数。

    考虑this piece of code

    test("Won't work!");
    
    test = function(text) { alert(text); }
    

    这会给你一个参考错误:

    Uncaught ReferenceError: test is not defined

    这让很多开发人员望而却步,因为 this piece of code 工作正常:

    test("Works!");
    
    function test(text) { alert(text); }
    

    如上所述,原因是分配部分没有被提升。所以在第一个示例中,当test("Won't work!") 运行时,test 变量已经被声明,但还没有分配给它的函数。

    在第二个例子中,我们没有使用变量赋值。相反,我们使用了正确的函数声明语法,确实完全提升了函数。


    Ben Cherry 就此写了一篇出色的文章,标题恰如其分 JavaScript Scoping and Hoisting
    阅读。它将为您提供完整的详细信息。

    【讨论】:

    • 这是一个很好的解释。但是,我错过了一个可以在函数内访问全局变量的解决方案。
    • @DuKes0mE - 您可以随时访问函数内部的全局变量。
    • 是的,为什么开篇文章中的案例 A 不起作用并说它未定义?我知道,它将被解释为本地变量而不是全局变量,但那它怎么会是全局变量呢?
    • 访问全局变量使用window.value
    • 这种类型的变量提升是什么时候实现的?这在 javascript 中一直是标准的吗?
    【解决方案2】:

    我有点失望,这里的问题得到了解释,但没有人提出解决方案。如果你想在函数范围内访问一个全局变量而不首先创建一个未定义的局部变量,请将该变量引用为window.varName

    【讨论】:

    • 是的,很糟糕,其他人没有提出解决方案,因为这是 Google 搜索结果中的第一个结果。他们几乎回答了关于它是 js 引擎中的一个陷阱的实际问题....叹息 --> 感谢您的回答
    • 这就是理论知识和完成工作之间的区别。感谢您的回答!
    • 对我来说,我在任何函数的任何地方都'var'd 一个全局名称是一个错误。至少会造成混乱。最坏的情况是需要谷歌搜索。谢谢
    • Javascript 每天都让我感到惊讶。谢谢大佬,答案很有帮助。
    • 感谢您的解决方案,我在未定义全局变量但仅在 Safari 中发现此问题。出现了其他“包含”文件和全局变量,例如“google”,所以我复制了 google 使用的方法:window.globalVarFromJS = window.globalVarFromJS || {};然后我找到了你的解决方案,所以我想我会添加它。
    【解决方案3】:

    JavaScript 中的变量始终具有函数范围。即使它们是在函数中间定义的,它们之前也是可见的。使用函数提升可能会观察到类似的现象。

    话虽如此,第一个console.log(value) 看到了value 变量(内部变量遮蔽了外部value),但它还没有被初始化。你可以把它想象成所有的变量声明都被隐式地移到了函数的开头(不是最里面的代码块),而定义却留在了同一个地方。

    另见

    【讨论】:

    • 我总是喜欢简单的文字+1 :)
    【解决方案4】:

    有一个全局变量value,但是当控制进入test函数时,又声明了另一个value变量,它遮蔽了全局变量。由于 JavaScript 中的变量声明(但不是赋值)被提升到声明它们的范围的顶部:

    //value == undefined (global)
    var value = 10;
    //value == 10 (global)
    
    function test() {
        //value == undefined (local)
        var value = 20;
        //value == 20 (local)
    }
    //value == 10 (global)
    

    请注意,函数声明也是如此,这意味着您可以在函数出现在代码中定义之前调用它:

    test(); //Call the function before it appears in the source
    function test() {
        //Do stuff
    }
    

    还值得注意的是,当你将两者组合成一个函数表达式时,变量将是undefined,直到赋值发生,所以在发生赋值之前你不能调用函数:

    var test = function() {
        //Do stuff
    };
    test(); //Have to call the function after the assignment
    

    【讨论】:

      【解决方案5】:
      1. 保持对外部变量(不仅仅是全局范围)的访问的最简单方法当然是尽量不在函数中以相同的名称重新声明它们;只是不要在那里使用 var 。建议使用proper descriptive naming rules。有了这些,就很难得到像 value 这样命名的变量(这方面不一定与问题中的示例相关,因为为简单起见,可能已经给出了这个变量名)。

      2. 如果该函数可能在其他地方重用,因此无法保证在该新上下文中实际定义的外部变量,则可以使用Eval 函数。此操作速度较慢,因此不建议用于性能要求高的功能:

        if (typeof variable === "undefined")
        {
            eval("var variable = 'Some value';");
        }
        
      3. 如果您要访问的外部范围变量是在命名函数中定义的,那么它可能首先附加到函数本身,然后从代码中的任何地方访问——无论是从深度嵌套的函数还是事件处理程序之外的一切。请注意,访问属性的速度要慢得多,并且需要您更改编程方式,因此除非确实有必要,否则不建议这样做:Variables as properties of functions (JSFiddle):

        // (the wrapper-binder is only necessary for using variables-properties
        // via "this"instead of the function's name)
        var functionAsImplicitObjectBody = function()
        {
            function someNestedFunction()
            {
                var redefinableVariable = "redefinableVariable's value from someNestedFunction";
                console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
                console.log('--> redefinableVariable: ', redefinableVariable);
            }
            var redefinableVariable = "redefinableVariable's value from someFunctionBody";
            console.log('this.variableAsProperty: ', this.variableAsProperty);
            console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('redefinableVariable: ', redefinableVariable);
            someNestedFunction();
        },
        functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
        functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
        functionAsImplicitObject();
        
        // (spread-like operator "..." provides passing of any number of arguments to
        // the target internal "func" function in as many steps as necessary)
        var functionAsExplicitObject = function(...arguments)
        {
            var functionAsExplicitObjectBody = {
                variableAsProperty: "variableAsProperty's value",
                func: function(argument1, argument2)
                {
                    function someNestedFunction()
                    {
                        console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                            functionAsExplicitObjectBody.variableAsProperty);
                    }
                    console.log("argument1: ", argument1);
                    console.log("argument2: ", argument2);
                    console.log("this.variableAsProperty: ", this.variableAsProperty);
                    someNestedFunction();
                }    
            };
            return functionAsExplicitObjectBody.func(...arguments);
        };
        functionAsExplicitObject("argument1's value", "argument2's value");
        

      【讨论】:

        【解决方案6】:

        即使使用全局变量,我也遇到了同样的问题。我发现我的问题是全局变量不会在 html 文件之间持续存在。

        <script>
            window.myVar = 'foo';
            window.myVarTwo = 'bar';
        </script>
        <object type="text/html" data="/myDataSource.html"></object>
        

        我尝试在加载的 HTML 文件中引用 myVar 和 myVarTwo,但收到未定义的错误。 长话短说,我发现我可以使用以下方法引用变量:

        <!DOCTYPE html>
        <html lang="en">
            <!! other stuff here !!>
            <script>
        
                var myHTMLVar = this.parent.myVar
        
                /* other stuff here */
            </script>
        </html>
        

        【讨论】:

          猜你喜欢
          • 2012-08-11
          • 1970-01-01
          • 2011-12-22
          • 2011-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多