前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解 ECMAScript 规范(1)

理解 ECMAScript 规范(1)

作者头像
前端老王
发布2020-09-23 11:41:40
5430
发布2020-09-23 11:41:40
举报
文章被收录于专栏:前端时空前端时空

编者按:本文是由 李松峰 翻译,翻译本文的目的是尝试给出 ECMAScript 规范中核心术语的译法,供同好品评。 本文英文原文:https://v8.dev/blog/understanding-ecmascript-part-1

前言

即便你懂JavaScript,阅读其规范也会让人畏缩。

让我们从一个具体的例子开始,然后通过规范去理解它。下面的代码演示了Object.prototype.hasOwnProperty的用法:

代码语言:javascript
复制
const o = { foo: 1 };
o.hasOwnProperty('foo'); // true
o.hasOwnProperty('bar'); // false

o并没有一个叫hasOwnProperty的属性,因此要沿原型链向上查找。于是,在o的原型Object.prototype上找到了它。

为描述Object.prototype.property的工作原理,规范使用了类似伪代码的说明:

Object.prototype.hasOwnProperty(V) 在以参数V调用hasOwnProperty方法时,将执行以下步骤:

  1. 令P为? ToPropertyKey(V);
  2. 令O为? ToObject(this值);
  3. 返回? HasOwnProperty(O, P)。

以及

HasOwnProperty(O, P) 抽象操作HasOwnProperty用于确定对象是否有一个以指定属性为键的自有属性。返回布尔值。这个操作以参数O和P调用,其中O是对象,P是属性键。这个抽象操作执行以下步骤。

  1. 断言:Type(O)为Object;
  2. 断言:IsPropertyKey(P)为true;
  3. 令desc为? O.[[GetOwnProperty]](P);
  4. 若desc为undefined,返回false;
  5. 返回true。

什么是“抽象操作”?[[]]里面的东西表示什么?为什么把一个?放在函数前面?“断言”又是什么意思?

语言类型与规范类型

规范使用了undefined、true和false这些我们在JavaScript中已经知道的值。这些都是语言值,即规范中定义的语言类型的值。

规范内部也使用语言值,比如某个内部数据类型的字段可能包含true或false。相对而言,JavaScript引擎通常不会在内部使用语言值。例如,如果JavaScript引擎是用C++写的,那通常会使用C++的true和false,而这并不是JavaScript语言值true和false的内部表示。

除了语言类型,规范也有自己的规范类型。规范类型是只存在于规范中的类型,JavaScript语言中不存在。JavaScript引擎不需要(但完全可以)实现它们。本文将介绍规范类型记录(Record)及其子类型完成记录(Completion Record)。

抽象操作

抽象操作是ECMAScript规范定义的函数,定义它们的目的是为了让规范更简洁。JavaScript引擎不必在内部实现这些函数。这些函数不能直接在JavaScript中调用。

内部栏位及内部方法

内部栏位(slot)和内部方法包含在[[]]中。

内部栏位是JavaScript对象或规范类型的数据成员,用于存储对象的状态。内部方法是JavaScript对象的内部成员函数。

比如,每个JavaScript对象都有一个内部栏位[[Prototype]]和一个内部方法[[GetOwnProperty]]。

内部栏位和内部方法不能在JavaScript中使用。换句话说,不能访问o.[[Prototype]]或调用o.[[GetOwnProperty]]()。JavaScript引擎可以为了内部使用实现它们,但不是必需的。

有时候内部方法也会委托到名字类似的抽象操作,比如普通对象(ordinary object)的[[GetOwnProperty]]:

[[GetOwnProperty]](P) 在以属性键P调用内部方法[[GetOwnProperty]]时,将执行以下步骤:

  1. 返回! OrdinaryGetOwnProperty(O, P)。

(下一篇文章会介绍这里的叹号表示什么意思。)

OrdinaryGetOwnProperty不是内部方法,因为它不与任何对象关联,而是以接收参数的形式取得要操作的对象。

OrdinaryGetOwnProperty前面的“ordinary”(普通)表示它只操作普通对象。ECMAScript对象要么是普通对象(ordinary),要么是异质对象(exotic)。普通对象必须具有一组被称为基本内部方法(essential internal methods)的方法所定义的默认行为。如果某个对象修改了默认行为(即覆盖或重写了一个或多个基本内部方法。——译者注),那它就是异质对象。

大家最熟悉的Array就是异质对象,因为其length属性的行为与默认行为不同:设置数组的length属性可能会从数组中删除元素。

这里给出了所有基本内部方法(普通对象11个,函数对象2个。——译者注)。

完成记录

前面例子中出现的问号和叹号表示什么意思?要理解它们,需要先理解完成记录(Completion Record)!

完成记录是一种规范类型(只在规范中使用)。JavaScript引擎不需要实现对应的内部数据类型。

完成记录是一种记录类型(Record),而记录具有一组固定的命名字段。完成记录具有以下3个字段。

名字

说明

[[Type]]

