2020-05-25 JS执行机制之EventLoop

188 阅读6分钟

记住两点

  • Javascript是一门单线程语言
  • Javascript的执行机制是Event Loop(事件循环)

执行上下文和执行栈

执行上下文

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
  • Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用

执行栈

也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文

如下图示例: LIFO

执行上下文分创建为两个阶段

  • 创建阶段:
    1. 确定 this 的值,也被称为 This Binding
    2. LexicalEnvironment词法环境) 组件被创建
    3. VariableEnvironment变量环境) 组件被创建
  • 执行阶段:此阶段,完成对所有变量的分配,最后执行代码。如果 Javascript 引擎在源代码中声明的实际位置找不到let变量的值,那么将为其分配undefined

进程与线程

进程

CPU是计算机的核心,承担所有的计算任务,进程是CPU资源分配的最小单位,字面意思是进行中的程序,可以将它理解为一个可以独立运行且拥有自己的资源空间的任务程序 进程包括运行中的程序和程序所使用到的内存和系统资源

线程

CPU调度的最小单位,线程是建立在进程的基础上的一次程序运行单位,通俗点解释线程就是程序中的一个执行流,一个进程可以有多个线程 一个进程中只有一个执行流称作单线程,即程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行 一个进程中有多个执行流称作多线程,即在一个程序中可以同时运行多个不同的线程来执行不同的任务, 也就是说允许单个程序创建多个并行执行的线程来完成各自的任务

进程与线程的区别

  • 进程是操作系统分配资源的最小单位,线程是程序执行的最小单位
  • 一个进程由一个或多个线程组成,线程可以理解为是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)
  • 调度和切换:线程上下文切换比进程上下文切换要快得多

Javascript Engine

JS引擎,负责解释并编译代码,让它变成能交给机器运行的代码 Javascript runtime

JS运行环境,主要提供一些对外调用的接口。比如浏览器环境:windowDOM,还有Node.js环境:requireexport

执行机制

任务队列

JS中任务分为同步任务和异步任务,同步任务指的是在主线程上排队执行的任务;异步任务指的是不进入主线程,而进入任务队列(task queue)的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,存在事件触发线程管理一个任务队列,只要异步任务有了运行结果,就在任务队列中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
  4. 主线程不断重复上面的第三步

Event Loop(事件循环)

事件循环是通过任务队列的机制来进行协调的。一个Event Loop中,可以有一个或多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的task必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise等API便是任务源,而进入队列的是他们指定的具体执行任务

EventLoop.png

宏任务

macrotask称之为宏任务,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

浏览器为了能够使得JS内部的macrotask与DOM任务能够有序的执行,会在上一个macrotask执行结束后,在下一个macrotask执行开始前,对页面进行重新渲染,流程如下:

macrotask->渲染->macrotask->...

宏任务macrotask主要包含

  • script(整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI render
  • setImmediate(NodeJS环境)

微任务

microtask称为微任务,可以理解是在当前task执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task任务之前,在渲染之前

所以它的响应速度相比setTimeout会更快,因为无需等待渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都之行完毕(在渲染前)

微任务microtask主要包含

  • Promise.then
  • MutationObserver(html5新特性)
  • process.nextTick(NodeJS环境)

运行机制

一个线程中,事件循环是唯一的,但是任务队列可以有多个,任务队列又分为宏任务和微任务,大体步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到宏任务就将其添加到宏任务队列中;遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始执行下一个宏任务(从事件队列中获取)

流程如下:

运行机制.jpg