1. JS开销和如何缩短解析时间
体积相同情况下,JS性能开销是远大于图片等文件的,如下图所示:
JS要经过编译、解析、执行过程,而图片只需要解码与绘制
1.1 尽量减少主线程工作量
- 避免长任务
- 避免超过1kB的行间脚本
- 使用rAF和rIC进行时间调度
时间分片避免长任务:什么是时间分片(Time Slicing)?
1.2 渐进式启动(Progressive Bootstrapping)
- 可见不可交互VS最小可交互资源集(如开始只展示首屏)
2. 配合V8 有效优化代码
2.1 V8编译原理
- 源码=>抽象语法树=>字节码Bytecode =>机器码
- 编译过程会进行优化
- 运行时可能发生反优化
2.2 V8优化机制
- 脚本流(下载完脚本一段时就开始解析,边解析边下载)
- 字节码缓存(对使用率高的片段缓存起来)
- 懒解析 (不用的函数先不解析内部逻辑)
v8内部有一个带优化功能的编译器,当我们的代码不适合优化时,会发生逆优化过程,我们需要避免这种情况。
如以下代码所示:
const {performance, PerformanceObserver} = require('perf_hooks');
const add = (a, b) => a+b;
const num1 = 1;
const num2 = 2;
performance.mark('start');
for(let i = 0; i < 10000000; i++) {
add(num1, num2);
}
add(num1, 's');
for(let i = 0; i < 10000000; i++) {
add(num1, num2);
}
performance.mark('end');
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries()[0]);
})
observer.observe({entryTypes: ['measure']});
performance.measure('测量1', 'start', 'end');
下图为注释掉add(num1, 's')前后的测试结果:可以发现add(num1, 's')将第二个参数改为字符串后,导致反优化,所以第一次运行结果远大于第二次
3. 函数优化
- lazy parsing 懒解析(浏览器默认)
- eager parsing 饥饿解析(立即解析)
浏览器默认是懒解析,如果我们需要立即解析,可以对该函数加一个括号,就会变为饥饿解析。
export default () => {
// 加一个括号,就会变为饥饿解析(立即解析)
// 立即要调用的函数用饥饿解析
const add = ((a, b) => a*b);
const num1 = 1;
const num2 = 2;
add(num1, num2);
}
因为压缩代码时会将饥饿解析的()去掉,所以我们用optimize-js来解决这个问题,利用Optimize.js优化初次加载时间
4. 对象优化
- 以相同顺序初始化对象成员,避免隐藏类的调整
- 实例化后避免添加新属性
- 尽量使用Array代替array-like(类数组,如arguments) 对象
- 避免读取超过数组的长度
- 避免元素类型转换
/*
* 1 隐藏类型 hidden class,以下简称(HC)
*/
class RectArea { // HC0
constructor(l, w) {
this.l = l; // HC1
this.w = w; // HC2
}
}
const rect1 = new RectArea(3,4); // 创建了隐藏类HC0, HC1, HC2
const rect2 = new RectArea(5,6); // 相同的对象结构,可复用之前的所有隐藏类
// 反例
const car1 = {color: 'red'}; // HC0
// 追加属性会降低效率
car1.seats = 4; // HC1
const car2 = {seats: 2}; // 没有可复用的隐藏类,创建HC2
car2.color = 'blue'; // 没有可复用的隐藏类,创建HC3
/*
* 2
* 不要在声明对象后再追加属性,可以通过class构造对象,或者在声明时直接添加好属性
*/
const car1 = {color: 'red'}; // In-object 属性,直接访问到
car1.seats = 4; // Normal/Fast 属性,存储在property store里,需要通过描述数组间接查找
/*
* 3
* 将类数组专为数组再操作
*/
// 最好把类数组转化为真实数组再遍历
Array.prototype.forEach.call(arrObj, (value, index) => { // 不如在真实数组上效率高
console.log(`${ index }: ${ value }`);
});
const arr = Array.prototype.slice.call(arrObj, 0); // 转换的代价相对优化效率是很小的
arr.forEach((value, index) => {
console.log(`${ index }: ${ value }`);
});
/*
* 4
* 避免读取超过数组的长度
*/
function foo(array) {
for (let i = 0; i <= array.length; i++) { // 越界比较
if(array[i] > 1000) { // 1.超过数组长度会沿原型链查找 2.造成undefined与数进行比较
console.log(array[i]); // 业务上无效、出错
}
}
}
/*
* 5
* 避免元素类型转换
*/
const array = [3, 2, 1]; // PACKED_SMI_ELEMENTS(类型)
array.push(4.4); // PACKED_DOUBLE_ELEMENTS(类型)
5例子如下图:
5. HTML优化
- 减小iframes使用
- 压缩空白符
- 避免节点深层级嵌套
- 避免table布局
- 删除注释
- css&js尽量外链
- 会造成html文档过大
- 引擎难以做优化
- 删除元素默认属性
通过使用html-minifier自动优化
我们可以在页面加载完成后,通过JS动态设置来延迟iframe加载,避免堵塞页面,如下代码:
<iframe id='a'></iframe>
document.getElementById('a').setAttribute('src', 'url');
HTML最佳实践:
- head部分
HTML语义化:juejin.cn/post/684490…
6. CSS对性能的影响
通过以下方式可以观测到样式计算开销
通过以下几个方面优化css:
- 降低CSS对渲染的阻塞
- 利用GPU进行完成动画
- 使用font-display属性
- 使用contain属性
使用contain与未使用对比:
contain表示该盒子中样式变化不会影响外部:
推荐文章
仅使用CSS提高页面渲染速度
【前端性能优化指南】5.2 - 优化你的 CSS
使用BEM与命名空间来规范CSS
7. 代码规范
你所需要知道的代码整洁之道
如何写出优雅健壮的代码?
不知道怎么提高代码复用性?看看这几种设计模式吧!
Google 官方文章——如何去做code review