Skip to content

JavaScript中执行上下文和执行栈详解

前言

在JavaScript中,执行上下文(Execution Context)和执行栈(Execution Stack)是两个非常重要的概念。它们是JavaScript代码执行的基础,也是理解作用域、闭包、this等高级概念的关键。本文将以通俗易懂的方式,结合实例详细讲解这两个概念。

一、什么是执行上下文?

1.1 基本概念

执行上下文可以理解为JavaScript代码运行的环境。当JavaScript代码执行的时候,会创建对应的执行上下文。

1.2 执行上下文的类型

JavaScript中有三种执行上下文类型:

  1. 全局执行上下文

    • 默认的上下文
    • 只有一个
    • 创建全局对象(浏览器中是window)
    • 将this指向全局对象
  2. 函数执行上下文

    • 每次函数调用时创建
    • 可以有多个
    • 创建arguments对象
    • this绑定取决于函数的调用方式
  3. eval执行上下文

    • eval函数执行时创建
    • 不建议使用

1.3 代码示例

javascript
// 全局执行上下文
var globalVar = "全局变量";

function outer() {
    // outer函数执行上下文
    var outerVar = "外部函数变量";
    
    function inner() {
        // inner函数执行上下文
        var innerVar = "内部函数变量";
        console.log(innerVar); // 可访问
        console.log(outerVar); // 可访问
        console.log(globalVar); // 可访问
    }
    
    inner();
}

outer();

二、执行上下文的生命周期

2.1 创建阶段

创建阶段主要做三件事:

  1. 创建变量对象(Variable Object)

    javascript
    function foo(a) {
        var b = 2;
        function c() {}
        var d = function() {};
    }
    foo(1);
    
    // 创建阶段的变量对象
    VO = {
        arguments: { 0: 1, length: 1 },
        a: 1,
        b: undefined,
        c: reference to function c(),
        d: undefined
    }
  2. 建立作用域链(Scope Chain)

    javascript
    var color = "blue";
    function changeColor() {
        var anotherColor = "red";
        function swapColors() {
            var tempColor = color;
            color = anotherColor;
            anotherColor = tempColor;
        }
        swapColors();
    }
    // swapColors的作用域链: [swapColors VO, changeColor VO, Global VO]
  3. 确定this指向

    javascript
    const person = {
        name: '张三',
        sayHi() {
            console.log(`你好, ${this.name}`);
        }
    };
    person.sayHi(); // this指向person对象

2.2 执行阶段

执行阶段主要进行变量赋值、函数引用以及执行其他代码。

2.3 回收阶段

执行上下文出栈,等待垃圾回收。

三、执行栈的工作原理

3.1 什么是执行栈?

执行栈是一个后进先出(LIFO)的栈结构,用于存储代码执行期间创建的所有执行上下文。

3.2 执行栈工作流程示例

javascript
function multiply(x, y) {
    return x * y;
}

function square(n) {
    return multiply(n, n);
}

function printSquare(n) {
    var squared = square(n);
    console.log(squared);
}

printSquare(4);

执行流程:

  1. 压入全局执行上下文
  2. 调用printSquare(4) -> 压入printSquare执行上下文
  3. 调用square(4) -> 压入square执行上下文
  4. 调用multiply(4,4) -> 压入multiply执行上下文
  5. multiply执行完毕 -> 弹出multiply执行上下文
  6. square执行完毕 -> 弹出square执行上下文
  7. printSquare执行完毕 -> 弹出printSquare执行上下文
  8. 程序结束 -> 弹出全局执行上下文

四、面试常见问题

4.1 变量提升相关

Q: 下面代码的输出是什么?

javascript
console.log(a);
var a = 1;
function a() {}
console.log(a);

A: 输出:

javascript
ƒ a() {}
1

解释:

  • 在创建阶段,函数声明优先级高于变量声明
  • 第一个console.log输出函数a
  • 执行到var a = 1时,将a的值改为1
  • 第二个console.log输出1

4.2 this指向相关

Q: 以下代码输出什么?

javascript
const obj = {
    name: '张三',
    sayName: function() {
        setTimeout(function() {
            console.log(this.name);
        }, 100);
    }
};
obj.sayName();

A: 输出: undefined 解释:

  • setTimeout中的函数在全局环境中执行
  • this指向window而不是obj
  • 解决方案可以使用箭头函数或bind

4.3 作用域链相关

Q: 输出结果是什么?

javascript
var value = 1;
function foo() {
    console.log(value);
    var value = 2;
    console.log(value);
}
foo();

A: 输出:

undefined
2

解释:

  • 由于变量提升,函数内部的value声明会提升到函数作用域顶部
  • 第一个console.log时value已声明但未赋值,所以是undefined
  • 第二个console.log时value已被赋值为2

五、实践建议

  1. 避免使用var,优先使用let/const
  2. 注意闭包可能导致的内存泄露
  3. 合理使用箭头函数来绑定this
  4. 理解变量提升机制,避免相关bug
  5. 注意代码块级作用域的使用

总结

理解执行上下文和执行栈对于JavaScript开发至关重要。它们是理解作用域、闭包、this等概念的基础,也是解决变量提升、this指向等常见问题的关键。在实际开发中,我们要注意合理使用这些特性,编写更可靠的代码。