JS性能优化方案总结

1,093 阅读4分钟

内存

  1. 内存的生命周期
  • 内存分配:声明变量、函数
  • 内存使用:调用、使用
  • 内存回收
// 申请
let obj = {}
// 使用
obj.name = '111'
// 释放
obj = null
  1. 常见内存泄漏
  • 全局变量
  • 未被清除的定时器和函数
  • 闭包
  • DOM的引用
  1. 如何避免
  • 减少不必要的全局变量
  • 使用完数据后及时解除引用
const element = {
    image: document.getElementById('image');
}
document.body.removeChild(document.getElementById('image'));
element.image = null

垃圾回收与GC算法

  1. 垃圾:对象不再被引用或者不能从根(当前全局变量对象)上访问
  2. GC:垃圾回收机制,工作内容是查找空间、释放空间、回收空间
  3. 引用与可达
let obj = {name: 'ztt'} // 可达
let all = obj // 引用
obj = null // 由于引用未断,依旧是可达

function func(obj1, obj2) {
    obj1.name = obj2
    obj2.name = obj1

    return {
        o1: obj1,
        o2: obj2
    }
}
let obj = func({val: 1}, {val:2})
console.log(obj)
  1. 引用计数
    • 设置引用数,判断当前引用数是否为0
    • 优点:发现垃圾立即回收、减少程序暂停
    • 缺点:无法回收循环引用的对象、时间开销大
    const user1 = {name: 'ztt'}
    const user2 = {name: 'zz'}
    const user3 = {name: 'xx'}
    
    const list = [user1, user2, user3] // 不会被回收
    function fn(){
        // 会被回收
        const num1 = 1
        const num2 = 2
    }
    fn()
    
  2. 标记清除
    • 递归遍历找到可达对象做标记、清除没有标记的对象和第一次所做的标记
    • 优点:解决循环引用不能回收的问题
    • 缺点:垃圾回收的对象地址不连续,导致空间碎片化
  3. 标记整理
    • 清除阶段会整理对象位置,再进行回收
    • 优点:解决空间碎片化

V8引擎

  1. 一款主流的JavaScript执行引擎,采用即时编译,内存设限
  2. 垃圾回收策略:分代回收思想,将内存分为新老生代,针对不同对象采用不同GC算法
  3. 小空间存储新生代对象(存活时间较短),采用复制算法+标记整理,继续分为两个等大空间(活动对象存储在使用空间From,标记整理后将活动对象拷贝到空闲空间To,From与To交换空间完成释放)
  4. 老生代指的是存活时间较长的对象(全局变量、闭包数据等),采用标记清除(垃圾回收)+标记整理(空间优化)+标记增量(提升效率)

performance工具

  • 监控程序的内存变化
  1. 使用步骤 ·打开浏览器输入目标网址 ·进入开发人员工具面板,选择性能 ·开启录制功能,访问具体界面 ·执行用户行为,一段时间后停止录制 ·分析界面中记录的内存信息 ·勾选内存,观察蓝色线条走势
  2. 内存问题
    • 内存泄漏:内存使用持续升高
    • 内存膨胀:在大多数设备上都存在性能问题
    • 频繁垃圾回收
  3. 内存监控
    • 浏览器任务管理器:根据JavaScript内存小括号的内存是否持续增加,判断是否存在问题
    • Timeline时序图:根据Performance工具蓝色走势图定位到具体操作
    • 堆快照查找分离DOM:Memory工具
    • 判断是否存在频繁GC(GC工作时应用程序是停止的,频繁且过长的GC会导致应用假死,用户使用中感知应用卡顿):Timeline中频繁的上升下降、任务管理器中数据频繁的增加减小

代码优化

  1. 慎用全局变量
  2. 缓存全局变量
let obj = document
obj.createElement...
  1. 通过原型增加方法
// before
const fn = function(){
    this.foo = function(){}
}
// after
const fn = function(){}
fn.prototype.foo = function(){}
  1. 避开闭包陷阱
function foo() {
    let el = document.getElementById('el')
    el.onclick = function(){
        console.log(el.id)
    }
    el = null;
}
  1. 避免属性访问方法的使用
function foo() {
    this.name = 'foo';
    this.getName = function(){
        return this.name; // 会增加一层重定义,没有访问的控制力
    }
}
  1. 优化for循环
for(var i = 0;len = arr.length, i <len;i++){}
  1. 采用最优的循环方式
  • forEach > for > for in
  1. 节点添加优化
  • 节点操作会有重绘和回流
// before
for(var i = 0; i < 10; i++) {
    var oP = document.createElement("div");
    oP.innerHTML = i;
    document.body.appendChild(oP);
}
// after
// 1、文档碎片
const fragEle = document.createDocumentFragment();
for(var i = 0; i < 10; i++) {
    var oP = document.createElement("div");
    oP.innerHTML = i;
    fragEle.appendChild(oP);
}
document.body.appendChild(oP);
// 2、克隆
var oldP = document.createElement('box1');
for(var i = 0; i < 10; i++) {
    var newP = oldP.cloneNode(false);
    newP.innerHTML = i;
    document.body.appendChild(newP);
}
  1. 直接量替换Object
// brfore
var a1 = new Array();
a1[0] = 1;
// after
var a = [1,2,3]
  1. 工具:JSBench

其他优化

  • 栈:存放执行上下文,由主线程进行回收
  • 堆:存放引用类型作用域,由GC垃圾回收机制回收
  1. 堆栈执行过程
let a = 10;
function foo(b) {
    let a = 2;
    function baz(c){
        console.log(c + b + a);
    }
    return baz
}
let fn = foo(2)

2. 使用事件委托 3. 减少声明及语句数 4. 减少循环体中活动 5. 减少数据读取次数

// before
function hasEle(ele, cls){
    return ele.className == cls
}
// after
function hasEle(ele, cls){
    var clsname = ele.className
    return classname == cls
}
  1. 减少作用域链查找层级
  • 多次创建执行上下文,上下文有自己的作用域,函数执行过程中,每遇到一个变量都会搜索作用域
  1. 减少判断层级:if

JS性能优化

目的

  • 首屏时间
  • 首次交互时间
  • 首次有意义内容渲染时间

页面性能检测

  • https://developers.googLe.com/speed/pagespeed/insights/

方法

  1. 只请求当前需要的资源:
  • 异步加载
  • 懒加载
  • 按需加载:优化polyfill(针对低版本浏览器的转义)https://polyfill.io/v3/url-builder/
  1. 缩减资源体积:
  1. 时序优化:
  • promise.all
  • ssr (而且还方便seo)
  • prerender prefetch preload
<!-- 让文件会立马解析DNS(DNS预解析) -->
<link rel="dns-prefetch" href=''>
<!-- 预加载 -->
<link rel="dns-preload" as="image" href=''>
<!-- 预链接 -->
<link rel="dns-preconnect" href=''>
  1. 合理利用缓存:
  • cdn预热和刷新

通过刷新功能,您可以强制CDN节点回源并获取最新文件;通过预热功能您可以在业务高峰期预热热门资源,提高资源访问效率。