JS异步运行机制

683 阅读4分钟

JS引擎

是什么?

JavaScript是解释型语言,在代码运行之前不会进行编译工作,将源码转换成字节码等中间代码或者是机器码。

因此需要JavaScript引擎在代码运行的过程中将其转换成可执行的机器码并执行。

特点

  • 内存堆
    引用类型实际值
  • 调用栈
    基本类型存储、引用类型地址名存储、代码逻辑执行的地方
  • 单线程
    • 原因
      JS最初被设计使用在浏览器上,作为浏览器上的脚本语言,需要与用户的操作互动以及操作DOM,如果多线程,需要关注各个线程之间的状态同步问题
    • 特点
      单线程意味着,JS引擎会按照代码书写的逻辑,依次调用函数,在任意时间点,有且只能有一个函数被执行。外层函数必须等到内层函数处理完毕后有返回值才能继续执行。

常见的JavaScript引擎

  • Chrome V8引擎(chrome、Node、Opera)
  • SpiderMokey(Firefox)
  • Nitro(safari)
  • Chakra(Edge)

运行时Rumtime

是什么?

事实上,Javascript引擎是工作在一个环境(容器)内的,这个环境提供了一些额外的功能(API),我们的代码在执行的时候可以使用这些特性。

不像其他编译型语言,编译之后称为机器码可以直接在主机上运行。JS是运行在一个宿主环境中的,这个容器一般需要做2件事情

  • 解析JS代码,转换成可执行的机器语言
  • 暴露一些额外的API,可以跟JS代码做交互

常见的JS宿主环境

  • web浏览器
  • node.js
宿主环境 JS引擎 运行时特点
浏览器 chrome V8引擎 DOM、window对象、用户事件、Timers等
node.js chrome V8引擎 require对象、Buffer、Process、fs等



分析到这里,目前由于JS引擎是单线程,执行栈内的函数都是同步执行的,遇到耗时操作怎么办??

浏览器环境下JS的异步机制

浏览器

一个浏览器通常由一下几个常驻线程:

  • GUI渲染线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时器触发线程:处理定时事件,比如setTimeout,setInterval
  • 事件触发线程:处理DOM事件,与EventLoop密切相关
  • 异步http请求线程

GUI渲染线程和JS引擎线程是互斥的,为了防止DOM渲染的不一致性,其中一个线程执行时另一个线程会被挂起

实现异步的思路

JavaScript是单线程的,但是浏览器内部不是单线程的,可以将一些异步操作交给浏览器内部其他线程来完成。

任务队列和事件循环

任务队列

一旦某个异步任务有了响应就会被推入任务队列。比如用户的点击事件,浏览器收到服务器的响应和settimeout中待执行的事件。

事件循环

JS引擎线程从消息队列中读取任务是不断循环的,每次执行栈被清空后,都会在消息队列中读取新的任务。这个过程叫做事件循环

Macrotask & Microtask

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

    • 我们可以把宿主发起的任务称为宏任务
    • 每个宏观任务中又包含了一个微观任务队列
  • Microtask:可以理解是在当前task执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

    • 把JavaScript引擎发起的任务称为微任务

事件循环流程:

  • 执行一个宏任务(首次执行的主代码或者任务队列中的回调函数)
  • 执行过程中如果遇到微任务,就将它添加到微任务的队列中
  • 宏任务执行完毕后,立即执行当前微任务的任务队列中的所有任务(依次执行)
  • JS引擎线程挂起,GUI线程执行渲染
  • GUI线程渲染完毕挂起,JS引擎线程执行任务队列中的下一个宏任务

综上,整个JS机制图如下

image

参考文章

人人都看得懂的JS运行机制
JavaScript异步机制详解
你真的理解$nextTick么