js单线程异步+事件循环

54 阅读5分钟

进程:

每个应用程序运行都需要一段内存空间,可以把这一块内存空间理解为进程

线程:

有了内存空间,一个进程会自动创建一个线程运行程序,线程负责运行程序,这个线程是主线程。一个程序里面,可能同时需要执行多段代码,主线程就会启动更多的线程。一个进程里面可以有多个线程。

浏览器进程和线程

浏览器是一个多进程多线程的程序。

  • 浏览器进程
    -主要负责和用户交互,界面显示,子进程管理等。比如用户的点击,滚动事件。浏览器进程内部会启动多个线程完成任务。
  • 网络进程
    -负责加载网络资源
  • 渲染进程
    浏览器的渲染进程开启之后。会开启一个渲染主线程,负责执行html,css,js代码。 默认情况下,一个标签页就是一个渲染进程。

渲染主线程的任务:

  • 解析HTML
  • 解析CSS
  • 计算样式
  • 布局
  • 处理图层
  • 每秒把页面画60次
  • 执行全局js代码
  • 执行事件处理函数
  • 执行计时器的回调函数

事件循环

  1. 最开始的时候,渲染主线程会开启无限循环
  2. 每一次循环,都会判断消息队列中是否有任务存在,如果有,就执行取出第一个任务执行。执行完以后,进入下一次循环,没有的话,就会进入休眠状态。
  3. 其他所有线程可以随时向消息队列里面添加任务,新任务会加入消息队列的末尾。如果是休眠状态,会唤醒主线程,继续循环执行任务。 整个过程,就是事件循环(消息循环)

异步

代码在执行的过程中,会遇到一些无法立即执行的任务:

  • 计时器完成后需要执行的任务
  • 网络通信完成后需要执行的任务
  • 用户操作后需要执行的任务**。
    如果等待这些任务执行的时机到达,渲染主线程会处于阻塞状态,浏览器卡死。所以需要异步执行这些任务。 - 使用异步的方式,渲染主线程就不会阻塞

任务优先级

任务没有优先级,但是消息队列有优先级。过去就是微任务队列和宏任务队列。最新的W3c解释如下:

  • 每个任务都有一种任务类型,相同类型的任务必须放在一个队列中,不同类型的任务放在不同的队列中。浏览器可根据实际情况选取队列执行任务。
  • 浏览器必须准备好一个微任务队列,微任务队列中的任务优先其他所有任务执行。
  • chrome浏览器:目前的主要队列:延时队列,用户交互队列,微任务队列。

js的异步理解

  • js是一门单线程的语言。因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
  • 渲染主线程要执行很多任务,解析html,执行js等。
  • 如果主线程采用同步的方式执行代码,可能会阻塞主线程的执行,导致消息队列中的其他任务无法执行。会浪费时间,以及导致页面无法及时更新卡死。
  • 所以采用异步的方式去解决。当遇到一些异步的任务,渲染主线程将这些任务交给其他线程去处理,自身立即结束执行任务,执行后续代码。当其他线程完成,将事先传递的回调函数包装成任务,放入消息队列的末尾,等待主线程的调度执行。
  • 在这种异步模式下,浏览器就不会阻塞,单线程也可以流畅运行。

Js的事件循环

事件循环又称之为消息循环,是浏览器渲染主线程的工作方式。 在chrome的源码中,它开启一个死循环,每次循环从消息队列中取出第一个任务执行,其他线程只需要在合适的时候把任务放入消息队列的末尾即可。 过去就是微任务队列和宏任务队列。目前的浏览器情况更加复杂,灵活的处理。 最新的W3c解释如下:

  • 每个任务都有一种任务类型,相同类型的任务必须放在一个队列中,不同类型的任务放在不同的队列中。
  • 不同的任务队列有不同的优先级
  • 在一次事件循环中,浏览器可根据实际情况选取队列执行任务。
  • 但是浏览器必须准备好一个微任务队列,微任务队列中的任务优先其他所有任务执行。必须优先调度。

Promise

promise.resolve.then(函数)的功能是:立即把这个函数放入微任务队列中

const promise = new Promise((resolve, reject) => {
  console.log(1)
  setTimeout(() => {
    console.log('timerStart')
    resolve('success')
    console.log('timerEnd')
  }, 0)
  console.log(2)
})
promise.then((res) => {
  console.log(res)
})
console.log(4)
输出顺序:1 2 4  timerStart   timerEnd  success(等这个宏任务执行完毕)

async 和 await

  • await之前的代码是同步代码
  • await 等待的一定是一个promise对象
  • await 会暂停后面代码的执行,将其放入微任务队列中
async function async1() {
  console.log('async1')   同步代码执行
  await async2()       执行(等待返回一个promise对象,将promise。then的内容放入微任务队列)
  console.log('async1 end')  暂停执行(等)
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(() => {
  console.log('setTimeOut')
}, 0)
async1()
new Promise((resolve) => {
  console.log('promise')
  resolve()
}).then(() => {
  console.log('promise2')
})
console.log('script end')

输出结果: script start async1 async2 promise script end async1 end promise2 setTimeOut 执行顺序: 打印 script start
st推入宏任务 调用async1 函数 打印 async1 执行 async2 打印async2 就是这里会返回一个执行成功的promise 已经等到了 ,所以将 后续的放入微任务队列 微任务:aend 继续同步代码 打印promise 微任务:promise2 打印script end 微任务:处理 打印 async1 end promise2 宏任务 :打印 setTimeout