基本的实现方法
模块是实现特定功能的一组属性和方法的封装。
简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
上面的函数m1
和m2
,都封装在module1
对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1._count = 5;
封装私有变量:构造函数的写法
我们可以利用构造函数,封装私有变量。
function StringBuilder() {
var buffer = [];
this.add = function (str) {
buffer.push(str);
};
this.toString = function () {
return buffer.join('');
};
}
上面代码中,buffer
是模块的私有变量。一旦生成实例对象,外部是无法直接访问buffer
的。但是,这种方法将私有变量封装在构造函数中,导致构造函数与实例对象是一体的,总是存在于内存之中,无法在使用完成后清除。这意味着,构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据,违背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外)。同时,非常耗费内存。
function StringBuilder() {
this._buffer = [];
}
StringBuilder.prototype = {
constructor: StringBuilder,
add: function (str) {
this._buffer.push(str);
},
toString: function () {
return this._buffer.join('');
}
};
这种方法将私有变量放入实例对象中,好处是看上去更自然,但是它的私有变量可以从外部读写,不是很安全。
封装私有变量:立即执行函数的写法
另一种做法是使用“立即执行函数”(Immediately-Invoked Function Expression,IIFE),将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的。
var module1 = (function () {
var _count = 0;
var m1 = function () {
//...
};
var m2 = function () {
//...
};
return {
m1 : m1,
m2 : m2
};
})();
使用上面的写法,外部代码无法读取内部的_count
变量。
console.info(module1._count); //undefined
上面的module1
就是 JavaScript 模块的基本写法。下面,再对这种写法进行加工。
模块的放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1
模块添加了一个新方法m3()
,然后返回新的module1
模块。
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"(Loose augmentation)。
var module1 = (function (mod) {
//...
return mod;
})(window.module1 || {});
与"放大模式"相比,“宽放大模式”就是“立即执行函数”的参数可以是空对象