logo学习随笔

【译】JavaScript 可视化之作用域链

February 01, 2020

本文假设读者已了解执行上下文的基本概念。

我们首先来看以下代码:

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 函数时,返回了一串包含 name, agecity 变量的字符串:Sarah is 22 and lives in San Francisco。但是 getPersonInfo 函数内并没有声明变量 `city,它是怎么得到的呢?

JavaScript 引擎为不同上下文设置了内存空间。我们有默认的全局上下文(浏览器中为window,Node 中为global),调用 getPersonInfo 函数的本地上下文。每个上下文都有一个作用域链。

对于 getPersonInfo 函数,作用域链看起来如下:

scope chain

作用域链本质上来说是对对象的引用链,这些对象包含对该执行上下文中可引用的值(和其他作用域)的引用。作用域链在执行上下文创建时一同创建,就是说是在运行时创建的。

在本文中对活动对象(activation object)、执行上下文不做过多描述,只来聚焦于作用域。在下面的例子中,执行上下文中的键值对代表作用域链对变量的引用。

scope chain reference

全局执行上下文中的作用域链中有 3 个变量的引用:name: Lydiaage: 21city: San Francisco。在本地上下文中,我们有两个变量的引用:name: Sarahage: 22

当我们在 getPersonInfo 函数中访问这些变量时,引擎会首先去检查本地作用域链。

local scope chain

在本地作用域链中只找到了 nameage,那么 city 应该去哪获取呢?实际上 JavaScript 引擎如果在当前作用域找不到,会跳到上层作用域直至找到或者返回 ReferenceError,本例中在全局对象中找到了 city 变量。

global scope

在全局上下文中,我们声明了值为 San Franciscocity 变量,getPersonInfo 函数得以找到该变量。


我们可以向外层作用域链但不能向内部去寻找。

down scope

或者更高层:

deep scope


我们再来看下面的例子:

mutated demo

和一开始的例子类似,不过有个最大的不同点:我们只在 getPersonInfo 函数内部声明了 city 变量。我们没有调用 getPersonInfo 函数,所以没有本地上下文被创建。我们尝试在全局上下文中获取 name, agecity 变量。

reference error

在全局作用域中找不到 city 变量,没有更外层的作用域来查找,抛出了 ReferenceError


除了全局和本地作用域,还有块级作用域。通过 letconst 关键字声明的变量的作用域在距{}最近的内部。

const age = 21;

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

block scope

我们创建了一个全局作用域,函数作用域和两个块级作用域。我们能够声明两次 message,因为该变量的作用域是在大括号内的。


快速回顾:

  • 作用域链实际是对我们在当前上下文中访问的值的链式的引用。
  • 得益于作用域链向上查找的规则,我们可以使用上层作用域已声明过的变量。

参考

https://dev.to/lydiahallie/javascript-visualized-scope-chain-13pd