preface

The definition of scope is very clear, but do you know the bottom part of the scope chain? This article will deeply analyze the underlying principles of the scope chain. This article starts with scope and execution context, then goes through the scope chain, and finally adds closure.

(Too long, can be divided into several edible)

Classification of scopes

There are three types of scopes:

  • Global scope
  • Function scope
  • Block-level scope (new in ES6)

Where A is global scope, B and C are function scope.

Global scope

The scope of global properties is also the scope of global methods (functions). The global scope is not wrapped in {}.

Function scope

Every time you create a function, you create a corresponding function scope. Its access is the area wrapped in {}

Note the difference between having var and not having var:

function fn(){
    var n = 999;
}
fn()
console.log(n); // Uncaught ReferenceError: n is not defined
---------------------------------------------------------------------------
function fn(){
    n = 999;
}
fn()
console.log(n); / / 999
Copy the code

The absence of var actually declares a global variable n, which will appear in the window object

The reason is: without the declaration, n = 999 is actually window.n = 999, no matter where it is. Just like you don’t need window.document to access the document object, you need document.

Since the browser will think that the action of the current TAB is referring to the current TAB, it is accessing the property (window.document)/adding the property (window.a = 999) to the window object of the TAB. So we can abbreviate this to give the window object access property (document)/add property (a = 999)

Block-level scope

[Transition] Scenario: The code is intended to implement:

  1. The if external code uses the TMP variable in the outer layernew Date()
  2. The if internal code uses the TMP variable on the inside"hello world"
var tmp = new Date(a)function fn(){
    console.log(tmp)
    if(true) {var tmp = "hello world"
        console.log(tmp)
    }
}
fn() / / undefined; hello world
Copy the code

The result is that fn() is called with undefined, which means that the internal variable overwrites the external variable, because:

  1. Var declares variables without the concept of block-level scope, so that the variables inside if overwrite the variables outside
  2. The variables declared by var areVariable ascensionSo the print is not"hello world"butundefined

This is contrary to the code’s intention, so ES6 adds block-level scope, as well as let and const variable declarations, which are corresponding to block-level scope. Change the var in the if statement to let:

var tmp = new Date(a)function fn(){
    console.log(tmp)
    if(true) {let tmp = "hello world"
        console.log(tmp)
    }
}
fn() // Thu Jun 17 2021 11:01:31 GMT+0800; hello world
Copy the code

Block-level scope can also be simply an area wrapped in {}. This is common in if and for statements.

  • Before ES6, there was no concept of block-level scope and variables were declared using var. Here’s an example:
var a = Awesome!
console.log(a) / / 666
Copy the code
{
    var a = Awesome!
}
console.log(a) / / 666
Copy the code

The declaration and assignment of the variable a are indistinguishable from those placed inside or outside {}. My understanding is to treat {} as transparent, so the two are equivalent. In other words, no matter how many layers you have nested, all kinds of fancy nesting are the same, they are all treated as nothing. Var a = 666 = window.a = 666

  • After ES6 came the concept of block-level scope, variables can also be declared as lets and const. Here’s an example:
{
    let a = Awesome!
}
console.log(a) // Error: Uncaught ReferenceError: A is not defined
Copy the code

But the reverse is true:

let a = Awesome!
{
    console.log(a) / / 666
}
Copy the code

Why this is possible depends on the scope chain, as shown below.

It is worth noting that:

var obj = {
    a: 6
}
Copy the code

Obj also has a corresponding {}, but it is an object, which is not to be confused with block-level scope.

The difference between block-level scope and function scope

Both have curly braces {}, but they are completely different concepts:

  • Var declares a variable in the scope of the function, which is a local variable. (Global if there is no var declaration)
  • Var in the block-level scope declares a variable that is a global variable. Window. (XXX)

Execution context (as a collection)

Mentioned above to perform image context, for example, we know the context, in fact, many students (including me ha ha ha) are confused the concept of scope and the execution context, the difference between reference JS scope and the execution context I have a preliminary understanding of separating with both:

  • Scope: YesstaticIt’s defined when the function is defined
  • Execution context: YesdynamicIs determined when it is called,Each call to a function creates the context in which the function will be executed. Here’s a simple example:

Even multiple calls to the same function create multiple execution contexts

