JavaScript 模块化编程及编写加载遵循 AMD 规范的代码
最原始的写法
模块就是实现一组特定功能的方法,把不同的函数简单的放在一起就是一个模块。
对象写法
把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count: 10,
func1: function () {
console.log('this is fun1');
},
func2: function () {
console.log('this is func2');
},
});
module1.func1();
要使用模块成员的时候直接调用模块成员就行了,但是这样的写法会暴露所有的模块成员,内部状态可以被外部改写,比如外部直接可以改写_count 的值。
立即执行函数写法
使用立即执行函数的写法可以达到不暴露私有成员的目的。
var module = (function () {
var _count = 10;
var func1 = function () {
console.log(_count);
};
var func2 = function () {
console.log(_count);
};
return {
func1: func1,
func2: func2,
};
})();
module.func1();
这样的写法外部不能访问到_count 变量。
放大模式
当一个模块需要继承一个模块,或者一个模块需要添加功能时,可以使用放大模式
var module = (function () {
var _count = 10;
var func1 = function () {
console.log(_count);
};
var func2 = function () {
console.log(_count);
};
return {
func1: func1,
func2: func2,
};
})();
var module = (function (mod) {
var name = 'jiavan';
mod.func3 = function () {
console.log('name is' + name);
};
return mod;
})(module);
module.func3();
console.log(module.name); //undefind
增加了新的函数后,返回了新的 module。
宽放大模式
在浏览器环境中,各个模块通常是从网上获取的,有时不知道那个模块会先加载,如果采用上面的写法可能会导致 modul 还没有加载就被使用,加载一个不存在的空对象,这时就要采用“宽放大模式”:
var module = (function () {
var _count = 10;
var func1 = function () {
console.log(_count);
};
var func2 = function () {
console.log(_count);
};
return {
func1: func1,
func2: func2,
};
})();
var module = (function (mod) {
var name = 'jiavan';
mod.func3 = function () {
console.log('name is' + name);
};
return mod;
})(module || {}); //如果没有module还没有被加载,传入一个空对象
module.func3();
console.log(module.name); //undefind
与上面的模式相比,宽放大模式的立即执行函数的参数可以是空对象。
输入全局变量
独立性是模块的重要特点,模块内部最好不要直接与其他模块交互,为了在模块内部调用全局变量,必须显示的将其他变量输入模块。
var module = (function ($, YAHOO) {
//....
})(jQuery, YAHOO);
将 jQuery 和 YAHOO 两个库当做参数输入到 module 中,这样做即保证了模块的独立性,还使得模块之间的依赖关系变得明显。
AMD 模块诞生背景
在 nodejs 的 CommonJS 规范中可以使用 require 来加载模块,如:
var module1 = require('mod');
module1.someFunc();
这样的写法存在一个很明显的问题,要执行第二行必须要等待模块加载完成后才可以,着在服务器端似乎没有多大问题,因为记载模块的时间大约就是 I/O 耗时,但是在浏览器端问题就比较明显,js 模块都在服务器端下载,长时间的不响应会导致浏览器假死。
因此,在浏览器端的模块不能采用同步加载,只能采用异步加载,这就是 AMD 规范诞生的背景。
AMD
Asynchronous Module Definition 的意思就是异步模块定义,它采用异步方式加载模块,模块的加速不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完以后,这个回调函数才会运行。
AMD 也采用 require 语句来加载模块,但是不同于 CommonJS 的是,他要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,成员就是要加载的模块,第二个参数 callback,就是加载成功后的回调函数。如果将上面的 Node 模块加载形式改为 AMD 形式,就是下面的:
require(['mod'], function () {
mod.someFunc();
});
模块和回调函数的加载是异步的,浏览器就不会出现假死的情况,所以 AMD 比较适合浏览器环境。
实现了 AMD 规范的库有:require.js 和 curl.js。
为何使用 require.js
当一个页面有许多 js 文件并且各个文件之间存在依赖关系:
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
依赖性越强的就越要往后面放,这样的写法有很明显的缺点,加载的时候浏览器会停止网页渲染,加载的文件越多,网页失去响应的时间就越长,很强的依赖关系也使得代码编写和维护变得困难。
require.js 就是为了解决这两个问题的: 0. 实现 js 文件的异步加载,避免网页失去响应
- 管理模块之间的依赖性,便于代码的编写和维护
require.js 的加载
<script src="js/require.js" defer async="true"></script>
将 requirejs 放到网页的底部或者使用 defer,async 属性进行异步加载,IE 只支持 defer
加载自己的模块,假设我们的模块是 main.js:
<script src="js/require.js" data-main="js/main"><script>
data-main
属性的作用是指定网页程序的主模块,上面的是 main.js,这个文件会被第一个加载,由于 requrie.js 的默认文件名是 js,所以可以将 main.js 写成 main
主模块的写法
上面的 main.js 称为主模块,意思是整个网页的入口代码,有点像
C 语言的 main 函数。
如果我们的代码布衣赖任何其他模块,可以直接写入 javascript 代码
//main.js
alert('main.js加载成功');
但是这样的话,使用 require 就没有什么意义了,真正常见的情况是主模块依赖于其他模块,这时就需要使用 AMD 规范定义的 require 函数。
require(['mod1', 'mod2', 'mod3'], function (mod1, mod2, mod3) {
//some code here
});
使用 require 异步加载,浏览器不会失去响应,只有前面的模块加载成功后,才会运行,解决了依赖问题。
模块的加载
使用 require.config()方法,可以对模块的加载行为进行自定义,require.config()就写在主模块 main.js 的头部,参数是一个对象,paths 属性就是各个模块的加载路径:
require.config({
paths: {
mod1: 'modA',
mod2: 'modB',
mod3: 'modC',
},
});
当模块在同一个目录下时,可以使用上面的写法,在不同的路径下可以写成下面的形式:
require.config({
paths: {
mod1: 'lib/modA',
mod2: 'lib/modB',
mod3: 'lib/modC',
},
});
或者直接改变基目录:
require.config({
baseUrl: 'js/lib',
paths: {
mod1: 'modA',
mod2: 'modB',
mod3: 'modC',
modExt: 'http://www.xxx.com/xxx.js', //模块在另一个主机上
},
});
AMD 模块的写法
模块必须采用特定的 define 函数来定义,如果一个模块不依赖其他模块,可以直接定义在 define 函数中:
//math.js
define(function () {
var add = function (x, y) {
return x + y;
};
return {
add: add
};
});
//main.js
require(['math'], function (math) {
console.log(math.add(1, 2));
});
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script src="require.js" defer async="true"></script>
<script src="require.js" data-main="main"></script>
</html>
输出 3
如果定义的模块还依赖于其他模块,那么 define 函数的第一个参数必须是一个数组,指明该模块的依赖性:
//main.js
require(['math', 'print'], function (math) {
math.add(100, 200);
});
//math.js
define(['print'], function (print) {
var add = function (x, y) {
print.log(x + y);
};
return {
add: add,
};
});
//print.js
define(function () {
var log = function (str) {
console.log(str);
};
return {
log: log,
};
});
输出 300
上面的例程定义了一个 print 模块用于输出,在 math 模块中依赖了 print 模块并用于输出,所以在定义 math 模块的时候,第一个参数是 print 构成的数组,并作为参数传入了 math 模块中进行了调用,最后在主模块中加载了 math,print 模块,实现了相互依赖模块的异步加载。
注:本文内容是对http://www.ruanyifeng.com/blog/2012/10/javascript_module.html系列文章的学习总结,感谢原作者的分享。