赞叹下JQuery的构造器$()完美

jQuery的构造器已经做得非常之完美
服务器君一共花费了391.298 ms进行了6次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

jQuery的$符号非常神奇,它可以接受一个字符,也可以接受一个文档对象或window对象,亦可以传个函数进行变为domReady加载器。显然,能做到这一步,其实现是相当的复杂,这个实现就是它的init方法,jQuery的真实构造器。它功能也随着版本的升级而升级,越来越长。

2009-01-13发布的1.3版

init: function( selector, context ) {
    // Make sure that a selection was provided
    selector = selector || document;
 
    // 处理节点参数,直接添加属性到新实例上
    if ( selector.nodeType ) {
        this[0] = selector;
        this.length = 1;
        this.context = selector;
        return this;
    }
    // 处理字符串参数
    if ( typeof selector === "string" ) {
        // 判定是否为HTML片断还是ID
        var match = quickExpr.exec( selector );
      
        if ( match && (match[1] || !context) ) {
 
            // 如果是HTML片断,转换一个由节点构造的数组
            if ( match[1] )
                selector = jQuery.clean( [ match[1] ], context );
 
            // 如果是ID,则查找此元素,如果找到放进空数组中
            else {
                var elem = document.getElementById( match[3] );
 
                // Make sure an element was located
                if ( elem ){
                    // 处理 IE and Opera 混淆ID与NAME的bug
                    if ( elem.id != match[3] )
                        return jQuery().find( selector );
                    var ret = jQuery( elem );
                    ret.context = document;
                    ret.selector = selector;
                    return ret;
                }
                selector = [];
            }
        } else
        //使用Sizzle处理其他CSS表达式,生成实例并返回
            return jQuery( context ).find( selector );
        // 处理函数参数,直接domReady
    } else if ( jQuery.isFunction( selector ) )
        return jQuery( document ).ready( selector );
 
    //处理jQuery对象参数,简单地将其两个属性赋给新实例
    if ( selector.selector && selector.context ) {
        this.selector = selector.selector;
        this.context = selector.context;
    }
    //将上面得到节点数组,用setArray方法把它们变成实例的元素
    return this.setArray(jQuery.makeArray(selector));
},

2009-02-19发布的1.32版

init: function( selector, context ) {
    // Make sure that a selection was provided
    selector = selector || document;
    // 处理节点参数,直接添加属性到新实例上
    if ( selector.nodeType ) {
        this[0] = selector;
        this.length = 1;
        this.context = selector;
        return this;
    }
     //处理字符串参数
    if ( typeof selector === "string" ) {
        //判定是否为HTML片断还是ID
        var match = quickExpr.exec( selector );
        if ( match && (match[1] || !context) ) {
           // 如果是HTML片断,转换一个由节点构造的数组
            if ( match[1] )
                selector = jQuery.clean( [ match[1] ], context );
            else {
                var elem = document.getElementById( match[3] );
 
                // 如果是ID,则查找此元素,如果找到放进空数组中
                if ( elem && elem.id != match[3] )
                    return jQuery().find( selector );
 
                //这里对1.3版做了些优化,更简洁
                var ret = jQuery( elem || [] );
                ret.context = document;
                ret.selector = selector;
                return ret;
            }
        } else
            //使用Sizzle处理其他CSS表达式,生成实例并返回
            return jQuery( context ).find( selector );
 
         // 处理函数参数,进行domReady操作
    } else if ( jQuery.isFunction( selector ) )
        return jQuery( document ).ready( selector );
 
     //处理jQuery对象参数,简单地将其两个属性赋给新实例
    if ( selector.selector && selector.context ) {
        this.selector = selector.selector;
        this.context = selector.context;
    }
//这里对1.3版做了些扩展,允许传珍上元素集合(HTMLCollection)与节点集合(NodeList),
//元素数组可能是我们用字符串转换过来的,也可以是用户直接传进来的
    return this.setArray(jQuery.isArray( selector ) ? selector : jQuery.makeArray(selector));
},

2010-01-13发布的1.4版