1     let a = 10 // 1. Enter the global context
2     let bar = function(x) {
3         let b = 5
4         fn(x + b) // 3, enter the fn function context
5     }
6     let fn = function(y) {
7         let c = 5
8         console.log(y + c)
9     }
10
11    bar(10) // 2, enter the bar function context
Copy the code

Think of it this way: the execution context is a collection of key-value pairs, and the variables and this make up the execution context. We break down the whole process of code execution:

(1) The code executes on line 1, creates a global context, and assigns all variables from line 1 to line 10 (note: In this case, a, bar, fn, and this are bound together. No matter where bar or fn is called, the bound variable a has the value 10, and the bound variable this is window (unless this is changed).

// Global context
a: undefined-- - >10
bar: undefined-- - >function
fn: undefined-- - >function
this: window-- - >window
Copy the code

(2) When the code is executed to line 11, the bar function is called. Jumping inside the bar function creates a new execution context before executing the body statement

// Bar function execution context
b: undefined-- - >5
x: 10-- - >10
arguments: [10] - > [10]
this: window-- - >window
Copy the code

Then press the bar execution context and set it to active (currently unique) (top of the stack)

(3) Then go to line 4 and call the fn function. When called inside the fn function, a new execution context is created before the body statement is executed

// fn function execution context
c: undefined-- - >5
y: 15-- - >15
arguments: [15] - > [15]
this: window-- - >window
Copy the code

Then push the FN execution context to the active state (currently unique) (top of the stack)

(4) After fn execution is completed, fn context generated by calling fn function is off the stack and destroyed. After the bar is executed, the context generated by calling the bar function is removed from the stack and destroyed. Then the global context is left and the stack is destroyed

When the function call completes, the context and the data in it are eliminated (although the closure does not comply)

The “stack” corresponding to the “push” and “out” of the execution context of the whole process of code execution is the “execution context stack” :

As you can see from the above example, an active environment is the corresponding execution context at the top of the stack. When we say the chain of scope, we say that an active object corresponds to an object at the head of the chain, and an execution context corresponds to an object, and that works.

A stack corresponds to a different chain of scope, and the active execution context at the top of the stack corresponds to the active object at the head of the chain. Go down one by one.

PS: This also remains the same, all environments are Windows from beginning to end.

(Note: this is only a synchronous task for the main thread. Asynchronous tasks for other threads are queued. See JavaScript series — Event Loop event polling for details.)

The difference between scope and context

A scope is like a fixed house, a materialized concept; Context, like living environment, is an abstract concept.

The relationship is that the scope is determined from the moment the code is written, and the context changes to different scopes as the code is executed.

The scope chain

In JavaScript, functions are also objects. In fact, everything in JavaScript is an object. Function objects, like other objects, have properties that can be accessed through code and a set of internal properties that are accessed only by the JavaScript engine.

One of these internal properties is [[Scope]]. Officially, this internal property contains the set of Scope objects that the function was created with. This set is called the Scope chain of the function, and it determines what data can be accessed by the function

When a function is created, its scope chain is populated with data objects accessible in the scope in which the function was created. For example, define the following function:

function add(num1,num2) {
    var sum = num1 + num2;
    return sum;
}
Copy the code

When the add function is created, its scope chain is filled with a global object that contains all global variables, as shown in the figure below (note: this is only a partial example of all variables) :

Corresponding console:

(Window.global (===window))

The scope of the add function will be used during execution. For example, run the following code:

var total = add(5.10);
Copy the code

When this function is executed, an internal object called the execution context is created, which defines the context in which the function is executed. Each execution context has its own Scope chain, which is used for identifier resolution. When an execution context is created, its Scope chain is initialized to the objects contained in the currently running function’s [[Scope]].

These values are copied into the scope chain of the execution context in the order in which they appear in the function. Together, they form a new object called the “live object,” which contains all of the function’s local variables, named parameters, set of parameters, and this. This object is then pushed into the head of the scope chain, and when the execution context is destroyed, so is the live object. The new scope chain looks like this:

Corresponding console:

Each node in the scope chain corresponds to an object

Process:

  • Search for variables in the global execution context: the corresponding scope chain [[Scopes]] is the Scopes array with only one element, the element Scopes[0] beingGlobal object, find the desired variable in the object;
  • The scope chain for the add function’s execution context [[Scopes]] is the Scopes array with two elements,Start at the end of the chain, the first element, Scopes[0], is the currently active objectActivation objectIf you can’t find it, continueGo down the scope chain, come to the second element, Scopes[1]Global object, find the desired variable in the object

Execute context stack and scope chain:

  • In the previous example of the execution context, we likened the relationship between execution contexts to a stack, whereas above we said that an execution context corresponds to a chain of scopes. The active environment corresponds to the execution context at the top of the stack; An object in an active state corresponds to an object at the head of the chain. The former is a visual description, the latter is a description of the underlying principles.

My understanding of this is that the different states of the stack correspond to a scope chain. The scope chain is likened to a “linked list”, and the context at the top of the stack is a “pointer” to this “list”.

It is important to note that the scope chain is determined as soon as the code is written, regardless of the order in which the code is executed. The execution context stack depends on the order in which the code is executed. So the context on the execution context stack does not necessarily correspond to the objects on the scope chain

For example:

var a = 10
function foo(){
    console.log(a)
}
function sum() {
    var a = 20
    foo()
}
sum() / / 10
Copy the code

Here you can see that in phase 3, our execution context stack has three environments, and the corresponding scope chain has only one node, not one to one. So here’s what we should do: The corresponding scope chain looks like only in the currently active context, not in the entire execution context stack

Conclusion:

  • Each function has an execution environment, and an execution environment is associated with a variable object. The collection of variable objects is called a scope chain
  • During function execution, each variable encountered goes through an identifier resolution process to determine where to fetch and store data. The process fromScope chain headerThat is, fromActive objectsStart searching. FindThe same nameIf found, use the variable corresponding to this identifier. If not, continue searching the scope chainNext objectIf all objects are searchedHave not foundIs considered as the identifierundefined. Every identifier goes through this search process during the execution of the function.
  • This mechanism ensures that variables/methods between functions at the same level and between functions at the higher and lower levels do not interfere with each other. (Closures are needed if you want to break through this mechanism.)

Note:

  • The this point above does not change orientation as a result of performing a context switch. This is determined when the function is called and does not change thereafter unless the call/apply/bind methods are used
  • The global execution environment in the Web is the Window object. The global execution environment is destroyed when the application exits

Code performance optimization for scope chains

From the structure and principle of the scope chain, we can see that the deeper the identifier is in the scope chain of the execution context, the slower the read and write time will be. As the figure above shows, because global variables always exist at the end of the execution context scope chain, finding them is the slowest during identifier resolution. Therefore, when writing code, use global variables as little as possible and local variables as much as possible

A good rule of thumb is: if a cross-scoped object is referenced more than once, store it in a local variable before using it. For example, the following code:

function changeColor(){
    document.getElementById("btnChange").onclick=function(){
        document.getElementById("targetCanvas").style.backgroundColor="red";
    };
}
Copy the code
function changeColor(){
    var doc = document; // Save the following global variable document in the local variable doc, which will be used more than once, to reduce the number of reads
    doc.getElementById("btnChange").onclick=function(){
        doc.getElementById("targetCanvas").style.backgroundColor="red";
    };
}
Copy the code

Avoid using the with statement

function initUI(){
    with(document){ // Use the with statement
        var bd = body,
            links = getElementsByTagName("a"),
            i = 0,
            len = links.length;
        while(i < len){
            update(links[i++]);
        }
        getElementById("btnInit").onclick = function(){ doSomething(); }; }}Copy the code

Here, because document is used multiple times, using the with statement may seem neat and efficient, but it actually causes performance problems.

The scope chain of the runtime context when code runs into the with statementTemporarily changed. A new mutable object is created that contains all the properties of the object specified by the argument. This object will be pushed into the scope chainThe head, which means that all local variables of the function are now inThe second scope chain object, so access is more expensive. As shown in the figure below:Therefore, avoid using the with statement in your program. In this case, simply storing the document in a local variable can improve performance.

try… Catch statement

Another thing that changes the scope chain is the catch statement in a try-catch statement. When an error occurs in the try block, execution jumps to a catch statement and pushes the exception object into a mutable object at the head of the scope chain. Inside the catch block, all of the function’s local variables will be placed in a second scope chain object. Example code:

try{
    doSomething();
}catch(ex){
    alert(ex.message); // The scope chain changes here
}
Copy the code

Try-catch statements are so useful in code debugging and exception handling that it is not recommended to avoid them altogether. You can optimize your code to reduce the performance impact of catch statements. A good approach is to hand over the error to a function, such as:

try{
    doSomething();
}catch(ex){
    handleError(ex); // Leave it to handleError
}
Copy the code

In optimized code, the handleError method is the only code executed in the catch clause. This function takes an exception object as an argument, so you can handle errors more flexibly and uniformly. Since only one statement is executed and there is no access to local variables, temporary changes to the scope chain do not affect code performance

Take a look at some common interview questions

  1. Block-level scope related:
for(var i=0; i<5; i++){console.log(window.i) // 0 1 2 3 4
}
for(let i=0; i<5; i++){console.log(window.i) // undefined * 5
}
Copy the code

The reason why this output is different is that there is no I in the window {}, and the for loop looks at the five {}.

  1. Execution context
var a = 10
function foo(){
    console.log(a)
}
function sum() {
    var a = 20
    foo() // This is equivalent to window.foo(), so think about it every time you see it
}
sum()
Copy the code

This will print 10 because: Even calling foo from within sum is equivalent to executing window.foo. Foo and the global variable window.a = 10 are bound in a context, so the output is the global variable a = 10. Instead of the local variable a = 20.

In general, it doesn’t matter where foo is called, because as long as foo is called, it must be in the global execution context, and the a variable must be used

The above two examples show that it doesn’t matter where the function is called, we just recognize the corresponding context created by the called function, and we go into that context to find the variable/method.

  1. Block-level scope, execution context, this point
var obj = {
    a: 5.get1: function(){
        console.log(this.a)
    },
    get2: function(){
        setTimeout(this.get1,0)
    }
}

obj.get1() / / 5 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 1

var fn = obj.get1
fn() // undefined ----------------- 2

obj.get2() // undefined ----------------- 3
Copy the code

The reason why 1 is different from 2 is that obj.get1() is an obj object calling get1(), so this refers to an obj object, so this.a is 5; Fn () also calls get1(), but it is called globally, so this defaults to window, so this.a is undefined.

3. Undefined (); This refers to obj, but in setTimeout this refers to window because it is a thread that is opened globally outside of the main thread (but not always, see JavaScript series — this keyword). So this.get1 inside setTimeout passes this (===window) to get1, so undefined.

closure

Let’s look at an example above:

var n = 5
function fn(){
    var n = 6
    function f(){
        console.log(n)
    }
}
fn()
console.log(n) / / an error
f() / / an error
Copy the code

Uncaught ReferenceError: n/f is not defined because the f() function is not in the global context, but only in the context of the fn() function. F () does not have the f() method in the scope chain of the global context where f() is executed

So what’s the fundamental reason for this, from a computer perspective it’s memory, so let’s start with stack memory free in JavaScript

Stack memory release mechanism

  • Heap memory: storageReference typesValue, the object type isKey/value pairThe function isCode string.
  • Heap free: assign a spatial address variable of the reference type to null, or the browser will free the address if no variables occupy heap memory
  • Stack memory: storageBasic types ofValue and the environment (context) that provides code execution.
  • Stack memory releaseGeneral when:Function completionThe private scope of the function is then released.

But the release of stack memory also has special cases: ① Function execution, but the function’s private scope contents are outside the stack variables are still in use, the stack memory can not be released inside the basic value will not be released. The global stack memory is only released when the page is closed

The fn() function has been executed, so the stack and heap memory occupied by the variables and methods in its private scope will be freed. Therefore, the fn() function cannot be accessed.

So how does the above example ensure that we can use f() and n after fn() (the meaning of closure)? We use return f inside fn() to return the entire f() function outside of fn() (and assign a value to a new variable s). (s) : fn(); (n) : fn(); (n) : fn();

function fn(){
    var n = 6
    function f(){
        console.log(n)
    }
    return f
}
var s = fn()
s() / / 6
Copy the code

This is a typical example of closures and how they work. From this we derive the concept of closures:

The meaning of closures

A closure is the ability to read variables or methods inside a function from outside of that function. In Javascript, only subfunctions inside a function can read local variables, so closures break this boundary. In essence, closures are the bridge that connects the inside of a function to the outside. (This can also lead to memory leaks, as described below)

In this case, I think closures are functions that can read the internal variables of other functions and the last function in this case should be the f() function above;

In this case, I think the closure is the bridge that connects the inside of the function to the outside of the function and the bridge inside of the function is s = fn() where the value of fn() is f, so it’s essentially the same thing as s = f is the bridge, the end that connects the outside of the function of fn is s, the end that connects the inside of the function of fn is f. Because s is there, the outside of fn can access the f method and the variable n inside fn; Because of the closure function f

How closures work

Let’s look at the example above again:

function fn(){
    var n = 6
    function f(){
        console.log(n)
    }
    return f
}
var s = fn()
Copy the code

So let’s not worry about s() here, let’s print out s

Since s is a global variable, window.s === s, we print out its detailed properties in the console

We found that the scope chain it points to is

(1) The first node of the scope chain corresponds to {n: 6}, and the second node corresponds to the window object. So we can only get n when we execute s(). This is exactly the same thing as f in f sub n.

Conclusion: The principle of closures is to copy the closure function to the Window object as one of the methods, so calls to the closure function are made in the global context

(2) {n: 6} is still used in the scope chain of s, so it consumes memory. Window. n is undefined, so the variable n is not a global variable, but a local variable. This leads to the action of closures:

Closures do:

  1. The variables inside the function can be read (get)/set (①) outside the function, as long as they remain local (③).
  2. Keep the values of these variables in memory (② above)

But it is possible to get rid of the s variable, but we can think of it in terms of the new variable, by thinking of the first part of fn()() as a function s (/ method) in the global context (/window object).

function fn(){
    var n = 6
    function f(){
        console.log(n)
    }
    return f
}
fn()()
Copy the code

So let’s take the concept of closures one step further:

A closure is a function that is executed in any environment other than the context of the function, and that function is the closure function

In the example above, although we use the auxiliary variable window.s, which does not actually exist, it can help us understand, such as the following two problems

The title of the closure

  • This points to the + closure
var name = "The Window";
var object = {
    name : "My Object".getNameFunc : function(){
        return function(){
            return this.name; }; }};console.log(object.getNameFunc()());
Copy the code
var name = "The Window";
var object = {
    name : "My Object".getNameFunc : function(){
        var that = this;
        return function(){
            returnthat.name; }; }};console.log(object.getNameFunc()());
Copy the code

(1) Output The Window (2) Output My Object (3) Output The Window (4) Output My Object (4)

We treat the first half of object.getNameFunc() as a whole, an anonymous function in the global context f (){return this.name}, Since this anonymous function is called without an object., this refers to window, so The window is printed.

This is not the default window. This is the object specified by the anonymous function. This is because executing object.getNameFunc() makes this of getNameFunc() point to object, so that also points to object, So executing f (){return that. Name} in the global context naturally outputs My Object

So the first question is equivalent to:

var name = "The Window";
(function(){
    return this.name
})()
Copy the code

And the second question is equivalent to:

var object = {
    name : "My Object"
}
function fn(){
    return this.name
}
fn.call(object)
Copy the code

In addition: the nAdd value for the following problem is an anonymous function, which is also a closure

function f1(){
    var n = 999;
    nAdd = function(){ n++ } // Note: This is a global method
    function f2(){
        console.log(n);
    }
    return f2;
}
var result = f1();
result(); / / 999
nAdd();
result(); / / 1000
Copy the code

Window.n = 999; window.n = 999; window.n = 999

So performnAdd()This line of code executes the method inside the F1 function in the global context.

  • For loop + closure
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0] (); data[1] (); data[2] ();Copy the code

