JavaScript variables are loose, and a variable is nothing more than the name of a particular value at a particular point in time. Since there are no rules defining what data types a variable must contain, its value and data type can change over the lifetime of the script. Such variables are interesting, powerful, and of course problematic.

Original and reference values

ECMAScript variables contain two different types of data: raw and reference values. Raw values are the simplest data, and reference values are objects made up of multiple values.

Variables that hold original values are accessed by value, because we operate on actual values stored in variables, whereas reference values are objects held in memory.

Since JS does not allow direct access to memory locations, it is not possible to directly manipulate the memory space where the object resides, so when manipulating an object, you are actually manipulating the reference to the object rather than the actual object itself.

1. Dynamic properties

We can add, modify, and delete properties and methods for reference values. The original value cannot be attributed, and attempts to add attributes to the original value do not fail.

Note that primitive types are initialized using primitive literals, and if the new keyword is used, an instance of type Object is created that behaves like the original value.

2. Copy the value

The original and reference values are also different when copied through variables. When assigning an original value from a variable to another variable, the original value is copied to the location of the new variable, which is a copy.

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. What is actually copied is a pointer to an object stored in heap memory, and the two variables point to the same object.

3. Pass parameters

Arguments to a function are passed by value. This means that values outside the function are copied to arguments inside the function, just as they are copied from one variable to another. If it’s a primitive value, it’s just like copying the original value variable, if it’s a reference value, it’s just like copying the reference value variable.

When you pass an argument by value, the value is copied to a local variable (argument). When a parameter is passed by reference, the value’s location in memory is stored in a local variable, which means that changes to the local variable are reflected outside the function.

4. Determine the type

Typeof determines whether a variable is a string, value, Boolean, or undefined. If the value is an object or null, typeof returns ‘object’.

Instanceof is used to check whether the constructor’s Prototype property appears on the prototype chain of an instance object.

Checking any reference values and Object constructors through the instanceof operator returns true. Similarly, if instanceof is used to detect the original value, false will always be returned because the original value is not an object.

Execution context and scope

1. Scope chain enhancement

The context of variables or functions determines what data they can access and how they behave. Each context has an associated variable object on which all variables and functions defined in that context reside.

The global context is the outermost context, which in the browser is the Window object. Therefore, variables and methods defined by var become properties and methods of the Window object.

The context is destroyed after all of its code has been executed, including all variables and functions defined on it (the global context is destroyed before the application exits, such as closing the web page or exiting the browser).

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. The variable object of the context in which the code is executing is always at the front of the scope chain, and the variable object of the global context is always the last variable object in the scope chain.

The connections between contexts are linear and ordered, and if an access variable is not found in the current scope, it is searched up to the global context.

2. Variable declarations

Currently var, let, and const can be used to declare a variable.

The var statement

When a variable is declared using var, it is automatically added to the nearest context. In a function, the closest context is the local context of the function.

function add(num1, num2) {
  var sum = num1 + num2;
  return sum;
}
let result = add(10.20); / / 30
console.log(sum); Error: sum is not a valid variable
Copy the code

If sum had not been declared with var, it would have been added to the global context and the above code would not have reported an error.

The VAR declaration is carried to the top of the function or global scope, before all the code in the scope. This phenomenon is called ascension.

var name = "Jake";
// This is equivalent to:
name = "Jake";
var name;

/ / ⬆ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - ⬇

function fn1() {
  var name = "Jake";
}
// This is equivalent to:
function fn2() {
  var name;
  name = "Jake";
}
Copy the code

Notice that the variable declaration is promoted! The promotion of declarations means that undefined is printed instead of Reference Error.

Let the statement

Let is the block-level scope defined by the nearest pair of enclosing curly braces {}.

Another difference between let and VAR is that you cannot declare it twice in the same scope. Duplicate var declarations are ignored, while duplicate let declarations raise syntaxErrors.

Note that we cannot use let variables before the declaration, and we disallow var declarations of iteration variables in for loops.

Const statement

Variables declared using const must also be initialized to a value. Once declared, new values cannot be reassigned at any time during its life cycle, but the object’s keys are not affected. To keep the entire Object from being modified, use object.freeze ().

const o3 = Object.freeze({});
o3.name = "Jake";
console.log(o3.name); // undefined
Copy the code

Three, garbage recycling

JavaScript implements memory allocation and idle resource reclamation through automatic memory management. The basic idea is simple: determine which variable will no longer be used, and then free up its memory. This process is periodic, meaning that the garbage collector runs automatically at certain intervals (or at a predetermined collection time during code execution).

Let’s take the normal life cycle of a local variable in a function. Local variables in a function exist at the time the function is executed. At this point, stack (or heap) memory is allocated space to hold the corresponding value. The function uses variables internally and exits. At this point, the local variable is no longer needed and its memory can be freed for later use.

There are two main marking strategies for garbage collection: tag cleaning and reference counting:

1. Mark cleaning

The most common JavaScript garbage collection strategy is tag cleaning. 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.

2. Reference counting

Reference counting strategies are less common. 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.

One serious problem with this strategy is circular references, where object A has A pointer to object B, and object B also refers to object A.

function problem() {
  let objectA = new Object(a);let objectB = new Object(a); objectA.someOtherObject = objectB; objectB.anotherObject = objectA; }Copy the code

Under the tag cleanup strategy, this is not a problem because neither object is in scope after the function ends. Under a reference-counting strategy, objectA and objectB survive the function because their references never go to zero. If the function is called multiple times, a large amount of memory will never be freed.

So, when we code, we clear the relationship between variables that are not needed and the reference values, and the next time the garbage collector runs, the values are removed and the memory is reclaimed.

myObject.element = null;
element.someObject = null;
Copy the code

Memory management

The best way to optimize memory footprint is to ensure that only necessary data is saved when executing code. If the data is no longer necessary, set it to NULL, freeing its reference. This can also be called dereferencing.

Note, however, that dereferencing a value does not automatically cause the associated memory to be reclaimed. The key to dereferencing is to ensure that the associated value is no longer in context, so it will be collected in the next garbage collection.

Using let and const declarations as much as possible helps improve the garbage collection process.

A memory leak

Accidental declaration of global variables is one of the most common and easiest to fix memory leaks, as follows:

function setName() {
  name = "Jake";
}
Copy the code

The interpreter creates the variable name as an attribute of the window, which will not disappear as long as the window itself is not cleaned up.

Timers can also cause memory leaks:

let name = "Jake";
setInterval(() = > {
  console.log(name);
}, 100);
Copy the code

The name referenced in the callback will occupy memory as long as the timer is running.

It is also easy to leak memory using closures:

let outer = function () {
  let name = "Jake";
  return function () {
    return name;
  };
};
Copy the code

Calling outer() causes memory allocated to name to leak. This code creates an internal closure that cannot clean up the name as long as the returned function exists because the closure keeps referring to it. If the content of the name is large (more than a small string), that could be a big problem.

conclusion

  • 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.
  • Execution context is divided into global context, function context and block-level 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.
  • 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.
  • Dereferencing variables not only eliminates circular references, but also helps with garbage collection.

This study note is from JavaScript Advanced Programming (version 4) Chapter 4 (Variables, scopes, and Memory)