即时施行函数表明式(IIFE)

class=”vcard author”>原文:immediately-invoked-function-expression

译者:nzbin

可能你还没有在意到,笔者是二个对术语相比较坚定不移的人。由此,在听到很频繁相比流行却不难产生误导的
JavaScript 术语“自推行匿名函数”之后,最后自个儿主宰把自家的想法写成一篇小说。

为了提供关于这一格局怎么着运行的不亦乐乎消息,笔者早已提议了小编们相应什么称呼它的建议,继续向下看。当然,要是您想跳过早先,你能够只看“自实施函数表明式”这一节,不过本身提出你看完整篇小说。

请通晓这篇小说并非要抒发“作者是对的,你是错的”这一看法。我确实感兴趣的是赞助人们知道一些诡秘的扑朔迷离概念,并且让大千世界发现到利用同一的和规范的术语是芸芸众生能够完成以方便清楚的最简单易行的事体之一。

那正是说,那到底是怎么回事呢?

在 JavaScript
中,每1个函数在实施时都会时有产生2个新的推行环境。由于在函数中定义的变量和函数只可以在里头访问而不能够被外表访问。这一履行环境调用的函数提供了一个相当简单的措施来成立私有效用域。

  // 因为返回的函数有权访问私有变量 `i`

  function makeCounter() {
    // `i` 只能在 `makeCounter`内被访问.
    var i = 0;

    return function() {
      console.log( ++i );
    };
  }

  // 注意 `counter` 和 `counter2` 都有私有的作用域 `i`.

  var counter = makeCounter();
  counter(); // logs: 1
  counter(); // logs: 2

  var counter2 = makeCounter();
  counter2(); // logs: 1
  counter2(); // logs: 2

  i; // ReferenceError: i 未定义 (只存在 makeCounter 内部)

诸多气象下,你并不供给 makeWhatever
函数重回七个实例,能够用一个实例来做。在此外意况下,你居然从不显明的重返值。

这件事的着力

以往,无论你用 function foo(){} `还是var foo = function(){}的方式定义函数,最终都会以一个函数的标识符结尾,你可以通过圆括号()调用函数,像foo()` 。

  // 像这样定义的函数可以在函数名后放置 () 来执行
  // 比如 foo(), 因为 foo 只是函数表达式 `function() { /* code */ }`的引用

  var foo = function(){ /* code */ }
  // ...是不是只在函数表达式之后放置 () 就能执行?

  function(){ /* code */ }(); // SyntaxError: Unexpected token (

如您所见,有三个报错。当解析器在大局范围内或在函数中遇见 function
关键字时,默许意况下,它会以为那是函数声明而不是函数表明式。即使您没有显著告诉解析器那是一个表明式,它会以为那是1个匿名的函数注明并抛出意外的语法错误,因为函数表明须求名称。

题外话:函数,括号,语法错误

有趣的是,倘诺你为1个函数钦定了名称并且在当时在其背后放置了括号,解析器也会抛出荒谬,但原因莫衷一是。固然在表达式之后放置括号表达那是一个将被实施的函数,但在申明之后放置括号会与眼下的言辞分离,成为三个分组操作符(能够看成优先进步的情势)。

  // 现在这个函数声明的语法是正确的,但还是有报错
  // 表达式后面的括号是非法的, 因为分组运算符必须包含表达式

  function foo(){ /* code */ }(); // SyntaxError: Unexpected token )

  // 如果你在括号内放置了表达式, 没有错误抛出...
  // 但是函数也不会执行, 因为:

  function foo(){ /* code */ }( 1 );

  // 它与一个函数声明后面放一个完全无关的表达式是一样的:

  function foo(){ /* code */ }

  ( 1 );

你能够翻阅 Dmitry A. Soshnikov
的文章来打听更加多关于那方面包车型大巴知识,ECMA-262-3 in detail. Chapter 5.
Functions

