ECMAScript 引用记录

前言

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呢, 这背后的执行逻辑

这个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 * 环境记录 * 语言类型的值 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/21a6679914caf2bee0f767bedb3ad688.webp) ### [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` ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/8e006263408255d8242044da9b5d0588.webp) 未申明标志符aaaa 返回的引用记录就是不可达 ```arduino javascriptjavascript... 复制代码 console.log(bbbb) // caught ReferenceError: bbbb is not defined ``` 直接使用未申明的标志符一般都会抛出异常,也有例外 ```csharp javascriptjavascript... 复制代码 typeof aaaa // "undefined" ``` 至于为什么, 协议里做了明确的说明,引用不可达, 直接返回的是 "undefined"。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/0cc666270faa0101c6d28e10e6ee51bd.webp) ### [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]]`的值是不是空值。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/311f71a3595ec2f3c98cb21618f60eeb.webp) 那你可能会问,这个值啥时候不是 `empty`呢?你在使用`super[propertyName]`或者 `super.propertyName`的时候,就会生成`[[ThisValue]]`不是 empty 的 的引用记录。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/db8ff62311e015b89250fb874464f2c5.webp) ### [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的私有字段。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/264cc70b45d8b5e57625e08af3e5227a.webp) 这里出现了 [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 就是引用记录 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/a9f982b0e9a51e51007657f0df11ad74.webp) 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为值)。其实不能理解为引用记录设置值,实际上可能是给对象/全局对象添加属性,也可能是给环境记录新增绑定关系。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/b02bc3b185fffa2b03a4f02f731908cc.webp) 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 '#' ``` 3. 如果上面逻辑都没走,就是走环境记录设置绑定关系。 环境记录有好几种,逻辑也有一些变化。具体后续会细说。 ### [GetThisValue ( V )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-getthisvalue "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-getthisvalue") 属性引用记录时,获取this的值,这个和后面的函数调用 this 的值有关联, 函数调用的 this 的值,就是从 特定的环境记录借用的 `[[ThisValue]]`。 * 如果是 [IsSuperReference](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")(V),即class或者属性方法的super属性引用,直接返回 `[[ThisValue]]` * 不然就返回 `[[Base]]` super 不仅仅是 class 可以使用,对象也是可以使用 super的![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/4077a70f896c4da1b207a5c851788df0.webp) ```javascript javascript 复制代码 var obj = { name: "name", toStr(){ console.log(super.toString()) } }; obj.toStr() // [object Object] ``` 到这里是不是想到了函数调用的`this`,这个还就真和函数调用有关系。至于具体的情况,函数章节细说。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/32668db3cb374ed0cea182c2fd63a45a.webp) ### [InitializeReferencedBinding ( V, W )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-initializereferencedbinding "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-initializereferencedbinding") 【可以跳过】 在引用记录的环境记录上初始化一个绑定。更多细节参见环境记录。 与初始化的还有一个概念,叫做实例化,后面会细说。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/6a38f676da32abff2b30464c7b9459c5.webp) ### [MakePrivateReference ( baseValue, privateIdentifier )](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-initializereferencedbinding "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-initializereferencedbinding") 【可以跳过】 创建一个私有引用记录。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/c960e2dc52d52124214f3698d2bf2ac0.webp) 可以从下面的协议的内容关联看到, fieldNameString 是一个 [PrivateIdentifier](https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23prod-PrivateIdentifier "https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F%23prod-PrivateIdentifier"), 再往下 其格式 `# IdentifierName`,这不就是class的私有属性的格式嘛。 ![](https://oss.xyyzone.com/jishuzhan/article/1915311912352546817/341758c3e7318175929c47e776095ce6.webp)

相关推荐
云端看世界几秒前
ECMAscript 暂时性死区, with语句,申明和赋值等背后的逻辑
前端·javascript
云端看世界1 分钟前
ECMAScript 杂谈:再谈数值
前端·javascript
云端看世界2 分钟前
ECMAScript 杂谈:快慢数组
前端·javascript
云端看世界3 分钟前
ECMAScript for, for-in for-of 基本原理
前端·javascript·ecmascript 6
云端看世界4 分钟前
ECMAScript 严格模式的限制和例外情况
前端·javascript
云端看世界5 分钟前
[ECMAScript] 杂谈: setTimeout 还有多少未知
前端·javascript
云端看世界5 分钟前
ECMAScript 杂谈:对象属性照妖镜
前端·javascript
云端看世界7 分钟前
ECMAScript 函数之动态执行脚本
前端·javascript·ecmascript 6
云端看世界9 分钟前
ECMAScript 函数this全解析 上
前端·javascript
云端看世界13 分钟前
ECMAScript 函数对象和 new你到底做了什么
前端·javascript·ecmascript 6