4.1 introduction

JavaScript variables are loosely typed, and since there are no rules that define what data types a variable must contain, its value and data type can change over the lifetime of the script. This chapter covers variables, execution context and scope, and garbage collection.

4.2 MIND

4.3 Original and Reference Values

ECMAScript variables contain two different types of data: raw and reference values

  • Primitive Value

    For the simplest data, the variable that holds the original value is accessed by value.

  • Reference value: An object composed of multiple values

    A reference value is an object held in memory. When you manipulate an object, you are actually manipulating a reference to the object rather than the actual object itself.

4.3.1 Dynamic Properties

  • Primitive values cannot have attributes, and primitive types can be initialized using only the primitive literal form.
  • Only reference values can dynamically add attributes that can be used later

4.3.2 duplicate values

  • When the original value is assigned to another variable, the original value is copied to the location of the new variable. The two variables are used independently of each other.
  • When a reference value is assigned from one variable to another, the value stored in the variable is copied to the location of the new variable. The value copied here is actually a pointer to an object stored in heap memory.

4.3.3 Passing Parameters

  • Arguments to all functions in ECMAScript are passed by value.
  • Objects are also passed by value, even if they are accessed by reference.

Arguments to ECMAScript functions are local variables.

4.3.4 Determining the type

  • Typeof, while useful for raw values, is less useful for reference values.
  • ECMAScript provides the instanceof operator to determine object types.
    • The instanceof operator returns true if the variable is an instanceof a given reference type (as determined by its stereotype chain).
    • All reference values are instances of Object, and the original values are not (the original values are not objects).

Ecma-262 specifies that any object that implements an internal [[Call]] method should return “function” when typeOF is detected.

4.4 Execution context and scope

  • The context of variables or functions determines what data they can access and how they behave.
  • The global context is the outermost context.
  • In the browser, the global context is the Window object, and global variables and functions defined by var become properties and methods of the Window object.
  • Top-level declarations with lets and const are not defined in the global context.
  • Each function call has its own context, and ECMAScript controls the flow of program execution through the execution context stack.

    When code execution flows into a function, the context of the function is pushed onto a context stack. After the function completes execution, the context stack pops the function context, returning control to the previous execution context.

  • When the code in the context executes, it creates a scope chain of variable objects. This chain of scopes determines the order in which the code at each level of context accesses variables and functions.

    Identifier resolution at code execution is done by searching identifier names down the scope chain. The search process always starts at the very top of the scope chain and moves down until the identifier is found.

Function parameters are considered variables in the current context and therefore follow the same access rules as other variables below.

4.4.1 Scope chain enhancement

While there are two main execution contexts: global and function contexts (a third exists inside an eval() call), there are other ways to enhance the scope chain.

Some statements cause a temporary context to be added to the front of the scope chain, which is removed after code execution.

Usually:

  • The catch block of a try/catch statement
  • With statement

In both cases, a variable object is added to the front of the scope chain. For the with statement, the specified object is added to the front of the scope chain; For a catch statement, a new variable object is created that contains the declaration of the error object to be thrown.

4.4.2 Variable declaration

  1. Function scope declarations using var

    • When a variable is declared using var, it is automatically added to the nearest context (the local context of the function).
    • Variable promotion: The var declaration is carried to the top of the function or global scope, before all code in the scope. This phenomenon is called “ascending.”
  2. Use the block-level scope declaration for lets

    • The let keyword is similar to var, but its scope is block-level.
    • Another difference between let and VAR is that you cannot declare it twice in the same scope.
    • Let is a great way to declare iteration variables in a loop; var declares iteration variables that leak out of the loop.
    • Temporary dead zones: Strictly speaking, let is also promoted in JavaScript runtime, but because of “temporal dead zones” you can’t actually use the let variable before declaring it.
  3. Use a const constant declaration

    • Variables declared with const must be initialized at the same time and cannot be reassigned new values at any point in their life.
    • Const declarations apply only to top-level primitives or objects.

      A const variable assigned to an object cannot be reassigned to another reference, but the key of the object is not restricted.

    • To make the entire Object immutable, you can use object.freeze () so that assigning an attribute again does not report an error, but silently fails.
      const o3 = Object.freeze({});
      o3.name = 'Jake';
      console.log(o3.name); // undefined
      Copy the code
  4. Identifier lookup

  • Identifier lookup process:
    • The search starts at the front of the scope chain, searching for the corresponding identifier by the given name.
    • If the identifier is found in the local context, the search stops and the variable is determined; If the variable name is not found, the search continues along the scope chain. (The search may involve a chain of prototypes for each object)
    • This process continues until the variable object is searched to the global context.
    • If the identifier is still not found, it is undeclared.
  • Referencing local variables causes the search to stop automatically, without continuing the search for the next level variable object.
  • Using block-level scope declarations does not change the search process, but it can add additional layers to the lexical hierarchy.

