写在前面
一直计划准备一些博客,种种原因搁浅(主要还是没时间。。。)。最近公司里的项目逐渐稳定下来了,前端的开发节奏虽然很快,但是都能实现,剩下的开发大多其实是繁复的代码组装的体力活。哈哈哈...遂给自己开了这个专栏,一是为了督促自己记录下编写过程和一些踩的坑,跟大家分享;二是写点自己想写的代码,让自己开心一些吧!
JAVASCRIPT性能优化
1、为什么要做性能优化
Javascript是一门非常灵活的语言,我们可以随心所欲的书写各种风格的代码,不同风格的代码也必然也会导致执行效率的差异,作用域链、闭包、原型继承、eval等特性,在提供各种神奇功能的同时也带来了各种效率问题,用之不慎就会导致执行效率低下。因此js的性能优化也逐渐被大家重视。
2、内存管理
2.1、内存
内存就是由可读写单元组成的可操作空间
2.2、管理
人为的操作内存空间
2.3、内存管理
申请——开辟内存空间(var i)
使用——赋值修改和删除 (i=18)
回收——当开辟的内存空间不被任何变量指向的适合即为垃圾,将被回收
注:js中的内存管理是自动的,没有提供相应的api给开发人员操作
3、垃圾回收
3.1、什么是垃圾
要定义什么是垃圾,首要需要了解两个概念:
可达对象
可以访问到的对象就是可达对象
可达的标准就是从根触发能否被找到(根:全局变量)
javascript中的根就可以理解为全局变量对象
上述图内所有的对象均为可达对象,因为从根(gv)出发均可以找到。
不可达对象
从根出发无法找到的对象
当o1关系链和prev关系链不在指向obj1对象的时候,该对象就无法从根出发被找到。
可以说此时,obj1是被独立的。因为javascript将视其为垃圾,将其回收。
4、GC
4.1、GC的定义和作用
GC就是垃圾回收机制的简称
GC可以找到内存中的垃圾、并释放和回收空间
4.2、GC算法是什么
GC算法是一种机制,垃圾回收期的实现方式
工作内容就是查找垃圾并释放空间
算法就是工作时查找和回收的规则
4.3、常见的GC算法
引用计数算法
标记清除算法
标记整理算法
标记增量算法
分代回收
4.4、引用计数算法
4.4.1、简介
核心思想:设置引用数,判断当前引用是否为0决定是否进行回收
引用计数器
引用关系发生改变(±)时就修改引用数字
引用数字为0 时就立即回收
ex:
var a=1 //计数器+1function fn(){ let c=a //计数器+1 此时c计数器=2 let d=1//计数器+1}fn()//执行结束,d由于只能在函数内部被调用,因此函数结束调用也就不复存在了,计数器为0。而a在全局仍有技术1,因此a不会被回收。
4.4.2、优点
发现垃圾时就立即回收
最大限度减少程序暂停(栈即将堆满前,立即进行一波垃圾回收)
4.4.3、缺点
无法对循环引用对象进行垃圾回收
时间开销大
ex:
function fn(){ const obj1={}//+1 const obj2={}//+1 obj1.name=obj2//+1 obj2.name=obj1//+1}fn()//当函数执行结束后,obj1和obj2并不会被回收,因为两者被相互引用了。引用计数算法无法对将其计数归0也就无法作为垃圾进行回收。
4.5、标记清除算法
4.5.1、简介
核心思想:分标记和清除两个阶段完成
遍历所有活动对象并打上标记
遍历所有对象清除没有标记对象
回收相应空间
ex:
commit:
假设当前存在abc三个变量,de为ac的children。a1、b1为函数局部作用域变量。
此时标记清除算法,首先会遍历所有活动对象,ac因为有子属性所有算法会对其进行递归标记。
而a1、b1则不会被标记
第二阶段清除a1、b1
4.5.2、优点
标记清除算法其实就是引用算法的升级版本,因此相对引用算法优点就是可清除循环引用对象
4.5.3、缺点
如图,进行清除的时候地址不联系,也就是空间碎片化
不会立即回收垃圾对象
4.6、标记整理算法
4.6.1、简介
标记整理可以看做是标记清除的增强版
标记阶段的操作和标记清除的一致
清除阶段会先做整理,移动再进行清除
4.6.2、优点
解决了清除算法的空间碎片化问题
4.6.3、缺点
不会立即回收垃圾对象
移动对象位置,回收效率慢
5、V8引擎
5.1、认识V8
V8是一款主流的javascript引擎
V8采用及时编译
V8内存设限
64位 1.5g
32位 0.8g
5.2、V8的垃圾回收策略
采用分代回收的思想
新生代
小空间(32M|16M)
存活周期短
老生代
大空间(1.4g|0.7g)
存活周期长
针对不同的对象采用不同算法
5.3、V8引擎中采用GC算法
分代回收
空间复制
标记清除
标记整理
标记增量
5.4、新生代对象
5.4.1、新生代对象回收实现
回收过程采用复制算法+标记整理
新生代内存区分为两个等大的内存空间
From——活动空间
To——空闲空间
标记整理后将活动对象从From拷贝至To
然后采用复制算法,From和To交换空间完成释放
5.4.2、回收细节
拷贝过程中可能出现晋升
晋升就是将新生代对象移动到老生代对象
一轮GC后还存活的就需要晋升
To空间使用率超过25%的
5.5、老生代对象
5.5.1、老生代对象回收实现
采用标记清除、标记整理、标记增量算法
首先使用标记清除完成垃圾回收
采用标记整理完成空间优化
采用标记增量进行效率优化
5.5.2、与新生代对象对比
新生代区域回收使用空间换时间
这和其本身空间小有关系,牺牲的一点点空间和所换来的效率提升相比,微不足道
老生代区域垃圾回收不适合复制算法
因为其内存空间大,如果闲置着就造成了极大的空间浪费。
5.6、增量算法如何进行优化
增量标记算法将程序执行和垃圾回收进行了分段耦合,程序分段执行,垃圾回收也同步进行,程序执行完了,垃圾回收也就结束了。
不进行增量算法
优先执行程序,程序执行完之后再进行垃圾回收
进行垃圾回收的时候必然会造成程序停滞
6、Performance
6.1、为什么使用performance
Gc的目的是为了实现内存空间的良好循环
良好循环的基石是合理的使用
时刻关注才能确定是否合理
而performance提供多种监控方式
6.2、使用
打开浏览器输入地址
打开调试工具,选择performance
开始录制
模仿用户操作
暂停录制
在分析界面查看性能报告
6.3、界定内存问题的标准
内存泄漏:内存使用持续升高
内存膨胀:在多数设备上都存在性能问题
频繁垃圾回收:通过内存变化图进行分析
6.4监控内存的几种方式
浏览器任务管理器
shift+esc
timeline时序图
堆快照查找分离DOM
开发工具的memory,点击take snapshot获取快照即可
分离DOM
垃圾对象时的DOM节点
脱离DOM树,且没有js使用,即为垃圾DOM
分离状态的DOM节点
脱离DOM树,但有js使用,即为分离DOM
判断是否存在频繁的垃圾回收
GC工作时应用程序是停止的
频繁的GC会导致应用假死
用户使用过程中感知到卡顿
7、JAVASCRIPT代码优化
7.1、前提
js的内存管理自动完成
执行引擎会使用不同的GC算法
算法的目的是为了实现内存空间的良性循环
performance工具监测内存的变化
js是单线程机制的解释性语言
(1)源代码不能直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行; 源代码—>中间代码—>机器语言
(2)程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次; (3)解释性语言代表:Python、JavaScript、Shell、Ruby、MATLAB等; (4)运行效率一般相对比较低,依赖解释器,跨平台性好;
————————————————版权声明:本文为CSDN博主「你的代码有灵魂吗?」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/yuanziwoxin/article/details/82872792
JAVASCRIPT没有给开发人员相应的回收创建内存空间的API
因此我们只能在代码层面上进行优化
7.2、避免使用全局变量
7.2.1、全局变量特点
挂载在window或者global下
全局变量至少有一个引用计数器
全局变量存活更久,但持续占用内存
ex:
var i, str = ''for (i = 0; i < 1000; i++) { str += i}for (let i = 0; i < 1000; i++) { let str = '' str += i}
7.3、避免全局查找
7.3.1全局查找相关
目标变量不存在当前作用域,通过作用域链向上查找
减少全局查找带来的性能浪费
减少不必要的全局定义
全局变量数据局部化
7.4、避免循环引用
7.5、采用字面量代替new操作
7.6、setTimeout替换setInterval
7.7、采用事件委托
7.8、合并循环变量和条件
var arrList = []arrList[10000] = 'icoder'for (var i = 0; i < arrList.length; i++) { console.log(arrList[i])}for (var i = arrList.length; i; i--) { console.log(arrList[i])}
8、性能检测工具
8.1、Jsperf的使用流程
github登录
填写个人信息——非必填
填写详细的测试用例信息(title、slug)
填写Code snippets to compare(代码不够可以add)
8.2、慎用全局变量
全局变量定义在顶端,是所有作用链的顶部
全局执行时一直存在,直到程序退出。
如果局部作用域出现了同名变量,会覆盖或污染
8.3、缓存全局变量
function getBtn() { let oBtn1 = document.getElementById('btn1') let oBtn3 = document.getElementById('btn3') let oBtn5 = document.getElementById('btn5') let oBtn7 = document.getElementById('btn7') let oBtn9 = document.getElementById('btn9') } function getBtn2() { let obj = document let oBtn1 = obj.getElementById('btn1') let oBtn3 = obj.getElementById('btn3') let oBtn5 = obj.getElementById('btn5') let oBtn7 = obj.getElementById('btn7') let oBtn9 = obj.getElementById('btn9') }
8.4、通过原型添加方法
var fn1 = function() { this.foo = function() { console.log(11111) }}let f1 = new fn1()var fn2 = function() {}fn2.prototype.foo = function() { console.log(11111)}let f2 = new fn2()
8.5、避开闭包陷阱
function test(func) { console.log(func())}function test2() { var name = 'lg' return name}test(function() { var name = 'lg' return name})test(test2)
8.6、避免属性访问方法的使用
function Person() { this.name = 'icoder' this.age = 18 this.getAge = function() { return this.age }}const p1 = new Person()const a = p1.getAge()function Person() { this.name = 'icoder' this.age = 18}const p2 = new Person()const b = p2.age
js中的对象
对象中的所有属性都是外部可直接访问的
通过属性访问会增加一次重定义,没有访问的控制力
8.7、for循环优化
var arrList = []arrList[10000] = 'icoder'for (var i = 0; i < arrList.length; i++) { console.log(arrList[i])}for (var i = arrList.length; i; i--) { console.log(arrList[i])}
8.8、节点优化添加
document.createDocumentFragment()
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)
8.9、克隆节点操作
for (var i = 0; i < 3; i++) { var oP = document.createElement('p') oP.innerHTML = i document.body.appendChild(oP) } var oldP = document.getElementById('box1') for (var i = 0; i < 3; i++) { var newP = oldP.cloneNode(false) newP.innerHTML = i document.body.appendChild(newP) }
8.10、直接量替换object操作
var a = [1, 2, 3]var a1 = new Array(3)a1[0] = 1a1[1] = 2a1[2] = 3
结语
文章中可能会有很多错误,如果出现了错误请大家多多包涵指正(/*手动狗头保命*/),我也会及时修改,希望能和大家一起成长。
下一章,前端工程化