likes
comments
collection
share

浏览器事件循环

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

前言

在JavaScript的执行过程中,是所有代码都按顺序执行完毕吗?

一、进程和线程

进程(process)线程(thread)是操作系统中的两个概念。

1. 进程

  • 计算机已经运行的程序,是操作系统管理程序的一种方式。
  • 启动一个应用程序,就会默认启动一个进程(也可能是多个进程)。

2. 线程

  • 操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中。
  • 一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称为主线程

总结:进程是线程的容器。 浏览器事件循环

3.操作系统的工作方式

操作系统可以让多个进程同时工作

  • 因为CPU运算速度非常快,可以在多个进程中快速切换
  • 进程中的线程获取到时间片,就可以快速执行代码。
  • 用户感知不到这种快速的切换。

二、浏览器JavaScript线程

JavaScript单线程的,它的容器进程有两种:

  • 浏览器
  • Node

1.浏览器线程

  • 目前多数的浏览器都是多进程的,打开一个tab页面就会开启一个新的进程,为了防止一个页面卡死造成所有页面无法响应,整个浏览器需要强制退出。
  • 每个进程中有很多线程,其中包括执行JavaScript代码的线程。

2.JavaScript线程

因为JavaScript的代码在一个单独的线程中执行,所以:

  • JavaScript代码在同一时刻只能做一件事(参考JavaScript的执行过程)。
  • 如果这件事非常耗时,意味着当前的线程会被阻塞

为了线程不阻塞,耗时的操作实际上不是JavaScript线程在执行。

  • 浏览器的进程是多线程的,所以其它线程可以完成这个耗时的操作。比如网络请求、定时器等。
  • JavaScript线程只需要处理对应的回调函数

总结:在JavaScript代码执行过程中,如果遇到异步操作如setTimeout,则setTimeout被放到调用栈中并立即执行结束出栈,不会阻塞后续代码的执行。

三、事件循环

在JavaScript线程中,先执行执行上下文栈中的执行上下文。当执行上下文栈为空时,开始检查浏览器是否把一些任务(DOM监听、XMLHttpRequest、定时器加入事件队列(任务队列)里。如果事件队列不为空,则从事件队列中取出最先放进去的任务加入到执行上下文栈中执行,如此往复,将事件队列内的任务执行完毕。形成的回环就是事件循环

浏览器事件循环

四、事件队列

事件队列包含宏任务队列(macrotask queue)微任务队列(microtask queue)

1.宏任务队列

宏任务:ajax、setTimeout、setInterval、DOM监听、UI Rendering等。

2.微任务队列

微任务:Promise的then回调、Mutation Observer API、queueMicrotask()等。

注:Promise必须等到为fulfilled状态,即executor函数执行到resolve()之后才会把回调加入到微任务队列。

3.执行机制

  1. main script中的代码优先执行(编写的顶层script代码)。
  2. 在执行任何一个宏任务之前(不是队列,是一个宏任务),先查看微任务队列中是否有任务需要执行。
  • 宏任务执行前,必须保证微任务队列是空的。
  • 如果不为空,优先执行微任务队列中的任务(回调)。

五、面试题

1.面试题1-Promise

console.log('script start')

setTimeout(function () {
  console.log('setTimeout1')
  new Promise((resolve, reject) => {
    resolve()
  }).then(res => {
    console.log('then4')
  })
  console.log('then2')
})

new Promise((resolve, reject) => {
  console.log('promise1')
  resolve()
}).then(res => {
  console.log('then1');
})

setTimeout(() => {
  console.log('setTimeout2');
})

console.log(2);

queueMicrotask(() => {
  console.log('queueMicrotask');
})

new Promise((resolve, reject) => {
  resolve()
}).then(res => {
  console.log('then3');
})

console.log('script end');
// script start
// promise1
// 2
// script end
// then1
// queueMicrotask
// then3
// setTimeout1
// then2
// then4
// setTimeout2

思路:

  1. 依次执行main script代码,遇到微任务宏任务时将任务加入到各自的任务队列。
  2. main script代码执行完毕,依次执行微任务队列中的微任务。执行微任务时,遇到微任务和宏任务时将任务加入到各自的任务队列。
  3. 微任务队列执行完毕,执行第一个宏任务
  4. 执行宏任务时,遇到微任务和宏任务时将任务加入到各自的任务队列。
  5. 执行完一个宏任务,检查并执行微任务队列中的微任务。
  6. 如此往复。执行完微任务队列和宏任务队列中的任务。

2.面试题2-接口请求

2.1 promise.then

console.log('script start');

function requestData (url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('setTimeout');
      resolve(url)
    }, 2000)
  })
}

function getData () {
  console.log('getData start');
  requestData('why').then(res => {
    console.log('then1-res:', res);
  })
  console.log('getData end');
}

getData()

console.log('script start');
// script start
// getData start
// getData end
// script end
// setTimeout
// then1-res:why

思路:

  1. 注意Promise必须等到为fulfilled状态,即executor函数执行到resolve()之后才会把回调加入到微任务队列。

2.2 async/await

console.log('script start');

function requestData (url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('setTimeout');
      resolve(url)
    }, 2000)
  })
}

async function getData () {
  console.log('getData start');
  const res = await requestData('why')
  console.log('then1-res:', res);
  console.log('getData end');
}

getData()

console.log('script start');
// script start
// getData start
// script start'
// setTimeout
// then1-res:why
// getData end

思路:

  1. await之后的代码必须等到Promise的状态变成fulfilled才会执行

3.面试题3

async function async1 () {
  console.log('async1 start');
  await async2()
  console.log('async1 end');
}

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

console.log('script start');

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

async1()

new Promise(resolve => {
  console.log('promise1');
  resolve()
}).then(res => {
  console.log('promise2');
})

console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// timeout

思路:

  1. 执行async2()时,相当于return undefined => Promise.resolve(undefined),所以console.log('async1 end')会被加入到微任务队列中。

附录

  1. 视频:浏览器事件循环
转载自:https://juejin.cn/post/7235967072528957499
评论
请登录