Scope & Context
1. Scope
Scope is the accessible scope of JS functions and variables, including global scope, local scope and block-level scope. The Global scope is the area that the entire program can access, which is the Window object in the Web environment and the Global object in the Node environment. A local scope is a function scope. It forms an independent scope inside a function and is destroyed when the function is executed. Variables inside a function can only be accessed inside the function. Block-level scope, which is generated when a variable is declared with the let or const keyword. The declared variable is valid only in the block, and variables declared in the let or const must be declared before they are used.
var a = 10; // Global variables
if (true) {
console.log(b) // Error must be defined before being used
let b = 20; // Block variables
console.log(b) / / 20
console.log(a) / / 10
}
console.log(a) / / 10
console.log(b) // error not defined
function add (a, b) {
var c = 0; // Local variables
console.log(c) / / 0
return a + b + c;
}
console.log(c) // error not defined
Copy the code
When the JS engine detects that a block-level scope has been created, the system generates a temporary dead zone that stores all variable names declared by lets or const. When accessing a variable stored in the temporary dead zone, the system will throw an error indicating that you need to declare the variable before using it. When encountering a variable declaration statement, declare the variable and delete the variable from the temporary dead zone.
2. Context
Context represents the value this represents in code execution. This in a JS function always refers to the object on which the function was called; Use call, apply, bind, etc to modify this.
Second, function execution
- Execution-time context The execution-time context is generated during the execution of a function and defines the specific information generated within the function during execution that represents the currently executing function. The first step is to create an Activation Object (AO). Save the AO to the top of the scope chain. Set the value of context this after the AO is created and before the function is executed, Variables defined internally and function parameters are placed in the AO with the initial value undefined. Assign the actual parameters of the function to variables in the AO. Puts the function declared inside the function into the AO, starting with the function itself. Look at an example:
function add (a, b) {
debugger
var temp1 = 100;
function validateNum (n) {
return typeof n === "number";
}
var validateNum = 100;
return a + b;
}
console.log(add(1.2))
Copy the code
As you can see, when the function starts executing, the actual parameters of the function are pre-assigned to the corresponding variables, but the values of the variables declared inside the function are initialized to undefined.
- Scope chain said above, JS is divided into many scopes, which function can access the variables inside a lot of, that these variables are from where, which contains the scope of the variables? This problem needs to start with scope chains. The scope of a function is stored in the [[scope]] variable and can only be called by the JS engine. Let’s use the simplest example to see what the scope contains:
function add (a: number, b: number) :number {
return a + b;
}
Copy the code
The scope generated by Add includes the following:As you can see, the function’s scope [[scope]] is an array containing a window object, the global object. What does the generated scope look like if the function is not defined directly in the global scope?
function add (a, b) {
function validateNum (n) {
console.log(a, b)
return typeof n === "number";
}
debugger
if(! validateNum(a) || ! validateNum(b)) {throw new Error('type error');
}
return a + b;
}
console.log(add(1.2))
console.log(add(2.3))
Copy the code
The function scope [[scope]] contains two objects, one is the global object and the other is the internal value of the add function. Thus, the generation of the function scope is based on the function definition environment, which holds the data of the current environment at the time of definition. After the above procedure, we can sort out the entire function execution process:As you can see, the scope chain of the validateNum function holds all the variables or functions that the function can access, starting with the variables in the AO of the self-generated active object, including the variables and functions defined inside the function and the argument variables
Two, the end of the function, memory release
At the end of execution, the function releases the AO, the active object created during execution. After a period of time, the AO object and its internal variables are garbage collected, freeing up memory. After executing validateNum, the validateNum AO is released, but the [[scope]] property still exists in the validateNum object. After the Add function is executed, the Add AO object is released. The AO validateNum is also released, but the Add function still exists. The final state in memory looks like this.
Third, the closure
A closure is a block of memory that is always referred to by a variable in the system. As a result, this block of memory is never released, forming a closed memory space that is usually invisible and accessible only to the variables referencing it.
Under normal circumstances, at the end of each function performs, all face will be generated by the memory recovery, but there are exceptions, is that if the memory is still referenced in other parts of the variables, then, the space will not be memory recovery, become a hidden in the black in the memory space, can only be reference the variable access of the space, If there are too many of these, memory will not be freed, causing a memory leak. Such as:
var el = document.getElementById('id');
function add (a, b) {
function validateNum (n) {
return typeof n === "number";
}
el.onclick = function clickHandle () {
console.log(a, b)
}
if(! validateNum(a) || ! validateNum(b)) {throw new Error('type error');
}
return a + b;
}
console.log(add(1.2))
Copy the code
When the add function is executed, the clickHandle function for the EL element is defined. The AO generated by the Add function is stored in the clickHandle [[Scope]]. The clickHandle function is bound to the EL element, and the clickHandle function will exist as long as the EL element exists and the clickHandle event response function is bound to it. The AO object that causes the Add function to be stored in [[scope]] of the clickHandle object will also remain in place and will not be freed by memory, as if there was a dark room that closed the AO object of the Add function and the garbage collection mechanism would ignore this memory. Closures essentially hold the AO of the active object generated when other functions are executed.
Four, subsequent
When a function inside a function does not reference an external variable, no closure is formed
function add (a, b) {
function validateNum (n) {
return typeof n === "number";
}
debugger
if(! validateNum(a) || ! validateNum(b)) {throw new Error('type error');
}
return a + b;
}
console.log(add(1.2))
Copy the code
The resulting scope chain is as follows:As you can see, if the function inside the function life does not use a variable in the external AO, then the AO is not included in the function’s [[scope]] scope chain.
function add (a, b) {
function validateNum (n) {
console.log(a)
return typeof n === "number";
}
debugger
if(! validateNum(a) || ! validateNum(b)) {throw new Error('type error');
}
return a + b;
}
console.log(add(1.2))
Copy the code
The chain of scopes seen in the execution phase is as follows:As you can see in Chrome, if there are closures, the JS engine will do a wave of optimization based on the referenced variables, save only the used variables, and move this part of the variables from the JS execution stack, reducing the execution stack memory footprint. ValidateNum is not called when the function is not called:The validateNum function is called when:
Closures in react functional components
const [value, setValue] = useState([]);
useEffect(() = > {
notificationCenter.on(EVENT_NAME, eventListener);
return () = >{ notificationCenter.off(EVENT_NAME, eventListener); }; } []);function eventListener(chatId? :string) {
console.log(value);
}
Copy the code
The execution of a function, the event listeners found themselves unable to access to the latest data value because the component rendering for the first time, the binding event monitoring function, at this point the function declared in the scope chain to keep the state of the data (value) of the initial value, when the page status changes, function modules can be carried to render, However, the event listener function is still generated for the first time. The initial value is stored in [[scope]], so the value accessed from the scope chain is always the initial value during function execution. A similar situation applies to setTimeout and other delayed callbacks.
There are two solutions to this situation:
- In useEffect, add a dependency that needs to be used. When the dependency changes, re-register the listening event.
const [value, setValue] = useState([]);
useEffect(() = > {
notificationCenter.on(EVENT_NAME, eventListener);
return () = > {
notificationCenter.off(EVENT_NAME, eventListener);
};
}, [value]);
Copy the code
- The second way is to use ref to change the variable to reference type. When external modification and internal function access are actually accessing the same reference properties, both ensure that the latest data is retrieved.
const valueRef = useRef([]);
useEffect(() = > {
notificationCenter.on(EVENT_NAME, eventListener);
return () = >{ notificationCenter.off(EVENT_NAME, eventListener); }; } []);function eventListener(chatId? :string) {
console.log(valueRef.curremt);
}
Copy the code