1. javascript性能优化

1.1. 内容概要

  • 内存管理
  • 垃圾回收与常见GC算法
  • V8引擎的垃圾回收
  • Performance工具

1.2. 内存管理

申请---使用---释放,ecmascript不像c++那样可以主动调用api完成内存管理

1.3. 垃圾回收

什么是可达对象

  • 可以访问到的对象(引用,作用域链)
  • 可达的标准就是从根出发沿着作用域链能否被找到
  • javascript中的根可以理解为全局变量对象
let obj = { name: 'zs' }
let p = obj
obj = null
// p是可达的

1.4. GC算法

GC是垃圾回收机制的简写,它可以找到内存中的垃圾,并释放和回收空间

GC中的垃圾是什么?

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

1.4.1. 常见的GC算法

1.4.2. 引用计数

设置引用数,判断当前引用数是否为0;引用发生改变时修改引用数字,引用为0时立即回收

优点:

  • 发现垃圾时立即回收
  • 最大限度减少程序暂停

缺点

  • 无法回收循环引用的对象
  • 时间,资源开销大

标记清除法

  • 将整个垃圾回收机制分成两个阶段,标记和清除

  • 遍历所有对象找到所有活动对象(可达对象)进行标记

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

  • 回收相应空间

优点

解决循环引用不能回收的问题

缺点

由于地址不连续导致空间碎片化 不会立即回收垃圾对象

标记整理

  • 标记清除的增强操作

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

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

优点

减少碎片化空间

缺点

不会立即回收垃圾对象

1.5. V8垃圾回收策略

  • 采用分代回收的思想,将内存分为新生代(存活时间较短的对象),老生代
  • 针对不同对象采用不同算法

V8内存分配

  • V8空间一分为二
  • 小空间用于存储新生代对象

新生代对象回收实现 (空间换时间)

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

老生代对象回收实现

  • 主要采用标记清除,标记整理(新生代对象往老生代对象移动,而老生代空间不足时),增量标记算法
  • 首先使用标记清除完成垃圾空间的回收
  • 采用标记整理进行空间优化
  • 采用增量标记进行效率优化

回收中可能出现晋升

1.6. DOM节点的类型

分为垃圾对象和分离DOM

  • 垃圾对象: 在dom树上脱离,且js中也没有引用
  • 分离DOM: 在dom树上脱离,但是js中有引用,此时会造成内存泄漏

chrome开发工具中的堆快照可以查到到分离dom (Detached)

1.7. 频繁gc

  • GC工作时应用程序是停止的
  • 频繁且过长的GC会导致应用假死
  • 用户会感知到卡顿

如何确定

  • timeline中频繁的上升下级
  • 任务管理器中数据频繁的增加减小

1.8. 代码优化部分

1.8.1. 如何精准测试JavaScript性能

  • 采集大量的执行样本进行数学统计和分析
  • 使用基于benchmark.js的jsperf.com完成

1.8.2. 慎用全局变量

  • 影响GC的垃圾清除
  • 局部变量会遮蔽或污染全局变量

如通过document获取元素时,可以使用变量缓存document

1.8.3. 在原型对象添加附加方法

1.8.4. 避开闭包陷阱

  • 外部具有指向内部的引用
  • "外"部作用域访问"内"部作用域的数据
function foo() {
  var el = document.getElementById('btn')   // el本身是dom节点上的,此处存了下而已
  el.onClick = function() {   // 1. el引用了foo函数内部的function
    console.log(el.id)    // 用到了foo作用域的el
  }
  // 释放引用,因为GC不会回收这里的el引用
  el = null
}

foo()   // 2. 外部作用域访问到了foo内部的el

1.8.5. 避免属性访问方法使用

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

1.8.6. 遍历优化

for循环优化,缓存数组长度

性能顺序 forEach > for > for...in

1.8.7. 节点优化

节点的操作伴随着回流和重绘

使用文档碎片优化节点添加

for(var i =0;i < 10;i++) {
  var op = document.createElement('p')
  document.body.appendChild(op)
}

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

1.8.8. 定义数组,对象时,使用字面量而不是构造式

1.9. 减少判断层级

无用的条件提前return调 明确的条件而不是区间使用switch...case代替if-else-if

1.10. 减少作用域链查找层级

每当一个函数执行的时候就会产生一个执行上下文,当函数执行完之后,如果没有闭包存在,上下文就会被销毁掉

1.11. 减少数据读取

访问字面量,局部变量是最快的,因为存在栈中

要减少时间查询消耗,就应该减少对象成员查找次数,和属性的嵌套层级,我们可以提前对对象进行缓存

Jason Huang all right reserved,powered by Gitbook该文件最后修改时间: 2020-11-14 16:33:49

results matching ""

    No results matching ""