JavaScript scope and scope chain in depth analysis

  • 2020-03-30 00:47:57
  • OfStack

JavaScript scopes

Any programming language has the concept of scope. Simply put, scope is the accessibility of variables and functions, that is, scope controls the visibility and life cycle of variables and functions. In JavaScript, variables have global and local scopes.

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 the variables defined outside the outermost function have global scope, for example:


var authorName=" Mountain stream "; 
function doSomething(){ 
var blogName=""; 
function innerSay(){ 
alert(blogName); 
} 
innerSay(); 
} 
alert(authorName); //Mountain stream
alert(blogName); //Script errors
doSomething(); // 
innerSay() //Script error & NBSP;

(2) all directly assigned variables are automatically declared as having global scope, for example:

function doSomething(){ 
var authorName=" Mountain stream "; 
blogName=""; 
alert(authorName); 
} 
alert(blogName); // 
alert(authorName); //Script error & NBSP;

The variable blogName has a global scope, but the authorName cannot be accessed outside the function.

(3) the properties of all window objects have global scope

In general, the built-in properties of a window object have a global scope, such as window.name, window.location, window.top, and so on.

2. Local Scope

In contrast to global scopes, local scopes are generally only accessible within fixed code fragments, the most common being inside a function, so in some places you'll also see someone calling this a function scope, such as the blogName and the function innerSay in the following code.


function doSomething(){ 
var blogName=""; 
function innerSay(){ 
alert(blogName); 
} 
innerSay(); 
} 
alert(blogName); //Script errors
innerSay(); //Script error & NBSP;

Scope Chain

In JavaScript, functions are also objects, and in fact, everything in JavaScript is an object. A function object, like any other object, has properties that can be accessed through code and a set of internal properties that are accessible only to the JavaScript engine. One of the internal properties is [[Scope]], defined by the third edition of the ecma-262 standard. This internal property contains the collection of Scope objects that the function is created. This collection is called the Scope chain of the function, which determines what data can be accessed by the function.

When a function is created, its scope chain is populated by accessible data objects in the scope where the function was created. For example, define the following function:


function add(num1,num2) { 
var sum = num1 + num2; 
return sum; 
} 

When the function add is created, its scope chain is filled with a global object that contains all global variables, as shown in the following figure (note that the picture illustrates only some of them) :

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201312/20131206101455.png ">

The scope of the function add will be used at execution time. For example, execute the following code:


var total = add(5,10); 

When this function is executed, an internal object is created called the execution context, which defines the environment in which the function is executed. Each runtime context has its own Scope chain for identifier resolution, and when the runtime context is created, its Scope chain is initialized to the object contained in the [[Scope]] of the current running function.

These values are copied into the scope chain of the runtime context in the order in which they appear in the function. Together, they form a new object, called an activation object, which contains all the local variables of the function, named parameters, parameter sets, and this. This object is then pushed to the front of the scope chain, and when the runtime context is destroyed, the active object is destroyed. The new scope chain is shown in the following figure:

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201312/20131206101517.png ">

Without encountering a variable during the execution of the function, an identifier resolution is performed to determine where to get and store the data. The procedure starts from the head of the scope chain, that is, from the active object, to search for the identifier of the same name, if found, use the corresponding variable of this identifier, if not found to continue to search for the next object in the scope chain, if all the objects are not found, the identifier is considered undefined. As the function executes, each identifier undergoes this search.

Scope chain and code optimization

As you can see from the structure of the scope chain, the deeper the identifier is in the scope chain of the runtime context, the slower the read and write will be. As shown in the figure above, because global variables are always at the end of the run-time context scope chain, it is slowest to find global variables during identifier resolution. Therefore, when writing code, use as few global variables as possible and as many local variables as possible. A good rule of thumb is that if a cross-scoped object is referenced more than once, store it in a local variable before using it. For example, the following code:


function changeColor(){ 
document.getElementById("btnChange").onclick=function(){ 
document.getElementById("targetCanvas").style.backgroundColor="red"; 
}; 
} 

This function references the global variable document twice, and to find it you must traverse the entire scope chain until you finally find it in the global object. This code can be rewritten as follows:

function changeColor(){ 
var doc=document; 
doc.getElementById("btnChange").onclick=function(){ 
doc.getElementById("targetCanvas").style.backgroundColor="red"; 
}; 
} 

This code is relatively simple and does not show a huge performance improvement when rewritten, but if a large number of global variables are accessed repeatedly in the program, the performance of the rewritten code can be significantly improved.

Change the scope chain

The runtime context is unique each time the function is executed, so multiple calls to the same function result in the creation of multiple runtime contexts, which are destroyed when the function is executed. Each runtime context is associated with a scope chain. In general, the scope chain is only affected by the with and catch statements as the runtime context runs.

The with statement is a shortcut to objects to avoid writing duplicate code. Such as:


function initUI(){ 
with(document){ 
var bd=body, 
links=getElementsByTagName("a"), 
i=0, 
len=links.length; 
while(i < len){ 
update(links[i++]); 
} 
getElementById("btnInit").onclick=function(){ 
doSomething(); 
}; 
} 
} 

The use of the width statement here to avoid writing the document multiple times seems to be more efficient and actually causes performance problems.

When the code runs into 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 argument. This object is pushed to the head of the scope chain, which means that all the local variables of the function are now in the second scope chain object, so access is more expensive. As shown in the following figure:


< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201312/20131206101527.png ">

So avoid using the with statement in your program. In this case, 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 a try block, execution jumps to the catch statement, then pushes the exception object into a mutable object and into the header of the scope. Inside the catch block, all the local variables of the function will be placed in the second scoped chain object. Sample code:


try{ 
doSomething(); 
}catch(ex){ 
alert(ex.message); //The scope chain changes here
}

Note that once the catch statement is executed, the scope chain returns to its previous state. Try-catch statements are useful in code debugging and exception handling, and therefore are not recommended to be avoided entirely. You can optimize your code to reduce the performance impact of catch statements. A good pattern is to delegate errors to a function, for example:

try{ 
doSomething(); 
}catch(ex){ 
handleError(ex); //Delegate to the handler method
} 

In the optimized code, the handleError method is the only code executed in the catch clause. This function takes an exception object as an argument so that you can handle errors more flexibly and uniformly. Because only one statement is executed and no local variables are accessed, temporary changes in the scope chain do not affect code performance.


Related articles: