自定义函数(也称为延迟函数定义),在使用函数声明时。

7
在Stoyan Stefanov的《JavaScript Patterns》一书中,有一个关于自定义函数的部分。
var scareMe = function(){
    console.log("Boo!");
    scareMe = function(){
        console.log("Double Boo!"); 
    }
}

scareMe();//==>Boo!
scareMe();//==>Double Boo!

它的表现符合我的预期。但是我修改了scareMe函数如下:

function scareMe(){
     console.log("Boo!");
     function scareMe(){
         console.log("Double Boo!");
     }
 }

 scareMe();//==>Boo!
 scareMe();//==>Boo!

问题

  1. 它们之间有什么区别?
  2. 为什么第二种情况的输出不是“Double Boo!”而是“Boo!”?

这个模式很棒。除了混淆之外,还有其他实际用途吗? - GameAlchemist
1
@GameAlchemist:是的。有时我有一个函数要做一些我只想做一次的事情;比如初始化。我可以这样定义该函数:function init() { init = function() {}; /* ... */ }; 现在只要我仅通过名称使用 init 而不实际捕获对原始函数的引用,它就只会初始化一次。这可以使用布尔变量来完成,但这样你就少了一个要跟踪的变量。 - icktoofay
有趣的。例如,rAF替换为setInterval可以变成:requestAnimationFrame = function(cb) { requestAnimationFrame=function(){}; return setInterval(cb,16); }。有点神秘,但优雅。 - GameAlchemist
记录一下,你打错了名字:正确的是Stoyan Stefanov,我的朋友Google告诉我。 - GameAlchemist
@GameAlchemist:在这本书中,作者还使用这种模式创建了单例模式。 function Singleton(){instance = this; Singleton = function(){return instance};}。然后当您创建两个实例时,它们是相同的。 var s1 = new Singleton(); var s2 = new Singleton(); console.log(s1 === s2); - jason
3个回答

11

第一个scareMe函数被调用时,它通过在其内部创建另一个函数scareMe来覆盖自己的行为,从而覆盖了上层作用域中的函数。因此,原始scareMe的定义发生了变化。我已经看到过这种方法被用于应用程序的首次设置,如果想要在设置完成后立即更改其行为。

如果你定义了:

var scareMe = function(){
    console.log("Boo!");
    var scareMe = function(){ //define it with var
        console.log("Double boo!"); 
    }
}

scareMe();//==>Boo!
scareMe();//==>Boo! //you will see the behavior as that of the second one.

还有一个一次性设置的实际应用:

var scareMe = function(){
    console.log("Boo!");

   //I have done my job now. I am no longer needed.
    scareMe = undefined;

}

scareMe();//==>Boo!
scareMe();//==> oops error

您创建了一个名为 scareMe 的新函数,它的作用域仅限于该函数内部,不会覆盖自身。

例如,尝试这个:

function scareMe(){
     console.log("Boo!");
     function scareMe(){
         console.log("Double bool!");
     }
    scareMe(); //Now this invokes the once defined inside the scope of this function itself.
 }

 scareMe();//==>Boo! and Double bool!

我认为你所谓的提升并不是真正的提升。我认为你实际上是想说他们正在修改一个闭合变量。 - icktoofay
@user2782160:你的回答实际上有些不同。我理解你的意思是说你不能覆盖一个函数,这是错误的。你当然可以;只是在第二个片段中,他们没有覆盖该函数;他们只是遮蔽了它。 - icktoofay
@icktoofay,我的意思不是实际的scareMe函数在全局范围内,而是你在第一个示例中定义了另一个没有使用var的函数,它被提升到全局范围,这意味着它只是覆盖了第一个scareMe的定义。请看一下我的答案中第一个示例的变化。 - PSL
@PSL:我理解你的意思,但我认为你的术语有误。提升是JavaScript表现出所有函数定义和变量声明都被移动到作用域开头的方式。这不是你所描述的提升;你实际上想说的是修改一个封闭在外部作用域中的变量,这与提升不同。 - icktoofay
@icktoofay 啊,是的,就是因为缺乏作用域而修改了自己的行为。可能措辞不太准确。 - PSL
我认为需要重新表述,正如icktoofay所说的那样:scareMe在脚本开始时被彻底地提升,并且具有未定义的值,然后当它被定义时改变值一次,然后在函数被执行时第二次改变值。顺便说一下,示例很好。 - GameAlchemist

4

在第一个方法中,scareMe是全局变量(在您的上下文中)。当在“double boo”时,您更改该全局变量的值,因此可以正常工作。 在第二个方法中,内部的scareMe是局部变量,它不会更改全局变量的值。 所以这是关于变量的作用域。


2

除了提升和可调试性之外,您可以考虑:

function f(/* ... */) { /* ... */ }

等价于:

var f = function(/* ... */) { /* ... */ };

如果我们将您的第二个代码示例转换为使用第二种形式,我们得到:
var scareMe = function() {
    console.log("Boo!");
    var scareMe = function() {
        console.log("Double bool!");
    };
};

请注意,这与您的第一个片段不同;内部函数定义中有一个 var。使用内部的 var,它创建了一个名为 scareMe 的新变量,覆盖了外部变量。

1
在第二个例子中,我不认为内部的scareMe会遮盖外部的那一个,如果是这样的话,输出将会是“Double boo!”,但实际上输出是“Boo!”。我认为Teddy是正确的。 - jason
@jason:Teddy是对的,但是当我说阴影时,我的意思是你有两个同名变量,最内层的一个隐藏了最外层的定义;我并不是说外部的那个被改变了,只是被隐藏了。 - icktoofay
是的,我后来明白你的意思了,抱歉 :) - jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接