浏览器下的 Event Loop

分享

前言

javascript 是以单线程的形式运行在宿主环境下,javascript 采用了回调的形式来解决异步任务。

为什么是单线程?

javascript 的最开始的出现是为了给 web 页面增添一些动态的效果,那么就避免不了获取页面上的元素信息,如果 javascript 是以多线程的形式运行在浏览器内,如果两个线程内的 javascript 同时去获取/修改,某个页面上的元素,那么浏览器该让哪个 javascript 线程拥有获取/修改该元素的权限呢?由于元素的信息会经常性的发生变化,那么又改如何去同步各个线程内所保存的元素信息呢?

所以综合以上问题, javascript 是单线程的原因就显示意见了,单线程在执行时,对于元素信息的引用在同一时间仅可能只有一个,那么以上所有的问题都不存在了。

什么是异步任务?

任何代码在执行时,都会碰到一些需要经过大量时间运算或是等待的代码,在浏览器的环境下,常见的就是 http 任务,比如:资源的加载(图片的 onload 事件),ajax 的请求(XMLHttpRequestonLoad 事件)还有页面元素的点击事件以及定时器等。

以上的任务都极其的耗时而且会受环境的影响,如果同步执行的话就会造成 javascript 执行的卡顿,而 javascript 又是单线程的形式存在在浏览器端,为了使得 javascript 的执行不受到影响,javascript 会将这些任务执行放在另一个环境下,而将这些任务执行完成后的需要执行的函数给保存下来(也就是回调),这也是为什么一定要写一个回调的原因。当另一个环境下通知 javascript 线程该任务已完成,并将任务数据给到 javascript 线程,javascript 再去保存的回调中寻找该任务对应的回调,将数据当做参数并执行该回调。

Event Loop

上面大概简述了下 javascript 为什么要以异步回调的形式来处理一些耗时任务,那么接下来就说说 javascript 到底是如何处理这些异步回调的。

从代码入手

// a.js
let image = new Image();
image.src = 'image url';

image.onload = () => {
    // image 加载成功回调
}
image.onerror = () => {
    // image 加载失败回调
}

javascript 会从上到下执行该代码,当执行到 image.src = 'image url' 时,javascript 线程通知浏览器图片加载程序去加载相应图片,然后 javascript 继续执行剩下的代码,当执行到 onloadonerror 时,javascript 仅仅是保存了这两个函数而已(保存回调)。

当浏览器图片加载程序加载好图片,就会通知 javascript 线程, image 已加载完毕,如果没有发生错误,那么 javascript 在接收到该信号以后就会执行 image.onload 方法,如果通知回来是加载失败,那么就会执行 image.onerror 方法。

事件队列

按照上面所说,并结合最开始说的,如果图片加载程序加载好图片返回加载成功的信号时 javascript 正在处理别的任务,由于 javascript 是单线程不能同时处理多个任务,那么这个加载成功的信号就会被搁置,放在一个事件队列中,javascript 线程在处理好当前的任务后就会去事件队列中取出一个事件并执行响应的回调。

Loop

在真正的浏览器环境下,异步任务的信号每时每刻都会发生(比如设置的定期器,用户的行为,ajax等),那么每时每刻都会有新的任务信号进入事件队列中,所以在浏览器中 javascript 的执行会有以下的效果:

以下为 javascript 线程执行的内容

  1. 加载 script 所对应的 javascript 脚本
  2. 执行 javascript 代码,注册异步任务,保存回调函数
  3. 引入的脚本所有代码执行完毕
  4. 一些 UI 渲染(该步骤不一定会有)
  5. 取事件队列中最早进入的事件,并在事件队列中删除该事件
  6. 执行该事件对应的回调代码
  7. 回调代码执行完毕
  8. 一些 UI 渲染(该步骤不一定会有)
  9. 回到步骤 5
  • 1 - 4 步是浏览器加载 javascript 所必须执行的,可以认为是注册异步任务最开始的地方。
  • 步骤 6 执行回调的过程中可能会产生新的回调,比如在 ajax 请求成功回调中注册了页面元素的点击事件

以下为浏览器相关程序的内容(异步任务)

  1. 接收到 javascript 注册的异步任务
  2. 执行任务
  3. 任务完成后在事件队列中推入成功事件
  4. 任务失败后在事件队列中推入失败事件

这样下来,javascript 线程就会持续不断的执行,也不会因为耗时任务而暂停执行。

javascript 线程中 5 - 9 步就是在浏览器下的 Event Loop

图解

浏览器下的 Event Loop
  • heap 回调函数保存处(堆)
  • stack 可以认为是主线程执行的地方(栈)
  • callback queue 事件队列
  • WebAPIs 浏览器中处理 javascript 发出异步任务的程序

