前端用到的模块解决方案通常有CommonJS、AMD、CMD、UMD、ES6 Module等,这里对它们分别做一个简单的介绍。

# CommonJS

CommonJS加载模块是同步的。

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

// count.js
let count = 1

function increase() {
  count += 1
}

module.exports = {
  count,
  increase
}
// index.js
const { count, increase } = require('./count')
console.log(count) // 1
increase()
console.log(count) // 1

上述代码表明 count 被导出后,count 模块内部的变化影响不到外部导出的 count 值,这是因为 count 是一个原始类型的值,会被缓存。下面我们把 count 当成引用类型的属性再看看输出结果:

// count.js
let obj = {
  count: 1
}

function increase() {
  obj.count += 1
}

module.exports = {
  obj,
  increase
}
// index.js
const { obj, increase } = require('./count')
console.log(obj.count) // 1
increase()
console.log(obj.count) // 2

# AMD

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要来着早。

未使用AMD

// init.js
(function(window) {
  function initData() {
    return 'hello'
  }
  window.initData = initData
})(window)
// getData.js
(function(window) {
  function getData() {
    return initData()
  }
  window.getData = getData
})(window)
// index.js
(function(window) {
  alert(getData())
})(window)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="./init.js"></script>
  <script src="./getData.js"></script>
  <script src="./index.js"></script>
</body>
</html>

上面的代码在加载 JS 文件的时候会发送多个请求,请求多个 JS 文件,其次引入的js文件顺序不能搞错,否则会报错。

下面看下使用 AMD 模块:

// init.js
define(function() {
  function initData() {
    return 'hello'
  }
  return { initData }
})
// getData.js
define(['init'], function(data) {
  function getData() {
    return data.initData()
  }
  return { getData }
})
// init.js
(function() {
  require(['getData'], function(data) {
    alert(data.getData())
  })
})()
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"></script>
  <script src="./index.js"></script>
</body>
</html>

AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。但是对比上面的 CommonJS 规范来看,AMD 这种方式代码不便理解,使用也不方便。

# CMD

主要用于浏览器,模块的加载是异步的,模块使用时才会加载执行。典型代表 sea.js (opens new window)

它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。

// data.js
define(function(require, exports, module) {
  // 下面两种导出方式都可以
  // exports.data = 'f'
  module.exports = {
    data: 'f'
  }
})
// index.js
define(function(require, exports, module) {
  const data = require('./data.js')
  console.log(data.data)
})
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://cdn.bootcdn.net/ajax/libs/seajs/3.0.3/sea.js"></script>
  <script>
    seajs.use('./index.js')
  </script>
</body>
</html>

从上面的使用方式看是不是有些类似 CommonJS 规范。

# UMD

上面介绍的这几种模块解决方案,要不主要针对的是浏览器端,要不主要针对的服务端。有没有一种既能在浏览器端用也能在服务端用的方式呢?毕竟一套代码可以多端运行那得多爽。下面我们来说一下 UMD 这种模块方式。

UMD(Universal Module Definition) 是一种JS通用定义规范,可以让你的模块儿在 JS 所有的环境中都能使用。它集CommonJS、AMD规范于一身。来看一个下它的实现:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // 兼容 AMD
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    // 兼容 CommonJS
    var $ = requie('jquery');
    module.exports = factory($);
  } else {
    // 全局对象挂载属性
    root.umdModule = factory(root.jQuery);
  }
})(this, function($) {
  // todo
})

随着 ES Module 的普及,UMD 这种模块方式也将慢慢过时。

# ES6 模块

// data.js
export {
  name: 'f'
}
// index.js
import { name } from './data.js'

上面这种用法是不是很熟悉?它就是我们所说的 ES Module 。这种方式这里就不多做介绍了,因为目前前端开发使用的基本都是这种,一些简要知识可以看下开篇的大纲图。

参考:

可能是最详细的UMD模块入门指南 (opens new window)

前端模块化详解(完整版) (opens new window)

前端模块化:CommonJS,AMD,CMD,ES6 (opens new window)