用javascript实现自我执行功能的目的是什么?


427

在javascript中,什么时候要使用它:

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

在此:

//Bunch of code...

3
也有看一个(技术的说明,并在这里。有关语法,请参见为什么需要括号以及括号应放在何处
Bergi 2014年


为什么在分号之前有最后两个括号?
约翰尼

3
@johnny在最后两个括号之前的部分声明一个(匿名)函数。这两个括号称为函数。
Ej。

7
为此,“立即调用函数表达式”或IIFE是一个更好的名称
Flimm

Answers:


404

这都是关于变量作用域的。默认情况下,自执行函数中声明的变量仅可用于自执行函数中的代码。这样就可以编写代码,而不必担心在其他JavaScript代码块中如何命名变量。

例如,如亚历山大的评论中所述:

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

console.log(foo);

这将首先记录日志3,然后在下一次引发错误,console.log因为foo未定义。


7
同时也为包括许多Netflix工程师在内的许多人的利益服务:这只是功能。它本身并不表示关闭。有时将自动调用程序与与关闭相关的方案结合使用,以完成整洁的工作,但是如果您看不到某个引用会保留为垃圾收集并以非关闭语言运行,那么它就无济于事了。与CLOSURES扯在一起。
埃里克·雷彭

2
所以这意味着,它主要与闭包一起使用?
皮尔·阿卜杜勒

@AlexanderBird,不太对劲......如果你不这样做var,是这样的:...function(){ foo=3;}?它将设置全局变量,对吗?
T.Todua '16

2
@AlexanderBird但已经发生在内部函数的局部变量:function(){ var foo = 3; alert(foo); }; alert(foo);所以我还是不明白这一点
若奥·皮门特尔·费雷拉

嗯,我明白了,您一次实现了所有这三个功能:1)仅通过声明就可以执行该函数;2)作为任何函数,变量作用域仅是局部的;3)该函数可以是匿名的,没有污染的主要适用范围
若昂·皮门特尔·费雷拉

94

简单化。看起来很正常,几乎令人安慰:

var userName = "Sean";

console.log(name());

function name() {
  return userName;
}

但是,如果我在页面中包含一个非常方便的javascript库,该库会将高级字符转换为它们的基本级表示形式,该怎么办?

等等...什么

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

因此,一个好人编写了一个全面的字符转换器,我可以将其包含在我的网站中……我将其包含在内。

一个问题:它中有一个名为“ name”的函数,与我的函数相同。

这就是所谓的碰撞。我们在同一个作用域中声明了两个具有相同名称的函数。我们要避免这种情况。

因此,我们需要以某种方式确定代码范围。

在javascript中作用域代码的唯一方法是将其包装在函数中:

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;
  }
}

那可能解决我们的问题。现在,所有内容都被封闭,并且只能从我们的打开和关闭括号内访问。

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

只有一个问题。我们的代码无效。我们的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;
    }
  }
)();

因此,在您阅读的大多数教程中,您现在都会被术语“匿名自我执行”或类似的东西所吸引。

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

当出现问题(并且会发生问题)时,您将在浏览器中检查回溯。它总是更容易缩小你的代码的问题时,在堆栈跟踪中的条目有名字!

long之以鼻,希望对您有所帮助!


2
谢谢:)我一直在搜索互联网,试图了解IIFE在可变隐私方面相对于正常功能的优势,您的答案简直是最好的。每个人都说,最大的优势之一是,当普通函数给您完全相同的东西时,IIFE中的变量和函数是“最终”私有的。最后,我认为我是通过您的解释过程来解决的。毕竟IIFE只是一个功能,但现在我明白了为什么要使用它了,再次感谢!
viery365 '16

感谢您抽出宝贵的时间来很好地解释这一点。
MSC

好答案。不过,我对您的最后一点有疑问-当您建议命名所有函数时,您是说有一种方法可以对自执行函数执行此操作,还是建议大家先命名该函数然后再调用它?编辑哦,我知道了。这个已经被命名了。咄。可能要指出的是,您证明使用命名的自执行功能是合理的。
FireSBurnsmuP

那么我的朋友,这是我一直在寻找的答案:)
若昂·皮门特尔·费雷拉

这是我一直在寻找的答案,而不是标记为已接受的答案。我说的不是变量名冲突,而是函数名冲突吗?甚至普通的非iife函数中的变量都是局部作用域的,不会与全局变量发生冲突,不是吗?
Kumar Manish

32

自调用(也称为自动调用)是指函数在定义后立即执行。这是一种核心模式,并且是许多其他JavaScript开发模式的基础。

