JavaScript 模块机制详解,涵盖模块定义与作用域特性、export 与 import 的多种用法、默认导出与命名导出、模块在浏览器环境中的加载行为、以及动态导入 import() 表达式的用法与应用场景。
模块概念
一个模块(module)就是一个文件。一个脚本就是一个模块。就这么简单。
模块可以相互加载,并可以使用特殊的指令 export
和
import
来交换功能,从另一个模块调用一个模块的函数:
export
关键字标记了可以从当前模块外部访问的变量和函数。import
关键字允许从其他模块导入功能。
模块特点
- 始终使用 “use strict”
- 模块级作用域
每个模块都有自己的顶级作用域(top-level scope)。换句话说,一个模块中的顶级作用域变量和函数在其他脚本中是不可见的。
只能通过export和import来实现。
模块代码仅在第一次导入时被解析
模块代码仅在第一次导入时被解析
import.meta
包含关于当前模块的信息。
它的内容取决于其所在的环境。在浏览器环境中,它包含当前脚本的 URL,或者如果它是在 HTML 中的话,则包含当前页面的 URL。
浏览器特定功能
- 模块脚本是延迟的
模块脚本 总是 被延迟的
- 下载外部模块脚本
<script type="module" src="...">
不会阻塞 HTML 的处理,它们会与其他资源并行加载。 - 模块脚本会等到 HTML 文档完全准备就绪(即使它们很小并且比 HTML 加载速度更快),然后才会运行。
- 保持脚本的相对顺序:在文档中排在前面的脚本先执行。
例如:
1 | <script type="module"> |
导出和导入
在声明前导出
1 | // 导出数组 |
导出 class/function 后没有分号
导出与声明分开
1 | // 📁 say.js |
也可以把 export
放在函数上面。
Import*
通常,我们把要导入的东西列在花括号 import {...}
中,就像这样:
1 | // 📁 main.js |
但是如果有很多要导入的内容,我们可以使用
import * as <obj>
将所有内容导入为一个对象,例如:
1 | // 📁 main.js |
import和export都可以使用as来重命名
Export default
在实际中,主要有两种模块。
- 包含库或函数包的模块,像上面的
say.js
。 - 声明单个实体的模块,例如模块
user.js
仅导出class User
。
将 export default
放在要导出的实体前:
1 | // 📁 user.js |
每个文件应该只有一个 export default
:
命名的导出 | 默认的导出 |
---|---|
export class User {...} |
export default class User {...} |
import {User} from ... |
import User from ... |
动态导入
import() 表达式
import(module)
表达式加载模块并返回一个 promise,该
promise resolve
为一个包含其所有导出的模块对象。我们可以在代码中的任意位置调用这个表达式。
1 | <!doctype html> |
动态导入在常规脚本中工作时,它们不需要
script type="module"
.
import()
看起来像一个函数调用,但它只是一种特殊语法,只是恰好使用了括号