Skip to content
本页目录

import()函数

简介

前面介绍过,import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)。所以,下面的代码会报错。

javascript
 // 报错if (x === 2) {  import MyModual from './myModual';}

上面代码中,引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报句法错误,而不是执行时错误。也就是说,importexport命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

javascript
 const path = './' + fileName;const myModual = require(path);

上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import语句做不到这一点。

因此,有一个提案,建议引入import()函数,完成动态加载。

javascript
 import(specifier)

上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

[ES6](http://www.waibo.wang/bible/es6/html/22/22.10.html) import()返回一个 Promise 对象。下面是一个例子。

javascript
 const main = document.querySelector('main');import(`./section-modules/${someVariable}.js`)  .then(module => {    module.loadPageInto(main);  })  .catch(err => {    main.textContent = err.message;  });

import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,也会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。

import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

适用场合

下面是import()的一些适用场合。

(1)按需加载。

import()可以在需要的时候,再加载某个模块。

javascript
 button.addEventListener('click', event => {  import('./dialogBox.js')  .then(dialogBox => {    dialogBox.open();  })  .catch(error => {    /* Error handling */  })});

上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

(2)条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块。

javascript
 if (condition) {  import('moduleA').then(...);} else {  import('moduleB').then(...);}

上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。

(3)动态的模块路径

import()允许模块路径动态生成。

javascript
 import(f()).then(...);

上面代码中,根据函数f的返回结果,加载不同的模块。

注意点

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

javascript
 import('./myModule.js').then(({export1, export2}) => {  // ...·});

上面代码中,export1export2都是myModule.js的输出接口,可以解构获得。

如果模块有default输出接口,可以用参数直接获得。

javascript
 import('./myModule.js').then(myModule => {  console.log(myModule.default);});

上面的代码也可以使用具名输入的形式。

javascript
 import('./myModule.js').then(({default: theDefault}) => {  console.log(theDefault);});

如果想同时加载多个模块,可以采用下面的写法。

javascript
 Promise.all([  import('./module1.js'),  import('./module2.js'),  import('./module3.js'),]).then(([module1, module2, module3]) => {   ···});

import()也可以用在 async 函数之中。

javascript
 async function main() {  const myModule = await import('./myModule.js');  const {export1, export2} = await import('./myModule.js');  const [module1, module2, module3] =    await Promise.all([      import('./module1.js'),      import('./module2.js'),      import('./module3.js'),    ]);}main();

动态导入

动态导入

Excerpt

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


动态导入

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

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

模块路径必须是原始类型字符串,不能是函数调用,下面这样的 import 行不通:

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

其次,我们无法根据条件或者在运行时导入:

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

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

这是因为 import/export 旨在提供代码结构的主干。这是非常好的事儿,因为这样便于分析代码结构,可以收集模块,可以使用特殊工具将收集的模块打包到一个文件中,可以删除未使用的导出(“tree-shaken”)。这些只有在 import/export 结构简单且固定的情况下才能够实现。

但是,我们如何才能动态地按需导入模块呢?

import() 表达式

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

我们可以在代码中的任意位置动态地使用它。例如:

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

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

或者,如果在异步函数中,我们可以使用 let module = await import(modulePath)

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

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

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

……那么,可以想像下面这样进行动态导入:

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

hi();
bye();

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

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

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

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

say();

这是一个完整的示例:

javascript
export function hi() {
  alert(`Hello`);
}

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

export default function() {
  alert("Module loaded (export default)!");
}
html
<!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。因为它不是一个函数。