为了更好的管理网页的业务逻辑,产生了模块化编程的理念。

常用的 JavaScript 的模块化规范有 CommonJS 、 AMD 、CMD

CommonJS 规范

服务器端的 Node.js 遵循 CommonJS 规范

CommonJS定义的模块分为:模块引用(require) 模块定义(exports) 模块标识(module)

require()用来引入外部模块,该方法读取一个文件并执行,最后返回文件内部的 exports 对象;exports对象用于导出当前模块的方法或变量,唯一的导出口;一个单独的文件就是一个模块,module对象就代表模块本身。

1
2
3
4
require("moduleA")
require("../file.js")
exports.func = function() {}
module.exports = moduleB

优点

CommonJS 规范加载模块是同步的,只有加载完成,才能执行后面的操作。这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。

缺点

如果在客户端,加载模块的时候有可能出现“假死”状况。不能非阻塞的并行加载多个模块

AMD 规范

异步模块定义规范,依赖前置,适用于客户端环境,并且能兼容服务器端模块的一种模块规范

AMD 其实只有一个主要接口 define(id?, dependenncies?, factory),要在声明模块的时候指定所有的依赖dependencies,依赖作为形参传到factory中,对于依赖的模块提前执行。

1
2
3
define("moduleA", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});

AMD也用require()语句来加载模块,但是他要求有两个参数require([module], callback)

第一个参数是一个数组,里面的成员就是要加载的模块,第二个参数callback,则是加载成功之后的回调函数

1
2
3
4
require(['package/moduleA'], function(moduleA) {
moduleA.add(2,3)
})
//modleA.add()与moduleA模块加载不是同步的,十分适合浏览器的环境

优点

AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖,提前执行依赖通常可以带来更好的用户体验,也可以尽早发现错误

适合在浏览器环境中异步加载模块,可以并行加载多个模块

缺点

若提前执行的依赖未被使用,就浪费了带宽和内存开销

不符合通用的模块化思维方式,是一种妥协的实现

CMD 规范

通用模块定义规范,依赖就近,用的时候再require ,异步加载模块

CMD 和 AMD 很相似

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,AMD 是提前执行,CMD 是延迟执行。

1
2
3
4
5
6
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})

ES6 模块

ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块都只能在运行时确定这些东西。

1
2
3
import "jquery";
export function func() {}
module "localModule" {}
  • 在ES6模块中,无论你是否加入use strict语句,默认情况下模块都是在严格模式下运行。
  • 当运行的模块中包含一条import声明时,首先会加载被导入的模块;然后依赖图的深度优先遍历按顺序执行每一个模块的主体代码;为了避免形成回环,所有已执行的模块都会被忽略。
重命名import 和 export

如果导出的名称与需要使用的其它名称产生冲突,可以使用重命名的方法,如

1
2
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";

在导出的时候也可以重命名

1
2
3
4
5
6
7
8
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
default exports

import _ from "lodash";

这种简略的表达方法等价于import {default as _} from "lodash";。在ES6的模块中导入的CommonJS模块和AMD模块都有一个默认的导出,如果你用require()加载这些模块也会得到相同的结果——exports对象。

模块对象

import * as cows from "cows";

import *时,导入的是一个模块命名空间对象,模块将它的所有属性都导出了。所以如果“cows”模块导出一个名为moon()的函数,然后用上面这种方法“cows”将其全部导入后,就可以这样调用函数cows.moo()

优点

  • 容易进行静态分析
  • 面向未来的 ECMAScript 标准

缺点

  • 原生浏览器端还没有实现该标准
  • 全新的命令字,新版的 Node.js才支持

实现

babel

前端模块化工具

webpack

webpack 是当下最热门的前端资源模块化管理工具。通过 loader 的转换,任何形式的资源都可以视作模块。如 commonJS 模块、AMD 模块、ES6模块、CSS、图片等…

webpack 将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。