JavaScript事件循环机制

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。

  • 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。

  • 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

  • macro-task大概包括:script(整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering。

  • micro-task 大概包括:process.nextTick, Promise, Object.observe(已废弃),MutationObserver(html5新特性)。

  • setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

     // setTimeout中的回调函数才是进入任务队列的任务
     setTimeout(function() {
         console.log('xxxx');
     })
     // setTimeout作为一个任务分发器,这个函数会立即执行,而它所要分发的 任务,也就是它的第一个参数,才是延迟执行
    
  • 来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

  • 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。

  • 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

例如:

console.log('golb1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})

上例执行流程如下:
一、宏任务script首先执行。全局入栈。glob1输出。
在这里插入图片描述
二、执行过程遇到setTimeout。setTimeout作为任务分发器,将任务分发到对应的任务队列中。
在这里插入图片描述
三、执行过程中遇到setImmediate。setImmediate也是一个宏任务分发器,将任务分发到对应的任务队列中。setImmediate的任务队列会在setTimeout队列的后面执行。
在这里插入图片描述
四、执行遇到nextTick,process.nextTick是一个微任务分发器,它会将任务分发到对应的微任务队列中去。
在这里插入图片描述
五、执行遇到Promise。Promise的then方法会将任务分发到对应的微任务队列中,但是它构造函数中的方法会直接执行。因此,glob1_promise会第二个输出。
在这里插入图片描述

在这里插入图片描述

六、执行遇到第二个setTimeout。
在这里插入图片描述

七、先后遇到nextTick与Promise
在这里插入图片描述

八、再次遇到setImmediate。
在这里插入图片描述
这个时候,script中的代码就执行完毕了,执行过程中,遇到不同的任务分发器,就将任务分发到各自对应的队列中去。接下来,将会执行所有的微任务队列中的任务。

当所有的微任务执行完毕之后,这一轮循环就表示结束了。下一轮循环继续从宏任务队列开始执行。

这个时候,script已经执行完毕,所以就从setTimeout队列开始执行。
在这里插入图片描述

执行结果如下:
在这里插入图片描述

执行流程总结: 首先执行全局script —> 任务分发 —> 执行所有微任务 (此时一轮循环结束)—> 执行宏任务中的一个 —> 分发任务 —> 执行所有微任务(第二轮循环结束)-------继续如此循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值