这两种 JavaScript 自执行函数的区别

4

我总是使用以下自执行函数来避免在JavaScript中将我的代码暴露给全局范围:

(function() { 
    //Code comes here
})();

我相信这也被称为自执行匿名函数。有时,我也看到下面的代码用于同样的目的:

(function(d){
    //Code comes here
})(document.documentElement);

我不确定是什么造成了差异,所以我提出这个问题。

在JavaScript中,这两种自执行函数有什么区别(或者说有哪些区别)?


10
第一个函数不接受任何参数,而第二个函数则需要参数。 - Rob W
好的,我明白了,但是 document.documentElement 也可以在函数内部使用。为什么要这样使用呢? - tugberk
1
JavaScript中的函数从未“自我执行”,无论它们是否匿名。如果它们执行,那么总会有一个对该函数的调用。您的调用位于分号之前。 - Greg Hewgill
@GregHewgill 嗯,所以我用错了这个术语?我经常听到这个词,但在这种情况下它是比喻性的。同时,这段代码和我上面引用的第一个代码一样吗:(function(w){})(window); - tugberk
@GregHewgill 在技术上是正确的,但这个习惯用法通常被称为自执行匿名函数 :) 关于 window,请看我的答案。 - Anentropic
2
这个术语是错误的,但现在大多数JS程序员都理解它指的是上面的示例。有很多人提倡使用“立即调用函数表达式”(IIFE,发音为“iffy”)这个术语,我认为这更有意义。 - Kevin Ennis
6个回答

8
下面的代码演示了发生了什么。实际上,foobar变量不存在,函数是匿名的。
var foo = function() {}
foo();

var bar = function(d){}
bar(document.documentElement);
(function(d){})(d) 方法被称为闭包。它用于传递可变的变量值,例如在循环中。以下是一个实际的例子:
for(var i=0; i<10; i++) {
    document.links[i].onclick = function(){
        alert(i); //Will always alert 9
    }
}

实现闭包后:

for(var i=0; i<10; i++) {
    (function(i){
        document.links[i].onclick = function(){
            alert(i); //Will alert 0, 1, ... 9
        }
    })(i);
}

谢谢。你的例子非常有趣和有用。我仍在努力弄清楚为什么第二个示例总是会警报9 :) - tugberk
@tugberk 因为循环从零到九遍历,每次枚举都会更新变量 i。当用户点击链接时,将显示变量 i 的值 - 这个值是根据最后一次更新而得到的 9 - Rob W
最后一个问题。当它们在DOM内部使用时,这两个是否相同:(function(){})();(function(w){})(window); - tugberk
  1. 为什么在这里使用函数表达式而不是函数声明?
  2. 你所称之为“闭包”的东西并不是闭包。
- Matt Ball
只有在第一个函数中定义了变量w才会执行。 - Rob W
显示剩余3条评论

3

请记住,函数参数和变量本质上是相同的东西。

第二个例子基本上只是缩写。

(function(){
    var d = document.documentElement;
}());

由于它避免了使用var=,因此这种模式非常实用。

这种模式有一些常见的用途:

  1. Creating lexically scoped variables (just remembered this after seeing Rob's answer...)

    //this does not work because JS only has function scope.
    // The i is shared so all the onclicks log N instead of the correct values
    for(var i = 0; i< N; i++){
       elems[i].onclick = function(){ console.log(i); }
    }
    
    //Each iteration now gets its own i variable in its own function
    // so things work fine.
    for(var i=0; i<N; i++){
       (function(i){
           elems[i].onclick = function{ console.log(i); };
       }(i));
    }
    

    In this case, passing the parameters directly allows us to reuse the same variable name inside, in a way that var i = i would not be able to. Also, the conciseness is a benefit, since this is just a boilerplate pattern that we don't want to dominate over the important code around it.

  2. It makes it easy to convert some old code without having to think too much about it

    (function($){
       //lots of code that expected $ to be a global...
    }(jQuery)) //and now we can seamlessly do $=jQuery instead.
    
  3. Parameters that are not passed are set to undefined. This is useful since normally undefined is just a global variable that can be set to different values (this is specially important if you are writing a library that needs to work w/ arbitrary third party scripts)

    (function(undefined){
       //...
    }())
    

0
第一个函数不需要参数。第二个函数需要一个参数。在函数内部,该参数被称为d。当调用该函数时,d被设置为document.documentElement

0

看起来第二段代码的作者想在函数内使用d作为document.documentElement的缩写。


0
使用带有参数的第二种形式的原因之一是,它可以使您的代码与页面后面加载的其他js代码(例如其他库或框架代码)隔离开来,这些代码可能会重新定义传递给参数的变量。
一个常见的例子是,如果您自执行的匿名函数中的代码依赖于jQuery并想要使用$变量。
其他js框架也定义了$变量。如果您将函数编写为:
(function($){

    //Code comes here

})(jQuery);

然后,即使您加载了定义$的其他库,也可以安全地使用$来代表jQuery。

我曾经看到人们在他们的代码块中传递所有需要的“全局”变量,例如window、document、jQuery/$等,以此来进行防御性编程。

小心谨慎总比后悔好,特别是如果您使用了大量第三方小部件和插件。

更新:
正如其他人指出的那样,函数周围的括号是一个闭包。虽然在使用这种模式的许多情况下它们并不是必需的(@Rob W给出了一个必须使用它们的很好的例子),但是假设您有一个非常长的函数体...外部括号告诉其他人阅读代码该函数可能是自执行的。

我看到的关于这种模式的最好解释在Paul Irish的视频中,链接在这里:http://paulirish.com/2010/10-things-i-learned-from-the-jquery-source/,从1:30开始。

这个SO问题也有一些有用的答案:如何解释JavaScript中的这个结构?


0
如果你想要向自执行匿名函数传递参数,可以使用第二种方式。当你想在函数中使用与全局作用域中其他变量同名的变量时,这种方式会非常有用:
var a = "I'm outside the function scope",
    b = 13;
alert(a);
alert(b);
(function(a,b){
     // a is different from the initial a
     alert(a);
     // same goes for b
     alert(b);
})("I'm inside the function scope",Math.PI);

使用类似以下的东西也可能很有用:

var a;
console.log(a === undefined); // true
undefined = true;
console.log(a === undefined); // false
(function(undefined){
    console.log(a === undefined); // true, no matter what value the variable "undefined" has assigned in the global scope
})();

当尝试实现空对象设计模式

如果你关心效率,你可能会最终使用类似这样的东西:

var a = 2;
(function(window){
    alert(a);
    // accessing a from the global scope gets translated 
    // to something like window.a, which is faster in this particular case
    // because we have the window object in the function scope
})(window);

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