深入理解JS事件循环机制(浏览器)

2022/02/12 JavaScript 共 1564 字,约 5 分钟

单线程与异步

js是单线程的,也就是一次只能执行一段代码。但是浏览器是事件驱动的,浏览器中很多行为都是异步的,会创建事件并放入执行队列中。

首先了解下同步与异步的区别

  • 同步任务:直接在主线程上排队执行
  • 异步任务:放到任务队列中,如果有多个异步任务则要在队列中排队等待

js是单线程的,如何实现异步操作

js的异步是通过回调函数实现的,即通过任务队列(任务队列类似一个缓冲区,任务下一步会被移到调用栈(call stack),然后主线程执行调用栈的任务),在主线程执行完当前的任务栈(所有的同步操作),主线程空闲后轮询任务队列,并将任务队列中的任务取出来执行。

异步运行机制如下: 1.所有同步任务都在主线程上执行,形成一个执行栈 2.主线程之外,还存在一个任务队列(task queue),只要异步任务有了运行结果,就在任务队列中放置一个事件 3.一旦执行栈中的所有同步任务执行完,系统就会读取任务队列,对应的异步任务就会结束等待,进入执行栈开始执行 4.主线程不断重复第三步

执行栈

每一个函数执行的时候,都会生成新的execution context(执行上下文),执行上下文会包含一些当前函数的参数、局部变量之类的信息,它会被推入栈中。当函数执行完后,它的执行上下文会从栈中弹出。

js中有三种执行上下文:

  • 全局执行上下文,默认的,在浏览器中是 window 对象,并且 this 在非严格模式下指向它
  • 函数执行上下文,js的函数每当被调用时会创建一个上下文
  • Eval 执行上下文

举个简单的例子:

function foo() {
  console.log(1)
  bar()
  console.log(3)
}

function bar() {
  console.log(2)
}

foo()
  • foo()函数被调用时,将 foo 函数的执行上下文压入执行栈,接着执行输出1
  • bar()函数被调用时,将bar函数的执行上下文压入执行栈,接着输出2
  • bar()函数执行完毕,被弹出执行栈,foo()函数接着执行,输出3
  • foo()函数执行完毕,被弹出执行栈

调用栈

调用栈是一个具有LIFO(后进先出)结构的堆栈,用于存储在代码执行期间创建的所有执行上下文。与执行栈一样,函数调用会形成一个栈帧,帧中包含了当前执行函数的执行上下文,函数执行完后,它的执行上下文会从栈中弹出。

宏任务(macrotask)

  • script全部代码、setTimeoutsetIntervalsetImmediateI/OUI Rendering

微任务(microtask)

事件循环

HTML规范中介绍:

  • There must be at least one event loop per user agent, and at most one event loop per unit of related similar-origin browsing contexts.
  • An event loop has one or more task queues.
  • Each task is defined as coming from a specific task source.

浏览器至少有一个事件循环,一个事件循环至少一个任务队列(macrotask),每个任务都有自己的分组,浏览器会为不用的任务组设置优先级。

一次事件循环包括: 1. 检查macrotask队列是否为空,非空则到步骤2,为空则到步骤3 2. 执行macrotask中的一个任务 3. 继续检查microtask队列是否为空,非空则到步骤4,为空则到步骤5 4. 取出microtask中的任务执行,执行完成后返回到步骤3 5. 执行视图更新

宏任务与微任务执行顺序: ma(i)crotask

参考文章

1.深入理解js事件循环机制(浏览器篇)
2.浏览器事件循环机制(event loop)