第 1 章 种子模块
种子模块也叫核心模块,是框架的最先执行的部分。即便像jQuery那样的单文件函数库,它的内部也分许多模块,必然有一些模块冲在前面立即执行,有一些模块只有用到才执行,也有一些模块可有可无,存在感比较弱,只在特定浏览器下才运行。
种子模块就是其中的急先锋,它里面的方法不一定要求个个神通广大,设计优良,但一定极具扩展性,常用,稳定。扩展性是指通过它们能将其他模块的方法包进来,让种子像大树一样成长;常用是指绝大多数模块都用到它们,防止做重复工作;稳定是指不能轻易在以后版本就给去掉,要信守承诺。
参照许多框架与库的实现,我认为种子模块应该包含如下功能:对象扩展,数组化,类型判定,简单的事件绑定与卸载,无冲突处理,模块加载与domReady。
1.1 命名空间
种子模块作为一个框架的最开始部分,除了负责辅建全局用的基础设施外,你有没有想到给读者一个震撼的开场呢?俗话说,好的开头是成功的一半。
时下“霸主”jQuery就有一个很好的开头——IIFE(立即调用函数表达式),一下子吸引住读者,让读者吃一颗定心丸——既然作者的水平如此高超,还怕什么啊,直接拿来用。
IIFE是现代JavaScript框架最主要的基础设施,它像细胞膜一样包裹自身,防止变量污染。但我们总得在Windows里设置一个立足点,这个就是命名空间。基本上我们可以把命名空间等同于框架的名字,不过对于某些框架,它们是没有统一的命名空间,如Prototype.js , mootools。它们就是不想让你感觉到框架的存在,它的意义深透到JavaScript、DOM、COM等整个执行环境的每个角落,对原生对象的原型进行扩展。由于道格拉斯(JSON作者)的极力反对,新兴的框架都在命名空间上构建了。
命名空间是干什么用呢?这里就不多说了。我们看怎么在JavaScript模拟命名空间。JavaScript一切基于对象,但只有复合类型的对象才符合这要求,比如function、regexp、object……不过最常用的还是object与function。我们往一个对象上添加一个属性,而这个属性又是一个对象,这个对象我们又可以为它添加一个对象,通过这种方法,我们就可以有条不紊地构建我们的框架。用户想调用某个方法,它就以XXX.YYY.ZZZ()的形式调用。
if (typeof(Ten) === "undefined") {
Ten = {};
Ten.Function = {/*略*/}
Ten.Array = {/*略*/}
Ten.Class = {/*略*/}
Ten.JSONP = new Ten.Class(/*略*/)
Ten.XHR = new Ten.Class(/*略*/)
纵观各大类库的实现,一开始基本都是定义一个全局变量作为命名空间,然后对它进行扩展,如Base2的Base、Ext的Ext、jQuery的jQuery、YUI的YUI、dojo的dojo、MochiKit的MochiKit等。从全局变量的污染程度来看,分为两大类。
Prototype、mootools与Base2归为一类。Prototype的哲学是对JavaScript原生对象进行扩展。早些年,Ptototype差点成为事实的标准,因此基本没有考虑到与其他库的共存问题。基于Prototype,也发展出诸如script.aculo.us、rico、Plotr、ProtoChart、Scripty 2等非常优秀的类库及一大堆收费插件,非jQuery那一大堆垃圾插件所能比拟的。而且,有点渊源的插件几乎都与Prototype有关,如著名的lightbox。mootools是Prototype的升级版,更加OO,全面复制其API。Base则是想修复IE的Bug,让IE拥有标准浏览器的API,因此也把所有原生对象污染一遍。
第二类是jQuery、YUI、EXT这些框架。YUI与EXT就是像上面给出的代码那样,以叠罗汉方式构建的。jQuery则另辟蹊径,它是以选择器为导向的,因此它的命名空间是一个函数,方便用户把CSS表达器字符串传进来,然后通过选择器引擎进行查找,最后返回一个jQuery实例。另外,像jQuery最初也是非常弱小的,它想让人家试用自己的框架,但也想像Prototype那样使用美元符号作为它的命名空间。因此它特意实现了多库共存机制,在$、jQuery与用户指定的新命名空间中任意切换。
jQuery的多库共存原理很简单,因此后来也成为许多小库的标配。首先把命名空间保存到一个临时变量中,注意这时这个对象并不是自己框架的东西,可能是Prototype.js等巨头的,然后再搞个noConflict放回去。
//jQuery1.2
var _jQuery = window.jQuery, _$ = window.$;//先把可能存在的同名变量保存起来
jQuery.extend({
noConflict: function(deep) {
window.$ = _$;//这时再放回去
if (deep)
window.jQuery = _jQuery;
returnjQuery;
}
})
但jQuery的noConflict只对单文件的类库框架有用,像EXT就不能复制了。因此把命名空间改名后,将EXT置为null,然后又通过动态加载方式引入新的JavaScript文件,该文件再以EXT调用,将会导致报错。
mass Framework对jQuery的多库共存进行改进,它与jQuery一样拥有两个命名空间,一个是美元符号的短命名空间,另一个是根据URL动态生成的长命名空间(jQuery就是jQuery!)。
namespace = DOC.URL.replace( /(#.+|\W)/g,'');
短的命名空间随便用户改名,长的命名空间则是加载新的模块时用的,虽然用户在模块中使用$做命名空间,但当JavaScript文件加载下来时,我们会对里面的内容再包一层,将$指向正确的对象,具体实现见define方法。