es6模块化
【es6模块化】
初识Module
(1)什么是模块
模块:一个一个的局部作用域的代码块。
(2)什么是模块系统
模块系统:系统的解决了模块化一系列问题。
- 模块化的写法(之前我们用立即执行函数模拟模块化,ES6 则实现了针对模块化的语法)
- 消除全局变量(模块中的变量都是局部的,不同模块之间不会相互干扰,可以通过特定语法暴露指定内容)
- 管理加载顺序(之前我们将一个总的 JavaScript 程序分几个文件写,但在最终合并调用时,js 的引入需要满足前后依赖关系。比如:被引用的 js 文件就一定要在引用它的 js 文件之前加载)
Module的基本用法
注意:Module 要生效,必须在服务器环境下才能执行。
普通的 HTML、JS 是本地文件环境,地址以 file 协议开头,服务器则以 http 或 https 开头。
方法:VSCode 中使用 Live Server 拓展,WebStorm 默认就是服务器环境。
- 一个 JS 文件就是一个模块
- 用 import 关键字导入模块
- 用 export 关键字导出模块需要暴露的部分
- 在使用 script 标签加载的时候,需要加上 type=“module”,否则就以普通 JS 文件的形式引入了,就不是模块了
Module的导入导出
导出的东西可以被导入(import),并访问到!
对于导入和导出有两种方法:
- export default 导出,import 导入
- export 导出,import 导入
export default 导出和对应的 import 导入
(1)一个模块没有导出,是否可以将其导入?
1 | <!-- 一个模块没有导出,也可以将其导入 --> |
(2)一个模块中只能有一个 export default。
【module.js】
1 | // 模块中的变量都是局部的 |
【index.html】
1 |
|
export 导出和对应的 import 导入
(1)基本用法
【module.js】
1 | /* |
【index.html】
1 |
|
注意:在用 export 导出时,也可以用对象简写形式形式!
【module.js】
1 | const age = 18; |
(2)多个导入
【module.js】
1 | // 1、采用声明或语句的形式 |
【index.html】
1 |
|
(3)导出导入时起别名
1 | export {fn as func, className as cN, age}; |
1 | import {func, cN, age as nl} from "./module.js"; |
(4)整体导入
1 | // 之前的导入方式,如果导入的模块不多那么还好,但是一但模块数量多了起来,那么就特别费劲 |
alt=“image-20220602154710981”> style=“zoom:50%;” />
(5)同时导入
当我们需要分别导入 export default 和 export 时,可以使用同时导入的方式。
1 | // 我们可以分开实现 |
1 | // 更推荐使用同时导入的方式 |
模块重导(Re-export)
Re-export一般用于收拢同类型的模块、一般都是以文件夹为单位,如components、routes、utils、hooks、stories
等都通过各自的index.tsx暴露,这样就能极大程度的简化导入路径、提升代码可读性、可维护性。
如果在一个模块之中,先输入后输出同一个模块,import
语句可以与export
语句写在一起。
1 | export { foo, bar } from 'my_module'; |
上面代码中,export
和import
语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo
和bar
实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo
和bar
。
模块的接口改名和整体输出,也可以采用这种写法。
1 | // 接口改名 |
默认接口的写法如下。
1 | export { default } from 'foo'; |
具名接口改为默认接口的写法如下。
1 | export { es6 as default } from './someModule'; |
同样地,默认接口也可以改名为具名接口。
1 | export { default as es6 } from './someModule'; |
ES2020 之前,有一种import
语句,没有对应的复合写法。
1 | import * as someIdentifier from "someModule"; |
ES2020补上了这个写法。
1 | export * as ns from "mod"; |
import()
简介
前面介绍过,import
命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行(import
命令叫做“连接” binding 其实更合适)。所以,下面的代码会报错。
1 | // 报错 |
上面代码中,引擎处理import
语句是在编译时,这时不会去分析或执行if
语句,所以import
语句放在if
代码块之中毫无意义,因此会报句法错误,而不是执行时错误。也就是说,import
和export
命令只能在模块的顶层,不能在代码块之中(比如,在if
代码块之中,或在函数之中)。
这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import
命令要取代 Node 的require
方法,这就形成了一个障碍。因为require
是运行时加载模块,import
命令无法取代require
的动态加载功能。
1 | const path = './' + fileName; |
上面的语句就是动态加载,require
到底加载哪一个模块,只有运行时才知道。import
命令做不到这一点。
ES2020提案 引入import()
函数,支持动态加载模块。
1 | import(specifier) |
上面代码中,import
函数的参数specifier
,指定所要加载的模块的位置。import
命令能够接受什么参数,import()
函数就能接受什么参数,两者区别主要是后者为动态加载。
import()
返回一个 Promise 对象。下面是一个例子。
1 | const main = document.querySelector('main'); |
import()
函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()
函数与所加载的模块没有静态连接关系,这点也是与import
语句不相同。import()
类似于 Node.js 的require()
方法,区别主要是前者是异步加载,后者是同步加载。
由于import()
返回 Promise对象,所以需要使用then()
方法指定处理函数。考虑到代码的清晰,更推荐使用await
命令。
1 | async function renderWidget() { |
上面示例中,await
命令后面就是使用import()
,对比then()
的写法明显更简洁易读。
适用场合
下面是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()
加载模块成功以后,这个模块会作为一个对象,当作then
方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
1 | import('./myModule.js') |
上面代码中,export1
和export2
都是myModule.js
的输出接口,可以解构获得。
如果模块有default
输出接口,可以用参数直接获得。
1 | import('./myModule.js') |
上面的代码也可以使用具名输入的形式。
1 | import('./myModule.js') |
如果想同时加载多个模块,可以采用下面的写法。
1 | Promise.all([ |
import()
也可以用在 async 函数之中。
1 | async function main() { |