JavaScript中自执行函数的目的是什么?

515

在JavaScript中,什么时候需要使用this:

(function(){
    //Bunch of code...
})();

在这个问题上:

//Bunch of code...

4
请参考这个技术性的 解释,还有这里。 关于语法,请看为什么需要括号它们应该放在哪里 - Bergi
3
@Johnny,这两个括号之前的部分声明了一个(匿名)函数。这两个括号调用了该函数。 - Ej.
10
"IIFE" 是 "立即调用函数表达式" 的更好名称,详情请参见 此链接 - Flimm
这是一篇关于该主题的有用文章。感谢Parth找到它。 - faintsignal
显示剩余3条评论
21个回答

491

这一切都与变量作用域有关。在自执行函数中声明的变量默认只能在自执行函数内部的代码中使用。这使得可以编写代码,而不必担心变量在JavaScript代码的其他块中如何命名。

例如,正如Alexander在评论中提到的那样:

(function() {
  var foo = 3;
  console.log(foo);
})();

console.log(foo);

这将首先记录3,然后在下一个console.log中抛出错误,因为foo未定义。


8
为了许多人的利益,包括许多 Netflix 工程师:它只是一个函数。 它本身并不能代表闭包。有时自动调用器与与闭包相关的场景一起使用以完成一些很棒的东西,但如果您没有看到任何保留对将在非闭包语言中进行垃圾收集和消失的引用的东西,则与闭包完全无关。 - Erik Reppen
2
这意味着它主要与闭包一起使用吗? - Pir Abdul
2
@AlexanderBird 但这已经发生在函数内的局部变量中了: function(){ var foo = 3; alert(foo); }; alert(foo); 所以我还是不理解。 - João Pimentel Ferreira
4
如果只是为了限定作用域,为什么不直接使用{ let foo = 3 }呢? - Giulio
3
这个回答是从2009年的。块级作用域是后来才引入的。 - yunzen
显示剩余4条评论

135

简约。看起来非常普通,几乎令人感到安慰:

var userName = "Sean";

console.log(name());

function name() {
  return userName;
}

然而,如果我在页面中引入一个非常方便的Javascript库,将高级字符转换为其基本级别表示,会怎么样?

等等...什么?

我的意思是,如果有人输入带有某种重音符号的字符,但我只想在程序中使用'英文' 字符A-Z?嗯...西班牙语的'ñ'和法语的'é'字符可以被翻译成基本字符 'n' 和 'e'。

所以有人写了一个全面的字符转换器,我可以将其包含在我的站点中...我把它包含进去了。

问题来了:它里面有一个叫做'name'的函数,和我的函数同名了。

这就是所谓的冲突。我们在同一作用域中声明了两个同名函数。我们要避免这种情况。

因此,我们需要以某种方式对代码进行作用域限定。

Javascript中唯一的作用域限制方式就是将其封装在一个函数中:

function main() {
  // We are now in our own sound-proofed room and the 
  // character-converter library's name() function can exist at the 
  // same time as ours. 

  var userName = "Sean";

  console.log(name());

  function name() {
    return userName;
  }
}

那可能解决我们的问题。现在所有内容都被封装在我们的起始大括号和结束大括号之内,只能从中访问。

我们的函数中有一个函数...看起来很奇怪,但完全合法。

只有一个问题。我们的代码无法正常工作。我们的userName变量从未在控制台上输出!

我们可以通过在现有代码块后添加对我们的函数的调用来解决此问题...

function main() {
  // We are now in our own sound-proofed room and the 
  // character-converter libarary's name() function can exist at the 
  // same time as ours. 

  var userName = "Sean";

  console.log(name());

  function name() {
    return userName;
  }
}

main();

或者之前!

main();

function main() {
  // We are now in our own sound-proofed room and the 
  // character-converter libarary's name() function can exist at the 
  // same time as ours. 

  var userName = "Sean";

  console.log(name());

  function name() {
    return userName;
  }
}

一个次要的问题是:名称“main”尚未被使用的机会有多大?非常非常渺茫。

我们需要更多的作用域。以及一些自动执行我们的main()函数的方式。

现在我们来谈谈自动执行函数(或自运行、自执行等)。

((){})();

语法非常别扭,但是它可以工作。

当您将函数定义括在括号中,并包括参数列表(另一个括号集!)时,它充当函数调用