After the for loop, an array of data in each element is an anonymous function, as a result of the var statement variable I will “pollution” to each other, so the data [0], the data [1], the data window. [2] I was inside, I call these anonymous function in the global context, The anonymous function finds the “I” in {}, but the “I” is contaminated, so there are three 3s. In addition to using let to declare variables, ③ we can also add a “protective layer” to each loop to prevent variables from being “contaminated”.

var data = [];
for (var i = 0; i < 3; i++) {
  (function(j){ // The parameter of the self-executing function
      data[j] = function () {
          console.log(j);
      };
  })(i) // The argument to the self-executing function
}
data[0] (); data[1] (); data[2] ();Copy the code
  • For loop + setTimeout + closure

We know:

for(var i = 0; i< 10; i++){
    setTimeout(function(){
        console.log(i)
    }, 200)}Copy the code

In other words, I ignores all {}, so every assignment to loop I breaks the {} wall and tamper with the global variable window. I, so the final output is all window. I. In addition to using let to declare variables, ③ we can also add a “protective layer” to each loop to prevent variables from being “contaminated”.

for(var i = 0; i< 10; i++){
    (function(j){ // Note: we need to pass in the argument I with the parameter j
        setTimeout(function(){
            console.log(j)
        }, 200) 
    })(i)
}
Copy the code

