import()函数
简介
前面介绍过,import
命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)。所以,下面的代码会报错。
// 报错if (x === 2) { import MyModual from './myModual';}
上面代码中,引擎处理import
语句是在编译时,这时不会去分析或执行if
语句,所以import
语句放在if
代码块之中毫无意义,因此会报句法错误,而不是执行时错误。也就是说,import
和export
命令只能在模块的顶层,不能在代码块之中(比如,在if
代码块之中,或在函数之中)。
这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import
命令要取代 Node 的require
方法,这就形成了一个障碍。因为require
是运行时加载模块,import
命令无法取代require
的动态加载功能。
const path = './' + fileName;const myModual = require(path);
上面的语句就是动态加载,require
到底加载哪一个模块,只有运行时才知道。import
语句做不到这一点。
因此,有一个提案,建议引入import()
函数,完成动态加载。
import(specifier)
上面代码中,import
函数的参数specifier
,指定所要加载的模块的位置。import
命令能够接受什么参数,import()
函数就能接受什么参数,两者区别主要是后者为动态加载。
[ES6](http://www.waibo.wang/bible/es6/html/22/22.10.html) import()
返回一个 Promise 对象。下面是一个例子。
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()
可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ })});
上面代码中,import()
方法放在click
事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。
(2)条件加载
import()
可以放在if
代码块,根据不同的情况,加载不同的模块。
if (condition) { import('moduleA').then(...);} else { import('moduleB').then(...);}
上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。
(3)动态的模块路径
import()
允许模块路径动态生成。
import(f()).then(...);
上面代码中,根据函数f
的返回结果,加载不同的模块。
注意点
import()
加载模块成功以后,这个模块会作为一个对象,当作then
方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
import('./myModule.js').then(({export1, export2}) => { // ...·});
上面代码中,export1
和export2
都是myModule.js
的输出接口,可以解构获得。
如果模块有default
输出接口,可以用参数直接获得。
import('./myModule.js').then(myModule => { console.log(myModule.default);});
上面的代码也可以使用具名输入的形式。
import('./myModule.js').then(({default: theDefault}) => { console.log(theDefault);});
如果想同时加载多个模块,可以采用下面的写法。
Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'),]).then(([module1, module2, module3]) => { ···});
import()
也可以用在 async 函数之中。
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
行不通:
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
/export
结构简单且固定的情况下才能够实现。
但是,我们如何才能动态地按需导入模块呢?
import() 表达式
import(module)
表达式加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。我们可以在代码中的任意位置调用这个表达式。
我们可以在代码中的任意位置动态地使用它。例如:
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
:
// 📁 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();
这是一个完整的示例:
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
。因为它不是一个函数。