由于JavaScript设计之初,只是作为嵌入式脚本语言,没有支持模块化开发,这就导致在多人开发和可维护性力不从心。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS
和 AMD
两种。
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS
和 AMD
规范,成为浏览器和服务器通用的模块解决方案。
比起 require/exports , import/export有什么优势?
require
是赋值过程,其实require
的结果就是对象、数字、字符串、函数等,再把结果赋值给某个变量。它是普通的值拷贝传递。
1 | // CommonJS模块 |
上面代码的实质是整体加载fs
模块(即加载fs
的所有方法),生成一个对象(_fs
),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入。
1 | // ES6模块 |
上面代码的实质是从fs
模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)。所以,下面的代码会报错。
1 | // 报错 |
引擎处理import语句是在编译时,import
和export
命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。
这也就导致无法条件加载,因此,ES6 中还新增了一个函数import(params)
。import函数
的参数,指定所要加载的模块的位置。import命令
能够接受什么参数,import()函数
就能接受什么参数,两者区别主要是后者为动态加载。import()
类似于 Node 的require
方法,区别主要是前者是异步加载,后者是同步加载。
下面是import()
的一些适用场合。
(1)按需加载。
import()
可以在需要的时候,再加载某个模块。
1 | button.addEventListener('click', event => { |
上面代码中,import()
方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。
(2)条件加载
import()
可以放在if代码块,根据不同的情况,加载不同的模块。
1 | if (condition) { |
上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。
(3)动态的模块路径
import()
允许模块路径动态生成。
1 | import(f()) |
上面代码中,根据函数f的返回结果,加载不同的模块。import()
返回一个 Promise
对象。
1 | const main = document.querySelector('main'); |
如果想同时加载多个模块,可以采用下面的写法。
1 | Promise.all([ |