因此,让我们再次查看我们的代码,使用一些自我执行的语法:

(function main() {
  var userName = "Sean";
                
    console.log(name());
                
    function name() {
      return userName;
    }
  }
)();

所以,在大多数你看到的教程中,你现在会被“匿名自执行”或类似术语轰炸。

经过多年的专业发展,我强烈建议为了调试目的命名每个你编写的函数。

当某些问题出现时(它们一定会出现),你将在浏览器中检查回溯。当堆栈跟踪中的条目有名称时,更容易缩小代码问题的范围!


4
谢谢:) 我一直在网上搜索,试图理解IIFE相对于普通函数在变量隐私方面的优势,你的答案是最好的。每个人都说,最大的优势之一是IIFE内部的变量和函数是“最终”私有的,而普通函数只会给出完全相同的东西。最后,通过你的解释过程,我想我明白了。毕竟,IIFE只是一个函数,但现在我明白为什么要使用它了。再次感谢! - viery365
2
感谢您花时间这么好地解释。 - MSC
很好的回答。不过我有一个关于你最后一点的问题 - 当你建议所有函数都要命名时,你是说有一种方法可以在自执行函数中这样做,还是建议每个人都先给函数命名再调用它?编辑 哦,我明白了。这个已经被命名了。可能需要指出你正在为使用命名的自执行函数辩护。 - FireSBurnsmuP
2
嗯,我的朋友,这就是我一直在寻找的答案 :) - João Pimentel Ferreira
3
我喜欢两种类型的回答:(1.) 简短而直截了当。(2.) 类似故事般的解释,能够永远留在你的脑海里。你的回答属于第二种类型。 - Vibhor Dube
显示剩余2条评论

33
自我调用(也称为自动调用)是指函数在定义时立即执行。这是一种核心模式,为JavaScript开发的许多其他模式提供了基础。
我非常喜欢它,因为:
- 它将代码保持到最少 - 它强制行为与表示分离 - 它提供了一个闭包,防止命名冲突
巨大的优点:
- 它是关于定义和执行一个函数的所有内容。 - 您可以使自我执行函数返回一个值并将该函数作为参数传递给另一个函数。 - 它对封装很有用。 - 它也适用于块作用域。 - 是的,您可以将所有.js文件封装在自我执行函数中,并且可以防止全局命名空间污染。 ;)
更多here

44
  1. 怎么做?
  2. 那是来自完全不同的最佳实践。
  3. 什么功能不支持? 4、5、6、7. 相关性是什么?
  4. 嗯,我猜1/8还不错。
- Erik Reppen
3
虽然已经晚了七年,但是对于第一点来说,这并没有减少代码量,事实上,在创建函数时会增加至少两行代码。 - ICW
2
这里唯一的重点是“它提供了一个闭包,防止命名冲突”,其他所有点都是对此的重新表述或错误陈述。也许您可以简化您的回答? - pcarvalho

21

命名空间。JavaScript 的作用域是函数级别的。


10
因为我使用了名称空间而不是作用域,所以仍然会收到负评;这是一个定义问题 - 详见例如Wikipedia在计算机科学中,名称空间(有时也称为名字范围)是创建的抽象容器或环境,用于保存一组唯一标识符或符号(即名称)的逻辑分组。名称空间标识符可以为名称提供上下文(计算机科学中的作用域),有时这些术语可以互换使用。 - Christoph
5
JavaScript的函数级作用域为变量提供了它们存在的空间,即一个命名空间;它是匿名的,并不与任何命名空间标识符相关联,这点并不重要。 - Christoph
我认为会有负评是因为你的评论对于初学者来说并不真正有帮助。 - Marius

14

我无法相信没有任何答案提到了隐式全局变量。

(function(){})() 的结构并不能防止隐式全局变量,这对我来说是一个更大的问题,详见http://yuiblog.com/blog/2006/06/01/global-domination/

基本上,函数块确保你定义的所有依赖“全局变量”都被限制在你的程序中,但它不能保护你免受定义隐式全局变量的影响。你可以使用类似JSHint或其他工具来建议如何防御这种行为。

更简洁的语法var App = {}提供了类似的保护级别,在“公共”页面上时可能会包裹在函数块中。(参见Ember.jsSproutCore等实际应用了该结构的库的示例)

