我对ES模块的理解正确吗

时间:2018-09-02 15:32:52

标签: javascript ecmascript-6 module

所以最近我想到了ES模块,这就是我认为它们起作用的方式:

  1. 存在一个用户无法访问的全局对象(我们将其称为#moduleMap)。它将模块的绝对URL映射到其导出:
#moduleMap = {
  "https://something.com/module.mjs": { exportName: "value" }
}
  1. 评估模块的过程如下:
    • 模块已获取
    • 模块被解析为ast
    • ast修改如下:

import { x } from "./module1.mjs" =>所有x引用均替换为 #moduleMap["abs path module1.mjs"].x(正在提取导入的模块)

export const y = "some value" => #moduleMap["abs path to this module"].y = "some value"

(正如@Bergi指出的那样,出口并不是那么简单,因为没有悬挂出口,因此const的死区和函数的悬挂不仅仅反映在属性分配中)

(上面是产生“实时绑定”的所谓绑定)

  • 对每个导入的模块重复此操作
  • 当获取,解析和修改所有模块时,评估从输入模块开始(每个模块都是独立执行的(〜在严格模式下在IIFE中进行包装)。

正如@Bergi指出的那样,在执行模块代码本身之前(从循环依赖中除外),从入口模块开始急切地评估模块,并评估模块的导入(实际上,循环依赖除外),这实际上意味着最先执行的导入将首先执行。

  • 当评估到达访问#moduleMap["some module"]的任何代码时,浏览器将检查模块是否已评估
    • 如果未进行评估,则在此时评估该评估,然后评估返回到该位置(现在,模块(或其确切的输出)已“缓存”在{{1 }})
    • 如果已对其进行评估,则可以从#moduleMap来访问导入
  • 对于从其他模块重新分配导入的情况,浏览器将引发错误

基本上,这一切都是在发生AFAIK。我说得对吗?

2 个答案:

答案 0 :(得分:2)

您了解得很好,但是有一些异常应该纠正。

ES模块

在ECMA-262中,所有模块都具有以下一般形状:

Abstract Module {
  Environment // Holds lexical environment for variables declared in module
  Namespace   // Exotic object that reaches into Environment to access exported values

  Instantiate()
  Evaluate()
}

模块可以来自很多不同的地方,因此此Abstract Module有“子类”。我们在这里所说的称为Source Text Module

Source Text Module : Abstract Module {
  ECMAScriptCode     // Concrete syntax tree of actual source text
  RequestedModules   // List of imports parsed from source text
  LocalExportEntries // List of exports parsed from source text

  Evaluate()         // interprets ECMAScriptCode
}

在模块(const a = 5中声明变量时,该变量将存储在模块的Environment中。如果在其中添加export声明,它也会显示在LocalExportEntries中。

当您import模块时,实际上是在抓取Namespace对象,该对象具有 exotic 行为,这意味着尽管它看起来像是普通对象,但类似获取和设置属性可能会执行与您预期不同的事情。

Module Namespace Objects的情况下,获取属性namespace.a,实际上是在关联的Environment中将该属性作为名称查找。

所以如果我有两个模块A和B:

// A
export const a = 5;
// B
import { a } from 'A';

console.log(a);

模块B导入A,然后在模块B中将a绑定到A.Namespace.a。因此,每当在模块{{1}中访问a时,它实际上就会在b上进行查找,而后者会在A.Namespace中进行查找。 (这是实时绑定的实际工作方式。)

最后进入模块图的主题。所有模块都将被实例化,然后才能进行评估。实例化是解析模块图并准备要评估的模块的过程。

模块图

“模块映射”的概念是特定于实现的,但是出于浏览器和节点的目的,它看起来像这样:A.Environment

动态Module Map <URL, Abstract Module>是显示浏览器/节点如何使用此模块映射的好方法:

import()

您实际上可以在Node.js中看到此确切行为:https://github.com/nodejs/node/blob/e24fc95398e89759b132d07352b55941e7fb8474/lib/internal/modules/esm/loader.js#L98

答案 1 :(得分:1)

  

export const y = "some value" => #moduleMap["abs path to this module"].y = "some value"

     

(上面是产生“实时绑定”的所谓绑定)

是的,基本上-您正确地理解它们都引用相同的内容,并且当模块重新分配时,进口商将注意到这一点。

但是,由于const y保留了const变量声明,所以它有点复杂,因此它仍然受时间死区的约束,function声明仍受悬挂。当您将导出视为对象的属性时,这不能很好地反映出来。

  
      
  • 当评估达到访问#moduleMap["some module"]的任何代码时,浏览器将检查模块是否已评估      
        
    • 如果未对它进行评估,则会在此时评估该评估,然后评估返回到该位置(现在模块(或确切地说是其导出)已“缓存”在#moduleMap中)
    •   
    • 如果已对其进行评估,则可以从#moduleMap["some module"].someImport来访问导入
    •   
  •   

不。当解释器遇到导入值的第一个引用时,模块评估就不会延迟进行。而是严格按照import语句的顺序评估模块(从输入模块开始)。在对模块的所有依赖关系进行评估之前(除了对其自身具有循环依赖关系之外),都不会开始对模块进行评估。