zoukankan      html  css  js  c++  java
  • 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>
  • 相关阅读:
    图片文件重命名
    MySql基础学习-Sql约束
    MySql基础学习-库表操作
    java内存模型
    数据库常用函数整理
    linux用户管理
    Db2数据库在Linux下的安装和配置
    图像金字塔
    特征值与特征向量
    齐次线性方程组
  • 原文地址:https://www.cnblogs.com/wingzw/p/7340674.html
Copyright © 2011-2022 走看看