setTimout-闭包-作用域-任务队列

  1. 请看下面这道题

    function show(){
        for(var i=0;i<5;i++){
            setTimeout(()=>{console.log(i)},0)
        }
    }
    show()
    console.log('start')

    输出结果:start

    5 5 5 5 5

    原因:setTimout的第一个参数属于异步函数,会被存到宏任务队列中,当执行栈执行完毕之后才会执行宏任务队列,又由于var定义的i这个变量时没有作用域块这个概念的,所以在执行栈中一直被++。

  2. 如何让这个函数,按正常顺序输出1 2 3 4 5

2.1这是面试中被经常被问到的,解决办法:使用闭包,并且让闭包立即执行。

function show(){
    for(var i=0;i<5;i++){
        let fn=function(){
            setTimeout(()=>{console.log(i)},0)
        }
        fn(i)
    }
}
show()
console.log('start')
    ```

输出结果:start   0 1 2 3 4

这个里面的闭包函数也可以换个写法,网上常见的:

```javascript
function show(){
    for(var i=0;i<5;i++){
        (function(i){
            setTimeout(()=>{console.log(i)},0)
        }).(i)
    }
}

原理解释:闭包的作用就是,让外部函数可以调用内部函数中的变量。深层一点理解:闭包的作用可以将一个函数中的数据存储在这个函数的上下文环境中,当外部执行执行闭包函数时,任然可以拿到这个原始的数据。执行完毕之后这个数据就被清掉。例子:A函数中定义了B函数并且返回了B函数,那么不管B函数在哪里被调用或如果被调用,它都会保留A函数的作用域。

解释:执行循环的时候,每次碰到setTimeout函数,都会将()=>console.log(i)这行代码给压到任务队列中,最后执行,但是由于这边写的闭包函数时立即执行的,所有i这个变量就被存储到了准确的上下文环境中。这里有五次循环,第一次循环 i=1被保存到上下文环境中,因为i是在setTimeout中才被引用的,同时setTimeout的被压到执行队列。第二次 i=2 被保存到上下文环境中,setTimout被压到执行队列中,如此重复五次。当第一个setTimout被执行的时候,i被准确保存在对应的上下文环境中,所以可以console.log(1),如此重复五次。

2.1 解决方案将 var 改为let

function show(){
    for(let i=0;i<5;i++){
        setTimeout(()=>{console.log(i)},0)
    }
}
show()
console.log('start')

输出结果:

start 0 1 2 3 4

在解释原因之前,let与var的区别有一个非常重要的一点就是:let有块级作用域,并且let在for循环中还有个特殊的行为,这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量,比方5次循环,i会被5次重新声明初始化

for(let i=0;i<5;i++){
    console.log(i)
}

本质上是以下代码:

{
    let i=0
    console.log(i)
    let tem=i+1
    {
        let i=tem
        console.log(i)
        tem=i+1

        {
            let i=tem
            console.log(i)
            tem=i+1

            {
                let i=tem
                console.log(i)
                tem=i+1 

                {
                    let i=tem
                    console.log(i)
                }
            }
        }
    }
}

所以对应的let 方案的本质代码如下:

{
    let i=0
    console.log(i)
    let tem=i+1
    {
        let i=tem
        setTimeout(()=>{console.log(i)},0)
        tem=i+1

        {
            let i=tem
            setTimeout(()=>{console.log(i)},0)
            tem=i+1

            {
                let i=tem
                setTimeout(()=>{console.log(i)},0)
                tem=i+1 

                {
                    let i=tem
                    setTimeout(()=>{console.log(i)},0)
                }
            }
        }
    }
}

执行setTimout的第一个参数,它都会去自己原先的作用域中对应的作用域链中往上找对应的i值,如果离自己最近的一个作用域中没有i值,就会忘上一个作用域中找,遍历作用域链,知道找到为止,如果没有找到,在非严格模式下,全局自己定义一个i,返回undefined

{
    let i=0
    console.log(i)
    {
       setTimeout(()=>{console.log(i)},0)
       i++
       {
        setTimeout(()=>{console.log(i)},0)
        i++

        {
            setTimeout(()=>{console.log(i)},0)
            i++

            {
                setTimeout(()=>{console.log(i)},0)
                i++

                {
                    setTimeout(()=>{console.log(i)},0)
                }
            }
        }
       }
    }
}

这种写法的结果:输出:4 4 4 4 4

这种写法的执行顺序是:let i=0 ,然后 i++ i ++ i++ i++ i++ 最后执行setTimeout,setTimouet在自己本身的作用域中找不到i的声明回往上找到,所以最终所有的setTimeout都找到同一i身上,且值为4

参考:

https://www.jianshu.com/p/3e482748369d?from=groupmessage

全部评论

相关推荐

07-17 11:50
门头沟学院 Java
投递腾讯等公司10个岗位
点赞 评论 收藏
分享
来个厂收我吧:首先,市场侧求职我不是很懂。 但是,如果hr把这份简历给我,我会觉得求职人不适合做产品经理。 问题点: 1,简历的字体格式不统一,排版不尽如人意 2,重点不突出,建议参考star法则写个人经历 3,印尼官方货币名称为印度尼西亚卢比(IDR),且GMV690000印尼盾换算为305人民币,总成交额不高。 4,右上角的意向职位在发给其他公司时记得删除。 5,你所有的经历都是新媒体运营,但是你要投市场营销岗位,jd和简历不匹配,建议用AI+提示词,参照多个jd改一下经历内容。 修改建议: 1,统一字体(中文:思源黑体或微软雅黑,英文数字:time new romans),在word中通过表格进行排版(b站学) 2,校招个人经历权重:实习经历=创业经历(大创另算)>项目经历>实训经历>校园经历 3,请将项目经历时间顺序改为倒序,最新的放最上方。 4,求职方向不同,简历文字描述侧重点也需要不同。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务