Heavy learning JavaScriptThe purpose of the article is to review the basis, convenient learning framework and source code when you can quickly locate knowledge points, check loopholes to fill gaps, all articles are synchronized inPublic number (front stack in road) 和 githubOn.
Raw and reference values
In JavaScript, data is divided into primitive values and reference values. Primitive values are the simplest data, also known as value types. A reference value is an object composed of multiple values, commonly known as reference types. The variable holding the original value is accessed by value, so the actual value stored in the variable is manipulated. A reference value is an object held in memory, and to change it, you actually operate on a reference to that object.
-
Original values cannot add attributes; reference values can add attributes
-
Copying the original value to another variable is two separate stacks, and copying the reference value to the other variable is the copied reference address, and the heap on which the object resides remains the same.
-
When an object is passed into a method and its properties are changed, the object is externally accessed with the same value
function setName(obj){ obj.name = "adc"; obj = new Object(a); obj.name ="def" } let person = new Object(a); setName(person);console.log(person.name) // abc Copy the code
When the internal obj is overwritten, obj becomes a pointer to a local object, which is destroyed at the end of the function.
Execution context
The concept of context is particularly important in JavaScript because it determines what data and behavior they can access. Each context has an associated variable object that contains everything defined in the context.
- The global context, the outermost context, is generally the window
- Function context, when executed, will be pushed to a context stack. After the function is executed, the context stack will pop up the function context, returning control to the previous execution context
- Context only takes effect when the function is called
Now let’s simulate the behavior of an execution context:
The first thing to know is that the entire execution of JavaScript is divided into two stages: compilation (defined by scoping rules and compiled into executable code) and execution (engine completion, which creates the execution context).
We define an execution context stack as an array: ECStack = [], when JavaScript begins to interpret the execution code, the global code will be encountered first, so we press in a global execution context. When the entire application ends, the ECStack will be emptied. So there will always be a globalContext at the bottom of the ECStack.
ECStack = [
globalContext
]
Copy the code
At this point, if a function is encountered:
// Execute the following function
function fn(){
function inFn(){}
inFn()
}
fn()
Copy the code
Executing the context stack goes through the following process:
/ / pressure stack
ECStack.push(globalContext)
ECStack.push(fnContext)
ECStack.push(inFnContext)
/ / the pop-up
ECStack.pop(inFnContext)
ECStack.pop(fnContext)
ECStack.pop(globalCotext)
Copy the code
During the creation phase of the execution context, three things happen:
-
Creating a variable object
-
Create scope chains
-
This point
Each execution context assigns a variable object (VO), whose properties are composed of variable and function declarations. In the case of function context, parameter lists are also added to the variable object as properties. Variable objects of different scopes are also different.
Note: only function declarations are added to variable objects, not function expressions!
// Function declaration
function a(){}
typeof a //function
// Function expression
var a - function fn(){}
typeof fn // undefined
Copy the code
When a function is activated, an activation object (AO) is created and assigned to the execution context. The AO consists of arguments initialization, which is then used as a variable object for variable initialization.
function a(name, age){
var gender = "male";
function b()}a("Xiao Ming".20)
Copy the code
AO = [arguments] an active object is created in the execution context of a and is initialized to: AO = [arguments]. VO = [arguments].concat([name.age,gender,b])
In general, variable objects include parameters, function declarations, and variable declarations.
function fn(value){
console.log(a);
console.log(inFn);
var a = 2;
function inFn(){};
var c = function() {};
a = 3;
}
fn(1);
Copy the code
After entering the execution context, the AO at this point is:
AO = {
arguments: {
0: 1.length: 1
}
value: 1.a: undefined.b: reference to function inFn(){},
c: undefined
}
Copy the code
Then the code starts executing, and when it’s done, the AO is:
AO = {
arguments: {
0: 1.length: 1
},
value: 1.a: 3.b: reference to function inFn(){},
c: reference to FunctionExpression "c"
}
Copy the code
From above, the code as a whole should be executed in the following order:
function fn(value){
var a;
function inFn(){};
var d;
console.log(a);
console.log(inFn);
a = 2;
function inFn(){};
c = function(){};
a = 3;
}
Copy the code
Only one active variable object exists at a time.
scope
JavaScript uses static and dynamic scopes. Static scopes are defined when a function is defined, and dynamic scopes are defined when a function is called.
var a = 1;
function out(){
var a = 2;
inner();
}
function inner(){
console.log(a)
}
out()
/ / 1
Copy the code
There is a link between scope and scope. When looking for a variable, if the current context is not found, the variable object of the parent execution context is looked up to the global context.
The scope of a function is determined when it is created, because there is an internal property called [[scope]], which retains all parent variables. In other words, it is a hierarchy of all parent variables. We can find the [[scope]] in a function from the console, but it does not represent the full scope chain!
function out(){
function inner(){}}Copy the code
When a function is created, its respective [[scope]] is:
out.[[scope]] = [
globalContext.VO
]
inner.[[scope]] = [
outContext.AO,
globalContext.VO
]
Copy the code
When the function is activated, it enters the function context. After the AO is created, the active object is added to the top of the Scope chain.
Scope = [AO].concat([[scope]])
Copy the code
As of now, the scope chain has been created.
Let’s combine the execution context and scope to see how it works:
var scope = "global scope";
function fn(){
var a = "local scope";
return a;
}
scope();
Copy the code
-
Fn maintains a private property [[scope]] to which it initializes the scope chain of the current environment
fn.[[scope]] = [ globalContext.VO ] Copy the code
-
The execution context of the fn function is created by executing the fn function, and the execution context of the FN function is pushed onto the execution context stack
ECStack = [ fnContext, globalContext ] Copy the code
-
The fn function copies the internal [[scope]] property to create the scope chain
fnContext = { Scope: fn.[[scope]] } Copy the code
-
At this point fn’s execution context and scope chain are constructed, and arguments are used to create and initialize active objects, adding parameters, function declarations, and variable declarations
fnContext = { AO: { arguments: { length: 0 }, a: undefined }, Scope: fn.[[scope]] } Copy the code
-
At this point, fn completes its internal construction and begins to push its own active object AO to the top of its scope chain
fnContext = { AO: { arguments: { length: 0 }, a: undefined }, Scope: [AO, [[Scope]]] } Copy the code
Note that the scope chain now includes its own AO and the scope chain created earlier by copying the inner [[scope]]
-
At this point, fn’s scope chain, variable, and execution context are all completed, and fn function is executed. The next step is to modify the value of AO, and then push AO off the stack, and finally:
ECStack = [ globalContext ] Copy the code
There is a more detailed example here: a JS interview questions triggered by the thinking
The garbage collection
In a function, a local variable exists during the execution of the function. If the function ends, the variable is no longer needed and its memory is freed. Two commonly used mechanisms are tag cleaning and reference counting.
Tag cleanup is the most common, where each time the variable is used, it is marked once, superimposed, and each time it is not used (i.e. out of context), the tag is reduced by one, decreasing in order.
Reference counting is a record of each reference to a value, which is incremented by one. The browser records the number of references. If the variable referenced by that value is overwritten by another value, it is reduced by one.
If a variable is referenced improperly, or if the final scope of the execution is not released, then it will not be marked and reference-counted, resulting in a memory leak.
function fn(value){
return function(name){
return value + name
}
}
var fn2 = fn("123");
var name = fn2("Xiao Ming")
Copy the code
Classic closure problem, function returns an anonymous function! Fn2 calls fn and returns an anonymous function that holds the VO of the fn function scope, including arguments and value. When fn is destroyed after execution, its VO will remain in memory. Its VO will remain in anonymous functions, which means that the VO will always be used, so the browser’s garbage collection mechanism will not deal with it, and it is now a memory leak.
A common way to avoid memory leaks is to assign a value of NULL
fn2 = null
Copy the code
Force the interior of fn2 to be empty, so that the reference to the anonymous function is null, and the FN VO it uses can be reclaimed.
Reference links:
- JavaScript deep into the execution context
- VO, AO, execution environment, and scope chain
- The difference between execution context and scope
My public number: the front end stack in the road, a front end article every day, the feeling of chewing is wonderful ~