JS 立即调用函数表达式 IIFE

2018-07-15  本文已影响0人  yfmei

写在前面

下文中提到的 IIFE 其实就是“立即调用函数表达式”

为什么需要 IIFE

// 因为该函数返回的是另一个有权访问私有变量 i 的函数,
// 所以实际上返回的函数是一种“特权”
function makeCounter() {
  // `i` 只能在 `makeCounter`内部访问.
  var i = 0;

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

//  `counter` and `counter2` 可以在自己的作用域内互不影响的访问 `i`.

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

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

i; // ReferenceError: i is not defined (it only exists inside makeCounter)

核心

// 这样定义的函数可以像 foo() 这样调用( 在函数名后面加一对() )。
// 因为 foo 只是函数表达式 `function(){/* code */}`的引用。
var foo = function(){ /* code */ }

// 那是不是也就是说在函数表达式后面加一对 () 就能调用它自己了?
function(){ /* code */ }(); // SyntaxError: Unexpected token (

函数,括号 和 语法错误

// 虽然该函数声明在语法上有效,但仍然是条语句,后面的一对 () 是无效的,
// 因为分组操作符需要包含表达式
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )

// 如果你在后面的 () 中放一个表达式就没异常了。。。
// 但是函数也不会执行,因为这条语句:
function foo(){ /* code */ }( 1 );
// 跟这条语句完全一样, 函数声明后面紧跟一个完全不相干的表达式
function foo(){ /* code */ }
(1);

你可以在这里了解更多有关函数的细节。

立即调用函数表达式 (IIFE)

// 下面的两种方法都可以实现立即调用函数表达式和利用函数的上下文来实现私有化。

(function(){ /* code */ }()); // 推荐这种方式
(function(){ /* code */ })(); // 这样也可以

// 因为 () 和一些操作符(如 = && || ,等)主要是用来在函数表达式和函数声明之间消除歧义的,
// 所以他们可以在解析器已经明确需要一个表达式时省略(但请参与下面的“重要说明”)。

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

// 如果你不关心返回值或者也不介意你的代码变得晦涩难懂,
// 你可以通过在函数前面添加一元操作符来保存字节。

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

// 还有使用关键字 new Function的方式使用立即调用函数表达式

new Function('console.log("hi")')() 
// 你需要把参数名、函数体都作为参数传给 Function 的构造函数,在后面的括号里写上实参,表示调用传值
new Function("a", "b", 'console.log(a + b)')(1, 2)

关于括号的重要说明

用闭包保存状态

// 这并没有像你想的那样运行,因为 i 的值没有被锁定。
// 相反的,每一次点击 link ,
// 都会弹出元素的总数量(循环完全结束时的值),因为那才是 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 的值是总的元素数量,而 lockedInIndex 是函数调用时传进来的 i 的值。
// 所以,当点击 link 时,就会弹出正确的值。

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' );

}

“自执行匿名函数”有什么问题?

//  自执行函数。递归执行或调用:
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(); }());

最后一块:模块

// 创建一个立即调用的匿名函数
// 把返回值赋值给一个变量,这个变量包含你要暴露的属性(私有化)
// 这种方式移除了名为`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` is not a property of the returned object)
i; // ReferenceError: i is not defined (it only exists inside the closure)
上一篇 下一篇

猜你喜欢

热点阅读