2023面试真题之浏览器篇

3,539 阅读23分钟

人生当中,总有一个环节,要收拾你一下,让你尝一尝生活的铁拳

大家好,我是柒八九

今天,我们继续2023前端面试真题系列。我们来谈谈关于浏览器的相关知识点。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. 2023前端面试真题之JS篇
  2. 2023面试真题之CSS篇

你能所学到的知识点

  1. 浏览器的进程和线程 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  2. 浏览器渲染过程 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  3. Link rel= "prelaod" 推荐阅读指数⭐️⭐️⭐️⭐️
  4. cookie设置的几种方式 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  5. cookie和session的区别和联系 推荐阅读指数⭐️⭐️⭐️⭐️
  6. 客户端缓存 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  7. LightHouse v8/v9性能指标 推荐阅读指数⭐️⭐️⭐️⭐️⭐️

好了,天不早了,干点正事哇。


浏览器的进程和线程

进程:某个应用程序的执行程序。
线程:常驻在进程内部并负责该进程部分功能的执行程序。

当你启动一个应用程序,对应的进程就被创建。进程可能会创建一些线程用于帮助它完成部分工作,新建线程是一个可选操作。在启动某个进程的同时,操作系统(OS)也会分配内存以用于进程进行私有数据的存储。该内存空间是和其他进程是互不干扰的。

有人的地方就会有江湖,如果想让多人齐心协力的办好一件事,就需要一个人去统筹这些工作,然后通过大喇叭将每个人的诉求告诉对方。而对于计算机而言,统筹的工作归OS负责,OS通过Inter Process Communication (IPC)的机制去传递消息。

网页中的主要进程


浏览器渲染过程(13步)

  1. 页面渲染起始标识

    • 当垂直同步信号(VSync)被排版线程接收到,新的屏幕渲染开始
  2. 输入事件回调

    • 输入事件的数据信息从排版线程主线程的事件回调中传递。
    • 所有输入事件的回调(touchmove/scroll/click)应该先被调用,并且每帧都应该触发,但是这不是必须的
  3. rAFrequestAnimationFrame

    • 这是一个用于屏幕视觉更新的理想的位置
    • 因为,在此处能够获取到垂直同步事件最新的输入数据。
  4. {解析HTML|Parse HTML}

    • 通过指定的解析器,将不能被浏览器识别的HTML文本,转换为浏览器能识别的数据结构:DOM对象
  5. 重新计算样式

    • 新生成被修改的元素进行样式信息计算
    • 生成CSSOM
    • 元素样式和DOM元素结合起来,就会生成Render Tree
  6. {布局|Layout}

    • 计算每个可视元素的位置信息(距离视口的距离和元素本身大小)。
    • 并生成对应的Layout Tree
  7. {更新图层树|Update Layer Tree}

    • Render 树的基础上,我们会将拥有相同z 坐标空间Layout Objects归属到同一个{渲染层|Paint Layer}中。
    • Paint Layer 最初是用来实现{层叠上下文|Stacking Context}
      • 它主要来保证⻚面元素以正确的顺序合成。
  8. {绘制|Paint}

    • 该过程包含两个过程,
    • 第一个过程是绘制操作(painting)
      • 该过程用于生成任何被新生成或者改动元素的绘制信息(包含图形信息和文本信息);
    • 第二个过程是栅格化(Rasterization),
      • 用于执行上一个过程生成的绘制信息。
  9. {页面合成|Composite}

    • 将图层信息(layer)和图块信息提交(commit)到合成线程(排版线程)中。并且在合成线程中会对一些额外的属性进行解释处理。
    • 例如:某些元素被赋值will-change或者一些使用了硬件加速的绘制方式(canvas)。
  10. {栅格化|Rasterize}

    • 在绘制阶段(Paint)生成的绘制记录(Paint Record)被合成线程维护的{图块工作线程|Compositor Tile Worker}所消费。
    • 栅格化是根据图层来完成的,而每个图层由多个图块组成
  11. 页面信息提交:

    • 当页面中所有的图层都被栅格化,并且所有的图块都被提交到{合成线程|Compositor},此时{合成线程|Compositor}将这些信息连同输入数据(input data)一起打包,并发送到GPU线程
  12. 页面显示:

    • 当前页面的所有信息在GPU中被处理,GPU会将页面信息传入到双缓存中的后缓存区,以备下次垂直同步信号到达后,前后缓存区相互置换。然后,此时屏幕中就会显示想要显示的页面信息。
  13. requestIdleCallback:如果在当前屏幕刷新过程中,主线程在处理完上述过程后还有剩余时间(<16.6ms),此时主线程会主动触发requestIdleCallback


