JavaScript 事件循环机制基本介绍

参考链接:

https://juejin.cn/post/6844903621872582669

https://juejin.cn/post/6844903638238756878

1. 单线程的js

js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。

在js高程中举过一个栗子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?

为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。

2. 执行栈和任务队列

因为js是单线程语言,当遇到异步任务(如ajax操作等)时,不可能一直等待异步完成,再继续往下执行,在这期间浏览器是空闲状态,显而易见这会导致巨大的资源浪费。

(1)执行栈

当执行某个函数、用户点击一次鼠标,Ajax完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取,遵循先进先出原则。

执行任务队列中的某个任务,这个被执行的任务就称为执行栈。

(2)主线程

要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。

主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。

当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。

当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。

(3)同步任务与异步任务

在这里插入图片描述

  1. 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册其回调函数
  2. 当指定的事情完成时,Event Table会将这个回调函数移入Event Queue。
  3. 主线程内的任务执行完毕为空,会去Event Queue读取对应的回调函数,进入主线程执行。
  4. 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

纯同步任务:

console.log('start')

console.log('end')

上边的执行结果大家肯定都明白,先输出start,再输出end,这一段代码会进入同步队列,顺序执行。

同步+异步:

console.log('start')

setTimeout(function() {
    console.log('setTimeout')
}, 0)

console.log('end')

注意:

注意:setTimeout的延迟时间表示的是xxx时间后将回调函数添加到异步队列中,而不是xxx时间后执行回调函数,所以它的定时并不精确。假设setTimeout规定2秒后执行,但同步队列中有一个函数,执行花了很长时间,甚至花了1秒。那么这时setTimeout中的回调也会等上至少1秒之后,同步任务都执行完了,再去执行。这时候的setTimeout回调执行的时机就会超过2秒,也就是至少3秒。

这样的情况,先执行同步队列任务,函数调用栈执行到setTimeout时,setTimeout会在规定的时间点将回调函数放入异步队列,等待同步队列的任务被执行完,立即执行setTimeout回调,所以结果是:start、end、setTimeout。

3. 宏任务与微任务

微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢?

其中宏任务(task)包括:

  • script(整体代码)
  • setTimeout, setInterval, setImmediate(属于node),
  • I/O
  • UI rendering

ajax请求不属于宏任务,js线程遇到ajax请求,会将请求交给对应的http线程处理,一旦请求返回结果,就会将对应的回调放入宏任务队列,等请求完成执行。

微任务(jobs)包括:

  • process.nextTick(属于node)
  • Promise
  • Object.observe(已废弃)
  • MutationObserver(html5新特性)

注意:这里的宏/微任务大多都表示的都是对应类型操作的回调函数,而Promise的微任务对应的不是其直接的回调函数,而是Promise.then回调

在这里插入图片描述
示例1:

console.log('start')

setTimeout(function() {
    console.log('timeout')
}, 0)

new Promise(function(resolve) {
    console.log('promise')
    resolve()
}).then(function() {
    console.log('promise resolved')
})

console.log('end')

执行过程解析:

  1. 建立执行上下文,进入执行栈开始执行代码,打印start
  2. 往下执行,遇到setTimeout,将回调函数放入宏任务队列,等待执行
  3. 继续往下,有个new
    Promise,其回调函数并不会被放入其他任务队列,因此会同步地执行,打印promise,但是当resolve后,.then会把其内部的回调函数放入微任务队列
  4. 执行到了最底部的代码,打印出end。这时,主执行栈清空了,开始寻找微任务队列里有没有可执行代码
  5. 发现了微任务队列中有之前放进去的代码,执行打印出promise resolved,第一次循环结束
  6. 再开始第二次循环,从宏任务开始,检查宏任务队列是否有可执行代码,发现有一个,打印timeout

所以,打印顺序是:start–>promise–>end–>promise resolved–>timeout

示例2:

console.log('第一次循环主执行栈开始')

setTimeout(function() {
    console.log('第二次循环开始,宏任务队列的第一个宏任务执行中')
    new Promise(function(resolve) {
        console.log('宏任务队列的第一个宏任务的微任务继续执行')
        resolve()
    }).then(function() {
        console.log('第二次循环的微任务队列的微任务执行')
    })
}, 0)

new Promise(function(resolve) {
    console.log('第一次循环主执行栈进行中...')
    resolve()
}).then(function() {
    console.log('第一次循环微任务,第一次循环结束')
    setTimeout(function() {
        console.log('第二次循环的宏任务队列的第二个宏任务执行')
    })
})

console.log('第一次循环主执行栈完成')
  • 第一次循环

1:进入执行栈执行代码,打印第一次循环主执行栈开始

2:遇到setTimeout,将回调放入宏任务队列等待执行

3:promise声明过程是同步的,打印第一次循环主执行栈进行中…,resolve后遇到.then,将回调放入微任务队列

4:打印第一次循环主执行栈完成

5:检查微任务队列是否有可执行代码,有一个第三步放入的任务,打印第一次循环微任务,第一次循环结束,第一次循环结束,同时遇到setTimeout,将回调放入宏任务队列

  • 第二次循环

1:从宏任务入手,检查宏任务队列,发现有两个宏任务,分别是第一次循环第二步和第一次循环第五步被放入的任务,先执行第一个宏任务,打印第二次循环开始,宏任务队列的第一个宏任务执行中

2:遇到promise声明语句,打印宏任务队列的第一个宏任务继续执行,这时候又被resolve了,又会将.then中的回调放入微任务队列,这是这个宏任务队列中的第一个任务还没执行完

3:第一个宏任务中的同步代码执行完毕,检查微任务队列,发现有一段第二步放进去的代码,执行打印第二次循环的微任务队列的微任务执行,此时第一个宏任务执行完毕

4:开始执行第二个宏任务,打印第二次循环的宏任务队列的第二个宏任务执行,所有任务队列全部清空,执行完毕

示例3:

在这里插入图片描述

这里的await和await下面的代码可看作是:

new Promise(console.log(‘async2’)).then(()=>{console.log(‘async1 end’)})

在这里插入图片描述
在这里插入图片描述

核心思想

  1. 执行完主执行线程中的任务也就是执行宏任务(第一次循环时基本执行的宏任务都是script同步代码)。
  2. 当前次循环执行过程中遇到异步宏任务则扔到宏任务队列(下一次循环开始时要执行),遇到异步微任务则扔到微任务队列中(当前次宏任务执行完毕后执行)
  3. 执行完当前次宏任务后执行微任务(如果有的话)
  4. 开启下一次循环,从宏任务开始(即重复1-3)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值