for + setTimeout用到了真实场景

169 阅读2分钟

这道题目大家应该都很熟悉了,一般会用来考察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 函数的参数

真实场景

image.png

在开发的时候,有一个场景是需要点击‘下拉查看详情’,直接下拉一部分(如上图右边),因为我手指下拉是根据下拉距离进行渐变动效(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毫秒执行完一个下拉的过程, 具体执行动画的时间可以自行设置,以达到人操作的一个效果。

没想到看似完全没用的面试题目会用到真实的开发场景中。