【问题标题】:Need to understand Javascript function hoisting example需要了解Javascript函数吊装示例
【发布时间】:2014-04-17 05:49: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”。

我想知道为什么即使 is_android 的值为 true 也会从 else 块调用 foo()

任何帮助将不胜感激。

【问题讨论】:

  • 我也坚持 JavaScript 的相同行为
  • 忘记“提升”。请记住,在执行任何代码之前,都会处理所有函数和变量声明。哦,菲利克斯说过……
  • 您的代码在某些浏览器中实际上是一个错误,特别是因为它几乎永远不会产生您真正想要的行为。
  • @NiettheDarkAbsol :这是工作示例,我在许多引用此示例的文档中都看到过。

标签: javascript function hoisting


【解决方案1】:

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


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

一般来说是这样的:

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

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

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

is_android = true;
if (is_android) {

} else {

}
foo();

其他引擎会interpret it differently,但仍然不正确(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() { ... })),那么函数的名称将无法在包含范围内访问,并且功能会丢失。

【讨论】:

  • 我也有同样的印象,但this code 对变量的工作方式不同。
  • 我想知道订单是否总是像这里一样保持不变。
  • @thefourtheye:代码在哪个浏览器中矛盾?你得到什么输出?
  • @FelixKling 我使用了 Node.js,链接在代码底部有程序的输出。你能解释一下吗?
  • @thefourtheye:变量声明本身也被提升了,只是赋值发生在代码中。总的来说,它比这更复杂,我更新了我的答案。
【解决方案2】:

在 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 使用该语言的规则的原因)。

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

如果您希望 sn-p 按预期工作,则只需更改代码

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

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

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

【讨论】:

    【解决方案3】:

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

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

    声明、名称和提升

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

    语言定义:默认情况下,所有作用域都被赋予名称 this 和参数。 形式参数:函数可以具有命名形式参数,其范围为该函数的主体。 函数声明:这些是函数 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,在某些特殊情况下会稍微复杂一些。

    【讨论】:

    • StackOverflow 不只是一组指向即将成为 404 的外部页面的链接。
    • @ProllyGeek :非常感谢。你拯救了我的一天。 :-)
    • @sid 很高兴它对您有所帮助,但不是正确的答案。
    【解决方案4】:

    您可能已经了解了声明函数以避免提升的方式,但万一不是: 你可以写成:

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

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

    【讨论】:

    • 你能解释一下这背后的逻辑吗?
    • @Sid 有人可能会说(这是对语言的滥用)这种函数声明(问题中的那个)是无范围的:它们在执行前被解析,即使else 在执行期间实际上没有到达。当您像 LexJacobs 那样编写它时,情况并非如此。
    【解决方案5】:

    你在混合概念。

    从这段代码开始:

    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 被定义为一个变量(包含一个函数,但是一个变量)。

    问候

    【讨论】:

    • “这只会警告错误”——在大多数情况下是正确的,但是 ECMA-262 ed 3 中有一个子句被某些浏览器利用,允许将上述内容视为FunctionStatement并有条件地创建函数foo。在这些浏览器中,调用 foo 将引发错误。
    猜你喜欢
    • 2013-08-09
    • 1970-01-01
    • 1970-01-01
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2012-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多