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机制图如下
