JS当即执行函数表达式(IIFE)

原文为javascript

http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iifejava

------------------------ES6拓展篇git

----------ES6  let实际上为 JavaScript 新增了块级做用域。github

----------块级做用域的出现,实际上使得得到普遍应用的当即执行函数表达式(IIFE)再也不必要了。express

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级做用域写法
{
  let tmp = ...;
  ...
}

---------------------------------------------------------------------------------------------------------------------------------------------------闭包

 

ps:下文中提到的“当即执行函数”其实就是“当即执行函数表达式”模块化

  • 问题的核心

如今咱们定义了一个函数(function foo(){}或者var foo = function(){}),函数名后加上一对小括号便可完成对该函数的调用,好比下面的代码:函数

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

接着咱们来看下面的代码:spa

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

报错了,这是为什么?这是由于在javascript代码解释时,当遇到function关键字时,会默认把它当作是一个函数声明,而不是函数表达式,若是没有把它显视地表达成函数表达式,就报错了,由于函数声明须要一个函数名,而上面的代码中函数没有函数名。(以上代码,也正是在执行到第一个左括号(时报错,由于(前理论上是应该有个函数名的。)debug

  • 一波未平一波又起

有意思的是,若是咱们给它函数名,而后加上()当即调用,一样也会报错,而此次报错缘由却不相同:

function foo(){ /* code */ }(); // SyntaxError: expected expression, got ')'

为何会这样?在一个表达式后面加上括号,表示该表达式当即执行;而若是是在一个语句后面加上括号,该括号彻底和以前的语句不搭嘎,而只是一个分组操做符,用来控制运算中的优先级(小括号里的先运算)。因此以上代码等价于:

经过以上的介绍,咱们大概了解经过()可使得一个函数表达式当即执行。

有的时候,咱们实际上不须要使用()使之变成一个函数表达式,啥意思?好比下面这行代码,其实不加上()也不会保错:

var i = function(){ return 10; }();

可是咱们依然推荐加上():

var i = (function(){ return 10; }());

为何?由于咱们在阅读代码的时候,若是function内部代码量庞大,咱们不得不滚动到最后去查看function(){}后是否带有()来肯定i值是个function仍是function内部的返回值。因此为了代码的可读性,请尽可能加上()不管是否已是表达式。

  • 当即执行函数与闭包的暧昧关系

当即执行函数能配合闭包保存状态。

像普通的函数传参同样,当即执行函数也能传参数。若是在函数内部再定义一个函数,而里面的那个函数能引用外部的变量和参数(闭包),利用这一点,咱们能使用当即执行函数锁住变量保存状态。

// 并不会像你想象那样的执行,由于i的值没有被锁住
// 当咱们点击连接的时候,其实for循环已经执行完了
// 因而在点击的时候i的值其实已是elems.length了
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' );
 
}
 
// 此次咱们获得了想要的结果
// 由于在当即执行函数内部,i的值传给了lockedIndex,而且被锁在内存中
// 尽管for循环结束后i的值已经改变,可是当即执行函数内部lockedIndex的值并不会改变
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 );
 
}
 
// 你也能够这样,可是毫无疑问上面的代码更具备可读性
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' );
 
}

其实上面代码的lockedIndex也能够换成i,由于两个i是在不一样的做用域里,因此不会互相干扰,可是写成不一样的名字更好解释。以上即是当即执行函数+闭包的做用。

  • 我为何更愿意称它是“当即执行函数”而不是“自执行函数”

IIFE的称谓在如今彷佛已经获得了普遍推广(不知道是否是原文做者的功劳?),而原文写于10年,彷佛当时流行的称呼是自执行函数(Self-executing anonymous function),接下去做者开始为了说明当即执行函数的称呼好于自执行函数的称呼开始力排众议,有点咬文嚼字,不过也蛮有意思的,咱们来看看做者说了些什么。

// 这是一个自执行函数,函数内部执行的是本身,递归调用
function foo() { foo(); }
 
// 这是一个自执行匿名函数,由于它没有函数名
// 因此若是要递归调用本身的话必须用arguments.callee
var foo = function() { arguments.callee(); };
 
// 这可能也算是个自执行匿名函数,但仅仅是foo标志引用它自身
// 若是你将foo改变成其它的,你将获得一个used-to-self-execute匿名函数
var foo = function() { foo(); };
 
// 有些人叫它自执行匿名函数,尽管它没有执行本身,只是当即执行而已
(function(){ /* code */ }());
 
// 给函数表达式添加了标志名称,能够方便debug
// 可是一旦添加了标志名称,这个函数就再也不是匿名的了
(function foo(){ /* code */ }());
 
// 当即执行函数也能够自执行,不过不经常使用罢了
(function(){ arguments.callee(); }());
(function foo(){ foo(); }());

个人理解是做者认为自执行函数是函数内部调用本身(递归调用),而当即执行函数就如字面意思,该函数当即执行便可。其实如今也不用去管它了,就叫IIFE好了。

  • 最后的旁白:模块模式

当即执行函数在模块化中也大有用处。用当即执行函数处理模块化能够减小全局变量形成的空间污染,构造更多的私有变量。

// 建立一个当即执行的匿名函数
// 该函数返回一个对象,包含你要暴露的属性
// 以下代码若是不使用当即执行函数,就会多一个属性i
// 若是有了属性i,咱们就能调用counter.i改变i的值
// 对咱们来讲这种不肯定的因素越少越好
 
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并非counter的属性
i; // ReferenceError: i is not defined (函数内部的是局部变量)
相关文章
相关标签/搜索