性能优化

183 阅读9分钟

clipboard.png

clipboard.png

内存管理

javascript内存管理

clipboard.png

内存变化持续升高、内存泄露。

、.png

clipboard.png

// 申请空间
let obj = {}
// 使用
obj.name = 'lg'
// 释放
obj = null;

javascript垃圾回收

clipboard.png

可达对象,从根上出发,可以访问到的,就是可达

clipboard.png

let obj = {name : 'a'}

let ali = obj;

obj = null;

// ali还在引用,name还是可达

clipboard.png

clipboard.png

delete obj.o1
delete obj.o2.prev

clipboard.png

o1不可达了,变成了辣鸡

垃圾回收与常见的GC算法

clipboard.png

clipboard.png

1、name作用域全局,但是没有引用了

2、name局部作用域

clipboard.png

clipboard.png

引用计数算法

clipboard.png

引用计数算法优缺点

clipboard.png

引用数为0,就立即释放 时刻监控引用为0的情况,所以可以一直释放,保证了当前内存不会占满

clipboard.png

1、obj1,obj2局部作用于,在根查找不到了。但是由于两者在作用域范围内,相互引用了,所以引用计数不为0,此时就造成了内存浪费;

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

2、需要维护数值变化,所以要一直查找数值是否为0,查找的数值越多,开销就越大

标记清楚算法

clipboard.png

标记清楚算法优缺点

1、对可达对象进行标记。

2、没有标记的,清除; 抹除可达对象的标记。

3、回收。

clipboard.png

标记阶段:递归的方式去遍历可达对象,进行标记。

清除阶段:GC后续开始工作的时候,就会把没有标记的对象进行回收,并清除已标记的对象的标记。

回收的空间会放在空闲列表中,方便程序直接在列表中申请空间使用

标记算法优缺点

clipboard.png

相对于引用计数,引用计数无法清除相互引用的对象。

而标记清除可以,如果局部函数中的相互引用对象,不可达,则认为可以回收。

缺点:

释放完的空间分散的,地址不连续。导致空间碎片化。无法最大化使用空间。

比如两个域,但是刚好数据是1.5个域,那么左侧空间过大,右侧空间过小。

标记整理算法实现原理

clipboard.png

整理过程:

clipboard.png 将对象进行移动,形成连续的空间

clipboard.png

常见的GC算法

1、引用计数:通过引用计数器,维护每个对象的引用数值,通过数值来判断是否需要回收 2、标记清除:遍历所有对象,对当前所有活动对象进行标记,对没有标记的对象进行回收,释放空间 3、标记整理:在清除上的前置操作,会整理当前的地址空间 4、分代回收:V8

各自优缺点

1、 优点:即时回收垃圾对象,只要发现计数器为0,就回收。最大程度减少程序卡顿。 缺点:无法回收存在的循环引用的对象。 资源消耗大,需要频繁操作、维护计数器。

2、 优:回收循环引用的对象 缺:容易产生碎片空间。 不会立即回收垃圾对象,需要等到遍历完成之后,才开始清除,清除的时候,程序会停止工作。

3、 优:减少空间碎片化。 缺:不能立即回收垃圾对象。

V8

