4.2 Execution environment and scope [JavaScript Advanced Programming Third Edition]

The execution context (sometimes called "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, which determines their respective behavior. Each execution environment has a variable object associated with it, and all variables and functions defined in the environment are stored in this object. Although the code we wrote does not have access to this object, the parser uses it behind the scenes while processing the data.

The global execution environment is the outermost execution environment. Depending on the host environment in which the ECMAScript implementation is implemented, the objects representing the execution environment are also different. In a Web browser, the global execution environment is considered the window object (discussed in detail in Chapter 7), 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, along with all variables and function definitions stored in it (the global execution environment is not until the application exits - such as closing a web page or browser - will be destroyed).

Each function has its own execution environment. When the flow of execution enters a function, the function's environment is pushed onto an environment stack. After the function executes, the stack pops its context, returning control to the previous execution context. The flow of execution in an ECMAScript program is controlled by this convenient mechanism.

When code is executed in an environment, a scope chain of variable objects is created. The purpose of the scope chain is to guarantee ordered access to all variables and functions that the execution environment has access to. The front end of the scope chain is always the variable object of the environment where the currently executed code is located. If the environment is a function, use its activation object as a 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 is from the containing (outer) environment, and the next variable object is from the next containing 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 resolution is the process of searching for identifiers one level at a time along the scope chain. The search process always starts at the front of the scope chain and works backwards step by step until an identifier is found (if the identifier is not found, an error usually occurs).

See the sample code below:

var color = "blue";
function changeColor() {
	if (color === "blue") {
		color = "red";
	} else {
		color = "blue";
	}
}
changeColor();
alert("Color is now " + color);

run it

In this simple example, the scope chain of the function changeColor() contains two objects: its own variables object (in which the arguments object is defined) and the variables object of the global environment. The variable color can be accessed inside the function because it can be found in this scope chain.

Furthermore, variables defined in the local scope can be used interchangeably with global variables in the local environment, 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
	}
	// Here you can access color and anotherColor, but not tempColor
	swapColors();
}
// Only color can be accessed here
changeColor();

The above code involves three execution environments: the global environment, the local environment of changeColor() and the local environment of swapColors(). There is a variable color and a function changeColor() in the global environment. changeColor() has a variable called anotherColor and a function called swapColors() in the local environment, but it also has access to the variable color in the global environment. There is a variable tempColor in the local environment of swapColors(), which can only be accessed in this environment.

Neither the global environment nor the local environment of changeColor() have access to tempColor. However, inside swapColors() all variables in the other two environments are accessible, since those two environments are its parent execution environments. Figure 4-3 visualizes the scope chain of the previous example.



 

The rectangles in Figure 4-3 represent specific execution environments. Among them, 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 connections between these environments are linear and sequential. Every environment can search up the scope chain for variable and function names; but no environment can search down the scope chain to enter another execution environment. For swapColors() in this example, its scope chain contains three objects: the variable object of swapColors(), the variable object of changeColor(), and the global variable object. The local environment of swapColors() will first search for variable and function names in its own variable object at the beginning, and then search the upper-level scope chain if it cannot be found. The scope chain of changeColor() contains only two objects: its own variable object and the global variable object. That is, it cannot access the context of swapColors().

 

Function parameters are also treated as variables, so their access rules are the same as for other variables in the execution environment.

 

 

4.2.1 Extending the scope chain

Although there are only two types of execution environments in total - global and local (functions), there are other ways to extend the scope chain.

This is said because some statements can temporarily add a variable object to the front end of the scope chain, which will be removed after the code is executed. This phenomenon occurs in two situations. Specifically, the scope chain is lengthened when the flow of execution enters any of the following statements:

  • the catch block of a try-catch statement;
  • with statement.

Both of these statements add a variable object to the front of the scope chain. For the with statement, adds the specified object to the scope chain. For the catch statement, a new variable object is created that contains the declaration of the thrown error object.

See an example below.

function buildUrl() {
	var qs = "?debug=true";
	with(location) {
		var url = href + qs;
	}
	return url;
}

run it