我非常喜欢:),因为:

  • 它将代码保持在最低限度
  • 它强制行为与表现分离
  • 它提供了一个闭包来防止命名冲突

极大地-(为什么要说它的好?)

  • 这是关于一次定义和执行一个函数。
  • 您可以让该自执行函数返回一个值,然后将该函数作为参数传递给另一个函数。
  • 对封装很有用。
  • 这对于块作用域也很好。
  • 是的,您可以将所有.js文件封装在一个自执行函数中,并可以防止全局命名空间污染。;)

这里更多。


43
点1.如何?第2点。这是完全不同的最佳实践。要点3.什么功能没有?4,5,6,7。关联?8.好吧,我猜是1/8不错。
埃里克·雷彭

2
迟了七年,但是,对于第1点,它根本不会减少代码,实际上,在创建函数时,它至少增加了两行代码。
YungGun

1
这里唯一的要点是“它提供了一个防止命名冲突的闭包”,每隔一点就是改写这个或false。也许您可以简化您的答案?
pcarvalho

19

命名空间。JavaScript的作用域是功能级别的。


7
因为我使用了命名空间来进行范围界定,所以仍然需要投票表决; 这是一个定义问题-例如,参见Wikipedia计算机科学中的名称空间(有时也称为名称范围)是创建的用于容纳唯一标识符或符号(即名称)逻辑分组的抽象容器或环境。命名空间标识符可以提供上下文(在计算机科学范围)到一个名称,该术语有时可互换使用。
克里斯多夫(Christoph)

5
Javascript的函数级作用域提供了变量名称所驻留的空间,即名称空间;它是与名称空间标识符不相关的匿名对象是不相关的...
Christoph

12

我不敢相信所有答案都暗含了全局变量。

(function(){})()构造无法防止隐含的全局变量,对我而言,这是一个更大的问题,请参见http://yuiblog.com/blog/2006/06/01/global-domination/

基本上,功能块确保您定义的所有相关“全局变量”都局限于程序中,它不能保护您免受隐式全局变量的定义。JSHint等可以提供有关如何防御此行为的建议。