主流的avascript执行引擎(优秀的内存管理机制 即时编译(可以把源码直接翻译成可执行,以往是需要先将源码转成字节码 内存设限,64位系统 1.5G,32位系统 800M

官方测试,当垃圾内存达到1.5G的时候,进行分量标记算法只需要消耗50ms;采用非分量算法,需要1s; 所以以1.5G为界;

V8引擎的辣鸡回收

策略:

分代回收思想

内存分为新生代、老生代

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

clipboard.png

clipboard.png

内存有上限,基于此,需要分代回收思路,不同代采用更适合的GC算法

V8内存分配:

clipboard.png V8内存空间一分为二,上图。

小空间用于存储新生代对象(32M | 16M)(64bit | 32bit)

新生代指的是存活时间较短的对象(局部作用域变量,执行完肯定要回收。不像全局变量,需要程序退出,才会回收。)

新生代 —— 实现回收:

回收过过程采用复制算法 + 标记整理

新生代内存区分为两个等大小空间

from是使用空间,to是空闲空间

活动对象存储于from空间 (当from空间满了

标记整理后将活动对象拷贝至to空间,完全释放from空间,(活动对象就是还有在使用的对象,完全释放from空间,因为活动对象已经复制到to了

from空间与to空间交换 (复制之后,to空间都是当前的活动对象,直接交换即可。

回收细节

拷贝过程,发现新生代在老生代也出现了,那么这个对象,就要晋升,

晋升就是将新生代对象,移动到老生代;

判断标准:

一轮GC还存活的新生代,那么就要晋升。

拷贝过程,To空间的使用率超过25%。(在未来回收操作的时候,是要把To跟from进行交换,那么就是剩余少于80%了,新对象可能会存储不进来。

老生代 —— 实现回收:

存储在右侧空间

内存大小(1.4G | 700M) (64bit | 32bit)

老生代指的就是存货时间长 (全局作用域对象,闭包

主要 标记清除,标记整理,增量标记算法

首先标记清除完成垃圾空间回收(会有空间碎片化出现

采用标记整理进行空间优化 (当新生代需要移动到老生代,出现空间不足,此时会触发标记整理,优化空间

采用增量标记进行效率优化

细节对比

(与新生代新生代区域垃圾回收,使用空间换时间 (采用复制算法,每时每刻,内部都会有空闲空间存在;因为新生代本身空间小,空间的浪费,换来的时间效益,是微不足道的。

老生代垃圾回收不适合复制算法,(如果跟新生代一样,采用一分为二,那么就有很多空闲空间浪费;老年代存储的对象也对,复制算法耗时也多。

增量算法

clipboard.png

垃圾回收工作的时候,是会阻塞javascript运行, 程序执行之后,空档期进行垃圾回收。

增量算法,就是把整个垃圾回收工作,分为几个小部分,分批完成。而不是以往的整个工作完成。合理分配时间消耗。利用每一个空档期。

先遍历找到第一层可达对象,然后就继续程序正常执行,然后再查找二层,再正常进行,知道完成标记工作。 开始清除,清除之后执行。

非增量总耗时不超过1s,这里采用增量,时间耗时就更少,所以程序多次阻塞,实际耗时不多。

总结

V8是主流的javascript执行引擎

V8内存设置上限(过多,用户感知明显

基于分代回收思想实现垃圾回收

内存分新生代、老生代

垃圾回收常见的GC算法(新生代:复制+标记整理; 老生代:标记清除+整理+增量标记)

Performace工具

clipboard.png

内存问题的体现

页面延迟加载、经常性暂停 (内存泄露: 内存使用持续升高

持续性出现槽糕的性能 (内存膨胀,内存满了的时候会去申请扩大内存,但是仍然不够满足

页面性能随时间越来越差 (频繁的内存回收

浏览器的任务管理器

timeline时序图记录

对快照查找分离DOM

判断是否存在频繁的垃圾回收

浏览器任务管理器

shift + esc

clipboard.png

clipboard.png

内存,表示原生内存,DOM节点占据的内存;不断增大,DOM增加越多

JavaScript内存,js的堆,括号中表示界面中可达对象的数量;

无法定位具体问题;

Timeline记录内存

clipboard.png

堆快照查找分离DOM 工作原理:

clipboard.png

分离dom:脱离dom树,在界面不显示。

Memory快照

clipboard.png

clipboard.png

clipboard.png

clipboard.png

频繁的垃圾回收,会阻塞程序运行。

Performance

clipboard.png

如何精准测试 JavaScript 性能

clipboard.png

代码优化实例

慎用全局变量

clipboard.png

全局变量跟局部变量的性能差别

clipboard.png

将使用中无法避免的全局变量,缓存到局部 document缓存到obj中

clipboard.png

通过原型新增方法

在原型对象,新增实例对象需要的方法

clipboard.png

避开闭包陷阱

闭包特点: 外部具有指向内部的引用 在外部作用域访问内部作用域的数据

clipboard.png

跨作用域使用对象,闭包。

clipboard.png

el被引用,无法回收。 此处,按钮对象, 被document引用、被el引用; 如果按钮以后被移除了,document被移除了,但是el还应用,内存不回收。

clipboard.png

需要手动清除。

避免属性访问方法使用

clipboard.png

clipboard.png

ops/s 指的是每秒可完成多少个这样的operations请求。

For循环优化

采用最优循环方式

clipboard.png

forEach > for > forin

节点添加优化

节点操作必然伴随着回流,重绘;

利用文档碎片

clipboard.png

克隆优化节点操作

clipboard.png

直接量替换Object操作

clipboard.png

JSBench jsbench.me/

运行速度快,不一定就是性能好。 速度只是性能中的一点,应该从空间、时间一起考虑。

减少判断层级,巧用return

明确判断条件,用case switch,易于维护

减少作用域链查找层级

查找层级多,但是空间消耗小。

name = '1';
function foo() {
   name = '2';
   console.log(name)
}

查找层级少,但是空间消耗大。

name = '1';
function foo() {
   var name = '2';
   console.log(name)
}

减少数据读取次数

每一次都要在ele查询

var a = {b: 1}
function foo(ele, cls) {
    return ele.b === cls
}

提前缓存,但是占用空间

var a = {b: 1}
function foo(ele, cls) {
    var name = ele.b;
    return name === cls
}

字面量与构造式

构造式,相当于函数调用

obj = new Object();
obj.name = 1;
obj.age = 12;

str = new String('1')

字面量,直接开辟新空间存储数据

obj = {
    name: 1,
    age: 12
};

str1 = '1'

减少循环体活动

不变的数据,在循环体外部执行,相当于数据缓存

clipboard.png

for ----  while

clipboard.png

for是从前,往后找,所以会多了一些前置条件判断 while是从后往前找

减少声明以及语句数 使用的时候再声明,而不是提前缓存。

clipboard.png

与之前的优化,减少访问层级,有冲突。

1、语句数过多,编译阶段有所消耗

2、不会每次都使用的,可以直接使用,也减少空间

clipboard.png

语句数少了,但是书写的时候,应该用第一种,可读性高。

采用事件委托

var list = document.querySelectorAll('li);
var showText = (e) => {console.log(e.target.innerHTML)}
for(let item of list) {
    item.onclick = showText;
}


var ul = document.querySelectorAll('ui);
var showText = (e) => {
    var obj = e.target;
    if (obj.nodeName.toLowerCase() === 'li') {
      console.log(e.target.innerHTML)  
    }
}
ul.addEventListener('click', showText, true);

事件监听太多占用内容,但是添加事件委托,也要考虑查找子元素的时候

文章内容输出来源:拉勾教育Java高薪训练营;