1.种子模块

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执行异常。我们看一下各大库怎么处理。

 1             //jQuery的makeArray
 2             var makeArray = function(array, results) {
 3                 var ret = results || [];
 4 
 5                 if(array != null) {
 6                     // The window, strings (and functions) also have 'length'
 7 
 8                     if(array.length == null || type === "string" || type === "function")
 9                         ret[0] = array;
10                     else
11                         while(i)
12                             ret[--i] = array[i];
13 
14                 }
15                 return ret;
16             }

jQuery对象是用来储存与处理dom元素的,它主要依赖于setArray方法来设置和维护长度与索引,而setArray的参数要求是一个数组,因此makeArray的地位非常重要。这方法保证就算没有参数也要返回一个空数组。

Prototype.js的$A方法:

 1             function $A(iterable){
 2                 if(iterable)
 3                     return [];
 4                 if(iterable.toArray)
 5                     return iterable.toArray();
 6                 var length=iterable.length ||0,results=new Array(length);
 7                 while(length--)
 8                     results[length]=iterable[length];
 9                 return results;
10             }

mootools的$A方法:

 1 function $A(iterable) {
 2                 if(iterable.item) {
 3                     var l = iterable.length,
 4                         array = new Array(l);
 5                     while (l--)
 6                         array[l]=iterable[l];    
 7                     return array;
 8                 }
 9                 return Array.prototype.slice.call(iterable);
10             }

Ext的toArray()方法:

 1             var toArray = function() {
 2                 returnisIE ?
 3                     function(a, i, j, res) {
 4                         res = [];
 5                         Ext.each(a, function(v) {
 6                             res.push(v);
 7                         });
 8                     } :
 9                     function(a, i, j) {
10                         return Array.prototype.slice.call(a, i || 0, j || a.length);
11                     }
12             }();

Ext的设计比较巧妙,功能也比较强大。它开始就自动执行自身,以后就不用判定浏览器了。它还有两个可选参数,对生成的数组进行操作。

dojo的toArray和Ext一样,后面两个参数是可选的,只不过第二个是偏移量,最后一个是已有的数组,用于把新生的新组元素合并过去。

 1.4  类型的判定

JavaScript存在两套类型系统,一套是基本数据类型,另一套是对象类型系统。基本数据类型包括6种,分别是:undefined,string,null,boolean,function,object。基本数据类型是通过typeof来检测的。对象类型系统是以基础类型系统为基础的,通过instanceof来检测。然而,JavaScript自带的这两套识别机制非常不靠谱,于是催生了isXXX系列。就拿typeof来说,它只能粗略识别出string,number,boolean,function,undefined,object这6种数据类型,无法识别Null、RegExp等细分对象类型。

让我们看一下这里面究竟有多少陷阱。

 1             typeof null; //"object"
 2             typeof document.childNodes; //safari "function"
 3             typeof document.createElement("embed"); //"function"
 4             typeof /d/i; //实现了ecma262v4的浏览器返回"function"
 5             typeof window.alert; //"IE678 "object"
 6 
 7             var iframe = document.createElement('iframe');
 8             document.body.appendChild(iframe);
 9             xArray = window.frames[window.frames.length - 1].Array;
10             var arr = new xArray(1, 2, 3); //[1,2,3]
11             arr instanceof Array; //false
12             arr.constructor === Array; //false
13 
14             window.onload = function() {
15                 alert(window.constructor); //IE67 undefined
16                 alert(document.constructor);//IE67 undefined
17                 alert(document.body.constructor);//IE67 undefined
18                 alert((new ActiveXObject('Microsoft.XMLHTTP')).constructor)//IE6789 undefined
19             }
20             
21             isNaN("aaa");//true
22     

上面分4组,第一组是typeof的坑。第二组是instanceof的陷阱,只是原型上存在此对象的构造器它就返回true,但如果跨文档比较,iframe里面的数组实例就不是父窗口的Array的实例。第三组相关constructor的陷阱,在旧版本IE下DOM与BOM对象的constructor属性是没有暴露出来的。最后有关NaN,NaN对象与null,undefined一样,在序列化时是原样输出的,但isNaN这方法非常不靠谱,把字符串、对象放进去也返回true,这对我们的序列化非常不利。

另外,在IE下typeof 还会返回unknow 的情况。

另外,以前人们总是以document.all(在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley 提出的鸭子测试)是否存在来判定IE,这其实是很危险的。因为用document.all 来获取页面中的所有元素是不错的注意,这个方法Firefox,Chrome觊觎好久了,不过人们都这样判定,于是有了chrome这样的闹剧。

在判定undefined、null、string、number、boolean、function这6个还算简单,前面两个可以分别于void(0)、null比较,后面4个直接typeof也可满足90%的情形。这样说是因为string,number,boolean可以包装成“伪对象”,typeof无法按照我们的意愿工作了。

            typeof new Boolean(1);//"object"
            typeof new Number(1);//"object"
            typeof new String("1");//"object"

这些还是最简单的,难点在于RegExp与Array。判定RegExp类型的情形很少,Array则不一样。

isArray 早些年的探索:

 1             function isArray(arr) {
 2                 return arr instanceof Array;
 3             }
 4 
 5             function isArray(arr) {
 6                 return !!arr && arr.constructor == Array;
 7             }
 8 
 9             function isArray(arr) {
10                 return arr != null && typeof arr === "object" && 'splice' in arr && 'join' in arr;
11             }
12 
13             function isArray(array) {
14                 var result = false;
15                 try {
16                     new array.constructor(Math.pow(2, 32))
17                 } catch(e) {
18                     result = /Array/.test(e.message)
19                 }
20                 return result;
21             }