Here, the with statement receives the location object, so its variable object contains all the properties and methods of the location object, and this 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 (the actual reference is location.href), it can be found in the variable object of the current execution environment. When the variable qs is referenced, the reference is to the variable defined in buildUrl(), which is located in the variable object of 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.

 

In IE8 and earlier JavaScript implementations, there was a standard inconsistency where error objects caught in a catch statement were added to the variable object of the execution environment, not the variable object of the catch statement. In other words, the error object is accessible even outside the catch block. IE9 fixes this problem.

 

4.2.2 No block scope

JavaScript's lack of block scope often leads to confusion. In other C-like languages, blocks of code enclosed by curly braces have their own scope (or their own execution environment, in ECMAScript parlance), and thus support conditional definition of variables. For example, the following code in JavaScript does not give the expected result:

if (true) {
    var color = "blue";
}
alert(color); //"blue"

Here the variable color is defined in an if statement. In C, C++ or Java, the color will be destroyed after the if statement is executed. But in JavaScript, a variable declaration in an if statement adds the variable to the current execution environment (in this case, the global environment). This difference is especially important to keep in mind when using for statements, such as:

for (var i=0; i < 10; i++){
    doSomething(i);
}
alert(i); //10

For languages ​​with block-level scope, the variable defined by the expression that initializes the variable of the for statement exists only 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 execution of the for loop ends.

1. Declare variables

Variables declared with var are automatically added to the closest environment. Inside a function, the closest environment is the function's local environment; in a with statement, the closest environment is the function environment. If a variable is initialized without a var declaration, the variable is automatically added to the global environment. As follows:

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
var result = add(10, 20); //30
alert(sum); // Causes an error because sum is not a valid variable

run it

The function add() in the above code defines a local variable called 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 in this example, then when add() finishes, sum will also have access to:

function add(num1, num2) {
    sum = num1 + num2;
    return sum;
}
var result = add(10, 20); //30
alert(sum); //30

run it

The variable sum in this example was initialized without using the var keyword. Therefore, after calling add(), the variable sum added to the global environment will continue to exist; even after the function has been executed, subsequent code can still access it.

In the process of writing JavaScript code, it is a common mistake to initialize a variable without declaring it, because it can lead to surprises. We recommend that you declare variables before initializing them, so that similar problems can be avoided. In strict mode, initializing an undeclared variable results in an error.

2. Query Identifier

When an identifier is referenced for reading or writing in an environment, a search must be performed to determine what the identifier actually represents. The search process starts at the front end of the scope chain and works up the level for identifiers that match a given name. 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, the search continues up the scope chain. The search process will go all the way back to the variable object of the global environment. If this identifier is also not found in the global environment, it means that the variable has not been declared.

The process of querying an identifier can be understood with the following example:

var color = "blue";
    function getColor(){
    return color;
}
alert(getColor()); //"blue"

run it

The variable color is referenced when calling the function getColor() in this example. To determine the value of the variable color, a two-step search process begins. First, the variable object of getColor() is searched for an identifier named color.

In the case of none found, the search continues to the next variable object (the variable object of the global environment), where the identifier named color is found. Because the variable object that defines this variable has been searched, the search process has ended. Figure 4-4 visualizes the above search process.



 

During this search process, if there is a local variable definition, the search will automatically stop and no longer enter another variable object. In other words, if an identifier with the same name exists in the local environment, the identifier in the parent environment will not be used, as in the following example:

var color = "blue";
function getColor(){
    var color = "red";
    return color;
}
alert(getColor()); //"red"

run it

The modified code declares a local variable named color in the getColor() function. The variable is declared when the function is called. And 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 first starts in the local environment, and here it finds a variable named color with the value "red". Since the variable has been found, the search stops immediately, the return statement uses the local variable, and returns "red" for the function. That is, any code that follows the declaration of the local variable color cannot access the global color variable without using window.color.

Variable lookups are not without cost. Obviously, accessing local variables is faster than accessing global variables because there is no need to search up the scope chain. JavaScript engines do a good job of optimizing identifier queries, so this difference will probably be negligible in the future.

 

wrote
Download offline version: http://www.shouce.ren/api/view/a/15255

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326707648&siteId=291194637