Link rel= "prelaod"

<link>元素的rel属性的preload值允许你在HTML的<head>中声明获取请求,指定页面将很快需要的资源,你希望在页面生命周期的早期开始加载这些资源,在浏览器的主线程启动之前。这确保了它们更早可用,不太可能阻塞页面的呈现,从而提高了性能。即使名称包含术语load,它也不会加载和执行脚本,而只是安排以更高的优先级下载和缓存脚本

rel属性设置为preload,它将<link>转换为我们想要的任何资源的预加载器

还需要指定其他的属性:

  • href属性设置资源的路径
  • as属性设置资源类型
<head>
  <meta charset="utf-8" />
  <link rel="preload" href="style.css" as="style" />
  <link rel="preload" href="main.js" as="script" />
</head>

预加载还有其他优点。使用as指定要预加载的内容类型允许浏览器:

  • 更准确地优先考虑资源加载。
  • 存储在缓存中以备将来的请求,并在适当时重用该资源。
  • 对资源应用正确的内容安全策略(CSP)。
    • 内容安全策略(CSP)是一个额外的安全层,它有助于检测和减轻某些类型的攻击,包括
    • 跨站脚本(XSS)
    • 数据注入攻击。
  • 为它设置正确的Accept请求标头。

预加载资源的类型(as的值类型)


cookie设置的几种方式

通常我们有两种方式给浏览器设置或获取Cookie

  1. 第一种 通过 HTTP 方式对 Cookie 进行赋值,又分为 RequestResponse
    • HTTP Response Headers 中的 Set-Cookie Header
    • HTTP Request Headers 中的 Cookie Header
  2. 第二种 通过JavaScriptdocument.cookie进行赋值或取值

两种方式的区别

HTTP Cookie

Set-Cookie Header,除了必须包含Cookie正文,还可以选择性包含6个属性

  1. path
  2. domain
  3. max-age
  4. expires
  5. secure
  6. httponly

它们之间用英文分号和空格("; ")连接;

JS Cookie

在浏览器端,通过 document.cookie 也可以设置CookieJS Cookie 的内容除了必须包含正文之外,还可选5个属性

  1. path
  2. domain
  3. max-age
  4. expires
  5. secure

JS 中设置 CookieHTTP 方式相比较,少了对 HttpOnly 的控制,是因为 JS 不能读写HttpOnly Cookie


http请求什么情况下会携带cookie

Cookie 请求头字段是客户端发送请求到服务器端时发送的信息