值为normal、break、continue、return或throw中的一个。其中,normal表示正常完成(normal completion),所有其他值表示突然完成(abrupt completion

[[Value]]

完成发生时产生的值,比如函数返回的值或抛出的异常

[[Target]]

用于定向转移控制(本文不讨论)

所有抽象操作都会隐式返回一个完成记录。即便一个抽象操作看起来返回简单类型(如Boolean)的值,这个值也会被隐式包装在一个normal类型(正常完成)的完成记录中返回(参见隐式完成值)。

注1:规范本身在这方面也不是完全一致。有一些辅助函数会返回裸值,而这些值将直接被使用,无需从完成记录中提取。不过这种情况在上下文中通常能够一目了然。

注2:规范编辑也在致力于更显式地处理完成记录。

如果某个算法抛出异常,则意味着返回的完成记录的[[Type]]为throw,[[Value]]为异常对象。我们这里不讨论break、continue和return类型(规范中没有相应的例子,因为这几种类型不能跨函数。——译者注)。

ReturnIfAbrupt(argument)表示执行如下步骤:

  1. 若argument为突然完成,返回argument;
  2. 设argument为argument.[[Value]]。

换句话说,对于完成记录,如果是突然完成,则立即返回;如果是正常完成,则提取完成记录的值。

ReturnIfAbrupt看起来虽然像函数调用,但它不是。ReturnIfAbrup会导致它所在位置的函数返回,而不是ReturnIfAbrupt本身返回。ReturnIfAbrupt有点像C语言中的宏。

ReturnIfAbrupt可以这样用:

  1. 令obj为Foo();(obj是一个完成记录。)
  2. ReturnIfAbrupt(obj);
  3. Bar(obj)。(如果到了这一步,obj已经变成了从完成记录中提取出来的值。)

现在该说到问号了:? Foo()等价于ReturnIfAbrupt(Foo())。显然,使用简写(?)可以省去每次都明确写出错误处理代码的麻烦。

类似地,“令val为! Foo()”等价于:

  1. 令val为Foo();(val是一个完成记录。)
  2. 断言:val非突然完成;
  3. 设val为val.[[Value]]。

(换句话说,叹号表示从正常完成记录中提取值。——译者注 )

知道了这些之后,就可以把前面的Object.prototype.hasOwnProperty以完整但冗余的形式重写如下:

Object.prototype.hasOwnProperty(V)

  1. 令P为ToPropertyKey(V);
  2. 若P为突然完成,返回P;
  3. 设P为P.[[Value]];
  4. 令O为ToObject(this值);
  5. 若O为突然完成,返回O;
  6. 设O为O.[[Value]];
  7. 令temp为HasOwnProperty(O, P);
  8. 若temp为突然完成,返回temp;
  9. 设temp为temp.[[Value]];
  10. 返回NormalCompletion(temp)。

把抽象操作HasOwnProperty()重写如下:

HasOwnProperty(O, P)

  1. 断言:Type(O)为Object;
  2. 断言:IsPropertyKey(P)为true;
  3. 令desc为O.[[GetOwnProperty]](P);
  4. 若desc为突然完成,返回desc;
  5. 设desc为desc.[[Value]];
  6. 若desc为undefined,返回NormalCompletion(false);
  7. 返回NormalCompletion(true)。

进而把内部方法O.[[GetOwnProperty]]以不带叹号的形式重写如下:

O.[[GetOwnProperty]]

  1. 令temp为OrdinaryGetOwnProperty(O, P);
  2. 断言:temp非突然完成;
  3. 令temp为temp.[[Value]];
  4. 返回NormalCompletion(temp);

这里假设temp是个新的临时变量,不与任何其他变量冲突。

这里也用到了前面说的当返回语句返回非完成记录时,实际上返回值将被隐式包装在一个NormalCompletion中。

扩展学习:返回? Foo()

规范中使用“返回? Foo()”这种写法,为什么还要加个问号呢?

“返回? Foo()”扩展后是:

  1. 令temp为Foo();
  2. 若temp为突然完成,返回temp;
  3. 设temp为temp.[[Value]];
  4. 返回NormalCompletion(temp)。

这跟“返回Foo()”完全一样:如果是突然完成,返回突然完成记录;如果是正常完成,返回正常完成记录。

写成“返回Foo()”只是为了编辑方便,为了更明确地表示返回的Foo()是一个完成记录。

断言

规范中的“断言”提示算法中不变的条件。添加这些“断言”是为了明确起见,不要求实现。换句话说,实现不需要检查这些条件。

挑战

抽象操作也会委托给其他抽象操作(见下图),但根据本文的介绍,大家应该能推断出这些操作最终干了什么事。这里面会碰到属性描述符(Property Descriptor),也是一种规范类型。

小结

我们通过规范看到了一个简单的方法Object.prototype.hasOwnProperty和它调用的抽象操作,知道了?和!与错误处理有关,也了解了语言类型、规范类型、内部栏位和内部方法。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端时空 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 语言类型与规范类型
  • 抽象操作
  • 内部栏位及内部方法
  • 完成记录
    • 扩展学习:返回? Foo()
    • 断言
    • 挑战
    • 小结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档