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
  • 环境记录
  • 语言类型的值

IsUnresolvableReference(V)

判断引用是否可达,即是否存在。 逻辑很简单,就是判断[[base]]字段的值是不是 unresolvable

未申明标志符aaaa 返回的引用记录就是不可达

arduino 复制代码
javascriptjavascript...
复制代码
console.log(bbbb)   // caught ReferenceError: bbbb is not defined

直接使用未申明的标志符一般都会抛出异常,也有例外

csharp 复制代码
javascriptjavascript...
复制代码
typeof aaaa  // "undefined"

至于为什么, 协议里做了明确的说明,引用不可达, 直接返回的是 "undefined"。

IsSuperReference ( V ) 【可以跳过】

就是判断 引用记录[[ThisValue]]的值是不是空值。

那你可能会问,这个值啥时候不是 empty呢?你在使用super[propertyName]或者 super.propertyName的时候,就会生成[[ThisValue]]不是 empty 的 的引用记录。

IsPrivateReference ( V ) 【可以跳过】

逻辑是通过判断引用记录的[[ReferencedName]]的值是不是 Private Name。用于class的私有字段。

这里出现了 Private Name,之前有提到过,是协议类型。 表示一个私有类元素(字段、方法或访问器)的键。每个私有名称有一个相关的不可变[[Description]]是一个字符串值。可以使用 PrivateFieldAddPrivateMethodOrAccessorAdd 在任何 ECMAScript 对象上安装私有名称,然后使用 PrivateGetPrivateSet 读或写。

白话文就是 有一些内部不可变的字符串白名单。 添加,获取,设置都得通过内部的方法。

GetValue(V)

从引用记录中取值,这个是重点。 引用记录本身和取值之后返回的值的是大不同的。 分组运算符和属性方法调用都是拿着引用进行操作的。

可还记得,[[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 )

给引用记录设置值(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
  1. 属性赋值。 有普通属性赋值,还有私有属性赋值。赋值在严格模式下还可能失败,比如属性描述符设置了不可写。
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 '#<Object>'
  
  1. 如果上面逻辑都没走,就是走环境记录设置绑定关系。 环境记录有好几种,逻辑也有一些变化。具体后续会细说。

GetThisValue ( V )

属性引用记录时,获取this的值,这个和后面的函数调用 this 的值有关联, 函数调用的 this 的值,就是从 特定的环境记录借用的 [[ThisValue]]

  • 如果是 IsSuperReference(V),即class或者属性方法的super属性引用,直接返回 [[ThisValue]]
  • 不然就返回 [[Base]]

super 不仅仅是 class 可以使用,对象也是可以使用 super的

javascript 复制代码
javascript
复制代码
var obj = {
  name: "name",
  toStr(){
    console.log(super.toString())
  }
};
obj.toStr() // [object Object]

到这里是不是想到了函数调用的this,这个还就真和函数调用有关系。至于具体的情况,函数章节细说。

InitializeReferencedBinding ( V, W ) 【可以跳过】

在引用记录的环境记录上初始化一个绑定。更多细节参见环境记录。 与初始化的还有一个概念,叫做实例化,后面会细说。

MakePrivateReference ( baseValue, privateIdentifier ) 【可以跳过】

创建一个私有引用记录。

可以从下面的协议的内容关联看到, fieldNameString 是一个 PrivateIdentifier, 再往下 其格式

# IdentifierName,这不就是class的私有属性的格式嘛。

相关推荐
前端毕业班6 分钟前
uni-app 小程序支持 teleport 了
前端·javascript·vue.js
东风破_35 分钟前
LLM 为什么记不住你?无状态、messages 和上下文管理
javascript·人工智能
梨子同志43 分钟前
qiankun
前端
梨子同志1 小时前
Nginx
前端
梨子同志1 小时前
NestJS
前端
梨子同志1 小时前
Next.js
前端
谭光志1 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
Hilaku1 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
陈随易2 小时前
前端项目部署只要30秒
前端·后端·程序员
本末倒置1832 小时前
从 Vue CLI 迁移到 Rsbuild 后,JSX 突然全挂了?一次排查记录
前端