js的引擎和运行时

我们经常会听到引擎和runtime,它们的区别是什么呢?

引擎

解释并编译代码,让它变成能交给机器运行的代码(runnable commands)。

runtime:

就是运行环境,它提供一些对外接口供js调用,以跟外界打交道,比如,浏览器环境、Node.js环境。不同的runtime,会提供不同的接口,比如,在 Node.js 环境中,我们可以通过 require 来引入模块;而在浏览器中,我们有 window、 DOM。

Js引擎是单线程的,如上图中,它负责维护任务队列,并通过 Event Loop 的机制,按顺序把任务放入栈中执行。而图中的异步处理模块,就是 runtime 提供的,拥有和Js引擎互不干扰的线程。接下来,我们会细说图中的:栈和任务队列。

浏览器中的事件循环

简单来讲,整体的js代码这个macrotask先执行,同步代码执行完后有microtask执行microtask,没有microtask执行下一个macrotask,如此往复循环至结束,首先明白2个概念

同步任务

在主线程上排队执行的任务,前一个任务执行完毕,才能执行后一个任务

异步任务

不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

因为是js是单线程语言,所谓单线程,是指在js引擎中负责解释和执行js代码的线程只有一个。不妨叫它主线程。但是实际上还存在其他的线程。例如:处理ajax请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。不妨叫它们工作线程。我认为原因如果是多线程都对一个同一个dom进行操作,那最后依据哪个线程的执行效果进行显示呢。 总之:主线程空,就回去读取任务队列里面的任务,这就是js的运行机制。 任务队列里面的任务是什么?

任务就是注册异步任务时添加的回调函数

可以简单认为这里取的

浏览器中的任务队列的分类

microtasks:

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

Object.observe方法用于异步地监视一个对象的修改。当对象属性被修改时,方法的回调函数会提供一个有序的修改流,在MDN上,这个接口已经被废弃并从各浏览器中移除。你可以使用更通用的 Proxy 对象替代。

MutationObserver 是一个检测页面DOM变化,然后对变化的某些DOM进行一些操作,是用来代替Mutation EventsMutation Observer 是在DOM4中定义的,用于替代 Mutation Events 的新API,它的不同于events的是,所有监听操作以及相应处理都是在其他脚本执行完成之后异步执行的,并且是所以变动触发之后,将变得记录在数组中,统一进行回调的,也就是说,当你使用observer监听多个DOM变化时,并且这若干个DOM发生了变化,那么observer会将变化记录到变化数组中,等待一起都结束了,然后一次性的从变化数组中执行其对应的回调函数。

macrotasks:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染

whatwg规范:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue

  1. 一个事件循环(event loop)会有一个或多个任务队列(task queue)
  2. task queue 就是 macrotask queue
  3. 每一个 event loop 都有一个 microtask queue
  4. task queue == macrotask queue != microtask queue
  5. 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中

看来标准之后再用自己语言组织一下什么是事件循环

事件循环决定了js的执行顺序,首先执行是整体的代码第一次执行,然后全局上下文进入函数调用栈,直到调用栈清空,只剩下全局,这时候执行所有的mircotasks,当所有的可以执行的mircotasks执行完毕之后,循环再次从macrotasks开始。找到其中macrotasks队列中的一个macroteask队列进行执行,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环,然后执行所有的microtask。这样一直循环下去。

用一张图片来表示(消息队列也就是任务队列)

需要注意的一点一个script标签里面的js代码也是一个task,确切说是macrotask。

所以这里就有个问题了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
console.log(1)
setTimeout(() => {
console.log(4)
}, 0);
</script>

<script>
setTimeout(() => {
console.log(2)
}, 0);
</script>

<script>
console.log(3)
</script>

// 1 3 4 2

一个事件循环有多个任务队列

为什么会这样的设计呢:任务队列是具有优先级的,按照优先级决定访问的先后顺序。而优先级在不同的环境中会有所不同,所以不能给出一个固定的优先级。每访问一个队列,执行栈会执行完这个任务队列的所有的代码,然后再取下一个任务队列需要执行的的代码。如果在执行中遇到了当前属于任务队列的异步任务时。此次任务的返回不会直接排到当前任务队列之后。因为这属于两次不同的事件循环,会被区分开来。

参考链接