Javascript性能优化

178 阅读9分钟

js内存管理

* 内存:由可读写单元组成,表示一片可操作的空间
* 管理:人为的去操作一片空间的申请、使用和释放
* 内存管理:开发者主动申请空间、使用空间、释放空间
* 管理流程:申请——使用——释放

javascript的内存管理
申请内存空间  let obj ={}
使用内存空间   obj.name = 'lg'
释放内存空间   obj = null

js中的垃圾回收

  • javascript中内存管理是自动的
  • 对象不再被使用时就是垃圾
  • 对象不能从根上访问到就是垃圾

javascript 中的可达对象

  • 可以访问到的对象就是可达对象(引用,作用域链)
  • 可达的标准就是从根出发是否能够被找到
  • javascript的根可以理解为全局变量
let obj = {name:'xm'}
let all = obj 
obj = null  
//obj依然是可达操作 原因是 obj虽然null 了但是all 还依然使用者

垃圾回收的过程演示

function objGroup(obj1,obj2){
    obj1.nex1 = obj2;
    obj2.prev = obj1;
    return{
        o1:obj1,
        o2:obj2
    }
}

let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)
// 打印结果
{o1:{name:'obj1',next:{name:'obj2',prev:[Circular]}},
o2:{name:'obj2',next:{name:'obj1',next:[Circular]}},
}

可达对象的梳理:
global variable  obj———— Object ———o1
global variable  obj———— Object ———o2
o1————next————o2
o2————prev————o1

GC定义与作用

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

GC里的垃圾是什么?

  • 程序中不再需要使用的对象
function func(){
    name = 'lg'
    return `${name} is coder`
}
func();
  • 程序中不能再访问到的对象
function func(){
    const name = 'lg'
    return `${name} is a coder`
}
func()

GC算法是什么

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

常见GC算法

  • 引用计数
  • 标记清楚
  • 标记整理
  • 分代回收

引用技数算法

  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器
  • 引用关系改变时修改引用数字
const user1 = {age:11}
const user2 = {age:22}
const nameList = [user1.age,user2.age]
function fn(){
    const num1 = 1
    const num2 = 2
}
fn();
fn执行后 num1 num2 不再被引用 所以计数器为0就会被回收
user1 在const之后 nameList中又被使用 所有不被回收

引用技数算法优点/缺点

优点:

  • 发现垃圾时立即回收
  • 最大限度减少程序卡顿时间 缺点:
  • 无法回收循环引用对象
  • 时间开销大
// 对象之间循环引用
function fn(){
    const obj1 = {}
    const obj2 ={}
    obj1.name = obj2
    obj2.name = obj1
    return 'lg is a coder'
}
fn()

标记清除算法实现原理

  • 核心思想: 分标记和清除两个阶段完成
  • 遍历所有对象找标记活动的对象<和可达对象相同>
  • 遍历所有对象清除没有标记对象
  • 回收相应的空间
解释: 会遍历所有对象同时会找到他的子对象看是否有所关联
global——A——D 
global——B
global——c——E

如果是内部声明的对象例如方法里面使用 c1 别的地方并没有引用 则用完会被清空

标记清除算法优点/缺点

优点:

  • 相对于引用计数,解决内部作用域中定义的变量,外部不能访问,内部使用过后被清除(解决循环引用不能回收的问题) 缺点:
  • 地址不连续,空间碎片化,浪费空间
  • 不会立即执行回收垃圾对象

标记整理算法 工作原理

  • 标记整理可以看做是标记清空的增强
  • 标记阶段的操作和标记清空一致
  • 清除阶段会执行整理 移除对象位置
//标记整理的过程
1]整理那些需要清空和没有引用的数据
2]、进行标记
3]、移动位置将不需要清空的放到一起,需要清空的放到一起
4]、进行清空(都是连续的)

标记清除算法优点/缺点

优点:

  • 减少碎片化空间 缺点:
  • 不会立即回收垃圾对象

认识V8引擎

  • V8是一款主流的javascript执行引擎
  • V8采用即时编译
  • V8内存设限 (64位不能超过1.5G,32位不超过800M,设置原因是基于web浏览器的这些已经够用了,为了方便垃圾回收)

垃圾回收策略

  • 采用分代回收的思想
  • 内存分为新生代、老生代
  • 针对不同对象采用不同的算法
//过程图
V8内存空间——新生代对象存储——采用具体GC算法
V8内存空间——老生代对象存储——采用具体算法

V8常用的GC算法

  • 分代回收
  • 空间复制
  • 空间清除
  • 标记整理
  • 标记增量

新生代

v8内存分配

From(新) To 老生代存储(老)

  • V8内存空间一分为二(左新,右老)
  • 小空间用于存储新生对象(32M | 16M)
  • 新生代指的是存货时间比较短的对象

V8如何回收新生代对象

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

回收细节说明

  • 拷贝过程中可能出现晋级
  • 晋升就是讲新生代对象移动至老生代
  • 一轮GC还存活的新生代需要晋升
  • To空间的使用率超过25%