当即施行函数表明式(IIFE)

幸而的是,固定的语法错误很简单。最广泛接受的法门告知解析器那是三个被括号包裹的函数表明式。因为在
JavaScript
中,括号内无法包括函数注明,在那一点上,当解析器遇到 function
关键字,它会以函数表达式而不是函数注脚去分析它。

// 以下的任何一种方式都可以立即执行函数表达式,利用函数的执行环境
// 创建私有作用域

(function(){ /* code */ }()); // Crockford 推荐这个
(function(){ /* code */ })(); // 这个同样运行正常

// 因为括号和强制运算符的目的就是区分函数表达式和函数声明
// 它们会在解析器解析表达式时被忽略(但是请看下面的“重要提示”)

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// 如果你不关心函数返回值或者你的代码变得难以阅读
// 你可以在函数前面加一个一元运算符

!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

// 下面是另一种变体, from @kuvos
// 我不确定使用 `new` 关键字是否有性能影响, 但是能够正常运行
// http://twitter.com/kuvos/status/18209252090847232

new function(){ /* code */ }
new function(){ /* code */ }() // 只需要使用括号传递参数

有关括号的注意事项

在函数表达式外面添加括号可避防除嫌疑,但这一状态并不是必须的,因为解析器已经预约义了三个函数表明式。作为约定,再做职责时接纳括号照旧是2个好点子。

这一括号一般意味着函数表明式会被立时施行,变量将包蕴函数的结果而不是函数本人。这也会解决部分劳神,否则一旦您写了2个不短的函数表明式,旁人必须拉到最头部查看该函数有没有被当下执行。

基于经验来说,书写鲜明的代码不仅可避防止浏览器抛出语法错误,也得以幸免任何开发者对您说“WTFError”(what
the fuck error)!

闭包的存款和储蓄状态

仿佛函数被函数名调用时参数会被传送一样,马上实施函数表明式时参数同样会被传送。因为在一个函数内部定义的函数能够访问外部函数的变量(那种关涉被称为闭包)。一个当下执行函数表明式能够用来封锁函数值并且有效的存款和储蓄状态。

假诺您想打听越多关于闭包的知识,请浏览Closures explained with
JavaScript

// 以下程序的运行结果和你想象的并不一样, 因为 `i` 的值
// 不会被锁定。相反,当点击每个链接的时候 (循环已经
// 结束), 会显示元素的总数, 因为那才是
// 点击时 `i` 实际的值.

var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  elems[ i ].addEventListener( 'click', function(e){
    e.preventDefault();
    alert( 'I am link #' + i );
  }, 'false' );

}

// 以下程序会按你想象的方式运行, 因为在 IIFE 中, `i` 的值
// 会作为 `lockedInIndex` 被锁定。 循环结束之后, 
// 尽管 `i` 的值是元素总数, 但是在 IIFE 中
// `lockedInIndex` 的值是函数表达式调用时传入的(`i`)的值
// 因此当点击链接时, 显示的值是正确的。

var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  (function( lockedInIndex ){

    elems[ i ].addEventListener( 'click', function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex );
    }, 'false' );

  })( i );

}

// 你也许会这样使用 IIFE , 只是包含 (返回) 
// 点击处理函数, 并不是整个 `addEventListener` 声明
// 无论哪种方式,两个示例都使用
// IIFE, 我发现前面的例子更易读懂

var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
    return function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex );
    };
  })( i ), 'false' );

}

小心最终八个例子,即便 lock``edInIndex 能够收获 i
的值,然而选择三个例外的称谓标识符作为函数参数能够使复杂的定义易于解释。

即时实施函数表明式最好的另一方面正是,因为那几个匿名函数表达式被立刻执行,没有标识符,所以闭包的施用不会传染当前作用域。

“自推行匿名函数”有荒唐吗?

