1. Original and reference values

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

Raw value: The simplest data

Reference value: An object composed of multiple values

Six raw values:

  1. Undefined
  2. Null
  3. Boolean
  4. Number
  5. String
  6. Symbol

When assigning a value to a variable, the JS engine must determine whether the value is a primitive or a reference value.

Variables that hold the original value are accessed by value, because we are operating on the actual value stored in the variable.

A reference value is an object stored in memory, and JS does not allow direct access to the memory location (that is, cannot directly manipulate the memory space in which the object resides). When you manipulate an object, you’re actually manipulating a reference to that object, not the actual object itself. To do this, variables holding reference values are accessed by reference.

1.1 Dynamic Properties

Raw and reference values are defined similarly, creating a variable and assigning it a value.

The difference is, once the variable holds that value, what can we do with that value?

Reference value: you can delete, add, and modify its properties and methods at any time

let person = new Object(a);// Create a new object
    person.name = 'Macxx';// Dynamically add a name attribute to this new object
    console.log(person.name);
    person.name = 'New Macxx';// Dynamically modify the value of an attribute in an object
    console.log(person.name);
Copy the code

Raw values: Cannot have attributes, although adding attributes to raw values does not generate an error

let name = 'XiaoMing';/ / the original value
    name.age = 27;// Add the attribute name to the original value
    console.log(name);
    console.log(name.age);
Copy the code

Primitive types can only be initialized using primitive literals. If the new keyword is used, JS creates an instance of type Object that behaves like the original.

let name1 = 'Macxx';// Initialize with the original literal
let name2 = new String('Diing');// Initialize with the new keyword

name1.age = 27; // Attributes cannot be added to raw values
name2.age = 23; // Objects can

console.log(name1.age, typeof name1);
console.log(name2.age, typeof name2);
Copy the code

1.2 copy the value

When an original value is copied to another variable through a variable, the original value is copied to the location of the new variable. The two variables can be used independently of each other.

let num1 = 5;
let num2 = num1;/ / copy the value
// The two variables are used independently of each other
num1 += 1;
num2 += 10;
console.log(num1,num2);
Copy the code

When a reference value is copied from one variable to another, the value stored in the variable is also copied to the location of the new variable. The difference here is that the copied value is actually a pointer to an object stored in heap memory. After the copy operation is complete, both variables actually point to the same object, so changes on one object are reflected on the other.

let name1 = {
    age:27.hair:"black"
}
let name2 = name1;  // Copy the reference value
name2.age = 18;
name2.hair = "red";
console.log(name1);
Copy the code

Variables as little notes, xiao Ming’s home is A storage object in the heap memory, we assume that there are notes A, with xiao Ming’s home address, and then took A piece of paper B, copy the above address A down, then we can note A go to xiao Ming house, also can go to xiao Ming house by note B. At the same time, when we go to Xiao Ming’s house through note A, we draw A picture in Xiao Ming’s house (dynamically add attributes), so when we go to Xiao Ming’s house through note B, we can also see this picture.

1.3 Transfer Parameters

In EcmaScript, arguments to all functions are passed by value (that is, values outside the function are copied to arguments inside the function as if they were copied from one variable to another). When arguments are passed by value, the value is copied to a local variable (that is, a named parameter, or a slot in the arguments object).

When parameters are passed by reference, the value’s location in memory is stored in a local variable, which means that changes to local variables are reflected outside of the function (which is not possible in EcmaScript).


If the argument is a primitive value, passing by value is easy to see:

addTen = (num) = > {
    return num += 10;
}
let count = 10 ;
let result = addTen(count);
console.log(count);// The value of count is not affected after executing addTen
console.log(result);
Copy the code

If the argument is an object, then it starts to make people think:

setName = (obj) = > {
    obj.name = 'Macxx';
}
let person = new Object(a);// Define an Object
console.log('Before executing setName',person.name);
setName(person);
console.log('After executing the setName function',person.name);
Copy the code

The above code, as a result, looks a lot like reference-by-reference, because changes to local variables are reflected outside the function, but they are not. Look back at the reference copy above to see why. Of course, we can also use the following code to prove that the object is also passed as a value:

setName = (obj) = > {
    obj.name = 'Macxx';
    obj = new Object(a); obj.name ='Diing';
}
let person = new Object(a);// Define an Object
console.log('Before executing setName',person.name);
setName(person);
console.log('After executing the setName function',person.name);
Copy the code

In the above code, you can see that two lines of code have been added to the setName function. If the object is passed by reference, then the value of person.name should be Diing after the setName function is executed, which is obviously not the case.

At first, the address of Xiao Ming’s house was written on A piece of paper A, and then we copied the address on the piece of paper A onto the piece of paper B. Then we took the piece of paper B to Xiao Ming’s house and broke A window (execute obj.name = ‘Macxx’). Then we erased the address of Xiao Ming’s house on the piece of paper B. Write down the address of Red’s house (execute obj = new Objector()), and then follow the address on the note to red’s house and break a cup (execute obj.name = ‘Diing’). Throughout the entire process, note A the above values have not changed, only note B, but we pass note B went to xiao Ming smash the window in the home, when we pass note also can see A go to xiao Ming house, but can’t see by smashing glass, because the smash of glass is the home of the little red, not xiao Ming’s home. If you are passing parameters by reference, you should not copy the address of strip A to strip B, but only strip A throughout the process.

