惊讶于全局变量在 JavaScript 中有未定义的值

IT技术 javascript scope
2021-01-22 10:19:40

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

例子:

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

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

给出输出为

undefined
20

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

这是 JavaScript 引擎的陷阱吗?

6个回答

这种现象被称为: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

这是另一个例子

var test = 'start';

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

end();
alert(test);

你认为这会提醒什么?不,不要只是继续阅读,考虑一下。的value是test什么?

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

var test = 'start';

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

end();
alert(test);

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

如您所见,无论您将变量声明放在哪里,它总是被提升到本地闭包的顶部。


边注:

这也适用于函数。

考虑这段代码

test("Won't work!");

test = function(text) { alert(text); }

这会给你一个参考错误:

未捕获的 ReferenceError:未定义测试

这让很多开发人员望而却步,因为这段代码运行良好:

test("Works!");

function test(text) { alert(text); }

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

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


Ben Cherry在这方面写了一篇出色的文章,标题恰如其分的JavaScript 范围和提升
阅读。它将为您提供完整详细的全貌。

@DuKes0mE - 您始终可以访问函数内的全局变量。
2021-03-21 10:19:40
这是一个很好的解释。但是,我错过了一个可以在函数内访问全局变量的解决方案。
2021-03-22 10:19:40
这种变量提升风格是什么时候实现的?这在javascript中一直是标准的吗?
2021-03-28 10:19:40
访问全局变量使用 window.value
2021-03-29 10:19:40
是的,为什么开头帖子中的案例 A 不起作用并说它未定义?我明白,它将被解释为局部变量而不是全局变量,但是它如何成为全局变量呢?
2021-04-06 10:19:40

我有点失望,这里的问题被解释了,但没有人提出解决方案。如果您想访问函数作用域中的全局变量,而无需函数首先创建未定义的局部变量,请将该变量引用为window.varName

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

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

话虽如此,第一个console.log(value)看到了value变量(内部变量遮蔽了外部value),但它还没有被初始化。您可以将其视为所有变量声明都隐式移动到函数的开头(不是最内部的代码块),而定义则保留在同一位置。

也可以看看

我总是喜欢简单的词 +1 :)
2021-04-04 10:19:40

有一个全局变量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
  1. 保持访问外部变量(不仅仅是全局范围)的最简单方法当然是尽量不要在函数中以相同的名称重新声明它们;只是不要在那里使用var建议使用适当的描述性命名规则有了这些,将很难以类似value命名的变量结束(这方面不一定与问题中的示例相关,因为为了简单起见,可能已经给出了这个变量名)。

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

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