likes
comments
collection
share

js的事件循环机制搞不清,手把手梳理事件循环

作者站长头像
站长
· 阅读数 7

js的事件循环机制是面试中常见的js问题, 本文将带你彻底理清js事件循环机制中,代码究竟是按照怎么样的顺序运行的。

一、 js是单线程语言

JavaScript 是一种单线程编程语言,这意味着它在同一时间只能执行一个任务,或者说,在任何给定的时间里,JavaScript 引擎中只会有一个执行线程来处理JavaScript脚本。这个执行线程遵循从上至下逐行执行代码的原则。就和人一样当前时间你的注意力只能集中在一件事情上面,等到这件事情做完之后才会去做下一件事。

单线程好处

  1. 避免复杂的并发控制

    • 在多线程环境中,开发者需要处理线程间的通信、同步和锁定等并发问题,这会增加编程复杂性和出错的可能性。JavaScript通过单线程设计简化了这些问题,使得开发者无需关注这些复杂的并发控制机制。
  2. 提供一致的运行时行为

    单线程模型有助于确保程序的行为可预测,尤其是在面对共享状态时,能够保证执行顺序的一致性,减少因并发引起的不确定性。

    在浏览器环境中,JavaScript主要用于动态更新页面内容、处理用户交互和操作DOM(文档对象模型)。DOM API并非线程安全的,如果有多个线程同时修改DOM结构,将会导致不可预知的结果和严重错误。因此,通过单线程设计,JavaScript确保任何时候只有一个线程访问和修改DOM,从而避免了竞态条件和同步问题。

  3. 内存占用小,占用资源少:

    • 在资源有限的客户端环境中(如浏览器),如果为每一个JavaScript任务创建单独的线程,可能会导致资源消耗过大,影响性能和用户体验。

二、异步

有些代码执行时间特别长的话,就需要等待很久,比如说网络请求。但是像人一样,我们在处理一个大任务的时候,会先将那些不怎么需要时间的任务先处理完成,回过头来再接着处理那些比较麻烦的事情。js当中也有这样的机制也就是异步

js当中的异步机制是就是我们所说的事件循环机制来实现

三、 事件循环机制 Event Loop

事件循环是JavaScript执行上下文中的一种机制,用于处理异步操作。它的核心思想是将所有的异步任务放入一个队列中,然后按照队列中的顺序依次执行,直到队列为空为止。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

宏任务,微任务

按照代码的书写顺序逐行执行,不需要耗时的代码,(耗时是相对的),我们称为同步代码. 需要耗时的代码我们称为异步代码,异步代码分为,宏任务与微任务

  1. 宏任务是一类相对低优先级的异步任务,它们按照一定的顺序在一个或多个任务队列中排队等待执行,每个宏任务执行结束之后,才会进入下一个事件循环阶段。 常见的宏任务来源:
    1. setTimeout
    2. script的整体代码
    3. setInterval
    4. I/O操作
    5. UI渲染
    6. setImmediate()(node.js环境)
  2. 微任务- 宏任务是一类相对低优先级的异步任务,它们按照一定的顺序在一个或多个任务队列中排队等待执行,每个宏任务执行结束之后,才会进入下一个事件循环阶段。常见的微任务来源:
    1. Promise的then()、catch()、finally()方法
    2. async/await(其源码中也是靠Promise实现的,也就是Promise)
    3. promise.nextTick() (node.js环境)
    4. MutationObserver(浏览器环境)

执行顺序

  • 1.先执行同步代码,所有同步代码都在主线程上执行,形成一个执行栈
  • 2.当遇到异步任务时,会将其挂起并添加到任务队列中,宏任务放入宏任务队列,微任务放进微任务队列
  • 3.当执行栈为空时,事件循环从任务队列中取出一个任务,加入到执行栈中执行
  • 4.重复上述步骤,直到任务队列为空

js的事件循环机制搞不清,手把手梳理事件循环

例子

async function async1() {

console.log('async1 start');
  await async2()
  console.log('async1 end') 


}
async function async2() {
  console.log('async2 end') 
}

console.log('script start') 

async1()

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


new Promise(resolve => {
  console.log('Promise') 
  resolve()
})
  .then(function () {
    console.log('then1') 
  })
  .then(function () {
    console.log('then2') 
  })
console.log('script end') 

解析

首先开始执行script整段代码

  1. 执行同步代码console.log('script start')
  2. 发现函数async1,执行内部逻辑==注意async函数并不是微任务而是其await关键字后面代码==
    1. 执行同步代码console.log('async1 start');
    2. await关键字将后面所有代码放入微任务队列,微任务队列:async2(), console.log('async1 end')
  3. 发现setTimeout放入宏任务队列
  4. 执行newPromise构造函数中的逻辑
    1. 执行同步代码 console.log('Promise') resolve()
  5. 发现两个then,依次放入微任务队列
    • 此时微任务队列:
      1. async2(),
      2. console.log('async1 end')
      3. 第一个 then()
      4. 第二个then()
  6. 同步代码执行完毕,查看微任务队列
    • 依次执行:
      1. async2 end
      2. async1 end
      3. then1
      4. then2
  7. 开启下一轮事件循环,查看宏任务队列,执行
    • 结果: setTimeOut
  8. 打印结果

js的事件循环机制搞不清,手把手梳理事件循环

总结

概括 js是一门单线程语言,通过事件循环机制实现异步执行代码。

单线程的好处:

  1. 占用资源少,只有一个线程
  2. 相比于多线程,a.不用切换执行上下文,速度快;b.对于访问DOM结构获取变量的时候无需考虑复杂的并发

时间循环机制

  1. 宏任务:整个script代码、setTimeout、setInterval、I/O操作、UI渲染setImmediate(node.js)
  2. 微任务:Promise的then、catch、finally;async/await、promise.nextTick(node.js)、Mutation.Observe(浏览器)
  3. 执行顺序:宏任务->清空微任务队列->继续下一个宏任务,直到所有队列为空

本人目前在准备春招,也希望和一起准备春招的佬们互相交流一下,一起冲刺大厂,欢迎私信评论或加v:guanyiiu1314