JS作用域的深入理解

166 阅读4分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

代码块

如果在代码块{...}内声明一个变量,则这个变量只在该代码块内可见(前提是用let声明)。例如

image-20211107202920209

因此我们可以用它来隔离一段代码,使该段代码执行自己的任务,并使用仅属于自己的变量。

image-20211107204144787

如果没有代码块则会直接报错

image-20211107204406882

对于 ifforwhile 等,在 {...} 中声明的变量也仅在内部可见;

嵌套函数

基础使用

当一个函数是在另一个函数中创建的时,该函数就被称为“嵌套”的。如

image-20211107205112377

嵌套函数作为结果返回

可以返回一个嵌套函数:作为一个新对象的属性或作为结果返回。(下面我们会针对这个示例做一个具体的分析)

image-20211107205808487

词法环境

变量

  • 变量是特殊内部对象的属性,与当前正在执行的(代码)块/函数/脚本有关。
  • 操作变量实际上是操作该对象的属性。

在 JavaScript 中,每个【运行的函数】,【代码块 {...}】 以及整个脚本,都有一个被称为 词法环境(Lexical Environment) 的内部(隐藏)的关联对象。

“词法环境”是一个规范对象(specification object):它仅仅是存在于 编程语言规范 中的“理论上”存在的,用于描述事物如何运作的对象。我们无法在代码中获取该对象并直接对其进行操作。

函数声明

函数也是一个值,就像变量一样。不同之处在于函数声明的初始化会被立即完成。所以我们可以在(函数声明)的定义之前调用函数声明。

image-20211107213655338

内外部的词法环境

在一个函数运行时,在调用刚开始时,会自动创建一个新的词法环境以存储这个调用的局部变量和参数。

如以下的函数示例

在这个函数调用期间,我们有两个词法环境:内部一个(用于函数调用)和外部一个(全局):

  • 内部词法环境与 say 的当前执行相对应。它具有一个单独的属性:name,函数的参数。我们调用的是 say("John"),所以 name 的值为 "John"
  • 外部词法环境是全局词法环境。它具有 phrase 变量和函数本身。

内部词法环境引用了 outer

image-20211107214343279

在这个示例中,搜索过程为:

  • 对于 name 变量,当 say 中的 alert 试图访问 name 时,会立即在内部词法环境中找到它。
  • 当它试图访问 phrase 时,然而内部没有 phrase,所以它顺着对外部词法环境的引用找到了它。

返回函数

再来看下上面【嵌套函数作为结果返回】部分中的示例

function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}
let counter = makeCounter();

每次 makeCounter() 调用的开始,都会创建一个新的词法环境对象,以存储该 makeCounter 运行时的变量。因此,有两层嵌套的词法环境。

image-20211107214453144

在执行 makeCounter() 的过程中创建了一个仅占一行的嵌套函数:return count++。此时只是创建了它,还没有运行。

所有的函数在“诞生”时都会记住创建它们的词法环境。所有函数都有名为 [[Environment]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用。

image-20211107215436976

因此,counter.[[Environment]] 有对 {count: 0} 词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。[[Environment]] 引用在函数创建时被设置并永久保存。

当调用 counter() 时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于 counter.[[Environment]]

image-20211107215606522

counter() 中的代码查找 count 变量时,

  • 会首先搜索自己的词法环境(为空,因为那里没有局部变量),
  • 然后是外部 makeCounter() 的词法环境,并且在哪里找到就在哪里修改。
  • 在变量所在的词法环境中更新变量。

所以当调用多次 counter() 时,count 变量将在同一位置增加到 23 等。

image-20211107215815410

参考资料:

Variable scope, closure


🎨【点赞】【关注】不迷路,更多前端干货等你解锁

往期推荐

👉 超详细的JS映射(Map)总结

👉 你真的了解JavaScript的解构赋值吗?

👉 JS内置日期对象Date的使用指南

👉 函数进阶之递归初识和实例分析