This article is featured on Github github.com/Michael-lzg…
Recently, we received feedback from testers that the page we developed occasionally freezes, and there is a high probability of no response when clicking on the page, especially when the page is opened for a long time. When you open the task manager, you can see that the memory usage is already high, and you may have a memory leak. Check the cause of the memory leak.
If memory that is no longer needed by a system process is not released in time, it is called a memory leak. As memory usage increases, it can affect system performance at best or cause process crashes at worst. Chrome limits the amount of memory the browser can use (1.4GB for 64-bit, 1.0GB for 32-bit)
Cause of memory leak
1. Unexpected global variables
Because JS handles an undeclared variable by creating a reference to that variable on a global object. In the browser, the global object is the Window object. Variables are not released until the window closes or the page is refreshed, and if an undeclared variable caches a large amount of data, it can cause a memory leak.
- Undeclared variable
function fn() {
a = 'global variable'
}
fn()
Copy the code
- A variable created using this (this refers to window).
function fn() {
this.a = 'global variable'
}
fn()
Copy the code
Solutions:
- Avoid creating global variables
- Use strict mode, at the top of the JavaScript file header or function
use strict
.
Memory leaks caused by closures
Reason: Closures can read variables inside functions and keep them in memory at all times. If local variables are not cleared at the end of use, memory leaks may result.
function fn () {
var a = "I'm a";
return function () {
console.log(a);
};
}
Copy the code
Resolution: Define the event handler outside, unwrap the closure, or inside the function that defines the event handler.
For example: function expression in the loop, can reuse best outside the loop.
// bad
for (var k = 0; k < 10; k++) {
var t = function (a) {
// Create a function object 10 times.
console.log(a)
}
t(k)
}
// good
function t(a) {
console.log(a)
}
for (var k = 0; k < 10; k++) {
t(k)
}
t = null
Copy the code
3. No clean REFERENCES to DOM elements
Reason: A reference to the DOM still exists in the object, although deleted elsewhere.
// Reference the DOM in the object
var elements = {
btn: document.getElementById('btn'),}function doSomeThing() {
elements.btn.click()
}
function removeBtn() {
// Remove the BTN from the body, i.e. remove the BTN from the DOM tree
document.body.removeChild(document.getElementById('button'))
// At this point, however, the global variable elements retains a reference to BTN, which is still in memory and cannot be collected by GC
}
Copy the code
Solution: Manually delete elements. BTN = null.
Forgotten timers or callbacks
There is a dom reference in the timer, and even if the DOM is deleted, the timer is still there, so the DOM is still in memory.
/ / timer
var serverData = loadData()
setInterval(function () {
var renderer = document.getElementById('renderer')
if (renderer) {
renderer.innerHTML = JSON.stringify(serverData)
}
}, 5000)
// Observer mode
var btn = document.getElementById('btn')
function onClick(element) {
element.innerHTMl = "I'm innerHTML"
}
btn.addEventListener('click', onClick)
Copy the code
Solutions:
- Manually delete the timer and DOM.
- RemoveEventListener Removes event listening
There are several situations in which memory leaks can occur in VUE
When developing applications for Vue SPA, it is especially important to be aware of memory leaks. Because SPA is designed so that users do not need to refresh the browser to use it, the JavaScript application needs to clean up the components itself to ensure that garbage collection works as expected. Therefore, you should always be on the lookout for memory leaks during development.
1. Memory leaks caused by global variables
Declared global variables are not cleared when switching pages
<template>
<div id="home">Here is the home page</div>
</template>
<script>
export default {
mounted() {
window.test = {
// The page dom object is referenced here in the global window object
name: 'home'.node: document.getElementById('home'),}}}</script>
Copy the code
Solution: Dispose of this reference when the page unloads.
destroyed () {
window.test = null // Dereference the page when it is unmounted
}
Copy the code
2. Listen on events like window/body that are not unbound
Pay special attention to time listeners such as window.addeventListener
<template>
<div id="home">Here is the home page</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('resize'.this.func) The // window object references the methods of the home page}}</script>
Copy the code
Solution: At the time of page destruction, incidentally dereference, free memory
mounted () {
window.addEventListener('resize'.this.func)
},
beforeDestroy () {
window.removeEventListener('resize'.this.func)
}
Copy the code
3. Events tied to EventBus are not untied
For example
<template>
<div id="home">Here is the home page</div>
</template>
<script>
export default {
mounted () {
this.$EventBus.$on('homeTask'.res= > this.func(res))
}
}
</script>
Copy the code
Workaround: Consider dereferencing when the page is unloaded
mounted () {
this.$EventBus.$on('homeTask'.res= > this.func(res))
},
destroyed () {
this.$EventBus.$off()
}
Copy the code
4, Echarts
Each legend creates a timer to render bubbles when there is no data. After a page switch, the Echarts legend is destroyed, but the echarts instance is still in memory and its bubble render timer is still running. This can lead to high CPU usage for Echarts, which can cause the browser to stall and even crash when the data is heavy.
Solutions: Add a beforeDestroy() method to release chart resources on the page. I also tried dispose() method, but dispose destroyed the legend, the legend did not exist, but the legend’s resize() method would start, resize method would not be reported. The clear() method clears the legend data, does not affect the legend’s resize, and frees up memory, so the switch is smooth.
beforeDestroy () {
this.chart.clear()
}
Copy the code
5. Memory leaks caused by V-if instructions
The V-if is bound to false, but the DOM element is not actually released when it is hidden.
For example, in the following example, we load a selection box with a lot of options, and then we use a show/hide button to add or remove it from the virtual DOM with a V-if command. The problem with this example is that the V-if directive removes the parent element from the DOM, but we don’t clear the newly added DOM fragment from Choices. Js, causing a memory leak.
<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 = []
// Let's load the selection box with a lot of options, so it takes up a lot of memory
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>
Copy the code
In the example above, we can use the hide() method to clean up the selection box before removing it from the DOM to solve the memory leak problem. To do this, we keep a property in the Vue instance’s data object and use the Destroy () method in the Choices API to clear it.
<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,
})
}
// Set a reference to 'choicesSelect' in the data object of our Vue instance
this.choicesSelect = new Choices("#choices-single-default", {
searchEnabled: true.removeItemButton: true.choices: list,
})
},
show: function () {
this.showChoices = true
this.$nextTick(() = > {
this.initializeChoices()
})
},
hide: function () {
Now we can tell Choices to use this reference to clean up the elements before removing them from the DOM
this.choicesSelect.destroy()
this.showChoices = false}},}</script>
Copy the code
ES6 prevents memory leaks
As mentioned earlier, it is important to remove references in a timely manner. However, you can’t remember that much, and sometimes you forget, which is why there are so many memory leaks.
With this in mind, ES6 introduced two new data structures: WeakSet and WeakMap. Their references to values are not counted in the garbage collection mechanism, which means that if the object is no longer referenced by other objects, the garbage collection mechanism automatically reclaims the memory occupied by the object.
const wm = new WeakMap(a)const element = document.getElementById('example')
vm.set(element, 'something')
vm.get(element)
Copy the code
In the above code, create a New Weakmap instance. Then, a DOM node is stored in the instance as the key name, and some additional information is stored in WeakMap as the key value. In this case, a WeakMap reference to an Element is a weak reference and is not counted in the garbage collection mechanism.
A Listener object that registers listening events is suitable for WeakMap implementation.
/ / code 1
ele.addEventListener('click', handler, false)
/ / code 2
const listener = new WeakMap()
listener.set(ele, handler)
ele.addEventListener('click', listener.get(ele), false)
Copy the code
The advantage of Code 2 over Code 1 is that since the listener function is placed inside WeakMap, once the DOM object ELE disappears, the listener function handler bound to it will also disappear automatically.