JavaScript 立即调用函数表达式
作者:Seiya
时间:2019年07月05日
前言
立即调用函数表达式可以令其函数中声明的变量绕过 JavaScript 的变量置顶声明规则,还可以避免新的变量被解释成全局变量或函数名占用全局变量名的情况。与此同时它能在禁止访问函数内声明变量的情况下允许外部对函数的调用。
有时,这种编程方法也被叫做“自执行(匿名)函数”,但“立即调用函数表达式”是语义上最准确的术语。
什么是自执行
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。
在 JavaScript 里,任何 function 在执行的时候都会创建一个执行上下文,因为为 function 声明的变量和 function 有可能只在该 function 内部,这个上下文,在调用 function 的时候,提供了一种简单的方式来创建自由变量或私有子 function。
/*
* 由于该 function 里返回了另外一个 function,其中这个 function 可以访问自由变量 i
* 所有说,这个内部的 function 实际上是有权限可以调用内部的对象。
*/
function makeCounter() {
var i = 0; // 只能在makeCounter内部访问i
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内部)。
自执行函数表达式
(function() {
// 这里的语句将获得新的作用域
})();
当代码被括弧全部括住之后,解析器在解析 function 关键字时,会将相应的代码解析成 function 表达式,而不是 function 声明。
举例如下:
/* 下面2个括弧()都会立即执行 */
(function () { /* code */ } ()); // 推荐使用这个
(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(); } ());
用闭包保存状态
和普通 function 执行的时候传参数一样,自执行的函数表达式也可以这么传参,因为闭包直接可以引用传入的这些参数,利用这些被 lock 住的传入参数,自执行函数表达式可以有效地保存状态。
/*
* 这个代码是错误的,因为变量 i 从来就没背 locked 住
* 相反,当循环执行以后,我们在点击的时候i才获得数值
* 因为这个时候i操真正获得值
* 所以说无论点击那个连接,最终显示的都是I am link #10(如果有10个a元素的话)
*/
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的值作为 locked 的索引存在,在循环执行结束以后,尽管最后i的值变成了a元素总数(例如10)
* 但闭包内部的 lockedInIndex 值是没有改变,因为他已经执行完毕了
* 所以当点击连接的时候,结果是正确的
*/
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);
}
/*
* 你也可以像下面这样应用,在处理函数那里使用自执行函数表达式
* 而不是在addEventListener外部
* 但是相对来说,上面的代码更具可读性
*/
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');
}