(译)看得见的 JavaScript: 作用域(链)Scope (Chain)

234 阅读4分钟
var code = "8aecffca-8909-4927-a3e0-49a8d64e4bd6"

让我们看一下以下这段代码:

const name = "Lydia"
const age = 21
const city = "San Francisco"


function getPersonInfo() {
  const name = "Sarah"
  const age = 22

  return `${name} is ${age} and lives in ${city}`
}

console.log(getPersonInfo())

我们调用了函数 getPersonInfo,它将返回一个由 nameagecity 变量组成的字符串:Sarah is 22 and lives in San Francisco。但是,getPersonInfo 函数里并未包含一个叫做 city 的变量,它是怎么知道 city 的值的呢?

首先,不同的上下文都会被分配不同的内存空间。我们拥有默认的全局上下文(浏览器里是 window,在 Node 中是 global),以及一个局部上下文,它是在 getPersonInfo 函数被调用时出现的。每一个上下文都会有一个作用域链

对于 getPersonInfo 函数,作用域链看起来有点像下面这样(不必担心,暂时可以不用理解它):

image.png

作用域链基本上就是一个对象的“引用链条”,它将包含当前执行上下文中所有值的引用。当执行上下文被创建的时候,作用域链就出现了,也就是说,它是在运行时被创建的。

然而,在这篇文章中,我并不打算讨论 activation object (活跃对象或激活对象)或是执行上下文,让我们聚焦作用域!在接下来的例子中,执行上下文中的键值对代表作用域链中变量的引用。

image.png

全局执行上下文中的作用域链有一条对 3 个变量的引用:带有值 Lydia 的变量 name,带有值 21 的变量 age,以及带有值 San Francisco 的变量 city。在局部上下文中,我们有一条对 2 个变量的引用:带有值 Sarah 的变量 name,以及带有值 22 的变量 age

当我们试着获取 getPersonInfo 函数中的变量时,引擎首先会检查局部作用域。

image.png

局部作用域链中含有一条对 nameage 的引用!name 中的值是 Sarahage 中的值是 22。但是现在,当它试着去获取 city 时会发生什么?

为了找到 city 的值,引擎将会顺着作用域链向下找。这基本上意味着引擎不会简单地放弃查找:它会为你努力工作,看看是否可以在局部作用域外部找到变量 city,这本例中,这个外部作用域就是全局对象(global object)

image.png

在全局上下文中,我们定义了变量 city,它的值是 San Franciso,因此就有了对变量 city 的引用。既然这个变量有对应的值,那么函数 getPersonInfo 就能返回出字符串 Sarah is 22 and lives in San Francisco

我们沿着作用域链向下找,但是不会反过来向上找。(好吧,这也许会令人很疑惑,因为有些人的说法正好相反,下说成上,因此我要提一句的是:你可以沿着作用域链向外找,而不是向内找……)我喜欢将它想象成瀑布:

image.png

甚至更深层次的:

image.png

让我们以这段代码为例。

image.png

跟之前的例子相比,几乎是一样的,一个比较大的差异是:我们现在只在 getPersonInfo 函数中声明了变量 city,而不是在全局作用域。我们没有去调用 getPersonInfo 函数,也就没有局部上下文被创建。此时,我们试着在全局上下文中获取变量 nameage 以及 city 的值。

image.png

它将会抛出错误:ReferenceError!在全局作用域中,它找不到 city 对应的引用值,也没有其他外部的作用域了,它不能再继续往上找了。

这样一来,你就能利用作用域作为一种保护变量的途径,而且可以出现同名变量。

除了全局和局部作用域,还有一个块级作用域。用 letconst 关键字声明的变量被限制在最近的大括号({})中。

const age = 21

function checkAge() {
  if (age < 21) {
    const message = "You cannot drink!"
    return message
  } else {
    const message = "You can drink!"
    return message
  }
} 

你可以将作用域想象成这样:

image.png

我们有一个全局作用域,一个函数作用域以及两个块级作用域。我们可以声明变量 message 两遍,因为变量的范围在大括号中,彼此独立、互不影响。

快速回顾一下:

  • 你可以把作用域链当作一条可以访问到当前上下文中值的引用链条。
  • 作用域能够让变量名称重复成为可能。

参考:dev.to/lydiahallie…

添加我的微信:with_his_x,共同成长,卷卷群里等你 🤪。