IIFE类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。
1 (function() { 2 // 块级作用域 3 })()
使用IIFE可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。ES5没有支持块级作用域,使用IIFE模拟块级作用域是相当普遍的。
1 // IIFE 2 (function () { 3 for (var i = 0; i < count; i++) { 4 console.log(i); 5 } 6 })(); 7 console.log(i); // 抛出错误
前面的代码在执行到 IIFE 外部的 console.log()时会出错,因为它访问的变量是在 IIFE 内部定义的,在外部访问不到。
ECMAScript 5.1 及以前,为了防止变量定义外泄,IIFE 是个非常有效的方式。这样也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。为此,只要函数执行完毕,其作用域链就可以被销毁。
在ES6以后,IIFE就没有那么必要了,因为块级作用域中的变量无需IIFE就可以实现同样的隔离
1 // 内嵌块级作用域 2 { 3 let i; 4 for (i = 0; i < count; i++) { 5 console.log(i); 6 } 7 } 8 console.log(i); // 抛出错误 9 // 循环的块级作用域 10 for (let i = 0; i < count; i++) { 11 console.log(i); 12 } 13 console.log(i); // 抛出错误
说明IIFE作用的一个实际的例子,就是可以用它锁定参数值。比如:
1 let divs = document.querySelectorAll('div'); 2 // 达不到目的! 3 for (var i = 0; i < divs.length; ++i) { 4 divs[i].addEventListener('click', function() { 5 console.log(i); 6 }); 7 }
这里使用var关键字声明了迭代变量i,这里的i在循环结束以后,将会编程循环结束时的最终值。因为这个变量i是全局的,外部随时可以访问。可以借助IIFE来执行一个函数表达式,传入每次循环的当前索引,从而达到“锁定”点击时的索引值。
1 let divs = document.querySelectorAll('div'); 2 for (var i = 0; i < divs.length; ++i) { 3 divs[i].addEventListener('click', (function(frozenCounter) { 4 return function() { 5 console.log(frozenCounter); 6 }; 7 })(i)); 8 }
这里使用let声明i也可以达到同样的目的