性能优化步骤:
First Contentful Paint(FCP):首个内容加载时间
Largest Contentful Paint(LCP):最大内容加载时间
First Input Delay(FID):首次可输入延迟
Time to Ineractive(TTI):可交互时间
Total Blocking Time(TBT):总阻塞时间
Cumulative Layout Shift(CLS):累计布局切换
查看lighthouse评分及对相应修改项目,找出可以改进的方向。
去掉重复引用
选择更小的第三方js
给阻塞式的js增加defer关键字,使得js加载不阻塞
特性:下载不阻塞,执行时仍会中断渲染
使用场景:统计脚本、广告SDK等独立功能
执行顺序:先下载完成的先执行
特性:下载不阻塞,执行在DOM解析完成后
使用场景:依赖DOM的核心业务代码
执行顺序:按照文档顺序执行
将与布局有关的css文件和js文件,放到head中引入。
进一步减少文件的网络传输体积
安装compress
npm install compress --save
引入压缩中间件
重排发生在浏览器重新计算网页某些部分的位置和几何形状时。
// 方法1:通过类名修改
// 通过类名更改样式
el.classList.add("optimized");
// 方法2:通过cssText批量修改
el.style.cssText += '; width:150px; height:150px; background:green;';
function goodDOMOperation() {
const container = document.getElementById("domTarget");
const fragment = document.createDocumentFragment(); //临时保存更改
// 方法1:使用文档片段
for (let i = 0; i < 100; i++) {
const div = document.createElement("div");
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment);
// 方法2:隐藏容器批量操作
container.style.display = 'none';
// ...批量操作...
container.style.display = 'block';
}
function badLayoutRead() {
const el = document.querySelector("#layoutTarget .box");
for (let i = 0; i < 100; i++) {
// 强制同步布局:每次读取触发重排
console.log("Bad read:", el.offsetWidth, el.offsetHeight);
el.style.marginLeft = `${i}px`; // 写入操作
}
}
function goodLayoutRead() {
const el = document.querySelector("#layoutTarget .box");
// 预先读取并缓存
const width = el.offsetWidth;
const height = el.offsetHeight;
for (let i = 0; i < 100; i++) {
console.log("Good read:", width, height); // 使用缓存值
el.style.marginLeft = `${i}px`;
}
}
CSS选择符对性能的影响源于浏览器匹配选择符和文档元素时所消耗的时间。
匹配规则:最右边优先,从右向左匹配
使用ID选择器时不需要再增加其他选择
使用类选择器时不需再增加其他选择器
避免使用后代/子选择器
懒加载的两种方案
传统方案(监听滚动事件,计算坐标) | 现代方案(交叉观察器) |
---|---|
监听scroll事件(频繁计算) | Intersection Observer(自动检测) |
手动计算位置 | 注册观察对象,自动回调 |
性能差(容易卡顿) | 高性能(原生API) |
目标元素:需要被观察的DOM元素
根元素:默认是浏览器视口,也可自定义为容器
交叉比例:触发回调的可见比例阈值
构造一个观察器
const observer = new IntersectionObserver(callback,option)
开始观察
observer.observe(document.getElementById("example");
停止观察
observer.unobserve(element);
关闭观察器
ob.disconnect();
示例
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
//dataset是元素中所有的data-属性的集合
img.src = img.dataset.src;
img.onload = () => {
img.classList.add("loaded");
// 停止观察
observer.unobserve(img);
};
}
});
});
document.querySelectorAll(".lazy-img").forEach((img) => {
// 开始观察
observer.observe(img);
});
返回一个布尔值,如果目标元素与交叉区域观察者对象 (intersection observer) 的根相交,则返回 true .如果返回 true, 则 IntersectionObserverEntry 描述了变换到交叉时的状态; 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态。
目标元素的可见性变化时,就会调用观察器的回调函数callback。
callback默认情况下会触发两次:一次是目标文件刚刚进入视口,另一次是完全离开视口
决定了什么时候触发回调函数。他是一个数组,每个成员都是一个门槛值,默认为.,即交叉比例达到0是触发回调函数。