【问题标题】:Ambiguous function declaration in JavascriptJavascript中的模棱两可的函数声明
【发布时间】:2010-01-15 15:30:47
【问题描述】:

我是 Javascript 新手,对函数声明的工作方式感到困惑。我对此进行了一些测试,得到了一些有趣的结果:

say();

function say()
{
    alert("say");
}

前向声明有效并弹出“说”

对面

say();

say = function()
{
    alert("say");
}

没用,虽然它也声明了一个函数对象

如果我们声明函数并在之后重新声明:

function say()
{
    alert("speak");
}

say();

function say()
{
    alert("say");
}

我得到的是“说”而不是“说”。太惊喜了!

好的。似乎只有最新的函数声明有效。然后让我们先声明函数对象,然后是“常规”函数:

say = function()
{
    alert("speak");
}

say();

function say()
{
    alert("say");
}

say();

另一个惊喜,它是“说话”,然后是“说话”。 “常规”函数声明根本不起作用!

对所有这些都有解释吗?而且,如果“常规”函数声明真的那么“脆弱”并且可以很容易地被同名的函数对象覆盖,我应该远离它吗?

另一个问题是:只有函数对象格式,这种前向声明是不可能的吗?有没有办法在 Javascript 中“模拟”它?

【问题讨论】:

    标签: javascript function declaration forward-declaration


    【解决方案1】:

    Javascript 是这样工作的:

    在执行实际语句之前,文档被解析,function 声明都被立即考虑在内。这解释了您的第一个示例。

    如果您将函数分配给局部变量,那是在执行期间完成的,因此您不能在第二个示例中使用该方法。

    你的经验是,如果你两次声明一个函数,最后一个将被整个应用程序使用。这是你的第三个例子。

    这些函数是window 对象的成员,它们实际上是全局声明的。如果将局部变量分配给函数的值,则该局部变量优先于 window 对象中的成员。如果 javascript 找不到局部变量,它会在范围内搜索以找到它,window 对象是最后的手段。这就是您上一个示例中发生的情况,它有一个变量 say,其范围比全局函数 say 更具体。

    如果您在运行时重新声明 say,即交换上一个示例中的声明顺序,那么您会看到两个不同的警报:

    say(); //speak, the global function
    
    function say() {
      alert('speak');
    }
    
    var say = function() {
      alert('say');
    }
    
    say(); //say, the declared local variable
    

    【讨论】:

    • 我明白你的观点。但是,如果我定义本地函数对象,范围是什么?我尝试调用 window.say() ,结果仍然不是我所期望的: function say() { alert("say"); } window.say(); 会弹出 "say" 和 var say = function() { alert("speak"); } window.say();功能说(){警报(“说”); } window.say(); 会弹出“speak”两次。这是否意味着“全局”函数和“本地”对象都是窗口对象的成员?
    • @Xiang:你得到“speak”两次的原因是 JavaScript 解释器做了两次传递:首先,它创建变量对象的属性(用于在当前作用域并且是全局代码中的全局对象)用于var 语句和函数声明,并且在第二遍中它按顺序执行语句。关键是 var 语句被有效地分成两部分:在第一遍中,如果变量对象中不包含具有该名称的属性,则将具有值 undefined 的属性添加到变量对象中(继续... )
    • (续) ... 如果变量声明中指定了变量的初始值,则在第二遍之前不会将其分配给变量对象。但是,任何函数声明都会在第一次传递中创建变量对象的属性,并将该函数作为值。所以在你的例子中,事件的顺序是: 1)var say = function() { alert("speak"); } 创建一个值为undefined 的变量对象的属性say; 2) function say() { alert("say"); } 用该函数覆盖变量对象的say 属性; (续...)
    • 我真的建议您阅读有关此主题的 ECMAScript 规范,以全面了解正在发生的事情。
    • 最后一件事:这个答案并不完全正确。该行为与任一声明处于“更具体的范围”无关:所有声明的范围都相同。
    【解决方案2】:
    say();
    
    function say()
    {
        alert("say");
    }
    

    这里解释器在调用时获取say()的定义,并执行它。

    say();
    
    say = function()
    {
        alert("say");
    }
    

    这里没有要获取的say() 的定义——而是将匿名函数分配给变量。解释器无法“找到”它,就像它可以找到前向声明一样。

    function say()
    {
        alert("speak");
    }
    
    say();
    
    function say()
    {
        alert("say");
    }
    

    这里 say 被定义然后重新定义 - 最后一个定义胜出。

    say = function()
    {
        alert("speak");
    }
    
    say();
    
    function say()
    {
        alert("say");
    }
    
    say();
    

    这里say 是一个指向匿名函数的变量(在解释第一条语句之后)。这优先于任何函数定义,就像您将函数定义放在赋值之前一样。

    如果你有

    say();
    
    say = function()
    {
        alert("speak");
    }
    
    say();
    
    function say()
    {
        alert("say");
    }
    

    然后你会得到“说”,然后是“说”。

    【讨论】:

      【解决方案3】:

      您可以期望按顺序应用您的函数定义。然后你所有的非方法代码行都会按顺序执行,包括函数对象的赋值。这解释了您的每个示例。不过,这是一个有趣的问题。你真的不能在试图调用它之后分配一个函数对象,并期望它工作。但是,实际上将首先应用可执行代码之后的函数定义。

      【讨论】:

        【解决方案4】:

        以后调用函数总是好的,即使 javascript 就是这样工作的。

        大多数语言不会那样工作,而是这样做。

        function say(){
            alert("say");
        }
        
        say();
        

        say = function(){
            alert("say");
        }
        
        say();
        

        (function(){
            alert("say");
        })();
        

        【讨论】:

          猜你喜欢
          • 2011-04-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多