浏览器中的Event Loop

JavaScript 语言的一大特色是单线程。前端

线程和进程

  • 线程是 cpu 调度的最小单位(线程是进程上的一次程序运行单位,一个进程能够有多个线程)。
  • 进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位)。

浏览器是多进程的

每开一个标签页系统就建立一个独立的进程,一个进程中可能包含如渲染线程、JS 引擎线程、HTTP 请求线程等。
注意:因为浏览器的优化机制某些进程可能会被合并。面试

为何JavaScript是单线程的

若是 JavaScript 是多线程的,假定 procces1 和 procces2 同时对同一个 DOM 做出操做,浏览器应该以哪个为准,因此 JavaScript 只能为单线程。浏览器

事件循环

任务分为同步任务和异步任务,同步任务会进入主线程,异步任务则会进入事件队列 ( Event Queue )。主线程中的任务执行完毕后会从事件队列中取出放入执行栈。bash

任务除了分为同步任务和异步任务,还分为微任务和宏任务。多线程

  • 微任务(microtask)包括:process.nextTick,Promise,MutationObserver
  • 宏任务(macrotask)包括:主代码script,setTimeout,setInterval

事件循环的执行顺序为异步

  • 事件循环开始,script 代码块作为宏任务进入主线程执行
  • 同步任务移入主线程执行,异步任务进入事件队列
  • 主线程执行完成后,取出事件队列中的微任务执行
  • 微任务执行完毕后,事件循环结束
  • 取出事件队列中的宏任务,开始新一轮事件循环
  • ......

示例:函数

setTimeout(() => {
  new Promise((resolve) => {
    console.log('1')
    resolve()
  }).then(() => {
    console.log('2')
  })
  console.log('3')
}, 0)

console.log('4')

new Promise(resolve => {
  console.log('5')
  resolve();
}).then(() => {
  console.log('6')
})

setTimeout(() => {
  new Promise((resolve) => {
    console.log('7')
    resolve();
  }).then(() => {
    console.log('8')
  })
  console.log('9')
}, 0)

复制代码

分析:优化

  • 第一轮事件循环:ui

    • script 做为宏任务进入主线程
    • 遇到 setTimeout,注册其回调函数做为宏任务移入事件队列,记为 setTimeout1
    • 遇到 console.log,输出'4'
    • 遇到 Promise,输出'5',注册其回调函数做为微任务进入事件队列,记为 then
    • 遇到 setTimeout,注册其回调函数做为宏任务移入事件队列,记为 setTimeout2
    • 宏任务执行完毕,执行事件队列中的微任务 then,输出'6'

    第一轮事件循环结束输出'4'、5'、'6'spa

  • 第二轮事件循环:

    • 执行宏任 setTimeout1
    • 遇到 Promise,输出'1',注册其回调函数做为微任务进入事件队列,记为 then
    • 遇到 console.log,输出'3'
    • 宏任务执行完毕,执行事件队列中的微任务 then,输出'2'

    第二轮事件循环结束输出'1'、'3'、'2'

  • 第三轮事件循环:

    • 遇到 Promise,输出'7',注册其回调函数做为微任务进入事件队列,记为 then
    • 遇到 console.log,输出'9'
    • 宏任务执行完毕,执行事件队列中的微任务 then,输出'8'

    第三轮事件循环结束输出'7'、'9'、'8'

    代码执行完毕输出结果为'4'、5'、'6'、'1'、'3'、'2'、'7'、'9'、'8'

参考:前端面试之道

相关文章
相关标签/搜索