2015的腾讯校招笔试中考察了这样一道前端题。
var cnt = 0;
function one() {
for(var i = 0; i < 10; ++i) {
setTimeout(function() {
cnt += i;
}, 0);
}
}
function two() {
consolo.log(cnt);
}
one();
setTimeout(two, 0);
问题是最后打印出来的数字是多少?
有以下几个选项。
A) 45
B) 55
C) 100
D) 0
有关题目求解
在考试的时候,我选择的是A)和D),最后这里两个答案都是不正确的。正确的答案是C)。
首先setTimeout,是一个定时器函数,如果会在指定的毫秒数之后执行该语句。而这个函数如果以setTimeout(function, 0)的方式执行后,由于js解释器的特性,会将控制权交出,执行后面的语句。并且将事件安排到上下文的最后去执行。类似做了一个将队首元素转移到队尾去执行。
那么这道题中,可以看到,所有和cnt变量有关的语句都调用了setTimeout这条语句。理论上和字面上解释应该是一样的咯,所以答案应该是45才对啊。
但是i这个变量随着setTimeout的出现而改变了,之前如果没有setTimeout的情况下,执行function内部的语句,cnt变量所增加的值是i的字面量。但是此时i增加到10之后,再执行的cnt += i。所以,对于cnt来说,他现在所取到的i其实是循环结束之后的i,所以但等于以i = 10的情况下,加了10次。
而最后console.log()也是由setTimeout调用的,所以也放到了队尾进行执行。最终的结果就是100。
有关解决方案
如果我们这段代码的目标是为了得到cnt = 45的话,我们应该怎么做呢?
因为之前每个值在暂缓执行的过程中,所存的值都是当前变量i的引用,所以我们可以捕获当前的索引值,然后保存在当前作用域中。那么在之后的调用中,我们所调用的i就不再是引用了,而是本地作用域中的那个局部变量。
function one() {
for(var i = 0; i < 10; ++i) {
setTimeout((function(j) {
cnt += j;
})(i), 0);
}
}
Tips
- console.log()可以添加多个参数,基本上打印出来的结果,就和Python里面的print函数是一样的,通过,隔开。
- setTimeout(console.log(1), 0); 如果直接使用字面量,是JS中不被推荐的。这样的话,字面量是直接执行的,所以其实并没有做到setTimeout的作用。那么他是讲什么东西放到了上下文队列的最后呢?一个函数地址。正确的写法应该是通过匿名函数,这种写法也是被推荐的:
setTimeout(function() {
console.log(cnt);
}, 0);
和题目中给出的方法其实是一样的。
- (function() {})(),这种代码的调用方式属于立即执行。其实实现原理就是申明了一个匿名构造函数,然后通过()直接执行他,当然()中是可以直接添加参数的,这些参数都是从当前作用域中直接捕获的。