Skip to content

模块 (Module) 简介

  • AMD —— 最古老的模块系统之一,最初由 require.js 库实现。
  • CommonJS —— 为 Node.js 服务器创建的模块系统。
  • UMD —— 另外一个模块系统,建议作为通用的模块系统,它与 AMD 和 CommonJS 都兼容。
  1. 什么是模块?
    1. 一个模块(module)就是一个文件。一个脚本就是一个模块。
    2. 模块可以相互加载,并可以使用特殊的指令 export 和 import 来交换功能
    3. 模块只通过 HTTP(s) 工作,而非本地
  2. 模块核心功能
    1. 始终使用 “use strict”
    2. 模块级作用域
      1. 每个模块都有自己的顶级作用域(top-level scope)。换句话说,一个模块中的顶级作用域变量和函数在其他脚本中是不可见的。
    3. 模块代码仅在第一次导入时被解析
      1. 如果同一个模块被导入到多个其他位置,那么它的代码只会执行一次,即在第一次被导入时。
  3. import.meta
    1. import.meta 对象包含关于当前模块的信息。
      1. alert(import.meta.url); // 脚本的 URL
    2. 在一个模块中,“this” 是 undefined
  4. 浏览器特定功能
    1. 与常规脚本相比,拥有 type="module" 标识的脚本有一些特定于浏览器的差异。
    2. 模块脚本是延迟的
    3. Async 适用于内联脚本(inline script)
    4. 外部脚本
      1. 具有 type="module" 的外部脚本(external script)在两个方面有所不同
        1. 具有相同 src 的外部脚本仅运行一次
        2. 从另一个源(例如另一个网站)获取的外部脚本需要 CORS header,如我们在 Fetch:跨源请求 一章中所讲的那样。换句话说,如果一个模块脚本是从另一个源获取的,则远程服务器必须提供表示允许获取的 header Access-Control-Allow-Origin
    5. 不允许裸模块(“bare” module)
      1. 在浏览器中,import 必须给出相对或绝对的 URL 路径。没有任何路径的模块被称为“裸(bare)”模块。在 import 中不允许这种模块。
    6. 兼容性,“nomodule”
      1. 旧时的浏览器不理解 type="module"。未知类型的脚本会被忽略。对此,我们可以使用 nomodule 特性来提供一个后备
  5. 构建工具
    1. 常,我们会使用一些特殊工具,例如 Webpack,将它们打包在一起,然后部署到生产环境的服务器。 它们可以更好地控制模块的解析方式,允许我们使用裸模块和更多的功能
    2. 构建工具做以下这些事儿
      1. 从一个打算放在 HTML 中的 <script type="module"> “主”模块开始。
      2. 分析它的依赖:它的导入,以及它的导入的导入等。
      3. 使用所有模块构建一个文件(或者多个文件,这是可调的),并用打包函数(bundler function)替代原生的 import 调用,以使其正常工作。还支持像 HTML/CSS 模块等“特殊”的模块类型。
      4. 在处理过程中,可能会应用其他转换和优化:
        • 删除无法访问的代码。
        • 删除未使用的导出(“tree-shaking”)。
        • 删除特定于开发的像 console 和 debugger 这样的语句。
        • 可以使用 Babel 将前沿的现代的 JavaScript 语法转换为具有类似功能的旧的 JavaScript 语法。
        • 压缩生成的文件(删除空格,用短的名字替换变量等)。

导出和导入

  1. 在声明前导出
    1. 我们可以通过在声明之前放置 export 来标记任意声明为导出,无论声明的是变量,函数还是类都可以。
    2. 导出 class/function 后没有分号
  2. 导出与声明分开
  3. Import *
    1. 通常,我们把要导入的东西列在花括号 import {...} 中
    2. 但是如果有很多要导入的内容,我们可以使用 import * as <obj> 将所有内容导入为一个对象
  4. Import “as” 取个别名
    1. 也可以使用 as 让导入具有不同的名字。
    2. import {sayHi as hi, sayBye as bye} from './say.js';
  5. Export “as” 取个别名
    1. export {sayHi as hi, sayBye as bye};
  6. Export default
    • 包含库或函数包的模块,像上面的 say.js
    • 声明单个实体的模块,例如模块 user.js 仅导出 class User
  7. “default” 名称
    1. 在某些情况下,default 关键词被用于引用默认的导出。
  8. 我应该使用默认的导出吗?
    1. 命名的导出是明确的。它们确切地命名了它们要导出的内容,因此我们能从它们获得这些信息,这是一件好事。
  9. 重新导出
    1. “重新导出(Re-export)”语法 export ... from ... 允许导入内容,并立即将其导出(可能是用的是其他的名字)
  10. 重新导出默认导出
    1. 要重新导出默认导出,我们必须明确写出 export {default as User}

动态导入

我们不能动态生成 import 的任何参数。

  1. import() 表达式
    1. 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();