本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。
美味值:🌟🌟🌟🌟🌟
口味:仔梅烧小排
开篇我们先来简单回顾下历史,从 1993 年发布的第一款“好用”的浏览器 Mosaic,到 1994 年网景公司推出的红极一时的 Navigator 浏览器,图形用户界面化的浏览器终于开始推动了 Web 技术的普及和发展。
微软也随后推出了 IE,加入战场并取得浏览器大战“一战”的胜利。战败的网景公司索性将 Navigator 源代码开源,创建了 Mozilla 基金会,并于 2004 年发布了 Firefox 浏览器。
苹果公司于 2003 年发布了 Safari 浏览器,Google 公司于 2008 年发布了 Chrome 浏览器。Chrome 浏览器在浏览器大战的“二战”中技压群雄,拔得头筹。现如今也是前端工程师最喜爱的浏览器,没有之一。
Chrome 浏览器从 2007 年以前的单进程架构到现在的多进程架构,浏览器的架构在不断的升级,变得更加稳定、更加流畅、更加安全。目前 Chrome 的浏览器包括如下进程:
不过,软件工程可没有银弹。浏览器的架构体系也随着调整变得更加复杂,也会有更高的资源占用。
那么如何寻求一种在资源占用
和复杂架构
体系之间的平衡便成为了一个难题。
小孩子才做选择,鱼和熊掌我都要!
Chrome 团队在 2016 年使用“面向服务的架构”(Services Oriented Architecture,简称 SOA)的思想设计了新的 Chrome 架构。
他们将模块重构成独立的服务(Service),服务运行在独立的进程中,想要访问的话必须使用定义好的接口,通过 IPC 来进行通信。这样的架构无疑更加内聚、松耦合、易于维护和扩展。
从输入 URL 到页面展示,这中间发生了什么?
这是一道十分常见的面试题,不过大多数人回答这个问题时都不够系统和全面,可见这道题能够充分考察应试者的知识深度。
我画了一张图整理了浏览器的导航渲染流程,下面我们来一起查缺补漏。
application/octet-stream 代表字节流类型,浏览器会按照下载类型来处理。
(当然在第 7 点中还有 300、303 等 3xx 的状态码,具体含义可以参考我的这一篇专栏 那些年与面试官交手过的HTTP问题)
process-per-site-instance 默认策略
:每个标签对应一个渲染进程,如果从一个页面打开了一个新页面,新打开的页面与当前页面还属于同一个站点的话,那么新页面会复用当前页面的渲染进程。
渲染流程在上图中一并画了出来,需要经过以下几个阶段:
因为渲染流程的内容比较多,本文先不详细展开,后面我们再开一篇专栏进行讲解。
DNS 的解析是一个递归流程,顺序如下图中数字标记所示:
先来看一段简单的示例代码:
function hello () { var name = '前端食堂' var food = { name: '回锅肉' } function world () { var description = { slogan: '吃好每一顿饭' } } world() } hello()
上面的代码所对应的内存堆栈空间如下图所示:
栈中的垃圾回收比较简单,当一个函数执行结束后,JavaScript 引擎会通过向下移动 ESP 来销毁函数调用栈中所保存的执行上下文,ESP 就是记录当前执行状态的指针
。
先来看两个概念,能够帮助我们更好的理解堆中的垃圾回收操作。
堆中的垃圾回收策略都是建立在代际假说
的基础之上,代际假说有以下两个特点:
在 Chrome 浏览器引擎 V8 中会把堆分为新生代
和老生代
两个区域,如下图所示:
顾名思义,生存时间短的对象放在新生区中,生存时间久的对象放在老生区中。
堆中的垃圾回收需要用到垃圾回收器,分为主垃圾回收器
和副垃圾回收器。
负责新生区的垃圾回收,新生区区域不大(为了执行效率),回收频繁。
新生区中使用了 Scavenge 算法
,该算法会把新生区的空间划分为两个区域,一半是对象区域,一半是空闲区域。
副垃圾回收器的工作流程如下:
翻转的这种操作可以让对象区和空闲区无限重复的使用,不过由于新生区空间并不大,很容易会被存活的对象塞满。所以 V8 引擎采用了对象晋升
的策略,经过两次垃圾回收后依然还能存活的对象会被晋升到老生区中。
负责老生区中的垃圾回收,老生区中对象占用空间大,对象存活时间长。
除了上文说到的新生区中晋升的对象,一些大的对象也会直接被分配到老生区。
主垃圾回收器是使用了标记 - 清除(Mark-Sweep)
的算法,工作流程如下:
标记 - 整理(Mark-Compact)
,整理时可以让存活的对象都向一端移动,然后直接清除掉端边界以外的内存。垃圾回收操作会暂停
JavaScript 的运行,回收完毕后才会恢复
执行,这种行为就是全停顿。
为了降低全停顿所带来的卡顿,V8 引擎采用了增量标记(Incremental Marking)
算法进行优化,将标记过程分为一个个小任务,这些小任务的执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样就不会有明显的卡顿了。
当然,V8 所采用的优化方案不只这一种,而是多种方案综合使用的,除了增量回收还有并行回收、并发回收
等。
如果你了解 React 的 Concurrent 模式中时间切片的原理,它的实现思想是不是与增量标记算法有异曲同工之妙呢。
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 来获取这些指标的话可以参考下面的获取方法:
LCP用于衡量标准报告视口内可见的最大图像或文本块的渲染时间,为了提供良好的用户体验,网站应努力在开始加载页面的前2.5 秒内进行“最大内容绘制”。
优化LCP方案
FID用于衡量从用户第一次与页面进行交互到浏览器实际上能够开始处理事件处理程序的时间。为了提供良好的用户体验,网站应努力使首次输入延迟小于 100 毫秒。
下图中米色方块代表主线程处于忙碌阶段,如果此时用户进行输入,则它必须等待任务完成时才能响应输入,等待的时间也就是此页面上该用户的 FID 值。
优化FID方案
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方案
好了,本文到这里就结束了,文中参考的链接都整理到了下面,大家可以自行查阅。
1.如果你觉得食堂酒菜还合胃口,就点个赞支持下吧,你的赞是我最大的动力。
2.关注公众号前端食堂,吃好每一顿饭!
3.点赞、评论、转发 === 催更!