立即调用函数表达式

立即调用函数表达式(英文:immediately-invoked function expression,缩写:IIFE[1],是一种利用JavaScript函数生成新作用域的编程方法。

立即调用函数表达式可以令其函数中声明的变量绕过JavaScript的变量置顶声明规则,还可以避免新的变量被解释成全域变量或函数名占用全域变量名的情况。与此同时它能在禁止访问函数内声明变量的情况下允许外部对函数的调用。有时,这种编程方法也被叫做“自执行(匿名)函数”,但“立即调用函数表达式”是语义上最准确的术语。 [2][1][3][4]

用法

立即调用函数表达式拥有数种不同的写法[5]。最常见的一种是将函数表达式字面量置于圆括号(分组运算符)之内,然后使用圆括号调用函数。[6][7]

(function() {
  // 这里的语句将获得新的作用域
})();

若要将作用域外变量传递进函数,则按下述方式书写:

(function(a, b) {
  // a == 'hello'
  // b == 'world'
})('hello', 'world');

开头的括号可能会因为解释器的分号自动插入特性造成一些问题。括号本用于明确字面量为表达式以与函数声明语句区分,但解释器可能将括号解释为对以上一行中结尾的变量名为名的函数的调用。在一些省略分号的程序中,可见将分号至于行首的做法。这样的分号被称为“防御性分号”[8][9],举例:

a = b + c
;(function() {  // 故意将分号放在这里
  // 代码
})();

如此书写,以防止语句被理解为对函数c的调用(c(...))。

例子

理解立即调用函数表达式的关键在于认清JavaScript拥有函数作用域,但没有块作用域(ES6之前),且通过指针(而非复制)将变量传入一个函数闭包[10] ES6 引入了新关键字 let和 const,用它们定义的常量和变量具有块级作用域。

求值上下文(Evaluation context)

缺少块作用域意味着一个在类似于for循环的块中声明的变量会被置顶到其所包含的函数中。如果一个内部函数依赖于一个外部变量,而该外部变量被外部函数更改,那么执行内函数就有些困难。举例,我们在声明函数之后,但在定义函数之前,改变一个变量的值。[11]

var v, getValue;
v = 1;
getValue = function() { return v; };
v = 2;
 
getValue(); // 2

当我们手动给v赋值时这结果似乎没什么问题。不过,如果getValue()是在一个循环中被定义的,那么就可能出现预想外的结果。

var v, getValue;
v = 1;
getValue = (function(x) {
  return function() { return x; };
})(v);
v = 2;

getValue(); // 1

此例中,function将 v 作为参数传入并立即调用,保护了内部函数的执行上下文。[12]

David Herman's作品 Effective JavaScript 包含了一个用来在循环中求值导致问题的例子。[13] 虽然他的例子刻意编写得非常复杂,但是原因都是缺乏块作用域导致的.[14]

利用IIFE建立真正的私有函数和变量,并用闭包访问

立即调用函数表达式也可以用来创建私有方法来访问函数,不仅起到保护作用,同时也暴露了一些可以后续使用的属性。[15] 下面的例子来自于 Alman's 关于IIFE的网帖。[1]

// 'counter' 函数返回一个具有属性的对象, 这里的属性就是
// get set等函数
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 这个数据由IIFE封装,它并不是 counter的属性。同样的,如果我们试图访问 i 也会收到错误,因为 i 并没有在全局作用域中定义。

术语

"立即调用函数表达式" 最早称为“自执行(匿名)函数”[1][5] 但是立即执行的函数不一定是匿名的。 ECMAScript 5的 strict mode 禁止arguments.callee,[16] 因此,这个术语不够准确.[3][12]

在lambda-calculus(λ演算)中,这个构造称为 "redex", 用来化简表达式, 参阅:Reduction strategy (code optimization).

参考

  1. ^ 1.0 1.1 1.2 1.3 Alman, Ben. Immediately Invoked Function Expressions. 2010 [4 February 2013]. (原始内容存档于2013-01-20). 
  2. ^ Resig, John. Pro JavaScript Techniques. Apress. 2006: 29. ISBN 9781430202837. 
  3. ^ 3.0 3.1 Osmani, Addy. Learning JavaScript Design Patterns. O'Reilly. 2012: 206. ISBN 9781449334871. 
  4. ^ Baagoe, Johannes. Closing parenthesis in function's definition followed by its call. [19 April 2010]. (原始内容存档于2011-01-22). 
  5. ^ 5.0 5.1 Lindley, Cody. JavaScript Enlightenment. O'Reilly. 2013: 61. ISBN 9781449342883. 
  6. ^ Zakas, Nicholas. Maintainable JavaScript. O'Reilly. 2012: 44. ISBN 9781449327682. 
  7. ^ Crockford, Douglas. Code Conventions for the JavaScript Programming Language. [3 February 2013]. (原始内容存档于2012-03-05). 
  8. ^ "JavaScript Semicolon Insertion: Everything you need to know页面存档备份,存于互联网档案馆)", Friday, May 28, 2010
  9. ^ "Semicolons in JavaScript are optional页面存档备份,存于互联网档案馆)", by Mislav Marohnić, 07 May 2010
  10. ^ Haverbeke, Marijn. Eloquent JavaScript. No Starch Press. 2011: 29–30. ISBN 9781593272821. 
  11. ^ Alman, Ben. simple-iife-example.js. Github. [5 February 2013]. (原始内容存档于2021-04-14). 
  12. ^ 12.0 12.1 Otero, Cesar; Larsen, Rob. Professional jQuery. John Wiley & Sons. 2012: 31. ISBN 9781118222119. 
  13. ^ Herman, David. Effective Javascript. Addison-Wesley. 2012: 44–45. ISBN 9780321812186. 
  14. ^ Zakas, Nicholas C. Mimicking Block Scope. Professional JavaScript for Web Developers. John Wiley & Sons. 2011. ISBN 9781118233092. 
  15. ^ Rettig, Pascal. Professional HTML5 Mobile Game Development. John Wiley & Sons. 2012: 145. ISBN 9781118301333. 
  16. ^ Strict mode. Mozilla JavaScript Reference. Mozilla Developer Network. [4 February 2013]. (原始内容存档于2013-05-25). 

外部链接