前言
最近又温故而知新了一遍JavaScript的闭包,前面几年懵懵懂懂看了很多篇文章,今天再看还是受益匪浅,从而也搞懂了一道经典面试题,这个面试题之前也迟迟不理解,直到今天才豁然开朗。接下来就来逐步分析分析这道经典面试题。
题目
1: var data = [];
2: for (var i = 0; i < 3; i++) {
3: data[i] = function () {
4: console.log(i);
5: };
6: }
7:
8: data[0](); // 3
9: data[1](); // 3
10:data[2](); // 3
分析:
-
开始 JavaScript执行的是全局上下文作用域。
-
第一行声明的全局变量 data,并赋值为一个空数组
-
2-6行 定义了一个for 循环体,此时重点就在这个循环体里面了,再细细拆分来看。
-
循环体中
var i = 0;声明的也是全局变量,i < 3; i++是循环条件。 -
循环体里面 变量
data开始进行被赋值,而且是经过了三次赋值,其结果拆开来写就是:data[0] = function(){ console.log(i) } data[1] = function(){ console.log(i) } data[2] = function(){ console.log(i) }此时注意 三次循环赋值 的结果的函数体内都是
console.log(i),而并不是console.log(0),console.log(1),console.log(2),此时的三个函数都是声明中,并未开始调用执行; -
真正执行函数是 8 - 9 行,单独拿出每个数组中的函数执行。
-
再结合 全局声明的
var i = 0;经过三次循环,全局的 i 等于 3 了。 -
最后 调用函数体内的
console.log(i),此时的 i 找到的就是 全局变量i = 3,最后输出执行的都是console.log(3)这个结果。
分析原因
为什么会造成这样的结果。在没了解到闭包之前,还傻傻的以为输出的是 0,1,2,但因为词法作用域的原因,导致了这样的结果,因为污染了全局变量,导致函数作用域一层一层往上找的过程,没有找到对应点变量,就找了全局的变量。
解决方案
相信网上也有很多这类面试题的解法,我也不过多描述,主要氛围异步setTimeOut处理,和ES6 let声明处理。
setTimeOut 自执行函数和闭包
var data = [];
for (var i = 0; i < 3; i++) {
(function(i){
setTimeout(data[i] = function () {
console.log(i);
}, 0)
})(i)
}
data[0]();
data[1]();
data[2]();
用 let
var data = [];
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
}
}
data[0]();
data[1]();
data[2]();
最后
经过这次理解函数的闭包,再结合这道经典面试题,让我更深刻的理解了一遍闭包中的全局作用域 和 词法作用域。突然发现好多原理性的东西都是需要多看几遍才能深刻理解,每次看都有或多或少的收货。