乐趣区

关于chrome:图解浏览器

本文已收录在前端食堂同名仓库 Github github.com/Geekhyt,欢迎光临食堂,如果感觉酒菜还算可口,赏个 Star 对食堂老板来说是莫大的激励。

美味值:????????????????????

口味:仔梅烧小排

01 浏览器架构演进

开篇咱们先来简略回顾下历史,从 1993 年公布的第一款“好用”的浏览器 Mosaic,到 1994 年网景公司推出的红极一时的 Navigator 浏览器,图形用户界面化的浏览器终于开始推动了 Web 技术的遍及和倒退。

微软也随后推出了 IE,退出战场并获得浏览器大战“一战”的胜利。战败的网景公司索性将 Navigator 源代码开源,创立了 Mozilla 基金会,并于 2004 年公布了 Firefox 浏览器。

苹果公司于 2003 年公布了 Safari 浏览器,Google 公司于 2008 年公布了 Chrome 浏览器。Chrome 浏览器在浏览器大战的“二战”中技压群雄,拔得头筹。现如今也是前端工程师最青睐的浏览器,没有之一。

Chrome 浏览器从 2007 年以前的单过程架构到当初的多过程架构,浏览器的架构在一直的降级,变得更加稳固、更加晦涩、更加平安。目前 Chrome 的浏览器包含如下过程:

  • 1 个浏览器 (Browser) 主过程
  • 1 个 GPU 过程
  • 1 个网络 (NetWork) 过程
  • 多个渲染过程(运行在沙箱模式下)
  • 多个插件过程

不过,软件工程可没有银弹。浏览器的架构体系也随着调整变得更加简单,也会有更高的资源占用。

那么如何寻求一种在 资源占用 简单架构 体系之间的均衡便成为了一个难题。

小孩子才做抉择,鱼和熊掌我都要!

Chrome 团队在 2016 年应用“面向服务的架构”(Services Oriented Architecture,简称 SOA)的思维设计了新的 Chrome 架构。

他们将模块重形成独立的服务(Service),服务运行在独立的过程中,想要拜访的话必须应用定义好的接口,通过 IPC 来进行通信。这样的架构无疑更加内聚、松耦合、易于保护和扩大。

02 浏览器导航渲染流程

从输出 URL 到页面展现,这两头产生了什么?

这是一道非常常见的面试题,不过大多数人答复这个问题时都不够零碎和全面,可见这道题可能充沛考查应试者的常识深度。

我画了一张图整顿了浏览器的导航渲染流程,上面咱们来一起查缺补漏。

导航流程

  1. 用户在地址栏输出内容后,地址栏会将输出的内容进行合成 URL。
  2. 当用户输出完内容并按下回车键时,浏览器会在以后页面执行 beforeunload 事件,你能够在这个钩子中询问是否要来到以后页面,常见于一些表单提交的场景。
  3. 接下来开始导航流程,浏览器进入加载状态。
  4. 浏览器的网络过程会先查找缓存中是否存在该资源,有的话间接返回,如果没有的话会发动 URL 申请。
  5. 接下来首先要进行的是 DNS 解析,取得申请域名的服务器的 IP 地址(这个过程我也画了一张图,放在下文),如果协定是 HTTPS,还须要建设 TLS 连贯。
  6. 接着利用指标服务器的 IP 地址建设 TCP 连贯(三次握手),构建 HTTP 申请报文,发动申请。服务器收到申请后,会依据申请信息生成响应报文。
  7. 浏览器的网络过程接管到响应报文后进行解析,如果状态码是 301 或者 302,则须要获得响应头中的 Location 对应的地址进行重定向,再从新发动申请。
  8. 如果状态码是 200,浏览器会依据响应头中的 Content-Type 字段来辨认返回的响应体数据类型,从而进行不同的流程。如 text/html 代表 html 格局,

application/octet-stream 代表字节流类型,浏览器会依照下载类型来解决。

  1. 如果是 HTML,浏览器会遵循 process-per-site-instance 默认策略筹备渲染过程,筹备好后就提交文档(将网络过程接管到的数据提交给渲染过程)。文档被提交后,渲染过程便开始进行页面解析和子资源的加载。

(当然在第 7 点中还有 300、303 等 3xx 的状态码,具体含意能够参考我的这一篇专栏 那些年与面试官交手过的 HTTP 问题)

