《你不知道的JavaScript 上卷》笔记 (每天更新)


第一章 作用域是什么

作用域:用来存储变量,并且之后可以方便地找到这些变量

JS 代码片段:编译 -> 执行

1.2 理解作用域

  • 引擎:负责 JS 程序的编译及执行过程
  • 编译器:负责 语法分析、代码生成
  • 作用域:负责 收集并维护由所有标识符(变量)组成的一系列查询,并确定当前执行的代码对这些标识符的访问权限

在JS中所有的可以由我们自主命名的都可以称为是标识符.例如:变量名、函数名、属性名都属于标识符

var a = 2
/*
变量的赋值操作会执行两个动作:
	首先,编译器会在当前作用域中声明一个变量(如果之前没有声明过)
	然后在运行时引擎会在作用域中查找该变量(查找的过程由作用域协助),如果能够找到就会对它赋值
*/

但是,引擎进行怎样的查找,会影响最终的查找结果:
RHS 查询:仅查找某个变量的值
LHS 查询:找到变量的容器本身

深入研究考虑一下代码:

a = 2;
// 这里对a的引用是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为 =2 这个赋值操作找到一个目标

console.log(a);
// 对a的引用是一个 RHS 引用,因为这里a并没有赋予任何值。相应地,需要查找并取到a的值,这样才能将值传递给 console.log(...)

在概念上最好理解为:
赋值操作的目标是谁(LHS)
谁是赋值操作的源头(RHS)

下面的程序既有 LHS 也有 RHS 引用

function foo(a) {
  console.log(a);		// 2
}

foo(2);

最后一行 foo(...) 函数的调用 需要对 foo 进行 RHS 引用,意思是:去找 foo 的值,并把它给我
并且 (...) 意味着 foo 的值需要被执行,因此它最好真的是一个函数类型的值!???

代码中还有隐式的 a = 2 操作,发生在 2 被当作参数传递给 foo(...) 函数时,2 会被分配给参数a。为了给参数a(隐式地)分配值,需要进行一次 LHS 查询

这里还有对 a 进行的 RHS 引用,并且将得到的值传给了 console.log(...)
console.log(...) 本身也需要一个引用才能执行,因此会对 console 对象进行 RHS 查询,并且检查得到的值中是否有一个叫做 log 的方法

最后,在概念上可以理解为在 LHS 和 RHS 之间通过对值2进行交互来将其传递进log(...)

TEST

function foo(a) {
  var b = a;
  return a + b;
}

var c = foo(2);

1.找出其中所有的 LHS 查询(3处)

c = ..		// c = foo(2)
a = 2			
b = ..			// b = a;

2.找出其中所有的 RHS 查询(4处)

foo(2..		// foo(2)
= a;			// b = a
a ..			// a + b
.. b			// a + b

1.3 作用域嵌套

一个块或函数嵌套在另一个块或函数中

JS引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论是否找到,查找都会停止

1.4 异常

为什么要区分RHS 和 LHS ?

解:因为在变量还没有声明(意味着在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不一样的

上代码:

function foo(a) {
	console.log(a + b);
	b = a;
}

foo(2);

第一次对 b 进行 RHS 查询是无法找到该变量的,因为在任何相关的作用域中都无法找到它

当引擎执行 LHS 查询时,如果在全局作用域中也无法找到目标变量,全局作用域就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下

1.5 小结

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。
如果查找的目的是对变量进行赋值,则会使用 LHS 查询。(所以 赋值 -> LHS)

  • “ = ” 操作符 或 调用函数时传入参数的操作 都会导致关联作用域的赋值操作

如果目的是仅获取变量的值,就会使用 RHS 查询

第二章 词法作用域

大部分标准语言编译器的第一个工作阶段叫作词法化
词法化会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词语义。

这个概念是理解词法作用域及其名称来历的基础

词法作用域:定义在词法阶段的作用域
(换句话说,词法作用域是由你在写代码时 将变量和块作用域写在哪里 来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样))

function foo(a) {
  var b = a * 2;
  
  function bar(c) {
    console.log(a,b,c);
  }
  
  bar(b*3);
}

在这个例子中,有三个逐级嵌套的作用域

image-20211202152208965

没有任何函数可以部分地同时出现在两个父级函数中

查找

作用域气泡的结构 和 互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置

作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符,这叫作遮蔽效应