让我们以一个问题开始发帖。
名为increment 的ES2015模块包含以下代码。
// increment.js
let counter = 0;
counter++;
export default counter;
然后在另一个模块consumer 里面,上述模块increment 被导入2次。
// consumer.js
import counter1 from './increment';
import counter2 from './increment';
counter1; // => ???
counter2; // => ???
问题是:当consumer 模块运行时,变量counter1 和counter2 的内容是什么?
要回答这个问题,首先,你需要了解JavaScript如何评估和导入模块。
1.模块评估
了解JavaScript内部如何工作的一个好方法是看规范。
就规范而言,每个JavaScript模块都与一个Module Record相关联。模块记录有一个方法Evaluate() ,用来评估这个模块。
如果这个模块已经被评估成功,返回undefined; [...]。否则,过渡性地评估该模块的所有模块依赖关系,然后评估该模块。
因此,同一个模块只被评估一次。
不幸的是,问题并没有就此结束。如何确保以相同的路径调用2次import语句返回相同的模块?
2.解决导入的问题
HostResolveImportedModule()的抽象操作负责将路径(也称为指定器)与具体模块联系起来。
javascript
import module from 'path';
根据该规范。
HostResolveImportedModule 的实现必须符合以下要求:
- 正常的返回值必须是
Module Record的一个具体子类的实例。 - 如果与
referencingScriptOrModule, specifier对应的Module Record不存在或不能被创建,必须抛出一个异常。 - 每次以特定的
referencingScriptOrModule, specifier对作为参数调用这个操作时,如果正常完成,它必须返回相同的Module Record实例。
让我们以一种人类可读的方式来回顾一下发生了什么:
HostResolveImportedModule(referencingScriptOrModule, specifier) 是一个抽象的操作,返回一个与 referencingScriptOrModule, specifier对应的模块。
- 参数
referencingScriptOrModule是当前的模块,即进行导入的模块。 - 参数
specifier是与import语句中的模块路径相对应的字符串。
最后,HostResolveImportedModule() 说,当从同一路径导入模块时,将导入同一模块。
javascript
import moduleA from 'path';
import moduleB from 'path';
import moduleC from 'path';
// moduleA, moduleB and moduleC are equal
moduleA === moduleB; // => true
moduleB === moduleC; // => true
有趣的是,该规范说主机(浏览器、Node或任何试图运行JavaScript的东西)必须提供HostResolveImportedModule() 的具体实现。
3.答案
在回顾了规范之后,你知道一个JavaScript模块会被评估一次。而且,当从同一路径导入模块时,会返回同一个模块实例。
让我们回到问题上。
increment 模块总是被评估一次。
javascript
// increment.js
let counter = 0;
counter++;
export default counter;
无论 increment 模块被导入多少次,counter++ 语句只被执行一次。默认导出的counter 变量的值是1 。
现在看一下consumer 模块。
// consumer.js
import counter1 from './increment';
import counter2 from './increment';
counter1; // => 1
counter2; // => 1
最后,counter1 和counter2 里面的变量consumer 都等于1 。
4.总结
仅仅通过探索所提出的简单问题,你就发现了JavaScript模块如何被评估和导入的细节。
规则很简单:同一个模块只被评估一次,换句话说,模块级范围只被执行一次。如果模块被评估过后再次被导入,它的第二次评估将被跳过,并使用已经解决的导出。
如果一个模块被多次导入,但有相同的指定符(即路径),JavaScript规范保证你会收到相同的模块实例。