更好地理解 javascript 预编译

A better understanding of javascript precompile

var foo=1;
function bar(){
  foo=10;
  return;
  function foo(){}
}
bar();
alert(foo);

我目前正在学习 javascript 在机器中实际上是如何 运行 的,这是我在示例中看到的一段代码。我不知道为什么最后的警报是 1 而不是 10。所以我想知道任何人都可以帮助我解释 javascript 虚拟机实际上是如何执行这些代码的。谢谢!

I got no idea why the final alert is 1 instead of 10.

因为bar这一行的foo:

foo = 10;

...是函数声明稍后在该函数中声明的类似变量的东西*:

function foo(){}

...不是 foo bar 之外的 foo。即:

var foo=1;
function bar(){
  foo=10;             // <== This `foo`
  return;
  function foo(){}    // <== Is the `foo` declared here
}
bar();
alert(foo);

...不是在包含范围 (var foo) 中声明的 foo

发生这种情况有两个原因:

  1. 函数声明在进入包含作用域时立即处理(在本例中调用 bar),先于函数中的任何分步代码。这有时被称为 "hoisting" 声明(因为它们就像在最顶层一样发生)。由于函数声明不是逐步代码,因此 return 对其是否被处理没有影响;它在 return 发生之前得到处理。

  2. 函数声明还可以创建带有函数名称的变量。因此,函数声明中的 foo 实际上变成了具有该名称的变量(更多内容见下文)——正如您在该代码中所见,您可以为这些 "variables."

    分配新值

当您 运行 该代码时,JavaScript 引擎执行的顺序如下:

  1. 创建一个名为foo的变量并赋予它初始值undefined

  2. 创建函数 bar,将 bar 添加为当前范围内的范围内符号(实际上是一个变量),并使其成为对 bar函数。

  3. 开始该范围的分步代码。

  4. 将值1赋给foo

  5. 调用 bar 函数。

  6. 创建与调用 bar 相关的 foo 函数,在调用期间添加 foo 作为范围内符号(实际上是一个变量),并且使其成为函数的引用。

  7. 启动该范围的分步代码。

  8. 将值10赋值给局部foo(用于引用函数)。

  9. Returns出函数.

  10. 在该范围内使用 foo 调用 alert,其值仍然为 1

您可以在 §10.4.3 of the spec 及其链接的部分中阅读所有详细信息。


* "variable-like thing" 在 JavaScript 中,每个 执行上下文 (全局上下文和通过调用函数等创建的任何上下文)都有一个对象它用于保存在该上下文中使用的各种名称及其值;它被称为 "binding object." 上下文的绑定对象(我在这里跳过了一些不相关的细节)对于每个变量、函数声明和其他一些东西都有 properties arguments 伪数组,函数本身的名称(指回函数),等等。属性的名称是变量的名称、声明的函数等。这就是为什么在 bar 中分配给 foo 会覆盖对 bar 中声明的 foo 函数的引用,而不是分配给外部范围内的变量。 foo 实际上bar 中的局部变量,即使由于函数声明,它没有用 var 声明。

这是由于function declaration hoisting:

var foo=1;
function bar(){
  function foo(){} // This gets moved up here by the engine
  foo=10; // You've reassigned the local `foo` function to 10,
          // leaving the global `foo` untouched
  return;
}
bar();
alert(foo); // Since the foo has never changed in this scope, it's still 1

这与一个叫做提升的概念有关。 function foo 本质上只是 var foo = function .. 的替代语法,因此在 bar 内部,名称 foo 不引用外部 foo 变量,而是引用本地定义的 foofoo 首先是一个函数,但后来被 10 覆盖。

现在,通过提升名称 foo 是 "reserved" 并在代码执行之前在解析时限定范围。本质上,它是这样执行的:

function bar(){
  var foo = function () {};
  foo = 10;
  return;
}

因此它根本不会覆盖外部变量。