Scope is one of the most important concepts in JavaScript. If you want to learn JavaScript well, you need to understand how JavaScript scope and scope chains work. Today's article provides a brief introduction to JavaScript scope and scope chain, hoping to help everyone learn JavaScript better.
JavaScript scope
Any programming language has the concept of scope. Simply put, scope is the accessible range of variables and functions. That is, scope controls the visibility and life cycle of variables and functions. In JavaScript, there are two types of variable scope: global scope and local scope.
1. Global Scope
Objects that can be accessed anywhere in the code have global scope. Generally speaking, the following situations have global scope:
(1) The outermost function and variables defined outside the outermost function have global scope, for example:
Copy the code code as follows:
varauthorName="Mountainside Stream";
functiondoSomething(){
varblogName="";
functioninnerSay(){
alert(blogName);
}
innerSay();
}
alert(authorName);//Mountainside creek
alert(blogName);//Script error
doSomething();//
innerSay()//script error
(2) All variables that are not defined and directly assigned are automatically declared to have global scope, for example:
Copy the code code as follows:
functiondoSomething(){
varauthorName="Mountainside Stream";
blogName="";
alert(authorName);
}
alert(blogName);//
alert(authorName);//Script error
The variable blogName has global scope, but authorName cannot be accessed outside the function.
(3) All properties of window objects have global scope
Generally, the built-in properties of the window object have global scope, such as window.name, window.location, window.top, etc.
2. Local Scope
Contrary to the global scope, the local scope is generally only accessible within a fixed piece of code, most commonly within a function, so in some places you will also see people refer to this scope as a function scope, such as the following code Both blogName and function innerSay in have only local scope.
Copy the code code as follows:
functiondoSomething(){
varblogName="";
functioninnerSay(){
alert(blogName);
}
innerSay();
}
alert(blogName);//Script error
innerSay();//script error
Scope Chain
In JavaScript, functions are also objects. In fact, everything in JavaScript is an object. Function objects, like other objects, have properties that can be accessed through code and a set of internal properties that are only accessible to the JavaScript engine. One of the internal properties is [[Scope]], defined by the ECMA-262 standard third edition. This internal property contains the collection of objects in the scope in which the function is created. This collection is called the scope chain of the function, which determines Which data can be accessed by the function.
When a function is created, its scope chain is populated with data objects accessible from the scope in which the function was created. For example, define the following function:
Copy the code code as follows:
functionadd(num1,num2){
varsum=num1+num2;
returnsum;
}
When the function add is created, its scope chain will be filled with a global object, which contains all global variables, as shown in the following figure (note: the picture only illustrates a part of all variables):
The scope of function add will be used during execution. For example, execute the following code:
Copy the code code as follows:
var total = add(5,10);
When this function is executed, an internal object called an "execution context" is created. The runtime context defines the environment in which the function is executed. Each runtime context has its own scope chain for identifier resolution. When a runtime context is created, its scope chain is initialized to the object contained in [[Scope]] of the currently running function.
The values are copied into the runtime context's scope chain in the order they appear in the function. Together they form a new object called an "activation object". This object contains all local variables, named parameters, parameter collections and this of the function. Then this object will be pushed to the front end of the scope chain. When the runtime context is destroyed, the active object is also destroyed. The new scope chain is shown below:
During the execution of the function, if a variable is not encountered, it will go through an identifier resolution process to determine where to obtain and store the data. This process starts from the head of the scope chain, that is, from the active object, and looks for an identifier with the same name. If it is found, use the variable corresponding to this identifier. If it is not found, continue to search for the next object in the scope chain. If If no objects are found after searching, the identifier is considered undefined. During function execution, each identifier undergoes such a search process.
Scope chaining and code optimization
It can be seen from the structure of the scope chain that the deeper the identifier is located in the scope chain of the runtime context, the slower the reading and writing speed will be. As shown in the figure above, because global variables always exist at the end of the runtime context scope chain, finding global variables is the slowest during identifier resolution. Therefore, when writing code, you should use global variables as little as possible and use local variables as much as possible. A good rule of thumb is: if a cross-scope object is referenced more than once, store it in a local variable before using it. For example the following code:
Copy the code code as follows:
functionchangeColor(){
document.getElementById("btnChange").onclick=function(){
document.getElementById("targetCanvas").style.backgroundColor="red";
};
}
This function references the global variable document twice. To find the variable, you must traverse the entire scope chain until it is finally found in the global object. This code can be rewritten as follows:
Copy the code code as follows:
functionchangeColor(){
vardoc=document;
doc.getElementById("btnChange").onclick=function(){
doc.getElementById("targetCanvas").style.backgroundColor="red";
};
}
This code is relatively simple and will not show a huge performance improvement after rewriting. However, if there are a large number of global variables in the program that are accessed repeatedly, the performance of the rewritten code will be significantly improved.
change scope chain
The corresponding runtime context is unique each time a function is executed, so calling the same function multiple times will result in the creation of multiple runtime contexts. When the function completes execution, the execution context will be destroyed. Each runtime context is associated with a scope chain. Under normal circumstances, during the execution of the runtime context, its scope chain will only be affected by the with statement and the catch statement.
The with statement is a shortcut way to use objects to avoid writing repeated code. For example:
Copy the code code as follows:
functioninitUI(){
with(document){
varbd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i<len){
update(links[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
}
The width statement is used here to avoid writing the document multiple times, which seems more efficient, but actually causes performance problems.
When the code reaches the with statement, the scope chain of the runtime context is temporarily changed. A new mutable object is created that contains all the properties of the object specified by the parameter. This object will be pushed to the head of the scope chain, which means that all local variables of the function are now in the second scope chain object, and therefore more expensive to access. As shown below:
Therefore, you should avoid using the with statement in your program. In this example, simply storing the document in a local variable can improve performance.
Another thing that changes the scope chain is the catch statement in the try-catch statement. When an error occurs in the try code block, the execution process jumps to the catch statement, and then the exception object is pushed into a mutable object and placed at the head of the scope. Inside the catch block, all local variables of the function will be placed in the second scope chain object. Sample code:
Copy the code code as follows:
try{
doSomething();
}catch(ex){
alert(ex.message);//The scope chain changes here
}
Please note that once the catch statement is executed, the scope chain will return to its previous state. The try-catch statement is very useful in code debugging and exception handling, so it is not recommended to avoid it completely. You can reduce the performance impact of catch statements by optimizing your code. A good pattern is to delegate error handling to a function, for example:
Copy the code code as follows:
try{
doSomething();
}catch(ex){
handleError(ex);//Delegate to the processor method
}
In the optimized code, the handleError method is the only code executed in the catch clause. This function receives an exception object as a parameter, so that you can handle errors more flexibly and uniformly. Since only one statement is executed and no local variables are accessed, temporary changes in the scope chain will not affect code performance.