如果满足下面几个条件:(domain/http/path

  1. 浏览器端某个 Cookiedomain.a.com) 字段等于请求的域名或者是请求的父域名,请求的域名需要是 a.com/b.a.com 才可以
  2. 都是 http 或者 https,或者不同的情况下 Secure 属性为 false(即 securetrue 的情况下,只有 https 请求才能携带这个 cookie
  3. 要发送请求的路径,跟浏览器端 Cookiepath 属性必须一致,或者是浏览器端 Cookiepath子目录
    • 比如浏览器端 Cookiepath/test,那么请求的路径必须为/test 或者/test/xxxx 等子目录才可以

上面 3 个条件必须同时满足,否则该请求就不能自动带上浏览器端已存在的 Cookie


客户端怎么设置跨域携带 cookie

  1. 前端请求时在request对象中
    • 配置"withCredentials": true;
  2. 服务端responseheader
    • 配置"Access-Control-Allow-Origin", "http://xxx:${port}";
    • 配置"Access-Control-Allow-Credentials", "true" `

cookie和session的区别和联系

SessionCookie安全,Session存储在服务器端的,Cookie存储在客户端

  1. cookie数据存放在客户端,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗
    • 考虑到安全应当使用session
  3. session会在一定时间内保存在服务器上,当访问增多,会比较占用服务器的性能
    • 考虑性能应当使用cookie
  4. 不同浏览器对cookie的数据大小限制不同,个数限制也不相同。
  5. 可以考虑将登陆信息等重要信息存放为session,不重要的信息可以放在cookie中。

客户端缓存

本地存储小容量

  1. Cookie 主要用于用户信息的存储,Cookie的内容可以自动在请求的时候被传递给服务器。
    • 服务器响应 HTTP 请求时,通过发送 Set-Cookie HTTP 头部包含会话信息。
    • 浏览器会存储这些会话信息,并在之后的每个请求中都会通过 HTTP 头部 cookie将它们发回服务器
    • 有一种叫作 HTTP-onlycookieHTTP-only 可以在浏览器设置,也可以在服务器设置,但只能在服务器上读取
  2. Web Storage
    • 提供cookie 之外存储会话数据的途径
    • 提供跨会话持久化存储大量数据的机制
    • Web Storage 的第 2 版定义了两个对象
    • 1.LocalStorage 的数据将一直保存在浏览器内,直到用户清除浏览器缓存数据为止。
    • 2.SessionStorage 的其他属性同LocalStorage,只不过它的生命周期同标签页的生命周期,当标签页被关闭时,SessionStorage也会被清除。 。


本地存储大容量

  1. IndexDB:是浏览器中存储结构化数据的一个方案
    • IndexedDB 是类似于 MySQLWeb SQL Database数据库
  2. WebSQL: 用于存储较大量数据的缓存机制。
    • 已废弃并且被IndexDB所替代
  3. Application Cache:允许浏览器通过manifest配置文件在本地有选择的存储JS/CSS/图片等静态资源的文件级缓存机制
    • 已废弃并且被ServerWorkers所替代
  4. ServerWorkers:离线缓存

{服务工作线程|Service Worker}

{服务工作线程|Service Worker}是一种类似浏览器中代理服务器的线程,可以拦截外出请求缓存响应。这可以让网页在没有网络连接的情况下正常使用,因为部分或全部页面可以从服务工作线程缓存中提供服务。

服务工作线程在两个主要任务上最有用:

  • 充当网络请求的缓存层
  • 启用推送通知

在某种意义上

  • 服务工作线程就是用于把网页变成像原生应用程序一样的工具
  • 服务工作线程对大多数主流浏览器而言就是网络缓存

创建服务工作线程

ServiceWorkerContainer 没有通过全局构造函数创建,而是暴露了 register()方法,该方法以与 Worker()SharedWorker()构造函数相同的方式传递脚本 URL

serviceWorker.js
// 处理相关逻辑

main.js
navigator.serviceWorker.register('./serviceWorker.js');

register()方法返回一个Promise

  • Promise 成功时返回 ServiceWorkerRegistration 对象
  • 在注册失败时拒绝
serviceWorker.js
// 处理相关逻辑

main.js
// 注册成功,成功回调(解决)
navigator.serviceWorker.register('./serviceWorker.js')
 .then(console.log, console.error);
// ServiceWorkerRegistration { ... }


// 使用不存在的文件注册,失败回调(拒绝)
navigator.serviceWorker.register('./doesNotExist.js')
 .then(console.log, console.error);
// TypeError: Failed to register a ServiceWorker:
// A bad HTTP response code (404) was received 
// when fetching the script.

即使浏览器未全局支持服务工作线程,服务工作线程本身对页面也应该是不可见的。这是因为它的行为类似代理,就算有需要它处理的操作,也仅仅是发送常规的网络请求

考虑到上述情况,注册服务工作线程的一种非常常见的模式是基于特性检测,并在页面的 load 事件中操作。

if ('serviceWorker' in navigator) {
 window.addEventListener('load', () => {
     navigator.serviceWorker
     .register('./serviceWorker.js');
 });
} 

如果没有 load 事件做检测,服务工作线程的注册就会与页面资源的加载重叠,进而拖慢初始页面渲染的过程


使用 ServiceWorkerContainer 对象

ServiceWorkerContainer 接口是浏览器对服务工作线程生态的顶部封装

ServiceWorkerContainer 始终可以在客户端上下文中访问:

console.log(navigator.serviceWorker);
// ServiceWorkerContainer { ... }

ServiceWorkerContainer 支持以下事件处理程序

  • oncontrollerchange
    ServiceWorkerContainer 触发 controllerchange 事件时会调用指定的事件处理程序。
    • 在获得新激活的 ServiceWorkerRegistration 时触发。
    • 可以使用 navigator.serviceWorker.addEventListener('controllerchange',handler)处理。
  • onerror
    在关联的服务工作线程触发 ErrorEvent 错误事件时会调用指定的事件处理程序。
    • 关联的服务工作线程内部抛出错误时触发
    • 也可以使用 navigator.serviceWorker.addEventListener('error', handler)处理
  • onmessage
    在服务工作线程触发 MessageEvent 事件时会调用指定的事件处理程序
    • 在服务脚本向父上下文发送消息时触发
    • 也可以使用 navigator.serviceWorker.addEventListener('message', handler)处理

ServiceWorkerContainer 支持下列属性

  • ready:返回 Promise
    • 成功时候返回激活的 ServiceWorkerRegistration 对象。
    • 该Promise不会拒绝
  • controller
    返回与当前页面关联的激活的 ServiceWorker 对象,如果没有激活的服务工作线程则返回 null

ServiceWorkerContainer 支持下列方法

  • register()
    使用接收的 urloptions 对象创建或更新 ServiceWorkerRegistration
  • getRegistration():返回 Promise
    • 成功时候返回与提供的作用域匹配的 ServiceWorkerRegistration对象
    • 如果没有匹配的服务工作线程则返回 undefined
  • getRegistrations():返回 Promise
    • 成功时候返回与 ServiceWorkerContainer 关联的 ServiceWorkerRegistration 对象的数组
    • 如果没有关联的服务工作者线程则返回空数组。
  • startMessage():开始传送通过 Client.postMessage()派发的消息


使用 ServiceWorkerRegistration 对象

ServiceWorkerRegistration 对象表示注册成功的服务工作线程。该对象可以在 register() 返回的解决Promise的处理程序中访问到。通过它的一些属性可以确定关联服务工作线程的生命周期状态

调用 navigator.serviceWorker.register()之后返回的Promise会将注册成功的 ServiceWorkerRegistration 对象(注册对象)发送给处理函数。

同一页面使用同一 URL 多次调用该方法会返回相同的注册对象:即该操作是幂等

navigator.serviceWorker.register('./sw1.js')
  .then((registrationA) => {
     console.log(registrationA);

     navigator.serviceWorker.register('./sw2.js')
       .then((registrationB) => {
         console.log(registrationA === registrationB);
         // 这里结果为true
       });
});

ServiceWorkerRegistration 支持以下事件处理程序

  • onupdatefound
    在服务工作线程触发 updatefound 事件时会调用指定的事件处理程序。
    • 在服务工作线程开始安装新版本时触发,表现为 ServiceWorkerRegistration.installing 收到一个新的服务工作者线程
    • 也可以使用 serviceWorkerRegistration.addEventListener('updatefound',handler)处理

LightHouse v8/v9性能指标 (6个)

  1. FCP(First Contentful Paint)
    • FCP衡量的是,在用户导航到页面后,浏览器呈现第一块DOM内容所需的时间。
    • 页面上的图片非白色<canvas>元素svg都被认为是DOM内容;
    • iframe内的任何内容都不包括在内
    • 优化手段:缩短字体加载时间
  2. SI(Speed Index)
    • SI指数衡量内容在页面加载期间视觉显示的速度Lighthouse首先在浏览器中捕获页面加载的视频,并计算帧之间的视觉进展
    • 优化手段:1. 减少主线程工作 2. 减少JavaScript的执行时间
  3. LCP(Largest Contentful Paint)
    • LCP测量视口最大的内容元素何时呈现到屏幕上。这接近于用户可以看到页面的主要内容
  4. TTI(Time to Interactive)
    • TTI测量一个页面变成完全交互式需要多长时间
    • 当页面显示
    • 有用的内容(由First Contentful Paint衡量),
    • 为大多数可见的页面元素注册了事件处理程序
    • 并且页面在50毫秒内响应用户交互时
    • 页面被认为是完全交互式的。
  5. TBT(Total Blocking Time)
    • TBT 测量页面被阻止响应用户输入(例如鼠标点击、屏幕点击或按下键盘)的总时间。总和是FCPTTI之间所有长时间任务的阻塞部分之和
    • 任何执行时间超过 50 毫秒的任务都是长任务。50 毫秒后的时间量是阻塞部分。
    • 例如,如果检测到一个 70 毫秒长的任务,则阻塞部分将为 20 毫秒
  6. CLS(Cumulative Layout Shift)
    • 累积布局偏移 (CLS) 是测量视觉稳定性的一个以用户为中心的重要指标
    • CLS 较差的最常见原因为:
    • 1.无尺寸的图像
    • 2.无尺寸的嵌入和 iframe
    • 3.动态注入的内容
    • 优化手段1. 除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容 2. 倾向于选择transform动画

优化LCP

导致 LCP 不佳的最常见原因是:

  1. 缓慢的服务器响应速度
  2. 阻塞渲染的 JavaScriptCSS
  3. 缓慢的资源加载速度
  4. 客户端渲染

缓慢的服务器响应速度

使用{首字节时间|Time to First Byte}(TTFB) 来测量您的服务器响应时间

  1. 将用户路由到附近的 CDN
  2. 缓存资产
    • 如果 HTML静态的,且不需要针对每个请求进行更改,那么缓存可以防止网页进行不必要的重建。通过在磁盘上存储已生成 HTML 的副本,服务器端缓存可以减少 TTFB 并最大限度地减少资源使用。
    • 配置反向代理(Varnish、nginx)来提供缓存内容
    • 使用提供边缘服务器的 CDN
  3. 优先使用缓存提供 HTML 页面
    • 安装好的 Service Worker 会在浏览器后台运行,并可以拦截来自服务器的请求。此级别的程序化缓存控制使得缓存部分或全部 HTML 页面内容得以实现,并且只会在内容发生更改时更新缓存。
  4. 尽早建立第三方连接
    • 第三方域的服务器请求也会影响 LCP,尤其是当浏览器需要这些请求来在页面上显示关键内容的情况下。
    • 使用rel="preconnect"来告知浏览器您的页面打算尽快建立连接
    • <link rel="preconnect" href="https://example.com" />
    • 还可以使用dns-prefetch更快地完成 DNS 查找
    • <link rel="dns-prefetch" href="https://example.com" />
    • 尽管两种提示的原理不同,但对于不支持preconnect的浏览器,可以考虑将dns-prefetch做为后备。

阻塞渲染的 JavaScript 和 CSS

  1. 减少 CSS 阻塞时间
    1. 削减 CSS: CSS 文件可以包含空格缩进注释等字符。这些字符对于浏览器来说都不是必要的,而对这些文件进行削减能够确保将这些字符删除。使用模块打包器或构建工具,那么可以在其中包含一个相应的插件来在每次构建时削减 CSS 文件:对于 webpack5css-minimizer-webpack-plugin i
    2. 延迟加载非关键 CSS:使用 Chrome 开发者工具中的代码覆盖率选项卡查找您网页上任何未使用的 CSS
      对于任何初始渲染时不需要CSS,使用 loadCSS 来异步加载文件,这里运用了rel="preload"onload
      <link rel="preload" href="stylesheet.css" as="style" onload="this.rel='stylesheet'">
    3. 内联关键 CSS:把用于首屏内容的任何关键路径 CSS 直接包括在<head>中来将这些 CSS 进行内联。
  2. 减少 JavaScript 阻塞时间
    1. 缩小和压缩 JavaScript 文件:
      缩小是删除空格和不需要的代码,从而创建较小但完全有效的代码文件的过程。Terser 是一种流行的 JavaScript 压缩工具;
      压缩是使用压缩算法修改数据的过程Gzip 是用于服务器和客户端交互的最广泛使用的压缩格式。Brotli 是一种较新的压缩算法,可以提供比 Gzip 更好的压缩结果。
      静态压缩涉及提前压缩和保存资产。这会使构建过程花费更长的时间,尤其是在使用高压缩级别的情况下,但可确保浏览器获取压缩资源时不会出现延迟。如果您的 web 服务器支持 Brotli,那么请使用 BrotliWebpackPlugin 等插件通过 webpack 压缩资产,将其纳入构建步骤。否则,请使用 CompressionPlugin 通过 gzip 压缩您的资产。
    2. 延迟加载未使用的 JavaScript
      通过代码拆分减少 JavaScript 负载,- SplitChunksPlugin
    3. 最大限度减少未使用的 polyfill

最大限度减少未使用的 polyfill

Babel 是最广泛使用的编译代码的工具,它将包含较新语法的代码编译成不同浏览器和环境都能理解的代码。

要使用 Babel 只传递用户需要的信息

  1. 确定浏览器范围
  2. @babel/preset-env设置适当的浏览器目标
  3. 使用<script type="module">停止向不需要的浏览器发送已翻译的代码
确定浏览器范围

翻译代码通常会导致文件的大小大于其原始形式。通过最小化编译量,可以减少捆绑包的大小,从而提高网页的性能。

Babel 没有包含特定的插件来选择性地编译您正在使用的某些语言特性,而是提供了许多预设,将插件捆绑在一起。使用@babel/preset-env 仅包含您计划瞄准的浏览器所需的转换和填充。

@Babel/preset-env 包含在 Babel 配置文件的预置数组中.babelrc:

{
 "presets": [
   [
     "@babel/preset-env",
     {
       "targets": ">0.25%"
     }
   ]
 ]
}

通过向浏览器字段添加适当的查询,可以使用 target 字段指定要包含的浏览器版本。@babel/preset-env 集成了 Browserslist,这是一种在不同工具之间共享的针对浏览器的开源配置。浏览器列表文档中有一个兼容查询的完整列表。另一种选择是使用。Browserslstrc 文件来列出您希望定位的环境。

> 0.25%值告诉 Babel 只包含支持占全球使用量0.25% 以上的浏览器所需的转换。这样可以确保您的捆绑包不包含用于只有很小一部分用户使用的浏览器的不必要的已翻译代码。

在大多数情况下,这种方法比使用以下配置更好:

"targets": "last 2 versions"

“last 2 versions”的值会转换每个浏览器的最后两个版本的代码,这意味着支持停止使用的浏览器,如 Internet Explorer。如果您不希望使用这些浏览器访问应用程序,那么这可能会不必要地增加捆绑包的大小。


缓慢的资源加载速度

  1. 优化和压缩图像
    • 对于许多网站来说,在页面加载完毕后,图像会是视图中的最大元素。这种情况的常见示例包括首图、大型轮播或横幅图像
    • 改善这些类型的图像进行加载和渲染所需的时间将直接提升 LCP 的速度。实现方式:
    • 首先考虑不使用图像。如果图像与内容无关,请将其删除。
    • 压缩图像(例如使用 Imagemin
    • 将图像转换为更新的格式(JPEG 2000、JPEG XR 或 WebP)
    • 使用响应式图像
    • 考虑使用图像 CDN
  2. 预加载重要资源
    • 有时,在某个 CSSJavaScript 文件中声明或使用的重要资源可能会比所期望的要晚一点被获取,例如深藏在应用程序众多 CSS 文件中的某个字体
    • 知道某个特定资源应该被优先获取,请使用<link rel="preload">来更加及时地获取该资源
    • 多种类型的资源都可以进行预加载,但您应该首先侧重于预加载关键资产,例如字体首屏图像视频,以及关键路径 CSS 或 JavaScript
    • <link rel="preload" as="script" href="script.js" />
    • <link rel="preload" as="style" href="style.css" />
    • <link rel="preload" as="image" href="img.png" />
  3. 压缩文本文件
    • 压缩诸如 GzipBrotli 之类的算法可以显著缩减在服务器和浏览器之间传输的文本文件HTMLCSSJavaScript)大小。所有浏览器都有效支持 Gzip,而 Brotli 几乎可以在所有较新的浏览器中使用,并能提供更好的压缩结果。
    • 首先,检查您的服务器是否已经自动压缩文件。大多数托管平台、CDN 和反向代理服务器在默认情况下都会对资产进行压缩编码,或者使您能够轻松配置资产。
    • 如果您需要对服务器进行修改来使其压缩文件,请考虑使用 Brotli,而不是 gzip,因为 Brotli 可以提供更好的压缩率
    • 选择您要使用的压缩算法后,请在构建过程中提前压缩资产,而不是在浏览器请求时实时压缩资产。这样能够最大限度地减少服务器开销并防止在发出请求时出现延迟,尤其是在使用高压缩比的情况下。
  4. 使用 Service Worker 缓存资产
    • Service Worker 可用于缓存任何静态资源,并在收到重复请求时将资源直接提供给浏览器,而无需通过网络。
    • 使用 Service Worker 预缓存关键资源可以显著减少资源加载时间

客户端渲染

在搭建客户端渲染的网站时,请考虑以下优化:

  1. 最小化关键 JavaScript
  2. 使用服务端渲染
  3. 使用预渲染

最小化关键 JavaScript

如果您网站上的内容只有在一定数量的 JavaScript 完成下载后才变得可见或可以与之交互:尽可能缩减您的代码包的大小就变得尤为重要。这可以通过以下方式实现:

  1. 削减 JavaScript
  2. 延迟加载未使用的 JavaScript
  3. 最大限度减少未使用的 polyfill

使用预渲染

预渲染是一种独立的技巧,该技巧比服务端渲染简单。无头浏览器是一种没有用户界面的浏览器,我们会用无头浏览器在构建期间生成每个路由的静态 HTML 文件。然后可以将这些文件与应用程序所需的 JavaScript 包一起进行运送。


优化TTI

TTI有特别大影响的一个改进是延迟或删除不必要的JavaScript工作。寻找优化JavaScript的机会。特别是,考虑通过代码拆分和应用PRPL模式来减少JavaScript的有效负载。对第三方JavaScript的优化也为一些站点带来了显著的改进。

使用 PRPL 模式实现即时加载

PRPL 是四个英文单词的首字母缩写,它描述了一种可以提高网页加载速度和交互性的模式

  1. 推送 (Push)(或预加载)最重要的资源。
  2. 尽快渲染 (Render) 初始路线。
  3. 预缓存 (Pre-cache) 剩余资产。
  4. 延迟加载 (Lazy load) 其他路线和非关键资产。

后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。