So we add one more for closures:

  1. The variables inside the function can be read (get)/set (①) outside the function, as long as they remain local (③).
  2. Keep the values of these variables in memory (② above)
  3. Self-executing functioncreatePrivate scope, protect the private variables of the function from external interference (③ above)

The above example is equivalent to:

for(var i = 0; i< 10; i++){
    function fn(j){ // Note: we need to pass in the argument I with the parameter j
        setTimeout(function(){
            console.log(j)
        }, 200) 
    }
    fn(i)
}
Copy the code

Garbage collection mechanism

How do you know to free stack memory? You need to understand the garbage collection mechanism of the browser.

The necessity of garbage collection

The memory space is limited. When the number of functions and variables in a file increases, it will occupy a lot of memory space. If there are some functions or variables that are no longer used, you should clear them in time to free up more memory space.

Garbage collection mechanism

The JS engine in the browser will automatically clean up the memory garbage, which can be seen in the performance of the new version of Chrome:

As you can see from the graph, the memory usage drops every once in a while, indicating that JavaScript periodically cleans up the garbage. How does it do this?

Reference counting

1. What is a quote?

References can be simply understood as our daily assignment operations:

var arr = [1.2.3.4]
Copy the code

In the above code, the array [1, 2, 3] is a value that allocates some memory space, and the array is assigned to the variable arr. In other words, the variable arr refers to the array [1, 2, 3]

