-
Notifications
You must be signed in to change notification settings - Fork 0
Description
引子
先从一道简单的面试题说起. 下面代码的输出结果是?
setTimeout(function(){console.log("three");}, 0);
Promise.resolve().then(function(){ console.log( "two" ); });
console.log("one");
答案是: one two three
! 看似一个简单到不能再简单的面试题, 涉及的知识点很多. 作为面试题短小精悍, 用来摸底候选人, 真的不错.
下面来解析一下涉及的知识点
执行上下文(Execution Context)
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的 执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
函数调用栈(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
- MutationObserve
Promise
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