4.5 Garbage Collection

JavaScript implements memory allocation and idle resource reclamation through automatic memory management.

The basic idea: Determine which variable will no longer be used, and then release the memory it occupies.

This process is periodic, meaning that the garbage collector runs automatically at certain intervals (or at a predetermined collection time during code execution).

Garbage recycling programs must keep track of which variables are still used and which variables are no longer used in order to reclaim memory. JavaScript has two tagging strategies for marking unused variables: tag cleanup and reference counting.

4.5.1 Mark cleaning

The most common JavaScript garbage collection strategy is mark-and-sweep.

When a variable is entered into a context, such as declaring a variable inside a function, the variable is marked as existing in the context. Variables in context, logically, should never be freed, because they can be used as long as the code in context is running. Variables are also marked out of context when they are out of context.

When the garbage collector runs, it marks all variables stored in memory (in a variety of ways). It then strips out all variables in the context, and all variables referenced by variables in the context. Variables tagged after this point are deleted because they are not accessible to any variables in context. The garbage collector then does a memory cleanup, destroying all tagged values and reclaiming their memory.

4.5.2 Reference Counting

Another, less commonly used garbage collection strategy is reference counting.

The idea is to keep track of how many times each value is referenced. When you declare a variable and assign it a reference value, the number of references to that value is 1. If the same value is assigned to another variable, the number of references is increased by one. Similarly, if the variable holding a reference to that value is overwritten by another value, the number of references is reduced by one. When the number of references to a value is zero, the value can no longer be accessed, so its memory can be safely reclaimed. The next time the garbage collector runs, it frees memory for the zero reference value.

Reference counting issues: Circular references

An effective way to avoid circular application is to dereference, which is to set an object or its attributes to NULL when they are not in use.

4.5.3 performance

Garbage collection programs run periodically, and if many variables are allocated in memory, performance can suffer, so scheduling garbage collection is important.

Modern garbage collectors decide when to run based on probing the JavaScript runtime environment. The detection mechanism varies from engine to engine, but is basically based on the size and number of allocated objects.

According to a 2016 blog post from the V8 team: “After a full garbage collection, V8’s heap growth strategy determines when to recycle again based on the number of active objects plus some margin.”

4.5.4 Memory Management

Memory limits affect not only variable allocation, but also the call stack and the number of statements that can be executed simultaneously in a thread.

Keeping the memory footprint to a small value allows for better page performance, and dereferencing ensures that only necessary data is saved when code is executed, with unnecessary data set to NULL.

4.5.4.1 Const and LET declarations improve performance

Both const and let are scoped by blocks (not functions), so using these two new keywords may allow the garbage collector to step in and reclaim memory that should be reclaimed sooner than var would.

4.5.4.2 Hiding classes and Deleting Classes

V8 makes use of “hidden classes” when it compiles interpreted JavaScript code into actual machine code.

At run time, V8 associates the created objects with hidden classes to track their attribute characteristics. Objects that can share the same hidden class perform better, and V8 is optimized for this, but not always. For example:

