模块 (Module) 简介
- AMD —— 最古老的模块系统之一,最初由 require.js 库实现。
- CommonJS —— 为 Node.js 服务器创建的模块系统。
- UMD —— 另外一个模块系统,建议作为通用的模块系统,它与 AMD 和 CommonJS 都兼容。
- 什么是模块?
- 一个模块(module)就是一个文件。一个脚本就是一个模块。
- 模块可以相互加载,并可以使用特殊的指令
export
和import
来交换功能 - 模块只通过 HTTP(s) 工作,而非本地
- 模块核心功能
- 始终使用 “use strict”
- 模块级作用域
- 每个模块都有自己的顶级作用域(top-level scope)。换句话说,一个模块中的顶级作用域变量和函数在其他脚本中是不可见的。
- 模块代码仅在第一次导入时被解析
- 如果同一个模块被导入到多个其他位置,那么它的代码只会执行一次,即在第一次被导入时。
- import.meta
import.meta
对象包含关于当前模块的信息。- alert(import.meta.url); // 脚本的 URL
- 在一个模块中,“this” 是 undefined
- 浏览器特定功能
- 与常规脚本相比,拥有
type="module"
标识的脚本有一些特定于浏览器的差异。 - 模块脚本是延迟的
- Async 适用于内联脚本(inline script)
- 外部脚本
- 具有
type="module"
的外部脚本(external script)在两个方面有所不同- 具有相同
src
的外部脚本仅运行一次 - 从另一个源(例如另一个网站)获取的外部脚本需要 CORS header,如我们在 Fetch:跨源请求 一章中所讲的那样。换句话说,如果一个模块脚本是从另一个源获取的,则远程服务器必须提供表示允许获取的 header
Access-Control-Allow-Origin
。
- 具有相同
- 具有
- 不允许裸模块(“bare” module)
- 在浏览器中,
import
必须给出相对或绝对的 URL 路径。没有任何路径的模块被称为“裸(bare)”模块。在import
中不允许这种模块。
- 在浏览器中,
- 兼容性,“nomodule”
- 旧时的浏览器不理解
type="module"
。未知类型的脚本会被忽略。对此,我们可以使用nomodule
特性来提供一个后备
- 旧时的浏览器不理解
- 与常规脚本相比,拥有
- 构建工具
- 常,我们会使用一些特殊工具,例如 Webpack,将它们打包在一起,然后部署到生产环境的服务器。 它们可以更好地控制模块的解析方式,允许我们使用裸模块和更多的功能
- 构建工具做以下这些事儿
- 从一个打算放在 HTML 中的
<script type="module">
“主”模块开始。 - 分析它的依赖:它的导入,以及它的导入的导入等。
- 使用所有模块构建一个文件(或者多个文件,这是可调的),并用打包函数(bundler function)替代原生的
import
调用,以使其正常工作。还支持像 HTML/CSS 模块等“特殊”的模块类型。 - 在处理过程中,可能会应用其他转换和优化:
- 删除无法访问的代码。
- 删除未使用的导出(“tree-shaking”)。
- 删除特定于开发的像
console
和debugger
这样的语句。 - 可以使用 Babel 将前沿的现代的 JavaScript 语法转换为具有类似功能的旧的 JavaScript 语法。
- 压缩生成的文件(删除空格,用短的名字替换变量等)。
- 从一个打算放在 HTML 中的
导出和导入
- 在声明前导出
- 我们可以通过在声明之前放置
export
来标记任意声明为导出,无论声明的是变量,函数还是类都可以。 - 导出 class/function 后没有分号
- 我们可以通过在声明之前放置
- 导出与声明分开
- Import
*
- 通常,我们把要导入的东西列在花括号
import {...}
中 - 但是如果有很多要导入的内容,我们可以使用
import * as <obj>
将所有内容导入为一个对象
- 通常,我们把要导入的东西列在花括号
- Import “as” 取个别名
- 也可以使用
as
让导入具有不同的名字。 - import {sayHi as hi, sayBye as bye} from './say.js';
- 也可以使用
- Export “as” 取个别名
- export {sayHi as hi, sayBye as bye};
- Export default
- 包含库或函数包的模块,像上面的
say.js
。 - 声明单个实体的模块,例如模块
user.js
仅导出class User
。
- 包含库或函数包的模块,像上面的
- “default” 名称
- 在某些情况下,
default
关键词被用于引用默认的导出。
- 在某些情况下,
- 我应该使用默认的导出吗?
- 命名的导出是明确的。它们确切地命名了它们要导出的内容,因此我们能从它们获得这些信息,这是一件好事。
- 重新导出
- “重新导出(Re-export)”语法
export ... from ...
允许导入内容,并立即将其导出(可能是用的是其他的名字)
- “重新导出(Re-export)”语法
- 重新导出默认导出
- 要重新导出默认导出,我们必须明确写出
export {default as User}
,
- 要重新导出默认导出,我们必须明确写出
动态导入
我们不能动态生成 import
的任何参数。
- 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>)
// 📁 say.js
export function hi() {
alert(`Hello`);
}
export function bye() {
alert(`Bye`);
}
let {hi, bye} = await import('./say.js');
hi();
bye();