1.4 Determining the Type

  1. The typeof operator

The best way to determine whether a variable is a string, value, Boolean, or undefined is to use the Typeof operator. However, if the evaluated value is an object or null, the Typeof operator returns an ‘object’.

let num = 123;/ / values
let str = 'Hello';/ / string
let variable;//undefined
let isTrue = true;/ / a Boolean value
let obj = {};/ / object
let isNull = null;//null

console.log(typeof num);
console.log(typeof str);
console.log(typeof variable);
console.log(typeof isTrue);
console.log(typeof obj);
console.log(typeof isNull);
Copy the code

Typeof is useful for raw values, but not so useful for reference values, because a lot of the time we don’t care if a value is an object, we care what kind of object it is. At this point, we use the instanceof operator. 2. Instanceof operator

Syntax: result = variable instanceof constructor

let arA = [1.2.3.4];
let objB = {
    name:'Macxx'.age:26
}
let strC = 'Hello';
console.log( arA instanceof Array );Is arA an Array type?
console.log( objB instanceof Object);// Is objB of type Object?
console.log( strC instanceof Object);// Is strC of type Object?
Copy the code

By definition, all reference values are instances of Object, so checking any object constructor with the Instanceof operator returns true. Checking the original value with instanceof always returns false because the original value is not an object.

2. Execution context and scope

2.1 Execution Context

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 within that context reside. The global context is the outermost context. In the browser, the global context is our Window object. All variables and functions defined by var become properties and methods of the Window object.

console.log(window);// Prints out the window object
var name = 'Macxx';// Declare variables with var in the global context
console.log(window.name);
Copy the code

Top-level declarations using lets and const are not defined in the global context, but have the same effect on scope-chain resolution.

let hair = 'Black';
const age = 27;
console.log(window.hair,window.age);
Copy the code

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

// Define a function that contains the function context
function setName(){
    let fun_name = 'setName';// Define a variable fun_name in the function context
    console.log(fun_name);
}
setName();// Execute the function
console.log('After function execution',fun_name);
Copy the code

The fun_name variable in the above code exists in the function context. After the function is executed, the function context is destroyed and the fun_name variable is also destroyed. Therefore, printing fun_name after the setName function is executed will cause an error.

Each function call has its own context, and when the code execution stream/enters the function, the function context is pushed to a function context stack, which pops out of the function context after the function completes execution, returning control to the previous context.

2.2 Scope chain

The execution flow of an ECMAScript program is controlled by the context stack described above, which creates a scoped chain of variable objects when executed.

This chain of scopes determines the order in which the code at each level of context accesses variables and functions

The context variable object that the code is executing is always at the front of the scope chain.

The variable object of the global context is always the last variable object in the scope chain.

If the context is a function, its “active object” is used as a variable object. Active objects initially have only one definition variable: arguments (this variable is not available in the global context)

// Define a function
function fun(e){
    console.log(arguments);// The active object of the function
}
fun();
console.log(window.arguments);// Global context has no arguments
Copy the code

The next variable object in the scope chain comes fromInclude contextThe next variable object comes from the next containing context.

As you can see from the code above, the fun context contains the setName context, and the setName context contains the color context. When the function in the above code executes normally, the scope chain is preceded by the color variable object, followed by the setName variable object, followed by the fun variable object, and finally by the window object.

When code is executed, identifier resolution is accomplished by searching the identifier name down the scope chain, always starting at the very front of the scope chain and continuing down the chain until the identifier is found (if not, an error is reported)

var color = 'blue';

function changeColor(){
    color = color === 'blue' ? 'red' :'blue';
}

changeColor();

console.log(color);
Copy the code

Color is not defined in the context of the changeColor function. Color is defined in the global context, but the changeColor function is executed without error, and the value of color is changed at the end. This is because, when the code is executed, the JS engine passes through the scope chain, First search changeColor variable object, did not find color, then the next level, to the window object, search color, determine the value of color, then perform judgment, and finally change the value of color to red.

Variables defined in a local scope chain can be used to replace global variables in a local context.

var color = 'blue';// Define global variables

function ChangeColr(){
    let anotherColor = 'yellow';// Define local variables
    function swapColor(){
        // Local variables replace global variables
        [color,anotherColor]=[anotherColor,color];
    }
    swapColor();
}

ChangeColr();

console.log(color);
Copy the code

The internal context can access everything about the external context through the scope chain, but the external context cannot access anything about the internal context.

The connections between contexts are linear and ordered

Each context can search for variables and functions in the upper-level context, but no context can search in the lower-level context. Arguments to a function are considered to be variables in the current context, and therefore follow the same rules as other variables below.

2.3 Scope chain enhancement