您曾经意识这一称呼被波及了频仍,但可能并不明显,小编早就提出“立刻施行函数表明式”这一术语,若是您喜爱缩写,也足以叫做“IIFE”。“iffy”的发音提示了自个儿,笔者很欣赏,让大家这么称呼它吧。

“霎时施行函数表达式”是何许?它是贰个被登时实施的函数表明式,就如这几个名称会让你相信一样。

自小编期待观察 JavaScript
社区分子在他们的篇章和告知中利用“即刻实施函数表明式”那几个术语。因为自个儿认为这几个术语使得驾驭这一概念变得简单,而“自实施匿名函数”这一术语并不标准。

// 这是一个自执行函数。 这种函数会递归地
// 执行 (或调用) 自身:

function foo() { foo(); }

// 这是一个自执行匿名函数。因为它没有
// 标识符, 必须使用 `arguments.callee` 属性 (它
// 表示当前执行的函数) 来调用自身。

var foo = function() { arguments.callee(); };

// 这 *可能* 是一个自执行匿名函数, 但只有当
// `foo` 标识符实际引用它的时候。如果你把`foo` 换成
// 别的东西, 你可能会有一个 "用于自执行" 的匿名函数。

var foo = function() { foo(); };

// 有些人把这个称为 "自执行匿名函数" ,其实它并
// 不是自执行, 因为它没有调用自身。它只是
// 立即调用。

(function(){ /* code */ }());

// 给函数表达式添加一个标识符 (因此创建了一个命名
// 函数表达式) ,调试时会非常有用。一旦命名,
// 函数不再是匿名的。

(function foo(){ /* code */ }());

// IIFE 也可以自执行, 尽管这并不是最
// 有用的方式。

(function(){ arguments.callee(); }());
(function foo(){ foo(); }());

// 最后需要注意的一点: 这在 BlackBerry 5 中会报错, 因为
// 在一个命名函数表达式中, 函数名是 undefined。很奇怪,对吧?

(function foo(){ foo(); }());

梦想那些示例能够证实“自推行”的术语简单被误解,因为并不是函数执行本人,即便函数被执行了。同样“匿名”也不具体,因为“立时实施函数表明式”既能够匿名也能够命名。因为相对而言“executed”,笔者更爱好“invoked”,3个简约的来头是因为
头韵。我认为“IIFE”听上去比“IEFE”更好。

以上就是自己的看法。

幽默的是:因为 arguments.calleeECMAScript 5 strict
mode

严苛方式下一度过时,所以不或然在 ES5 的严酷模式下创办“自推行匿名函数”。

最终的题外话:模块化

既然如此涉及了函数表达式,尽管本人不说一下模块化正是笔者的不经意。你不熟悉JavaScript的模块化也没提到,作者的率先个示范格外简单,只是最后回到的是二个对象而不是函数(平常作为单例形式运作,如以下示例)

// 创建一个立即执行的匿名函数表达式, 然后
// 将它的 *返回值* 赋给一个变量。这种方法无须再
// 创建一个 `makeWhatever` 函数的引用。
// 
// 如同上面 "关于括号的注意事项" 中提到的一样, 尽管括号在函数
// 表达式中不是必须添加的, 但是按照习惯还是应该添加括号,
// 因为这可以更清晰的表示出赋值给一个变量的是
// 函数的 *结果* 而不是函数自身

var counter = (function(){
  var i = 0;

  return {
    get: function(){
      return i;
    },
    set: function( val ){
      i = val;
    },
    increment: function() {
      return ++i;
    }
  };
}());

// `counter` 是一个有属性的对象, 它的属性都是方法

counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.increment(); // 5

counter.i; // undefined (`i` 不是返回对象的属性)
i; // ReferenceError: i 未定义 (它只存在于闭包内)

模块化方法不但有力而且简单。你能够用更少的代码有效地命超情势和性情,用一种办法组织拥有的代码模块,并且能够幸免全局变量的污染以及开创私有成效域。

壮大阅读