关于js垃圾回收机制与内存泄漏

1. 垃圾回收机制

浏览器的js具有自动垃圾回收机制(GC),执行环境会管理代码在执行中所使用的内存,垃圾回收器会定期寻找不再使用的变量,释放其内存,垃圾回收器会按照时间间隔周期性的执行

2. 变量的死亡

全局作用域内的变量,会在关闭浏览器关闭页面时结束,被垃圾回收期回收

函数级与块级作用域内的变量,只有在函数执行的过程中存在,函数执行完毕,垃圾回收器回收释放闭包内,因为内部函数使用外部函数变量的原因,它内部的变量,永远不会结束。判别变量是否还有用的两种方式

1) 标记清除(常用)

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记。当一个变量进入环境时,就将这个变量标记为“进入环境”,而当变量离开环境时,则将其标记为“离开环境”。垃圾收集器下次运行时,就会把所有标为离开环境的变量回收释放 

function test(){

    var a = 10 ;        

    var b = 20 ;      

}

// 在调用该函数时,内部的变量会标记为进入环境

test();            

// 函数执行完毕之后 a、b又被标离开环境,被回收。

2) 引用计数

跟踪记录每个值别引用的次数,当声明了一个变量,并将一个引用类型的值赋给该变量时,那这个值的引用次数就是1,而这个值再被赋值给另一个变量,那这个值的引用次数+1,相反,如果包含这个值的变量,获取了另外的值,那这个值的引用次数-1,当这个值的引用次数为0时,垃圾回收器下次运行时,就会把这个值回收释放

function test() {

    var a = {};    // a指向对象的引用次数为1

    var b = a;     // a指向对象的引用次数加1,为2

    var c = a;     // a指向对象的引用次数再加1,为3

    var b = {};    // a指向对象的引用次数减1,为2

}

3. 内存泄漏

JS 程序的内存溢出后,会使某一段函数体永远失效(取决于当时的 JS 代码运行到哪一个函数),通常表现为程序突然卡死或程序出现异常。这时我们就要对该 JS 程序进行内存泄漏的排查,找出哪些对象所占用的内存没有释放。这些对象通常都是开发者以为释放掉了,但事实上仍被某个闭包引用着,或者放在某个数组里面。

4. 循环引用

一个DOM对象被一个Javascript对象引用,又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄露。将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,赋值为null即可

5. 全局变量引起

在window下声明的变量不会被回收

路由切换之后,此component不会被释放(但是router会重新生成一个此实例,原先的仍旧没有被回收),window.myVue指向此对象

6. 闭包

在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收

7. 在 vue 组件中处理 addEventListener

调用 addEventListener 添加事件监听后在beforeDestroy 中调用 removeEventListener 移除对应的事件监听。为了准确移除监听,尽量不要使用匿名函数或者已有的函数的绑定来直接作为事件监听函数。

mounted() {

    const box = document.getElementById('time-line')

    this.width = box.offsetWidth

    this.resizefun = () => {

    this.width = box.offsetWidth

}

    window.addEventListener('resize', this.resizefun)
    
},

beforeDestroy() {

    window.removeEventListener('resize', this.resizefun)

    this.resizefun = null

}

8. echarts销毁实例 

1) 在mounted(生命周期函数)如果定义了一些全局变量,或者是定时器、监听事件,要在beforeDestroy(beforeMounted)中释放或者移除掉

2) 如果echarts用定时器获取数据的,也是注意在合适的时间移除掉定时器

切换页面(页面销毁)时注意对echarts实例做clear()或者dispose() 

9. 绑在 EventBus 的事件没有解绑

解决方案:在页面卸载的时候解除引用

mounted () {

  this.$EventBus.$on( homeTask , res => this.func(res))

},

destroyed () {

this.$EventBus.$off()

}

10. v-if 指令产生的内存泄露

v-if 绑定到 false 的值,但是实际上 dom 元素在隐藏的时候没有被真实的释放掉。 比如下面的示例中,我们加载了一个带有非常多选项的选择框,然后我们用到了一个显示/隐藏按钮,通过一个 v-if 指令从虚拟 DOM 中添加或移除它。这个示例的问题在于这个 v-if 指令会从 DOM 中移除父级元素,但是我们并没有清除由 Choices.js 新添加的 DOM 片段,从而导致了内存泄漏。

<div id="app">

   <button v-if="showChoices" @click="hide">Hide</button>

   <button v-if="!showChoices" @click="show">Show</button>

   <div v-if="showChoices">

     <select id="choices-single-default"></select>

   </div>

</div>

<script>

  export default {

    data() {

      return {

        showChoices: true,

      }

    },

    mounted: function () {

      this.initializeChoices()

    },

    methods: {

      initializeChoices: function () {

        let list = []

        // 我们来为选择框载入很多选项,这样的话它会占用大量的内存

        for (let i = 0; i < 1000; i++) {

          list.push({

            label:  Item   + i,

            value: i,

          })

        }

        new Choices( #choices-single-default , {

          searchEnabled: true,

          removeItemButton: true,

          choices: list,

        })

      },

      show: function () {

        this.showChoices = true

        this.$nextTick(() => {

          this.initializeChoices()

        })

      },

      hide: function () {

        this.showChoices = false

      },

    },

  }

</script>

在上述的示例中,我们可以用 hide() 方法在将选择框从 DOM 中移除之前做一些清理工作,来解决内存泄露问题。为了做到这一点,我们会在 Vue 实例的数据对象中保留一个属性,并会使用 Choices API 中的 destroy() 方法将其清除。

<div id="app">

  <button v-if="showChoices" @click="hide">Hide</button>

  <button v-if="!showChoices" @click="show">Show</button>

  <div v-if="showChoices">

    <select id="choices-single-default"></select>

  </div>

</div>

<script>

  export default {

    data() {

      return {

        showChoices: true,

        choicesSelect: null

      }

    },

    mounted: function () {

      this.initializeChoices()

    },

    methods: {

      initializeChoices: function () {

        let list = []

        for (let i = 0; i < 1000; i++) {

          list.push({

            label:  Item   + i,

            value: i,

          })

        }

         // 在我们的 Vue 实例的数据对象中设置一个 `choicesSelect` 的引用

        this.choicesSelect = new Choices("#choices-single-default", {

          searchEnabled: true,

          removeItemButton: true,

          choices: list,

        })

      },

      show: function () {

        this.showChoices = true

        this.$nextTick(() => {

          this.initializeChoices()

        })

      },

      hide: function () {

        // 现在我们可以让 Choices 使用这个引用,从 DOM 中移除这些元素之前进行清理工作

        this.choicesSelect.destroy()

        this.showChoices = false

      },

    },

  }

</script>

11. 接口数据量巨大,前端渲染时占用大量内存

常见的表格数据渲染,则采用分批分页展示数据。下拉选择器、级联选择器、树形控件、表单数据渲染等数据量较多则采用数据懒加载的方式解决。

12. 项目上线打包移除console.log

在传递给console.log的对象是不能被垃圾回收️,因为在代码运行之后需要在开发工具能查看对象信息。在打包后不要在生产环境中console.log任何对象。除了console.log外,另外还有console.dir、console.error、console.warn等都存在类似的问题。其解决方案如下:

在build文件下,打开webpack.pre.conf.js,导入该插件(webpack自带),并启用除去debugger和打印功能

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')// 导入

plugin:[

        new UglifyJsPlugin({

                compress:{
                        //使用
    
                        warnings:false,

                        drop_debugger:true,

                        drop_console:true

                }

        })

]

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值