V8&Object优化

719 阅读4分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

JS 优化

js资源会经过加载,解析,编译和执行几个步骤,js资源的大小会导致页面性能出现问题,例如相同大小的js资源和图片资源,js会比图片资源占用更长的时间

一个页面的网络加载时间大概会占到整个页面的1/3时间,因为需要针对js进行处理.因此js的优化需要针对其大小,解析,编译和执行几个环节进行优化

大小的优化

  • code splitting 代码拆分 按需加载
  • tree shaking 移除无用代码 dead code

解析&执行的优化

  • 避免长任务

  • 避免超过1KB的行间脚本

    • 将首屏使用到的脚本提取到行间脚本会加快首屏的渲染,其余通过外部文件加载,但是不易过大,需要合理规划,因为浏览器目前对于行间脚本不能很好的进行优化
  • 使用rAF和rIC进行时间调度

V8优化

  • 脚本流
  • 字节码缓存
  • 懒解析

函数的解析方式

  • lazy parsing 懒解析 vs eager parsing 饥饿解析

    默认对代码进行的是懒解析,但是当我们编写完一个函数并在其之后立即调用,因为被v8默认使用懒解析的方式进行解析,但是解析到函数执行时,会转换到饥饿解析的方式继续解析代码,因此会导致性能浪费,所以需要告诉v8使用懒解析还是饥饿解析的方式解析代码,从而优化性能

    const add = (a, b) => a + b // 此时默认是进行懒解析, 只是记住函数的声明,并不进行解析
    const num1 = 1
    const num2 = 2
    add(num1, num2) // 发现需要使用add函数, 转换为饥饿解析,浪费性能
    

    告诉v8 使用饥饿解析

    const add = ((a, b) => a + b) // 只需要添加一对括号,便可以告诉v8使用饥饿解析
    const num1 = 1
    const num2 = 2
    add(num1, num2) // 发现需要使用add函数, 转换为饥饿解析,浪费性能
    
  • 利用 optimuze.js优化初次加载时间

    目前工程化时webpack在压缩混淆时,会将告诉v8饥饿解析时的一对括号移除, 使用 optimize.js时可以保持这对括号

对象优化

  • 以相同顺序初始化对象成员,避免隐藏类的调整

    js是一门弱语言类型,代码编写的时候是不能确定其具体是什么类型,但是v8内部在进行解析代码的时候还是需要确定一个类型的,因此会在v8解析的时候根据判断去给代码赋予一个具体的类型,这种类型就是隐藏类型(hide class),简称HC

    // 在每次初始化的时候 v8会自动判断 增加HC
    class Person { // HC0
        constructor(name, age) {
            this.name = name // HC1
            this.age = age // HC2
        }
    }
    ​
    // 接下来每次初始化都不会有问题
    const p1 = new Person('wy', 18)
    const p2 = new Pserson('nordon', 22)
    
    const p1 = {name: 'wy'} // HC0
    p1.age = 18 // HC1, 此时的HC1 包含了  name 和 age,且顺序不一样, 结构不能被 p2 复用
    ​
    const p2 = {age: 22} // HC2
    p2.name = 'nordon' // HC3
    

    由于每次的初始化顺序不一样,导致v8内部创建的HC不能复用,每次都会重新创建

  • 实例化后避免添加新属性

    const p1 = {
        name: 'nordon' // 此时声明的属性 是 In-object属性
    }
    ​
    p1.age = 18 // 此时声明的属性是 Normal/Fast 属性,存储在 property store里, 需要通过描述数组间接查找
    
  • 尽量使用Array代替array-like对象

    const add = () => {
        // bad 不如直接在真实数组上效率高
        Array.prototype.forEach.call(arguments, item => {}) 
        
        // good
        const arr = Array.prototype.slice.call(arguemnts, 0)
        arr.forEach(item => {}) // forEach 的效率不如 for循环
    }
    
  • 避免读取超过数组的长度

    const arr = [1,2]
    if(arr[2] === 3) // 值为 undefined, 此时读取超过了数组的边界, 此时并不会报错, 
    // 1. 造成undefined 和 3 进行比较
    // 2. 若是arr[2]找不到值,会沿着原型链进行查找
    // 3. 可能会导致隐藏问题
    
  • 避免元素类型转换