process-per-site-instance 默认策略:每个标签对应一个渲染过程,如果从一个页面关上了一个新页面,新关上的页面与以后页面还属于同一个站点的话,那么新页面会复用以后页面的渲染过程。

渲染流程

渲染流程在上图中一并画了进去,须要通过以下几个阶段:

  1. 构建 DOM 树
  2. 款式计算
  3. 布局
  4. 分层
  5. 绘制
  6. 分块
  7. 光栅化
  8. 合成

因为渲染流程的内容比拟多,本文先不具体开展,前面咱们再开一篇专栏进行解说。

DNS

DNS 的解析是一个递归流程,程序如下图中数字标记所示:

  • 根 DNS 服务器:返回顶级域 DNS 服务器的 IP 地址
  • 顶级 DNS 服务器:返回权威 DNS 服务器的 IP 地址
  • 权威 DNS 服务器:返回相应主机的 IP 地址

03 垃圾回收

栈中的垃圾数据

先来看一段简略的示例代码:

function hello () {
    var name = '前端食堂'
    var food = {name: '回锅肉'} 
    function world () {var description = { slogan: '吃好每一顿饭'}
    }
    world()}
hello()

下面的代码所对应的内存堆栈空间如下图所示:

栈中的垃圾回收比较简单,当一个函数执行完结后,JavaScript 引擎会通过向下挪动 ESP 来销毁函数调用栈中所保留的执行上下文,ESP 就是记录以后执行状态的指针

堆中的垃圾数据

先来看两个概念,可能帮忙咱们更好的了解堆中的垃圾回收操作。

代际假说

堆中的垃圾回收策略都是建设在 代际假说 的根底之上,代际假说有以下两个特点:

  1. 大部分对象在内存中存在的工夫很短,简略来说,就是很多对象一经分配内存,很快就变得不可拜访。
  2. 不死的对象,会活得更久。

分代收集

在 Chrome 浏览器引擎 V8 中会把堆分为 新生代 老生代 两个区域,如下图所示:

顾名思义,生存工夫短的对象放在新生区中,生存工夫久的对象放在老生区中。

堆中的垃圾回收须要用到垃圾回收器,分为 主垃圾回收器 副垃圾回收器。

副垃圾回收器

负责新生区的垃圾回收,新生区区域不大(为了执行效率),回收频繁。

新生区中应用了 Scavenge 算法,该算法会把新生区的空间划分为两个区域,一半是对象区域,一半是闲暇区域。

副垃圾回收器的工作流程如下:

  1. 首先对对象区域中的垃圾进行标记。
  2. 标记实现后,副垃圾回收器会将存活的对象复制到闲暇区域中,为了防止产生内存碎片,还须要进行有序的排列,有序排列相当于内存整理。
  3. 实现复制后,将对象区域和闲暇区域进行翻转,就实现了垃圾回收的操作。

翻转的这种操作能够让对象区和闲暇区有限反复的应用,不过因为新生区空间并不大,很容易会被存活的对象塞满。所以 V8 引擎采纳了 对象降职 的策略,通过两次垃圾回收后仍然还能存活的对象会被降职到老生区中。

主垃圾回收器

负责老生区中的垃圾回收,老生区中对象占用空间大,对象存活工夫长。

除了上文说到的新生区中降职的对象,一些大的对象也会间接被调配到老生区。

主垃圾回收器是应用了 标记 - 革除 (Mark-Sweep) 的算法,工作流程如下:

  1. 首先是标记阶段,从一组根元素开始递归遍历,能达到的元素就是流动对象,否则就是垃圾。
  2. 而后应用标记 – 革除算法进行垃圾回收,不过回收后会产生大量不间断的内存碎片。
  3. 于是又产生了另外一种算法 标记 - 整顿(Mark-Compact),整顿时能够让存活的对象都向一端挪动,而后间接革除掉端边界以外的内存。

全进展

垃圾回收操作会 暂停 JavaScript 的运行,回收结束后才会 复原 执行,这种行为就是全进展。

为了升高全进展所带来的卡顿,V8 引擎采纳了 增量标记(Incremental Marking) 算法进行优化,将标记过程分为一个个小工作,这些小工作的执行工夫比拟短,能够穿插在其余的 JavaScript 工作两头执行,这样就不会有显著的卡顿了。

