【使你的页面飞起来】4-代码优化

278 阅读4分钟

1. JS开销和如何缩短解析时间

体积相同情况下,JS性能开销是远大于图片等文件的,如下图所示:

JS要经过编译、解析、执行过程,而图片只需要解码与绘制 image.png

1.1 尽量减少主线程工作量

  1. 避免长任务
  2. 避免超过1kB的行间脚本
  3. 使用rAF和rIC进行时间调度

时间分片避免长任务:什么是时间分片(Time Slicing)?

1.2 渐进式启动(Progressive Bootstrapping)

  • 可见不可交互VS最小可交互资源集(如开始只展示首屏)

image.png

2. 配合V8 有效优化代码

2.1 V8编译原理

  • 源码=>抽象语法树=>字节码Bytecode =>机器码
  • 编译过程会进行优化
  • 运行时可能发生反优化 image.png

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')将第二个参数改为字符串后,导致反优化,所以第一次运行结果远大于第二次

image.png

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例子如下图:

image.png

5. HTML优化

  • 减小iframes使用
  • 压缩空白符
  • 避免节点深层级嵌套
  • 避免table布局
  • 删除注释
  • css&js尽量外链
    • 会造成html文档过大
    • 引擎难以做优化
  • 删除元素默认属性

通过使用html-minifier自动优化

我们可以在页面加载完成后,通过JS动态设置来延迟iframe加载,避免堵塞页面,如下代码:

<iframe id='a'></iframe>
document.getElementById('a').setAttribute('src', 'url');

HTML最佳实践:

  1. head部分

image.png

image.png

image.png

image.png

HTML语义化:juejin.cn/post/684490…

6. CSS对性能的影响

通过以下方式可以观测到样式计算开销 image.png

通过以下几个方面优化css:

  • 降低CSS对渲染的阻塞
  • 利用GPU进行完成动画
  • 使用font-display属性
  • 使用contain属性

使用contain与未使用对比: image.png

contain表示该盒子中样式变化不会影响外部: image.png

推荐文章

仅使用CSS提高页面渲染速度
【前端性能优化指南】5.2 - 优化你的 CSS
使用BEM与命名空间来规范CSS

7. 代码规范

你所需要知道的代码整洁之道
如何写出优雅健壮的代码?
不知道怎么提高代码复用性?看看这几种设计模式吧!
Google 官方文章——如何去做code review