前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂javascript事件循环原理?「前端每日一题v22.11.16」

一文搞懂javascript事件循环原理?「前端每日一题v22.11.16」

作者头像
FE情报局
发布2022-12-05 10:20:04
2660
发布2022-12-05 10:20:04
举报
文章被收录于专栏:FE情报局FE情报局

一文搞懂javascript事件循环原理?「前端每日一题v22.11.16」

了解javascript的第一步,就是要了解事件循环机制。但是要真正的了解javascript的事件循环机制并不容易,因为它是javascript引擎最基础的部分。它可以让单线程的javascript以非阻塞方式执行

事件循环机制Event Loop,要真正了解这些,我们可能需要知道javascript引擎相关内容,比如调用栈,宏任务,微任务,任务队列等相关,一起看一下这些概念

调用栈

调用栈是一种跟踪javascript代码执行的数据结构,它是一个栈,因此遵循先进后出的数据结构,执行的每个函数都表示为调用栈中的一个帧,并放在前一个函数的顶部

举个例子,这是最常见的一段javascript代码

代码语言:javascript
复制
function foo(){
  console.log('foo')
  bar()
}

function bar(){
  console.log('bar')
}

foo()

我们详细的讲述一下上述代码的执行流程

  1. 调用栈最初是空的,忽略前面所有的函数,直到foo被调用
  2. 函数foo被压入调用栈,调用栈列表为foo
  3. 执行foo中的代码console.log,栈列表添加console.log函数,这个函数执行完就会立刻被推出栈,所以栈列表还是foo
  4. 代码执行到bar时候,该函数被调用
  5. bar函数被压入调用栈,调用栈列表为bar-foo
  6. 执行bar中的代码,直到全部执行完毕,调用栈列表没变
  7. 返回来继续执行foo函数中bar之后的代码
  8. 删除调用栈列表中bar函数,调用栈列表为foo
  9. foo中的bar函数后续没有可执行代码
  10. 删除调用栈列表中的foo,此时调用栈列表为空

一开始我们会往空的调用栈中推入函数,执行完函数代码之后,调用栈又会移除这个函数,最终又会得到一个空的调用栈,如果在执行过程中,

任务和任务队列

上面的案例主要是针对javascript单线程的执行方式,但是这种方式是不可取的,比如如果我们碰到了定时器,或者请求数据等执行时间比较长的代码的话,后面的逻辑就得等这些代码执行完成才能进行下一步

所以就有了任务队列的概念

任务分为同步任务和异步任务,区别就是同步任务执行后立刻就能取到结果,异步任务需要等一会儿才能取到结果

同步任务执行按照调用栈执行逻辑,执行完成移出调用栈,异步任务执行也会进入调用栈,不同的是它会将其回调函数或者回调任务放入一个任务队列,任务队列遵循先进先出的原则,放入任务队列的函数不会立刻执行,需要等待调用栈中同步的任务执行完成

当调用栈清空之后,也就是所有同步任务结束之后,解释器开始读取任务队列执行,将「已经完成」的异步任务放入调用栈执行

看代码

代码语言:javascript
复制
console.log('foo')

setTimeout(() => {
  console.log('bar')
}, 1000)

console.log('loo')
  1. console.log('foo')压入调用栈,执行完成推出调用栈
  2. setTimeout压入调用栈
  3. 浏览器的定时线程会等时间结束后将setTimeout中的回调函数箭头函数放入任务队列
  4. setTimeout推出调用栈
  5. console.log('loo')压入调用栈,执行并推出
  6. 此时同步任务全部执行完成,调用栈为空
  7. 1s过后,浏览器的定时线程将匿名函数放入任务队列
  8. 调用栈为空,执行任务队列匿名函数内容
  9. console.log('bar')执行
  10. 匿名函数推出调用栈
  11. 调用栈为空

宏任务和微任务

前面说到的任务队列,其实还分为宏任务和微任务,这两种都属于异步任务,它们的区别就在于它们的执行顺序。

为什么会区分宏任务和微任务,最主要的一点就是如果任务队列中某一个任务需要1个小时,后续的任务都需要等待一个小时,这显然是不合理的,所以宏任务和微任务最主要的一个目的就是区分任务的优先级,微任务优先级高于宏任务

「宏任务」

  • 整体代码都属于宏任务
  • UI交互事件
  • I/O
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

「微任务」

  • process.nextTick
  • MutationObserver
  • Promise.then catch finally
代码语言:javascript
复制
console.log('foo')

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

new Promise((res) => {res(1)}).then((d) => {console.log(d)})

顺序为foo、1、bar

事件循环

上面的内容结合起来,基本上就是整个事件循环的机制了

  1. 先从调用栈开始
  2. 同步任务压入调用栈,开始执行
  3. 碰到异步任务,将有结果的回调压入任务队列,注意区分宏任务和微任务
  4. 调用栈清空之后,微任务队列按照先入先执行顺序开始执行
  5. 微任务队列清空之后,一个循环结束
  6. 进入宏任务队列直到宏任务队列清空
代码语言:javascript
复制
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
    console.log('promise2')
}).then(function () {
    console.log('promise3');
});
console.log('script end');

按照以上逻辑,做完这个题就可以出师了,以后碰到所有的这种执行顺序的都不怕了

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FE情报局 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 调用栈
  • 任务和任务队列
  • 宏任务和微任务
  • 事件循环
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档