1. 什么是执行上下文?
执行上下文(Execution Context)是JavaScript中的一个核心概念。它代表了代码被执行时的环境和条件。换句话说,执行上下文是一个抽象的概念,涵盖了变量、函数和它们的作用域。
每当我们运行JavaScript代码时,都会创建一个执行上下文。这个上下文可以是全局的,也可以是与特定函数相关的。理解执行上下文对于我们正确理解代码的运行机制至关重要。
2. 执行上下文的种类
在JavaScript中,存在不同类型的执行上下文,每种都在特定的情况下创建。让我们一一了解这些执行上下文的类型及其特点。
2.1 全局执行上下文
全局执行上下文是代码中最外层的上下文,它在整个脚本执行期间都存在。在浏览器环境中,全局执行上下文通常与window
对象相关联。
javascript
// 示例:全局执行上下文
var globalVariable = "I'm global!";
function globalFunction() {
// 该函数属于全局执行上下文
console.log(globalVariable);
}
globalFunction(); // 输出:"I'm global!"
全局执行上下文包含了我们代码中定义的所有全局变量和函数。通过var
关键字声明的全局变量会成为window
对象的属性。
2.2 函数执行上下文
每次调用函数时,都会创建一个新的函数执行上下文。函数执行上下文与全局执行上下文类似,但它仅在函数调用期间存在。
javascript
// 示例:函数执行上下文
function greet(name) {
var greeting = "Hello, " + name + "!";
console.log(greeting);
}
greet("Alice"); // 输出:"Hello, Alice!"
在上述示例中,每当我们调用greet
函数时,都会创建一个新的函数执行上下文,其中包含了参数name
和局部变量greeting
。
2.3 块级执行上下文
ES6引入了let
和const
关键字,它们允许我们创建块级作用域。块级执行上下文与函数执行上下文类似,但它们存在于用花括号 {}
定义的块中。
javascript
// 示例:块级执行上下文
if (true) {
let blockVariable = "I'm in a block!";
console.log(blockVariable);
}
// 下面这行代码会导致 ReferenceError,因为 blockVariable 不在此作用域中
console.log(blockVariable);
在这个示例中,blockVariable
仅在if
语句的块级作用域内存在,超出这个范围就无法访问它。
3. 执行上下文栈(调用栈)
执行上下文栈是JavaScript中一个重要的概念,它决定了代码执行时上下文的切换和管理。让我们深入了解这个栈是如何工作的,以及它是如何影响我们代码的执行流程的。
3.1 什么是执行上下文栈?
执行上下文栈是一个存储执行上下文的栈结构。在JavaScript中,每当代码执行流进入一个新的执行上下文(如函数调用),就会将该执行上下文推入栈的顶部。反之,当执行流离开一个上下文时,该上下文将被从栈顶弹出。
这种先进后出的栈结构保证了代码执行的顺序是有序的。最顶部的执行上下文始终是当前正在执行的上下文。
3.2 执行上下文栈的工作原理
为了更好地理解执行上下文栈的工作原理,让我们通过一个简单的例子来演示:
javascript
function outerFunction() {
let outerVar = 'I am in outer function';
function innerFunction() {
let innerVar = 'I am in inner function';
console.log(outerVar + ' and ' + innerVar);
}
innerFunction();
}
outerFunction();
在这个例子中,当outerFunction
被调用时,它的执行上下文将被推入执行上下文栈的顶部。接着,当innerFunction
被调用时,它的执行上下文将被推入栈的顶部,成为当前执行上下文。当innerFunction
执行完毕后,它的执行上下文将从栈顶弹出,控制权交还给outerFunction
的执行上下文。
最终,当所有代码执行完毕时,全局执行上下文将成为栈顶的执行上下文。这个栈的不断推入和弹出,就是执行上下文栈的工作原理。
3.3 上下文栈的影响
理解执行上下文栈对我们编写的代码有着深远的影响。首先,它决定了变量和函数的访问顺序。在当前执行上下文中,我们可以访问局部变量和函数,而在父级上下文中我们也能访问全局变量和函数。
其次,执行上下文栈也决定了代码执行的顺序。通过了解执行上下文栈的变化,我们能更好地理解代码是如何执行的,特别是在涉及到函数调用和上下文切换的情况下。
深入了解执行上下文栈将使我们能够更加精准地定位和解决代码中的问题,为我们的JavaScript编程技能注入新的力量。
4. 作用域与作用域链:解密JavaScript中的变量查找
在JavaScript中,作用域是一个关键概念,它决定了变量和函数的可访问性以及它们在代码中的生命周期。作用域链是一种机制,用于决定在特定上下文中标识符的查找顺序。
4.1 作用域是什么?
作用域定义了变量和函数在程序中的可访问性。在JavaScript中,我们通常有两种主要的作用域:
- 全局作用域: 全局作用域是指在整个程序中都可访问的变量和函数。在浏览器环境中,全局作用域通常指的是
window
对象。 - 局部作用域: 局部作用域是指变量和函数仅在定义它们的块或函数内部可访问。这有助于控制变量的可见性,防止污染全局命名空间。
作用域的存在使得我们能够更好地组织和控制代码,避免命名冲突,并提高代码的可维护性。
4.2 作用域链是如何工作的?
作用域链是一个保存变量对象的列表,它决定了在特定上下文中标识符的查找顺序。在JavaScript中,当代码在一个上下文中执行时,会创建一个与该上下文相关的作用域链。
4.2.1 作用域链的构建
当函数被调用时,JavaScript引擎会为该函数创建一个执行上下文。执行上下文中包含了一个变量对象,用于存储变量和函数声明。同时,该执行上下文的作用域链被创建,它包含了当前上下文的变量对象以及所有包含它的外部上下文的变量对象。
举例说明:
javascript
function outerFunction() {
let outerVar = 'I am in outer function';
function innerFunction() {
let innerVar = 'I am in inner function';
console.log(outerVar + ' and ' + innerVar);
}
innerFunction();
}
outerFunction();
在这个例子中,当innerFunction
被调用时,它的作用域链将包含innerFunction
的变量对象和outerFunction
的变量对象。
4.2.2 标识符的查找过程
当在一个上下文中引用一个标识符时,JavaScript引擎会按照作用域链的顺序进行查找。它首先在当前上下文的变量对象中查找标识符,如果找到了就使用该变量,否则继续在外部上下文的变量对象中查找。这个过程一直持续,直到找到标识符或者搜索到达全局作用域。
下面是一个简单的例子:
javascript
var globalVar = 'I am a global variable';
function outerFunction() {
var outerVar = 'I am in outer function';
function innerFunction() {
console.log(globalVar); // 可以访问全局变量
console.log(outerVar); // 可以访问外部函数的变量
}
innerFunction();
}
outerFunction();
在这个例子中,innerFunction
内部可以访问到全局变量globalVar
和外部函数outerFunction
的变量outerVar
,这是因为它们都在innerFunction
的作用域链上。
4.3 作用域链的影响
深入理解作用域链对于避免变量命名冲突、提高代码可读性和维护性至关重要。同时,它也有助于我们理解JavaScript中变量查找的机制,使我们能够更好地编写高效的代码。
5. 变量对象:JavaScript中变量存储的秘密
在JavaScript中,变量是程序中最基本的组成部分之一。了解变量对象是理解变量如何在内存中存储和访问的关键。
5.1 什么是变量对象?
变量对象是在执行上下文中用于存储变量和函数声明的对象。每个执行上下文都有其对应的变量对象,负责跟踪和管理在该上下文中创建的所有变量和函数。
在全局上下文中,变量对象被称为全局对象,通常是window
对象。在函数上下文中,变量对象被称为活动对象(activation object)。
5.2 变量对象的创建过程
在进入执行上下文时,变量对象会被创建并初始化。变量对象的创建过程与作用域链的建立密切相关。以下是变量对象创建的主要步骤:
5.2.1 创建阶段
在创建阶段,JavaScript引擎会按照以下步骤初始化变量对象:
- 建立变量对象(VO): 变量对象被创建并分配内存空间。
- 初始化变量和函数: 变量对象中会存储在当前上下文中声明的所有变量和函数声明。对于变量,将其初始化为
undefined
;对于函数声明,则会将整个函数体添加到变量对象中。
举例说明:
javascript
console.log(x); // undefined
var x = 10;
console.log(x); // 10
// 在上述代码执行前,变量对象的初始化阶段会将变量 x 初始化为 undefined。
5.2.2 代码执行阶段
在代码执行阶段,JavaScript引擎会按照执行流程逐行执行代码。变量对象会随着代码的执行而更新。变量的赋值操作会在变量对象中找到相应的标识符并更新其值。
javascript
var y = 20;
console.log(y); // 20
// 在执行阶段,变量对象会更新变量 y 的值为 20。
5.3 变量对象与作用域链的关系
变量对象与作用域链密切相关,它们共同构成了JavaScript中变量访问的机制。作用域链的每个节点都包含一个变量对象,而作用域链的顶端是当前执行上下文的变量对象。
举例说明:
javascript
var a = 30;
function foo() {
var b = 40;
function bar() {
var c = 50;
console.log(a + b + c); // 120
}
bar();
}
foo();
在上述代码中,全局上下文的变量对象包含变量a
。当调用foo
函数时,会创建一个新的函数上下文,它的变量对象包含变量b
。而当调用bar
函数时,又创建了一个新的函数上下文,其变量对象包含变量c
。
作用域链就是由这些变量对象构成的,它决定了在当前上下文中标识符的查找顺序。在执行过程中,JavaScript引擎会根据作用域链逐级查找变量对象,找到匹配的标识符。
总结
调用栈和作用域链对于闭包有着相当关键的作用,感兴趣的可以阅读(闭包:JavaScript秘密武器还是隐藏的内存泄漏罪魁祸首? - 掘金 (juejin.cn))
同时为了更加了解执行上下文,还可以增加对let,const的理解