function Article() {
      this.title = 'Inauguration Ceremony Features Kazoo Band';
}
let a1 = new Article();
let a2 = new Article();
Copy the code

V8 is configured behind the scenes so that the two class instances share the same hidden class, because the two instances share the same constructor and stereotype. Suppose we add the following line of code:

a2.author = 'Jake';
Copy the code

At this point, the two Article instances correspond to two different hidden classes. Depending on the frequency of such operations and the size of the hidden classes, this can have a significant impact on performance.

Dynamically deleting attributes has the same effect as dynamically adding attributes (no longer sharing a hidden class). The best practice is to set unwanted attributes to NULL. This allows the hidden classes to remain unchanged and continue to be shared, while also removing the reference values for the garbage collector to collect. Such as:

function Article() {
  this.title = 'Inauguration Ceremony Features Kazoo Band';
  this.author = 'Jake';
}
let a1 = new Article();
let a2 = new Article();

a1.author = null;
Copy the code

4.5.4.3 Memory Leakage

Poorly written JavaScript can have memory leaks that are difficult to detect and harmful. On devices with limited memory, or in cases where functions are called many times, memory leaks can be a big problem. Memory leaks in JavaScript are mostly caused by improper references.

Advice:

  1. Use the keywords const, let, and var as much as possible to declare variables in a function and leave the scope after the function is executed.
  2. Variables caused by timers occupy the memory for a long time. Use timers to enable and disable properly, or release variables in time.
  3. Use closures wisely to avoid long-term memory hogging of objects in packages.

4.5.4.4 Static Allocation and Object Pool

How can I reduce the number of times my browser performs garbage collection?

You can’t directly control when garbage collection starts, but you can indirectly control the conditions that trigger it.

Object pooling

At some point during initialization, a pool of objects can be created to manage a collection of recyclable objects. An application can request an object from the object pool, set its properties, use it, and then return it to the object pool when the operation is complete. Since no object initialization occurs, garbage collection probes do not detect object turnover, and therefore the garbage collection program will not run as often.

If the object pool is allocated only on demand (creating new objects when they don’t exist and reusing them when they do exist), then the implementation is essentially a greedy algorithm with monotonously growing but static memory. The object pool must maintain all objects in some structure, and arrays are a good choice. However, when implementing with arrays, you must be careful not to incur additional garbage collection.

To avoid dynamic allocation, you can create an array of sufficient size at initialization to avoid the delete and create operations described above.

4.6 summary

  1. Raw value and reference value characteristics

    • The original value is fixed in size and therefore stored in stack memory.
    • Copying the original value from one variable to another creates a second copy of the value.
    • Reference values are objects stored in heap memory.
    • Variables that contain reference values actually contain only a pointer to the corresponding object, not the object itself.
    • Copying a reference value from one variable to another only copies Pointers, so the result is that both variables point to the same object.
    • The typeof operator determines the original typeof a value, while the instanceof operator is used to ensure the reference typeof a value.
  2. Execution context

    • Execution context is divided into global context, function context and block-level context.
    • Each time the code execution flow enters a new context, a chain of scopes is created to search for variables and functions.
    • The local context of a function or block can access variables not only in its own scope, but also in any containing context or even in the global context.
    • The global context can only access variables and functions in the global context, and cannot directly access any data in the local context.
    • The execution context of a variable is used to determine when memory is freed.
  3. The garbage collection

    • Values that leave scope are automatically marked as recyclable and then deleted during garbage collection.
    • The dominant garbage collection algorithm is tag cleaning, which marks values that are not currently in use and then comes back to reclaim their memory.
    • Reference counting is another garbage collection strategy that keeps track of how many times a value is referenced. (JavaScript engines no longer use this algorithm, but some older versions of IE still suffer from it because JavaScript accesses non-native JavaScript objects (such as DOM elements).)
    • Reference counting has problems with circular references in your code.
    • Dereferencing variables not only eliminates circular references, but also helps with garbage collection. To facilitate memory reclamation, global objects, properties of global objects, and circular references should all be dereferenced when no longer needed.