当用户在地址栏中输入网址,到最后看到页面,中间都经历了什么 ?
性能指标
-
DCL:DOMContentLoaded事件耗时,当HTML文档完全加载和解析完成之后,DOMContentLoaded事件被触发 -
L:onLoad事件耗时 当依赖的资源全部加载完毕之后才会触发 -
FP: First Paint (首次绘制) 第一个像素点绘制到屏幕的时间 -
FCP: First Contentful Paint 首次内容绘制,首次绘制任何文本,图像,非空白节点的时间 -
FMP: First Meaningful paint 首次有意义的绘制是页面可用性的度量标准 -
LCP: Largest Contentful Paint 最大内容渲染,在 viewport 中 最大的页面元素加载的时间 -
FID: First Input Delay 首次输入延迟,用户首次和页面交互 (单机链接,点击按钮等)到页面响应交互时间 -
TTFB: time to first byte 首字节时间,从请求到数据返回第一个字节所消耗的时间 -
TTI: Time to Interactive 可交互时间,DOM 树构建完毕,代表可以绑定事件
HTML 优化
- 标签语义化,合适的标签做合适的事情,有利于
SEO搜索引擎的检索和优化 - 尽可能延迟 JavaScript 执行,将其放到页面底部,合理使用
async和defer - 减少 iframe 的使用 (将内容加载到
<iframe>中比将内容作为同一直接页面的一部分加载要消耗资源得多—不仅需要额外的 HTTP 请求来加载内容,而且浏览器还需要为每个<iframe>创建一个单独的页面实例。每个<iframe>实际上是嵌入在顶级网页中的一个独立网页实例。- 每个<iframe>还需要单独加载所有共享的数据和媒体文件——你无法在不同的页面嵌入之间共享缓存的资源(再次强调,除非嵌入的页面来自同一源)。这可能导致页面使用的带宽比你预期的要多得多。)
为什么要使用 lazy 属性 和 srcset 属性?
lazy 懒加载
srcset 多设备适配、提高页面加载速度、节省带宽和流量、视网膜屏幕支持(提供更清晰和更锐利的图像显示效果)
<img src="800w.jpg" alt="家庭合照" loading="lazy" />### [图像的懒加载]
<img
srcset="480w.jpg 480w, 800w.jpg 800w" [### [通过 srcset 提供不同的图像分辨率]
sizes="(max-width: 600px) 480px,
800px"
src="800w.jpg"
alt="家庭照" />
为什么要使用 preload?
preload 预加载资源,以减少后续延迟,并提高页面加载速度
<video controls preload="none" poster="poster.jpg">
<source src="video.webm" type="video/webm" />
<source src="video.mp4" type="video/mp4" />
</video>
为什么要使用 dns-prefetch?
dns-prefetch 可帮助开发人员掩盖 DNS 解析延迟,此延迟可能会大大降低加载性能
CSS 优化
-
删除不必要的样式,推荐 css 模块化,Chrome 的 DevTools 有一个“覆盖率”选项卡,可以告诉您 CSS 和 JavaScript 的使用量。
-
将 CSS 拆分为独立模块:CSS 模块化可以延迟加载在页面加载阶段非必要的 CSS,缩短初始 CSS 的阻塞和加载时间。最简单的方法是将 CSS 拆分为独立的文件,link 标签的 media 属性 按需引入 css 并仅加载所需内容:
-
最小化和压缩你的 CSS gzip 压缩。
-
简化选择器
-
不要将样式应用于不需要的元素,比如 * 通配符操作
-
使用 CSS 精灵图减少图像相关的 HTTP 请求
-
预加载重要资源:你可以使用 [
rel="preload"] 将 [<link>]元素转换为预加载器,用于关键资源,包括 CSS 文件、字体和图片: -
避免重绘和回流 改元素的尺寸,例如 [
width]、[height]、[border] [padding] 重新定位元素,例如 [margin]、[top]、[bottom]、[left]和 [right] 更改元素的布局,例如 [align-content]、[align-items] 和 [flex]。 添加改变元素几何形状的视觉效果,例如 [box-shadow]。 -
[使用
will-change优化元素变化]auto: 浏览器会自行判断应该对哪些元素进行优化。scroll-position: 优化元素的滚动位置。contents: 优化元素的内容(例如字体大小、边框等)。transform: 优化元素的变换效果(例如旋转、缩放等)。opacity: 优化元素的透明度。 -
[改善字体性能]
-
使用
content-visibility跳过渲染工作
JS 优化
async:这会导致脚本获取与 DOM 解析并行进行,因此它将在同一时间准备好,不会阻塞渲染。
defer:它会导致脚本在文档解析完成之后,但在触发 [DOMContentLoaded]事件之前执行。这与 async 有类似的效果。
分解长任务
async function main() {
// 创建要运行的函数数组
const tasks = [a, b, c, d, e];
while (tasks.length > 0) {
// 让步给挂起的用户输入
if (navigator.scheduling.isInputPending()) {
await yield();
} else {
// 从任务数组中取出第一个任务
const task = tasks.shift();
// 运行任务
task();
}
}
}
requestAnimationFrame 相较于 setInterval 更加高效。这是因为 requestAnimationFrame 利用了浏览器的优化机制,在执行动画和界面渲染时能够更加平滑地进行操作,同时还能节省 CPU 资源。另一方面,setInterval 在每个设定的时间间隔内都会执行回调,而不管浏览器是否准备好。这可能会导致一些性能问题,尤其是在处理复杂的动画或需要更新频率较高的界面时。
- DOM二级事件,不要忘记 removeEventListener
- 简化 HTML 代码 ,减少 DOM 和 JavaScript 操作,创建文档碎片
DocumentFragment,减少循环代码的数量 - 尽量减少
HTTP的请求次数, 基于Promise实现接口并发 - 如果,不希望阻塞主线程多使用
Web Worker,允许你打开一个单独的线程来运行一段 JavaScript 代码,并且 Worker 可以与主线程之间发送和接收消息。在Worker中进行计算,等待结果,并在准备好时将结果发送回主线程。 - 如果使用状态管理类似于
Redux这些库,组件销毁的时候别忘记清除状态管理的值 - 首屏加载 ssr 优化,加快渲染速率
- 图片懒加载、骨架屏、只渲染可视区域,按需加载 ...
- 不要对同一个标签DOM绑定多个事件,多使用 自定义属性和事件委托(事件代理)
- 由于浏览器的js 引擎和 GUI的渲染线程是互斥的,所以JS执行的时候会阻塞它的渲染,所以一般会把CSS放到顶部优先渲染 ,JS在底部
事件委托案例
<!DOCTYPE html>
<html>
<head>
<title>事件委托</title>
</head>
<body>
<div id="container">
<ul id="list">
<li data-index="1">Item 1</li>
<li data-index="2">Item 2</li>
<li data-index="3">Item 3</li>
</ul>
</div>
</body>
<script>
const handleClick = (e) => {
alert(event.target.dataset.index)
}
list.addEventListener('click', handleClick)
// 在需要的时候卸载事件
// list.removeEventListener('click', handleClick);
</script>
</html>
图片懒加载的实现原理
1、基本原理: 监听图片是否位于页面的可视区域内,若在则加载图片,不在则不加载图片;
2、实现方案: 自定义属性-将图片真实地址 url 存储在自定义属性中,当监听到图片进入可视区域时,将自定义属性值赋值给 img 的 src 属性;
// 方法1: H + S > offsetTop
function lazyLoad1(imgs) {
// offsetTop是元素与offsetParent的距离,循环获取直到页面顶部
function getTop(e) {
var T = e.offsetTop;
while ((e = e.offsetParent)) {
T += e.offsetTop;
}
return T;
}
var H = document.documentElement.clientHeight; // 获取可视区域高度
var S = document.documentElement.scrollTop || document.body.scrollTop;
Array.from(imgs).forEach(function (img) {
// +100 提前100个像素就开始加载
// 并且只处理没有src即没有加载过的图片
if (H + S + 100 > getTop(img) && !img.src) {
img.src = img.dataset.src;
}
});
}
throttle(lazyLoad1, 200);
// 方法2:el.getBoundingClientRect().top <= window.innerHeight function lazyLoad2(imgs) {
function isIn(el) {
var bound = el.getBoundingClientRect();
var clientHeight = window.innerHeight;
return bound.top <= clientHeight + 100;
}
Array.from(imgs).forEach(function (img) {
if (isIn(img) && !img.src) {
img.src = img.dataset.src;
}
});
}
const throttleLazyLoad2 = throttle(lazyLoad2, 200);
// 方法3:IntersectionObserver
function lazyLoad3(imgs) {
const io = new IntersectionObserver((ioes) => {
ioes.forEach((ioe) => {
const img = ioe.target;
const intersectionRatio = ioe.intersectionRatio;
if (intersectionRatio > 0 && intersectionRatio <= 1) {
if (!img.src) {
img.src = img.dataset.src;
}
}
img.onload = img.onerror = () => io.unobserve(img);
});
});
imgs.forEach((img) => io.observe(img));
}
lazyLoad3(imgs);
服务器 优化
- 加机器 、cdn 、开启 http2、gzip 压缩,配置强缓存、协商缓存、静态资源服务器分层
缓存
为什么要配置缓存
- 减少了冗余的
数据传输,节省了网费。 - 减少了服务器的负担, 大大提高了网站的
性能 - 加快了客户端加载网页的
速度
缓存分类
- 强制缓存 (Cache-Control 、 Expires )
- 协商缓存/对比缓存 (ETag 、 Last-Modified)
缓存优先级
- Cache-Control > Expires
- ETag > Last-Modified
Cache-Control 的属性配置介绍
- private 客户端可以缓存
- public 客户端和代理服务器都可以缓存
- max-age=60 缓存内容将在60秒后失效
- no-cache 需要使用对比缓存验证数据,强制向源服务器再次验证
- no-store 所有内容都不会缓存,强制缓存和对比缓存都不会触发
缓存图例解析
大白话总结
- 强制缓存不会发请求
- 对比缓存 会携带对应的请求头去发送请求看文件又没发现变化
- Expires 有自己的缺陷,如果本地时间更改的话,会按照本地时间比对,可能会导致缓存失效,Cache-Control 是依赖于服务器时间来的,所以尽量用 Cache-Control
- Last-Modified只能
精确到秒。Etag能解决 Last-Modified 秒级以下不缓存的问题
Nginx 示例如何配置前端缓存
if ($request_filename ~* .*\.(?:htm|html)$) {
add_header Cache-Control "no-store, no-cache";
}
if ($request_filename ~* .*\.(?:js|css)$) {
Cache-Control max-age=2592000;
}
思考
- 由于前端缓存导致的一些线上问题,需要那些注意点 ?
性能检测
Lighthouse、Performance