例一:
1 function createFunctions(){ 2 var result = new Array(); 3 for (var i=0; i < 10; i++){ 4 result[i] = function(){ 5 return i; 6 }; 7 } 8 return result; 9 } 10 var funcs = createFunctions(); 11 for (var i=0; i < funcs.length; i++){ 12 console.log(funcs[i]()); 13 }
乍一看,以为输出 0~9 ,万万没想到输出10个10?
这里的陷阱就是:函数带()才是执行函数! 单纯的一句 var f = function() { alert('Hi'); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。上面代码翻译一下就是:
var result = new Array(), i; result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! ... result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! i = 10; funcs = result; result = null; console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10 console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10 ... console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10
例二:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(new Date, i); }, 1000); } console.log(new Date, i);
- A. 20% 的人会快速扫描代码,然后给出结果:
0,1,2,3,4,5
; - B. 30% 的人会拿着代码逐行看,然后给出结果:
5,0,1,2,3,4
; - C. 50% 的人会拿着代码仔细琢磨,然后给出结果:
5,5,5,5,5,5
; - 正确答案是:c
接下来我会追问:如果我们约定,用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略,代码实际运行的结果该如何描述?会有下面两种答案:
- A. 60% 的人会描述为:
5 -> 5 -> 5 -> 5 -> 5
,即每个 5 之间都有 1 秒的时间间隔; - B. 40% 的人会描述为:
5 -> 5,5,5,5,5
,即第 1 个 5 直接输出,1 秒之后,输出 5 个 5;
这就要求候选人对 JS 中的定时器工作机制非常熟悉,循环执行过程中,几乎同时设置了 5 个定时器,一般情况下,这些定时器都会在 1 秒之后触发,而循环完的输出是立即执行的,显而易见,正确的描述是 B。
如果期望代码的输出变成:5 -> 0,1,2,3,4
,该怎么改造代码?熟悉闭包的同学很快能给出下面的解决办法:
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);
有没有更符合直觉的做法?答案是有,我们只需要对循环体稍做手脚,让负责输出的那段代码能拿到每次循环的 i
值即可。该怎么做呢?利用 JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征,不难改造出下面的代码:
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(new Date, i);
作者:王仕军 链接:https://juejin.im/post/58f1fa6a44d904006cf25d22 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。