不多说,先看两道笔试题,首先上代码。
例1
<script>
window.onload = function () {
var list = document.getElementsByClassName('list')
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function () {
alert(i);
})
}
}
</script>
<ul>
<li class="list">点我1</li>
<li class="list">点我2</li>
<li class="list">点我3</li>
</ul>
按照逻辑应该实现的效果是,我们点击每个元素,弹框提示对应元素的索引值,但真实的结果是无论我们点击哪个li,均弹出3.
例2来自京东的笔试题,代码如下
var name = 'Tom';
(function () {
if (typeof name == 'undefined') {
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
如果我们不了解IIFE的执行逻辑,可能会认为输出是Hello Tom,然而程序的实际输出是Goodbye Jack。
要解决这两道题目,我们就要去搞明白IIFE的相关内容。
1 何为IIFE
IIFE( 立即调用函数表达式)顾名思义是一个在定义时就会立即执行的 [JavaScript]。
语法格式如下
(function(){
alert('hello world')
})();
这两行代码完成了两个功能,声明了一个函数,并自己进行了调用。简单来说,我们以往定义的函数,如果要执行是需要调用的,可能是事件触发,也可能是我们直接调用。但IIFE明显独树一帜,完成声明后,自己调用执行自己,非常独立和自我。
2.IIFE的作用
解决了“我是谁 ”的问题,接着来看IIFE存在的意义是什么。
1)因为IIFE的执行是自主完成的,所以我们无需为其命名,这样可以避免全局性的变量污染。
2)IIFE圈地自治,IIFE函数体内的变量只在本作用域内有效,可以有效减少变量污染,同时实现变量私有,函数执行完成后,变量立即销毁。
3.如何使用IIFE
回到第一题,之所以会输出3,是因为点击函数并不是立即执行的,变量i的值在for循环里是在不断变化的,当for循环执行后,i的最终值为3,那么点击元素出发弹框后,会自动访问i的地址,找到的就是3。
使用立即执行函数可以有效解决这个问题。
window.onload = function () {
var list = document.getElementsByClassName('list')
for (var i = 0; i < list.length; i++) {
(function (i) {
list[i].addEventListener('click', function () {
alert(i);
})
})(i)
}
}
首先将每次绑定都改为立即执行函数,其次将i作为变量传入IIFE,这样for循环对i的更新将不会影响IIFE内的i。
除了这样修改,还可以用ES6的关键字let的替代var进行i声明,也可以解决这个问题。
第二个问题也迎刃而解,我们知道name的值并没有作为变量传入IIFE,那么if的时候name理所当然是未定义,此时程序走的第一个分支,输出的是Goodbye Jack。
光理解这个输出是不够的,可以试下,将立即执行函数的对name的声明改为let,你会发现,程序居然输出了Hello Tom。
这个地方就有点吊诡了,看了资料分析说,是因为我用let声明的变量,引擎认为在IIFE内是有这个变量的,所以执行的时候会走else,但因为if分支并未执行,所以初始化称jack的操作没有进行,此时系统在本作用域内找不到name,就会逐级向上找,一直找到window,然后就找到了Tom。
好吧,js引擎是个任性boy,不按常理出牌,他的套路分分钟绕晕我。