【问题标题】:Why JavaScript Engine Compile LET variable before initialization?为什么 JavaScript 引擎在初始化之前编译 LET 变量?
【发布时间】:2019-12-21 11:35:22
【问题描述】:

当我们编写类似下面 sn-p JavaScript 的代码时,不要编译这段代码并返回 Uncaught SyntaxError: Identifier 'variableName' has already been declared 而且我们看不到“Hello World”控制台

function functionName() {
  console.log("Hello World");
}

functionName();
let variableName = "Murad";
let variableName = "Tofiq";

但是当我们编写像下面的sn-p这样的代码时,代码被编译但在执行时返回错误,我们在控制台中看到“Hello World”。

function functionName() {
  console.log("Hello World");
}

functionName();
console.log(variableName);
let variableName = "Tofiq";

我的问题是 JavaScript 编译器知道我们声明变量的语义分析时间让这个是创建 TDZ(临时死区)并且此代码返回错误。但是为什么这段代码会编译?这是bug吗?

【问题讨论】:

  • JavaScript 编译整个脚本,而不是顺序编译。
  • @Barmar 我理解不能按顺序编译。但是编译器可以在初始化之前看到“语义错误”这个变量的使用时间,并且可以停止编译返回错误。像第一个代码示例
  • 不,这只是在我们用 VAR 声明变量时发生的。如果你想在初始化之前访问用 LET 声明的变量,这是返回错误。未捕获的 ReferenceError:在初始化之前无法访问“variableName”
  • 解析时检测到语法错误,运行时检测到引用错误。
  • 但是为什么第一个例子返回错误?这也是语义错误。

标签: javascript v8 jit


【解决方案1】:

原因是因为 JavaScript 在 host environment 中运行。

严格来说,V8 JavaScript 编译器/解释器无法在 parsetime 知道variableName 尚不存在于全局环境中。因此,虽然在同一函数范围(或环境)内两次声明 constletSyntaxError,但您给出的第二个示例是 runtime ReferenceError,因为它不是在代码执行之前生成。

然而,TypeScript 可以检测到这些东西,因为它知道它运行的环境(通过配置),所以它会正确地抱怨并拒绝编译对它不知道的全局变量的引用。

希望这是有道理的。

【讨论】:

  • 感谢您的回答。但实际上我不明白你的第一句话。第一个代码示例与全局环境有什么关系。
  • @andreas-rossberg 的回答可能比我更正确,但是您可以将您编写的每个闭包或函数视为它自己的“环境”。在该环境中声明的变量“关闭”了更高环境中的变量或绑定。因此,当您在
  • 所以当你像Andreas提到的那样给解析器一些随机的JS时,它可以很容易地检测到重复的变量声明,但是它没有检测对未定义变量的引用的知识,因为全局环境变化很大。
  • 以上是浏览器示例。但是 node.js 非常相似,而是当您将一些代码写入文件并运行 node code.js 时。该代码在如下所示的环境中执行:blog.risingstack.com/…(第三个代码 sn-p)可以合理地使 NodeJS 可以启用一个选项,如果你在模块的上下文中引用未定义的变量,但如前所述,它比听起来更难,并且可能导致不可预测的行为。
【解决方案2】:

你的例子很简单,但这里有一个更有趣的例子:

function f() { return g }
let h = f()
if (b) h()
let a = 7
function g() { return a }

一般来说,在初始化之前提前检测变量是否被使用是非常重要的(甚至是不可判定的)。所以它被认为太复杂了,最终是徒劳的,如果只是在某些情况下被拒绝,那将是不一致的。

相比之下,检测重复绑定总是微不足道的。

【讨论】:

  • 好吧,这更有意义。但我认为我们忘记了一件事。例如,在您的 sn-p 中,这是真的,我们不需要在初始化之前使用检测变量。但是 JavaScript 可以看到用 LET 声明的这个变量,并且在一个范围内你不能在 LET 声明之前使用变量。而且我认为这不是问题,只在编译时检查 TDZ(Temproary Dead Zone)。
  • 而且我忘了说 V8 还检查所有变量是否是在当前范围内声明的变量,如果变量未在范围引擎中声明,则搜索变量父范围,如果发现它添加到 Closure 对象中。这个过程发生在编译时。如果你不相信我,我可以通过 chrome 开发者工具展示这个
  • 正如我所说,JS 委员会不想引入不连贯的规则——要么总是静态检查,要么从不检查。披露:我是那个委员会的成员。我在 V8 中实现了let,所以我知道它做了什么。 ;)
  • OMG 太酷了 :) 但是到处都有静态规则 :d 我认为你必须考虑一下。当我们用 let 声明变量同名时,Ecma International 也说我们不能这样做我认为这个问题可以在编译时解决:)
  • 还有我的最后一个问题,为什么你因为性能问题或其他原因不实施?
猜你喜欢
  • 2021-10-19
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-18
  • 1970-01-01
  • 2013-02-26
  • 2019-05-25
相关资源
最近更新 更多