欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

前端路 - JavaScript 性能优化

程序员文章站 2022-05-14 12:02:00
...

浏览器内存管理


基本概念

  • 内存
    • 由可读写的单元组成,表示一片可操作空间
    • JS 中的内存空间在变量定义时自动分配,无法人为指定明确大小
  • 管理:认为的去操控一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间
  • 管理流程:申请 => 使用 => 释放

JavaScript 中垃圾的定义

  • 不再被引用的对象
  • 不能从根*问到的对象(非可达对象)
  • 程序中不再需要使用的对象
  • 程序中不能再访问到的对象

JavaScript 中可达对象的定义

  • 可以访问到的对象
  • 通过根可以有路径(如引用、作用域链)查找到的对象

内存问题

  • 外在表现
    • 页面出现延迟加载或者经常性暂停(网络正常情况)
      • 因为某部分程序使得内存急剧升高,导致底层存在频繁的垃圾回收
    • 页面持续性出现糟糕的性能
      • 程序运行时,为了是当前页面实现最大运行速度,所需要(申请)的内存远超过了设备本身所能提供的大小,即内存膨胀
    • 页面的性能随时间延长越来越差
      • 存在内存泄露
      • 程序运行时,未能及时清理导致内存持续升高,最终内存泄漏
  • 监控方式
    • 浏览器任务管理器
    • Timline 时序图记录
    • 堆快照查找分离 DOM
    • 判断是否存在频繁的垃圾回收
      • GC 工作时,应用程序时停止的
      • 频繁且过长的 GC 会导致应用假死
      • 用户使用感知应用卡顿

 

GC 算法


基本概念

  • GC是一种垃圾回收的工作机制
  • 工作的内容就是查找内存中的垃圾并释放和回收空间
  • 算法,即工作时查找和回收所遵循的规则

常用 GC 算法

  引用计数 标记清除 标记整理
核心思想 就是设置引用数,判断当前对象的引用数是否为 0 分标记和清除两个阶段 可以看做是标记清除的增强
实现原理

1. 当前对象的引用关系改变时,修改其引用数字

2. 当引用数字为 0 时,立即回收该对象

1. 遍历所有对象查找标记活动对象

2. 遍历所有对象清除没有标记的对象

3. 回收相应的空间

1. 标记阶段的操作和标记清除一致

2. 清除阶段会先执行整理,移动对象的位置,使得回收的空间连续

优点

可以及即时回收垃圾对象

最大限度减少程序卡顿(保证空间不会被占满)

可以解决循环引用不能回收的问题  
缺点

无法回收循环引用的对象

时间资源开销大(监控对象,维护引用数)

因为标记的对象空间不完全连续,回收后有可能产生大量碎片化空间,浪费空间,不利于内存使用的最大化  

 

V8 引擎


概述

V8 引擎是一款主流的 JavaScript 执行引擎,采用的是即时编译的形式。V8 引擎本身是为浏览器而生的引擎,过大的内存占用会影响浏览器的性能,因此,V8 引擎的内存设有上限。而且,V8 引擎将内存空间一分为二,其中,较小的内存空间用于存储新生代对象,大空间用于存储老年代对象。

垃圾回收策略

V8 引擎采用的是分代回收思想,将内存分为新生代和老年代,针对不同的对象采用不同的回收算法。

新生代(存活时间较短的对象)的回收实现

  • 回收过程采用复制算法 + 标记整理
  • 新生代内存区分为 2 个等大小的空间
  • 使用空间为 From,空闲空间为 To
  • 活动对象存储于 From 空间
  • 标记整理后将活动对象拷贝至 To
  • From 与 To 交换空间完成释放
  • * 拷贝过程中,若一轮 GC 后,某个新生代对象仍然存留,则该新生代的对象会被移动至老年代保存

老年代(存活时间较长的对象)的回收实现

  1. 使用标记清除完成空间垃圾的回收
  2. 采用标记整理进行空间优化
  3. 采用增量标记进行效率优化

 

性能优化方案


1、使用变量对 DOM 元素进行缓存

let oDiv = document.querySelector('#div')

通过使用变量来缓存 DOM 元素,在后续的使用中,直接调用变量即可操作对应的 DOM 元素,而不需要重新检索获取该 DOM 元素。从而,减少 DOM 元素的检索操作,优化性能。

2、通过原型对象添加附加方法

let fn1 = function() {
    this.foo = function() {
        console.log(11111)
    }
}
let f1 = new fn1()
// ===>
let fn2 = function() {}
fn2.prototype.foo = function() {
    console.log(11111)
}
let f2 = new fn2()

3、避开闭包陷阱

当闭包使用不当时,容易出现内存泄露,所以当闭包中的变量完成使用后,应置空

4、避免属性访问方法的使用

即,给对象添加方法,并通过该方法对对象属性做修改。

let obj = {
    name: 'Azer',
    getName() {
        return this.name
    }
}

因为对象所有的属性都是外部可见的,所以JS 是不需要属性访问方法的。使用属性访问方法只会增加一层重定义,没有访问的控制力

5、for 循环的优化

因为对于类数组元素的长度,每次引用都会触发新的计算,所以在 for 循环中,提前获取并缓存数组的长的,可以有效的减少重新计算的次数,提升性能。

for(var i = 0; i < arr.length; i++) {}
// ===> 
for(var i = 0, len = arr.length; i < len; i++) {}

6、直接量替换 new 操作

let arr = new Array(3)
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// ===>
let arr = [1, 2, 3]