一道JS基础题由浅入深(由输出01234谈到js运行机制)

题目一

说出下面代码输出的结果。

for(var i=0; i<5; i++){
    setTimeout(function(){
        console.log(i);
    }, 3000);
}

回答:55555。

说一下,为什么?

解答:因为这里for中的var声明是全局的。

运行这段代码,会往异步事件表(异步Event Table)中注册5个 “过3秒后运行的function(){console.log(i);}任务” 。直到同步队列(主队列)中的任务执行完成后,此时主队列为空,才会去检查异步事件表。按顺序按照对应的时间等待(这里都是3秒)结束后,才注册到异步任务队列中(也成等待队列),按照队列先进先出的原则,从异步任务队列不断取任务出来并放入到主队列中并由主线程来执行。此时在全局中,i的值为5,因此取出来的5个异步任务中运行结果都是打印5。

那你说说,既然js是单线程的,它又是如何实现异步的呢?

js中的异步以及多线程都可以理解成为一种“假象”,就拿h5的WebWorker来说,子线程有诸多限制,不能控制DOM,不能修改全局对象等等,通常只用来做计算做数据处理。
这些限制并没有违背js是单线程语言的思想,所以说是“假象”。

总的来说,JS异步的执行机制其实就是事件循环(eventloop),理解了eventloop机制,就理解了js异步的执行机制。

在这里插入图片描述

好,你提到了JS的事件循环(eventloop),你能说一下它是怎么运作的

  1. 首先判断JS是同步还是异步,同步就进入主线程运行,异步就进入event table.
  2. 异步任务在event table中注册事件,当满足触发条件后,(触发条件可能是延时也可能是ajax回调),被推入event queue
  3. 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中。
  4. 每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是优先级比宏任务高,且与微任务所处的代码位置无关。

img

题目二

for主体和seTimeout条件不变的前提下,那如果我要输出01234呢?

  • 使用自执行函数实现闭合作用域
for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 3000);
    })(i);
}
  • 直接用let,块级作用域,使每次注册异步任务时使用的i都是当前块内的值。
for(let i=0; i<5; i++){
    setTimeout(function(){
        console.log(i);
    }, 3000);
}
  • 其实和上面道理类似,使用了function实现闭合作用域。
var output01 = function(i){
    setTimeout(function () {
        console.log(i);
    }, 3000);
}
for (var i = 0; i < 5; i++) {
    output01(i);
}

  • 与上同理,闭合作用域
var output02 = (i)=>{
    setTimeout(function () {
        console.log(i);
    }, 3000);
}
for (var i = 0; i < 5; i++) {
    output02(i);
}

题目三

那如果我要输出012345呢?

  • 道理同上,先输出01234,再输出5
for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 500 * j);
    })(i);
}
setTimeout(function () {
    console.log(i);
}, 500 * i);
  • 这是比较骚的实现方法,使用promise.all(),等待所有异步任务执行完成后,才输出5
const tasks = [];
for (var i = 0; i < 5; i++) {
    (
        (j) => {
            tasks.push(new Promise((resolve) => {
                setTimeout(() => {
                    console.log(j)
                    resolve();
                }, 500 * j)
            }))
        }
    )(i);
}
// 等待所有异步任务执行完成
Promise.all(tasks).then(() => {
    setTimeout(function () {
        console.log(i);
    })
})
  • 道理同上
const tasks = []
var output03 = function(i){
    new Promise((resolve)=>{
        setTimeout(function(){
            console.log(i);
        }, 500*i)
    })
}

for(var i=0;i<5;i++){
    tasks.push(output03(i));
}

Promise.all(tasks).then(()=>{
    setTimeout(function(){
        console.log(i)
    }, 500*i)
})

结语

这个Promise和setTimeout这块确实比较绕,不过只要遵守事件队列先进先出的原则来分析之,总能慢慢分析清楚,有空我将做一做这方面的笔记,并分享之。

参考资料

《js执行机制》 https://www.jianshu.com/p/1368d375aa66

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值