init: function( selector, context ) {
    var match, elem, ret, doc;
 
    //处理空白字符串,null,undefined参数(新增),返回一个非常纯净的实例
    if ( !selector ) {
        return this;
    }
 
    // 处理节点参数,直接添加属性到新实例上
    if ( selector.nodeType ) {
        this.context = this[0] = selector;//写法上优化
        this.length = 1;
        return this;
    }
 
    //处理字符串参数
    if ( typeof selector === "string" ) {
        //  判定是否为HTML片断还是ID
        match = quickExpr.exec( selector );
        if ( match && (match[1] || !context) ) {
 
            //如果是HTML片断
            if ( match[1] ) {
                //取得文档对象
                doc = (context ? context.ownerDocument || context : document);
 
                // 如果是单个标签,直接使用 document.createElement创建此节点并放入数组中
                ret = rsingleTag.exec( selector );
 
                if ( ret ) {
                    //如果后面跟着一个纯净的JS对象,则为此节点添加相应的属性或样式
                    if ( jQuery.isPlainObject( context ) ) {
                        selector = [ document.createElement( ret[1] ) ];
                        jQuery.fn.attr.call( selector, context, true );
                    } else {
                        selector = [ doc.createElement( ret[1] ) ];
                    }
 
                } else {
                    //改由buildFragment来生成节点集合(NodeList)
                    ret = buildFragment( [ match[1] ], [ doc ] );
                    selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
                }
            } else {
                // 如果是ID,则查找此元素,如果找到放进空数组中
                elem = document.getElementById( match[2] );
 
                if ( elem ) {
                    // 处理 IE and Opera 混淆ID与NAME的bug
                    if ( elem.id !== match[2] ) {
                        return rootjQuery.find( selector );
                    }
                    //这里也做了一些优化,原来是很傻地再生成一个jQuery实例
                    this.length = 1;
                    this[0] = elem;
                }
                this.context = document;
                this.selector = selector;
                return this;
            }
 
            // 如果字符是很简单的标签选择器,那基本没有必要走Sizzle路线,直接getElementsByTagName,很好的优化
        } else if ( !context && /^\w+$/.test( selector ) ) {
            this.selector = selector;
            this.context = document;
            selector = document.getElementsByTagName( selector );
 
            // 如果第二个参数不存在或者是jQuery对象,那么用它或rootjQuery调用find查找目标节点(走Sizzle路线)
        } else if ( !context || context.jquery ) {
            return (context || rootjQuery).find( selector );
 
            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
        } else {
            //如果第二个参数已指定为某元素节点,转为jQuery对象,走Sizzle路线
            return jQuery( context ).find( selector );
        }
 
        // 处理函数参数,直接domReady
 
    } else if ( jQuery.isFunction( selector ) ) {
        return rootjQuery.ready( selector );
    }
    //处理jQuery对象参数,简单地将其两个属性赋给新实例
    if (selector.selector !== undefined) {
        this.selector = selector.selector;
        this.context = selector.context;
    }
//这里又做了些许修改,缘于makeArray可以接受第二个参数(可以是数组或类数组,这时相当合并操作)
    return jQuery.isArray( selector ) ?
        this.setArray( selector ) ://内部用push方法,迅速将一个普通对象变成类数组对象
        jQuery.makeArray( selector, this );
},
接着是广受欢迎的2010-02-13发布的1.42版
init: function( selector, context ) {
    var match, elem, ret, doc;
 
    // 处理空白字符串,null,undefined参数
    if ( !selector ) {
        return this;
    }
    // 处理节点参数
    if ( selector.nodeType ) {
        this.context = this[0] = selector;
        this.length = 1;
        return this;
    }  
    // 处理body参数(新增)
    if ( selector === "body" && !context ) {
        this.context = document;
        this[0] = document.body;
        this.selector = "body";
        this.length = 1;
        return this;
    }
 
    // 处理字符串参数,分七种情形:
    //①单个标签,带对象属性包           --->   jQuery.merge
    //②单个标签,不带对象属性包         --->   attr + jQuery.merge
    //③复杂的HTML片断                 --->   buildFragment + jQuery.merge
    //④ID选择器,与找到的元素的ID不同   --->   getElementById + Sizzle + pushStack
    //⑤ID选择器,与找到的元素的ID相同   --->   getElementById + 简单属性添加
    //⑥标签选择器                     --->   getElementsByTagName + jQuery.merge
    //⑦其他CSS表达式                  --->   Sizzle + pushStack
    if ( typeof selector === "string" ) {
        match = quickExpr.exec( selector );
        if ( match && (match[1] || !context) ) {
            if ( match[1] ) {
                doc = (context ? context.ownerDocument || context : document);
                ret = rsingleTag.exec( selector );
                if ( ret ) {
                    if ( jQuery.isPlainObject( context ) ) {
                        selector = [ document.createElement( ret[1] ) ];
                        jQuery.fn.attr.call( selector, context, true );
 
                    } else {
                        selector = [ doc.createElement( ret[1] ) ];
                    }
                } else {
                    ret = buildFragment( [ match[1] ], [ doc ] );
                    selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
                }
                     
                return jQuery.merge( this, selector );
            } else {
                elem = document.getElementById( match[2] );
 
                if ( elem ) {
                    if ( elem.id !== match[2] ) {
                        return rootjQuery.find( selector );
                    }
                    this.length = 1;
                    this[0] = elem;
                }
 
                this.context = document;
                this.selector = selector;
                return this;
            }
        } else if ( !context && /^\w+$/.test( selector ) ) {
            this.selector = selector;
            this.context = document;
            selector = document.getElementsByTagName( selector );
            return jQuery.merge( this, selector );
             
        } else if ( !context || context.jquery ) {
            return (context || rootjQuery).find( selector );
        } else {
            return jQuery( context ).find( selector );
        }
        // 处理函数参数,直接domReady
    } else if ( jQuery.isFunction( selector ) ) {
        return rootjQuery.ready( selector );
    }
    //处理jQuery对象参数
    if (selector.selector !== undefined) {
        this.selector = selector.selector;
        this.context = selector.context;
    }
    //无论是数组还是类数组(如NodeList),统统使用jQuery.makeArray来为实例添加新的元素
    return jQuery.makeArray( selector, this );
},

