为什么函数声明在评估期间总是移到顶部?

时间:2011-07-07 09:02:31

标签: javascript evaluation

这实际上不是我的具体问题,而是我想教育自己的事情:

据我所知,在JavaScript中使用以下代码

if (true) {
    function greet(){ alert("Hello!"); }
} else {
    function greet(){ alert("Hi!"); }
} 
greet();

输出Hi!因为代码实际上被评估为:

greet = function(){ alert("Hello!"); }
greet = function(){ alert("Hi!"); }

if(true){
    // possibly a no-op assignment, such as greet = greet?
}else{
    // same as the other branch?
} 
greet();

从语言设计的角度来看, 为什么JavaScript会以这种方式运作?

3 个答案:

答案 0 :(得分:3)

命名函数是在代码启动之前创建的,因此您不必在使用它的代码之前放置函数的声明:

x(); // this is possible because the function already exists

function x() {}

函数声明的位置无关紧要,即使它在流控制结构中,它仍然在代码启动之前创建。因此,在您的示例中,if语句中根本没有可执行代码,甚至没有无操作分配。

如果要动态分配函数声明,可以使用变量:

var greet;
if (true) {
  greet = function(){ alert("Hello!"); }
} else {
  greet = function(){ alert("Hi!"); }
} 
greet();

您不必使用匿名函数,您可以将两个命名函数中的一个指定给变量。

答案 1 :(得分:1)

你可能已经找到了一种语言的怪癖,如果你看得更远,你也会发现不同的实现。

ECMAScript处理有两个阶段 - 在第一阶段,处理所有声明以在其声明范围内创建命名属性。在第二个中,代码被执行。因此,声明的函数和变量在执行开始时在其声明的作用域中可用,无论它们在声明的代码中的何处。

通过赋值创建或赋值的那些只有在执行赋值代码时才会有值。

所以:

// Call x
x();

// Declare x
function x(){}

无错误地工作。这通俗地称为“吊装”,意味着声明被“悬挂”到范围的顶部。

然而,用你的例子来解释:

if (true) {
    function x(){
        alert('true x');
    }
} else {
    function x(){
        alert('false x');
    }
}

// In most browsers
x(); // false x

// But in Firefox
x(); // true x

对于大多数浏览器,第二个声明会覆盖第一个。这是因为ECMAScript没有块范围,只有函数和全局范围。

但是,在这种情况下,扩展名允许使用该语言。 Firefox将上述视为命名函数表达式,因此显示为true x。更重要的是,因为其他浏览器将表达式视为声明,可以从代码块上方调用 x ,但在Firefox中它不能,你会收到错误。

底线是,如果您想有条件地将一个函数分配给命名参数或变量,请明确地执行:

var x;
if (true) {
    x = function (){
          alert('true x');
        };
} else {
    x = function (){
            alert('false x');
        };
}

以便您获得一致的行为。而且你还必须记住, x 在代码执行之后才会成为函数,它不会被“提升”,因为它不是声明。

答案 2 :(得分:0)

首先,它并不总是以这种方式表现。对于函数表达式,函数对象仅在内部作用域中可用。

其次,当您定义一个函数时,您很可能希望在当前作用域中使用它。因此,它在当前范围内可用是非常自然的。考虑封闭范围(取决于函数定义位置)是全局对象或封闭函数体而不是if块。

现在,我可以将您的问题减少到以下问题:

  • 为什么函数名称用作变量名?

    答:否则开发人员必须始终将函数分配给变量(我更喜欢)。