前端小白红宝书之旅:垃圾回收(二)

389 阅读4分钟

前言

上一篇我们简单聊了下js中的原始值与引用值,以及传递参数 。

本篇文章我们看一看js的垃圾回收机制,以及在实际开发中,可能出现的内存泄漏的情况,以及克制内存泄漏手段

js垃圾回收图片.png


JS垃圾回收主要策略


1.标记清理

标记清理是js最常用的垃圾回收策略

当垃圾回收程序运行的时候,会标记内存中存储的所有变量(标记方法不唯一),之后他会将所有在上下文的变量,以及被在上下文中的变量引用的标记都去掉

关于上下文补充说明:

上下文分为全局上下文和函数上下文(eval()中存在第三种上下文), 执行上下文中了包含变量环境、词法环境、this指向、作用域链等

运行代码时,会开辟多块执行上下文,生成词法环境,变量被暂时压入词法环境的栈结构,用于执行。

当然,在生成变量环境时,var声明的变量都会被提升到当前作用域的最前端

在代码执行时,程序沿着作用域链条,从内到外,从上到下的搜索变量与函数

想详细了解上下文的同学,可以看看这篇大神文章:blog.csdn.net/Lin__hr/art…

在此之后再被加上标记的变量就是应该带删除的变量,因为任何上下文中的变量都无法访问它们。

随后垃圾回收程序做一次内存清理(不同浏览器清理频率有所不同)

2.引用计数

引用计数是js中不算常用的垃圾回收策略,主要设计思路为:对每个值都记录它被引用的次数,声明变量并且给它赋一个引用值时,这个值的引用数变为1,如果该值又被赋给另外一个变量 那么这个值引用次数加一

同理,每当指向该引用的变量失效时,该值引用数就减一

当一个值引用数变为0时,垃圾回收程序下次运行就会释放引用数为0的变量内存

  • 引用计数策略缺点:

要命的是,引用计数有一个致命的缺陷,无法回收循环引用的对象

我们直接上代码加深印象:

function problem() {
         let ObjectA = new Object()
         let ObjectB = new Object()

         ObjectA.someOtherObject = ObjectB
         ObjectB.anotherObject = ObjectA

     }

在这段代码中,由于两个对象始终相互引用,引用计数永不为零,也就永不被回收

关于引用计数的优缺点,可以看看这篇大神的文章:juejin.cn/post/702836…

3.JS内存泄漏场景有哪些?(以VUE为例),以及如何解决内存泄漏

3.1被全局变量、函数引用,组件销毁时未清除

export default {
  name: 'Memory Leak Demo',
  data() {
    return {
      arr: [10, 20, 30], // 数组 对象
    }
  },
  methods: {
   mounted()
  },
  mounted() {
      // 变量挂载在全局对象上
      window.arr = this.arr
      // 变量被全局函数使用
      window.printArr = () => {
           console.log(this.arr)
      }
  // 清除方法,释放内存
  beforeUnmount() {
      window.arr = null
      window.printArr = null
     },
  }
}

清除手段: 我们可以在vue实例销毁之前,于钩子函数beforeUnmount()手动释放全局变量和全局函数

3.2,定时器引用,组件销毁时未清除

export default {
  name: 'Memory Leak Demo',
  data() {
    return {
      arr: [10, 20, 30], // 数组 对象
      intervalId:0
    }
  },
  methods: {
    printArr() {
        console.log(this.arr)
    }
  },
  mounted() {
      // 定时器引用变量
      this.intervalId = setInterval(() => {
           console.log(this.arr)
      }, 100)
  },
  // Vue2 - beforeDestroy
  beforeUnmount() {
      if(this.intervalId) {
          clearInterval(this.intervalId)
      }
  }
}

清除手段:

我们可以在vue实例销毁之前,于钩子函数beforeUnmount()中移除掉这个定时器

3.3被全局事件引用,组件销毁时未清除

export default {
  name: 'Memory Leak Demo',
  data() {
    return {
      arr: [10, 20, 30], // 数组 对象
    }
  },
  methods: {
    printArr() {
        console.log(this.arr)
    }
  },
  mounted() {
      window.addEventListener('resize', this.printArr)
      // 全局事件也是这样
  },
  // Vue2 - beforeDestroy
  beforeUnmount() {
      window.removeEventListener('resize', this.printArr)
  },
}

清除手段:

我们可以在vue实例销毁之前,于钩子函数beforeUnmount()中移除掉这个全局事件

4.小结

  • 标记清除是主流的js垃圾回收策略,原理为:给当前不使用的变量加上标记,再回来回收它们的内存

  • 变量的执行上下文用于确定什么时候释放内存

  • 引用计数是非主流的js垃圾回收策略,需要记录值被引用次数,JavaScript引擎不再使用这种策略,但是有时候JavaScript需要访问非原生js对象,这些对象有可能使用的是该策略,因此要小心代码中的循环引用情况,可以通过设置null值,来解除引用