js性能优化

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js性能优化相关的知识,希望对你有一定的参考价值。

JS性能优化

内存

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

js的垃圾

  • js的内存管理是自动的。
  • 对象不再被引用时就是垃圾。
  • 对象不能从根上访问到时就是垃圾。(js的根可以理解为全局变量对象)

GC算法

  • 垃圾回收机制的简写
  • GC可以找到内存中的垃圾,并释放和回收空间。
  • 算法就是工作时查找和回收所遵循的规则。
    优点:
  • 发现垃圾立即回收。
  • 最大限度的减少程序暂停。
    缺点:
  • 循环引用对象无法回收
  • 时间开销大(时刻维护对象的引用)

常见的GC算法

引用计数
  • 维护一个引用计数器,判断当前引用数是否为0
  • 引用数字为0的时候立即回收。
  • 互相引用的对象无法被回收。

优缺点:

  • 可以即使回收垃圾对象
  • 减少程序卡顿时间
  • 无法回收循环引用的对象
  • 资源消耗较大
标记清除
  • 核心思想:分标记和清除两个阶段完成
  • 遍历所有对象找标记活动对象
  • 遍历所有对象清除没有标记对象
  • 回收相应空间

优缺点:

  • 相对于引用计数,可以回收循环引用但没有用处的对象。
  • 内存空间地址不连续,空间碎片化,由于回收的对象在本来的地址也是不连续的,就会导致回收的对象地址也是不连续的,浪费空间。
  • 不会立即回收垃圾对象
标记整理
  • 标记清除的增强版
  • 标记阶段的操作和标记清除一致
  • 清除阶段会先执行整理,移动对象位置,重点就是这个整理,标记清除就是因为回收的对象的地址不连续,导致碎片空间化。而标记整理会在清除前,先整理一遍,将活动的对象移动到一起,需要回收的对象移动到一起,这样回收的时候,回收对象的地址就是连续的。

优缺点:

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

V8

  • V8是一款主流的js执行引擎。
  • V8采用即时编译
  • V8内存设限

V8垃圾回收策略

  • 采用分代回收
  • 内存分为新生代,老生代
  • 针对不同对象采用不同算法

V8中常用的GC: 分代回收,空间复制,标记清除,标记整理,标记增量。

V8如何回收新生代对象
  • V8内存空间一分为二
  • 小空间用于存储新生代对象(32M|16M)
  • 新生代指的是存活时间较短的对象
新生代对象回收实现
  • 回收过程采用复制算法+标记整理
  • 新生代内存区分为两个等大小空间
  • 使用空间为form,空闲空间为to。
  • 活动对象存储于From空间
  • 标记整理后将活动对象拷贝至To空间。
  • Form与To交换空间完成释放
回收细节说明
  • 拷贝过程中可能出现晋升
  • 晋升就是将新生代对象移动至老生代
  • 一轮GC还存活的新生代就需要晋升。
  • To空间的使用率超过25%
回收老生代
老生代对象说明
  • 老生代对象存放在右侧老生代区域。
  • 64为操作系统1.4G,32位操作系统700M
  • 老生代对象就是指存活时间较长的对象
垃圾回收实现
  • 主要采用标记清除,标记整理,增量标记算法。
  • 首先使用标记清除完成垃圾空间的回收。(空间碎片)
  • 采用标记整理进行空间优化(晋升的时候,使用标记整理,进行空间优化)
  • 采用增量标记进行效率优化。
新老生代回收细节对比
  • 新生代区域垃圾回收使用空间换时间。
  • 老生代区域回收不适合复制算法(多,大)
增量标记的优化方式
  • 标记阶段的时候,会阻塞js的执行,而增量标记就是将标记的过程,分为几个小标记分段回收,类似于react的时间切片,优化用户体验。

总结

  • V8是一款主流的js执行引擎。
  • V8采用即时编译
  • V8内存设限
  • V8采用分代回收思想实现垃圾回收
  • V8内存分为新生代和老生代
  • V8常见的GC算法,新生代:复制算法+标记整理;老生代:标记清除,标记整理,增量标记算法

Performance工具介绍

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

内存问题外在表现

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

界定内存问题的标准

  • 内存泄漏:内存使用持续升高。
  • 内存膨胀:在多数设备上都存在性能问题
  • 频繁垃圾回收(通过内存变化图进行分析)
监控内存:
  • 浏览器任务管理器
  • TimeLine时序图记录
  • 堆快照查找分离DOM
  • 判断是否存在频繁的垃圾回收。
任务管理器
<body>
    <button id="btn">+ </button>
    <script>
        const oBtn = document.getElementById('btn')
        oBtn.onclick = ()=>
            const arr = new Array(1000000)
            console.log('arr',arr);
        
    </script>
</body>

一开始

点击三次后

TimeLine记录内存
<body>
    <button id="btn">+ </button>
    <script>
        const arrList = []
        const oBtn = document.getElementById('btn')
        function test() 
            for (let i = 0; i < 10000; i++) 
                document.body.appendChild(document.createElement('p'))
                arrList.push(new Array(10000).join('x'))
            
        
        oBtn.onclick = test
    </script>
</body>

点击三次之后,内存会出现如下这种情况

飙升是因为消耗内存,然后过一阵子之后又下降,是GC开始工作了,它会将垃圾进行一个回收。

堆快照查找分离DOM
  • 界面元素存活在DOM树上
  • 垃圾对象时的DOM节点
  • 分离状态的DOM节点(ui上没有但是内存中还引用的dom)
判断是否存在频繁垃圾回收
  • GC工作时应用程序是停止的
  • 频繁且过长的GC会导致应用假死
  • 用户使用中感知应用卡顿
  • TimeLine中频繁的上升下降
  • 任务管理器中数据频繁的增加减小。

js代码优化

  • 慎用全局变量:全局变量定义在全局执行上下文,是所有作用域的顶端,而且全局上下文会一直存在上下文执行栈,直到程序退出,还会导致局部变量遮蔽污染。
  • 缓存全局变量,比如缓存dom节点,避免频繁获取
  • 通过原型新增方法
  • 避免属性访问方法使用,就是对象直接获取属性就行,不用封装方法获取属性。
  • for循环遍历的时候,如果是数组长度,可以提前使用变量保存,不用每次循环都去获取。
  • 遍历一个数组,forEach的性能比for和for in好。优化后的for(数组长度提前读取)的性能比for in好。
  • 节点添加优化,使用文档碎片(document.createDocumentFragment)添加节点,等节点添加完毕之后再统一加入到dom上。
  • 克隆节点比创建节点性能更高
  • 直接量替换Object操作,比如数组,const a = [1,2,3]替换const a = new Array(1,2,3)
  • 减少判断层级,if else中,退出的情况应该放在最前判断,复杂层级深的情况应该放在最后
  • 减少作用域链查找层级
  • 减少数据读取次数,比如dom读取,可以先缓存。
  • 减少声明及语句数
  • 采用事件委托代替多个事件注册。

堆栈中代码执行流程

堆栈执行过程

以上是关于js性能优化的主要内容,如果未能解决你的问题,请参考以下文章

前端性能优化之重排和重绘

前端性能优化之重排和重绘

Java 应用性能调优实践

web性能优化的15条实用技巧

面试时这样回答 Java 应用性能调优,回报是更多 Money!

性能优化