JavaScript runtime

可以把浏览器的 runtime 想成一个机器,这个机器由若干个零件组成。

JavaScript 引擎
里面有两个东西:
- Memory Heap
- Calling Stack
Memory Heap(堆)
- 分配空间给「复杂数据类型」使用,分配好之后会得到一个地址(例如 function, object, array)
- 释放不再使用的内存空间(Garbage Collection)
Calling Stack(栈)
- 用来保存「基本数据类型」(例如 number, string…)
- JavaScript 执行上下文(Execution Context)(决定什么时候会做什么事情)
Web APIs
- 通过浏览器
window物件可以用浏览器提供的方法 - 所以 JavaScript 本身沒有这些方法,只是接到了浏览器的 Web APIs 所以才可以用它们
Callback Queue/Task Queue(回调队列也叫消息队列)
这个队列负责保存「已经调用」的 callback function。然后当 Calling Stack 空了的时候,Event loop 就会把队列中的 callback function 传给 Calling Stack ,以便执行 callback function 的任务。
Event Loop(事件循环)
Event Loop 最重要的工作,就是控制 Call Stack 和 Callback Queue(Task Queue)
Event Loop只有一个任务:检查call stack是否是空的,如果发现是空的,就从消息队列(callback queue)推送(push)一个任务(item)进call stack
要注意 Event loop 一次只会推一个,当 call stack 空了它才会推另一个。

- Javascript engine 执行 call stack 中的任务。
- 当遇到 Web APIs 或是无法处理的任务时候会交给 Javascript runtime 执行,Javascript runtime 处理完成后会将任务交给 callback function,并将 callback function 放进 queue 中,形成 Callback Queue。
- Javascript runtime 等待 call stack 中的任务全部执行结束变成空的,从 callback queue 中拉取第一个任务放进 call stack,回到第一步继续重复循环下去。
Macrotask Queue(宏任务)
macrotask 包含:
- 从
<script src="...">外部下载的 script - DOM event handlers,例如 mousemove event 的 callback function handler
- 各种 Web APIs,例如 setTimeout 的 callback function
- ajax callback function
macrotask 在执行时浏览器不会渲染(render)DOM,浏览器会被阻塞住,也就是只执行 macrotask 不会做其他事情,有时我门会看到浏览器跳出页面沒有响应的警告,可能就是因為 macrotask 有复杂运算(CPU-hungry tasks)或是代码逻辑错误导致无限循环发生,使得其他任务无法被处理。

Microtask Queue(微任务)
microtask 包含:
- Promise
.then/catch/finally中的 callback function queueMicrotask(func)中的 func
每个 macrotask 执行结束后会先將 microtask queue 中的 task 全部执行完,才会继续执行浏览器渲染跟其他 macrotask。
将 callback queue 分解成 macrotask 以及 microtask 之后,他门在 Event Loop 中详细的运行流程会是这样:
- 从 macrotask queue 中拿出一个 macrotask 丟到 call stack 中执行。
- 將 microtask queue 中全部的 task 依照顺序 Dequeue 到 call stack 中执行。
- Browser render DOM。
- 如果 macrotask queue 是空的,sleep 直到 macrotask 再次出现。
- 回到步驟 1。

调用栈 (Call Stack)
JavaScript 是一个单线程编程语言,这意味着它只有一个调用栈。因此在同一时间它执行一个任务。
Call Stack 会记录我们目前代码跑到什么位置,如果执行了一个函数,我们会把这个函数放到 (pop in) Stack 的顶端,函数执行结束之后,这个函数就会跳出 (pop out) Stack 中
调用栈是一种我们程序中的基本记录的数据结构。
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其他函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
- 当分配的调用栈空间被占满时,会引发“堆栈溢出”错误。

执行过程
JavaScript 执行过程分为两个阶段:
- 编译阶段
- 执行阶段
编译阶段
在编译阶段 JS 引擎主要做了三件事:
- 词法分析
- 语法分析
- 字节码生成
执行阶段
在执行阶段,会分情况创建各种类型的执行上下文,例如:全局执行上下文 (只有一个)、函数执行上下文。而执行上下文的创建分为两个阶段:
- 创建阶段
- 执行阶段
在创建阶段会做如下事情:
- 绑定 this
- 为函数和变量分配内存空间
- 初始化相关变量为
undefined
我们日常提到的 变量提升 和 函数提升 就是在 创建阶段 做的,所以下面的写法并不会报错:
console.log(msg);
add(1,2)
var msg = 'hello'
function add(a,b){
return a + b;
}因为在执行之前的创建阶段,已经分配好了 msg 和 add 的内存空间。
JavaScript 工作原理:JS 引擎,runtime 和调用栈的概述
解釋 Event Loop ( 下 ) --- Task Queue ( Callback Queue )
