了解事件循环机制 =>更好的阅读代码

前言

我们在之前在从回调地狱到 Promise:实现优雅的异步处理这篇文章中聊到了异步这个概念,我们可以主动控制事件的执行顺序,那么今天我们就来看看在人为不干预的情况下,事件执行的机制,让你更好的阅读代码。

进程和线程

在聊执行机制之前,我们需要知道几个基础概念

  1. 进程:CPU 在运行指令和保存上下文所需要的时间。- 进程之间相互不影响
  2. 线程:执行一段指令所需要的时间

一个进程可能会有多个线程

比如:浏览器每开一个type页就是新开一个进程,此时可能会有

  1. 渲染线程
  2. js引擎线程
  3. http线程

唯独渲染线程和js引擎线程不能同时工作的,是互斥的,因为渲染操作和JavaScript执行都需要访问DOM(文档对象模型)并更新页面,浏览器通常会将这两个线程串行化,以避免冲突。

我们在前面说过Js是单线程语言,但是今天要补充的是在默认情况v8引擎在运行js代码时,这个进程中只有一个线程会被开启,除非人为再开辟,是可以变成多线程的,想知道的伙伴自行去可以查阅资料。

异步和同步代码

我们之前提到过异步,将要耗时的代码说是异步代码是片面的也是不对的,今天要纠正,同时认识一下异步代码。

异步代码分为两种:

微任务:

  • promise.then(),

  • process.nextTick() 是Node.js 中的一种方法,浏览器没有

  • MutationObserver()

宏任务:

  • script标签,

  • setTimeout(),

  • setInterval(),

  • setImmediate(),

  • I/O,

  • UI-rendering

与异步代码相对应的是同步代码,差不多除了上述所说的都是同步代码,耗时的代码一定不是同步代码,不耗时的代码不一定是同步代码

for循环执行时虽然可能会很耗时,但是它就是同步代码。

event-loop

简单的说是v8按照先执行同步,再执行异步的策略,反复重复,但是要更深层一点的话 eventLoop步骤是

  1. 执行同步代码(这属于宏任务)
  2. 执行完同步代码后,检查是否有异步代码要执行
  3. 执行所有的微任务
  4. 如果有需要就渲染页面
  5. 执行宏任务,也是开启了下一次事件循环

小考点:微任务一定在宏任务前执行吗???

不是的,第一次执行的宏任务在微任务前,第一次事件循环的结尾就是第二次事件循环的开头

js 复制代码
console.log(1);//1

new Promise(function (resolve, reject) {
  console.log(2);//2
  resolve()
})
  .then(() => {
    console.log(3);//4
    setTimeout(() => {
      console.log(4);//6
    }, 0)
  })
  
setTimeout(() => {
  console.log(5); //5
  setTimeout(() => {
    console.log(6);//7
  }, 0)
}, 0)

console.log(7);//3

//1 2 7 3 5 4 6

我们来细细分析这段代码用上述逻辑是怎么走的:

从上往下走,第1行是同步代码直接执行输出1,(1)

第3-6行是创建promise实例对象,直接执行输出2,(1,2)

同时第5行改变了promise的状态,从Pending 变为 Fulfilled,让其可以继续走.then方法,但是.then方法是异步代码中的微任务,就让7-12行暂且挂起在微任务队列中,我们称其微任务1

继续走,看到第14行有setTimeout(),它属于异步任务中的宏任务,将14-19行挂起在宏任务队列中,我们称其宏任务2

到21行同步代码直接执行,输出 7,(1,2,7) 此时执行完了所有的同步代码,

按照eventLoop步骤2,开始先执行异步代码中的微任务,在微任务队列中找到了微任务1,第8行是同步代码直接输出3(1,2,7,3)

到第9行是异步代码中的宏任务,将9-12行挂起放进宏任务队列中宏任务1 后,我们称其为宏任务2,微任务队列空了,(步骤3)微任务执行完毕。

步骤4暂且不谈,到步骤5执行宏任务,此时宏任务队列中,有宏任务1和宏任务2,队列是一个先进先出的数据结构 ,因此宏任务1先执行,执行15行输出5(1,2,7,3,5),

随后又遇到宏任务放进宏任务队列,我们称其宏任务3 ,此时要继续执行宏任务队列中的宏任务2,执行第10行,输出4(1,2,7,3,5,4),为了清空宏任务队列,继续执行宏任务3,执行17行输出6(1,2,7,3,5,4,6),所有代码执行完毕。

事件循环小练习

给你们找了一个题目来练练手,能输出正确结果的小伙伴那你对时间循环机制的了解就差不多啦

js 复制代码
console.log('script start');//1
async function async1() {
  await async2()  //await 后面的代码当成同步来执行
  console.log('async1 end');//5   被挤进了微任务队列
}
async function async2() {
  console.log('async2 end');//2
}
async1()
setTimeout(() => {
  console.log('setTimeout');//8
}, 0)
new Promise((resolve, reject) => {
  console.log('promise');//3
  resolve()
})
  .then(() => {
    console.log('then1');//6
  })
  .then(() => {
    console.log('then2');//7
  });
console.log('script end');//4

小伙伴们可以对照着eventloop步骤自己动动手,输出是

script start

async2 end

promise

script end

async1 end

then1

then2

setTimeout

这里有一个小知识点,不知道的同学就会犯错

  1. 浏览器对await执行提前了 (await 后面的代码当成同步来执行)
  2. 会将后续(下面)代码挤进微任务队列

拓展

setTimeout()定时器执行的时间准吗?

当settimeout被执行时,浏览器会启动一个新的线程来计时,等到计时结束才将定时器的回调取出来执行(js主线程将其取出),如果此时js主线程还在执行同步代码,那么该回调就会被一直挂起,直到同步执行完毕,微任务也执行完毕,才执行该回调。取平均约等于3s误差。

欢迎大家的交流与指正! 看到这里了,就不妨动动手点个赞吧,谢谢大家

相关推荐
高山我梦口香糖10 分钟前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_7482352413 分钟前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240251 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar1 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人2 小时前
前端知识补充—CSS
前端·css
GISer_Jing2 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245522 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v2 小时前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing2 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
2401_857600952 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js