JS内存泄漏如何检测?场景有哪些?

140 阅读2分钟

垃圾回收GC

  • 什么是垃圾回收?
function fn1() {
    const a = 'aa'
    console.log(a)

    const obj = { x: 100 }
    console.log(obj)
  }
  fn1() // 一边执行一边垃圾回收

  function fn2() {
    const obj = { x: 100 }
    window.obj = obj // 符合用户预期,不称为内存泄漏
  }
  fn2() // 执行完后依旧有,回收不了

  function getDataFns() {
    const data = {} // 闭包 常驻内存不会被销毁
    return {
      get(key) {
        return data[key]
      },
      set(key, value) {
        data[key] = value
      },
    }
  }
  const { get, set } = getDataFns()
  set('x', 100)
  get('x')

引用计数(之前)

  // 对象被a引用
  let a = {
    x: 100,
  }
  let a1 = a
  a = 10
  a1 = null // 此时上面对象的引用数为0 被销毁

  // 缺陷 循环引用
  function fn3() {
    const obj1 = {}
    const obj2 = {}
    obj1.a = obj2
    obj2.a = obj1
  }
  fn3()

  // IE6-7 内存泄漏的bug
  var div1 = document.getElementById('div1')
  div1.a = div1
  div.someBigData = {} // 非预期的内存泄漏

标记清除(现代)

  • 从js的根(window)遍历各个属性,看看能不能得到某个对象,如果得到,保留,得不到,则删除

闭包是内存泄漏吗?

  • 闭包严格来说不算内存泄漏
  • 内存泄漏是一种非预期的情况
  • 闭包符合预期

检测内存变化

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=
  , initial-scale=1.0"
    />
    <title>Document</title>
  </head>
  <body>
    <p>
      memory change
      <button id="btn1">start</button>
    </p>
    <script>
      const arr = []
      for (let i = 0; i < 10 * 10000; i++) {
        arr.push(i)
      }

      function bind() {
        // 模拟比较大的数据
        const obj = {
          str: JSON.stringify(arr), // 简单的拷贝
        }

        window.addEventListener('resize', () => {
          console.log(obj)
        })
        // 不会销毁 因为是全局引用
      }
      let n = 0
      function start() {
        setTimeout(() => {
          bind()
          n++
          if (n < 50) {
            start()
          } else {
            alert('done')
          }
        }, 200)
      }
      document.getElementById('btn1').addEventListener('click', () => {
        start()
      })
    </script>
  </body>
</html>

操作过程:

image.png

然后点击start:

image.png

出现alert弹框后,点击stop,观测数据

image.png 可以看到内存从4mb一直飙升到了33mb,验证无法进行垃圾回收带来的结果 无法销毁bind绑定的数据,内存随着时间持续增大

内存泄漏的场景(以vue为例)

  • 被全局变量、函数引用,组件销毁时未清除
  • 被全局事件、定时器引用,组件销毁时未清除
  • 被自定义事件引用,组件销毁时未清除

全局变量清除内存泄漏风险

image.png

定时器清除内存泄漏风险

image.png

全局事件清除内存泄漏风险

image.png

自定义事件清除内存泄漏风险

image.png

划重点

  • 前几年前端不太注重内存泄漏,因为不像后端7*24小时运行
  • 近几年前端功能不断复杂,内存问题也要重点考虑

扩展:WeakMap WeakSet

  // 标记清除
  const data = {}
  function fn1() {
    const obj = { x: 100 }
    data.obj = obj // 被全局变量引用不能垃圾回收
  }
  fn1()
  const map = new Map()
  function fn2() {
    const obj = { x: 100 }
    map.set('a', obj) // 全局引用
  }
  fn2()

  const wMap = new Map() // 弱引用
  function fn3() {
    const obj = { x: 100 }
    wMap.set(obj,100) // 弱引用 weakMap的key只能是引用类型 留不住x=100这个数据,会被清理掉
  }
  fn3()