JS性能优化

392 阅读6分钟

​ 任何可以提高运行效率降低运行运行开销的行为(网络、数据传输方式、框架)

内存管理

​ 内存:由可读写单元组成,表示一片可操作的内存空间

​ 管理:人为的去操作一片空间的申请、使用和释放

​ 内存管理:开发者主动去申请空间、使用空间、释放空间

js中的内存管理
// 申请空间
let obj = {}
// 使用空间
obj.name = 'lg'
// 释放空间
obj = null
javascript中的垃圾
  • js中内存管理是自动的
  • 对象不在被引用时是垃圾
  • 对象不能从跟上访问时是垃圾
js中的可达对象
  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根出发是否能够被找到
  • js中的根可以被理解为全局变量对象

找到垃圾,让js引擎进行空间的释放和回收

GC算法

​ GC: 垃圾回收机制

  • 程序中不在需要使用的对象
  • 程序中不能再访问到的对象

​ GC算法是什么

  • GC是一种机制,垃圾回收器完成的具体工作
  • 工作的内容就是查找垃圾释放空间、回收空间
  • 算法就是工作时查找和回收所遵循的规则
引用计数算法实现原理

​ 核心思想: 设置引用数。判断当前引用数是否为0

​ 引用计数器:

​ 引用关系改变时修改引用数字

​ 引用数字为0时立即回收

const user1 = {age: 11}
const user2 = {age: 12}
const user3 = {age: 13}

const namelist = [user1.age, user2.age, user3.age]

function fn() {
    const num1 = 1
    const num2 = 2
}
fn()

优点:发现垃圾时立即回收,最大限度减少程序暂停

缺点:无法回收循环引用的对象,时间开销大

function fn() {
    const obj1 = {}
    const obj2 = {}
    obj1.name = obj2
    obj2.name = obj1
    return 'zxt'
}

fn()
标记清除算法

核心思想: 分标记和清除两个阶段完成

遍历所有对象找标记活动对象

遍历所有对象清除没有标记对象

回收相应空间

优点:解决循环引用不能回收问题

缺点:产生空间碎片化问题,不能最大空间使用

标记整理算法

可以看成标记清除的增强

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

清除阶段会先执行整理,移动对象位置

优点:减少碎片化空间

缺点:不会立即回收垃圾对象

V8

是一款主流的js执行引擎

采用即时编译

v8内存设限

垃圾回收策略
  • 采用分代回收的思想

  • 内存分为新生代、老生代

  • 针对不同对象采用不同算法

    v8常用GC算法:分代回收、空间复制、标记清除、标记整理、标记增强

回收新新生代对象
  • v8内存空间一分为二

  • 小空间用于存储新生代对象(32m|16m)

  • 新生代是指存活时间较短的对象

    实现

    • 回收过程采用复制算法+标记整理
    • 新生代内存区分为两个等大小空间
    • 使用空间为Form,空闲空间为To
    • 活动对象存储于Form空间
    • 标记整理后将活动对象拷贝至To
    • Form与To交换空间完成释放

    回收细节说明

    • 拷贝过程中可能出现晋升
    • 晋升就是将新生代对象移动至老生代
    • 一轮GC还存活的新生代需要晋升
    • To空间使用率超过25%
回收老生代对象
  • 老生代对象放在右侧老生代区域

  • 64位操作系统1.4G,32位操作系统700M

  • 老生代对象就是指存活时间较长的对象

    实现

    • 主要采用标记清除、标记整理、增量标记算法
    • 首先使用标记清除完成垃圾空间的回收
    • 采用标记整理进行空间优化

    细节对比

    • 新生代区域垃圾回收使用空间交换时间
    • 老生代区域垃圾回收不适合复制算法
标记增量如何优化垃圾回收

performance工具介绍

监控内存的几种方式

浏览器任务管理器
Timeline时序图记录
堆快照查找分离DOM

分离DOM

  1. 界面元素存活在dom上
  2. 垃圾对象时的dom节点
  3. 分离状态的DOM节点
为什么确定是频繁垃圾回收
  • GC工作时应用程序是停止的

  • 频繁且过长的GC会导致应用假死

  • 用户使用中感知应用卡顿

    • Timeline中频繁的上升下降
    • 任务管理器中频繁的增加减少

慎用全局变量

  • 全局变量定义在全局执行上下文,是所有作用域链的顶端
  • 全局执行上下文一直存在于上下文执行栈,直到程序退出
  • 如果在某个局部作用域出现了同名变量则会遮蔽或污染全局

缓存全局变量

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

var fn1 = function () {
    this.foo = function () {
        console.log(1111)
    }
}
fn1 = new fn1()

var fn2 = function () {}
fn2.prototype.foo = function () {
    console.log(1111)
}
fn2 = new fn2()

避开闭包陷阱

function foo () {
var el = dociment.getElementById('btn')
   e.onclick = function() {
       console.log(el.id)
   }
   el = null
}
foo()

避免属性访问方法使用

  • JS不需属性的访问方法,所有属性都是外部可见的
  • 使用属性访问方法只会增加一层重定义,没有访问控制力
function Person() {
    this.name = 'zs'
    this.age = 18
    this.getAge = function () {
        return this.age
    }
}
const p1 = new Person()
const a = p1.getAge()

function Person() {
    this.name = 'zs'
    this.age = 18
}
const p2 = new Person()
const b = p2.age

FOR循环优化

var btns = dociment.querySelector('btn')
for (var i = 0; i < btns.length; i++) {
    console.log(i)
}

// 优
for (var i = 0; len = btns.length; i < len ; i++) {
    console.log(i)
}

最有循环算法

var arr = new Array(1, 2, 3, 4, 5)
// 优
arr.forEach(item => {
    console.log(item)
})

// 二
for (var i = 0; len = arr.length; i < len ; i++) {
    console.log(arr[i])
}

for (var i in arr) {
    console.log(arr[i])
}

文档碎片优化节点添加

for (var i = 0; i < 10; i++) {
    var oP = document.createElement('p')
    oP.innerHTML = i
    document.body.appendChild(oP)
}
// 优
const fragEle = document.createDocumentFragment()
for (var i = 0; i < 10; i++) {
    var oP = document.createElement('p')
    oP.innerHTML = i
    fragEle.appendChild(oP)
}
document.body.appendChild(fragEle)

克隆优化节点操作

for (var i = 0; i < 3; i++) {
    var oP = document.createElement('p')
    oP.innerHTML = i
    document.body.appendChild(oP)
}
// 优
var oP = document.createElement('p')
for (var i = 0; i < 3; i++) {
    var newP = oldP.cloneNode(false)
    oP.innerHTML = i
    document.body.appendChild(oP)
}

直接量替换new Object

var a = new Array(3)
a[0] = 1
a[1] = 2
a[2] = 3

var a = [1, 2, 3]