参考文章:
理解JavaScript的执行上下文 - 知乎 (zhihu.com)
事件循环 - JavaScript Guidebook (tsejx.github.io)
1;执行上下文:
指当前执行环境中的变量、函数声明,参数(arguments),作用域链,this等信息。分为全局执行上下文、函数执行上下文,其区别在于全局执行上下文只有一个,函数执行上下文在每次调用函数时候会创建一个新的函数执行上下文。
2;执行上下文组成:
执行上下文的类型:
- 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置
this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文。 - 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
3;变量对象:
每个执行环境文都有一个表示变量的对象——变量对象。
变量对象是与执行上下文相关的数据作用域,存储了该上下文中定义的变量和函数声明。
全局执行环境的变量对象始终存在,而函数这样局部环境的变量,只会在函数执行的过程中存在。
4;执行栈(执行上下文栈):
执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
代码示例:
function getName() {
const year = getYear();
const name = 'Lynn';
console.log(`${name} ${year} years old this year`);
}
function getYear() {
return 18;
}
getName();
函数执行上下文过程图解:
5;事件循环:
(1)JavaScript 的异步任务根据事件分类分为两种:宏任务(MacroTask)和微任务(MicroTask)
- 宏任务:main script、setTimeout、setInterval、setImmediate(Node.js)、I/O(Mouse Events、Keyboard Events、Network Events)、UI Rendering(HTML Parsing)、MessageChannel
- 微任务:Promise.then(非 new Promise)、process.nextTick(Node.js)、MutationObserver
(2)宏任务与微任务的区别在于任务队列中事件的执行优先级。进入整体代码(宏任务)后,开始首次事件循环,当执行上下文栈清空后,事件循环机制会优先检测微任务队列中的事件并推至主线程执行,当微任务队列清空后,才会去检测宏任务队列中的事件,再将事件推至主线程中执行,而当执行上下文栈再次清空后,事件循环机制又会检测微任务队列,如此反复循环。
(3)宏任务与微任务的优先级
- 宏任务的优先级高于微任务
- 每个宏任务执行完毕后都必须将当前的微任务队列清空
- 第一个
<script>
标签的代码是第一个宏任务 process.nextTick
优先级高于Promise.then