2. Reference counting

The variable arr makes a reference to the array [1, 2, 3], so the number of references to the array [1, 2, 3] is increased by one. When a new array (value) is assigned to arr:

var arr = [1.2.3]
arr = [4.5.6]
Copy the code

The number of references to the array [1, 2, 3] is reduced by 1, so it becomes 0, so the JS garbage collection will remove the array [1, 2, 3].

3. Circular references
var obj1 = {}
var obj2 = {}
obj1.a = obj2
obj2.b = obj1
Copy the code

Two objects, is one of the attributes of each their own, can cause a circular reference, that is to say, object obj1 obj2, citations are 1, but if the two objects are no longer used, the reference number will not be reduced to 0, so use reference counting method can never recognize this memory garbage, also can never remove, This is called a memory leak. So modern browsers generally use tag elimination

Mark clearance

The algorithm assumes setting an object called root (in Javascript, root is a global object). The garbage collector will periodically scan objects in memory, starting from the root (which in JS is global objects). Anything that can be reached from the root is still needed. Objects that cannot be reached from the root are marked as unused and recycled later

Speaking of execution contexts above, here’s an example:

function fn(){
    let s = "ALKAOUA"
    console.log(s)
}
fn()
Copy the code

The process of fn() is divided into: entering function fn context – > the code that executes function fn – > leaving function FN context