至于私有属性,除非你正在创建一个公共框架或库,否则它们有点被高估了。但如果你需要实现它们,Douglas Crockford有一些好的想法。


8
严格模式可以保护不被声明的全局变量所影响。此外,与自动调用器一起使用,可以提供更好的保护。我从未理解为什么私有属性会引起这么多麻烦。只要在函数构造器内部声明变量即可解决问题。如果担心忘记使用“new”关键字而睡不着觉,可以编写一个工厂函数来解决。这样问题也就解决了。 - Erik Reppen

13

我已经阅读了所有答案,这里缺少非常重要的内容,我将简述。我需要自执行匿名函数或更好地说"立即调用函数表达式(IIFE)"有两个主要原因:

  1. 更好的命名空间管理(避免命名空间污染-> JS模块)
  2. 闭包(模拟面向对象编程中的私有类成员)

第一个原因已经被很好地解释了。就第二个原因而言,请参考以下示例:

var MyClosureObject = (function (){
  var MyName = 'Michael Jackson RIP';
  return {
    getMyName: function () { return MyName;},
    setMyName: function (name) { MyName = name}
  }
}());

注意 1:我们并没有将一个函数赋值给 MyClosureObject,而且更重要的是,在最后一行调用该函数的结果。请注意最后一行的 ()

注意 2:还有一点需要了解的是,在 JavaScript 中,内部函数可以访问其所在函数的参数和变量

让我们进行一些实验:

我可以使用 getMyName 得到 MyName,它可以正常工作:

 console.log(MyClosureObject.getMyName()); 
 // Michael Jackson RIP
以下天真的方法不起作用:
console.log(MyClosureObject.MyName); 
// undefined

但我可以设置另一个名称并获得预期结果:

MyClosureObject.setMyName('George Michael RIP');
console.log(MyClosureObject.getMyName()); 
// George Michael RIP

编辑:在上面的例子中,MyClosureObject 的设计是不需要使用 new 前缀的,因此按照惯例它不应该大写。


1
你的回答让我第一次意识到可以使用 ( function(){ }( ) ) 代替问题中的语法 (function(){ })(); 它们似乎能够达到相同的结果。 - Angela P

7

有没有一个参数,而“一堆代码”返回一个函数?

var a = function(x) { return function() { document.write(x); } }(something);

闭包。变量 something 的值被分配给变量 a 所代表的函数。 something 的值可以是不同的(例如在循环中),并且每当 a 有一个新函数时,something 的值会随之改变。

+1;我更喜欢在外部函数中使用显式的 var x = something;,而不是将 x 作为参数传递,因为这样更易读... - Christoph
@Christoph:如果"something"的值在函数创建后发生更改,则函数将使用新值而不是创建时的旧值。 - stesch
@stesch:你从哪里得到的这个信息?据我所知,这并不是事实;在JS中获取真正的引用的唯一方法是使用arguments对象,但即使如此,在某些浏览器中也无法正常工作。 - Christoph
@Christoph: "JavaScript: The Good Parts", Douglas Crockford (O'Reilly) - stesch
@stesch:它不是你描述的那样工作的:如果你放弃变量x并直接依赖于词法作用域,即document.write(something),则将使用新值... - Christoph
只有在想要保留创建时的值,即使在最外层作用域中更改时,才需要使用 x(作为参数或本地变量)。 - Christoph

7

可能需要作用域隔离。这样,函数声明内部的变量不会污染外部命名空间。

当然,在现有的一半JS实现中,它们仍然会发生。


4
这些实现将是哪些? - Matthew Crumley
1
任何未使用严格模式编写的实现,并包含导致其成为全局变量的隐式 var 声明。 - Erik Reppen

6

下面是一个实际例子,展示了自执行匿名函数的实用性。

for( var i = 0; i < 10; i++ ) {
  setTimeout(function(){
    console.log(i)
  })
}

输出:10, 10, 10, 10, 10...
for( var i = 0; i < 10; i++ ) {
  (function(num){
    setTimeout(function(){
      console.log(num)
    })
  })(i)
}

输出:0,1,2,3,4...

你能否再解释一下第一组代码正在发生什么? - radio_head
2
使用 let 替代 var,第一个情况就没问题了。 - Vitaly Zdanevich

3

一个不同之处在于你在函数中声明的变量是局部的,所以当你退出函数时它们会消失,并且它们不会与其他代码中的变量发生冲突。


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