Appearance
全局配置
Appearance
JS通过tree shaking,CSS通过使用浏览器开发者工具的Coverage或Audit功能找出未使用的CSS,然后将其从CSS文件中删除或注释掉。
Service Worker 预缓存可以使用 Cache API 提取和保存资源,使浏览器无需进入网络即可使用 Cache API 处理请求。Service Worker 预缓存使用非常有效的 Service Worker 缓存策略,称为“仅缓存”策略。此模式非常有效,因为将资源放入 Service Worker 缓存后,请求会立即提取。 要使用 Service Worker 预缓存资源,可以使用 Workbox。 Workbox 使用预缓存清单来确定应预缓存哪些资源。预缓存清单是文件和版本控制信息的列表,可作为要预缓存的资源的“可信来源”。
<iframe>
元素 <iframe>
元素本质上是在顶级文档中加载的完整 HTML 文档,因此它们可能包含大量子资源(尤其是 JavaScript),如果这些框架中的任务需要大量处理时间,则可能会严重影响页面的 INP。尤其是非首屏位置,更要考虑使用。
下载 CSS 文件所需的时间可能会增加网页的 FCP。在文档 <head>
中内嵌关键样式可以消除对 CSS 资源的网络请求。不过,内嵌 CSS 不会被缓存,需要做测试和权衡。
预加载扫描程序是一种浏览器优化功能,它采用辅助 HTML 解析器的形式,可扫描原始 HTML 响应,以便先找到资源并进行推测性提取,然后再让主要 HTML 解析器发现这些资源。
预加载扫描程序无法发现以下资源加载模式:
由 CSS 使用
background-image
属性加载的图片。这些图片引用位于 CSS 中,并且预加载扫描器无法发现这些引用。动态加载的脚本,采用
<script>
元素标记的形式,使用 JavaScript 或使用动态 import() 加载的模块注入 DOM。使用 JavaScript 在客户端上呈现的 HTML。此类标记包含在 JavaScript资源的字符串中,预加载扫描程序无法发现此类标记。
CSS @import 声明。
资源提示可以告知浏览器如何加载资源并确定资源的优先级,从而帮助开发者进一步优化网页加载时间。 可以通过其 fetchpriority 属性使用 Fetch Priority API 来提高资源的优先级。您可以将该属性与<link>
、<img>
和 <script>
元素一起使用。
- 使用 preconnect,您可以预计浏览器计划在不久的将来连接到特定的跨源服务器,并且浏览器应尽快打开该连接,最好是在等待 HTML 解析器或预加载扫描程序执行此操作之前。如果网页上有大量跨源资源,请对对当前网页最关键的资源使用 preconnect。
- dns-prefetch 不会与跨源服务器建立连接,而只是提前为其执行 DNS 查找。将域名解析为其底层 IP 地址时会发生 DNS 查询。虽然设备和网络级别的 DNS 缓存有助于加快此过程,但仍然需要一些时间。DNS 查找的费用相当低廉,并且由于费用相对较小,在某些情况下,DNS 查找可能比 preconnect 更适合。同时与许多跨源服务器建立连接可能是不合理的或不可行的。如果您担心可能过度使用 preconnect,可以使用 dns-prefetch 提示来使用开销更低的资源提示。dnstradamus 就是这样一种使用 JavaScript 自动执行此操作的工具,它会使用 Intersection Observer API 在指向其他网站的链接滚动到用户的视图时,将 dns-prefetch 提示注入当前页面的 HTML 中。
- preload 指令用于针对呈现网页所需的资源发起提前请求。最常见的用例是字体文件、通过 @import 声明提取的 CSS 文件,或可能是 Largest Contentful Paint (LCP) 候选对象的 CSS background-image 资源。在这种情况下,预加载扫描程序将不会发现这些文件,因为相关资源是在外部资源中引用的。使用 preload 指令下载的资源将以高优先级有效下载,如果过度使用,preload 可能会以对网页加载速度产生负面影响的方式产生带宽争用。
- prefetch 指令用于针对可能会用于未来导航的资源发起低优先级请求:与 preload 指令不同的是,prefetch 主要是推测性的,因为您将发起对某个资源的提取,以便将来的导航操作不一定会发生。存在潜在的缺点,即如果用户没有转到最终需要预提取资源的页面,则用于提取资源的数据可能会被未使用的数据占用。 preload 优先级高于 prefetch;preload 告诉浏览器立即加载资源;prefetch 告诉浏览器在空闲时才开始加载资源;使用 preload 会将资源优先级设置为 Highest,而使用 prefetch 会将资源优先级设置为 Lowest,Lowest 资源将会在网络空闲时才开始加载。preload、prefetch 仅仅是加载资源,并不会“执行”;相同的资源重复使用 preload、prefetch 属性时将会导致重复下载;当页面中实际用到的资源与 preload、prefetch 加载的资源重复时,浏览器不会进行重复请求;
- fetchpriority 属性在用于页面的 LCP 图片时尤其有效。通过使用此属性提高 LCP 图片的优先级,您可以相对轻松地改善网页的 LCP。
- 现代浏览器会分两个阶段加载资源。第一阶段预留供关键资源使用,并会在所有阻塞脚本下载并执行完毕后结束。在此阶段,低优先级资源可能会出现延迟,无法下载。通过使用 fetchpriority=“high”,您可以提高资源的优先级,使浏览器能够在第一阶段下载该资源。
<div class="gallery">
<div class="poster">
<img src="img/poster-1.jpg" fetchpriority="high">
</div>
<div class="thumbnails">
<img src="img/thumbnail-2.jpg" fetchpriority="low">
<img src="img/thumbnail-3.jpg" fetchpriority="low">
<img src="img/thumbnail-4.jpg" fetchpriority="low">
</div>
</div>
<link rel="stylesheet">
元素替换 @import 对css而言,@import 声明产生所谓的请求链,下载解析包含@import 声明的css后,才又去下载。
<body>
元素的末尾 Prerender 比 Prefetch 更进一步,可以粗略地理解不仅会预获取,还会预执行。 如果你指定 Prerender 一个页面<link rel="prerender" href="//test.com/textpage.html">
,那么它依赖的其他资源,像 <script>
、<link>
等页面所需资源也可能会被下载与处理。 <link rel="prerender">
是 Chrome 特定的,从未标准化,Chrome 工程团队正在取消它。
Speculation Rules API(推测规则 API)是<link rel="prefetch">
功能的替代方案,用于指定应预取或预渲染哪些文档: 需要指定一个不同的数组来包含每种推测加载类型的规则(例如"prerender" 或"prefetch")。每个规则都包含在一个对象中,该对象指定要获取的资源列表,以及每个规则的显式 Referrer-Policy 设置等选项。 其中,包含 prerender 规则将导致支持浏览器获取、呈现内容并将其加载到存储在每个文档中的不可见选项卡中内存缓存。这包括加载所有子资源、运行所有 JavaScript,甚至加载子资源并执行由 JavaScript 启动的数据获取。 未来到预渲染页面的导航将几乎是即时的。浏览器激活不可见的选项卡,而不是执行通常的导航过程,用预渲染的页面替换旧的前台页面。如果页面在完全预渲染之前被激活,它将以其当前状态激活,然后继续加载,这意味着您仍然会看到显着的性能改进。 预渲染的前期成本远大于预取的前期成本,并且存在更多可能导致内容不安全预渲染的条件(请参阅不安全的推测加载条件了解更多详情)。因此,需要更加谨慎地采用prerender,仔细考虑很有可能导航到该页面的情况且用户体验的好处值得付出额外的成本下使用它。 另外,目前似乎无法进行跨站点预渲染。
<script type="speculationrules">
{
"prerender": [
{
"source": "list",
"urls": ["extra.html", "extra2.html"]
}
],
"prefetch": [
{
"source": "list",
"urls": ["next.html", "next2.html"],
"requires": ["anonymous-client-ip-when-cross-origin"],
"referrer_policy": "no-referrer"
}
]
}
</script>
document.addEventListener('touchstart', function (e) {
// 做了一些操作……
e.preventDefault();
}, true);
在 touchstart 中调用了 e.preventDefault() 会阻止页面的滚动与缩放。那么浏览器是如何知道不要让页面滚动与缩放的呢?当然是因为我们调用了 e.preventDefault(),你可能认为这是废话,不过问题就在这。如果浏览器不执行完监听回调里的代码,就不会知道开发者有没有禁止默认事件。所以不管你是否调用了 e.preventDefault(),当你添加触摸、滚轮的事件监听后,每次触发该事件,浏览器都会先花费事件执行完你的回调,然后根据结果来判断是否需要滚动页面。如果的操作花费了 200ms,那页面只能在 200ms 后再滚动或缩放,这就导致了性能问题。
很多时候我不会阻止默认事件呀,我有没有办法告诉浏览器,让它不用等啦(默认行为没有被禁用),直接滚动页面就行呢?Passive event listeners 就是为此而生的。
document.addEventListener('touchstart', function (e) {
// 做了一些操作……
}, {passive: true});
设置了 3D transform,所以会从普通的渲染层提升至合成层,拥有独立的 GraphicsLayers。当合成层更新时,浏览器会将布局调整限制在该层中,做到尽可能小的布局变动。
合成层在性能优化上的优点在于:
注意,出现过多的层,反而导致性能的下降
WebP 和 AVIF 等现代图片格式可提供优于 PNG 或 JPEG 的压缩效果。
借助
<picture>
元素,您可以更灵活地指定多个候选图片
<video>
视频代替 通常应该避免使用动画图片格式,因为等效的 <video>
对此类媒体而言效率更高。 gif格式是一种很老的格式了,压缩率不高,所以体积特别大。例如,不处理gif的话,gif和MP4格式视频体积差不多相差20倍。即使对gif做压缩和抽帧处理,体积也会比mp4格式大一些,并且播放没有mp4清晰。 可以直接在 <video>
元素上使用 autoplay自动播放、loop循环播放、muted静音、playsinline指明视频将内联(inline)播放等属性,代替gif。
decoding="async"
<img src="big_and_expensive_img_to_load" decoding="async" />
decoding 属性会告知浏览器应如何解码图片。async 值可告知浏览器图片可异步解码,从而可能缩短渲染其他内容的时间。值 sync 会告知浏览器,相应图片应与其他内容同时呈现。
http1.1的问题: 长连接:长连接也还是有缺点的;就算是在空闲状态,它还是会消耗服务器资源,而且在重负载时,还有可能遭受 DoS 攻击。 管线化:HTTP pipelining是把多个HTTP请求放到一个TCP连接中一一发送,而在发送过程中不需要等待服务器对前一个请求的响应;只不,客户端还是要按照发送请求的顺序来接收响应。就像在超市收银台或者银行柜台排队时一样,你并不知道前面的顾客是干脆利索的还是会跟收银员/柜员磨蹭到世界末日。这就是所谓的线头阻塞(head-of-line blocking)。当然,你可以在选择队伍时候就做好功课,去排一个你认为最快的队伍,或者甚至另起一个新的队伍(译者注:即新建一个TCP连接)。但不管怎么样,你总归得先选择一个队伍,而且一旦选定之后,就不能更换队伍。但是,另起新队伍会导致资源耗费和性能损失。这种另起新队伍的方式只在新队伍数量很少的情况下有作用(因为TCP连接上限依旧存在),因此它并不具备可扩展性。
ffmpeg -i input.mov output.webm
<video>
元素上的 poster 属性添加的,该属性可在开始播放前向用户提示视频内容 style
元素帮助浏览器更快地发现网页字体 WOFF2 可提供广泛的浏览器支持和最佳压缩效果,比 WOFF 高出 30%。文件大小缩减,下载速度更快。WOFF2 格式往往是现代浏览器完全兼容的唯一格式。
只有在需要支持旧版浏览器时,您才可能需要使用其他格式(例如 WOFF、EOT 和 TTF)。 如果您不需要支持旧版浏览器,就没有理由依赖于 WOFF2 以外的网页字体格式。
浏览器发现并下载网页字体后,即可渲染该字体。默认情况下,在下载使用网页字体的任何文本之前,浏览器会阻止其渲染。 默认值为 block。使用 block,浏览器会阻止渲染使用指定网页字体的任何文本。 swap 是使用最广泛的 font-display 值。swap 不会阻止渲染,它会在交换指定的网页字体之前立即以回退方式显示文本。这样,您就可以立即显示内容,而无需等待网页字体下载完成。
不过,swap 的缺点是,如果后备网页字体和 CSS 中指定的网页字体在行高、字距调整和其他字体指标方面存在很大差异,则会导致布局偏移。如果您不小心使用 preload 提示尽快加载网页字体资源,或者不考虑其他 font-display 值,这可能会影响网站的 CLS。 font-display 的 fallback 值在 block 和 swap 之间是一种折衷的方案。使用fallback浏览器会阻止字体渲染,但阻塞期极短。 如果网页字体可以快速下载,网页字体就是网页首次渲染时立即使用的字体。不过,如果网络速度较慢,系统会在屏蔽期过后,先显示后备文本,然后在网页字体到达时交换出后备文本。 optional 是最严格的 font-display 值,仅当在 100 毫秒内下载时才会使用网页字体资源。如果网页字体的加载用时超过该时间,则系统不会在网页上使用该字体,而浏览器会使用后备字体进行当前导航,而网页字体会在后台下载并放入浏览器缓存中。
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能,即使有100个中只有一个需要使用v-if,也需要整个循环数组,这在性能上是极大的浪费。可以选择使用computed过滤掉列表中不需要显示的项目。
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露
仅支持React 15.3及以上版本 PureComponent 是继承自 React.Component 的一个子类,它额外实现了shouldComponentUpdate 方法,并通过对组件的 props 和 state 进行浅层比较来确定是否需要重新渲染组件。
如果 props 和 state 没有发生改变,就不会进入 render 节点,省去了生成 Virtual DOM 和 Diff 的过程。 低版本可以使用PureRenderMixin,使用浅比较来决定是否应该触发组件的重新渲染。它会自动为组件添加一个 shouldComponentUpdate 方法,该方法会比较新的 props 和 state 与当前的 props 和 state,并根据比较结果决定是否重新渲染组件
class App extends React.PureComponent
如果想对渲染进行更加细微的控制,或者是对引用类型进行渲染控制我们可以使用shouldComponentUpdate,通过返回 true 进行更新, false 阻止不必要的更新。
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 对比新旧属性和状态
if (this.props.value === nextProps.value && this.state.count === nextState.count) {
return false; // 属性和状态相同,不需要重新渲染
}
return true; // 需要重新渲染
}
render() {
return <div>{this.props.value}</div>;
}
}
当我们在类组件中绑定类方法通过需要绑定他的 this 指向。 虽然用起来是一样的,但是第一种方式在 render 的时候,每次会 bind this 生成新的函数实例,而第二种只会执行一次。
// 方式一
render() {
return <Button onClick={this.handleClick.bind(this)}>测试按钮</Button>
}
// 方式二
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
它用于在组件渲染过程中进行记忆化计算,以避免不必要的重复计算,提高应用的性能。 使用场景:
计算昂贵的计算结果:涉及到需要执行昂贵的计算或处理大量数据的情况下,可以使用 useMemo 将计算结果缓存起来 避免不必要的渲染:某个组件的渲染结果仅依赖于特定的输入参数,并且这些参数没有发生变化时,可以使用 useMemo 缓存该组件的输出,避免不必要的重新渲染
import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
// 使用 useMemo 缓存结果
const processedData = useMemo(() => {
// 执行昂贵的计算或处理逻辑
// 这里只是一个简单的示例,实际场景可能更复杂
console.log('Processing data...');
return data.map(item => item * 2);
}, [data]); // 依赖项: 当 data 发生变化时重新计算
return (
<div>
{/* 渲染使用 useMemo 缓存的结果 */}
<ul>
{processedData.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
当我们在 React 中使用内联函数时,每次重新 render 将导致生成新的函数实例从而为元素绑定新的函数,在非嵌套的组件使用时影响不大,但是如果存在嵌套组件并且该内联函数是作为 props 传递给子组件时将会导致子组件重新渲染,即使内联函数里的代码相同的情况下。
Class 组件:将内联函数定义为类方法传递给子组件 Function 组件:useCallback进行缓存
在大多数情况下,使用普通的函数定义也是有效的。只有在性能优化成为问题时,才需要考虑使用 useCallback。过度使用该useCallback会导致内存占用增加(每个缓存的回调函数都会占用内存),代码复杂度增加可读性变差并且难以维护。 可以遵循以下原则:
只在需要时使用:只有在明确的性能问题存在时,或者需要将回调函数作为依赖项传递给其他 Hooks(如 useEffect、useMemo)时,才使用 useCallback 明确指定依赖项:确保正确指定 useCallback 的依赖项数组,以确保缓存的回调函数在依赖项未发生变化时不会重新创建
// bad
import React, { useCallback } from 'react';
function MyComponent() {
return (
<button
onClick={() => {
// 处理点击事件
}}
>
Click me
</button>
);
}
// good
import React, { useCallback } from 'react';
function MyComponent() {
const handleClick = useCallback(() => {
// 处理点击事件
}, []);
return (
<button onClick={handleClick}>Click me</button>
);
}
组件中注册的全局的监听器、定时器等,需要在组件卸载的时候进行清理,防止后续的执行影响性能以及内存泄露等问题 Class 组件:componentWillUnmount Function 组件:useEffect return
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}
export default App;
import React from 'react';
function MyComponent(props) {
const data = props.data; // 假设这是一个包含大量数据的数组
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
性能总结.puml:
@startmindmap
* Debian
** 减少资源体积
*** 压缩JS、CSS代码,移除冗余内容(如空格和其他不可见字符、注释等)
*** 压缩算法Brotli代替gzip。
*** 移除未使用的js和css
*** 拆分代码
** 减少访问次数
*** 使用HTTP缓存策略缓存 CSS、JavaScript、图片和其他资源类型
*** 使用ssr
*** Service Worker 预缓存
*** 尽量减少不必要的重定向
*** 聚合接口
*** 为静态 HTML 资源分配5分钟缓存时间
** 更快的发现资源
*** 內嵌关键 CSS
*** 关键资源应包含在服务器发送的 HTML 标记中。
*** 通过资源提示协助浏览器(preconnect 和 dns-prefetch、proload、prefetch、Fetch Priority API)
*** 使用'<link rel="stylesheet">元素替换 @import
*** 优化请求资源位置顺序,非关键 css 可以异步加载,也可以附加在“<body>元素的末尾。
** 更快的渲染资源
*** CSS放在head,JS放在body最下面
*** 尽早开始执行一些异步JS,如放到DOMContentLoaded触发
*** 使用预渲染prerender
*** 渲染更少的数据,使用分页、滚动加载等
** 更快的网络
*** CDN,从物理上距离用户更近的边缘服务器传送资源。
*** 使用HTTP2、HTTP3协议
** 优化首屏
*** 避免使用 JavaScript 来渲染任何关键内容或网页的 LCP 元素。
*** defer加载不关键的资源
*** 懒加载非首屏所用的JS
*** 拆分非首屏所用的css
*** 使用骨架屏
*** 避免使用 JavaScript 来渲染任何关键内容或网页的 LCP 元素。
*** 使用 loading 属性延迟加载非初始视口的图片
*** 延迟加载‘<iframe>元素
*** 流式渲染/bigpipe
** 优化运行时
*** 对DOM查询进行缓存
*** 频繁DOM操作,合并到一起插入DOM结构
*** 频繁触发的函数考虑使用节流throttle/防抖debounce
*** 计算量大的任务,考虑使用Web Worker
*** 优化css选择器:嵌套层级不易过深
*** 尽量减少回流和重绘
*** event listeners开启Passive
*** 长列表使用虚拟列表
*** 尽量避免强制同步布局的发生
*** 善用合成层
*** 选用性能高的算法
** 优化具体资源
*** 图片
**** 以正确的大小显示图片
**** 通过以现代格式提供图片
**** 对图片进行压缩
**** JPEG进行有损压缩
**** 对 GIF、PNG格式使用无损压缩
**** 对WebP 和 AVIF 格式使用有损或无损压缩
**** 使用基于 Node.js 的 SVG 优化工具 svgo 进行有损优化
**** git图片,使用等效的‘<video>视频代替
**** 大图片设置decoding="async"、
**** HTTP1.1协议下,小图使用base64编码,或者使用雪碧图合成大图,减少请求
**** 非关键图片延迟加载(懒加载)
*** 视频
**** 使用 FFmpeg压缩视频文件
**** 使用现代格式的视频文件,对于不支持所有现代格式的浏览器,指定多种格式可作为后备方案。
**** 视频的海报图片是使用‘<video>元素上的 poster 属性添加的,该属性可在开始播放前向用户提示视频内容。
**** 使用 提示和 fetchpriority="high” 来提高 poster 图片的优先级
*** 字体
**** @font-face 声明是在外部样式表中定义的,使用preload或内联'style 元素帮助浏览器更快地发现网页字体
**** 借助 preconnect,您可以告知浏览器比浏览器通常尽快打开跨源连接
**** 仅使用 WOFF2
**** 选择合适的浏览器的文本渲染行为,如设置font-display为fallback。
** 框架层面性能优化
*** vue
**** 正确区分v-if 和 v-show 使用场景
**** v-for 遍历避免同时使用 v-if
*** react
**** class组件
***** 巧用PureComponent
***** 合理使用shouldComponentUpdate
***** 在构造函数中绑定this
**** function组件
***** 使用useMemo
***** 使用useCallback
*** 通用项
**** 组件销毁时移除手动绑定的js事件、定时器等
**** 非首页路由页面组件使用异步加载
**** 使用key提升列表的渲染性能
@endmindmap