ECMAScript旋即实施函数表达式(IIFE)

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

译者:nzbin

或者你还无放在心上到,我是一个对术语比较坚持的口。因此,在视听那个频繁于盛行却容易生出误导之
JavaScript 术语“自实施匿名函数”之后,最终自控制拿我的想法写成一篇稿子。

为了供关于这无异于模式如何运转的淋漓尽致信息,我早已提出了咱相应如何称呼它们的建议,继续朝下看。当然,如果你想越了起,你得只是看“自实施函数表达式”这无异省,但是自己建议您看完整首稿子。

告理解就首稿子并非假设表达“我是对的,你是错的”这无异于意。我真的感兴趣的是拉人们了解一些秘密的复杂概念,并且为众人发现及用同样的及准之术语是人人能好为造福清楚的最为简便易行的作业有。

那,这究竟是怎么回事呢?

当 JavaScript
中,每一个函数在执行时还见面有一个初的尽环境。由于当函数中定义之变量和函数只能以里访问使休能够让表面看。这无异于执环境调用的函数提供了一个非常简单的章程来创造私有作用域。

  // 因为返回的函数有权访问私有变量 `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
关键字时,默认情况下,它见面以为就是函数声明如未是函数表达式。如果您没明确告知解析器这是一个表达式,它见面认为就是一个匿名的函数声明并丢掉来意外之语法错误,因为函数声明需要名称。

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

诙谐之是,如果你也一个函数指定了名并且于当下在该背后放置了括号,解析器也会见丢弃来荒谬,但由莫衷一是。虽然于表达式之后放置括号说明及时是一个用受实践的函数,但于宣称后放置括号会和前方的说话分离,成为一个分组操作符(可以用作优先提升的道)。

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

  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 */ }() // 只需要使用括号传递参数

关于括号的注意事项

于函数表达式外面添加括号可以打消困惑,但当时等同景并无是得的,因为解析器已经预定义了一个函数表达式。作为约定,再开任务时使用括号仍然是一个吓点子。

这同一载号一般意味着函数表达式会被及时施行,变量将富含函数的结果使不是函数本身。这为会缓解部分难为,否则要你写了一个百般丰富的函数表达式,别人要拉至绝底部查看该函数有没发出吃当下施行。

冲涉来说,书写明确的代码不仅可免浏览器抛来语法错误,也可免任何开发者对而说“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”,一个大概的原故是因
头韵。我认为“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 未定义 (它只存在于闭包内)

模块化方法不但有力又简。你得据此重新不见的代码有效地命名方式与性能,用同种方式组织有的代码模块,并且可以免全局变量的传染和开创私有作用域。

扩张阅读

  • ECMA-262-3 in detail. Chapter 5.
    Functions. –
    Dmitry A. Soshnikov
  • Functions and function
    scope –
    Mozilla Developer Network
  • Named function expressions – Juriy
    “kangax” Zaytsev
  • JavaScript Module Pattern:
    In-Depth –
    Ben Cherry
  • Closures explained with
    JavaScript –
    Nick Morgan