更好地理解 javascript 预编译

Posted

技术标签:

【中文标题】更好地理解 javascript 预编译【英文标题】:A better understanding of javascript precompile 【发布时间】:2015-04-21 11:36:12 【问题描述】:

var foo=1;
function bar()
  foo=10;
  return;
  function foo()

bar();
alert(foo);

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

【问题讨论】:

【参考方案1】:

这是由于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

【讨论】:

如果在bar 中声明foo移动 到顶部,然后应用相同的行为,bar 的声明不会在var foo=1; 之前移动(真正的问题,而不是修辞问题)? @Ejay 正确。这意味着如果我调用bar() 之前 function bar(),代码仍然会成功。【参考方案2】:

我不知道为什么最后的警报是 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);

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

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

    函数声明在进入包含范围时立即处理(在这种情况下,调用bar),在函数中的任何分步代码之前。这有时被称为“提升”声明(因为它们就像它们在最顶端一样发生)。而且由于函数声明不是一步一步的代码,return 对它是否被处理没有影响;它在return 发生之前得到处理。

    函数声明还可以创建带有函数名称的变量。因此,函数声明中的 foo 实际上变成了具有该名称的变量(更多内容见下文)——正如您在该代码中看到的那样,您可以为这些“变量”分配新值。

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

    创建一个名为foo的变量,并为其赋予初始值undefined

    创建函数bar,在当前作用域中添加bar作为作用域内符号(实际上是一个变量),并使其成为对bar函数的引用。

    李>

    开始该范围的分步代码。

    将值1 分配给foo

    调用bar函数。

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

    启动该范围的分步代码。

    将值10赋值给本地的foo(用于引用函数)。

    从函数中返回。

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

您可以阅读§10.4.3 of the spec 中的所有血腥细节及其链接的部分。


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

【讨论】:

【参考方案3】:

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

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

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

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

【讨论】:

以上是关于更好地理解 javascript 预编译的主要内容,如果未能解决你的问题,请参考以下文章

arXiv | 如何更好地理解自然语言?自训练+预训练

个人对JavaScript预编译的理解

JavaScript预编译

JavaScript预编译流程详解

JavaScript预编译原理分析

JavaScript之预编译