美文网首页
JS性能优化——JavaScript语言的优化

JS性能优化——JavaScript语言的优化

作者: yapingXu | 来源:发表于2020-11-11 22:54 被阅读0次

    拉勾大前端的学习笔记,仅作为学习记录

    内容概要

    • 内存管理
    • 垃圾回收与常见GC算法
    • V8引擎的垃圾回收
    • Performance工具
    • 代码优化实例

    内存管理

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

    Js中的内存管理

    • 申请内存空间
    // 申请
    let obj = {}
    // 使用
    obj.name="lg"
    // 释放
    obj = null
    

    Js中的垃圾回收

    什么样的内容会被当做垃圾
    因为js中内存管理是自动的,所以当对象不再被引用,对象不能从根上访问到,都是垃圾

    可达对象
    可以通过引用、作用于链访问到的对象就是可达对象
    可达的标准是从根出发是否能够被找到
    js中根就可以理解为是全局变量对象

    垃圾回收
    找到垃圾,让js的执行引擎来进行空间的释放和回收

    js中的引用和可达

    待补充

    GC回收机制

    GC的定义和作用

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

    GC里面的垃圾是什么

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

    GC算法是什么

    • GC是一种机制,垃圾回收器完成具体工作
    • 工作的内容就是查找垃圾,释放空间,回收空间
      • 如何查找空间 ?
      • 释放空间的时候应该怎样去释放 ?
      • 回收空间的时候要怎样进行去分配 ?
    • 算法就是工作时查找和回收所遵循的规则

    常见的GC算法

    • 引用计数 : 通过一个数字判断当前对象是不是一个垃圾
    • 标记清除: 在GC工作时给活动对象加标记判断对象是否是垃圾
    • 标记整理:和标记清除类似,只不过在后续回收过程会做一些事情
    • 分代回收:在V8中会用到的回收机制
    引用计数算法实现原理

    核心思想:设置引用数,判断当前引用数是否为0
    算法规则:当某个对象引用关系发生改变的时候,引用计数器会去修改这个对象所对应的引用数值,引用数字为0时立即回收
    优点

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

    缺点

    • 无法回收循环引用的对象
    function func(){
      const obj1 = {}
      const obj2 = {}
    
      obj1.name = obj2
      obj2.name = obj1  // 当垃圾回收obj1的时候会发现obj1被obj2所引用,obj1的引用计数不为0,所以GC引用计数算法下,obj1不能被回收
    }
    func()
    
    • 时间开销大
    标记清除算法的实现原理

    核心思想:分标记和清除两个阶段

    • 标记阶段: 遍历所有对象找到并标记活动对象(也就是可达对象)
    • 清除阶段:遍历所有对象清除没有被标记的对象,把第一阶段的标记抹掉方便GC继续下次工作
      最后,把回收的空间放到空闲列表上面,方便后面的程序直接申请空间使用

    优点

    • 可以解决对象循环引用的回收操作

    缺点

    • 回收空间地址不连续,空间的碎片化:由于当前我们回收的对象在地址上是不连续的,从而造成回收后他们分散在各个角落,一旦新的空间申请大于或者小于当前的空间碎片,会造成空间的浪费或空间不足
    • 不能立即回收垃圾对象
    标记整理算法实现原理

    标记整理可以看做是标记清除的增强
    标记阶段的操作和标记清除一致
    清除阶段会先执行整理,移动对象位置
    优点

    • 减少碎片化空间

    缺点

    • 不能立即回收垃圾对象

    认识V8

    • 主流的js执行引擎
    • 采用即时编译,之前很多js引擎都需要将代码转为字节码,然后才能去执行,V8是直接将代码编译为可执行的机器码,速度上快了很多
    • V8内存设有上限,64位操作系统上限不超过1.5G,32位系统不超过800M

    V8垃圾回收策略

    • 采用分代回收的思想
    • 内存分为新生代、老生代
    • 针对不同代的算法采用不同的GC算法

    V8中常用的GC算法

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

    V8如何回收新生代对象

    V8内存分配
    新生代对象(64位32M | 32位16M) / 老生代的对象
    新生代指的是存活时间较短的对象

    新生代对象的回收实现

    主要用复制算法+ 标记算法,将储存新生代对象的空间分为两个等大小的空间,使用空间叫做From ,空闲空间叫做To

    • 所有的所动对象都在From里面
    • 当From存储到一定量时,触发GC操作
    • 标记整理后,把标记的活动对象copy到To
    • 然后把To Copy到From,From到To进行空间置换,达到From的空间释放
      细节说明
    • copy过程发现变量晋升,
      晋升含义: 某个对象使用空间在老生代对象中也出现过,将新生代移到老生代储存
      晋升触发时机:
    • 一轮GC后还存活的新生代需要晋升
    • To空间的使用率超过25%
    老生代回收说明

    老生代对象说明

    • 存在右侧的老生代区,64位1.4G,32位700M
    • 存活时间较长的对象

    回收实现
    标记清除,标记整理,增量标记算法

    步骤

    1. 首先标记清除
    2. 当想把新生代放入老生代的时候,并且当老生代空间不足的时候,触发标记整理
    3. 最后采用增量标记进行效率优化
    细节对比
    • 新声带区域垃圾回收就是使用空间换时间
    • 老生代不适合复制算法(耗时,空间占用大)

    代码优化相关

    • 慎用全局变量
    • 缓存全局变量
    function getBtn(){
      let btn1 = document.getElementById('btn1')
      let btn2 = document.getElementById('btn2')
    }
    function getBtn(){
      let doc = document
      let btn1 = doc.getElementById('btn1')
      let btn2 = doc.getElementById('btn2')
    }
    
    • 通过原型对象增加附加方法
    function F1(){
      this.foo = function (){
          console.log(111)
      }
    }
    f1 = new F1()
    
    function F2(){}
    F2.property.foo =  function (){
          console.log(111)
    }
    f2 = new F2()
    
    • 避开闭包陷阱
    function foo(){
      let el = document.getElementById('btn')
      el.onclick = function(){
        console.log(el.id)
      }
      el = null // 在函数内部删除对dom的引用,从而避免内存泄漏
    }
    foo()
    
    • 避免属性访问方法使用
      js不需要属性的访问方法,所有属性都是外部可见的
      使用属性访问方法只会增加一层重定义,没有访问的控制力
    function Person(){
      this.name = "coder"
      this.age = 18
      this.getAge = function (){
        return this.age
      }
    }
    const p1 = new Person()
    const pAge = p1.getAge()
    
    function Person(){
      this.name = "coder"
      this.age = 18
    }
    const p1 = new Person()
    const pAge = p1.age
    
    • For循环优化
    const btns = document.getElementByClass('.btn')
    for(var i ; i< btns.length;i++){
      console.log(i)
    }
    for(var i ;len=btns.length; i< len;i++){
      console.log(i)
    }
    
    • 采用最优循环方式
      for / forEach / for...in
    var arrList = [1,2,3,4,5]
    // 最优
    arrList.forEach(function(item){
      console.log(item)
    })
    // 第二
    for(var i = arrList.length; i ;i--){
      console.log(arrList[i])
    }
    // 第三
    for(var i in arrList){
      console.log(arrList[i])
    }
    
    • 文档碎片优化节点添加
      节点添加操作必然会有回流和重绘
    for ( var i = 0; i<10; i++ ){
      var oP = document.createElement('p')
      oP.innerHTML = i
      document.body.appendChild(oP)
    }
    
    const fragEle = document.createElement('p')
    for ( var i = 0; i<10; i++ ){
      var oP = document.createElement('p')
      oP.innerHTML = i
      fragEle.appendChild(oP)
    }
    document.body.appendChild(fragEle)
    
    • 克隆优化节点操作
      当新增节点的时候,可以找当前已经存在的相似节点clone后添加到界面上
    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)
    }
    
    • 直接量替换Object操作
      当定义一些对象和数组的时候,可以直接通过new的方式 ,也可以通过字面量
    const a = new Array()
    a[0] = 1
    a[1] = 2
    a[2] = 3
    
    const a = [1,2,3]
    

    相关文章

      网友评论

          本文标题:JS性能优化——JavaScript语言的优化

          本文链接:https://www.haomeiwen.com/subject/rwfrbktx.html