我已经尝试在互联网上搜索导入模块的执行顺序。例如,假设我有以下代码:
import "one"
import "two"
console.log("three");
其中one.js
和two.js
定义如下:
// one.js
console.log("one");
// two.js
console.log("two");
控制台输出是否保证为:
one
two
three
还是未定义?
我已经尝试在互联网上搜索导入模块的执行顺序。例如,假设我有以下代码:
import "one"
import "two"
console.log("three");
其中one.js
和two.js
定义如下:
// one.js
console.log("one");
// two.js
console.log("two");
控制台输出是否保证为:
one
two
three
还是未定义?
JavaScript 模块是异步评估的。但是,所有导入都在模块主体进行导入之前进行评估。这使得 JavaScript 模块不同于Node 中的 CommonJS 模块或没有该属性的<script>
标签。JavaScript 模块在加载async
方式方面更接近AMD 规范。有关更多详细信息,请参阅Axel Rauschmayer的探索 ES6的第 16.6.1 节。
因此,在提问者提供的示例中,无法保证执行的顺序。有两种可能的结果。我们可能会在控制台中看到:
one
two
three
或者我们可能会看到:
two
one
three
换句话说,两个导入的模块可以console.log()
按任何顺序执行它们的调用;它们彼此是异步的。但它们肯定会在导入它们的模块主体之前执行,因此"three"
保证最后记录。
使用顶级await
语句(现在在 Chrome 中实现)时可以观察到模块的异步性。例如,假设我们稍微修改提问者的例子:
// main.js
import './one.js';
import './two.js';
console.log('three');
// one.js
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('one');
// two.js
console.log('two');
当我们运行时main.js
,我们会在控制台中看到以下内容(添加了时间戳以进行说明):
[0s] two
[1s] one
[1s] three
根据petamoriken 的回答,从 ES2020 开始,似乎可以保证非异步模块的评估顺序。因此,如果您知道要导入的模块都不包含顶级await
语句,它们将按照导入的顺序执行。在提问者的示例中,控制台输出将始终为:
one
two
three
根据最新规范InnerModuleEvaluation,module.ExecuteModule()
由于[[RequestedModules]] 是源代码出现的有序列表,因此保证了 的顺序。
// 16.2.1.5.2.1 rough sketch
function InnerModuleEvaluation(module, stack, index) {
// ...
// 8
module.[[PendingAsyncDependencies]] = 0;
// ...
// 11: resolve dependencies (source code occurrences order)
for (required of module.[[RequestedModules]]) {
let requiredModule = HostResolveImportedModule(module, required);
// **recursive**
InnerModuleEvaluation(requiredModule, stack, index);
// ...
if (requiredModule.[[AsyncEvaluation]]) {
++module.[[PendingAsyncDependencies]];
}
}
// 12: execute
if (module.[[PendingAsyncDependencies]] > 0 || module.[[HasTLA]]) {
module.[[AsyncEvaluation]] = true;
if (module.[[PendingAsyncDependencies]] === 0) {
ExecuteAsyncModule(module);
}
} else {
module.ExecuteModule();
}
// ...
}
控制台输出始终如下:
one
two
three