深入理解javascript中的立即调用的函数表达式,IIFE

  在弄清楚什么是立即调用的函数表达式(IIFE)之前,我们先了解一些函数的基本概念。

  函数声明、函数表达式、匿名函数

    函数声明:function functionName() {...}; 使用function关键字声明一个函数,再指定一个函数名,叫函数声明。函数声明后不会立即执行,会在我们需要的时候调用到。

    函数表达式:var test = function () {…};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。

    匿名函数:function () {}; 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。

  函数声明和函数表达式不同之处在于

  1. Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式
  2. 函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以 functionName()形式调用 。

   以下是两者差别的两个例子:  

myFunction();
function myFunction () {
    //code
}
//正常,因为‘提升'了函数声明,函数调用可在函数声明之前

myFunction();
var myFunction = function () {
    //code
}
//报错,变量myFunction还未保存对函数的引用,函数调用必须在函数表达式之后

  在JavaScript里,任何function在执行的时候都会创建一个执行上下文,因为为function声明的变量和function有可能只在该function内部,这个上下文,在调用function的时候,提供了一种简单的方式来创建自由变量或私有子function。

// 由于该function里返回了另外一个function,其中这个function可以访问自由变量i
// 所以说,这个内部的function实际上是有权限可以调用内部的对象。
function makeCounter() {
// 只能在makeCounter内部访问i
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
alert(i); // 引用错误:i 没有defind(因为i是存在于makeCounter内部)。

  很多情况下,我们不需要makeCounter多个实例,甚至某些情况下,我们也不需要显示的返回值。

  有时需要在定义函数之后,立即调用该函数。这种函数就叫做立即执行函数,全称为立即调用的函数表达式IIFE(Imdiately Invoked Function Expression)

  javascript引擎规定,如果function关键字出现在行首,一律解释成函数声明语句

  【1】函数声明语句需要一个函数名,由于没有函数名,所以报错

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

  【2】函数声明语句后面加上一对圆括号,只是函数声明语句与分组操作符的组合。由于分组操作符不能为空,所以报错

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

//等价于
function foo(){};
();  //SyntaxError: Unexpected token

  【3】函数声明语句加上一对有值的圆括号,也仅仅是函数声明语句与不报错的分组操作符的组合而已

function foo(){}(1);

//等价于
function foo(){};
(1);

  所以,解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式

  最常用的两种办法

(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(){ /* code */ };
new function(){ /* code */ }();

  自执行匿名函数和立即执行的函数表达式区别 

// 这是一个自执行的函数,函数内部执行自身,递归
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 */ } ());

// 立即调用的函数表达式(IIFE)也可以自执行,不过可能不常用罢了
(function () { arguments.callee(); } ());
(function foo() { foo(); } ());

  用途

   javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉

   根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,

   所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。

  假设有一个需求,每次调用函数,都返回加1的一个数字(数字初始值为0)

  【1】全局变量:一般情况下,我们会使用全局变量来保存该数字状态

var a = 0;
function add(){
    return ++a;
}
console.log(add());//1
console.log(add());//2
但上面的方法中,变量a实际上只和add函数相关,却声明为全局变量,不太合适。

  【2】自定义属性:将变量a更改为函数的自定义属性更为恰当

function add(){
    return ++add.count;
}
add.count = 0;
console.log(add());//1
console.log(add());//2
其实这样做,还是有问题。有些代码可能会无意中将add.count重置

  【3】立即调用的函数表达式(IIFE):使用IIFE把计数器变量保存为私有变量更安全,同时也可以减少对全局空间的污染

var add = (function(){
    var counter = 0;
    return function(){
        return ++counter; 
    }
})();
console.log(add())//1
console.log(add())//2