为什么第二个函数声明获胜,即使我在它之前返回

Why does second function declaration win even though I return before it?

本文关键字:返回 函数 第二个 声明 获胜 为什么      更新时间:2023-09-26

我有以下JavaScript代码:

(function() { 
    function f(){ alert(1); }
    return f();
    function f(){ alert(2); }
})();

你能解释一下为什么警报会弹出2而不是1吗?

谢谢,

这涉及到当执行进入函数时会发生什么:省略了很多细节,所有函数声明(您使用的样式(都会被处理,只有在之后才会逐步执行代码。因此,return语句对选择哪个函数声明没有影响。选择的声明始终是源代码顺序中的最后一个声明(这在规范的第10.5节中用极其夸张的散文进行了介绍(。

如果您使用函数表达式,结果将从根本上不同,这些表达式作为逐步代码的一部分进行评估:

(function() { 
    var f;
    f = function(){ alert(1); };
    return f();
    f = function(){ alert(2); };
})();

从技术上讲,此代码是不正确的(因为您有一个始终遵循return的代码(,但它说明了差异:您看到的是alert(1)而不是alert(2),因为这些表达式在达到它们之前不会求值。

您可以在本规范的第10.4.3节和第10.5节中阅读更多关于执行进入函数时会发生什么的信息(在第一步代码之前,声明并不是唯一要做的事情(。


用你的新知识,一个小测验:这里发生了什么?(注意:永远不要这样做。(

function foo() {
    if (true) {
        function bar() {
            alert(1);
        }
    }
    else {
        function bar() {
            alert(2);
        }
    }
    bar();
}
foo();

答案是:各不相同,不要那样做一些引擎将使用第一个bar,其他引擎将使用第二个,其他引擎则称其为语法错误。这是因为这实际上是一个错误,所以引擎可以自由地做他们认为最好的事情。如果您仔细研究语言语法,您会发现将函数声明放在其直接包含范围内的分支内是无效的。它们必须处于该范围的最高级别。随着你对声明的新理解,原因应该是显而易见的:它们与范围内的执行流无关,因此自然不能根据执行流来选择它们。

那么现实世界中会发生什么呢?遗憾的是,如果你处于"宽松"模式(不严格(,通常不会出错。一些引擎(例如,截至本文撰写之时,Chrome的V8(会忽略流控制语句,只选择最后声明的函数(因此,您会得到使用第二个bar函数的反直觉结果(,其他引擎(Firefox的SpiderMonkey、IE11的JScript(会有效地动态重写您的代码,将这些声明转换为表达式,所以你得到了第一个CCD_ 7。例如,它会因发动机而异。好消息是,如果您在严格模式下尝试此操作,所有这三个(V8、SpiderMonkey和IE11的JScript(都将失败,而不是选择一个(V8和SpiderMonkeys在控制台中有清晰的错误消息;JScript只有令人惊讶的"bar未定义",但是…(。

如果你想做一些类似的事情,但在引擎之间有效且一致,请使用表达式:

function foo() {
    var bar;
    if (true) {
        bar = function() {
            alert(1);
        };
    }
    else {
        bar = function() {
            alert(2);
        };
    }
    bar();
}
foo();

在kangax的命名函数表达式解密页面上有一个有趣的探索。

有一种叫做"提升"的东西——它是范围/功能激活解决过程的必然结果(不需要太多细节(。

这意味着,在进入变量和函数的作用域时,函数声明将被处理和存储,就像它们是在作用域的开头一样。作业部分留在原地。

所以下面的代码:

function()
{
    console.log(a)
    var a = 5;
}

将打印"未定义",这是因为,简单地说,它相当于:

function()
{
    var a;
    console.log(a)
    a = 5;
}

在您的示例中,有一件重要的事情需要记住-这些是函数声明-因此它们将像任何变量一样进行处理,但由于它们"包含"函数体,因此整个声明(包括等效的a = 5(将被"提升"。

在变量处理阶段,如果已经处理了同名变量,则替换其值和属性。

所以你的代码相当于:

(function() { 
    function f(){ alert(2); }
    return f();
})();

因为匿名函数的主体并不像您所期望的那样是过程性的。

相反,它是一个具有属性的对象。并且允许正向引用——也就是说,一个函数中的代码可以引用稍后在对象中定义的属性。

由于同一属性有重复的定义,因此后一个定义优先,有效地覆盖了第一个定义。因此,您的代码片段相当于(删除第一个被覆盖的定义(:

(function() { 
    return f();
    function f(){ alert(2); }
})();

在这一点上,警报包含CCD_ 10应该不会让您感到惊讶。

因为代码是首先解析的,所以f((函数是被编写的,而不是被执行的,最后一个f((功能是被调用的。