Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重学js —— modules:循环模块记录 #90

Open
lizhongzhen11 opened this issue Mar 18, 2020 · 0 comments
Open

重学js —— modules:循环模块记录 #90

lizhongzhen11 opened this issue Mar 18, 2020 · 0 comments
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Mar 18, 2020

modules:循环模块记录

重复的 ExportedNames 规则意味着在 ModuleBody 中如果出现多个 export default ExportDeclaration 会报语法错误。

LexicallyDeclaredNames

注意:在 模块 顶层,将函数声明视为词汇声明而不是 var 声明。

抽象模块记录

模块记录封装有关单个模块的导入和导出的结构信息。此信息用于链接已连接模块集的导入和导出。模块记录包含四个字段,仅在求值时使用。

出于规范目的,模块记录值是 Record 类型值且可以认为它存在于简单的面向对象的层次结构中,其中模块记录是具有抽象和具体子类的抽象类。该规范定义名为 Cyclic Module Record 的抽象子类且其具体子类命名为 Source Text Module Record。其它规范和实现可能会定义与它们定义的可选的模块定义功能相对应的其它模块记录子类。

模块记录定义的字段在下表列出。所有模块定义的子类都包含这些字段。模块记录也定义抽象方法在下面第二个表中列出。所有模块定义子类必须提供这些抽象方法的具体实现。

模块记录字段
字段名 值类型 含义
[[Realm]] Realm Record / undefined 创建该模块的领域。如果没赋值默认是 undefined
[[Environment]] 词法环境 / undefined 包含此模块的顶级绑定的 词法环境。当模块被链接时该字段被赋值。
[[Namespace]] Object / undefined 该模块创建的 模块命名空间对象。否则是 undefined
[[HostDefined]] 任何类型,默认值是 undefined 保留给需要将其他信息与模块关联的主机环境使用的字段。

