简评: 如果你对 JavaScript 异步的原理感兴趣, 这里有一篇不错的介绍.
JavaScript 同步代码是如果工作的
在介绍 JavaScript 异步执行之前先来了解一下, JavaScript 同步代码是如何执行的.
这里有两个概念需要了解:
** 执行上下文 (Excution Context)**
执行上下文是一个抽象的概念, 用于表示 JavaScript 的运行环境, 任何代码都会有一个执行上下文.
全局代码运行在全局执行上下文, 函数里的代码运行在函数执行上下文, 每一个函数都有自己的执行上下文.
调用堆栈 (Call Stack)
调用栈是一个具有 LIFO(后进先出) 结构的栈, 用于储存代码执行阶段所有的执行上下文.
因为 JavaScript 是单线程的, 所以 JavaScript 只有一个单独的调用栈.
我们以下面例子介绍同步代码执行过程.
- const second = () => {
- console.log('Hello there!');
- }
- const first = () => {
- console.log('Hi there!');
- second();
- console.log('The End');
- }
- first();
创建全局上下文 (由 main() 表示), 并将全局上下文推到栈顶. 然后依次将遇到函数执行上下文推到栈顶 (如果函数中执行其他他函数, 其他函数依次推到栈顶以此类推). 当函数执行完毕对应的执行上下文会从调用栈弹出, 程序结束时全局上下文从调用栈弹出.
JavaScript 异步代码是如何执行的?
通过上个章节我们已经对调用栈和 JavaScript 的同步执行有了基本的了解, 现在来看看 JavaScript 异步执行是如何工作的.
什么是阻塞?
由于 JavaScript 是单线程的, 如果某个函数耗费的时间比较长, 会阻塞后面的任务执行, 这就造成了阻塞. 解决阻塞最简单的方法是函数直接返回不等待, 使用异步回调来处理返回结果.
在了解 JavaScript 异步执行之前还需要知道一些概念, 事件循环和回调队列 (也称为任务队列或消息队列).
注意: Event Loop ,web APIs 和 Message Queue 并不是 JavaScript 引擎的一部分, 而是浏览器运行时环境和 Node.JS 运行时环境的一部分.
我们以下面代码为例, 解释异步代码是如何执行的.
- const networkRequest =()=> {
- setTimeout(()=> {
- console.log('Async Code');
- },2000);
- };
- console.log('Hello World');
- networkRequest();
- console.log('The End');
当上述程序加载到浏览器时 console.log('Hello World') 代码执行时会一次在调用栈推入和弹出. 遇到 networkRequest() 将其推入到调用栈顶. 然后继续将 networkRequest 内的 setTimeout 方法推入栈顶, 随后 setTimeout networkRequest 依次出栈. 最后对 console.log('The End') 进行入栈出栈.
当 timer 到期后会将 callback 推入 message queue(消息队列) 中, 此时 callback 不会马上执行. 会等待事件循环调度.
事件循环
事件循环的作用是查看调用栈并确定调用栈是否空闲. 如果调用栈空闲, even loop 会查看消息队列是否有待处理的 callback 需要触发. 例子中的消息队列只包含一个 callback, 当调用栈为空的时候, even loop 会将 callback 推入调用栈中触发 networkRequest 的回调.
DOM 事件
消息队列还会包含来自 DOM 的事件回调, 比如鼠标和键盘事件回调. 例如:
- document.querySelector('.btn').addEventListener('click',function callback(event) {
- console.log('Button Clicked');
- });
对于 DOM 事件, 当具体的事件触发会将 callback 推入消息队列中, 等待 even loop 来调度执行.
ES6 job queue/micro-task queue
ES6 新增了 job queue/micro-task queue 概念, 在 Promise 中用到. job queue 比 message queue 拥有更高的优先级. 意味着 job queue 和 message queue 都有任务时会优先执行 job queue 中的任务. 例如:
- console.log('Script start');
- // callback 在 message queue 中
- setTimeout(function callback() {
- console.log('setTimeout');
- }, 0);
- // 任务在 micro-task queue 中
- new Promise((resolve, reject) => {
- resolve('Promise resolved');
- }).then(res => console.log(res))
- .catch(err => console.log(err));
- console.log('Script End');
- // 输出:
- Script start
- Script End
- Promise resolved
- setTimeout
再来看下一个例子 (两个 setTimeout 和 两个 Promise):
- console.log('Script start');
- setTimeout(() => {
- console.log('setTimeout 1');
- }, 0);
- setTimeout(() => {
- console.log('setTimeout 2');
- }, 0);
- new Promise((resolve, reject) => {
- resolve('Promise 1 resolved');
- }).then(res => console.log(res))
- .catch(err => console.log(err));
- new Promise((resolve, reject) => {
- resolve('Promise 2 resolved');
- }).then(res => console.log(res))
- .catch(err => console.log(err));
- console.log('Script End');
- // 输出为
- Script start
- Script End
- Promise 1 resolved
- Promise 2 resolved
- setTimeout 1
- setTimeout 2
由此可见 micro-task queue 中的所有任务都会优先于 message queue 中的任务执行.
来源: https://www.cnblogs.com/jpush88/p/10073979.html