至于null、undefined、NaN直接这样

 1             function isNaN(obj) {
 2                 return obj !== obj;
 3             }
 4 
 5             function isNull(obj) {
 6                 return obj === null;
 7             }
 8 
 9             function isUndefined(obj) {
10                 return obj === void 0;
11             }

最后要判定的对象是window,用于ECMA是不规范Host对象,window对象属于Host,所以也没有被约定,就算Object.prototype.toString 也对它无可奈何。

 

 1             jQuery.isPlainObject = function(obj) {
 2                 //首先排除基础类型不为Object的类型,然后是DOM节点与window对象
 3                 if(jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) {
 4                     return false;
 5                 }
 6                 //然后回溯它的最近的原型对象是否有isPlainObject
 7                 //旧版本IE的一些原生对象没有暴露constructor、Prototype,因此会在这里过滤
 8                 try {
 9                     if(obj.constructor && !hasOwn.call(obj.constructor.prototype, "isPlainObject")) {
10                         return false;
11                     }
12                 } catch(e) {
13                     return false;
14                 }
15                 return true;
16             }

1.5  主流框架引入的机制——domReady

domReady其实是一种名为“DOMContentLoaded”事件的别称,不过由于框架的需要,它与真正的DOMContentLoaded有一点区别。在许多旧的JavaScript数据中,它们都会教导我们把JavaScript逻辑写在window.onload回调中,以防DOM树还没有建完就开始对节点操作,导致出错。而对于框架来说,越早介入对DOM的干涉就越好,如要进行什么特征侦测之类的。domReady还可以满足用户提前绑定时间的需求,因为有事页面图片等资源过多,window.onload就迟迟不能触发,这时若没有绑定事件,用户点哪个按钮没反应。因此主流框架都引入了domReady机制,并且费了很大劲兼容所有的浏览器,具体策略如下。

(1)对于支持DOMContentLoaded事件的使用DOMContentLoaded事件。

(2)旧版本IE使用Diego Perini发现的著名hack!

 1             function IEContentLoaded(w, fn) {
 2                 var d = w.document,
 3                     done = false,
 4                     init = function() {
 5                         if(!done) {
 6                             done = true;
 7                             fn();
 8                         }
 9                     };
10                 (function() {
11                     try {//在DOM未建完之前调用元素doScroll抛出错误
12                         d.documentElement.doScroll("left");
13                     } catch(e) {
14                         setTimeout(arguments.callee, 50);
15                         return;
16                     }
17                     init();//没有错误则执行用户回调
18                 })();
19                 //如果用户是在domReady之后绑定这个函数呢?立即执行它
20                 d.onreadystatechange = function() {
21                     if(d.readyState == 'complete') {
22                         d.onreadystatechange = null;
23                         init();
24                     }
25                 };
26             }

此外,IE还可以通过script defer hack进行判定。

http://www.cnblogs.com/pigtail/archive/2012/06/18/2553556.html

1.6  无冲突处理

无冲突处理也叫多库共存。不得不说,$是最重要的函数名,这么多框架都爱用它做自己的命名空间。在jQuery还比较弱小的时候,如何让人们使用它呢?当时Prototype是主流,jQuery于是发明了noConflict函数,下面是源代码:

 1             var window = this,
 2                 undefined,
 3                 _jQuery = window.jQuery,
 4                 _$ = window.$;
 5             //把window存入闭包中的同名变量,方便内部函数在调用window时不用费大力气查找它
 6             //_jQuery与_$用于以后重写
 7             jQuery = window.jQuery = window.$ = function(selector, context) {
 8                 //用于返回一个jQuery对象
 9                 return new jQuery.fn.init(selector, context);
10             }
11             jQuery.extend({
12                 noConflict: function(deep) {
13                     //引入jQuery类库后,闭包外面的window.$与window.jQuery都存储着一个函数
14                     //它是用来生成jQuery对象或在domReady后执行里面的函数的
15                     //回顾最上面的代码,在还没有把function赋给它们时,_jQuery与_$已经被赋值了
16 
17                     //因此它们俩的值比如是undefined
18                     //因此这种放弃控制权的技术很简单,就是用undefined把window.$里面的jQuery系的函数清除掉
19                     //这时Prototype或mootools的$就可以使用了
20 
21                     window.$ = _$; //相当于window.$=undefined
22 
23                     //如果连你的程序也有一个jQuery的东西,jQuery可以大方地连这个也让渡出去
24                     //这时就要为noConflict添加一个布尔值,为true
25                     if(deep)
26                         window.jQuery = _jQuery; //相当于window.jQuery=undefined
27                     return jQuery;
28                 }
29             });

使用时,先引入别的库,然后引入jQuery,使用调用$.noConflict()进行改名,这样就不影响别的$运作了。

mass Framework更进一步,在引入种子模块的script标签上定义一个nick属性,那么释放出来的命名空间就是你的那个属性值。里面也偷偷实现了jQuery那种机制。

        <script nick="AAA"  src="mass.js"></script>
        <script>
            AAA.log(1);
        </script>
原文地址:https://www.cnblogs.com/wingzw/p/7340674.html