长话短说 “执行上下文和执行栈”

232 阅读7分钟

作为前端开发者,有很多必备基础需要知道比如(提升机制、作用域、作用域链)。这些我的主页有文章进行讲解,有兴趣可以一起讨论学习。正确理解执行上下文和执行栈的概念将有助于你成为一名更好的 JavaScript 开发人员

切入正题,下面介绍执行上下文和执行栈

一句话定义

执行上下文是JavaScript运行时的一个重要概念,它是代码被执行时所处的环境。这个环境包含了代码执行所需要的信息,如变量、函数以及它们的作用域等。

执行上下文三种类型

  • 1.全局上下文

全局上下文是在浏览器的window环境下为全局变量(如使用var声明的变量)创建的执行环境。当JavaScript代码首次被解析时,会先进行短暂的编译,创建全局环境,并将其压入执行栈的最底层。全局上下文在整个程序生命周期内始终存在,直到浏览器窗口关闭

  • 2.函数上下文

每次函数被调用时,JavaScript引擎会为该函数创建一个新的执行上下文。这个上下文不仅包含了函数内部声明的所有变量和参数,还形成了一个局部作用域。每当调用一个函数fn,对应的函数执行上下文会被压入执行栈的顶部。当函数执行完毕后,该上下文从栈顶弹出,控制权返回到上一个执行上下文

  • 3.Eval上下文

执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

执行上下文内容

执行上下文主要包括三个部分:执行代码、词法环境和变量环境。

  1. 执行代码:这是实际运行的JavaScript代码。
  2. 词法环境:包含函数内部声明的变量和函数,以及作用域链信息。
  3. 变量环境:包含使用var声明的变量和函数声明。

当V8引擎对代码进行短暂编译后,会在全局作用域中创建全局上下文。每当调用一个函数时,会创建一个函数上下文,并将其动态压入执行栈。执行代码在词法环境或变量环境中运行,通过作用域链查找变量或函数。如果当前作用域中找不到变量或函数,JavaScript引擎会沿着作用域链向上查找,直到找到为止或到达全局上下文。执行完成后,当前的执行上下文从栈中弹出,进行垃圾回收。

可以用一个链式的结构来理解这个过程: js 代码执行 <- 执行机制(执行栈) <- 函数入栈(操作系统)<- 执行上下文(代码和变量声明的关系) <- 作用域 + 变量提升+ 作用域链 <- 全局上下文

执行栈

在编程语言中,执行栈(也称为调用栈)是一种使用LIFO(后进先出)数据结构的栈,用于存储代码运行时创建的所有执行上下文。JavaScript作为一种动态语言和脚本语言,能够在运行时动态地更新执行栈。

每当调用一个函数时,该函数的执行上下文会被压入执行栈的顶部,而全局执行上下文始终位于栈底。当函数执行完毕后,其执行上下文从栈顶弹出,控制权返回到上一个执行上下文。这种机制确保了函数调用的正确顺序和作用域链的完整性。

注意:

  • 函数声明优先于变量声明。在每个执行上下文中,所有函数声明会被提升到顶部,然后按顺序处理变量声明。
  • 执行栈的管理确保了代码的正确执行顺序,同时也支持JavaScript的动态特性和异步编程模型。

创建执行上下文

在了解了JavaScript引擎如何管理执行上下文之后,接下来我们将深入探讨JavaScript引擎是如何创建执行上下文的。

执行上下文的创建分为两个阶段:1)创建阶段;2)执行阶段

1. 创建阶段

在创建阶段,JavaScript引擎会执行以下步骤:

  1. This Binding
    • 即确定this的值
    • 在全局执行上下文中,this的值指向全局对象,即在浏览器中指向window对象。
    • 在函数执行上下文中,this的值取决于函数的调用方式。如果函数作为对象的方法被调用,this将指向该对象;否则,在非严格模式下,this指向window对象,而在严格模式下,thisundefined。代码例如:
let student = {  
  name: 'peter',  
  birthYear: 2000,  
  calcAge: function() {  
    console.log(2024 - this.birthYear);  
  }  
}

person.calcAge();   
// 'this' 指向 'person', 因为 'calcAge' 是被 'person' 对象引用调用的。

let calculateAge = person.calcAge;  
calculateAge();  
// 'this' 指向全局 window 对象,因为没有给出任何对象引用

  1. 词法环境(Lexical Environment)

    • 根据ES6官方文档,词法环境是一种规范类型,用于定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空引用(null)的外部词法环境组成。

    • 简而言之,词法环境是一个包含标识符变量的集合结构。标识符指的是变量或函数的名称。

    • 词法环境包含两个主要部分:

      1. 环境记录(Environment Record) :存储变量和函数声明的实际位置。
      2. 对外部环境的引用:允许访问其外部词法环境。
  2. 变量环境(Variable Environment)

    • 变量环境主要用于存储使用var声明的变量和函数声明。在创建阶段,这些声明会被初始化,但变量的值默认为undefined,而函数声明会被提升并赋值。

2. 执行阶段

在执行阶段,JavaScript引擎会执行以下操作:

  1. 执行代码:逐行执行代码,根据需要读取和修改变量的值。
  2. 作用域链:如果在当前执行上下文中找不到某个变量或函数,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或函数,或者到达全局上下文。
  3. 垃圾回收:当执行上下文中的代码执行完毕后,该上下文从执行栈中弹出,相关的变量和函数会被垃圾回收机制回收。

示例代码分析

考虑以下示例代码:

var a = 1;
function fn(a) {
    var a = 2;
    var funA = function a() {};
    // 函数优先
    var b = a;
    console.log(funA); //打印 function
    console.log(a);//打印 2 ,想想为啥不是3
}

fn(3);
  • 创建阶段
  1. 全局执行上下文

    this绑定到window对象,创建词法环境和变量环境,包含变量a,初始值为undefined,再 初始化a的值为1

  2. 函数fn的执行上下文

    • this绑定到window对象(假设非严格模式)。
    • 创建词法环境和变量环境,包含参数a,初始值为undefined。传入参数(即a = 3)初始化参数a的值为3
    • var a = 2;声明变量a,初始值为undefined,初始化 a = 2。
    • 声明函数funA,初始值为function a() {}
    • 声明变量b,初始值为undefined,初始化为 a 。
  • 执行阶段
  1. 全局执行上下文 调用函数fn(3),将fn的执行上下文压入执行栈。

  2. 函数fn的执行上下文

    • 执行代码,首先将a的值从3重新赋值为2
    • 声明函数funA,值为function a() {}
    • b的值赋为a的值,即2
  3. 全局执行上下文

    • fn执行完毕后,其执行上下文从执行栈中弹出。
    • 全局执行上下文继续执行后续代码,直到所有代码执行完毕,全局执行上下文从执行栈中弹出,执行栈为空。

总结

通过上述分析,我们可以看到JavaScript引擎在创建和执行执行上下文时的详细步骤。这些步骤确保了代码的正确执行顺序和作用域链的完整性。希望这些内容能帮助您更好地理解JavaScript的执行上下文机制。有篇大佬文章将三种执行上下文环境讲的很nice,推荐给大家

收藏等于学会,点赞等于~~(脑补中)

后续推出闭包以及其他JavaScript相关文章,敬请期待~~