23

我阅读了 Javascript Hoisting 的概念。它非常令人困惑,但我看到了一些示例并了解了提升的实际作用。

所以基本上“提升是 JavaScript 将所有声明移动到当前范围的顶部(到当前脚本或当前函数的顶部)的默认行为。

但我无法理解以下实现:

var is_android = true;
if (is_android) {
    function foo() {
        alert('I am Android');
    }
} else {
    function foo() {
        alert('I am NOT Android');
    }
}
foo();

输出在警告框中显示“我不是 Android ”。

我想知道为什么foo()即使is_androidvalue 为 true 也会从 else 块中调用。

任何帮助将不胜感激。

4

5 回答 5

21

tl;dr:不要在块内使用看起来像函数声明的东西,尤其是不是条件的。


事实上,大多数浏览器都以错误的方式解释这段代码。他们将函数定义视为函数声明,即使在块内不允许函数声明,因为函数声明不是语句,它是源元素

这通常是如何工作的:

在代码执行之前,解释器会查找所有变量和函数声明,无论它们在哪里,并在当前/新环境中为它们创建一个绑定。然后它开始实际执行代码。

因此,假设函数定义被解释为声明,因为有两个声明,最后一个获胜。你的代码基本上变成:

function foo() {
    alert('I am Android');
}
function foo() {
    alert('I am NOT Android');
}
var is_android;

is_android = true;
if (is_android) {

} else {

}
foo();

其他引擎会以不同的方式解释它,但仍然不正确(IMO,见下文):

在以下脚本中,零函数从未定义且无法调用,因为 'if (0)' 将其条件评估为 false:

if (0) {
   function zero() {
      document.writeln("This is zero.");
   }
}

注意:一些 JavaScript 引擎,不包括 SpiderMonkey,错误地将任何具有名称的函数表达式视为函数定义。这将导致定义为零,即使在始终为假的 if 条件下也是如此。有条件地定义函数的一种更安全的方法是匿名定义函数并将其分配给变量:

if (0) {
   var zero = function() {
      document.writeln("This is zero.");
   }
}

但是在这种情况下,如果函数定义真的被解释为函数表达式(类似于(function zero() { ... })),那么函数的名称将无法在包含范围内访问,并且函数将丢失。

于 2014-04-17T05:52:50.263 回答
5

在 Javascript 中,两个相似的外观之间存在细微的差异

 function square(x) { return x*x; }

 var square = function(x) { return x*x; }

最大的区别是,在第一种情况下,函数对象对square名称的分配是在进入作用域时完成的,而不是在到达行时完成的。这允许调用稍后在源代码中定义的函数......例如:

console.log(cube(12));
function cube(x) { return x*x*x; }

是一个可以工作的有效脚本,即使调用发生在函数定义“之前”(顺便说一句,允许这种代码是 IMO 规则在语言中的原因)。

在第二种情况下,赋值只是一个常规语句,当(如果)控制流通过它时,它就会被执行。

如果您希望该代码段按预期工作,则只需将代码从

function <name> (...) { ... }

var <name> = function (...) { ... }

PS:您也可以在第二种形式中重复名称,但通常不会这样做,而是使用匿名函数。

于 2014-04-17T06:02:24.993 回答
3

http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

这是我读过的关于托管的最好的文章。

声明、名称和吊装

在 JavaScript 中,名称以四种基本方式之一进入范围:

语言定义:默认情况下,所有作用域都被赋予名称 this 和 arguments。形参:函数可以具有命名形参,其作用域为该函数的主体。函数声明:这些是函数 foo() {} 的形式。变量声明:它们采用 var foo; 的形式。函数声明和变量声明总是被 JavaScript 解释器以不可见的方式移动(“提升”)到其包含范围的顶部。显然,函数参数和语言定义的名称已经存在。这意味着这样的代码:

function foo() {
    bar();
    var x = 1;
}

实际上是这样解释的:

function foo() {
    var x;
    bar();
    x = 1;
}

事实证明,包含声明的行是否会被执行并不重要。以下两个函数是等价的:

function foo() {
    if (false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x, y;
    if (false) {
        x = 1;
    }
    return;
    y = 1;
}

请注意,声明的赋值部分没有被提升。只有名字被吊起。这不是函数声明的情况,整个函数体也将被提升。但请记住,声明函数有两种常规方式。考虑以下 JavaScript:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // function expression assigned to local variable 'foo'
        alert("this won't run!");
    }
    function bar() { // function declaration, given the name 'bar'
        alert("this will run!");
    }
}
test();

在这种情况下,只有函数声明的主体被提升到顶部。名称 'foo' 被提升,但主体被留下,在执行期间被分配。

这涵盖了提升的基础知识,这并不像看起来那么复杂或令人困惑。当然,这是 JavaScript,在某些特殊情况下会稍微复杂一些。

于 2014-04-17T05:51:31.657 回答
3

您可能已经了解了声明函数以避免提升的方法,但如果没有:您可以将其写为:

var is_android = true;
if (is_android) {
    var foo = function() {
        alert(5);
    };
} else {
    var foo = function() {
        alert(7);
    };
}
foo();

并且 foo() 在 javascript 解释器评估条件语句之前不会被评估。

于 2014-04-17T05:59:09.727 回答
2

你在混合概念。

从这段代码开始:

function foo() {
    alert('1');
}
function foo() {
    alert('2');
}
foo();

这不会出错。取而代之的是警报 2。因为 foo() 的第二个定义覆盖了第一个。

现在试试下面的代码:

if (false) {
    function foo() { alert('false');}
}
foo();

这只会警告错误。即使 foo 是在未执行的块内定义的(如果为 false),函数声明始终由 JavaScript 处理。

考虑到这两个示例,很容易理解代码中发生的情况:您定义了两次函数 foo,第二个定义覆盖了第一个。

您在代码中尝试的是非常接近“条件编译”的东西,并且可以在 JavaScript 中将函数声明为变量来模拟这种行为:

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

这段代码只是提醒真实。请注意,现在 foo 被定义为一个变量(包含一个函数,但是一个变量)。

问候

于 2014-04-17T06:00:19.330 回答