今天一个做后端的同事问了我一个JS的问题:
有个循环,循环一个异步回调,为啥回调引用的循环值都是最后一步循环的循环值?然后,又有些时候无论什么循环值都得不到?
好吧,JavaScript跟PHP的循环有时候确实不一样,JavaScript的函数有同步函数跟异步函数的区分,PHP里面没这种概念,拿PHP的常识来理解JavaScript有时候行不通。关于JS异步机制的研究,看我另一篇JS异步执行机制理解。
我想了想,他说的“什么循环值也得不到的”,应该是下面这个情况:
<script type="text/javascript">
var arr = [1,3,5,7,9];
var arrLength = arr.length;
for (var i = 0; i < arrLength; i++) {
setTimeout(function() {
console.log(i);
console.log(arr[i]);
}, 2000);
}
</script>
结果是:
5
undefined
5
undefined
5
undefined
5
undefined
5
undefined
for循环有一个特点,就是“i判断失败一次才停止”。所以,i在不断的自加1的时候,直到i等于5,i才失败,这时候循环体不再执行,会跳出,所以i等于5没错。那么为什么5次循环的i都等于5?原因就是setTimeout()的回调,也就是console.log(i);console.log(arr[i]);
被压到任务队列的最后,for循环是同步任务,所以先执行,等于是空跑了5次循环。于是,i都等于5之后,console.log(i);console.log(arr[i]);
刚开始第一次执行,当然输出全是5。
然后,同事说,有时候JS的for循环,永远只得到最后一个循环值,那其实他用的是for...in...循环。具体不多解释了。
我既然听了他的问题,就要给他解决方案。
我先建议他用自执行函数传参,这样自执行函数内部形成了局部作用域,不受外部变量变化的影响。范例代码是:
<script type="text/javascript">
var arr = [1,3,5,7,9];
var arrLength = arr.length;
for (var i = 0; i < arrLength; i++) {
(function(i) {
setTimeout(function() {
console.log('i是' + i);
console.log('value是' + arr[i]);
}, 2000);
})(i);
}
</script>
得到:
不但解决了undefined的问题,而且解决了异步函数传参的问题。
然后我把范例代码给了他。然而,他的JS代码写的太乱,拿这个例子改居然改不对。于是我又给了一个jQuery方案给他:
<script type="text/javascript">
var arr = [1,3,5,7,9];
$.each(arr, function(key, value) {
setTimeout(function() {
console.log('i是' + key);
console.log('value是' + value);
}, 2000);
});
</script>
用jQuery的$.each(),自带回调函数,形成了函数作用域,这娃最终解决了问题。