立即调用的函数表达式
函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。 立即调用的函数表达式如果不在包含作用域中将返回值赋给一个变量,则其包含的所有变量都会被销毁。
立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked Function Expression)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。下面是一个简单的例子:
(function() {
// 块级作用域
})();
使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。这样位于函数体作用域的变量就像是在块级作用域中一样。ECMAScript 5 尚未支持块级作用域,使用 IIFE 模拟块级作用域是相当普遍的。比如下面的例子:
// IIFE
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
上面的代码在执行到 IIFE 外部的 console.log() 时会出错,因为它访问的变量是在 IIFE 内部定义的,在外部访问不到。在 ECMAScript 5.1 及以前,为了防止变量定义外泄,IIFE 是个非常有效的方式。 这样也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。为此,只要函数执行完毕, 其作用域链就可以被销毁。
在 ECMAScript 6 以后,IIFE 就没有那么必要了,因为块级作用域中的变量无须 IIFE 就可以实现同样的隔离。下面展示了两种不同的块级作用域形式:
// 内嵌块级作用域
{
let i;
for (i = 0; i < count; i++) {
console.log(i);
}
}
console.log(i); // 抛出错误
// 循环的块级作用域
for (let i = 0; i < count; i++) {
console.log(i);
}
console.log(i); // 抛出错误
说明 IIFE 用途的一个实际的例子,就是可以用它锁定参数值。比如:
let divs = document.querySelectorAll('div');
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}
这里使用 var 关键字声明了循环迭代变量 i,但这个变量并不会被限制在 for 循环的块级作用域内。 因此,渲染到页面上之后,点击每个都会弹出元素总数。这是因为在执行单击处理程序时,迭代变量的值是循环结束时的最终值,即元素的个数。而且,这个变量 i 存在于循环体外部,随时可以访问。
以前,为了实现点击第几个就显示相应的索引值,需要借助 IIFE 来执行一个函数表达式,传入每次循环的当前索引,从而“锁定”点击时应该显示的索引值,现在使用es6提供的let变量的块级作用域:
let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}
这样就可以让每次点击都显示正确的索引了。这里,事件处理程序执行时就会引用 for 循环块级作用域中的索引值。这是因为在 ECMAScript 6 中,如果对 for 循环使用块级作用域变量关键字,在这里就是 let,那么循环就会为每个循环创建独立的变量,从而让每个单击处理程序都能引用特定的索引。
但要注意,如果把变量声明拿到 for 循环外部,那就不行了。下面这种写法会碰到跟在循环中使用 var i = 0 同样的问题:
let divs = document.querySelectorAll('div');
// 达不到目的!
let i;
for (i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}