Advanced variable hoisting and the execution context object

Một phần của tài liệu single page web applications (Trang 54 - 59)

All the concepts we’ve covered so far are generally regarded as necessary to know in order to be successful as a JavaScript developer. Let’s take it a step beyond that and see what happens under the hood: you’ll be one of the few who understands how JavaScript really works. We’ll start with one of JavaScript’s more “magical” features:

variable and function hoisting.

2.3.1 Hoisting

Like all forms of magic, the trick becomes almost disappointing when the secret is revealed. The secret is that the JavaScript engine makes two passes over code when it comes into scope. On the first pass it initializes variables and on the second pass it exe- cutes code. I know, simple; I have no idea why it’s not usually described in these terms.

Let’s go into more detail on what the JavaScript engine does during the first pass because it has some interesting repercussions.

Outputs “prisoner assigned”

regular_joe is defined in the global scope

‘Regular Joe’ global variable regular_joe is logged inside of the prison function

Outputs “undefined”. The declaration of regular_joe is hoisted to the top of the function and that hoisted declaration is checked before looking for regular_joe in the global scope.

On the first pass, the JavaScript engine walks through the code and does three things:

1 Declares and initializes the function arguments.

2 Declares the local variables, including anonymous functions assigned to a local variable, but doesn’t initialize them.

3 Declares and initializes functions.

function myFunction( arg1, arg2 ) { var local_var = 'foo',

a_function = function () { console.log( 'a function' );

};

function inner () { console.log('inner');

} }

myFunction( 1,2 );

Values are not assigned to local variables during the first pass because code may have to be executed to determine the value and the first pass doesn’t execute code. Values are assigned to the arguments, because any code needed to determine an argument’s value was run before the argument was passed into the function.

We can demonstrate that the values of the arguments are set in the first pass by comparing them to the code demonstrating function hoisting from the end of the last section.

var regular_joe = 'regular_joe is assigned';

function prison () {

console.log(regular_joe);

var regular_joe;

}

prison();

regular_joe is undefined before it’s declared in the prison function, but if regular_joe is also passed in as an argument, it has a value before it’s declared.

var regular_joe = 'regular_joe is assigned';

function prison ( regular_joe ) { console.log(regular_joe);

var regular_joe;

console.log(regular_joe);

}

prison( 'the regular_joe argument' );

Listing 2.2 The first pass

Listing 2.3 Variables are undefined before they are declared

Listing 2.4 Variables have a value before they’re declared

B Declares and initializes the function arguments C Declares the local

variables, including anonymous functions assigned to a local variable, but doesn’t

initialize them. D Declares and

initializes functions

Outputs “undefined”. The declaration of regular_joe is hoisted to the top of the function and that hoisted declaration is checked before looking for regular_joe in the global scope.

Outputs “the regular_joe argument”. Arguments are assigned a value during the first pass. Without understanding the two passes the JavaScript engine makes, it looks like the regular_joe argument would be overwritten by the regular_joe local variable declaration being hoisted.

Outputs “the regular_joe argument”. Surprise!

Since regular_joe was assigned a value from the argument, it’s not overwritten with undefined when it’s declared. This declaration is redundant.

31 Advanced variable hoisting and the execution context object

If your head is spinning from this, that’s okay. Though we’ve explained that the JavaScript engine makes two passes over a function when it executes, and that on the first pass it stores the variables, we haven’t seen how it stores the variables. Seeing how the JavaScript engine stores variables will hopefully clear up any remaining confusion.

The JavaScript engine stores variables as attributes on an object referred to as the exe- cution context object.

2.3.2 Execution context and the execution context object

Every time a function is invoked, there’s a new execution context. The execution con- text is a concept, the concept of a running function—it’s not an object. It’s like think- ing of an athlete in a running context or a jumping context. We could say a running athlete instead of an athlete in a running context, just like we could say a running function, but that’s not how the jargon works. We say the execution context.

The execution context is made up of everything that happens while that function is executing. This is separate from a function declaration, because the function decla- ration describes what will happen when the function is executed. The execution con- text is the execution of the function.

All of the variables and functions defined in a function are considered part of the execution context. The execution context is a part of what developers are referring to when they talk about a function’s scope. A variable is considered “in scope” if it’s acces- sible in the current execution context, which is another way of saying the variable is in scope if it’s accessible while the function is running.

The variables and functions that are part of the execution context are stored on the execution context object, an implementation of the ECMA standard for the execution context. The execution context object is an object in the JavaScript engine, and not a variable directly accessible in JavaScript. It’s easy enough to access indirectly, as every time you use a variable you’re accessing an attribute of an execution context object.

Earlier, we discussed how the JavaScript engine makes two passes over an execu- tion context, declaring and initializing the variables, but where does it store these vari- ables? The JavaScript engine declares and initializes the variables as attributes on the execution context object. For an example of how the variables are stored, take a look at table 2.1.

It’s possible that you’ve never heard of the execution context object. It’s not some- thing commonly discussed in the web developer community, probably because the

Table 2.1 Execution context object

Code Execution context object

var example_variable = "example", another_example = "another";

{

example_variable: "example", another_example: "another"

};

execution context object is buried in the implementation of JavaScript and not directly accessible during development.

Understanding the execution context object will be key to understanding the rest of the chapter, so let’s walk through the lifecycle of an execution context object and the JavaScript code that creates it.

outer(1);

function outer( arg ) { var local_var = 'foo';

function inner () { console.log('inner');

}

inner();

}

Now that the arguments and functions have been declared and assigned, and the local variables have been declared, a second pass is made, executing the JavaScript and assigning the definitions of the local variables.

outer(1);

function outer( arg ) { var local_var = 'foo';

function inner () { console.log('inner');

}

inner();

}

Listing 2.5 Execution context object—first pass

Listing 2.6 Execution context object—second pass {aaaaaaaaaaaa

}aaaaaaaaaaaa An empty execution context object is created when outer is invoked.

{

arg : 1 }

Arguments are declared and assigned

{aaaaaaaaaaaaaaaaaaaa a aarg : 1,aa

aa local_var: undefined }aaaaaaaaaaaaaaaaaaaa Local variables are declared but not assigned

{

arg : 1,

local_var : undefined, inner : function () { console.log('inner');

} }

Functions are declared and assigned, but not executed.

Nothing happens; code isn’t executed on the first pass

{

arg: 1,aa

local_var: undefined,aa inner: function () {aa console.log('inner');aaaa }aa

}

{

arg: 1,

local_var: 'foo', inner: function () { console.log('inner');

} };

Local variables are assigned as code is executed.

{

arg: 1,

local_var: 'foo', inner: function () { console.log('inner');

} }

The attributes representing variables on this execution context object remain the same, but when function inner is invoked, a new execution context object is created inside of this one.

33 Advanced variable hoisting and the execution context object

This can go many layers deep, as functions can be invoked inside of an execution con- text. Invoking a function inside of an execution context creates a new execution con- text nested inside the existing execution context. Okay, head spinning again; it’s picture time. See figure 2.3.

1 Everything inside of the <script> tag is in the global execution context.

2 Invoking first_function creates a new execution context inside the global execution context. When first_function runs, it has access to the variables of the execution context in which it was invoked. In this case, first_function has access to the variables defined in the global execution context and the local variables defined in first_function. These variables are said to be in scope.

3 Invoking second_function creates a new execution context inside of the first_function execution context. second_function has access to the vari- ables from the first_function execution context because it was invoked inside of it. second_function also has access to variables in the global execution con- text and the local variables defined in second_function. These variables are said to be in scope.

4 second_function is invoked again, this time in the global execution context.

This second_function doesn’t have access to the variables in the first_function execution context because this time second_function wasn’t invoked in the first_function execution context. Said another way, this time when second_function is called, it doesn’t have access to the variables defined in first_function because it wasn’t called inside of first_function.

This second_function execution context doesn’t have access to the variables from the previous time second_function was invoked either, because they occur in different execution contexts. Said another way, when you call a function, you

<script>

var global_var;

first_function();

function first_function() { var first_var;

second_function();

}

function second_function() { var second_var;

}

second_function();

</script>

second_function() execution context

second_function() execution context first_function() execution context Global execution context

B C

D

E

Figure 2.3 Calling a function creates an execution context.

don’t have access to local variables created the last time the function was called, and the next time you call this function you won’t have access to the local variables from this function call. These inaccessible variables are said to be out of scope.

The order in which the JavaScript engine looks in the execution context objects to access variables that are “in scope” is referred to as the scope chain, which, together with the prototype chain, describes the order in which JavaScript accesses variables and their attributes. We’ll discuss these concepts in the next few sections.

Một phần của tài liệu single page web applications (Trang 54 - 59)

Tải bản đầy đủ (PDF)

(433 trang)