【问题标题】:Confusing operation of JavaScript `var` keywordJavaScript `var` 关键字的混淆操作
【发布时间】:2009-12-19 13:26:33
【问题描述】:

var 关键字遇到了一个非常奇怪的(对我而言)问题。我已将其缩减为一个相当小的测试用例,并发现它在 Node.js(因此是 V8 和 Chrome)、Safari 4 的检查器(因此是 Nitro)和 FireBug(显然是 SpiderMonkey)中都有体现。我最初是在准备一份错误报告,但由于它被广泛展示,我将假设我完全误解了 JavaScript 应该如何界定和查找变量。

测试用例非常小,在 GitHub 上:http://gist.github.com/260067。第一个示例和第二个示例之间的唯一区别是包含了 var 关键字。

这里还有一个类似的测试用例,它以不同的方式展示了相同的“问题”:https://gist.github.com/698b977ee0de2f0ee54a

编辑:为了排除任何试图解释级联作用域如何工作的答案,我对此非常熟悉。我的问题是我不明白为什么下面的代码“有效”(因为它alert()s 是“外部”,然后是“内部”,然后是“外部”):

(function(){
  var foo = 'outer';
  alert("Outer `foo`: " + foo);

  (function(){
    foo = 'inner';
    alert("Inner `foo`: " + foo);

    var foo;
  })();

  alert("Outer `foo`: " + foo);
})();

var foo; 出现在与foo 重新分配完全无关的位置;那么为什么它会以非常重要的方式影响该分配呢?

【问题讨论】:

  • 你为什么使用评估?绝对没有理由在您发布的代码中使用 eval。
  • 演示问题。实际的实现有很大的不同;你可以在这里看到它:github.com/elliottcable/poopy.js/blob/new-acquire/lib/…
  • 您的最后一个示例有效,因为 1) foo = 'inner';将值分配给此函数有权访问的父范围 foo。 2) 你不需要使用 var 来声明一个变量。
  • 看,错了。这就是我认为会起作用的方式,但事实并非如此。运行该代码(我刚刚在 V8 和 Nitro/WebKit 中对其进行了测试),最后一个警报仍将是“外部”。也就是说,foo = 'inner'; 被限定在子范围内,因为末尾的 var foo;的功能。
  • 好的,我现在明白了。我同意这令人困惑,但这就是为什么使用 JavaScript 的技巧之一是在函数的开头声明所有变量。它可以阻止您今天遇到的错误发生。

标签: javascript v8 spidermonkey


【解决方案1】:

问题在于,与其他语言不同,JavaScript 在函数的开头创建所有变量。这意味着代码:

(function(){
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        var myVar = 5;
    }
})();

实际上被编译解释为

(function(){
    var myVar;
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        myVar = 5;
    }
})();

要创建一个变量并且只让它在 if 或循环块中可用,您必须使用 let,这是一个新的 JavaScript 功能。我不确定有多少浏览器实现了它(如果你使用<script type="text/javascript;version=1.7">,Firefox 3.5 会实现)。

(function(){
    if(myVar == undefined){
        alert(myVar);
    }
    if(myVar == undefined){
        let myVar = 5;
    }
})();

【讨论】:

  • 是的,我知道这一点,请参阅上面我对暗嫩的回复。我的困惑源于它会影响 在本地范围中定义之前的调用。
  • 是的,我终于明白了你的问题,并重写了我的答案。希望这会有所帮助。
  • 是的,这很有帮助。请参阅我在原始问题中的说明。坦率地说,我仍然无法理解为什么会这样。我想我对 JavaScript 这种语言的直观理解不符合规范/实现。无论如何,我会将此答案标记为“已接受”。/=
  • ;version= 是什么鬼东西?那是专有的 Mozilla 扩展吗?我知道 MIME 类型数据可能包含版本规范……
  • elliottcable: ;version= 不是 MIME 类型的一部分。它是关于内容的数据。例如,查看这个 HTTP 标头:Content-Type: text/plain; charset=UTF-8; charset=UTF-8 不是 MIME 类型的一部分,它只是用于解析内容的额外数据。
【解决方案2】:

var exports 的工作方式与许多语言中的局部变量不同。它将exports 声明为整个函数中的局部变量,而不仅仅是封闭块(即使它出现在第一次使用之后),所以同名的函数参数被隐藏了。

编辑:let 关键字的工作方式更传统(它仅为包含块声明一个变量),但并非在所有版本的 JavaScript 中都可用。

【讨论】:

  • 所以解释器提前读取,并注意到变量的声明......以及局部范围内的变量定义(附加到undefined),覆盖了参数的定义?这是非常违反直觉的。在这种情况下,口译员怎么会提前阅读? o.O
  • 是的,它违反直觉,但事实就是如此……关于它是如何完成的,函数在运行之前被解析。
  • """为什么解释器在这种情况下会提前读取?""" - 它在每个实例中都提前读取。它不像其他一些语言那样“逐行”解析/解释器,解析器在开始执行任何内容之前读取所有脚本。
【解决方案3】:

包含 var 意味着 {} 的赋值是对局部变量 export 而不是全局变量 export,这意味着它没有任何效果。

【讨论】:

  • 是的。我明白了。我确实了解基本的 JavaScript。我的问题是,它发生在我们正在调查的电话之后;它对exports 在调用alert() 时是否未定义应该没有影响。不管之后发生什么,exports 在那个时候不是未定义的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-19
  • 1970-01-01
  • 2017-02-28
  • 1970-01-01
  • 2020-04-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多