JavaScript:动态导入ECMAScript模块的教程

195 阅读4分钟

如何动态导入ECMAScript模块

ECMAScript(又称ES2015,或ES)模块是一种组织JavaScript中具有凝聚力的代码块的方式。

ES模块系统有两个角色:

  1. 导入模块--使用模块的人import { func } from './myModule.js'
  2. 导入模块--导出export const func = () => {} ,并被导入的模块。

导入模块使用import 语法来导入一个依赖关系。

javascript

// The importing module
import { concat } from './concatModule.js';
concat('a', 'b'); // => 'ab'

导入的模块使用export 语法导出其组件:

javascript

// The imported module exports components
export const concat = (paramA, paramB) => paramA + paramB;

import { concat } from './concatModule.js' 使用ES模块的方式是静态的:意味着模块之间的依赖关系在编译时就已经知道。静态依赖总是包含在应用程序的包中。

静态导入在大多数情况下是有效的。但有时你想节省一点客户端的带宽,动态地加载模块。

如果你把import 作为一个函数--import(pathToModule) ,你就可以动态导入模块,这个功能从ES2020开始提供。

让我们来看看动态导入是如何工作的,以及什么时候它是有用的。

1.模块的动态导入

import 关键字作为一个函数使用时:

javascript

const module = await import(path);

它返回一个承诺并启动一个异步任务来加载模块。如果模块被成功加载,那么承诺会解析到模块内容,否则,承诺会拒绝。

path 可以是任何表达式,其值为表示路径的字符串。有效的路径表达式有:

javascript

// Classic string literals
const module1 = await import('./myModule.js');
// A variable
const path = './myOtherModule.js';
const module2 = await import(path);
// Function call
const getPath = (version) => `./myModule/versions/${version}.js`;
const moduleVersion1 = await import(getPath('v1.0'));
const moduleVersion2 = await import(getPath('v2.0'));

因为import(path) 返回一个承诺,所以它与async/await 语法非常匹配。例如,让我们在一个异步函数中加载一个模块。

javascript

async function loadMyModule() {
  const myModule = await import('./myModule.js');
  // ... use myModule
}
loadMyModule();

现在,知道了如何加载模块,让我们从导入的模块中提取组件(默认或命名)。

2.导入组件

2.1 导入命名出口

让我们考虑下面这个模块,命名为namedConcat.js

javascript

// namedConcat.js
export const concat = (paramA, paramB) => paramA + paramB;

namedConcat 执行concat 函数的命名导出。

如果你想动态地导入namedConcat.js ,并访问命名的导出concat ,那么只需通过命名的导出对解析的模块对象进行结构化。

javascript

async function loadMyModule() {
  const { concat } = await import('./namedConcat.js');
  concat('b', 'c'); // => 'bc'
}
loadMyModule();

2.2默认导出的导入

要动态地导入一个缺省,只需从模块对象中读取default 属性。

假设defaultConcat.js 将函数作为default 的导出:

javascript

// defaultConcat.js
export default (paramA, paramB) => paramA + paramB;

当动态导入defaultConcat.js ,特别是访问default 导出时,你需要的仅仅是读取default 属性。

但是有一个细微的差别。default 是JavaScript中的一个关键字,所以它不能被用作变量名。你要做的是使用去结构化与别名:

javascript

async function loadMyModule() {
  const { default: defaultFunc } = await import('./defaultConcat.js');
  defaultFunc('b', 'c'); // => 'bc'
}
loadMyModule();

2.3 导入混合内容

如果导入的模块导出了default 和多个命名的导出,那么你可以使用单一的析构访问所有这些组件。

javascript

async function loadMyModule() {
  const { 
    default: defaultImport,
    namedExport1,
    namedExport2
  } = await import('./mixedExportModule.js');
  // ...
}
loadMyModule();

3.何时使用动态导入

我建议在有条件地导入大模块时使用动态导入。

例如,你可能会根据运行时的条件,不时地使用该模块。或者你可能想加载一个大模块的不同版本,这也取决于运行时间条件。

javascript

async function execBigModule(condition) {
  if (condition) {
    const { funcA } = await import('./bigModuleA.js');
    funcA();
  } else {
    const { funcB } = await import('./bigModuleB.js');
    funcB();
  }
}
execBigModule(true);

对于只有几十行代码的小模块(如前面例子中的namedConcat.jsdefaultConcat.js ),动态导入并不值得麻烦。

4.总结

要动态加载一个模块,可以把import(path) 作为一个函数来调用,其参数是模块的指定符(又称路径)。

const module = await import(path) 返回一个承诺,该承诺可解析为一个包含导入模块组件的对象。

在该对象中,default 属性包含默认的出口,而命名的出口则包含在相应的属性中。

javascript

const { 
  default: defaultComponent, 
  namedExport1,
  namedExport2
} = await import(path);

Node.js(13.2及以上版本)和大多数现代浏览器都支持动态导入。