另附上makeArray方法与merge方法,merge方法好神奇啊,

makeArray: function( array, results ) {
    var ret = results || [];
 
    if ( array != null ) {
        // The window, strings (and functions) also have 'length'
        // The extra typeof function check is to prevent crashes
        // in Safari 2 (See: #3039)
        if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) {
            push.call( ret, array );
        } else {
            jQuery.merge( ret, array );
        }
    }
 
    return ret;
},
merge: function( first, second ) {
    var i = first.length, j = 0;
 
    if ( typeof second.length === "number" ) {
        for ( var l = second.length; j < l; j++ ) {
            first[ i++ ] = second[ j ];
        }
     
    } else {
        while ( second[j] !== undefined ) {
            first[ i++ ] = second[ j++ ];
        }
    }
 
    first.length = i;
 
    return first;
},

2011-01-23发布的1.5版,其init方法与1.42的变化不大:只有两处做了改动:

//1.42
-  ret = buildFragment( [ match[1] ], [ doc ] );
-  selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
//1.5
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
 
//1.42
- return jQuery( context ).find( selector );
//1.5
+ return this.constructor( context ).find( selector );//目的就是为了不再生成新实例

2011-05-02发布的jquery1.6,变化不大,只是对HTML片断进行了更严密的判定:

// Are we dealing with HTML string or an ID?
   if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
  // Assume that strings that start and end with <> are HTML and skip the regex check
    match = [ null, selector, null ];
   } else {
    match = quickExpr.exec( selector );
   }

总体来说,jQuery的构造器已经做得非常之完美,基本上达到"改无可改"的地步了。但是要保证其高效运作,我们还需要一点选择器的知识与了解buildFragment方法的运作,因为这两个实在太常用了,但也是最耗性能的。

本文地址:http://www.nowamagic.net/librarys/veda/detail/1435,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1435

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

大家都在看

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《代码整洁之道》 马丁(Robert C. Martin) (作者), 韩磊 (译者)

软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关。这一点,无论是敏捷开发流派还是传统开发流派,都不得不承认。《代码整洁之道》提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,《代码整洁之道》作者给出了一系列行之有效的整洁代码操作实践。这些实践在《代码整洁之道》中体现为一条条规则(或称“启示”),并辅以来自现实项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。

更多计算机宝库...