当然,V8 所采纳的优化计划不只这一种,而是多种计划综合应用的,除了增量回收还有 并行回收、并发回收 等。

  • 并行回收:垃圾回收器会应用多个辅助线程来并行执行垃圾回收
  • 并发回收:回收线程在执行 JavaScript 的过程中,辅助线程在后盾执行垃圾回收

如果你理解 React 的 Concurrent 模式中工夫切片的原理,它的实现思维是不是与增量标记算法有殊途同归之妙呢。

04 外围网页指标 Core Web Vitals

Google 大佬推出了 Core Web Vitals:目标是为了更好的简化场景,帮忙网站专一于最重要的指标以晋升用户体验。

在 2020 年次要关注三个方面:加载、交互性和视觉稳定性,并包含以下指标:

掂量所有 Core Web Vitals 最简略的办法就是应用 web-vitals 库,应用起来就像调用单个函数一样简略。

import {getCLS, getFID, getLCP} from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

也能够应用 Chrome 插件 Web Vitals Chrome 来帮忙咱们测量这些指标。

如果想要间接通过 Web API 来获取这些指标的话能够参考上面的获取办法:

  • 在 JavaScript 中测量 LCP
  • 在 JavaScript 中测量 FID
  • 在 JavaScript 中测量 CLS

LCP Largest Contentful Paint 最大内容绘制

LCP 用于衡量标准报告视口内可见的最大图像或文本块的渲染工夫,为了提供良好的用户体验,网站应致力在开始加载页面的前 2.5 秒内进行“最大内容绘制”。

优化 LCP 计划

FID First Input Delay 首次交互提早

FID 用于掂量从用户第一次与页面进行交互到浏览器实际上可能开始处理事件处理程序的工夫。为了提供良好的用户体验,网站应致力使首次输出提早小于 100 毫秒。

下图中米色方块代表主线程处于繁忙阶段,如果此时用户进行输出,则它必须期待工作实现时能力响应输出,期待的工夫也就是此页面上该用户的 FID 值。

优化 FID 计划

CLS Cumulative Layout Shift 累积布局偏移

CLS 用于测量在页面的整个生命周期中产生的每一个意外的布局挪动,它代表所有独自布局转移分数的总和。为了提供良好的用户体验,网站应致力使 CLS 分数小于 0.1。

布局偏移分数

浏览器将查看视口大小以及两个渲染帧之间的视口中不稳固元素的挪动。

布局偏移分数是该静止的两个指标的乘积:影响分数和间隔分数

layout shift score = impact fraction * distance fraction

影响分数

前一帧和以后帧的所有不稳固元素的可见区域的并集(占视口总面积的一部分)是以后帧的影响分数。

在上图中,有一个元素在一帧中占据了视口的一半。而后,在下一帧中,元素下移视口高度的 25%。红色的虚线矩形示意两个帧中元素的可见区域的并集,在这种状况下,其为总视口的 75%,因而其影响分数为 0.75。

间隔分数

布局偏移分数方程的另一部分 测量不稳固元素绝对于视口挪动的间隔。间隔分数是任何不稳固元素在框架中(程度或垂直)挪动的最大间隔除以视口的最大尺寸(宽度或高度,以较大者为准)。

在上图中,最大视口尺寸是高度,不稳固元素曾经挪动了视口高度的 25%,所以间隔分数是 0.25。

所以,布局偏移分数:0.75 * 0.25 = 0.1875

优化 CLS 计划

好了,本文到这里就完结了,文中参考的链接都整顿到了上面,大家能够自行查阅。

站在伟人的肩膀上

  • 图解 Google V8 李兵
  • 浏览器工作原理与实际 李兵
  • Core Web Vitals https://web.dev/vitals/
  • web-vitals https://github.com/GoogleChro…
  • LCP https://web.dev/lcp/
  • FID https://web.dev/fid/
  • CLS https://web.dev/cls/
  • 优化 FID 计划 https://web.dev/optimize-fid/
  • 优化 LCP 计划 https://web.dev/optimize-lcp/
  • 优化 CLS 计划 https://web.dev/optimize-cls/

爱心三连击

1. 如果你感觉食堂酒菜还合胃口,就点个赞反对下吧,你的 是我最大的能源。

2. 关注公众号前端食堂,吃好每一顿饭!

3. 点赞、评论、转发 === 催更!

退出移动版