这道题目大家应该都很熟悉了,一般会用来考察js执行机制,闭包,块级作用域等知识点,以前觉得这道题目单纯就是八股文,没想到在现实中的开发中,可以用来解决需求。先带大家回顾一下这道题目,后面会说到真实场景。
for(var i = 0;i<3;i++){
setTimeout(function(){
console.log(i)
},1000)
}
打印结果 333
为什么?
因为 for 循环会先执行完(同步优先于异步优先于回调),这时候 i = 3, 并且五个 setTimeout 的回调全部塞入了事件队列中,然后 1 秒后一起执行。
怎样让setTimeout可以按正常逻辑输出123?
解决方案一 闭包
for (var i=1; i<=3; i++) {
(function(j) {
setTimeout( function () {
console.log(j)
}, j*1000 )
})(i);
}
打印结果 1 2 3
代码中用了 立即执行函数 ,其作用:创建一个独立的作用域,防止变量污染。
通过闭包,这样 console.log(j) 中的 i 就保存在每一次循环生成的立刻执行函数中的作用域里了。
如果还是不理解,我们再看个例子
for (var i = 1; i <= 3; i++) {
setTimeout((function(i) {
console.log(i) // ㊀
return function() {
console.log('回调')
console.log(i) // ㊁
}
})(i), i * 1000)
}
打印结果 1 2 3 回调 1 回调 2 回调 3
等同于在㊀处的时候,当前作用域里就存储了这个 i 值,输出的时候先先在当前作用域的里找 i 值,找到了就直接输出
解决方案二 let
for (let i=1; i<=3; i++) {
setTimeout( function () {
console.log(i);
}, i*1000 );
}
打印结果 1 2 3
let 为代码块的作用域,所以每一次 for 循环,console.log(i); 都引用到 for 代码块作用域下的i,因为这样被引用,所以 for 循环结束后,这些作用域在 setTimeout 未执行前都不会被释放。
解决方案三 setTimeout第三个参数
for (var i = 1; i <= 3; i++) {
setTimeout((i) => console.log(i),1000,i);
}
打印结果 1 2 3
setTimeout(func, delay, param1, param2, ...) 第三个参数及以后的参数都可以作为 func 函数的参数
真实场景
在开发的时候,有一个场景是需要点击‘下拉查看详情’,直接下拉一部分(如上图右边),因为我手指下拉是根据下拉距离进行渐变动效(opacity,transform等属性设置),如果是直接点击的时候设置下拉的距离(即this.$refs.xx.distance = XX),则没有一个动效的过程而是直接一个模块出来,会显的很突兀。
所以为了模拟下拉动效,我随机取了几个下拉距离的值
const _that = this
const distanceArr = [1, 10, 24, 35, 44] // 下拉过程中的值
for (var i = 0; i < 6; i++) {
(function (j) {
setTimeout(function () {
_that.$refs.xx.distance = j
}, i * 100)
})(arr[i])
}
假设数组最后一个值44代表动效完成时下拉距离值,即通过500毫秒执行完一个下拉的过程, 具体执行动画的时间可以自行设置,以达到人操作的一个效果。
没想到看似完全没用的面试题目会用到真实的开发场景中。