Skip to content

JS 模块化规范演进历史

1. 原始的 JS 模块化

每个人编写的模块通过 script标签引入到页面中,然后就可以使用模块中的内容了。

2. CommonJS 模块化规范

随着 nodejs 的诞生,为了解决在 nodejs 中的模块化问题,社区自发形成了很多的模块化方案,其中 CommonJS 模块化规范得到了时间的检验,并最终确定为主流的模块化规范,这是一套社区的模块化规范,使用 commonjs 规范编写的模块并不能直接在浏览器中运行。后来出现了browserifywebpack 等编译、打包工具,使得 CommonJS 模块化规范编写的代码经过编译、打包后也可以用于浏览器中运行。

js
// 1. 使用 exports 导出模块
exports.add = (a, b) => a + b

// 2. 使用 module.exports 导出模块
module.exports = {
  add,
}
js
const utils = require('./utils')
// 或者加后缀
// const utils = require('./utils.js')
// 也可以解构方式导入
// const { add } = require('./utils.js')

const result = utils.add(1, 2)

注意事项:

  1. 每个模块内部的:thisexportsmodule.exports 在初始时,都指向同一个空对象,这个空对象就是当前模块导出的数据。
  2. 无论如何修改导出对象,最终导出的都是以module.exports 为准。
  3. exports 是对 module.exports 的引用,仅为了方便给导出对象添加属性,所以不能使用exports = value 的形式导出数据,但是可以使用module.exports = value 的形式导出数据。
  • ⚠️ 在commonjs模块化规范中,实际是通过内置函数来实现模块化的,我们可以通过arguments.callee.toString() 获取当前模块的代码,从以下的代码我们可以看出,一个 JS 模块在执行时,是被包裹在一个内置函数中执行的,所以每个模块都有自己的作用域。
js
// utils.js
function (exports, require, module, __filename, __dirname) {
  exports.add = (a, b) => {
    return a + b
  }

  exports.sub = (a, b) => {
    return a - b
  }
  console.log(arguments.callee.toString())
}

3. ESM 模块化规范

说明:

ES6模块化规范是ECMA官方标准的规范,它是在语言标准的层面上实现了模块化功能,是目前最流行的模块化规范,且浏览器与服务器均支持该规范。

ES6模块化规范的导入导出方式:

  1. export 分别导出语法
js
export const add = (a, b) => a + b
  1. export 统一导出语法
js
const sub = (a, b) => a - b
const mul = (a, b) => a * b

export { sub, mul }
  1. export default 默认导出语法
js
export default (a, b) => a + b
  1. export { mul as alias } 在统一导出方式中取别名
js
export { mul as alias }
  1. import * as alias 统一导入语法(通用)
js
import * as utils from './utils.js'
  1. 命名导入
js
import { mul as multi } from './utils.js'
  1. 默认导入
js
import utils from './utils.js'
  1. 动态导入
js
async function main() {
  const utils = await import('./utils.js')
  console.log(utils)
}

main()
  1. import可以不接受任何数据
js
import './utils.js'

提示:

通过模块化解决了:(1)全局污染问题 (2)依赖混乱问题 (3)数据安全问题

js
const add = (a, b) => a + b

const sub = (a, b) => a - b

const mul = (a, b) => a * b

// 分别导出
// export const greet = (name) => `hello ${name}`  // 错误演示
// 不能既分别导出又统一导出,否则会报 SyntaxError: Duplicate export of 'greet'

// 统一导出
export {
  add,
  // 使用 as 给导出模块取别名,取的别名如果是 default, 则该模块会等价 export default 默认导出
  mul as default,
  mul,
  sub, // greet
}
js
import mul, { add, mul as multi, sub } from './utils.js'
// 如果需要默认导入和按需导入在同一行,则默认导入需要写在最前面

console.log(add, sub, mul, multi)

注意事项:

  1. 如果使用了分别导出,则不能使用统一导出,否则会报 SyntaxError: Duplicate export of 'greet'。这两种导出本质是都是同一种方式。
  2. 使用 import 导入模块时,如果需要默认导入和按需导入在同一行,则默认导入需要写在最前面。
  3. 导入、导出都可以使用as关键字给变量名取别名。
  4. 如果要在服务端运行ES6模块化规范的代码,第一种方式可以在项目的package.json文件中添加"type": "module",第二种方式可以在文件名后添加.mjs后缀,表示该模块是ES6模块化规范的模块。

4. AMD 模块化规范

说明:

AMDAsynchronous Module Definition的缩写,即异步模块定义。它是一种模块化规范,主要用于浏览器端。该模块化规范需要依赖require.js库,所以需要先引入require.js库,然后才能使用AMD模块化规范。

  • 引入require.js

require.js库中,define函数用于定义模块,require函数用于导入模块。在引入require.js库后,需要在script标签上指定模块的入口文件data-main属性,该属性值为模块的入口文件路径。

html
<script data-main="./main.js" src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.7/require.min.js"></script>
  • main.js文件中编写配置,注册模块
js
/** AMD 模块化的入口文件,要编写配置对象,并且有固定写法 */
requirejs.config({
  // 基本路径
  baseUrl: './js',
  // 模块表示名 与 模块路径的映射关系
  paths: {
    utils: 'utils',
    math: 'math',
  },
})
js
/** AMD 使用 require 函数的第一个参数表示依赖模块,
 * 第二个参数表示回调函数,回调函数中的参数表示依赖模块的导出结果,会按照依赖顺序将模块对象依次传入到对应形参上
 */
require(['utils', 'math'], function (utils, math) {
  console.log(utils)
  console.log(math)

  const r1 = utils.getTime()
  const r2 = math.add(1, 2)

  console.log('当前时间:', r1, '\n1 + 2 = ', r2)
})
  • 定义模块
js
// ./js/math.js
const add = (a, b) => a + b

const sub = (a, b) => a - b

define(function () {
  return {
    add,
    sub,
  }
})
js
// ./js/utils.js
function getTime() {
  const t = new Date()

  const hours = t.getHours()
  const minutes = t.getMinutes()
  const seconds = t.getSeconds()

  return `${padZero(hours)}:${padZero(minutes)}:${padZero(seconds)}`
}

function padZero(n) {
  return n.toString().padStart(2, '0')
}

// AMD 使用 define 函数定义模块
define(function () {
  return {
    getTime,
    padZero,
  }
})

上古模块化规范

  • UMD
  • CMD