事件循环

291 阅读3分钟

前言

居然考到了事件循环,好偏底层的东西。我以为是事件冒泡或事件委托呢。既然被问到了,那么就写一下吧。

是什么

JavaPython都是多线程模式(通常为Thread类)语言。但JavaScript是单线程、非阻塞的。这与它的用途有关:

  • Java通常用来开发系统级应用,Python原本的用途就是系统脚本。
  • JavaScript最早是用来做网页交互逻辑以及操作DOM的

所以JavaScript被设计成了实质上的单线程语言。那么如何多任务并发呢?多线程语言可以用Thread类,JS用到了事件循环模型。

基础知识

调用栈 (stack)

观察下列代码:

// 来源:[3]
function foo(b) {
  let a = 10;
  return a + b + 11;
}

function bar(x) {
  let y = 3;
  return foo(x * y);
}

console.log(bar(7)); // 返回 42

MDN的讲解:

函数调用形成了一个由若干帧组成的栈。

当调用 bar 时,第一个帧被创建并压入栈中,帧中包含了 bar 的参数和局部变量。 当 bar 调用 foo 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 foo 的参数和局部变量。当 foo 执行完毕然后返回时,第二个帧就被弹出栈(剩下 bar 函数的调用帧 )。当 bar 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。

堆 (heap)

对象被分配在堆(heap)中,用于表示一大块的内存区域。

消息队列(queue)

一个JS runtime包含一个待处理信息的队列。每一个消息都关联着一个用以处理这个消息的回调函数。

在事件循环期间的某个时刻,运行时会从最先进入队列中的消息。被处理的消息被移出队列 ,并成为回调函数的关联参数。

函数的处理会一直进行到栈再次为空为止。

异步原理

任务队列 (task queue)

单线程语言意味着任务都需要排队,前一个任务结束才会执行后一个任务。

任务队列主要是给异步任务用的。不进入主线程、而进入任务队列的任务就是异步执行。

执行机制

  1. 所有同步任务都在主线程上执行,形成执行栈。
  2. 主线程之外还存在一个任务队列。只要多任务运行有了结果就会在任务队列中放置一个事件。
  3. 一旦执行栈中所有同步任务执行完毕,系统就会读取任务队列中的事件。对应的异步任务结束等待状态,并开始执行。
  4. 主线程不断重复上面的一步。

事件循环 (Event Loop)

主线程从任务队列中读取事件,这个过程是循环不断的。

图源自阮一峰2image.png

主线程运行时候,产生堆和栈,栈中的代码调用各种外部外部API,在任务队列中加入各种事件。只要栈中的代码执行完毕就会读取任务队列,依次执行事件中对应的回调函数。

References

  1. 什么是 Event Loop?(文章是错的) - 阮一峰的网络日志 (ruanyifeng.com)
  2. JavaScript 运行机制详解:再谈Event Loop(文章是对的) - 阮一峰的网络日志 (ruanyifeng.com)
  3. 并发模型与事件循环 - JavaScript | MDN (mozilla.org)