深入理解javascript中的立即调用的函数表达式(IIFE)
在弄清楚什么是立即调用的函数表达式(IIFE)之前,我们先了解一些函数的基本概念。
函数声明、函数表达式、匿名函数
函数声明:function functionName() {...}; 使用function关键字声明一个函数,再指定一个函数名,叫函数声明。函数声明后不会立即执行,会在我们需要的时候调用到。
函数表达式:var test = function () {…};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
匿名函数:function () {}; 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。
函数声明和函数表达式不同之处在于
- Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式
- 函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以 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