将代码组织到类中的一个重要原因是,让代码更加“模块化”,可以在很多不同场景中实现代码的重用,但类不是唯一的模块化代码的方式。
一般来讲,模块是一个独立的JS文件,模块文件可以包含一个类定义,一组相关的类,一个实用函数库或者是一些待执行的代码。
只要以模块的形式编写代码,任何JS代码段都可以当作一个模块。
JS中并没有定义用以支持模块的语言结构(但Imports 和exports是JS的保留字,未来版本可能会支持)。现阶段我们在JS编写模块代码的时候更多是遵循某种编码规定。
1. 使用命名空间对象
在模块创建过程中避免污染全局变量的一个方法是使用一个对象作为命名空间。它将函数和值作为命名空间对象属性存储起来,而不是定义全局函数和变量。
//假设Set的命名空间是collections.sets
var collections; //声明或重新声明这个全局变量
if(!collections){
collections={}; //如果原本不存在,则创建一个顶层的命名空间对象
}
collections.sets={}; //将sets命名空间创建在它的内部
coleections.sets.AbstractSet=function(){ //在collections.sets内定义set类
...
}
如果你的网站是abc.com, 那么可以通过com.abc.collections.sets这个地址来发布模块。如:
var sets=com.abc.collections.sets
并且,模块文件应当和命名空间匹配,sets模块得保存在文件sets.js中,如果这个模块使用命名空间collections.sets,那么这个文件保存目录应为collections/sets.js.
也就是com/abc/collections/sets.js
2.作为私有命名空间的函数
模块对外导出一些公用API,这些API是提供给其他程序使用的,但同时模块内也往往需要一些额外的辅助函数和方法,这些方法和函数不需要模块外部可见,相当与private。
//声明全局变量Set,使用一个函数的返回值给它赋值,而不是这个函数赋值给Set
//这是个函数表达式,而不是一条语句,因此函数invocation并没有创建变量
var Set = (function invocation() {
function Set() { // 这个构造函数是局部变量
this.values = {}; // 这个对象用来保存这个集合
this.n = 0; // 集合长度
this.add.apply(this, arguments); // 将所有参数添加到这个集合
}
// 现在给 Set.prototype定义实例方法
Set.prototype.contains = function(value) {
//这里我们调用了 v2s(), 但没有使用笨重的前缀Set._v2s()
return this.values.hasOwnProperty(v2s(value));
};
Set.prototype.size = function() { return this.n; };
Set.prototype.add = function() { /* ... */ };
Set.prototype.remove = function() { /* ... */ };
Set.prototype.foreach = function(f, context) { /* ... */ };
// 这里是上面方法用到的一些辅助变量和函数
// 它们不属于共有API,但它们都隐藏在这个函数作用域里
function v2s(val) { /* ... */ }
function objectId(o) { /* ... */ }
var nextId = 1;
return Set;
}()); // 定义函数之后立即执行
这里使用了立即执行的匿名函数,这在JS中是一种惯用法。如果想让代码在一个私有命名空间中运行,只须给这段代码加上前缀“(function(){"和后缀"}())”.
开始的左括号确保这时一个函数表达式,而不是函数定义语句,因此可以给该前缀添加一个函数名让代码变得更清晰,如invocation表示函数在定义后立即执行。我们也可以用namespace来强调这个函数被用作命名空间。
var collections;
if(!collections) collections={};
collections.sets=(function namespace(){
//这里定义多种“集合类”
return {
AbstractSet:AbstractSet,
NotSet:NotSet,
AbstractWritableSet:AbstractWritableSet,
ArraySet:ArraySet,
SingletonSet:SingletonSet
}
}());
//或者也可以这样写
var collections;
if(!collections) collections={};
collections.sets=(new function namespace(){
//这里省略很多代码
this.AbstractSet=AbstractSet,
this.NotSet=NotSet,
this.AbstractWritableSet=AbstractWritableSet,
this.ArraySet=ArraySet,
this.SingletonSet=SingletonSet
//注意这里没有返回值
}());