这一次,我搞懂了 作用域、上下文和执行栈

138 阅读8分钟

最近在项目空窗期,终于能有时间将困扰我许久的作用域、上下文和执行栈的概念进行整理归纳了,我也在这中间想明白了许多模糊的点,现在分享出来,希望能帮助到大家

上下文 & 执行上下文

在JavaScript中,执行代码时会形成一个执行环境(也称为上下文)。每个执行环境都有自己的变量、函数和作用域链,它决定了变量或函数的可见性和生命周期。 JavaScript中的上下文包括以下三种类型:

  1. 全局上下文 全局上下文是最外层的执行环境,即整个JavaScript文件的顶层。当进入JavaScript文件时,就会创建一个全局上下文,其变量和函数都可以被访问到和调用。
  2. 函数上下文 每当一个函数被调用时,都会创建一个新的函数上下文。函数上下文包含了该函数中定义的所有变量和函数,它们存在于函数的作用域内。函数上下文还包含了this和arguments等特殊变量。
  3. eval上下文 eval()函数会动态地创建一个新的上下文。这个上下文与函数上下文类似,但是它可以访问当前上下文中的变量和函数。

上下文之间通过作用域链来相互关联。当访问一个变量时,解释器首先查找当前上下文中是否定义了该变量,如果没有,则沿着作用域链一级一级地向上查找,直到找到全局上下文为止。

作用域 & 作用域链

作用域

在JavaScript中,作用域是指变量和函数的可见范围。作用域决定了变量和函数在何处可以被访问。通常情况下,作用域分为以下几种类型:

  1. 全局作用域:全局作用域是整个JavaScript程序中的最外层作用域,它定义的变量和函数都可以被程序中的任何地方访问。全局作用域的生命周期与程序的生命周期相同。
  2. 局部作用域:局部作用域是指在函数内部定义的作用域,它定义的变量和函数只能在该函数内部访问。当函数执行完毕后,局部作用域会被销毁。
  3. 在ES6中,引入了块级作用域的概念。块级作用域指的是由{}括起来的代码块内部创建的作用域。在块级作用域中定义的变量和函数只能在该块级作用域内部访问,离开该块级作用域后这些变量和函数就会被销毁。
  4. 词法作用域。词法作用域是指在代码编写时确定的作用域,而不是在运行时确定的。JavaScript采用词法作用域,也就是静态作用域,在函数定义时就确定了函数的作用域,与函数调用的位置无关。(这里初看会比较迷,请结合函数执行上下文理解)

作用域和执行上下文是有一定关系的。执行上下文表示代码在执行过程中所处的环境,每个执行上下文都有自己的作用域。作用域则是指变量和函数可访问的范围。

在JavaScript中,每个函数执行的时候都会创建一个新的执行上下文,并且这个执行上下文会形成一个新的作用域。在该作用域内定义的变量和函数只能在该作用域内部访问。当函数执行结束后,该执行上下文就会被销毁,它所创建的作用域也随之消失。 因此,可以说执行上下文和作用域是密切相关的,它们共同决定了变量和函数的可见性和生命周期。执行上下文负责管理当前代码的执行状态和数据,而作用域则决定了这些数据的可见范围

在JavaScript中,函数执行时可以访问的变量取决于函数的执行上下文所处的作用域,而不是函数定义时所在的作用域。具体来说,当一个函数被调用时,会创建一个新的执行上下文,并且该执行上下文会形成一个新的作用域。这个作用域包含了函数内部定义的所有变量和函数。当函数需要访问一个变量时,它会首先查找自己的作用域中是否有该变量,如果没有,则沿着作用域链一级一级地向上查找,直到找到全局作用域为止。 因此,一个函数能够访问到哪些变量,完全取决于其执行上下文所处的作用域链。即使这个函数是在另外一个函数内部声明的,只要它被调用时创建了新的执行上下文,就可以访问其父作用域或全局作用域中的变量。

作用域链

作用域链是指在Javascript中多层嵌套的函数中,由内向外逐级形成的变量访问链。它的作用是决定了一个变量在哪个作用域中被查找。

当JavaScript引擎执行代码时,在每个函数执行上下文中都会创建一个称为“变量环境”的对象。这个对象包含了该函数中定义的变量和函数,以及对父级作用域的引用。通过这些引用,JavaScript引擎可以沿着作用域链一级一级地查找变量,直到找到其定义的位置或查找到全局作用域为止。

作用域链的连接方式是通过函数内部的[[scope]]属性实现的。每个函数都有一个[[scope]]属性,它指向了当前函数所处的作用域链。当函数被创建时,JavaScript引擎会将当前函数的[[scope]]属性设置为创建时的作用域链。当函数执行时,JavaScript引擎会将该函数的执行上下文压入执行栈中,并将其作用域链设置为当前作用域链。

示例:

image.png

当函数需要访问一个变量时,JavaScript引擎首先在当前作用域链的变量环境中查找该变量是否已经声明,如果没有找到,则向上逐级查找父级作用域链,直到找到该变量或者查找到全局作用域为止。

在JavaScript中,函数的作用域是由词法作用域规则确定的,也就是说,函数在声明时就已经确定了它所处的作用域。这个作用域是由函数定义时所在的代码块决定的,而不是在运行时决定的。

当函数被调用时,会创建一个新的执行上下文,并且该执行上下文会形成一个新的作用域。这个作用域是由函数定义时的词法作用域规则决定的,它包含了函数内部定义的所有变量和函数,以及对父级作用域的引用。通过这些引用,JavaScript引擎可以沿着作用域链一级一级地查找变量,直到找到其定义的位置或查找到全局作用域为止。

因此,函数的执行作用域是由函数定义时的词法作用域规则决定的。在函数执行过程中,JavaScript引擎会根据当前的作用域链来查找变量和函数,并且将函数内部定义的变量与外部作用域的变量进行隔离,避免出现变量污染等问题。

执行栈 & 执行上下文

执行栈和执行上下文是JavaScript中两个不同的概念,但它们之间有联系。

执行栈(也称为调用栈)是一种数据结构,用于存储函数调用的顺序。当一个函数被调用时,其对应的执行上下文会被压入执行栈中,当该函数执行完毕后,其对应的执行上下文会从栈中弹出。

执行上下文是指当前代码在执行过程中所处的环境,包括变量、函数、参数等信息。每个执行上下文都有自己的变量环境和作用域链,它们决定了变量的可见性和生命周期。

在JavaScript中,执行栈维护着当前代码执行的状态,它通过执行上下文来管理每个函数的执行环境。执行栈的顶部始终是当前正在执行的函数的执行上下文,当一个函数执行完毕后,它的执行上下文会从栈中弹出,然后控制权转移到栈顶的下一个执行上下文。

总结

于我而言,系统性捋顺了作用域及上下文的概念让我对之前死记硬背下来的闭包、this指向、原型和原型链的概念有了更深刻的理解。虽然现在都是用各种框架搭建前端工程,但对于js的底层,我们怎么样重视也不为过。