How does JavaScript code execute?
When an interviewer gives you a piece of code and asks you to tell them the results, it’s our brain’s job to run the code. So understanding the rules of how code works is fundamental to learning the basics of a language. Only a good command of the code running rules can reduce the usual code bugs.
Let’s take a look at a picture to give us a general idea of these new concepts.
Obviously, there is a very important concept called “execution context,” and we’ll start with it.
Execution context
An execution context is an abstraction of the context in which the current JavaScript code is being parsed and executed. Any code that runs in JavaScript runs in an execution context
Let’s start with a piece of code:
var name = "jack";
var age = 18;
function foo(i){
var bar = "bar";
function f1(){
console.log('f1');
}
f1(); // 'f1'
console.log(i,name,age); // 10 "jack" 18
}
foo(10);
Copy the code
Process analysis:
Initialization has an empty stack of execution environments
executionContextStack = []
Copy the code
A globalExecutionContext is generated during initialization (there is only one global context).
globalExecutionContext = {
VO: {
name: undefined,
age: undefined,
foo: function foo(i){...}
},
scopeChain: [],
this: { ... }
}
Copy the code
The value of the execution environment stack is:
executionContextStack = [globalExecutionContext]
Copy the code
3. When executing global code
globalExecutionContext = {
AO: {
name: "jack",
age: 18,
foo: function foo(i){...}
},
scopeChain: [AO],
this: { ... }
}
Copy the code
4, the code executes to foo(I){… }, and find code foo(10) that calls the function; (Before foo(10) is executed)
Start by creating a context object
- Create arguments object, check the arguments of the current context, and establish the properties and property values under that object
- A function declaration that scans the context, pointing to the function’s address in memory (if the function name already exists in VO, the corresponding property value will be overwritten by a new reference)
- Variable declaration of the scan context, initialized to undefined (if the variable name already exists in VO, the scan will be skipped)
- Initialize the scope chain
- Identify the reference to this in the context
fooExecutionContext = { VO: { arguments: { 0: 10, length: 1 }, i: 10, f1: pointer to function f1(), bar: undefined }, scopeChain: [globalExecutionContext.AO], this: { ... }}Copy the code
The value of the execution environment stack is:
executionContextStack = [fooExecutionContext,globalExecutionContext]
Copy the code
When the code executes foo(10)
- perform
foo
The code in the function body assigns a value to a variable in VO - Simultaneous scanning
f1
Function and the function has a corresponding callf1();
The F1 function context is created
Step 1: Assign a value to a variable in VO
fooExecutionContext = { AO: { arguments: { 0: 10, length: 1 }, i: 10, f1: pointer to function f1(), bar: "bar" }, scopeChain: [AO,globalExecutionContext.AO], this: { ... }}Copy the code
Foo is searched for variables name and age in the scopeChain chain until they are found, and an error is thrown if none is found.
Step 2: Create the f1 function context
f1ExecutionContext = { variableObject: { arguments: { length: 0 } }, scopeChain: [fooExecutionContext.AO,globalExecutionContext.AO], this: { ... }}Copy the code
The value of the execution environment stack is:
executionContextStack = [f1ExecutionContext,fooExecutionContext,globalExecutionContext]
Copy the code
5. Execute the f1 function call
f1ExecutionContext = { AO: { arguments: { length: 0 } }, scopeChain: [AO,fooExecutionContext.AO,globalExecutionContext.AO], this: { ... }}Copy the code
6. After the execution of f1 function, the f1 function context pops up from the execution context stack
The value of the execution environment stack is:
executionContextStack = [fooExecutionContext,globalExecutionContext]
Copy the code
When function foo completes execution, the context of function foo is popped from the execution context stack
The value of the execution environment stack is:
executionContextStack = [globalExecutionContext]
Copy the code
ExecutionContextStack = []
Summary:
- When the function is called, the execution context is created for it and pushed to the top of the execution environment stack. When the execution is completed, the execution context is destroyed and VO is also destroyed
- Execution context is divided into creation phase and code execution phase
- The initial value of the variable is undefined during the creation phase and is assigned to the variable during the execution phase
- Function declarations precede variable declarations
[Special note] The execution context objects listed above are the ES3 specification, why use so early specification, the reason is simple and easy to understand. We only understand the implementation process, not every detail of the implementation.
The execution context, in ES3, consists of three parts
- A scope chain is also called a scope chain.
- Variable object: a variable object used to store variables.
- This value: This value.
In ES5, we improved the naming by changing the first three parts of the execution context to look like this.
- Lexical environment: lexical environment, used when fetching variables.
- Variable environment: used when declaring variables.
- This value: This value.
In ES2018, the execution context again looks like this, with the this value subsumed by instantiation
- Environment, but added a lot of content.
- Lexical environment: lexical environment, used when fetching variables or this values.
- Variable environment: used when declaring variables.
- Code Evaluation State: Used to restore the code execution location.
- Function: used when the task being executed is a Function.
- ScriptOrModule: used when executing a ScriptOrModule, indicating the code being executed.
- Realm: Base libraries and built-in object instances to use.
- Generator: Only the Generator context has this property, indicating the current Generator.
Lexical scope and dynamic scope
Scoped chains are a relatively easy concept to understand, but to fully understand scoped chains, you must understand lexical and dynamic scopes.
JavaScript uses lexical scope, and the scope of a function is determined at the time the function is defined.
The opposite of lexical scope is dynamic scope, where the scope of a function is determined at the time the function is called.
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
Copy the code
Lexical scope:
Foo (value = 1); foo (value = 1); foo (value = 1); foo (value = 1)
Dynamic scope:
When we execute foo, we still look inside foo to see if there is a local variable value. If not, the value variable is looked up from within the scope of the calling function, that is, the bar function, so the result prints 2.
Because JavaScript is lexical scoped (also known as static scoping), the output is 1.
Here’s another example from the Definitive JavaScript Guide:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
Copy the code
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
Copy the code
Both pieces of code will print: Local scope. The reason is simple, too, because JavaScript is lexical, and functions are scoped based on where they are created.
Analysis from the perspective of the execution environment stack:
The first:
ExecutionContextStack. Push (' checkscope context); ExecutionContextStack. Push (' f context); ECStack.pop(); // pop 'f context 'ecstack.pop (); // popup 'checkScope context'Copy the code
The second:
ExecutionContextStack. Push (' checkscope context); ECStack.pop(); / / the pop-up 'checkscope context executionContextStack. Push (' f context); ECStack.pop(); // pop 'f context 'Copy the code
The results are consistent, but the computer processes are different.
this
JavaScript’s this always points to an object, which object is dynamically bound at runtime based on the execution environment of the function, not the environment in which the function was declared.
Except for the infrequent cases of with and eval, in practical application, the reference of this can be roughly divided into the following four types.
- Method invocation as an object.
- Called as a normal function.
- Constructor call.
- Call or function.prototype. apply.
Method invocation as an object
var obj = { a: 1, getA: function(){ alert ( this === obj ); // Output: true alert (this.a); // Output: 1}}; obj.getA();Copy the code
When a function is called as a method of an object, this refers to that object
Called as a normal function
window.name = 'globalName'; var getName = function(){ return this.name; }; console.log( getName() ); // Output: globalNameCopy the code
When a function is not called as a property of an object, in the normal function way, this always refers to the global object. In browser JavaScript, this global object is the Window object.
This is bound to window by default when nested functions are called independently
< HTML > <body> <div id="div1"> I am a div</div> </body> <script> window.id = 'window'; document.getElementById( 'div1' ).onclick = function(){ alert ( this.id ); 'div1' var callback = function(){alert (this.id); // Output: 'window'} callback(); }; </script> </html>Copy the code
Solution: Use a variable to save this
document.getElementById( 'div1' ).onclick = function(){ var that = this; Var callback = function(){alert (thate.id); 'div1'} callback(); };Copy the code
In strict mode this does not refer to global objects
function func(){ "use strict" alert ( this ); // undefined} func();Copy the code
Constructor call
When a function is called with the new operator, the function always returns an object. Normally, this in the constructor refers to the object returned
var MyClass = function(){ this.name = 'sven'; }; var obj = new MyClass(); alert ( obj.name ); // Output: svenCopy the code
If the constructor explicitly returns an object of type Object, then the result of the operation will return that object instead of this as we expected
var MyClass = function(){ this.name = 'sven'; Return {// explicitly returns an object name: 'Anne'}}; var obj = new MyClass(); alert ( obj.name ); // Output: AnneCopy the code
Call and apply calls
var obj1 = { name: 'sven', getName: function(){ return this.name; }}; var obj2 = { name: 'anne' }; console.log( obj1.getName() ); // Output: sven console.log(obj1.getName.call(obj2)); // Output: AnneCopy the code
Call and Apply can dynamically change the this of the function passed in.
So that’s the summary of this. From the perspective of the ExecutionContext, this is determined when the compiler creates the ExecutionContext when it scans the function call and stores it in the context object.