Memory leaks

Memory leaks: Memory leaks occur when memory garbage that should be cleaned is not cleaned for some reason

As shown in the above diagram, when JavaScript periodically collects memory garbage, it reaches a minimum value within the interval. We can observe that this minimum value is getting larger and larger, indicating that there is a memory leak

1. Unexpected global variables

Why say accident? We declare variables in functions like this:

function fn(){
    let s = "ALKAOUA"
}
Copy the code

When fn finishes, the variable s is automatically garbage collected, but if you write:

function fn(){
    s = "ALKAOUA"} orfunction fn(){
    this.s = "ALKAOUA"
}
Copy the code

The variable s becomes a global variable (in the window object), so we thought it would be cleaned up, but it wasn’t

[Note] When creating a variable in a function, pay attention to whether the variable should be used continuously. If not, declare it

2. Memory leaks caused by closures

function fn(){
    var s = "ALKAOUA"
    function f(){
        console.log(s)
    }
    return f
}
fn()
Copy the code

Closures mentioned in the previous point can also leak memory because: When we use closures to make the function f and the variable s inside fn accessible from outside fn, that means we can access f and the variable s from the root object (the window object), so token clearing does not clean up these two. This causes a memory leak (but that’s what we want)

[note] If you do not need to access function f and variable s inside function fn, remember to resolve the closure:

Null (fn); (fn); (fn); (fn); (fn);

3. References to DOM elements

Sometimes it’s useful to save the internal data structures of DOM nodes. If you want to quickly update a few rows of a table, it makes sense to store each row of the DOM as a dictionary (JSON key-value pairs) or an array. At this point, there are two references to the same DOM element: one in the DOM tree and one in the dictionary. When you decide to delete these lines in the future, you need to remove both references

var elements = { // Store multiple DOM elements in an obj
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')};function doStuff() { // Perform DOM operations on the DOM nodes stored in obj
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
}
function removeButton() {
    document.body.removeChild(document.getElementById('button')); // Delete a DOM node
    // In this case, there is still a global #button reference -- elements object. The button element is still in memory and will not be reclaimed
}
Copy the code

In the example above, the #button DOM node is removed, but there is still a global reference to it, so the window object will reach the reference to the DOM node, so the memory occupied by the DOM node will not be cleared.

[Note] After deleting a node, delete any references to the node, such as elements. Button = null

4. Reference the DOM node timer

As mentioned above, after a DOM node is referenced, it is deleted because there are objects in the global reference to the DOM node, but the reference still exists, and GC (according to the method of marking clearance) does not clear the reference. In this way, the DOM node is still occupied by memory and will not be cleaned up

Similarly, if the DOM node is referenced by a timer, if the DOM node is deleted, the timer will not be deleted and will run until the page is unloaded, and if there are other callback functions used in the timer, the callback function will also take up memory, as if on a chain

So let’s simulate this

var obj = {
    num: Awesome!
}
var timer = setInterval(() = >{
    var n = obj.num
    console.log(n)
},1000) // Call once every second
Copy the code

obj = null // Simulate the DOM node deletion
Copy the code

After obj = null is executed, the timer keeps running. Because obj has become NULL, the timer keeps repeating errors

Note: If the timer references a DOM node or object, delete the DOM node or object if there is no other reference in the timer:

clearInterval(timer) // Clear the timer that is no longer needed
Copy the code

Optimization for garbage collection scenarios

An array of empty

[1,2,3] becomes memory garbage, and an address is applied to place an empty array

var arr = [1.2.3]
arr = []
Copy the code

Optimization mode:

var arr = [1.2.3]
arr.length = 0
Copy the code

Setting the length of the array to 0 (arr.length = 0) can also clear the array, and at the same time, it can realize array reuse and reduce the generation of memory garbage

Reuse objects in loops

Each loop creates a new object:

for (var i = 0; i < 10; i++) {
  var obj = {};// Each loop creates a new object.
  obj.index = i
  console.log(obj)
}
Copy the code

Optimization mode:

var t = {}; 
for (var i = 0; i < 10; i++) {
  obj.index = i
  console.log(obj)
}
Copy the code

The loop uses the same object every time, and set it to null when it is no longer in use. This is especially useful when the object is very large:

obj = null
Copy the code

Reuse functions in loops

The same is true for reuse functions within a loop:

// It is also best not to use function expressions in loops.
for (var k = 0; k < 10; k++) {
  var fn = function(a) {
    // Create the function object 10 times.
    console.log(a)
  }
  fn(k)
}
Copy the code

Optimization mode:

// Recommended usage
function fn(a) {
  console.log(a)
}
for (var k = 0; k < 10; k++) {
  fn(k)
}
fn = null
Copy the code

Refer to the article

  • Advanced JavaScript development: Understand JavaScript scopes and scope chains
  • The “front end material package” delves into JavaScript scope (chain) knowledge and closures
  • The difference between js scope and execution context
  • Interview | JS closure classic usage scenarios and including closures will brush
  • Learning Javascript Closures
  • Garbage collection and memory leaks in JavaScript
  • Javascript garbage collection mechanism
  • (2) Garbage collection mechanism