macro task 与 micro task

ES6 出现之前,只有一个事件队列,ES6 出现后,多了一个事件队列,叫 micro task (微任务),用来专门放在一些优先级较高的任务,而之前实现的事件队列就叫做 macro task (宏任务)。

那么多了一个事件队列,事件的读取也发生了变化

  1. 加载 script 所对应的 javascript 脚本
  2. 执行 javascript 代码,注册异步任务,保存回调函数
  3. 引入的脚本所有代码执行完毕
  4. 一些 UI 渲染(该步骤不一定会有)
  5. 读取微任务事件队列中最早进入的事件并删除该事件,有则进入下一步,没有执行第 7
  6. 执行该任务对应的回调,执行结束后回到第 5
  7. 读取宏任务事件队列中最早进入的事件
  8. 执行该任务事件对应的回调
  9. 读取微任务事件队列中最早进入的事件并删除该事件,有则进入下一步,没有执行第 11
  10. 执行该任务对应的回调,执行结束后回到第 9
  11. 宏任务回调代码执行完毕
  12. 一些 UI 渲染(该步骤不一定会有)
  13. 回到步骤 5

就是当每次 javascript 线程任务执行结束后,会优先处理微任务事件队列中的事件,与宏任务不一样的地方在于,浏览器会将微任务事件队列中的事件一次性全部处理完在进行 UI 渲染。

图解

微任务与宏任务

能产生微任务的方式:

  • MutationObserver
  • Promise.then catch finally

能产生宏任务的方式:

  • setTimeout
  • setInterval
  • 用户行为
  • Image#onload
  • XMLHttpRequest
  • requestAnimationFrame

参考

阅读更多

JavaScript 对象 & 原型

前言 这次的 why what or how 主题:JavaScript 对象 & 原型。 此类文章在百度上一搜一大把,其实不用再写了,但是本着把这个问题解释的清清楚楚明明白白,还是开始写了。 原因如下: 1. 面试宝典类文章,弄张图片一糊弄,让人觉得自己理解了。 2. 解释类文章,告诉你一堆语法,对语法一顿解释,告诉你就是这样的,还是没说清楚。 3. 很少有文章单独解释这个点!但这个点是基础!真的很重要! 所以本篇文章想说一说对象 & 原型,但为了确保能顺利理解,请先看完 JS 变量存储?栈 & 堆?NONONO!,因为该篇文章从变量存储的角度来解释 JavaScript 对象 & 原型,请确保看完,在看这篇文章。 什么是对象? 既然要说清楚,

By Breeze

node 下的 Event Loop

前言 通过 浏览器下的 Event Loop ,可以得知 javascript 是一门事件驱动的语言, javascript 主线程通过不断的调用事件队列中的事件来完成异步任务。那么 Node 下是否也是如此? Node 下的事件队列 在 Node 官网有这样一篇文章:The Node.js Event Loop, Timers, and process.nextTick() 。 该文章主要讲述了 Node 中是如何处理以及实现 Node 下的 Event Loop。 主要包含以下内容: 事件队列 不同于浏览器,Node 下的有 6 个事件处理阶段,其执行顺序和队列名称如下: ┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │

By Breeze

什么是 HTML 5?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章试图解释清楚一个问题。 这次的 why what or how 主题:现在几乎所有人都知道了 HTML5 ,那么 H5 到底相比于 HTML4 有什么区别呢? 升级版?标准版! HTML5 做为 HTML 标准的第 5 版,于 2014 年发布,相信关注过 HTML5 的发展史的朋友都知道该版本是 WHATWG 和 W3C 握手言和后诞生的,是 W3C 组织与浏览器厂商相互妥协的结果,其中的绝大部分规范都由 WHATWG 组织所制定,之后由 W3C 采纳并入标准中。

By Breeze

什么是 HTML?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章解释清楚一个问题。 作为 why what or how 的第一章,选一个较为简单的话题,什么是 HTML ? 释义 HTML - HyperText Markup Language,超文本标记语言 标记语言,一类以固定的形式描述文档结构或是数据处理细节的语言,一般为纯文本形式,其内容作为其他程序的输入。 因此,HTML 是一种用于描述网页的标记语言,纯文本,其内容作为浏览器的输入。浏览器会解析 HTML 文本内容,最终呈现页面。 那么超文本又是什么? 在网页出现前,人们的阅读习惯是从前往后,从上到下,就像阅读小说,必须从头到尾的看完,思维是单向的,并不存在岔路口。而超文本从字面上理解就是:超出了一般的阅读习惯,看文章的时候思维是发散的,

By Breeze