preface

In the past, memory leaks have not been a huge problem for Web developers. Pages are kept relatively simple and memory resources are freed up as they jump from page to page, and memory leaks, if any, are small enough to be ignored.

Now, new Web applications reach a higher level where pages can run for hours without skipping, retrieving and updating pages dynamically through Web services. JavaScript language features are also taken to the extreme, with complex event binding, object-oriented and closure features forming the entire Web application. In the face of these changes, memory leaks are becoming more and more prominent, especially those previously hidden by refreshing (navigation).

Memory leaks are an issue that every developer eventually faces, and they are the source of many problems: slow response times, crashes, high latency, and other application issues.

What is a memory leak?

A memory leak is when a block of allocated memory is neither used nor reclaimed until the browser process terminates. Memory leaks are a common occurrence in C++ because memory is managed manually. Popular languages such as C# and Java use automatic garbage collection to manage memory, with almost no memory leaks under normal use. Automatic garbage collection is also used to manage memory in the browser, but due to bugs in the browser garbage collection method, memory leaks can occur.

Second, JavaScript memory management

JavaScript is a garbage collection language. Garbage collection languages help developers manage memory by periodically checking that previously allocated memory is reachable. In other words, the garbage collection language alleviates the “memory still available” and “memory still reachable” problems. The difference is subtle and important: only the developer knows what memory will be used in the future, whereas unreachable memory is identified and marked by algorithms and reclaimed by the operating system in due course. The main cause of memory leaks in garbage collection languages is unwanted references. Before you can understand it, you need to understand how the garbage collection language distinguishes between reachable and unreachable memory.

Third, Mark – and – sweep

Most garbage collection languages use an algorithm called Mark-and-sweep. The algorithm consists of the following steps:

  1. The garbage collector creates a list of roots. Roots are usually references to global variables in code. In JavaScript, the “window” object is a global variable and is treated as root. The Window object always exists, so the garbage collector can check that it and all of its children are present (that is, not garbage);
  2. All roots were checked and marked as active (i.e. not garbage). All child objects are also checked recursively. All objects from root onwards are not treated as garbage if they are reachable.
  3. Any unmarked memory is treated as garbage, and the collector can now free the memory and return it to the operating system.

Modern garbage collectors have improved the algorithm, but the essence is the same: reachable memory is marked and the rest is garbage collected.

An unwanted reference is a memory reference that the developer knows is no longer needed, but for some reason remains in the active root tree. In JavaScript, an unwanted reference is a variable retained in the code that is no longer needed but refers to a chunk of memory that should have been freed. Some people think it’s the developers’ fault.

To understand the most common memory leaks in JavaScript, we need to understand which types of references are forgettable.

Several common JavaScript memory leaks

[1] Unexpected global variables

JavaScript handles undefined variables loosely, creating a new variable in the global object. In the browser, the global object is Window

function foo(arg) {
  bar = "this is a hidden global variable"; } // The truth is that foo forgot to use var inside and accidentally created a global variable bar, which leaked a simple stringfunction foo(arg) {
  window.bar = "this is an explicit global variable";
}Copy the code

Another unexpected global variable might be created by this

function foo() {
  this.variable = "potential accidental global"; } foo(); // Call foo(), this refers to the global object (window)Copy the code

Workaround: Add ‘use strict’ to the header of the JavaScript file and use strict mode to parse JavaScript to avoid unexpected global variables, in which case this points to undefined. If you must use a global variable to store a lot of data, be sure to set it to NULL or redefine it when you’re done using it.

Although we discussed some unexpected global variables, there are still some unambiguous global variables that generate garbage. They are defined as non-recyclable (unless they are defined as empty or redistributed). This is especially true when global variables are used to temporarily store and process large amounts of information. If you must use a global variable to store a lot of data, be sure to set it to NULL or redefine it when you’re done using it. One of the main causes of increased memory consumption associated with global variables is caching. Data is cached for reuse, and the cache must have an upper limit on its size to be useful. High memory consumption causes the cache ceiling to be breached because cached content cannot be reclaimed.

