1. 种子模块
种子模块也叫核心模块,是框架的最先执行的部分。
粽子模块包含功能:对象扩展,数组化,类型判定,简单的事件绑定与卸载,无冲突处理,模块加载与domReady。本章讲解以mass Framework的种子模块为范本。
1.1 命名空间
种子模块作为一个框架的最开始部分,除了负责辅建全局的基础设施外,你有没有想到给读者一个震撼的开场呢?俗话说,好的开头时成功的一半。
时下“霸主”jQuery 就有一个很好的开头——IIFE(立即调用函数表达式),一下子吸引住读者,让读者吃了一颗定心丸。
IIFE是现代JavaScript框架最主要的基础设施,它像细胞膜一样包裹自身,防止变量污染。但我们总得在Window里设置一个立足点,这个就是命名空间。
1 if(typeof(Ten) === "undefined") { 2 Ten = {}; 3 Ten.Function = {} 4 Ten.Array = {} 5 Ten.Class = {} 6 Ten.JSONP = new Ten.Class() 7 Ten.XHR = new Ten.Class() 8 9 }
纵观各大类库的实现,一开始基本都是定义一个全局变量作为命名空间,然后对它进行扩展,如Base2的Base、Ext的Ext,jQuery的jQuery、YUI的YUI、dojo的dojo等。从全局变量的污染程度来看,分为两大类。
Prototype、mootools与Base2归为一类。Prototype的哲学是对JavaScript原生对象进行扩展。
第二类是jQuery、YUI、EXT这些框架。YUI与EXT就像上面给出的代码那样,以叠罗汉方式构架的。jQuery则另辟蹊径,它是以选择器为导向的,因此它的命名空间是一个函数,方便用户把CSS表达式字符串传进来,然后通过选择器引擎进行查找,最后返回一个jQuery实例。jQuery初期非常弱小,它想让别人用自己的框架,但也想像Prototype那样使用美元符号作为命名空间。因此它特意实现了多库共存机制,在$,jQuery与用户指定的命名空间中任意切换。
jQuery的多库共存原理很简单,因此后来也成为许多小库的标配。首先把命名空间保存到一个临时变量中,注意这时这个对象并不是自己框架的东西,可能是Prototype.js等巨头的,然后再搞个noConflict放回去。
1 _jQuery = window.jQuery, _$ = window.$; //先把可能存在的同名变量保存起来 2 3 jQuery.extend({ 4 noConflict: function(deep) { 5 window.$ = _$;//这时再放回去 6 if(deep) { 7 window.jQuery = _jQuery; 8 } 9 return jQuery; 10 };
但jQuery的noConflict只对单文件的类库框架有用,想EXT就不能复制了。因此把命名空间改名后,将EXT置为null,然后又通过动态加载方式引入新的JavaScript文件,该文件再以EXT调用,将会导致报错。
mass Framework对JQuery的多库共存进行改进,它与jQuery一样有两个命名空间,一个是美元符号,一个是根据URL动态生成的长命名空间(jQuery就是jquery)
namespace=DOC.URL.replace(/(#.+|\W)/g,‘‘);
短的命名空随便用户改名,长的命名空间则是加载新的模块时用的,虽然用户在模块中使用$做命名空间,但当JavaScript问及加载下来时,我们会对立面的内容再包一层,将$指向正确的对象,具体实现见define方法。
1.2 对象扩展
我们需要一种机制,将新功能添加到我们的命名空间上。这方法在JavaScript通常被称做extend或mixin。JavaScript对象在属性描述符(Property Descriptor)没有诞生之前,是可以随意添加、更改、删除其成员,因此扩展一个对象非常便捷。一个简单的扩展方法实现是这样。
1 function extend(destination,source){ 2 for(var property in source) 3 destination[property]=source[property] 4 return destination; 5 }
不过,旧版本IE在这里有个问题,它认为像Object的原型方法就是不应该被遍历出来,因此for in循环是无法遍历名为valueOf、toString的属性名。这导致,后来人们模拟Object.keys方法实现时也遇到了这个问题。
Object.keys = Object.keys || function(obj) { var a = []; for(a[a.length] in obj); return a; }
不同的框架,这个方法不同的实现,如EXT分为apply与applyIf两个方法,前者会覆盖目标对象的同名属性,而后者不会。dojo允许多个对象合并在一起。jQuery还支持深拷贝。下面是mass Framework的mix方法,支持多对象合并与选择是否覆写。
1 function mix(target, source) { //如果最后参数是布尔,判定是否覆写同名属性 2 var args = [].slice.call(arguments), 3 i = 1, 4 key, ride = typeof args[args.length - 1] == "boolean" ? args.pop() : true; 5 6 if(args.length === 1) { //处理$.mix(hash)的情形 7 target = !this.window ? this : {}; 8 i = 0; 9 } 10 while(source = arge[i++]) { 11 for(key in source) { 12 if(ride || !(key in target)) { 13 target[key] = source[key]; 14 } 15 } 16 } 17 return target; 18 }
1.3 数组化
浏览器下存在许多类数组对象,如function内的arguments,通过document.forms、form.elements、document.links,select.options、document.getElementsByName、childNodes、children等方式获取的节点集合(HTMLCollection、NodeList),或依照某些特殊写法的自定义对象。
1 var arrarLike={ 2 0:"a", 3 1:"1", 4 2:"2", 5 length:3 6 }
类数组对象是一个很好的存储结构,不过功能太弱了,为了享受纯数组的那些便捷方法,我们在处理它们前都会做一下转换。
通常来说,只要[].slice.call 就能转换了,但旧版本IE下的HTMLCollection、NodeList不是Object的子类,采用如上方法将导致IE执行异常。我们看一下各大库怎么处理。