Execution Environment and scope
The execution environment (execution context, which is sometimes referred to as "environment" for simplicity) is one of the most important concepts in JavaScript. The execution environment defines the other data that a variable or function has access to, and determines their respective behavior. Each execution environment has a variable object associated with it (variable object), and all variables and functions defined in the environment are stored in the object. Although the code we write does not have access to this object, the parser uses it in the background when it processes the data.
The global execution environment is one of the outermost execution environments. Depending on the hosting environment where the ECMAScript implementation resides, the objects that represent the execution environment are different. in a Web browser, the global execution environment is considered a Window object , so all global variables and functions are created as properties and methods of the Window object . After all code in an execution environment is executed, the environment is destroyed, and all variables and function definitions stored therein are destroyed (the global execution environment is not destroyed until the application exits-for example, when the Web page or browser is closed).
Each function has its own execution environment. When the execution flow enters a function, the environment of the function is pushed into an environment stack. After the function executes, the stack pops up its environment and returns control to the previous execution environment. The execution flow in the ECMAScript program is controlled by this convenient mechanism.
When code executes in an environment, a scope chain of variable objects is created (scope chain). The purpose of a scope chain is to ensure an orderly access to all variables and functions that the execution environment has access to. The front end of the scope chain, which is always the variable object for the environment in which the code is currently executing. If the environment is a function, its active object (activation object) is used as the variable object. The active object initially contains only one variable, the arguments object (this object does not exist in the global environment). The next variable object in the scope chain comes from the containing (external) environment, and the next variable object comes from the next containment environment. In this way, it continues to the global execution environment; The variable object of the global execution environment is always the last object in the scope chain.
Identifier parsing is the process of searching identifiers one level at a scope chain. The search process always starts at the front end of the scope chain, and then goes backward backwards until the identifier is found (if an identifier is not found, this usually results in an error).
Take a look at the following sample code:
var color = "Blue";
function ChangeColor () {
if (color = = = "Blue") {
color = "Red";
} else {
color = "Blue";
}
}
ChangeColor ();
Alert ("Color is now" + color);
In this simple example, the scope chain of the function ChangeColor () contains two objects: its own variable object (which defines the arguments object) and the variable object of the global environment. The variable color can be accessed inside the function, because it can be found in this scope chain.
In addition, variables defined in a local scope can be used interchangeably with global variables in a local context, as shown in the following example:
var color = "Blue";
function ChangeColor () {
var anothercolor = "Red";
function Swapcolors () {
var tempcolor = Anothercolor;
Anothercolor = color;
color = Tempcolor;
Here you can access color, anothercolor, and Tempcolor
}
You can access color and anothercolor here, but you cannot access Tempcolor
Swapcolors ();
}
Only color can be accessed here
ChangeColor ();
The above code involves a total of 3 execution environments: The global environment, the local environment of the ChangeColor (), and the local environment of the swapcolors (). There is a variable color and a function changecolor () in the global environment. The local Environment of ChangeColor () has a variable named Anothercolor and a function named Swapcolors (), but it can also access the variable color in the global environment. The local Environment of Swapcolors () has a variable tempcolor, which can only be accessed in this environment.
Local environments, whether global or ChangeColor (), do not have access to tempcolor. However, within swapcolors (), you can access all the variables in the other two environments, because those two environments are its parent execution environment. Figure 4-3 graphically shows the scope chain of the preceding example.
The rectangle in Figure 4-3 represents a specific execution environment. The internal environment can access all external environments through the scope chain, but the external environment cannot access any variables and functions in the internal environment. the linkages between these environments are linear and sequential. Each environment can search up the scope chain to query for variables and function names, but any environment cannot enter another execution environment by searching down the scope chain. For Swapcolors () in this example, its scope chain contains 3 objects: A Variable object of swapcolors (), a variable object of ChangeColor (), and a global variable object. The local Environment of Swapcolors () begins by searching for variables and function names in its own variable object, and then searches for the upper-level scope chain if the search is not found. The scope chain of ChangeColor () contains only two objects: its own variable object and the global variable object. This means that it does not have access to the swapcolors () environment.
Note: Function parameters are also treated as variables, so their access rules are the same as other variables in the execution environment.
Extend the scope chain
Although there are only two types of execution environments-Global and local (functions)-There are other ways to extend the scope chain. This is because some statements can temporarily add a variable object to the front end of the scope chain, and the variable object is removed after the code executes. This behavior can occur in both cases. Specifically, the scope chain is extended when the execution stream enters any of the following statements:
? The catch block of the Try-catch statement;
? With statement.
Both statements add a variable object to the front of the scope chain. For the WITH statement, the specified object is added to the scope chain. For a catch statement, a new variable object is created that contains the declaration of the thrown error object. Let's look at an example below.
function BuildUrl () {
var qs = "Debug=true";
With (location) {
var url = href + qs;
}
return URL;
}
Here, the WITH statement receives the location object, so its variable object contains all the properties and methods of the Location object, and the variable object is added to the front end of the scope chain. A variable QS is defined in the BuildUrl () function. When the variable href is referenced in the WITH statement (actually referencing location.href), it can be found in the variable object of the current execution environment. When referring to the variable QS, the variable that is defined in BuildUrl () is referenced in the variable object in the function environment. As for the inside of the WITH statement, a variable named URL is defined, so the URL becomes part of the function execution environment, so it can be returned as the value of the function.
Note:
In IE8 and previous versions of JavaScript implementations, there is a standard inconsistency where the error object caught in a catch statement is added to the variable object of the execution environment, not the variable object of the Catch statement. In other words, the Error object can be accessed even outside the catch block. IE9 fixed the problem.
no block-level scopes
JavaScript without block-level scopes often leads to confusion in understanding. In other Class C languages, blocks of code that are enclosed by curly braces have their own scopes (in the case of ECMAScript, which is their own execution environment), and thus support the definition of variables based on conditions. For example, the following code does not get the imagined result in javascript:
if (true) {
var color = "Blue";
}
alert (color); "Blue"
Here is the variable color defined in an if statement. If it is in C, C + +, or Java, color is destroyed after the IF statement has finished executing. In JavaScript, however,variable declarations in the IF statement add variables to the current execution environment (in this case, the global environment). This difference is especially important to keep in mind when using a for statement, such as:
for (var i=0; i <; i++) {
DoSomething (i);
}
alert (i); 10
For a block-scoped language, the variables defined by the expression of the for-statement initialization variable will only exist in the context of the loop. For JavaScript, the variable I created by the for statement will still exist in the execution environment outside the loop even after the for loop execution has ended.
declaring variables
Variables declared with Var are automatically added to the closest environment. Within the function, the closest environment is the local environment of the function, and in the With statement, the closest environment is the function environment. If you initialize a variable without using the var declaration, the variable is automatically added to the global environment. is as follows:
function Add (NUM1, num2) {
var sum = num1 + num2;
return sum;
}
var result = Add (10, 20); 30
alert (sum); Because sum is not a valid variable, it causes an error
The function add () in the above code defines a local variable named sum that contains the result of the addition operation. Although the result value is returned from the function, the variable sum is not accessible outside the function. If you omit the var keyword from this example, when add () finishes executing, sum will also be accessible to:
function Add (NUM1, num2) {
sum = Num1 + num2;
return sum;
}
var result = Add (10, 20); 30
alert (sum); 30
The variable sum in this example does not use the var keyword when the assignment is initialized. Then, when Add () is finished, the variable sum added to the global environment will continue to exist, even if the function has been executed, and the subsequent code can still access it.
Query identifiers
When you reference an identifier in an environment for reading or writing, you must search to determine what the identifier actually represents. The search process starts at the front end of the scope chain and queries the identifiers that match the given name upwards. If the identifier is found in the local environment, the search process stops and the variable is ready. If the variable name is not found in the local environment, it continues to search up the scope chain. The search process goes back to the variable object of the global environment. If the identifier is not found in the global environment, it means that the variable is not yet declared.
With this example, you can understand the process of querying identifiers:
var color = "Blue";
function GetColor () {
return color;
}
Alert (GetColor ()); "Blue"
Calling the function GetColor () in this example will refer to the variable color. To determine the value of a variable color, a two-step search process begins. First, search for a variable object of GetColor () to find out if it contains an identifier named color. In the case where it is not found, the search continues to the next variable object (the variable object of the global environment) and the identifier named color is found there. Because a variable object that defines the variable is searched, the search process is declared closed. Figure 4-4 shows the search process visually.
In this search process, if there is a local definition of the variable, the search stops automatically and no longer enters another variable object. In other words, if there is an identifier with the same name in the local environment, the identifier in the parent environment is not used, as shown in the following example:
var color = "Blue";
function GetColor () {
var color = "Red";
return color;
}
Alert (GetColor ()); "Red"
The modified code declares a local variable named color in the GetColor () function. When the function is called, the variable is declared. When the second line of code in the function executes, it means that the value of the variable color must be found and returned. The search process begins with a local environment, and a variable named color is found here with the value "red". Because the variable is already found, the search stops, the return statement uses the local variable, and the function returns "Red". That is, any code that follows the declaration of a local variable color cannot access the global color variable without using Window.color.
NOTE: Variable queries are also not cost-free. Obviously, accessing a local variable is faster than accessing a global variable, because you do not need to search the scope chain up. The JavaScript engine is doing a good job of optimizing identifier queries, so the difference will be negligible in the future.
Garbage collection
JavaScript has an automatic garbage collection mechanism, which means that the execution environment is responsible for managing the memory used during code execution. In languages such as C and C + +, a basic task for developers is to manually track memory usage, which is a source of many problems. When writing JavaScript programs, developers no longer care about memory usage issues, the allocation of required memory, and the recycling of useless memory are fully automated management. The rationale for this garbage collection mechanism is simple: identify variables that are no longer in use, and then release the memory they occupy. To do this, the garbage collector periodically performs this operation at a fixed time interval (or a predetermined collection time in code execution).
Let's examine the normal life cycle of local variables in the function. Local variables exist only during the execution of a function. In this process, the local variables are allocated the appropriate space on the stack (or heap) memory to store their values. These variables are then used in the function until the function execution is finished. At this point, there is no need for local variables to exist, so they can be freed up for future use. In this case, it is easy to determine whether the variable is still necessary, but not all the circumstances are so easy to reach a conclusion. The garbage collector must keep track of which variables are useful and which ones are useless, marking variables that are no longer useful for future recall of their occupied memory. The policy used to identify the useless variable may vary by implementation, but there are typically two policies that are specific to the implementation in a browser.
Mark Clear
The most common method of garbage collection in JavaScript is tag Cleanup (mark-and-sweep). When a variable enters the environment (for example, when declaring a variable in a function), the variable is marked as "entering the environment." Logically, it is never possible to release the memory used by variables entering the environment, because they may be consumed as long as the execution flow enters the appropriate environment. When the variable leaves the environment, it is marked as "out of the environment."
You can use any way to tag variables. For example, you can record when a variable enters the environment by flipping a particular bit, or use a "go to environment" variable list and a "Leave environment" variable list to track which variable has changed. In the final analysis, how to tag variables is not important, but the key is to adopt any strategy.
The garbage collector will tag all variables stored in memory at run time (of course, you can use any markup method). It then removes the variables in the environment and the tags of the variables referenced by the variables in the environment. Variables that are tagged later will be considered as variables to be deleted because variables in the environment are inaccessible to those variables. Finally, the garbage collector completes the memory cleanup work, destroys those tagged values, and reclaims the memory space that they occupy. By 2008, the JavaScript implementations of IE, Firefox, Opera, Chrome, and Safari were all using a tag-clear garbage collection strategy (or similar strategy), except that the time interval for garbage collection was different.
reference count
Another less common garbage collection policy is called a reference count (reference counting). The reference count means that tracking records the number of times each value is referenced. when a variable is declared and a reference type value is assigned to the variable, the number of references to that value is 1.
If the same value is assigned to another variable, the number of references to the value is added by 1. Conversely, if a variable that contains a reference to this value has another value, the number of references to the value is reduced by 1. When the number of references to this value becomes 0 o'clock, it means that there is no way to access the value again, so that it can reclaim the memory space it occupies. That way, when the garbage collector runs again next time, it frees the memory used by those values that have a zero reference count.
Netscape Navigator 3.0 is the first browser to use a reference counting policy, but soon it encounters a serious problem: circular referencing. A circular reference refers to a pointer to object B that is contained in object A, and object B contains a reference to object A. Consider the following example:
function problem () {
var objecta = new Object ();
var OBJECTB = new Object ();
Objecta.someotherobject = OBJECTB;
Objectb.anotherobject = objecta;
}
In this example, Objecta and OBJECTB are referenced by each other by their respective properties, that is, both objects have a reference count of 2. In implementations that take a markup cleanup policy, these two objects leave the scope after the function executes, so this mutual reference is not an issue. However, in implementations with reference counting policies, objecta and OBJECTB will continue to exist when the functions have been executed, because their references will never be 0. If this function is repeated multiple calls, it will result in a large amount of memory not being recycled. To do this, Netscape discarded the reference counting method in Navigator 4.0, instead of using tag cleanup to implement its garbage collection mechanism. However, the trouble with reference counting does not end there.
We know that some of the objects in IE are not native JavaScript objects. For example, the objects in their BOM and Dom are implemented using C + + as COM (Component object model, Component object models) objects, and the garbage collection mechanism of COM objects is a reference counting strategy. Therefore, even though the JavaScript engine of IE is implemented using the tag purge policy, the COM objects that JavaScript accesses are still based on the reference counting policy. In other words, whenever a COM object is involved in IE, there is a circular reference problem. The following simple example shows a circular reference problem that is caused by using a COM object:
var element = document.getElementById ("some_element");
var myObject = new Object ();
Myobject.element = element;
Element.someobject = MyObject;
This example creates a circular reference between a DOM element and a native JavaScript object (myObject). Where the variable MyObject has a property named element pointing to the element object, and the variable element has a property called the Someobject callback MyObject. Since this circular reference exists, even if the DOM in the example is removed from the page, it will never be recycled.
To avoid circular reference problems like these, it is best to manually disconnect the native JavaScript object from the DOM element when you do not use them. For example, you can use the following code to eliminate the circular reference created by the previous example:
Myobject.element = null;
Element.someobject = null;
setting a variable to null means that the connection between the variable and the value it previously referred to is cut off. the next time the garbage collector runs, the values are removed and the memory they occupy is reclaimed.
To solve these problems, IE9 has converted both BOM and Dom objects into real JavaScript objects. This avoids the problems caused by the coexistence of two garbage collection algorithms, and eliminates the common memory leaks.
Performance issues
The garbage collector runs periodically, and if the amount of memory allocated to a variable is significant, the amount of recycling is considerable. In this case, determining the time interval for garbage collection is a very important issue. When it comes to how often the garbage collector runs, it makes people think of IE's notorious performance issues. IE's garbage collector runs according to the amount of memory allocated, specifically 256 variables, 4,096 objects (or array) literals and arrays of elements (slots), or 64KB strings. If any of these thresholds are reached, the garbage collector runs. The problem with this implementation is that if a script contains so many variables, it is likely that the script will retain so many variables throughout its life cycle. In this way, the garbage collector has to run frequently. As a result, the severity problem caused by this has prompted IE7 to rewrite its garbage collection routines.
With the release of IE7, the garbage collection routines of its JavaScript engine have changed the way it works: variable allocations that trigger garbage collection, literals, and (or) critical values of array elements are adjusted for dynamic remediation. The critical values in the IE7 are equal to IE6 at the initial time. If the garbage collection routine reclaims less than 15% of the memory allocated, the thresholds for variables, literals, and/or array elements are doubled. If the routine reclaims 85% of the memory allocations, resets the various thresholds back to the default values. This seemingly simple adjustment greatly improves the performance of IE when running pages that contain a lot of JavaScript.
Note:
In fact, the garbage collection process can be triggered in some browsers, but we do not recommend that the reader do so. In IE, call window. The CollectGarbage () method performs garbage collection immediately. In Opera 7 and later, calling Window.opera.collect () also initiates a garbage collection routine.
managing Memory
You write programs in a language that has a garbage collection mechanism, and developers generally don't have to worry about memory management issues. However, the problems that JavaScript faces in memory management and garbage collection are a bit different. One of the main problems is that the amount of available memory allocated to a Web browser is typically less than that allocated to a desktop application. This is done primarily for security reasons, in order to prevent the Web page running JavaScript from exhausting all system memory and causing the system to crash. Memory throttling issues not only affect allocating memory to variables, but also affect the call stack and the number of statements that can be executed concurrently in one thread.
Therefore, make sure that the least amount of memory is used to get better performance for the page. The best way to optimize memory consumption is to save only the necessary data for the code in execution. Once the data is no longer useful, it is best to release its reference by setting its value to null-this is called de-referencing (dereferencing). This practice applies to the properties of most global variables and global objects. Local variables are automatically dereferenced when they leave the execution environment , as shown in the following example:
function Createperson (name) {
var Localperson = new Object ();
Localperson.name = name;
return Localperson;
}
var Globalperson = Createperson ("Nicholas");
Manual de-globalperson reference
Globalperson = null;
In this example, the variable Globalperson gets the value returned by the Createperson () function. Inside the Createperson () function, we created an object and assigned it to the local variable Localperson, and then added a property named name to the object. Finally, when this function is called, Localperson is returned in the form of a function value and assigned to the global variable Globalperson. Since Localperson has left its execution environment after the Createperson () function has been executed, we do not need to explicitly de-reference it. But for global variable Globalperson, we need to manually dereference it when we don't use it, which is exactly what the last line of code in the above example does.
However, releasing a reference to a value does not mean that the memory consumed by the value is automatically reclaimed. The real effect of dereferencing is to leave the value out of the execution environment so that it can be reclaimed the next time the garbage collector runs.
JavaScript Advanced Program Design reading notes---execution environment and scope