Execution context and variable object
In JavaScript, execution context and variable object are core concepts for understanding how code operates. They determine the accessibility of variables and functions, the formation of the scope chain, and the order of code execution.
The Concept of Execution Context
An execution context is the environment in which JavaScript code is executed, containing information such as variables, function declarations, and the scope chain. A new execution context is created whenever a function is called. There are three types of execution contexts:
- Global Execution Context: Created when the code runs for the first time; there is only one global context.
- Function Execution Context: Created each time a function is called.
- Eval Execution Context: Created when code inside the
eval
function is executed (rarely used).
function foo() {
console.log('Function execution context');
}
foo(); // A new execution context is created when the function is called
Lifecycle of an Execution Context
The lifecycle of an execution context consists of two phases:
-
Creation Phase:
- Create the Variable Object (VO)
- Establish the scope chain
- Determine the
this
binding
-
Execution Phase:
- Assign values to variables
- Reference functions
- Execute other code
function bar(a) {
var b = 2;
function c() {}
var d = function() {};
}
bar(1);
During the creation phase of the bar
function:
- The parameter
a
is added to the variable object - The function declaration
c
is hoisted - Variables
b
andd
are declared (but not yet assigned)
Variable Object (VO) and Activation Object (AO)
The variable object is the data scope associated with the execution context, storing variables and function declarations defined in the context.
- Variable Object in Global Context: This is the global object (
window
in browsers) - Variable Object in Function Context: Called the Activation Object (AO), which includes variables, functions, and
arguments
function example(x, y) {
var z = 30;
function inner() {}
console.log(arguments); // Arguments object
}
example(10, 20);
In this example, the activation object contains:
arguments
: {0:10, 1:20, length:2}x
: 10y
: 20z
: undefined (during the creation phase)inner
: Reference to the function
The Essence of Hoisting
Variable hoisting is essentially the result of how variable and function declarations are processed during the creation phase of the execution context:
console.log(a); // undefined
var a = 1;
console.log(b()); // "hello"
function b() {
return 'hello';
}
The actual execution order is equivalent to:
function b() {
return 'hello';
}
var a;
console.log(a);
a = 1;
console.log(b());
Formation of the Scope Chain
When looking up a variable, the search starts from the current context's variable object and moves up the scope chain:
var globalVar = 'global';
function outer() {
var outerVar = 'outer';
function inner() {
var innerVar = 'inner';
console.log(innerVar); // 'inner'
console.log(outerVar); // 'outer'
console.log(globalVar); // 'global'
}
inner();
}
outer();
Scope chain:
inner
's AOouter
's AO- Global VO
let/const and the Variable Object
Variables declared with let
and const
have block scope and are not added to the variable object like var
:
function blockExample() {
console.log(a); // ReferenceError
let a = 1;
{
let b = 2;
console.log(b); // 2
}
console.log(b); // ReferenceError
}
Closures and the Variable Object
Closures can access the variable object of an outer function:
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
get: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.get()); // 1
Even after createCounter
finishes executing, its variable object is still referenced by the closure and is not destroyed.
Execution Context Stack
The JavaScript engine uses an execution context stack (call stack) to manage execution contexts:
function first() {
console.log('Entering first');
second();
console.log('Leaving first');
}
function second() {
console.log('Entering second');
third();
console.log('Leaving second');
}
function third() {
console.log('Entering third');
console.log('Leaving third');
}
first();
Changes in the execution stack:
- Global context is pushed onto the stack
first()
is pushed onto the stacksecond()
is pushed onto the stackthird()
is pushed onto the stackthird()
is popped off the stacksecond()
is popped off the stackfirst()
is popped off the stack
Special Behavior of the Variable Object
In some cases, the variable object behaves in noteworthy ways:
(function() {
console.log(typeof foo); // "function"
console.log(typeof bar); // "undefined"
var foo = 'hello';
function foo() {}
var bar = function() {};
console.log(typeof foo); // "string"
console.log(typeof bar); // "function"
})();
This is because:
- Function declarations take precedence over variable declarations
- Variable assignments occur during the execution phase
Changes in Strict Mode
Strict mode affects the variable object in specific ways:
'use strict';
function strictExample() {
undeclaredVar = 1; // ReferenceError
console.log(arguments.callee); // TypeError
}
strictExample();
In strict mode:
- Assigning to undeclared variables throws an error
- The
arguments
object becomes non-modifiable
Variable Object and Memory Management
Understanding the variable object is important for memory management:
function createHugeArray() {
var arr = new Array(1000000).fill('data');
return function() {
console.log(arr[0]);
};
}
var leak = createHugeArray();
// Even after the function finishes executing, `arr` is still referenced by the closure and cannot be garbage collected
Dynamic Scope and the Variable Object
Although JavaScript has lexical scope, this
provides dynamic scope-like behavior:
var obj = {
prop: 'value',
method: function() {
console.log(this.prop);
}
};
obj.method(); // "value"
var fn = obj.method;
fn(); // undefined (in non-strict mode)
Extensions to the Variable Object
ES6 introduced features that extend the concept of the variable object:
function spreadExample(...args) {
let [first, ...rest] = args;
const PI = 3.14;
class Inner {}
console.log(args.length);
}
spreadExample(1, 2, 3);
These new syntactic structures all influence the composition of the variable object.
Execution Context in Debugging
Developer tools allow observation of execution contexts:
function debugExample() {
debugger;
var a = 1;
let b = 2;
}
debugExample();
In the debugger, you can see:
- Variables in the current scope
- Variables in closures
- Global variables
Execution Context and the Event Loop
Execution contexts are closely related to the event loop mechanism:
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('End');
Output order:
- "Start"
- "End"
- "Promise"
- "Timeout"
This is because different tasks create different execution contexts and enter different queues.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn