Skip to content

JavaScript : Event Loop #6

@ifyour

Description

@ifyour

引子

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

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).
javascript event loop


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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions