更好地理解JavaScript预编译

5

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

我目前正在学习javascript在机器中的实际运行方式,以下是我在例子中看到的一段代码。我不知道为什么最终的警报是1而不是10。所以我想知道是否有人能帮我解释javascript虚拟机是如何执行这些代码的。谢谢!

3个回答

6

这是由于函数声明提升而导致的:

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

如果在bar内声明foo被_移动_到顶部,那么应用相同的行为,bar的声明不是会被移动到var foo=1;之前吗?(这是一个真正的问题,不是修辞问句) - Ejaz
@Ejay 正确。这意味着如果在 function bar() 之前调用了 bar(),代码仍将成功执行。 - CodingIntrigue

3

我不知道为什么最终的警报是1而不是10。

因为在bar中的这一行中的foo

foo = 10;

这是函数声明中稍后在该函数中声明的类似变量的东西*:
function foo(){}

...不是在bar之外的foo。也就是说:

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

...不是指向包含作用域中声明的foovar foo)。

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

  1. 函数声明在进入包含作用域(在本例中是调用bar)时立即处理,而不是在函数中逐步处理代码之前。这有时被称为“提升”声明(因为它们发生得就像在顶部一样)。由于函数声明不是逐步执行的代码,因此return对其是否被处理没有影响;它在return发生之前被处理。

  2. 函数声明还创建了可以看作是具有函数名称的变量。因此,在函数声明中的foo有效地成为该名称的变量(更多详细信息请参见下文),并且正如您在该代码中看到的那样,您可以将新值分配给这些“变量”。

当运行该代码时,JavaScript引擎执行以下操作:

  1. 创建名为foo的变量,并为其赋初始值undefined

  2. 创建函数bar,将bar添加为当前作用域内的符号(有效地是变量),并将其作为对bar函数的引用。

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

  4. 将值1分配给foo

  5. 调用bar函数。

  6. 创建与调用bar相关的foo函数,将foo添加为调用期间的符号(有效地是变量),并将其作为对该函数的引用。

  7. 开始该范围的逐步代码。

  8. 将值10分配给本地foo(原来是指向该函数的)。

  9. 从函数中返回。

  10. 使用该作用域中的foo调用alert,它仍然具有值1

您可以在规范的§10.4.3节和其链接的部分中了解所有详细信息。


在JavaScript中,每个执行上下文(全局上下文和通过调用函数创建的任何上下文等)都有一个对象,用于保存该上下文中使用的各种名称及其值;它被称为“绑定对象”。上下文的绑定对象(我在这里跳过了一些无关的细节)对于每个变量、函数声明和一些其他东西(如伪数组arguments、函数本身的名称(指向函数)等)都有属性。属性的名称是变量、声明的函数等的名称。这就是为什么在bar内部将foo赋值会覆盖在bar中声明的foo函数的引用,而不是分配给外部作用域中的变量。foo在bar中是一个有效的局部变量,即使它没有用var声明,因为它是一个函数声明。

1
这与一个叫做“提升”的概念有关。 function foo 本质上只是 var foo = function .. 的另一种语法,所以在 bar 中,名称 foo 不是指外部的 foo 变量,而是指本地定义的 foo。 这个 foo 最初是一个函数,但后来被 10 覆盖。
现在,通过提升,名称 foo 在代码执行之前在解析时就被“保留”并作用域化。从本质上讲,它像这样执行:
function bar(){
  var foo = function () {};
  foo = 10;
  return;
}

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接