老生代

V8如何回收老生代对象

  • 老生代对象存放在右侧老生代区
  • 64位操作系统1.4G,32位700M
  • 老年代对象就是指存货时间较长的对象(例如在全局变量下存在的对象,闭包中存在的对象)

老年代对象回收实现

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

细节对比

  • 新生代区域垃圾回收使用空间换时间
  • 老生代区余垃圾回收不适合复制算法

为什么使用Performance

  • GC的目的是为了实现内存空间的良性循环
  • 良性循环的基石是合理使用
  • 时刻关注才能确定是否合理
  • Performance提供了多种监控方式

Performance 使用步骤

  • 打开浏览器输入目标网址
  • 进入开发人员工具面板,选择性能
  • 开启录制功能,访问具体界面
  • 执行用户行为,一段时间后停止录制
  • 分析界面中记录的内存信息

内存问题的体现

  • 页面出现延迟加载或经常性暂停
  • 页面持续性出现糟糕的性能
  • 页面的性能随时间延长越来越差

监控内存的几种方式

界定内存问题的标准

  • 内存泄漏: 内存使用持续升高
  • 内存膨胀: 在多数设备上都存在性能问题
  • 频繁垃圾回收:通过内存变化图进行分析

监控内存的几种方式

  • 浏览器任务管理器
  • Timeline时序图记录
  • 堆快照查找分离DOM
  • 判断是否存在频繁的垃圾回收

任务管理器监控内存变化

1]、 正常写功能例如生成1000000长度的数组
2]、打开运行浏览器 调出任务管理器 shift+esc
3]、找到javascript使用的内存
4]、进行对比查看
内存占用空间:原生内存,DOM元素所占用的空间
javascript:js所占用的空间比 (小括号里面显示实际大小如果实际大小实施增加 证明有问题)

什么事分离DOM(堆快照)

  • 界面元素存活在DOM树上
  • 垃圾对象时的DOM节点
  • 分离状态的DOM节点(在节点中已经不再使用,但是子js中还有所引用。所以在js中依然占用内存空间的时候就是内存泄漏)
通过浏览器自带工具,内存建立堆快照进行对比操作
控制台——Memory——建立快照 通过搜索deta查看是否有分离的DOM 可以将分离的DOM通过清理处理

为什么确定是否存在频繁垃圾回收

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

确定频繁的垃圾回收

  • Timeline中平凡的上升下降
  • 任务管理器中数据不断地增加减少

如何精准测试Javascript性能

  • 本质上就是采集大量的执行样本进行数学统计和分析
  • 使用基于Benchmark.js 的https:jsperf.com完成

jsperf使用流程

  • 使用github账号登录
  • 填写个人信息(非必填)
  • 填写详细的测试用例信息(title,slug)
  • 填写准备代码(DOM操作时经常使用)
  • 填写必要的setup与teardown代码
  • 填写测试代码片段

为什么要慎用全局变量

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

缓存全局变量

  • 将使用中无法避免全局变量缓存到局部
// 演示的示例
html
<input type="button" value="btn" id="btn1">
<p>111</p>
<input type="button" value="btn" id="btn2">
script
function getBtn(){
    let obt1 = document.getElementById('btn1');
    let obt1 = document.getElementById('btn1')
}


function getBtn(){
let obj=document;
    let obt1 = obj.getElementById('btn1');
    let obt1 = obj.getElementById('btn1')
}

多次用的方法放到原型上

避开闭包陷阱

  • 外部具有指向内部的引用
  • 在‘外’不做用于访问'内'不做用于的数据
// 演示示例
function(){
    var name="lg";
    function fn(){
        console.log('name')
    }
    return fn
}
var a = foo()
a();
  • 不要为了闭包儿闭包
  • 使用闭包需要小心内存泄漏
//示例
function foo(){
    var el = document.getElementById('btn');
    el.onclick = function(){
        console.log(el.id)
    }
    el = null //如果dom中的元素被删除 这个时候没有清空就会造成 js中还在引用 js中只是减少了一个引用 垃圾回收并没有清空 如果变为null则彻底清除 代码被回收
}
foo();

js中的面向对象

  • js不需要属性的访问方法,所有属性都是外部可见的
  • 使用属性访问方法只会增加一层重定意,没有访问的控制力
//代码示例
function Age(){
    this.age = '12';
}
var xl = new Age();
console.log(xl.age)

function Age(){
    this.age = '29'
    this.age = function(){
        return this.age;  //为了性能更好不需要这种访问方式
    }
}
var xl = new Age();
xq.age();

for循环优化

//示例
for(let i=0;len = btn.length;i<len;i++){
    
}
//将len的长度定义缓存 性能更快

采用最优的循环方式

foreach>for>for in 性能排比

节点操作优化

//使用虚拟节点
使用前:
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.body.appendChild(oP)
}
document.body.appendChild(fragEle)

使用自变量比使用new性能更快

在创建节点的时候使用copy比创建更快