Front-end memory leaks and overflows and their solutions

Write in front:

When writing code in daily life, memory leaks will happen from time to time. Although js has a memory recycling mechanism, you still need to pay attention to avoid memory leaks in daily programming. A few days ago, when I was working on a mobile terminal, I encountered a memory leak that caused the mobile phone to move. The problem of page lag on the end, so I want to summarize the situation of front-end memory leaks and review the basic knowledge.

1. What is a memory leak?

 The operating system will allocate corresponding memory when the program is running. If the memory occupancy is not cleaned regularly, the memory occupancy will become higher and higher, which can easily cause page freezes and process crashes; if the program does not clear the memory space after the system allocates it, Reusing but not releasing it in time will cause a memory leak ; the memory space that the program applies to the system exceeds what the system can provide, causing a memory overflow . Memory leaks and overflows can affect program performance.

JS does not need to manually apply for memory for variables. When we declare a variable, JS will automatically allocate memory for it; when an object is not referenced, it will be recycled. The simplest garbage collection mechanism is reference counting. When an object It will be recycled when the number of references reaches 0

2. Common situations causing memory leaks

1. Global variables: If a variable is mounted on the window, it will always be reachable and will only be recycled when the page is closed or the browser is closed. This situation can be avoided in strict mode; solution: testVal = null

Local variables: After the function is executed, the local variable is recycled, and the memory it refers to can be released at this time.

Another kind of global variable may be created by this

function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,此时this 指向了全局对象(window)
foo();

2. Timer

When using the timer, we destroy the DOM, but when the DOM is used in the timer, the timer retains a reference to the DOM, so the timer needs to be cleared manually when clearing the DOM (timer = null)

var someResource = getData();
const timer = setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

Solution: document.body.removeChild(node); someResource = null

timer = null, so that the node and someResource in the timer will actually be recycled

3. Leakage caused by closure function

Closures can maintain local variables inside a function so that their variables cannot be released.

function bindEvent() {
  var obj = document.createElement('XXX');
  var unused = function () {
    console.log(obj, '闭包内引用obj obj不会被释放');
  };
  obj = null; // 解决方法
}

4.Event monitoring of DOM elements

btn.addEventListener('click', onClick)
btn.removeEventListener('click', onClick)

5. Not cleaning up references to DOM elements

const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用,能console出整个div 没有被回收
refA = null;  // 这里会报错  Assignment to constant variable(赋值给常数变量).因为这个refA是const声明的,值不可以改变,将const改为var
console.log(refA, 'refA'); // 解除引用

3. Several situations of memory usage in Vue

1. Global variables are not cleared when switching pages

mounted() {
      window.test = {
        // 此处在全局window对象中引用了本页面的dom对象
        name: 'home',
        node: document.getElementById('home'),
      }
    },

Solution: Clear the reference when the page is unloaded

destroyed(){

  window.test = null

}

2. The events monitored on the window or body are not unbound

  • If events in the DOM/BOM object are bound in the mounted/created hook, corresponding unbinding processing needs to be done in beforeDestroy
  • If third-party library initialization is used in the mounted/created hook, corresponding destruction processing needs to be done in beforeDestroy
  • If a timer is used in the component, corresponding destruction processing needs to be done in beforeDestroy
  • Some components may leak when using event binding in the template. Use $on to replace the binding in the template.

If you want the render function, avoid DOM/BOM events in the html tag

mounted () {
  window.addEventListener('resize', this.func)
},
methods:{
    func(){}
}

// 解决:
beforeDestroy () {
  window.removeEventListener('resize', this.func)
}

3. Open the pop-up dialog when entering the page, and do not set it to false when leaving the page.

mounted () {
  this.Dialog = true
},

// 在手动点击头部的返回按钮和回到首页的按钮时,会造成页面卡顿,无法滑动的问题

// 在离开页面时将它设为false
  beforeRouteLeave(to, from, next){
    this.Dialog = false
    next()
  },

 4. Events bound to EventBus are not unbound

Solution: Dereference when page is unloaded

mounted () {
 this.$EventBus.$on('homeTask', res => this.func(res))
},
destroyed () {
 this.$EventBus.$off()
}

5.Memory leak caused by v-if instruction

When using v-if to control display and hiding, when the condition of v-if is false, the browser will not render this element; but when we add other sub-elements to this element when v-if is true, , then when we set it to false, only the parent element itself is removed from the virtual DOM. The newly added child element is not removed. The child element references the parent element, and the parent element will not be removed.

example:

<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>

original writing

<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>

Post-processing writing: When we remove the parent element in the hide method, we clean up the child elements.

<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>

 6.Memory leak caused by echart chart

Each legend will create a timer to render bubbles when there is no data. After the page is switched, the echarts legend is destroyed, but the echarts instance is still in the memory, and its bubble rendering timer is still running. This causes Echarts to take up a lot of CPU, causing the browser to freeze, and even the browser to crash when the amount of data is relatively large.

Solution: Add a beforeDestroy() method to release the chart resource of the page. I also tried using the dispose() method, but dispose destroys the legend. The legend no longer exists, but the resize() method of the legend will be started. There is no resize method, but the clear() method clears the legend data, does not affect the resize of the legend, and can release memory, making switching smooth.

beforeDestroy () {
  this.chart.clear()
}

Preventing memory leaks in ES6

In daily programming, we may not clear references in time. There are two new data structures in ES6: weakset and weakmap. They are not counted in the garbage collection mechanism when they are worthy of reference. If other objects no longer reference the object, then The garbage collection mechanism will automatically reclaim the memory occupied by the object.

const wm = new WeakMap()
const element = document.getElementById('example')
vm.set(element, 'something')
vm.get(element)

In the above code, first create a new Weakmap instance. Then, a DOM node is stored in the instance as a key name, and some additional information is stored in the WeakMap as a key value. At this time, the reference to element in WeakMap is a weak reference and will not be included in the garbage collection mechanism.

Registering listener objects for listening events is very suitable to be implemented using WeakMap.

<div id="example">点击</div>

// 代码1
element.addEventListener('click', handler, false)
 
// 代码2
const listener = new WeakMap()
listener.set(element, handler)
element.addEventListener('click', listener.get(element), false)

function handler(){
    console.log('测试')
}

The advantage is: since the listening function is placed in the WeakMap, once the dom object ele disappears, the listening function handler bound to it will also disappear automatically.

Some of the above summaries are points that need attention in daily work, and some are based on other people's blogs:

Common memory leaks and solutions in the front end_Arrogant koala's blog-CSDN blog

 https://mp.weixin.qq.com/s/TEs6JKQsRo2ZbVhVfAuOBA

Guess you like

Origin blog.csdn.net/qq_38679823/article/details/132806521