立即执行函数表达式(IIFE)

也许你没有注意到,我是一个对于专业术语有一点强迫症的人。所以,当我多次听到流行却易产生误解的术语「自执行匿名函数」,我最终决定将我的想法写进这篇文章里。

更进一步地说,除了提供关于该模式究竟是如何工作的全面信息,事实上我还建议了我们应该怎样称呼这种模式。另外,如果你想跳过这里,你可以直接跳到立即调用函数表达式进行阅读,但是我建议你读完整篇文章。

请理解这篇文章不是想说「我对了,你错了」。我发自真心地想帮助人们理解看似复杂的概念,并且我认为使用前后一致的精确术语是有助于人们理解的最简单的方式之一。

 

它是什么

在JavaScript里,每个函数,当被调用时,都会创建一个新的执行上下文。因为在函数里定义的变量和函数只能在函数内部被访问,外部无法获取;当调用函数时,函数提供的上下文就提供了一个非常简单的方法创建私有变量。

在许多情况下,你可能并不需要makeWhatever这样的函数返回多次累加值,并且可以只调用一次得到一个单一的值,在其他一些情况里,你甚至不需要明确的知道返回值。

 

它的核心

现在,无论你定义一个函数像这样function foo(){}或者var foo = function(){},调用时,你都需要在后面加上一对圆括号,像这样foo()

正如你所看到的,这里捕获了一个错误。当圆括号为了调用函数出现在函数后面时,无论在全局环境或者局部环境里遇到了这样的function关键字,默认的,它会将它当作是一个函数声明,而不是函数表达式,如果你不明确的告诉圆括号它是一个表达式,它会将其当作没有名字的函数声明并且抛出一个错误,因为函数声明需要一个名字。
问题1:这里我么可以思考一个问题,我们是不是也可以像这样直接调用函数var foo = function(){console.log(1)}(),答案是可以的。
问题2:同样的,我们还可以思考一个问题,像这样的函数声明在后面加上圆括号被直接调用,又会出现什么情况呢?请看下面的解答。

 

题外话:函数、圆括号和错误

有趣的是,如果你为一个函数指定一个名字并在它后面放一对圆括号,同样的也会抛出错误,但这次是因为另外一个原因。当圆括号放在一个函数表达式后面指明了这是一个被调用的函数,而圆括号放在一个声明后面便意味着完全的和前面的函数声明分开了,此时圆括号只是一个简单的代表一个括号(用来控制运算优先的括号)。

关于这个细节,你可以阅读Dmitry A. Soshnikov的文章:ECMA-262-3 in detail. Chapter 5. Functions 中文版本

立即执行函数表达式(IIFE)

幸运的是,修正语法错误很简单。最流行的也最被接受的方法是将函数声明包裹在圆括号里来告诉语法分析器去表达一个函数表达式,因为在JavaScript里,圆括号不能包含声明。因为这点,当圆括号为了包裹函数碰上了 function关键词,它便知道将它作为一个函数表达式去解析而不是函数声明。注意理解这里的圆括号和上面的圆括号遇到函数时的表现是不一样的,也就是说。

  • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。

  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。

 

关于括号的重要笔记

在一些情况下,当额外的带着歧义的括号围绕在函数表达式周围是没有必要的(因为这时候的括号已经将其作为一个表达式去表达),但当括号用于调用函数表达式时,这仍然是一个好主意。

这样的括号指明函数表达式将会被立即调用,并且变量将会储存函数的结果,而不是函数本身。当这是一个非常长的函数表达式时,这可以节约其他人阅读你代码的时间,不用滚到页面底部去看这个函数是否被调用。

作为规则,当你书写清楚明晰的代码时,有必要阻止JavaScript抛出错误的,同样也有必要阻止其他开发者对你抛出错误WTFError!

 

保存闭包的状态

就像当函数通过他们的名字被调用时,参数会被传递,而当函数表达式被立即调用时,参数也会被传递。一个立即调用的函数表达式可以用来锁定值并且有效的保存此时的状态,因为任何定义在一个函数内的函数都可以使用外面函数传递进来的参数和变量(这种关系被叫做闭包)。

关于闭包的更多信息,参见 Closures explained with JavaScript

记住,在这最后两个例子里,lockedInIndex可以没有任何问题的访问i,但是作为函数的参数使用一个不同的命名标识符可以使概念更加容易的被解释。

立即执行函数一个最显著的优势是就算它没有命名或者说是匿名,函数表达式也可以在没有使用标识符的情况下被立即调用,一个闭包也可以在没有当前变量污染的情况下被使用。

 

「自执行匿名函数(Self-executing anonymous function)」有什么问题呢?

你看到它已经被提到好几次了,但它仍未被清楚地解释,我提议将术语改成"Immediately-Invoked Function Expression",或者,IIFE,如果你喜欢缩写的话(发音类似“iffy”)。

什么是Immediately-Invoked Function Expression呢?顾名思义,它就是一个被立即调用的函数表达式。

我想JavaScript社区的成员应该可以在他们的文章里或者陈述里接受术语Immediately-Invoked Function ExpressionIIFE,因为我感觉这样更容易让这个概念被理解,并且术语"self-executing anonymous function"真的也不够精确。

希望上面的例子可以让你更加清楚的知道术语'self-executing'是有一些误导的,因为他并不是执行自己的函数,尽管函数已经被执行。同样的,匿名函数也没用必要特别指出,因为,Immediately Invoked Function Expression,既可以是命名函数也可以匿名函数。

有趣的是:因为arguments.callee在ECMAScript 5 strict mode中被deprecated了,所以在ES5的strict mode中实际上不可能创建一个self-executing anonymous function

最后:模块模式

当我调用函数表达式时,如果我不至少一次的提醒我自己关于模块模式,我便很可能会忽略它。如果你并不熟悉JavaScript里的模块模式,它和我第一个例子很像,但是返回值用对象代替了函数。

模块模式方法不仅相当的厉害而且简单。非常少的代码,你可以有效的利用与方法和属性相关的命名,在一个对象里,组织全部的模块代码即最小化了全局变量的污染也创造了私人变量。

 

延伸阅读

希望这篇文章可以为你答疑解惑。当然,如果你产生了更多疑惑,你可以阅读下面这些关于函数和模块模式的文章。

来自:https://segmentfault.com/a/1190000007569312

原文:Immediately-Invoked Function Expression (IIFE) by Ben Alman
原译:立即执行函数 by Murphywuwu
改增内容: by blanu

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注