preface

This chapter introduces you to the knowledge point is relatively simple, but it is very important. It’s also part of the interview process.

You can learn from this reading:

  • There are four common memory leaks
  • Identification of memory leaks

There are four common memory leaks

In practice, it’s easy to inadvertently write code that leaks memory, as you might have seen in the following cases.

1. Unexpected global variables

Undeclared variables

When we assign a value to a variable in a function but do not declare it:

function fn () {
    a = "Actually, I'm a global variable"
}
Copy the code

The variable a is equivalent to a variable in the window object:

function fn () {
    window.a = "Actually, I'm a global variable"
}
Copy the code

We have already stated that global variables are very difficult to collect by the garbage collector, so unexpected global variables should be avoided.

usethisCreated variable

Here’s another case:

function fn () {
    this.a = "Actually, I'm a global variable"
}
fn();
Copy the code

We know that this refers to window, so the a variable created at this point will also be mounted under the Window object.

The way to avoid this is to add ‘use strict’ to the top of your JavaScript file header or function, turn on strict mode, and make this point undefined.

Of course, if you have to use a global variable to store a lot of data, be sure to set it to NULL or redefine it when you’re done.

Forgotten timers or callback functions

Timer cause

We can also leak memory when using timers in code:

var serverData = loadData()
setInterval(function() {
	var renderer = document.getElementById('renderer')
	if(renderer) {
		renderer.innerHTML = JSON.stringify(serverData)
	}
}, 5000) 
Copy the code

In the above example 🌰, the node renderer references serverData. The timer still points to the data when the node renderer or data is no longer needed. So even when the Renderer node is removed, the Interval still lives and the garbage collector cannot collect, nor can its dependencies be collected, unless the timer is terminated.

Object observer

Another example is the observer model:

var btn = document.getElementById('btn');
function onClick (element) {
    element.innerHTMl = "I'm innerHTML"
}
btn.addEventListener('click', onClick);
Copy the code

In the case of the observers above, it is important to explicitly remove them once they are no longer needed (or the associated objects become unreachable). Old IE 6 couldn’t handle circular references. Older versions of IE were unable to detect cyclic references between DOM nodes and JavaScript code, leading to memory leaks.

However, modern browsers (including Internet Explorer and Microsoft Edge) use more advanced garbage collection algorithms (tag sweep) that already detect and handle circular references correctly. You don’t have to call removeEventListener to reclaim node memory.

3. Out-of-dom references

The cause of this memory leak is simply:

If you store the DOM as a dictionary (JSON key-value pairs) or an array, the same DOM element has two references: one in the DOM tree and one in the dictionary. Both references will need to be cleared in the future.

Take this example:

// Reference the DOM in the object
var elements = {
    btn: document.getElementById('btn')}

function doSomeThing () { elements.btn.click(); }

Copy the code

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 }

In the 👆 case above, you can manually clear the reference to elements. BTN = null.

Fourth, the closure

And then there’s the closure we talked about earlier. This is what happens to closures when local variables are destroyed.

First, let’s be clear: the key to closures is that anonymous functions can access variables in the parent scope.

A simple example 🌰:

function fn () {
    var a = "I'm a";
    return function () {
        console.log(a);
    };
}
Copy the code

Because variable a is referenced by an anonymous function inside the fn() function, this variable is not recycled.

So one might ask, even if that would be wrong? In the above case 👆, of course, nothing is apparent. What if you make it more complicated?

var globalVar = null; // Global variables
var fn = function () {
    var originVal = globalVar; // Local variables
    var unused = function () { // Unused function
        if (originVal) {
            console.log('call')
        }
    }
    globalVar = {
        longStr: new Array(1000000).join(The '*'),
        someThing: function () {
            console.log('someThing')
        }
    }
}
setInterval(fn, 100);
Copy the code

Take a minute to look at the example above and you will find:

  1. Each callfnFunction to create a new objectoriginVal;
  2. variableunusedIt’s a quoteoriginValThe closure;
  3. unusedIt’s not used, but it’s referencedoriginValForce it to stay in memory and not be reclaimed.

The solution is to set originVal to NULL at the very bottom of fn.

Identification of memory leaks

The above 👆 introduces so many possible memory leaks, so is there any practical way to see the performance of memory leaks?

Of course there are. The following two ways are commonly used now:

  • ChromeBrowser consolePerformanceorMemory
  • NodeTo provide theprocess.memoryUsagemethods

ChromeBrowser consolePerformanceorMemory

To check the memory usage on Chrome, perform the following steps.

  1. Right-click on the page and click “Check” to open the console (Macshortcutsoption+command+i);
  2. choosePerformancePanel (used in many textbooksTimelinePanel, I don’t know if the version is not the reason);
  3. Check theMemoryThen click on the black dot in the upper left cornerRecordStart recording;
  4. Click in the popoverStopAfter recording, the panel displays the memory usage during this period.


If memory usage keeps incrementing, it is a memory leak:


Or you can use the method I described in Documenting Memory Leaks caused by One-time Timer and Closure Problems.

NodeTo provide theprocess.memoryUsagemethods

The other is the process.memoryUsage method provided by Node. I don’t use this method very much.

console.log(process.memoryUsage());
// { rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772 }
Copy the code

Process. memoryUsage returns an object containing memoryUsage information for Node processes. This object contains four fields, in bytes, with the following meanings:

Resident set size (RSS) : All memory usage, including instruction area and stack. HeapTotal: The amount of memory occupied by the heap, both used and unused. HeapUsed: part of the heapUsed. External: memory occupied by C++ objects inside the V8 engine.Copy the code

The heapUsed field is used to determine memory leaks.

conclusion

In general, common memory leaks include:

  • Unexpected global variables
  • Forgotten timer or callback function
  • Out-of-dom references
  • A variable created repeatedly in a closure

How to avoid memory leaks:

  • Pay attention to program logic and avoid “endless loops” and the like
  • Reduce unnecessary global variables, or objects with long life cycles, and timely garbage collection of useless data
  • Rule of thumb to avoid creating too many objects: Return what you don’t use

After the language

That concludes the JavaScript advanced memory stack, which is a total of five articles. In the article I also try to use more popular language to explain, I hope everyone can understand.

If you like Lin silly article, but also please help me a small favor, pay attention to a wave of I recently just began to beat the public number, I will above the irregular hair some of the front end of the original article, learn together, progress together 😊.

LinDaiDai public qr code.jpg