Hibop / Hibop.github.io

Hibop 个人博客
https://hibop.github.io/
23 stars 1 forks source link

关于调用栈和事件队列——js执行顺序 #9

Open Hibop opened 6 years ago

Hibop commented 6 years ago

先从一道简单的面试题说起. 下面代码的输出结果是?

setTimeout(function(){console.log("three");}, 0);

Promise.resolve().then(function(){ console.log( "two" ); });

console.log("one");

答案是: one two three! 看似一个简单到不能再简单的面试题, 涉及的知识点很多. 作为面试题短小精悍, 用来摸底候选人, 真的不错.

下面来解析一下涉及的知识点

函数调用栈(Call Stack)

每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的 执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。

函数调用栈(Call Stack): 在 JavaScript 程序中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。栈的数据结构是 先进后出, 最后进入调用栈中的函数会最先执行.

队列数据结构(Queue)的事件

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。队列的数据结构式 先进先出, 最后进入队列的函数最后执行. 任务队列有两种:

macro-task(宏任务 Task)

script(整体代码)
setTimeout/setinterval
setlmmediate
I/O 操作
UI rendering

micro-task (微任务 Job)

process.nextTick
Promise.then
MutationObserve
async/await

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Event Loop

Javscript 的执行机制是: 首先事件循环从宏任务队列开始, 这个时候宏任务队列中, 只有一个script(整体代码)任务. 每一个任务的执行顺序, 都依靠函数调用栈来搞定, 而当遇到任务源时, 则会先分发任务到对应的队列中去, 先执行调用栈中的函数, 当调用栈中的执行上下文全部被弹出, 只剩下全局执行上下文的时候, 就开始执行 Job 执行队列, Job 执行队列执行完成后就开始执行 Task 执行队列, 先进入的先执行, 后进入的后执行, 无论是 Task 还是 Job 都是通过函数调用栈来执行. Task 执行完一个, JavaScript 引擎会继续检查是否有 Job 需要执行. 就形成了 Task--Job--Task--Job 的循环, 这就行形成了事件循环 ( Event Loop).

好了, 回到一开始的面试题上来. 我们来大致走一下它的执行流程:

当前 Task 执行 (整体代码), 首先 setTimeout 的 callback 被添加到 macro-task 队列中 Promise.then 的 callback 被添加到 micro-task 队列中 代码 console.log("one") 进入调用栈, 输出 one 当前全局上下文全部被弹出, 开始执行 Job 队列, 输出 Job 队列的 two Job 队列执行完成, 开始执行 Task 队列, 输出 setTimeout 回调中的 three

Hibop commented 6 years ago

细说JavaScript单线程的一些事

http://www.codeceo.com/javascript-threaded.html

Hibop commented 6 years ago

Node.js的线程和进程详解

https://github.com/JChehe/blog/blob/master/posts/%E5%85%B3%E4%BA%8EJavaScript%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BA%8B.md