❝歌德说:”一旦你信任了你自己,你就会明白怎样生活“ ❞
大家好,我是「柒八九」。
今天,我们继续「前端面试」的知识点。我们来谈谈关于「Web性能优化」的相关知识点。
该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
好了,天不早了,干点正事哇。
❝
WebWorker
❞
对所有「网络通信」都有决定性影响的两个方面:
ms
)Mbit/s
)延迟和带宽
「延迟」是消息message 或分组packet从起点到终点经历的时间。
延迟主要分为4类。
❝传播延迟/传输延迟/处理延迟/排队延迟的时间总和,就是客户端到服务器的「总延迟时间」 ❞
延迟中相当大的一部分往往花在了「最后几公里」,而不是在横跨大洋或大陆时产生的,这就是所谓的「最后一公里」问题。
最后一公里的延迟与提供商、部署方法、网络拓扑,甚至一天中的哪个时段都有很大关系。作为最终用户,如果你想提高自己上网的速度,那选择延迟最短的 网络业务提供商Internet Service Provider(简称ISP)是最关键的。
❝
JavaScript
环境实际上是运行在操作系统(OS
)中的「虚拟环境」 ❞
在浏览器中每打开一个页面,就会分配一个它「自己的环境」:即每个页面都有自己的内存、事件循环、DOM。并且每个页面就相当于一个「沙盒」,不会干扰其他页面。
而使用「Worker 线程」,浏览器可以在「原始页面环境之外」再分配一个完全独立的「二级子环境」。这个子环境不能与依赖单线程交互的 API
(如 DOM
)互操作,但「可以与父环境并行」执行代码。
Worker
线程规范中定义了「三种主要」的工作者线程
Web Worker
或 Worker
,是一种实用的工具,可以让脚本「单独创建」一个 JS 线程,以执行委托的任务。专用工作线程是最简单的 Web 工作者线程,网页中的脚本可以创建专用工作者线程来执行在「页面线程之外」的其他任务。这样的线程可以与父页面交换信息、发送网络请求、执行文件输入/输出、进行「密集计算」、处理「大量数据」,以及实现其他不适合在页面执行线程里做的任务(否则会导致页面响应迟钝)。
Worker
构造函数,然后构造函数再在「后台异步加载」脚本并实例化工作线程worker.js
// 进行密集计算 bala bala
main.js
const worker = new Worker( 'worker.js');
console.log(worker); // Worker {} // {3}
Blob
Blob
创建了 URL
对象URL
对象,传给了 Worker()
构造函数Blob
❝通过「行内构建工作线程」有一个弊端,就是无法通过
import/require
引入一些第三方的包。 ❞
虽然在worker
中可以使用importScripts()
加载任意脚本,但是那些都是在worker
同目录或者是利用绝对路径进行引用。很不方便。
在Webpack
最为打包工具下,使用指定的loader
--worker-loader
可以解决上面的问题。
进行本地按照
$ npm install worker-loader --save-dev
配置webpack
-config.js
module.exports = {
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: "worker-loader" },
},
],
},
};
通过如上的配置,我们就可以像写常规的组件或者工具方法一些,「肆无忌惮」的通过import
引入第三方包。
longTime.js
const A = require('A')
self.onmessage = function (e) {
// A处理一些特殊场景
}
服务工作线程Service Worker是一种类似浏览器中「代理服务器」的线程,可以「拦截外出请求」和「缓存响应」。这可以让网页在「没有网络连接」的情况下正常使用,因为部分或全部页面可以从服务工作线程缓存中提供服务。
服务工作线程在两个主要任务上最有用:充当「网络请求的缓存层」
❝在某种意义上
❞
❝服务工作线程的一个主要能力是可以「通过编程方式实现真正的网络请求缓存机制」 ❞
有如下特点:
❝服务工作线程「最重要」的一个特性就是「拦截网络请求」 ❞
服务工作线程作用域中的「网络请求会注册为 fetch
事件」。这种拦截能力「不限于」 fetch()
方法发送的请求,也能拦截对 JavaScript
、CSS
、图片和HTML(包括对主 HTML 文档本身)等资源发送的请求。
这些请求可以来自 JavaScript,也可以通过 <script>
、<link>
或<img>
标签创建。
让服务工作线程能够决定如何处理 fetch
事件的方法是 event.respondWith(
)。该方法接收Promise,该Promise会解决为一个 Response
对象。该 Response
对象实际上来自哪里完全由服务工作线程决定。可以来自「网络」,来自「缓存」,或者「动态创建」。
❝这个策略就是「简单地转发」
fetch
事件 ❞
那些绝对「需要发送到服务器的请求」例如 POST
请求就适合该策略。
self.onfetch = (fetchEvent) => {
fetchEvent.respondWith(fetch(fetchEvent.request));
};
❝这个策略其实就是「缓存检查」 ❞
对于任何肯定有缓存的资源(如在安装阶段缓存的资源),可以采用该策略。
self.onfetch = (fetchEvent) => {
fetchEvent.respondWith(caches.match(fetchEvent.request));
};
通常一个页面有「三个阶段」
「DOM」:是HTML
页面在解析后,基于对象的表现形式。
每个浏览器都「需要一些时间解析HTML」。并且,「清晰的语义标记」有助于减少浏览器解析HTML所需的时间。(不完整或者错误的语义标记,还需要浏览器根据上下文去分析和判断)
CSSOM
也是一个基于对象的树。它「负责处理与DOM树相关的样式」。
❝一般来说,CSS被认为是一种渲染阻断Render-Blocking资源。 ❞
什么是「渲染阻断」?渲染阻塞资源是一个组件,它将「不允许浏览器渲染整个DOM树,直到给定的资源被完全加载」。
CSS
是一种渲染阻断资源,因为在CSS完全加载之前,你无法渲染树。
起初,页面中所有CSS
信息都被存放在一个文件中 。现在,开发人员通过一些技术手段,能够将CSS
文件「分割」开来,「只在渲染的早期阶段提供关键样式」。
「JavaScript
是一种用来操作DOM
的语言」。这些「操作花费时间」,并增加网站的整体加载时间。所有,
❝
JavaScript
代码被称为 解析器阻塞Parser Blocking资源。 ❞
什么是「解析器阻塞」?当需要「下载」和「执行」JavaScript
代码时,浏览器会「暂停执行和构建DOM树」。当JavaScript代码被执行完后,DOM树的构建才继续进行。
所以才有, 「JavaScript
是一种昂贵的资源」的说法。
记住,
❝关键渲染路径Critical Rendering Path都是「关于
HTML
、CSS
和Javascript
的」 ❞
如果你希望优化任何框架中的关键渲染路径,你需要在上述指标上下功夫并加以改进。
❝
JavaScript
和 CSS
改成内联的形式 (性能提升不是很大)JavaScript
代码没有 DOM
或者 CSSOM
的操作,则可以改成 sync
或者 defer
属性CSS
和 JavaScript
资源HTML
、CSS
、JavaScript
文件中一些「注释内容」CDN
来减少每次 RTT
时长❞
加载的关键是 "懒加载"。任何媒体资源、CSS
、JavaScript
、图像、甚至HTML
都可以被懒加载。每次加载「有限的页面的内容」,可以提高关键渲染路径。
CSS
、JavaScript
和 HTML
。button
添加一个事件监听,只有在用户点击按钮时才加载脚本。Webpack
来完成懒加载功能。当使用Preload
时,它被用于HTML文件中没有的文件,但在渲染或解析JavaScript或CSS文件的时候。有了Preload
,浏览器就会下载资源,在资源可用的时候就会执行。
<link>
标签」。Expires
和 Cache-control:max-age=x
(强缓存)Etag
和 If-None-Match
(协商缓存)优化被分成两个阶段。
React.lazy
+ Suspense
useState
/setState
- 防止回流shouldComponentUpdate()
生命周期方法做浅对比Profiler
的UI界面在逻辑上可分为4个主要部分。
剖析
切片中,代表某次commit
对应的组件渲染时间的相关信息。commit
操作。每当你通过点击选择一个commit
,「图表区域」和「提交信息」就会相应地更新。commit
阶段或单个选定组件的细节。整个过程大致可以分为「三个阶段」
客户端发起请求阶段
URL
DNS
查询从域名服务器获取这个 IP
地址TCP
的三次握手和TLS协商向服务器发起 HTTP
请求建立连接的过程在这个过程中
DNS
查询HTTP
请求很容易成为影响前端性能的瓶颈点
本地缓存可以让静态资源加载更快,想要让本地缓存发挥作用,就需要「先在服务器上进行配置」。
本地缓存一般包括强缓存和协商缓存两种形式
「强缓存」是指浏览器在加载资源时,根据请求头的 expires
/cache-control
,判断是否命中客户端缓存。
「协商缓存」是指,浏览器会先发送一个请求到服务器,通过 etag
/last-modified
,验证资源是否命中客户端缓存。
每进行一次 DNS
查询,都要经历从手机到移动信号塔,再到认证 DNS
服务器的过程。要节省时间,一个办法就是让 DNS
查询走缓存,浏览器提供了 DNS
预获取的接口。
在 HTTP
请求阶段,最大的瓶颈点来源于「请求阻塞」。所谓请求阻塞,就是浏览器为保证访问速度,会默认对同一域下的资源保持一定的连接数,请求过多就会进行阻塞
浏览器同域名的连接数限制是一般是 6 个,如果当前请求书多于 6 个,只能 6 个并发,其余的得等最先返回的请求后,才能做下一次请求
pic.google.com
,做成支持 pic0-5 的 6 个域名服务端数据处理阶段,是指 WebServer
接收到请求后,从数据存储层取到数据,再返回给前端的过程。
这个过程中的瓶颈点,就在于是否做了
数据缓存分为两种
Service Worker
是浏览器的一个高级属性,本质上是一个「请求代理层」。它存在的目的就是拦截和处理网络数据请求
借助本地存储的接口缓存,在一些对数据时效性要求不高的页面,第一次请求到数据后,程序将数据存储到本地存储
下一次请求的时候,先去缓存里面取将取数据,如果没有的话,再向服务器发起请求
通过在网络各处放置节点服务器,构造一个「智能虚拟网络」。将用户的请求导向离用户最近的服务节点上
Gzip 压缩是一种压缩技术,「服务器端通过使用 Gzip」,传输到浏览器端的文本类资源的大小可以变为原来的 1/3 左右
所谓重定向,是指网站资源迁移到其他位置后,用户访问站点时,程序自动将用户请求从一个页面转移到另外一个页面的过程。
在服务端处理阶段,重定向分为三类
它们都会引发新的 DNS 查询,导致新的 TCP 三次握手和 TLS 协商,以及产生新的 HTTP 请求。
所谓解析,就是 HTML 解析器把页面内容转换为 DOM 树和 CSSOM树的过程
主线程会计算 DOM 节点的最终样式,生成布局树。布局树会记录参与页面布局的节点和样式 。完成布局后,紧跟着就是绘制。
绘制就是把各个节点绘制到屏幕上的过程,绘制结果以层的方式保存
解析器构建 DOM 树的过程中, 有三点会严重影响前端性能
<br>
写成了 </br>
,又或者表格嵌套不标准,标签层次结构复杂等<SCRIPT>
标签时的情况<SCRIPT>
标签,DOM 的构造过程就会暂停,等待服务器请求脚本CSS
及完成 CSSOM
之后才继续执行defer
和 async
,告诉浏览器在等待脚本下载期间不阻止解析过程Lighthouse
:一个开源的「自动化工具」,用于改进网络应用的质量React Performance Devtools
:针对 React.js
项目的优化插件这些工具的弊端是,他们不能准确的测出 SPA
应用的「加载速度」。
为了能够真正的测出 SPA 的真实加载速度,在Chrome 中也存在一些「子工具」(如:Speed Index)用于模拟用户真正的上网过程。
但是,真实的用户操作受各种设备和网络影响,很难利用单一的插件和工具进行模拟。
所以,我们可以使用 真实用户模拟Real User Monitoring(RUM
)对应用就行处理。他能很好的跟踪用户在网页中的各种操作并且能够给出网站的实时加载数据情况。
这里列出一些针对SPA
的RUM
工具
Sentry
: 日志、性能收集 (多平台)WebSocket
JSONP/CORS
绕过同源策略CDN
处理提高Frist Meaningful Paint (FMP
)的指标。 => 「缩短」了用户能够看到页面「核心内容」的时间。
通过对「不可见元素的过滤渲染」也能提高Time to Interactive(TTL
)的性能指标。
发现「转换阶段」也可能存在性能瓶颈。在此阶段,SPA加载数据并且对数据进行序列化Normalizes处理,然后将处理完的数据「存入到内存」中。
可以使用一个「高优先级」调用来获取First Meaningful Paint
所需的数据,并使用另一个回调来「惰性加载」页面所需的其余数据。
一些SPA框架,例如(React/Vue
)是允许开发者将应用「代码分割」成很多bundles
。
❝所以,对一些非必要的
bundles
进行「按需加载」或者「延迟处理」。该方法可以加速「第一次导航」。 ❞
对你的SPA进行审查,从中甄别出可以在用户设备中被「缓存」的图片或者其他的静态资源。
❝
WebSocket
可以实现客户端与服务器间双向、基于消息的文本或二进制数据传输。它是浏览器中最靠近套接字的 API。 ❞
与HTTP不同,客户端不必不断地向服务器发送请求以获取新消息。相反,浏览器只需监听服务器,并在准备好时接收消息。
大部分应用需要「从第三方获取数据」。
但是,由于同源策略,不能对非同源的第三方服务进行AJAX
调用。
为了能够访问第三方网站,应用需要利用origin server作为代理。
❝额外的「往返」意味着更多的延迟。
❞
如果不处理检索到的数据,也不将其存储在系统中,则可以直接请求资源。为此,可以使用JSONP
或跨来源资源共享(CORS)进行数据获取。
<script>
元素,向服务器请求一个脚本callback
参数(?callback=bar
),用来告诉服务器,客户端的回调函数名称(bar
)<script src="http://XX.com?callback=bar"></script>
JSON
数据放在函数名里面,作为字符串返回(bar({...})
)<script>
标签请求的脚本内容。bar()
函数,就能在该函数体内,拿到服务器返回的 JSON
数据。❝
JSONP
只能是GET
请求 ❞
同时,我们可以使用async
和 defer
属性来对<script>
进行优化处理。
属性 | 解释 |
---|---|
没有 defer 或 async | 浏览器会「立即加载并执行」指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素「之前」,也就是说不等待后续载入的文档元素,「读到就加载并执行」 |
async | 加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步) |
defer | 加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的「执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成」 |
CORS
是跨源资源分享Cross-Origin Resource Sharing的缩写。它是 W3C
标准,属于跨源 AJAX
请求的根本解决方法。
但是,「除了」GET
、HEAD
和POST
之外,使用任何方法的请求都会发起一个预检请求Preflight Check,以确认服务器已经为跨源请求做好了准备。
=> 预备请求
OPTIONS /resource.js HTTP/1.1 ①
Host: thirdparty.com
Origin: http://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: My-Custom-Header
...
<= 预备响应
HTTP/1.1 200 OK ②
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: My-Custom-Header
...
(正式的 HTTP 请求) ③
「预检请求」多了一次往返时间,无形中加大了请求的延迟时间。
CDN
是内容交付网络Content Delivery Networks 的英文首字母缩写,是一组分布在「不同地理位置」的服务器,它「将 Web
内容存放在更靠近用户的位置,从而加速 Web
内容的交付」。
CDN
将网页、图像和视频等内容缓存在靠近你的实际地点的「代理服务器」中。
❝把
CDN
想成是一部ATM
机。如今几乎每个街角都有提款机,让我们可以快速高效地提取现金。 ❞
「为SPA使用CDN
意味着更快地加载脚本和减少交互时间」
SSR
Sitemap.xml
rel=canonical
的连接TDK
的优化处理tilte/keywords/description
可以在HTML的<meta>
标签内定义。title
的「权重最高」,利用title
提高页面权重keywords
相对权重较低,作为页面的「辅助关键词」搜索description
的描述一般会直接显示在搜索结果的介绍中「分享是一种态度」。
「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」