深入理解JavaScript中的执行上下文

112 阅读6分钟

引言

在JavaScript中,执行上下文是代码执行时的一个环境,它包含了代码执行所需的所有信息,如变量对象、作用域链和this绑定等。理解执行上下文对于编写高效、可维护的JavaScript代码至关重要。本文将通过几个具体的例子,详细解析JavaScript中的执行上下文及其相关概念。

执行上下文概述

执行上下文是JavaScript引擎在执行代码时创建的一个环境,它负责管理变量、函数声明、作用域链和this绑定等信息。每个执行上下文都有三个主要阶段:创建阶段、执行阶段和销毁阶段。

  • 创建阶段:在这个阶段,JavaScript引擎会进行变量声明、函数声明和this绑定等准备工作。变量会被初始化为 undefined,函数声明会被提升到作用域的顶部。
  • 执行阶段:在这个阶段,实际的代码被执行。变量被赋予具体的值,函数被调用,表达式被求值。
  • 销毁阶段:当代码执行完毕后,执行上下文被销毁,释放内存资源。

全局执行上下文

全局执行上下文是程序启动时创建的第一个执行上下文,它存在于整个程序的生命周期中。全局执行上下文包含全局变量对象、全局作用域链和全局this绑定(通常指向 windowglobal 对象)。

示例1:全局执行上下文
console.log(fun);
function fun() {}

console.log(name);
var name = "wql";

console.log(a);
let a = 1;
创建阶段
  1. 变量声明和函数声明提升

    • function fun() 被提升到全局变量对象中。
    • var name 被提升到全局变量对象中,并初始化为 undefined
    • let a 被提升到全局变量对象中,但不会被初始化为 undefined(暂时性死区)。
  2. 函数声明

    • function fun() 被提升并绑定到全局变量对象中。
  3. 变量初始化

    • var name = "wql"name 初始化为 "wql"
    • let a = 1a 初始化为 1
执行阶段
  1. console.log(fun); 输出 function fun() {},因为函数声明已经被提升。
  2. console.log(name); 输出 undefined,因为 var name 被提升但还没有初始化。
  3. console.log(a); 抛出 ReferenceError: Cannot access 'a' before initialization,因为 let a 具有暂时性死区。

函数执行上下文

每当一个函数被调用时,JavaScript引擎会创建一个新的函数执行上下文。函数执行上下文包含以下部分:

  • 变量对象(Variable Object):包含函数的参数、局部变量和内部声明的函数。
  • 作用域链(Scope Chain):用于查找变量的链表,从当前执行上下文的变量对象开始,向上查找父级作用域的变量对象。
  • this绑定:确定当前函数的 this 关键字指向的对象。
示例2:函数执行上下文
var a = 1;
function fn(a) {
  var a = 2;
  function a() {}
  var b = a;
  console.log(a);
}
fn(3);
console.log(a);
创建阶段
  1. 参数传递

    • 参数 a 被传入并初始化为 3
  2. 变量声明和函数声明提升

    • var a 被提升到函数的变量对象中,并初始化为 undefined
    • function a() 被提升到函数的变量对象中,并覆盖之前的 var a 声明。
    • var b 被提升到函数的变量对象中,并初始化为 undefined
执行阶段
  1. var a = 2;将 a 重新赋值为 2。
  2. var b = a; 将 b 赋值为 a 的当前值,即 2。 3.第六行的 console.log(a);输出 2,因为 a当前的值是 2。
回到全局执行上下文
  1. 最后一行console.log(a); 输出 1,因为这是全局作用域中的 a,其值为 1。

变量提升与暂时性死区

在JavaScript中,变量声明和函数声明都会被提升到它们所在作用域的顶部,但它们的提升方式有所不同。

  • 函数提升:函数声明会被完全提升到作用域的顶部,这意味着你可以在声明之前调用函数。
  • 变量提升:变量声明会被提升到作用域的顶部,但初始化不会被提升。也就是说,变量在声明之前会初始化为 undefined
示例3:变量提升与暂时性死区
console.log(x); // 输出: undefined
var x = 1;

console.log(y); // 抛出 ReferenceError: Cannot access 'y' before initialization
let y = 2;
创建阶段
  1. 变量声明提升
    • var x 被提升到全局变量对象中,并初始化为 undefined
    • let y 被提升到全局变量对象中,但不会被初始化为 undefined(暂时性死区)。
执行阶段
  1. console.log(x); 输出 undefined,因为 var x 被提升但还没有初始化。
  2. console.log(y); 抛出 ReferenceError: Cannot access 'y' before initialization,因为 let y 具有暂时性死区。

如果对这个变量提升和暂时性死区还有不解的地方可以去看笔者的另一篇文章【ES6】让你彻底搞懂const ,let和var的区别

调用栈

调用栈是JavaScript引擎管理函数调用的一种数据结构。每当一个函数被调用时,一个新的执行上下文会被压入调用栈的顶部;当函数执行完毕后,该执行上下文会被弹出调用栈。调用栈确保了函数调用的顺序和正确性。

示例4:调用栈
function foo() {
  console.log('foo');
}

function bar() {
  foo();
  console.log('bar');
}

bar();
调用栈变化
  1. 程序启动时,全局执行上下文被压入调用栈。
  2. bar 被调用,bar 的执行上下文被压入调用栈。
  3. foo 被调用,foo 的执行上下文被压入调用栈。
  4. foo 执行完毕,foo 的执行上下文被弹出调用栈。
  5. bar 继续执行,输出 'bar'。
  6. bar 执行完毕,bar 的执行上下文被弹出调用栈。
  7. 全局执行上下文继续执行,直到程序结束。

总结

通过本文的详细解析,我们可以看到:

  1. 全局执行上下文:在全局作用域中,变量声明和函数声明会被提升到顶部。函数声明会被完全提升,而变量声明会被初始化为 undefinedletconst 声明的变量具有暂时性死区。
  2. 函数执行上下文:在函数调用时,会创建一个新的执行上下文。参数、变量声明和函数声明会被提升到函数的顶部。函数声明会覆盖同名的变量声明。
  3. 调用栈:调用栈管理函数调用的顺序,确保代码的正确执行。

理解这些概念有助于我们在编写JavaScript代码时避免常见的错误,提高代码的可读性和可维护性。希望本文的内容对你有所帮助!

c224676594aae6804009e3fe521b122.jpg