[2] Circular reference

function func() {  
    let A = {};  
    letB = {}; A.a = B; // A = A; // B references A}Copy the code

Pure ECMAScript objects will still be recognized and recycled by the garbage collection system as long as no other objects refer to objects A and B, that is, they refer to each other. However, in Internet Explorer, if any object in a circular reference is a DOM node or ActiveX object, the garbage collection system will not notice that the circular relationship between them is isolated from other objects in the system and will release them. Eventually they are kept in memory until the browser closes.

Solution: Set both A and B to null

[3] Forgotten timer or delay timer

It’s common to use setInterval and setTimeout in JavaScript, but you often forget to clean them up after you use them

let result = getData();
setInterval(function () {
    let node = document.getElementById('Node');
    if(node) {// Process node and result node.innerhtml = json.stringify (result); }}, 1000).Copy the code

This in setInterval and setTimeout refers to the window object, so internally defined variables are also mounted globally; If references the result variable, and if setInterval is not cleared, result will not be released. The same is true for setTimeout.

Solution: Use clearInterval and clearTimeout

[4] Closure

Closures are a key aspect of JavaScript development: anonymous functions can access variables in a parent scope.

function bindEvent() {
    let obj = document.createElement("XXX");
    obj.onclick = function () {
        // do something 
    }
}Copy the code

Closures can hold local variables in a function without freeing them. The above example defines the event callback, because the function is defined within the function, and the reference to the inner function, the event callback, is exposed, forming a closure

Solutions:

Method one: Define the event handler outside of the closure

Method 2: Remove references to the DOM from external functions that define event handlers. As described in the JavaScript Authority’s Guide, unusable properties in a closure can be removed to reduce memory consumption.

/ / methodfunction bindEvent() {
    let obj = document.createElement("XXX");
    obj.onclick = onclickHandler;
}
function onclickHandler() {
    //doSomething} // Method twofunction bindEvent() {
    let obj = document.createElement("XXX");
    obj.onclick = function () {
        //do something 
    }
    obj = null;
}Copy the code

[5] MEMORY leaks caused by DOM

★ When the page element is removed or replaced, if the element binding event is still not removed, in IE will not make appropriate processing, at this time to manually remove the event, otherwise there will be memory leaks.

let btn = document.getElementById("myBtn");
btn.onclick = function () {
    document.getElementById("myDiv").innerHTML = "XXX";
}Copy the code

Solutions:

Method 1: Manually remove events

Method two: Use event delegation

// Manually remove eventslet btn = document.getElementById("myBtn");
btn.onclick = function () {
    btn.onclick = null;
    document.getElementById("myDiv").innerHTML = "XXX"; } // Use the event delegate document.onclick =function (event) {
    event = event || window.event;
    if (event.target.id == "myBtn") {
        document.getElementById("myDiv").innerHTML = "XXX"; }}Copy the code

★ THE DOM reference is not cleared

let myDiv = document.getElementById('myDiv');
document.body.removeChild(myDiv);Copy the code

MyDiv cannot be reclaimed because there is a reference to it by the variable myDiv

MyDiv = null

★ The attribute added to a DOM object is a reference to an object

let MyObject = {}; 
document.getElementById('myDiv').myProp = MyObject;Copy the code

Solution: Release document.getelementByID (‘myDiv’).myProp = null in the page onunload event

[6] Automatic type conversion

let s = 'xxx';
console.log(s.length);Copy the code

S itself is a string, not an object, and it has no length property. So when accessing length, the JS engine will automatically create a temporary string object to encapsulate S, and this object will definitely leak.

Unpacking: Remember to explicitly cast all value types before performing operations

let s = 'xxx';
console.log(new String(s).length);Copy the code


The article is updated every week. You can search “Front-end highlights” on wechat to read it in the first time, and reply to [Books] to get 200G video materials and 30 PDF books

​​