完全不封装时
最开始的js是很简单的,它并不承担很复杂的功能,于是所有的变量和函数都写在全局作用域中(Global)。这样一开始没有什么问题,但是当JS的代码越来越多,后来慢慢出现插件或多个JS文件时就有问题了,变量和函数的名称很容易冲突。
function foo(){
//...
}
function bar(){
//...
}
利用JS对象模拟命名空间
利用JS对象,将相关的方法和属性封装一层,这样可以避免很多同功能函数的冲突。
var MYAPP = {
foo: function(){},
bar: function(){}
}
MYAPP.foo();
但是这样只能做到简单的减少全局中变量的冲突,关于安全方面完全没有帮助,还是可以随便覆盖和访问。
利用函数的块级作用域
隐藏局部变量
块级作用域里的变量外面是访问不到的,利用这一点可以将我们的功能封装在一个匿名函数中,只通过返回值暴露出我们想暴露出的接口。
var Module = (function(){
var _private = "safe now";
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
Module.foo();
Module._private; // undefined
利用函数传参添加依赖
就是酱咯,这种封装的方式已经把现代模块化代码上的要素基本都包含了。
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var Module = (function($){
var _$body = $("body"); // we can use jQuery now!
var foo = function(){
console.log(_$body); // 特权方法
}
// Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo();
继承或拓展模块
利用传参的特性,可以继承已有的模块:
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
模块管理
当我们有多个js的时候我们会写很多的script标签,但是这样不仅增加了HTTP请求,而且难以维护,因为执行顺序是从上至下的,所以被依赖的需要放在前面,文件多了就比较尴尬了。
CommonJS
定义模块
上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一的导出的出口。在模块中,还在 一个module对象,它代表模块自身,exports是module的属性。将方法挂载在exports上作为属性即可。
exports.add = function () {
var sum = 0,
i = 0,
args = arguments,
l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
使用模块
使用时就在要使用的文件中引用:
var selfModule = require('selfModule');
console.log(selfModule.add(13,3));
在引用的过程中,根据具体实现的不同,使用的模块标识也不同,绝对路径是一般都支持的。
同步加载
在你require的时候,是同步加载这个模块的,也就是说,这个模块中的代码执行完才会继续执行主文件中下面的代码。
可以试下下面这个例子:
var EXE_TIME = 2;
(function(second){
var start = +new Date();
while(start + second*1000 > new Date()){}
})(EXE_TIME)
console.log("2000ms executed");
var timeout = require('E:\\Git\\NodeTest\\timeout.js');
console.log('done!');
这种阻塞式的加载在服务器端是没有问题的,比如node。但是在浏览器端,阻塞式加载并不是很理想。
AMD
定义模块
在这里,一个磁盘文件应该只定义一个模块
如果一个模块仅含值对,没有任何依赖,则在define()中定义这些值对就好了:
define({
color: "black",
size: "unisize"
});
如果一个模块没有任何依赖,但需要一个做setup工作的函数,则在define()中定义该函数,并将其传给define():
define(function () {
//Do setup work here
return {
color: "black",
size: "unisize"
}
});
如果模块存在依赖:则第一个参数是依赖的名称数组;第二个参数是函数,在模块的所有依赖加载完毕后,该函数会被调用来定义该模块,因此该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表一一对应。
define(["./cart", "./inventory"], function(cart, inventory) {
//return an object to define the "my/shirt" module.
return {
color: "blue",
size: "large",
addToCart: function() {
inventory.decrement(this);
cart.add(this);
}
}
}
);
使用模块
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
模块的加载
加载模块时默认要加载的模块和加载它们的文件在同一个目录下,但是如果不是你可以通过require.config来配置。
CMD
CMD的定义模块部分与AMD有区别,更加接近于CommonJS。
define(function(require, exports, module){
var a = require("a");
a.doSomething();
var b = require("b");
b.doSomething(); // 依赖就近,延迟执行
})
兼容模块规范
为了能将一个模块可以同时运行在使用不同包管理的地方,我们可以这样定义模块:
(function (name, definition) {
// 检测上文环境是否为AMDCMD
var hasDefine = typeof define === 'function',
// 检查上文环境是否为Node
hasExports = typeof module !== 'undefined' && module.exports;
if (hasDefine) {
// AMD环境CMD环境
define(definition);
} else if (hasExports) {
// 定义为通用Node模块
module.exports = definition();
} else {
// 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
this[name] = definition();
}
})('math', function () {
var interface = {};
interface.add=function () {
var sum = 0,
i = 0,
args = arguments,
l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
return interface;
});