JavaScript 模块化

为何要模块化

将实现某一功能的代码放在函数中,其实就是一个简单的模块:

1
2
3
function module1(){
...
}

然而这样会污染全局变量,无法避免与其他模块相同变量的冲突,也无法看出模块成员之间关系。

更进一步,将模块写成一个对象:

1
2
3
4
5
6
var module1 = {
count: 0,
m1: function(){
...
}
}

避免了直接用函数封装模块的缺点,但带来的问题是暴露了模块内私有成员。

再进一步,使用 立即执行函数 改进:

1
2
3
4
5
6
7
8
9
var module1 = (function(){
var count = 0;
var m1 = function(){
...
};
return {
m1: m1
};
})()

这样就达到了不暴露私有成员的目的。

如果一个模块需要继承(依赖)另一个模块,而且不确定该模块是否定义,可以这样写:

1
2
3
var module1 = (function(module2){
...
})(window.module2 || {})

这样就 OK 了,这也正是所谓的 模块化模式

模块的规范

要想模块化就要有模块的规范。Javascript模块规范共有三种:同步加载 CommonJS 、异步加载 AMD 和 通用模块定义 CMD

CommonJS

Node.js 中,使用的是 CommonJS ,比如:

1
2
var http = require('http');
var server = http.createServer();

因为对服务器端而言,所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。在浏览器端,显然不能采用这种方式,因为其等待时间取决于网速的快慢,故只能采用 异步方式 AMD

AMD

AMD(Asynchronous Module Definition,异步模块定义)也是采用 require() 语句加载模块:

require([module], callback);

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

1
2
3
require(['underscore'], function(underscore){
...
})

CMD

CMD(Common Module Definition,通用模块定义) 是国内发展起来的模块规范。AMD是依赖关系前置,CMD是按需加载。在 CMD 规范中,一个模块就是一个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  var b = require('./b') // 依赖可以就近书写
  b.doSomething()
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  b.doSomething()
})

模块规范的使用

require.js

使用 require.js的两个目的:

1)实现js文件的异步加载,避免网页失去响应;
2)管理模块之间的依赖性,便于代码的编写和维护。

使用 require.js 的第一步是加载 require.js

1
<script src="js/require.js" data-main="js/main"></script>

如果担心加载这个文件,可能造成网页失去响应,可以把它放在网页底部加载,或者加上 defer async="true"defer 是为了支持 IE。

main.js 相当于整个网页的入口代码。

如果主模块的依赖模块和主模块在同一目录,可以在 main.js 直接加载依赖的各模块:

1
2
3
4
// main.js
require(['moduleA', 'moduleB'], function (moduleA, moduleB){
...
});

如果不在同一目录,也可使用 require.config() 方法,对模块的加载行为进行自定义。

1
2
3
4
5
6
7
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min"
}
});

可以这样定义一个 moduleA 模块

1
2
3
4
// moduleA.js
define(function (){
...
});

如果 moduleA 有依赖,可以这样定义

1
2
3
4
// moduleA.js
define(['moduleB'], function (moduleB){
...
});

更多的使用可参考 StartAPI

sea.js

html 页尾,通过 script 引入 sea.js 后,进行简单配置:

1
2
3
4
5
6
7
8
9
10
// seajs 的简单配置
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
})

// 加载入口模块
seajs.use("../static/hello/src/main")

模块的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 所有模块都通过 define 来定义
define(function(require, exports, module) {

// 通过 require 引入依赖
var $ = require('jquery');
var Spinning = require('./spinning');

// 通过 exports 对外提供接口
exports.doSomething = ...

// 或者通过 module.exports 提供整个接口
module.exports = ...

});

Node.js 极其相近的写法。

以上摘自 Sea.js 的 快速上手 示例,更多可参考 API 文档

参考

  1. Javascript模块化编程
  2. 详解JavaScript模块化开发
  3. 再谈 SeaJS 与 RequireJS 的差异
坚持原创技术分享,您的支持将鼓励我继续创作!