2020 年 2 月 8 日

动态导入

我们在前几章中介绍的导出和导入语句被称为“静态”语句。语法非常简单且严格。

首先,我们无法动态生成 import 的任何参数。

模块路径必须是一个原始字符串,不能是函数调用。以下代码将无法运行

import ... from getModuleName(); // Error, only from "string" is allowed

其次,我们无法有条件地或在运行时进行导入

if(...) {
  import ...; // Error, not allowed!
}

{
  import ...; // Error, we can't put import in any block
}

这是因为 import/export 旨在为代码结构提供一个主干。这是一件好事,因为代码结构可以被分析,模块可以被收集并打包到一个文件中,未使用的导出可以被移除(“tree-shaken”)。这只有在导入/导出的结构简单且固定时才有可能。

但是我们如何按需动态导入模块?

import() 表达式

import(module) 表达式加载模块并返回一个 promise,该 promise 解析为包含其所有导出的模块对象。它可以在代码中的任何位置调用。

我们可以在代码的任何位置动态使用它,例如

let modulePath = prompt("Which module to load?");

import(modulePath)
  .then(obj => <module object>)
  .catch(err => <loading error, e.g. if no such module>)

或者,如果在 async 函数内部,我们可以使用 let module = await import(modulePath)

例如,如果我们有以下模块 say.js

// 📁 say.js
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

…那么动态导入可以这样

let {hi, bye} = await import('./say.js');

hi();
bye();

或者,如果 say.js 有默认导出

// 📁 say.js
export default function() {
  alert("Module loaded (export default)!");
}

…那么,为了访问它,我们可以使用模块对象的 default 属性

let obj = await import('./say.js');
let say = obj.default;
// or, in one line: let {default: say} = await import('./say.js');

say();

以下是完整示例

结果
say.js
index.html
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

export default function() {
  alert("Module loaded (export default)!");
}
<!doctype html>
<script>
  async function load() {
    let say = await import('./say.js');
    say.hi(); // Hello!
    say.bye(); // Bye!
    say.default(); // Module loaded (export default)!
  }
</script>
<button onclick="load()">Click me</button>
请注意

动态导入在常规脚本中起作用,它们不需要 script type="module"

请注意

尽管 import() 看起来像函数调用,但它是一种特殊的语法,碰巧使用括号(类似于 super())。

因此,我们无法将 import 复制到变量中或使用 call/apply。它不是一个函数。

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题 或提交拉取请求,而不是评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行 - 将其包装在 <pre> 标签中,对于 10 行以上 - 使用沙箱(plnkrjsbincodepen…)