模块记录抽象方法
方法 目的
GetExportedNames([exportStarSet]) 返回从该模块直接或非直接导出的命名列表
ResolveExport(exportName [, resolveSet]) 返回该模块导出的命名绑定。绑定由 ResolvedBinding Record 表示,格式为 {[[[Module]]:Module Record,[[BindingName]]:String}。如果导出是一个模块命名空间对象,而在任何模块中都没有直接绑定,[[BindingName]] 会被设置为 "*namespace*"。如果名称不能被解析则返回 null,如果有多个绑定则返回 "ambiguous"
每次该运算被指定的 exportName 调用,resolveSet 对作为参数,如果正常完成,则必须返回相同的结果。
Link() 通过暂时释放所有模块依赖并创建模块 环境记录 来准备要求值的模块
Evaluate() 如果该模块已经成功求值,返回 undefined;如果它求值失败,抛出产生的异常。否则,先对该模块所有的模块依赖求值然后再求值该模块。
调用此方法之前,链接必须已成功完成。

循环模块记录(Cyclic Module Records)

循环模块记录 用于表示有关模块的信息,该模块可以与作为循环模块记录类型的子类的其他模块一起参与依赖性循环。非循环模块记录类型的子类的模块记录禁止和 源文本模块记录(Source Text Module Records) 参与依赖性循环。

除了 模块记录字段 表中定义的字段,循环模块记录还定义了下表中的额外字段:

循环模块记录额外字段
字段名 值类型 含义
[[Status]] unlinked / linking / linked / evaluating / evaluated 初始值 unlinked。随着模块在其整个生命周期中的进展,经过 linkinglinkedevaluatingevaluated(按此顺序)。
[[EvaluationError]] abrupt completion / undefined 抛出的 completion 类型,表示在求值过程中发生的异常。如果没有异常发生或 [[Status]] 不是 evaluated,则值为 undefined
[[DFSIndex]] 整数 / undefined 仅在链接和求值期间使用的辅助字段。如果 [[Status]]linkingevaluating,这个非负数记录 在进行依赖图的深度优先遍历期间模块首次被访问的点。
[[DFSAncestorIndex]] 整数 / undefined 仅在链接和求值期间使用的辅助字段。要么是模块自己的 [[DFSIndex]],要么是在同一强连接组件中的“较早的”模块。
[[RequestedModules]] 字符串 List 该记录所代表的模块用于请求导入模块的所有 ModuleSpecifier 字符串的 List。该 List 按源代码顺序排列。

除了 模块记录方法 表中定义的方法,循环模块记录还定义了下表中的额外方法:

循环模块记录额外方法
方法 目的
InitializeEnvironment() 初始化模块的 词法环境,包含释放所有引入的绑定,以及创建模块的 执行上下文
ExecuteModule() 在其 执行上下文 中求值模块代码

Link() 具体方法

循环模块记录的 Link 具体方法实现模块记录对应的 抽象方法。(类似java中接口和具体实现的关系)

成功的话,Link 将模块的 [[Status]]unlinked 过渡到 linked。失败的话,抛出异常且模块的 [[Status]] 仍然是 unlinked

步骤(绝大部分工作由辅助函数 InnerModuleLinking 完成):

  1. 定义 module循环模块记录
  2. 断言:module.[[Status]] 不是 linkingevaluating
  3. 定义 stack 为一个新的空 List
  4. 定义 result 为 InnerModuleLinking(module, stack, 0)
  5. 如果 resultabrupt completion
    1. 遍历 stack 中的循环模块记录 m
      1. 断言:m.[[Status]]linking
      2. m.[[Status]] 设为 unlinked
      3. m.[[Environment]] 设为 undefined
      4. m.[[DFSIndex]] 设为 undefined
      5. m.[[DFSAncestorIndex]] 设为 undefined
    2. 断言:module.[[Status]]unlinked
    3. 返回 result
  6. 断言:module.[[Status]]linkedevaluated
  7. 断言:stack 为空
  8. 返回 undefined

Evaluate() 具体方法

Evaluate 会将模块的 [[Status]]linked 过渡到 evaluated

如果执行结果为异常,该异常被记录在 [[EvaluationError]] 字段内且被将来的求值调用重新抛出。

步骤(绝大部分工作由辅助函数 InnerModuleEvaluation 完成):

  1. 定义 module循环模块记录
  2. 断言:module.[[Status]]linkedevaluated
  3. 定义 stack 为一个新的空 List
  4. 定义 result 为 InnerModuleEvaluation(module, stack, 0)
  5. 如果 resultabrupt completion
    1. 遍历 stack 中的循环模块记录 m
      1. 断言:m.[[Status]]evaluating
      2. m.[[Status]] 设为 evaluated
      3. m.[[EvaluationError]] 设为 result
    2. 断言:module.[[Status]]evaluatedmodule.[[EvaluationError]]result
    3. 返回 result
  6. 断言:module.[[Status]]evaluatedmodule.[[EvaluationError]]undefined
  7. 断言:stack 为空
  8. 返回 undefined

循环模块记录图示例

首先考虑接下来的模块图:

首先确保没有错误情况。当主机首次调用 A.Link(),假设其正常完成,且递归地链接模块 B 和模块 C,例如:

A.[[Status]] = B.[[Status]] = C.[[Status]] = linked

该预备步骤可以随时执行。之后,当主机准备好承受模块任何可能的副作用时,可以调用 A.Evaluate(),成功完成(再次假设),递归的先 CB 进行求值。此时每个模块的 [[Status]]evaluated.

然后考虑涉及链接错误的情况。如果 C 的 InnerModuleLinking 成功但是,之后 B 失败了,例如 B 引入了 C 没有提供的某些东西,之后原先的 A.Link() 也会失败,同时,AB[[Status]] 仍然保持 unlinked 值。即使 C[[Status]] 已经改为了 linked

最终,考虑到涉及求值错误情况。如果 C 的 InnerModuleEvaluation 成功但是,之后 B 失败了,例如 B 包含的代码抛出了异常,那么原先的 A.Evaluate() 将会失败。该异常会记录在 AB[[EvaluationError]] 字段,且它们的 [[Status]] 会改为 evaluatedC 也会改为 evaluated 但是,与 AB 相反,不会携带 [[EvaluationError]],因为 C 成功完成了。存储异常确保无论何时主机尝试通过调用 ABEvaluate() 方法重用 AB 模块,都会得到相同的异常。(主机不需要重用循环模块记录;类似地,主机不需要暴露对象通过这些方法抛出的异常。但是,规范可以启用。)

链接和求值错误间的差异是由于求值只能执行一次,因为它可能会引起副作用;记住求值是否已被执行是相当重要的,即使求值失败。(在这些错误情况下,意味着也需要记住异常否则之后的 Evaluate() 调用将不得不合成一个新的异常。)另一方面来说,链接无副作用,且即使失败,以后也可以重试,不会有问题。

现在考虑另一种错误情况:

在这种情况下,模块 A 声明对其他模块的依赖,但是那个模块没有 模块记录,例如,当请求 HostResolveImportedModule 时会抛异常。有多种原因能引起这种情况,例如相关资源不存在,或资源存在但是当尝试用 ParseModule 解析结果源文本时抛异常。主机可以选择通过 HostResolveImportedModule 抛出的异常来暴露失败原因。任何情况下,异常都会引起链接错误,这与以前导致 A[[Status]] 保持 unlinked 状态一样。

最后,考虑考虑下循环模块图:

此处我们确保进入点是模块 A,这样主机通过调用 A.Link() 进行操作,即在模块 A 上执行 InnerModuleLinking。依次调用模块 B 上的 InnerModuleLinking。由于循环,再次触发模块 A 上的 InnerModuleLinking,但是当 A.[[Status]] 已经是 linking 状态时进入点为空。B.[[Status]] 保持 linking 状态当控制权从 A 返回且触发 C 的 InnerModuleLinking。之后返回 C.[[Status]],其值为 linked,模块 AB 一起从 linking 过渡到 linked;设计如此,因为它们形成了牢固连接的组建。

在成功的情况下,循环模块图的求值阶段会发生类似的故事。

现在考虑一种情况,A 存在链接错误;例如,它尝试从 C 引入不存在的绑定。这种情况下,上述步骤仍然发生,包含第二次调用提前返回到模块 A 的 InnerModuleLinking。但是,一旦我们退回到模块 A 原先的 InnerModuleLinking,在 InitializeEnvironment 期间失败的话,即在 C.ResolveExport() 之后。引发的 SyntaxError 异常传播到 A.Link,重置当前堆栈中所有模块(它们仍然是 linking 状态)。因此,AB 都变为 unlinked。 请注意,C 保持 linked 状态。

最后,考虑这种情况,A 出现求值错误;例如,其源码抛出异常。在这种情况下,上述步骤的求值时间模拟仍然发生,包含从第二次调用 A 的 InnerModuleEvaluation 早期返回。但是,一旦我们退回到原先 A 的 InnerModuleEvaluation,它因假设而失败。抛出的异常过渡到 A.Evaluate(),记录当前堆栈上所有模块的错误(例如,仍然是 evaluating 状态的模块)。因此,AB 都变为 evaluatedAB[[EvaluationError]] 字段都会记录异常,然而 C 保持 evaluated 且没有 [[EvaluationError]]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN
Projects
None yet
Development

No branches or pull requests

1 participant