JavaScript 模块

这篇指南会给你入门 JavaScript 模块的全部信息。

模块化的背景

JavaScript 程序本来很小——在早期,它们大多被用来执行独立的脚本任务,在你的 web 页面需要的地方提供一定交互,所以一般不需要多大的脚本。过了几年,我们现在有了运行大量 JavaScript 脚本的复杂程序,还有一些被用在其他环境(例如 Node.js)。

复杂的项目需要一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node.js 已经提供这个能力很长时间了,还有很多的 JavaScript 库和框架已经开始了模块的使用(例如,CommonJS 和基于 AMD 的其他模块系统,如 RequireJSwebpackBabel)。

所有现代浏览器都原生支持模块特性,无需转译。这是一件好事——浏览器可以优化模块的加载,使其比使用库进行所有额外的客户端处理和额外的网络开销更高效。不过,这并不意味着像 webpack 这样的打包工具就过时了——打包工具仍然在将代码分割成合理大小的块方面做得很好,并且能够进行其他优化,如极简化、无用代码消除和摇树优化。

介绍一个例子

为了演示模块的使用,我们创建了一系列简单的示例,你可以在 GitHub 上找到。这个例子演示了一个简单的模块集合,用来在 web 页面上创建了一个 标签,在 canvas 上绘制(并记录有关的信息)不同形状。

这的确有点简单,但是保持足够简单能够清晰地演示模块。

备注: 如果你想去下载这个例子在本地运行,你需要通过本地 web 服务器去运行。

基本的示例文件的结构

在我们的第一个例子(参见 basic-modules)文件结构如下:

index.html
main.js
modules/
    canvas.js
    square.js

备注: 在这个指南中所有示例都具有基本相同的结构;需要慢慢熟悉上面的内容。

modules 目录下的两个模块的描述如下:

  • canvas.js——包含与设置画布相关的功能:

    • create()——在指定 ID 的包装器
      内创建指定 widthheight 的画布,该 ID 本身附加在指定的父元素内。返回包含画布的 2D 上下文和包装器 ID 的对象。
    • createReportList()——创建一个无序列表,并将其添加到指定的包装元素内,该列表可用于输出报告数据。返回列表的 ID。
  • square.js——包含:

    • name——包含字符串“square”的常量。
    • draw()——在指定画布上绘制一个正方形,具有指定的大小,位置和颜色。返回包含正方形大小,位置和颜色的对象。
    • reportArea()——在给定长度的情况下,将正方形区域写入特定报告列表。
    • reportPerimeter()——在给定长度的情况下,将正方形的周长写入特定的报告列表。

.mjs.js

纵观此文,我们使用 .js 扩展名的模块文件,但在其他一些文章中,你可能会看到 .mjs 扩展名的使用。V8 推荐了这样的做法,比如有下列理由:

  • 比较清晰,这可以指出哪些文件是模块,哪些是常规的 JavaScript。
  • 这能保证你的模块可以被运行时环境和构建工具识别,比如 Node.jsBabel

但是我们决定继续使用 .js 扩展名,未来可能会更改。为了使模块可以在浏览器中正常地工作,你需要确保你的服务器能够正常地处理 Content-Type 标头,其应该包含 JavaScript 的 MIME 类型 text/javascript。如果没有这么做,你可能会得到一个严格 MIME 类型检查错误:“The server responded with a non-JavaScript MIME type(服务器返回了非 JavaScript MIME 类型)”,并且浏览器会拒绝执行相应的 JavaScript 代码。多数服务器可以正确地处理 .js 文件的类型,但是 .mjs 还不行。已经可以正常响应 .mjs 的服务器有 GitHub Pages 和 Node.js 的 http-server

如果你已经在使用相应的环境了,那么一切正常。或者如果你还没有,但你知道你在做什么(比如你可以配置服务器以为 .mjs 设置正确的 Content-Type)。但如果你不能控制提供服务,或者用于公开文件发布的服务器,这可能会导致混乱。

为了学习和保证代码的可移植的目的,我们建议使用 .js

如果你认为使用 .mjs 仅用于模块带来的清晰性非常重要,但不想引入上面描述的相应问题,你可以仅在开发过程中使用 .mjs,而在构建过程中将其转换为 .js

还值得注意的是:

  • 一些工具可能不支持 .mjs

导入映射是在一个