跳至主要內容

事件循环

wzCoding大约 6 分钟JavaScript事件循环

事件循环

事件循环机制(Event Loop)是 Javascript 中十分重要的概念,它是 JS 运行机制的核心,决定了 JS 代码的执行顺序,理解事件循环机制将能够帮助我们更加深入的理解 JS 中各种事件的运行原理,也会对代码优化有很大的帮助

单线程的JS

JS 是单线程语言,代码的执行顺序是从上到下执行,这意味着它在任何给定时刻只能执行一个任务

var a = 1;
console.log(a);  // 1

function foo(){
    console.log("foo");
}
foo();  // foo

console.log("hello");  // hello

上面的简单示例中会在控制台先输出 1,接着再输出 foo,然后再输出 hello,代码是从上到下的顺序执行的,一次输出一个结果,前一个方法(任务)执行完成后再执行下一个方法(任务),而不会同时输出所有的结果

为什么是单线程

JS 最根本的目的便是实现用户与浏览器之间的交互,试想一下,如果 JS 是多线程的话,当一个线程中的代码在获取某个 DOM 元素,而另外一个线程的代码又在删除这个 DOM 元素,那么这两种操作就会产生冲突,导致一系列复杂的同步问题,所以为了避免上述的问题以及一些其他的复杂问题,JS 只能是单线程的

同步和异步

当代码越来越复杂时,执行某个方法可能就会耗费大量时间,如果按照顺序来执行,那么这些耗时的方法(任务)不执行完成,后续的一系列方法(任务)都会处于等待状态中,如果迟迟无法得到结果,就会造成页面卡顿,极大影响了用户体验

为了避免上述的问题,JS 将我们编写的代码分为了两类情况:同步任务异步任务

同步任务

同步任务是执行后很快就可以得到结果,同步任务在主线程上面执行,会形成一个执行栈,这些任务按照代码的顺序依次执行,前一个任务结束后,才会执行后一个任务

异步任务

异步任务是执行后无法立即得到结果,需要等待一段时间才能得到结果,例如:向服务器请求接口数据、定时器、响应鼠标键盘操作的事件等,异步任务一般都会使用 回调函数 作为参数,用以处理这些异步任务的返回结果,异步任务在主线程之外执行,它的回调函数会被添加到 消息队列 中,当主线程中的所有同步任务都执行完成后,会从消息队列中取出已完成的异步任务的回调函数加入调用栈中并执行

ℹ️提示

回调函数:回调函数是一种特殊类型的函数,它被作为参数传递给另一个函数,并在某个特定的事件或条件发生时被调用执行。回调函数的工作方式类似于“我告诉你要做的事情,当你完成时,请告诉我”。它允许你定义一些代码,并在某个操作完成后通知你

消息队列:JS 在运行时(运行环境下)会有一个等待处理的消息队列(也可以称为任务队列),每个消息都关联一个异步操作的回调函数,遵循先进先出的规则

什么是事件循环

事件循环 的概念非常简单。它是一个在 JS 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环

事件循环的伪代码形式就像下面这样:

while (queue.waitForMessage()) {  //同步的等待任务
  queue.processNextMessage();     //处理任务
}

宏任务与微任务

在理解事件循环的具体运行机制前,需要先了解什么是宏任务与微任务

  • 宏任务: 宏任务(macro task)是相对于微任务的称呼,宏任务包括:<script> 整体代码、setTimeoutsetIntervalsetImmediateAjaxDOM 事件,每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
  • 微任务: 微任务(micro task)包括:process.nextTickMutationObserverPromise.then catch finally,微任务可以理解是在当前 task 执行结束后立即执行的任务
ℹ️提示

微任务队列:在任务队列中还分微任务队列与宏任务队列,里面对应存放微任务与宏任务

事件循环流程

事件循环的大致过程如下:

  1. 宏任务 队列中按照入队顺序,找到第一个要执行的宏任务(例如 script 整体代码),放入调用栈执行
  2. 执行完当前宏任务下的所有同步任务后(此时调用栈被清空,当前宏任务从宏任务队列中移除),开始执行 微任务(按照微任务的入队顺序依次执行,直到将所有微任务执行完成,清空微任务队列)
  3. 如果在上面的步骤中有对页面进行变更的操作,就将 变更渲染 出来
  4. 此时一次事件循环结束,如果此时宏任务队列为空,那么将开始等待进行下一次事件循环,重复执行以上的流程

JS 引擎在每个宏任务执行完成后,会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作,当微任务队列没有清空之前,是不会开始执行宏任务的

事件循环的特点

永不阻塞

JS的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它 永不阻塞。处理I/O通常通过事件和回调来执行,所以当一个应用正等待一个IndexedDB查询返回或者一个XHR请求返回时,它仍然可以处理其他事情,比如用户输入