前言
ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。
通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。
欢迎关注和订阅专栏 重学前端-ECMAScript协议上篇
本节内容对应协议内容 Reference Record
阅读这一章节之前,强烈建议先阅读前面的 语言类型和规范类型 章节。
定义
官方的解释: 引用记录类型用于解释诸如 delete、 typeof、赋值运算符、 super 关键字和其他语言特性等运算符的行为。例如,赋值的左边操作数应该生成一个引用记录。
赋值的左边操作数应该生成一个引用记录,这句话会在 标志符和作用域的章节得到详细的解释。
ini
javascript
复制代码
var a = 10;
var obj = {};
obj.name = "object name";
console.log(a);
console.log(object.name);
思考一下, a 是什么, 10又是什么。
a 是标志符 Identifiers, 10 是 ECMAScript 语言类型 的 Numeric Types,即数值。
a = 10
之后背后逻辑是 标志符 a 和 10 这个值,建立了某种绑定关系(后续会细说),之后可以标志符 a 访问到其值。
那为什么 console.log(a)
会输出10呢, 这背后的执行逻辑
- 执行上下文通过 标志符 a 通过ResolveBinding ( name [ , env ] ) 查找到 对应的 Reference Record引用记录
- 然后通过 Reference Record引用记录 取值 6.2.5.5 GetValue ( V )
- 最后输出值
这个Reference Record是代码执行中的一种协议数据类型, 通常是需要对标志符进行操作时,生成的临时的引用关系的数据结构,接下来就来详细分析这个 Reference Record。
引用记录字段
Reference Record其字段名,值和含义如下:
字段名 | 值 | 含义 |
---|---|---|
[[Base]] | 语言类型的值,或者环境记录,或者unresolvable | 持有这个绑定关系的值或者环境记录。如果值是 unresolvable表示无法解析。 |
[[ReferencedName]] | 字符串, Symbol, 或者 Private Name | 绑定的名称。如果[[Base]]是环境记录,其必为字符串。 |
[[Strict]] | 布尔值 | 如果引用记录起源于严格模式代码,则为 true,否则为 false。 |
[[ThisValue]] | 语言类型的值或者empty | 如果不为empty,引用记录表示使用 super 关键字表示的属性绑定; 它被称为 super 引用记录,其[[ Base ]]值永远不会是 环境记录。在这种情况下,[[ ThisValue ]]字段在创建引用记录时保存 this 值。 |
重点解释一下[[Base]]
字段,因为其真的很重要, 真的很重要吗,真的很重要。
[[Base]]
值可能是语言类型的值,环境记录或者unresolvable。其是标志符取值操作的关键和核心。
ini
javascript
复制代码
var a = 10;
console.log(a);
var obj = {};
obj.name = 'object name';
console.log(obj.name);
console.log(a)
执行时 ,通过标志符a
查找, 会返回一个引用记录,其个字段的值如下:
字段 | 值 | 备注 |
---|---|---|
[[Base]] | 全局环境记录 | 返回的是环境记录。这个绑定关系是与环境记录的绑定。 |
[[ReferencedName]] | a | |
[[Strict]] | false | |
[[ThisValue]] | empty |
console.log(obj.name)
执行时,通过标志符 name
查找,会返回一个引用记录,其个字段的值如下:
字段 | 值 | 备注 |
---|---|---|
[[Base]] | obj | 返回的是语言类型的值。 这个绑定关系是 name和 obj的绑定,即所谓的 属性引用。 |
[[ReferencedName]] | name | |
[[Strict]] | false | |
[[ThisValue]] | empty |
说完字段,再说一下引用记录的相关的抽象操作, 下面抽象方法的参数 V 都是 环境记录。
引用记录的抽象操作
IsPropertyReference ( V )
是否是属性引用。 如下的 obj.name
就是属性引用。 而 aaaa
则不是。
ini
javascript
复制代码
var aaaa = 10;
var obj = {};
obj.name = 'object.name'
console.log(aaaa);
console.log(obj.name);
其逻辑就是检查字段[[Base]]
的值,然后采用排查法。 就在上面已经清楚列出了[[Base]]
值的情况,无非就三种, 排查前两种,那么就是属性引用。
\[Base\]\] 值的选项 * unresolvable * 环境记录 * 语言类型的值  ### [IsUnresolvableReference(V)](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-isunresolvablereference "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-isunresolvablereference") 判断引用是否可达,即是否存在。 逻辑很简单,就是判断`[[base]]`字段的值是不是 `unresolvable`  未申明标志符aaaa 返回的引用记录就是不可达 ```arduino javascriptjavascript... 复制代码 console.log(bbbb) // caught ReferenceError: bbbb is not defined ``` 直接使用未申明的标志符一般都会抛出异常,也有例外 ```csharp javascriptjavascript... 复制代码 typeof aaaa // "undefined" ``` 至于为什么, 协议里做了明确的说明,引用不可达, 直接返回的是 "undefined"。  ### [IsSuperReference ( V )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-issuperreference "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-issuperreference") 【可以跳过】 就是判断 引用记录`[[ThisValue]]`的值是不是空值。  那你可能会问,这个值啥时候不是 `empty`呢?你在使用`super[propertyName]`或者 `super.propertyName`的时候,就会生成`[[ThisValue]]`不是 empty 的 的引用记录。  ### [IsPrivateReference ( V )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-isprivatereference "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-isprivatereference") 【可以跳过】 逻辑是通过判断引用记录的`[[ReferencedName]]`的值是不是 [Private Name](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-private-names "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-private-names")。用于class的私有字段。  这里出现了 [Private Name](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-private-names "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-private-names"),之前有提到过,是协议类型。 表示一个私有类元素(字段、方法或访问器)的键。每个私有名称有一个相关的不可变`[[Description]]`是一个字符串值。可以使用 [PrivateFieldAdd](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privatefieldadd "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privatefieldadd") 或 [PrivateMethodOrAccessorAdd](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privatemethodoraccessoradd "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privatemethodoraccessoradd") 在任何 ECMAScript 对象上安装私有名称,然后使用 [PrivateGet](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privateget "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privateget") 和 [PrivateSet](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privateset "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-privateset") 读或写。 **白话文就是 有一些内部不可变的字符串白名单。 添加,获取,设置都得通过内部的方法。** ### [GetValue(V)](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-getvalue "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-getvalue") 从引用记录中取值,这个是重点。 引用记录本身和取值之后返回的值的是大不同的。 分组运算符和属性方法调用都是拿着引用进行操作的。 可还记得,`[[Base]]` 的值可能是语言类型的值,也可能是环境记录。 所以值的来源就有两种方式,引用记录本身,还有环境记录,而环境记录还会嵌套。到这里,是不是想到了作用域的概念了, 细节会在对应的作用域链章节到来。 抽象方法逻辑如下:参数 V 就是引用记录  1. 不可达,抛出 ReferenceError 2. 如果是属性引用,直接从 `[[Base]]`上取值。 如果是私有引用(class),调用相关方法取值。 注意一下, 有 `ToObject`的操作,然后用 `baseObj.[[Get]]`去取值。 3. 否则从环境记录里面去取值, 环境记录可以`outerENV`关联外部的环境记录,形成链路。 再回顾`[[Base]]`提到的代码, * `console.log(obj.name)`中的`obj.name`是对象属性,直接去引用记录的`[[Base]]`去取值,也就是从对象上取属性值。 * `console.log(a)` 中的 `a`, 其不是对象属性,所以其 `[[Base]]`是环境记录,从**环境记录**去取值。 ```ini javascript 复制代码 var a = 10; var obj = {}; obj.name = 'object name'; console.log(obj.name); console.log(a); ``` ### [PutValue ( V, W )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-putvalue "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-putvalue") 给引用记录设置值(V为引用记录,W为值)。其实不能理解为引用记录设置值,实际上可能是给对象/全局对象添加属性,也可能是给环境记录新增绑定关系。  1. 如果 V 不是引用记录,直接抛出错误 2. 给未申明的标志符直接赋值,严格模式和非严格模式是有区别的。 非严格模式会在全局对象上新增属性,严格模式呢,抛出异常。 ```scss javascript 复制代码 function test(){ aaaa = 100; console.log(globalThis.aaaa) } test(); // 100 console.log(aaa); // 100 ``` ```scss javascript 复制代码 function test(){ "use strict" aaaa = 100; console.log(globalThis.aaaa) } test(); // caught ReferenceError: aaaa is not defined ``` 2. 属性赋值。 有普通属性赋值,还有私有属性赋值。赋值在严格模式下还可能失败,比如属性描述符设置了不可写。 ```ini javascript 复制代码 class Person { name = 'name'; #age = 18; setAge(val){ this.#age = val // 私有属性赋值 } } var person = new Person(); person.name = 'new name'; // 普通属性赋值 person.setAge(16) ``` ```ini javascript 复制代码 function test(){ "use strict" var obj = { name: "name" }; Object.defineProperty(obj, "name", { writable : false }); obj.name = 'new name'; }; test(); // caught TypeError: Cannot assign to read only property 'name' of object '#