更为简洁的var App = {}语法提供了类似的保护级别,并且在“公共”页面上时可以包装在功能块中。(有关使用此构造的库的真实示例,请参见Ember.jsSproutCore

private属性而言,除非您要创建公共框架或库,否则它们会被高估,但是如果您需要实现它们,Douglas Crockford有一些好主意。


8
严格模式可以防止隐含的全局变量。结合自动调用程序,您将得到覆盖。我从来不了解私人财产的喧嚣。在func构造函数中声明vars。做完了 如果忘记使用“ new”关键字使您彻夜难眠,请编写工厂函数。再做一次。
埃里克·雷彭

8

我已经阅读了所有答案,在此遗漏了一些非常重要的内容,我会接吻。有两个主要原因,为什么我需要自执行匿名函数,或者最好说“ 立即调用函数表达式(IIFE) ”:

  1. 更好的名称空间管理(避免名称空间污染-> JS模块)
  2. 封闭(模拟私有类成员,从OOP已知)

第一个已经很好地解释了。对于第二个,请研究以下示例:

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

注意1:我们没有分配功能MyClosureObject,更何况是调用该功能的结果。请注意()最后一行。

注意2:您还需要进一步了解Java语言中的函数,即内部函数可以访问内部定义的函数的参数和变量

让我们尝试一些实验:

我可以MyName使用了getMyName,它可以工作:

 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前缀使用,因此按照惯例,不应将其大写。


7

是否有参数,“代码段”返回一个函数?

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

关闭。的值something由分配给的函数使用asomething可能具有一些变化的值(for循环),并且每次具有新功能时。


+1; 我更喜欢var x = something;在外部函数中使用显式而不是x参数:imo这样更易读……
Christoph

@Christoph:如果函数创建后“ something”的值发生了变化,则它将使用新值,而不是创建时的值。
stesch

@stesch:你从哪里得到的?据我所知,事实并非如此。在JS中获得真实引用的唯一方法是使用arguments-object,但即使在所有浏览器中也无法使用
Christoph

@Christoph:“ JavaScript:好的部分”,道格拉斯·
克罗克福德

@stesch:它不工作,你怎么描述它的方式:如果你删除变量的新值会被使用x并直接取决于词汇范围,即document.write(something)...
克里斯托夫

6

范围隔离,也许。这样,函数声明中的变量不会污染外部名称空间。

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


4
这些将是什么实现?
马修·克鲁姆利

1
任何不是以严格模式编写的实现,都包含导致其成为全局的隐式var声明。
埃里克·雷彭

5

这是一个自我调用匿名函数如何有用的可靠示例。

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

用第一种情况let代替var就可以了。
Vitaly Zdanevich '18

3

一个区别是,您在函数中声明的变量是局部变量,因此在退出函数时它们将消失,并且不会与其他代码中的其他变量发生冲突。


1

由于Javascript中的函数是一流的对象,因此通过这样定义它,就可以有效地定义一个类似于C ++或C#的“类”。

该函数可以定义局部变量,并在其中包含函数。内部函数(有效的实例方法)将可以访问局部变量(有效的实例变量),但它们将与脚本的其余部分隔离。


1

javascript中的自调用函数:

自调用表达式将自动调用(启动),而不被调用。自调用表达式创建后立即被调用。基本上,这用于避免命名冲突以及实现封装。在此函数之外无法访问变量或声明的对象。为了避免最小化问题(filename.min),请始终使用自执行函数。


1

自执行功能用于管理变量的范围。

变量的范围是程序在其中定义的区域。

全局变量具有全局范围;它在JavaScript代码中的任何地方定义,并且可以从脚本中的任何位置(甚至在函数中)进行访问。另一方面,在函数内声明的变量仅在函数体内定义。它们是局部变量,具有局部作用域,只能在该函数中访问。函数参数也算作局部变量,并且仅在函数体内定义。

如下所示,您可以访问函数内部的globalvariable变量,还请注意,在函数主体内,局部变量优先于具有相同名称的全局变量。

var globalvar = "globalvar"; // this var can be accessed anywhere within the script

function scope() {
    alert(globalvar);
    localvar = "localvar" //can only be accessed within the function scope
}

scope(); 

因此,基本上,一个自执行函数允许编写代码而无需担心在其他JavaScript代码块中如何命名变量。


1
(function(){
    var foo = {
        name: 'bob'
    };
    console.log(foo.name); // bob
})();
console.log(foo.name); // Reference error

实际上,以上函数将被视为没有名称的函数表达式。

使用封闭的和开放的括号包​​装函数的主要目的是避免污染全局空间。

函数表达式内的变量和函数变为私有(即,它们在函数外部将不可用)。


1

简短的答案是: 防止污染全球(或更高)范围。

IIFE(立即调用函数表达式)是将脚本编写为插件,附加组件,用户脚本或任何希望与其他人的脚本一起使用的脚本的最佳实践。这样可以确保您定义的任何变量都不会对其他脚本产生不良影响。

这是编写IIFE表达式的另一种方法。我个人更喜欢以下方法:

void function() {
  console.log('boo!');
  // expected output: "boo!"
}();

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/void

从上面的示例可以很明显地看出,IIFE也会影响效率和性能,因为预期仅运行一次的函数将执行一次,然后永久地转储到void中。这意味着函数或方法声明不会保留在内存中。


很好,我以前没看过这种用法void。我喜欢。
Ej。

1

首先,您必须访问MDN IIFE,现在有关此点

  • 这是立即调用函数表达式。因此,当您的JavaScript文件从HTML调用时,此函数会立即调用。
  • 这样可以防止在IIFE惯用语中访问变量以及污染全局范围。

0

看来这个问题已经准备好了,但我还是会发表我的意见。

我知道何时需要使用自我执行功能。

var myObject = {
    childObject: new function(){
        // bunch of code
    },
    objVar1: <value>,
    objVar2: <value>
}

该函数使我可以使用一些额外的代码来定义更干净代码的childObjects属性和属性,例如设置常用变量或执行数学方程式;哦! 或错误检查。而不是限于...的嵌套对象实例化语法

object: {
    childObject: {
        childObject: {<value>, <value>, <value>}
    }, 
    objVar1: <value>,
    objVar2: <value>
}

通常,编码有很多晦涩的方式来做很多相同的事情,这使您想知道“为什么要打扰?” 但是,新的情况不断出现,您将无法再仅依靠基本/核心原则。


0

给您一个简单的问题:“在javascript中,您什么时候要使用此代码......”

我喜欢@ken_browning和@sean_holding的答案,但这是我未提及的另一个用例:

let red_tree = new Node(10);

(async function () {
    for (let i = 0; i < 1000; i++) {
        await red_tree.insert(i);
    }
})();

console.log('----->red_tree.printInOrder():', red_tree.printInOrder());

其中Node.insert是一些异步操作。

我不能在函数声明时不带async关键字就调用await,并且我不需要命名函数供以后使用,而是需要等待插入调用,或者我需要其他一些更丰富的功能(谁知道?) 。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.