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

时间:2011-11-07 11:57:27

标签: javascript

我有以下JavaScript代码:

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

你能解释为什么警告弹出2而不是1?

谢谢,

4 个答案:

答案 0 :(得分:9)

这涉及到执行进入函数时会发生什么:省略了很多细节,所有函数声明(你使用过的样式)都被处理了,只有之后 em>一步一步执行代码执行。因此,您的return语句对选择哪个函数声明没有影响。并且所选择的声明始终是源代码顺序中的最后一个(这包含在 - 非常粗糙的散文中 - 在规范的Section 10.5中)。

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

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

此代码在技术上是不正确的(因为您的代码跟随return始终会被跟踪),但它说明了差异:您看到alert(1)发生而不是alert(2) ,因为那些表达式在到达之前不会被评估。

您可以在10.4.310.5部分中阅读有关执行进入函数时发生的事情的更多信息(声明不是在第一个逐步代码之前完成的唯一事情)规范。


凭借你的新知识,一个小测验:这里发生了什么? (注意:从不这样做。)

function foo() {

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

    bar();
}

foo();

答案是: 它有所不同,不要这样做。 有些引擎将使用第一个bar,其他引擎将使用第二个,并且其他人会称它为语法错误。这是因为实际上 是一个错误,所以引擎可以自由地做他们认为最好的事情。如果仔细查看语言语法,您会发现将函数声明放在其立即包含的范围内的分支中是无效的。它们必须处于该范围的顶层。根据您对声明的新理解,原因应该是显而易见的:它们与范围内的执行流程无关,因此您无法根据执行流程选择它们。

那么现实世界会发生什么?可悲的是,如果你处于“松散”模式(不严格),通常不会出错。一些引擎(例如Chrome的V8,在撰写本文时)将忽略流控制语句,只选择最后声明的函数(因此您得到的反直觉结果是 second {{1}使用函数),其他引擎(Firefox的SpiderMonkey,IE11的JScript)有效地重写您的代码,将这些声明转换为表达式,因此您得到第一个bar。例如,它会因引擎而异。好消息是,如果您在严格模式下尝试此操作,那么所有这三个(V8,SpiderMonkey和IE11的JScript)将失败而不是选择一个(V8和SpiderMonkey)在控制台中有明确的错误消息; JScript只有令人惊讶的“bar未定义”,但......)。

如果你想做之类上面的内容,但是跨引擎有效且一致,请使用表达式:

bar

kangax's Named Function Expressions Demystified page上有一个有趣的探索。

答案 1 :(得分:2)

有些东西被称为“吊装” - 它是范围/功能激活解决过程的必然结果(没有太多细节)。

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

以下代码:

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

将打印“undefined”,这是因为,简单地说,它等同于:

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

在你的例子中 - 有一件重要的事情需要记住 - 这些是函数声明 - 因此它们会被处理为任何变量,但由于它们“包含”了函数体,所以整个声明(包括等同于a = 5)是“悬挂的”。

在变量处理阶段 - 如果已经处理了同名变量 - 它的值和属性被替换。

所以你的代码相当于:

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

答案 2 :(得分:1)

因为匿名函数的主体不像你期望的那样是程序性的。

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

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

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

此时警报包含2

对您来说并不奇怪

答案 3 :(得分:0)

因为首先解析代码,所以f()函数被承保,而不是执行,并调用最后一个f()函数。