Some statements cause a ‘temporary’ context to be added to the front of the scope chain, which is removed after code execution. This usually happens in two situations :(when the code executes to either of the following)

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

In both cases, a variable object is added to the front of the scope chain:

  • The with statement adds a specified object to the front of the scope chain.
  • The catch statement creates a new variable object that contains a declaration of the error object to be thrown.

2.4 Variable Declaration

1. Use the var function scope declaration

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, and in the with statement, the closest context is also the local context of the function. If a variable is undeclared and initialized directly, it is automatically added to the global context.

function fun(){
    // The local context of the function
    var color = 'blue';// Use var to declare variable color and initialize it
}
fun();
console.log(color);// Print color outside the function, reporting an error
Copy the code
function fun(){
    // The local context of the function
    color = 'blue';// Instead of using var to declare the color variable, initialize it directly
}
fun();
console.log(color);// You can print color because color is added to the global context
Copy the code

2. Variable promotion

Var declarations are carried to the top of a function or global scope, before all code in the scope, a phenomenon called “promotion.” Promotion lets code in the same scope be used without regard to whether it has been declared or not. (Promotion can be verified by printing variables before declaration)

function fun(){
	console.log(name);// Print out variables before declaring them
	var name = 'Macxx';// The variable is declared and initialized
	console.log(name);// Print out the variable after declaration
}
fun();
Copy the code

The variable can be used before the declaration, but the value of the variable is undefined. To understand variable promotion in more detail, look at the following code:The name and age variables are declared after the for loop, but since the var declaration is used, it can be seen as the code on the right. When entering the function, all variables and functions need to be declared at the top of the scope and declare, assign undefined, Then execute the following code.

3. Use the block-level scope declaration for lets

== block-level scope is defined by the nearest block containing curly braces {}. That is, if blocks, while blocks, function blocks, and so on, and even individual blocks are scope for let declaration variables. = =

if(true) {var name = 'Macxx';
    let age = 27;
}    
console.log(name);
console.log(age);
Copy the code

For example, the age variable declared with the let keyword is not accessible outside the if block, while the name variable declared with var is accessible outside the if block.

Let differs from VAR in that it cannot be declared twice in the same scope. Duplicate var declarations are ignored, but duplicate let declarations raise SyntaxError.

var name = 'Macxx';
var name = 'Diing';

let color = 'red';
let color = 'black';
Copy the code

Var (name); let (color); let (name);

This behavior of the let is perfect for declaring iterating variables in a loop, and using var to declare iterating variables leaks out of the loop.

for(var i = 0 ; i < 5 ; i++){}
console.log(i);// The iteration variable I is leaked out of the loop

for(let j = 0 ; j < 5 ; j++){}
console.log(j);// The iteration variable j is destroyed after the loop ends
Copy the code

Strictly speaking, let is promoted in JS execution, but you can’t actually use let variables before declaration due to “temporary dead zones”.

4. Use a const constant declaration

A variable declared with const must be initialized to a value at the same time it is declared. Once declared, no new value can be assigned at any point in its life. (The rest of the rules are the same as for let)

const NAME = 'Macxx';//const declares the constant NAME
NAME = 'Diing';// Add a new value
Copy the code

Const declarations apply only to top-level primitives or objects. In other words, a const variable assigned to an object cannot be reassigned to another reference, but the key of the object is not restricted.

const person = {
    name:'Macxx'.age:27.hairColor:'black'
}
// Change the key of person
person.hairColor = 'red';   // Modify the value of the person attribute
person.skin = 'yellow';     // Dynamically add a skin attribute
console.log(person.hairColor, person.skin);
// Assign additional reference values
person = {
    color:'green'
}
Copy the code

As you can see from the above code, if we initialize a const constant as an object, we can change the key of that object, but if we assign a new reference value to the constant, we will get an error.

To make the entire object immutable, you can use object.freeze() so that when assigning an attribute, no errors will be reported, but it will silently fail.

const Obj = Object.freeze({});
Obj.name = 'Macxx';
console.log(Obj.name);
Copy the code

Because a const declaration implies that the value of a variable is of a single type and cannot be modified, the JS runtime compiler can replace all instances of it with the actual value without looking up the variable through a query table. (This is an optimization method)

Development practice has shown that the use of const declarations without interfering with development tasks can fundamentally ensure that reassignment bugs are found in advance.

2.5 Identifier Search

When an identifier is introduced for reading or writing in a particular context, a search must be performed to determine what the identifier represents. The search starts at the front of the scope chain, searches for the corresponding identifier, given the name, all the way to the variable object in the global context. If the identifier is found, the search stops and the variable is determined, otherwise an error is reported.

let thisColor = 'black';
function fun(){
    // Here is the context for fun
    function setName(){
        // This is the context of setName
        function color(){
            // Here is the context of color
            let a = thisColor;
            let b = thisname;
        }
        color();
    }
    setName();
}
fun();
Copy the code

“Thisname” variable is not defined. “thisName” variable is not found in the global context, so this error is reported.

Identifier lookups are not free, and accessing local variables is faster than accessing global variables because you don’t have to switch scopes.