美文网首页JQuery让前端飞我爱编程
jQuery源码分析:深入理解js封装技术

jQuery源码分析:深入理解js封装技术

作者: _shen | 来源:发表于2018-03-01 08:36 被阅读85次

    jQuery实际上就是一个js函数库,对象jQuery作为window对象的一个属性封装了大量具有特定功能的函数。jQuery的代码很长,它是一个历史积累的过程,因此分析jQuery源代码就需要从其历史发展的过程来看,从简到繁,从少到多,去分析就容易得多了。

    1.使用函数来封装用户代码

    (function () {} )();  
    

    这段代码定义了一个匿名函数,并在js加载完毕之后执行一次。这样编写代码的目的就是使用函数作用域来隔离自己与他人的代码,防止污染到函数之外的代码。

    2.对象继承

    js中的对象分为function、object两种。系统会自动为构造函数对象创建一个原型对象,并由由成员porototype指向,同时原型对象也有一个成员constructor指向构造对象。

    js内置的对象有:Object、Array、Boolean、Number、String、Function、Argument、Math、Date、RegExp、Error。

    (1)普通对象(object)
    proto:指向构造自己的构造函数对象的原型对象

    (2)构造对象(function)
    proto:指向构造自己的构造函数对象的原型对象
    prototype:指向自己的原型对象

    (3)原型对象(object)
    proto:指向构造自己的构造函数对象的原型对象
    constructor:指向自己的构造函数对象

    通过模拟类的特性,我们可实现继承和复用。实现继承的方式主要有一下4种:

    • 构造继承
    • 原型继承
    • 实例继承
    • 拷贝继承

    下面展示的是构造继承,通过一个工厂类来实现

    (function () {  
      
     // 定义一个工厂类 factory  
     // 工厂类构造函数  
     var factory = function () {}  
      
     // 私有成员定义,子类无法继承此成员  
     factory.wheels = 4;  
      
     // 公共成员定义,子类可以继承此成员  
     factory.prototype.add = function(){}  
      
     // 工厂方法创建一个对象  
     var o = new factory();  
     // o可以调用factory.prototype.add  
     // o.__proto__ -> factory.prototype  
     o.add();//o自身不存在add方法时系统会通过o.__proto__即原型链不断去查找,如果找不到就报错。  
      
    } )(); 
    

    分析jQuery源码最基本结构,就是这么实现的

    (function (window, undefined ) {  
     var   
      rootjQuery,  
      jQuery = function( selector, context ) {  
       return new jQuery.fn.init( selector, context, rootjQuery);  
      }  
      
      jQuery.fn = jQuery.prototype = {  
       constructor: jQuery,  
       init: function( selector, context, rootjQuery ) {  
        return this;  
       }  
      }  
      
      jQuery.fn.init.prototype = jQuery.fn;  
      
      /////////////////////////////////////////  
      // jQuery私有成员区开始  
      
      jQuery.merge = function () {}  
      
      // jQuery私有成员区结束  
      /////////////////////////////////////////  
      
      /////////////////////////////////////////  
      // jQuery公共成员区开始  
           
      jQuery.fn.merge = function () {}  
      
      // jQuery公共成员区结束  
      /////////////////////////////////////////  
      
      
      window.jQuery = window.$ = jQuery;  
      
    })(window);  
    

    首先,定义了构造函数jQuery

    var jQuery = function ( selector, context ) {}  
    

    这与下面的实现方式是一样的:

    function jQuery ( selector, context ) {}  
    

    参数selector为选择字符串(类似于CSS选择器),context用于保存上下文环境。

    紧接着:

    window.jQuery = window.$ = jQuery;  
    

    这段代码用于将jQuery注册为window下的成员,即全局成员,同时用$替换jQuery,所以就有了我们经常使用的$("div")就可以直接返回一个jQuery对象,显得更为方便简洁。

    每次调用$()都会返回一个新的jQuery对象,避免使用同一个对象产生干扰和修改。它采用的思路是:

    return new jQuery.fn.init( selector, context, rootjQuery);  
    

    每次调用$(),都会new一个jQuery.fn.init对象,这里解释一下jQuery.fn,从后面代码

    jQuery.fn = jQuery.prototype = {}  
    

    可以看出,jQuery就是和jQuery.prototype一样对同一个原型对象的引用,定义为fn也是function的简写,一方面是为了书写方便,另一方面我们从命名可以看出fn主要是用来为jQuery的原型对象增加公共方法,用于子对象的继承。

    作者为jQuery.prototype新创建了一个自定义对象,因此在匿名对象内部需要增加这部分代码,确保原型对象指回jQuery这个构造对象本身,确保完整。:

    constructor: jQuery  
    

    通过jQuery.fn.init构造出一个对象,然后将其引用返回,也就是说,我们通过$()返回的是指向jQuery.fn.init构造的对象的一个引用。那么这个对象的proto应该就是指向jQuery.fn.init.prototype,也就是说

    $().__proto__ = jQuery.fn.init.prototype;  
    

    我们都知道,jQuery有个连缀操作,也就是说可以下面代码这样一直写下去:

    $("div").eq(0).css("color","red");  
    

    但是$().proto 指向了 jQuery.fn.init.prototype,要想实现连缀操作,我们就必须让jQuery.fn.init.prototype指向jQuery.prototype,因此才有了后面这段代码:

    jQuery.fn.init.prototype = jQuery.fn; 
    

    这样一来的话,通过$()构造出来的对象,其原型对象就是jQuery的原型对象,即:

    $().__proto__ = jQuery.prototype;  
    

    $()就可以连缀操作jQuery的公共方法。

    3.jQuery.extend 和 jQuery.fn.extend

    之前我们分析了jQuery源码的基本结构,了解了如何为jQuery添加公共方法和私有方法。

    //私有方法定义  
    jQuery.merge = function () {}  
    //公共方法定义  
    jQuery.fn.merge = function () {}  
    

    (1)实现自己的添加函数

    jQuery.extend = function () {  
        var  
            copy,  
            options = arguments[0] || {};  
      
        if ( typeof options === "object" && typeof options === "function" ) {  
            for ( key in options) {  
                copy = options[key];  
      
                if ( this === copy ) { continue; }  
      
                if ( copy !== undefined ) {  
                    this[key] = copy;  
                }  
      
            }  
        }  
      
        return this;//实现连缀操作  
    }  
      
    jQuery.fn.extend = function () {  
        var  
            copy,  
            key,  
            options = arguments[0] || {};  
      
        if ( typeof options === "object" && typeof options === "function" ) {  
            for ( key in options) {  
                copy = options[key];  
      
                if ( this === copy ) { continue; }  
      
                if ( copy !== undefined ) {  
                    this[key] = copy;  
                }  
      
            }  
        }  
      
        return this;//实现连缀操作  
    }  
    

    jQuery.extend 和 jQuery.fn.extend 实现的代码完全一致,现在考虑一下是否可以将其合并。

    当我们调用jQuery.extend的时候,this是jQuery,即为jQuery进行扩展;当调用jQuery.fn.extend的时候,this是jQuery.fn,即为jQuery.fn(jQuery.prototype进行扩展)。因此,我们可以将其进行合并,即:
    `

    jQuery.extend = jQuery.fn.extend = function () {  
        var  
            copy,  
            key,  
            options = arguments[0] || {};  
      
        if ( typeof options === "object" && typeof options === "function" ) {  
            for ( key in options ) {  
                copy = options[key];  
      
                if ( this === copy ) { continue; }  
      
                if ( copy !== undefined ) {  
                    this[key] = copy;  
                }  
            }  
        }  
      
        return this;  
    }  
    

    (2)实现对象的合并

    obj1 = {id:1,address:"China",owner:{age:30}}
    obj2={age:25,owner:{name:"liebert"}}  
    
    objcombine = {id:1,address:"China",age:25,owner:{name:"liebert",age:30}}  
    

    循环遍历被合并对象成员,设每次循环得到的成员为copy,同时取出接收合并对象的相同key的成员,记为src。对象obj1的成员不能指向对象A本身,因此,首先检测待合并对象成员

    if ( obj1 === copy ) { continue; }  
    

    如果对象合并采取浅拷贝的方式,直接进行赋值操作即可

    obj1[key] = copy;  
    

    如果对象合并采取深拷贝的方式,需要进行递归拷贝,不是所有的变量都需要进行深拷贝,普通的变量是不需要进行深拷贝的,也就是存储在栈空间的变量。深拷贝针对的是object

    jQuery.extend = function () {
        var 
            key,
            src,
            copy,
            isDeep,
            copyIsArray,
            i = 1,
            length = arguments.length;
            target = arguments[0] || {};
    
        if ( typeof target === "boolean" ) {
            //isDeep参数存在,并获取
            isDeep = target;
            //如果isDeep参数存在,需要调整接收合并对象为第二个参数
            target = arguments[1] || {};
            //待合并的对象为第三个参数,即索引为3-1=2
            i = 2;
        }
    
        for ( ; i < length; i++ ) {
            if ( (options = arguments[i]) != null) {}
            for( key in options ) {
                src  = target[key];
                copy = options[key];
    
                if ( target === copy ) { continue; }
    
                if ( isDeep && copy && (typeof copy === "object" || copyIsArray = Array.isArray(copy) ) ) {
                    if ( copyIsArray ) {
                        clone = src && Array.isArray(src) ? src : [] ;
                        copyIsArray = false;
                    } else {
                        clone = src && typeof src ? src : {} ;
                    }
                    target[key] = jQuery.extend( isDeep, clone, copy );
                } else {
                    target[key] = copy;
                }
    
            }
        }
    
        return target;
    }
    

    (3)对比jQuery中extend的实现

    为了方便jQuery方法的添加,jQuery定义了jQuery.extend和jQuery.fn.extend两个方法。

    jQuery.extend = jQuery.fn.extend = function () {
        var 
            //对待合并对象的临时引用
            options, 
            //键名
            key,
            //接收拷贝的变量
            src,
            //待拷贝的变量
            copy,
            //接收拷贝的临时变量
            clone,
            //待复制的变量是否为数组
            copyIsArray,
            //默认为将取第i+1=2个参数
            i      = 1,
            isDeep = false,
            target = arguments[0] || {},
            length = arguments.length;
    
        // 处理深拷贝情况
        if ( typeof target === "boolean" ) {
            isDeep = target;
            target = arguments[1] || {};
            i = 2;
        }
    
        //处理字符串情况
        if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
            target = {};
        }
    
        //如果只有一个参数,就是扩展jQuery自己,修改索引i = 0,target指向jQuery或者jQuery.fn
        //当调用jQuery.extend时,this为jQuery;当调用jQuery.fn.extend时,this为jQuery.fn
        if ( length === i) {
            target = this;
            --i;
        }
    
        for ( ; i < length; i++ ) {
            //处理非null和undefined类型的变量
            if ( (options = arguments[i]) != null ) {
                for ( key in options ) {
                    src  = target[key];
                    copy = options[key];
    
                    if ( target === copy ) {continue;}
    
                    //指定了要进行深拷贝
                    //待拷贝的变量有效
                    //待拷贝的变量为对象或数组,只有对象或数组才可以做深拷贝的
                    if ( isDeep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                        //处理数组
                        if( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && jQuery.isArray(src) ? src : [];
                        //处理对象
                        } else {
                            clone = src && jQuery.isPlainObject(src) ? src : {};
                        }
                        //递归处理
                        target[key] = jQuery.extend( isDeep, clone, copy);
                    } else if ( copy !== undefined ) {
                        target[key] = copy;
                    }
                }
            }
        }// end for
    
        return target;
    }
    

    先对函数的入口参数进行说明:

    jQuery.extend( [isDeep], target[,object1,..., objectN] )  
    
    • isDeep:用于指定是否进行深拷贝,是一个可选参数,默认为false,即不进行深拷贝
    • target:接收拷贝对象,如果不指定object1以及后续的对象,则是对jQuery本身进行扩展
    • object1:待合并到target中的对象

    再来谈一谈具体的处理思路:
    首先,对参数的第一个入口参数arguments[0]进行判断,如果为布尔类型就说明指定了isDeep这个参数;

    var target = arguments[0] || {};  
    if ( typeof target === "boolean" ) {  
        //获取参数中的isDeep值  
        isDeep = target;  
        //获取接收拷贝的对象引用  
        target = arguments[1] || {};  
        //设定拷贝的起始索引,从函数的第三参数开始,跳过isDeep和target  
        i = 2;  
    } 
    

    其次,检测target对象类型,如果不是object或function,则需要创建一个新的对象用于接收

    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {  
        target = {};  
    }
    

    再者,检测一个参数存在的情况,如果是,则视为对jQuery或jQuery.fn本身进行扩展

    if ( length === i ) {  
        target = this,  
        --i;  
    }
    

    接着,开始进行对象的合并。待合并的对象总共有length个,因此需要循环length-i次

    for ( ; i < length; i++ ) {
        //处理非null和undefined类型的变量
        if ( (options = arguments[i]) != null ) {
            for ( key in options ) {
                src  = target[key];
                copy = options[key];
    
                if ( target === copy ) {continue;}
    
                //指定了要进行深拷贝 (isDeep === true)
                //待拷贝的变量有效 (copy !== null || copy !==undefined)
                //待拷贝的变量为对象或数组,只有对象或数组才可以做深拷贝的
                if ( isDeep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    //处理数组
                    if( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];
                    //处理对象
                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }
                    //递归处理
                    target[key] = jQuery.extend( isDeep, clone, copy);
                } else if ( copy !== undefined ) {
                    target[key] = copy;
                }
            }
        }
    }// end for
    

    最后,返回合并后的对象

    return target;  
    

    4.实现CSS选择器功能

    js为我们提供了如下三个函数,用于实现DOM节点的选择:

    getElementById();
    getElementsByClassName();
    getElementsByTagName();

    但是这三个函数的名字太长了,不容易记忆,使用过程中也容易出错,更重要的是不能够向CSS选择器那样,通过一连串的选择符实现节点的选择,因此我们想到通过js现有的函数去模拟CSS选择器。

    function ( window ) {
        var 
            rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
            jQuery = function ( selector ) {
            return new jQuery.fn.init( selector );
        }
    
        /////////////////////////////////////////////////////
        // jQuery private defination 
        // jQuery私有成员区开始
    
        jQuery.type = function ( obj ) {
            return obj == null ? String(obj) : obj.toString();
        }
    
        jQuery.isWindow = function () {
    
        }
    
        jQuery.merge = function ( first, second ) {
            var l = second.length,
                i = first.length,
                j = 0;
    
            if ( typeof l == "number" ) {
                for (;j < l; j++) {
                    first[i++] = second[j++];
                }
            } else {
                while ( second[j] !== undefined ) {
                    first[i++] = second[j++];
                }
            }
    
            first.length = i;
            return first;
        }
        
        jQuery.makeArray = function( arr, results ) {
            var type,
                ret = results || [];
    
            if ( arr != null ) {
                type = jQuery.type( arr );
    
                if ( arr.length == null || type === "string" || type === "function" || type === "regxp" || jQuery.isWindow( arr ) ) {
    
                } else {
                    jQuery.merge( ret, arr );
                }
            }
            return ret;
        }// end of jQuery.makeArray
    
        jQuery.find = function ( selector ) {
            if ( typeof selector !== "string" ) {}
        }
    
        // jQuery私有成员区结束
        /////////////////////////////////////////////////////
    
        /////////////////////////////////////////////////////
        // jQuery public defination
        // jQuery公共成员区开始
        jQuery.fn = jQuery.prototype = {
            constructor: jQuery,
            length:0,
            selector:"",
            init:function ( selector, context ) {
                var match, elem;
                // 规避$(""), $(null), $(undefined), $(false)
                if ( !selector ) {
                    return this;
                }
    
                // 处理HTML节点选择字符串
                if ( typeof selector === "string" ) {
                    // 如果为html标签比如"<p>html</p>"
                    if ( selector.charAt(0) ==="<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >=3 ) {
                        match = [null, selector, null];
                    } else {
                        match = rquickExpr.exec( selector );
                    }
    
                }
                // 处理匹配了html标签
                if ( match && (match[1] || !context) ) {
                    if( match[1] ) {
                        if ( match[1] ) {
                            context = context instanceof jQuery ? context[0] : context;
                            doc = ( context && context.nodeType ? context.ownerDocument || context : document );
                        }
                    } else {
                        elem = document.getElementById( match[2] );
                        if ( elem && elem.parentNode ) {
                            //return rootjQuery.find( selector );
                            this.length = 1;
                            this[0] = elem;
                        }
                        
                        this.context = document;
                        this.selector = selector;
                        return this;
                    }
                } else if ( !context || context.jquery) {
                    return ( context || rootjQuery ).find( selector );
                } else {
                    return this.constructor( context ).find( selector );
                }
                return jQuery.makeArray( selector, this );
            },
            eq: function( i ){
                i = +i;
                return i === -1 ? this.slice( i ) : this.slice( i, i+1 );
            },
            slice: function() {
                return this.pushStack(Array.prototype.slice.apply( this, arguments ), "slice", Array.prototype.slice.call(arguments).join(","));
            },
            pushStack: function( elems, name, selector ) {
                // Build a new jQuery matched element set
                var ret = jQuery.merge( this.constructor(), elems );
    
                ret.context = this.context;
    
                if ( name === "find" ) {
                    ret.selector = this.selector + ( this.selector ? " " : "") + selector;
                } else if ( name ) {
                    ret.selector = this.selector + "." + name + "(" + selector + ")";
                }
    
                return ret;
            }
    
        }// end of jQuery.prototype
    
    
        // jQuery公共成员区结束
        /////////////////////////////////////////////////////
    
        jQuery.fn.init.prototype = jQuery.fn;
    
        //Expose jQuery to the global
        window.jQuery = window.$ = jQuery;
    
    } )(window);
    

    5.each函数分析

    (1)apply和call区别

    // obj:这个对象将替代Function类里的this对象
    // args:必须是数组变量,只能接受数组格式的参数
    Function.apply( obj, args )
    

    call对参数没有要求

    (2)each函数参数分析

    obj:待遍历的对象列表
    callback:回调函数,由obj列表中的对象进行调用
    args:可选的回调函数参数

    (3)each函数功能分析

    each函数用于遍历对象列表中对象,每一次遍历都执行一个过程(也称为函数),这个过程作用于当前遍历到的对象,也就是说这个过程中的this为当前对象。

    jQuery.extend({
        each: function ( obj, callback, args ) {
            var name,
                i      = 0,
                length = obj.length,
                isObj  = length === undefined;
    
            if ( args ) {
                if ( isObj ) {
                    for ( key in obj ) {
                        if ( callback.apply( obj[key], args) === false ) {
                            break;
                        }
                    }
                } else {
                    for ( ; i < length; i++ ) {
                        if ( callback.apply( obj[key], args ) === false ) {
                            break;
                        }
                    }
                }
            } else {
                if ( isObj ) {
                    for ( key in obj ) {
                        if ( callback.call( obj[key], key, obj[key] ) === false ) {
                            break;
                        }
                    }
                } else {
                    for ( ; i < length; i++ ) {
                        if ( callback.call( obj[i], i, obj[i] ) === false ) {
                            break;
                        }
                    }
                }
            }
        }
    });
    
    jQuery.fn.extend({
        each: function ( callback, args ) {
            return jQuery.each( this, callback, args );
        }
    });
    

    6.parseXML函数分析

    (1)功能分析

    parseXML函数主要用于对XML文件进行解析,因为IE和其他浏览器对XML文件提供了不同方法进行解析,jQuery对XML文件进行了封装,兼容各个浏览器差异性,对用户提供统一的操作接口。

    (2)代码分析

    jQuery.extend( {
        error: function ( msg ) {
            throw new Error( msg );//报错
        },
    
        parseXML: function ( data ) {
            var domParser, xml;
            // 首先对参数进行校验,必须为字符串类型
            if ( !data || typeof data !== "string" ) {
                return null;
            }
    
            try {
                //检测浏览器window对象是否提供DOM节点解析对象,IE是不提供的,针对不同浏览器提供的方法对XML字符串进行解析,生成XML对象
                if ( window.DOMParser ) {
                    domParser = new DOMParser();
                    xml       = domParser.parseFromString( data, "text/xml" );
                } else {
                    xml = new ActiveXObject( "Microsoft.XMLDOM" );
                    xml.async = "false";
                    xml.loadXML( data );
                }
            } catch (e) {
                xml = undefined;
            }
            //检测xml合法性,报告错误
            if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
                jQuery.error( "Invalid XML: " + data );
            }
            return xml;
        }
    } );
    

    8.find函数分析

    jQuery.find = Sizzle;
    function Sizzle( selector, context, results,seed ) {
        //参数过滤
        results = results || [];
        context = context || document;
        var 
            nodeType = context.nodeType;
        if ( !selector || typeof selector !== "string" ) {
            return results;
        }
        if ( nodeType != 1 && nodeType != 9 ) {
            return [];
        }
    
    }
    

    9.data函数

    jQuery.fn.extend({
        data: function ( key, value ) {
            var
                elem = this[0];
            //获取全部值
            if ( key === undefined ) {
                if ( this.length ) {
                    data = jQuery.data( elem );
    
                    if ( elem.nodeType === 1 && !jQuery._data(elem, "parsedAttrs") ) {
                        attr = elem.attributes;
                        for ( ; i < attr.length; i++ ) {
                            name = attr[i].name;
    
                            if ( !name.indexOf( "data-" ) ) {
                                name = jQuery.camelCase( name.substring(5) );
    
                                dataAttr( elem, name, data[name] );
                            }
                        }
                        jQuery._data( elem, "parsedAttrs", true );
                    }
                }
                return data;
            }
    
            if ( typeof key === "object" ) {
                return this.each(function(){
                    jQuery.data( this, key );
                });
            }
    
            parts = key.split( ".", 2 );
            parts[1] = parts[1] ? "." + parts[1] : "";
            part = parts[1] + "!";
    
            return jQuery.access();
        }
    });
    
    jQuery.extend({
        data: function( elem, name, value ) {
    
        }
    });
    

    10.词法分析器

    词法分析匹配正则对象集合

    whitespace = "[\\x20\\t\\r\\n\\f]";
    characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+";
    identifier = characterEncoding.replace( "w", "w#" );
    
    attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
            "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]";
    
    
    matchExpr = {
            "ID": new RegExp( "^#(" + characterEncoding + ")" ),
            "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
            "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
            "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
            "ATTR": new RegExp( "^" + attributes ),
            "PSEUDO": new RegExp( "^" + pseudos ),
            "POS": new RegExp( pos, "i" ),
            "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
                "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
                "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
            // For use in libraries implementing .is()
            "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
        }
    
    Token格式
    {
        value:'匹配到的字符串',
        type:'对应的Token类型',
        matches:'正则匹配到的一个结构'
    }
    
    function tokenize( selector, parseOnly ) {
        // soFar:目前为止还未分析的字符串
        // groups:目前已经匹配到的规则组
        var matched, match, tokens, type,
            soFar, groups, preFilters,
            cached = tokenCache[ expando ][ selector + " " ];
    
        if ( cached ) {
            return parseOnly ? 0 : cached.slice( 0 );
        }
    
        
        soFar = selector;
        groups = [];
        preFilters = Expr.preFilter;
    
        while ( soFar ) {
    
            // Comma and first run
            if ( !matched || (match = rcomma.exec( soFar )) ) {
                if ( match ) {
                    // Don't consume trailing commas as valid
                    soFar = soFar.slice( match[0].length ) || soFar;
                }
                groups.push( tokens = [] );
            }
    
            matched = false;
    
            // Combinators
            // 关系选择符
            if ( (match = rcombinators.exec( soFar )) ) {
                tokens.push( matched = new Token( match.shift() ) );
                soFar = soFar.slice( matched.length );
    
                // Cast descendant combinators to space
                matched.type = match[0].replace( rtrim, " " );
            }
    
            // Filters
            // 正则过滤
            for ( type in Expr.filter ) {
                if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
                    (match = preFilters[ type ]( match ))) ) {
    
                    tokens.push( matched = new Token( match.shift() ) );
                    soFar = soFar.slice( matched.length );
                    matched.type = type;
                    matched.matches = match;
                }
            }
    
            if ( !matched ) {
                break;
            }
        }
    
        // Return the length of the invalid excess
        // if we're just parsing
        // Otherwise, throw an error or return tokens
        return parseOnly ?
            soFar.length :
            soFar ?
                Sizzle.error( selector ) :
                // Cache the tokens
                tokenCache( selector, groups ).slice( 0 );
    }
    

    11.select

    function select( selector, context, results, seed, xml ) {
        var i, tokens, token, type, find,
            // 解析词法格式
            match = tokenize( selector ),
            j = match.length;
    
        if ( !seed ) {
            // Try to minimize operations if there is only one group
            // 没有多分组,只是个单选则器,也就是selector中没有逗号,可以进行一些优化操作
            if ( match.length === 1 ) {
    
                // Take a shortcut and set the context if the root selector is an ID
                // 取出Token序列
                tokens = match[0] = match[0].slice( 0 );
                // 如果选择器selector第一部分为ID选择器
                if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
                        context.nodeType === 9 && !xml &&
                        Expr.relative[ tokens[1].type ] ) {
    
                    context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
                    // 如果查询结果为空(selector第一个ID选择器查询为空),就不用再查找了
                    if ( !context ) {
                        return results;
                    }
    
                    // 去掉第一个ID选择器
                    selector = selector.slice( tokens.shift().length );
                }
    
                // Fetch a seed set for right-to-left matching
                // 没有结构性伪类的话,从右至左进行扫描tokens中选择器序列(从数组后往前)匹配
                for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
                    // 获取一个规则
                    token = tokens[i];
    
                    // Abort if we hit a combinator
                    // 遇到关系选择符( > + ~  空 )终止循环
                    if ( Expr.relative[ (type = token.type) ] ) {
                        break;
                    }
                    // 如果搜索器中支持此种类型查找
                    if ( (find = Expr.find[ type ]) ) {
                        // Search, expanding context for leading sibling combinators
                        if ( (seed = find(
                            token.matches[0].replace( rbackslash, "" ),
                            rsibling.test( tokens[0].type ) && context.parentNode || context,
                            xml
                        )) ) {
    
                            // If seed is empty or no tokens remain, we can return early
                            tokens.splice( i, 1 );
                            selector = seed.length && tokens.join("");
                            if ( !selector ) {
                                push.apply( results, slice.call( seed, 0 ) );
                                return results;
                            }
    
                            break;
                        }//end if ( (seed = find
                    }// end if ( (find = Expr.find[ type ]) ) 
                }//end for ( i = matchExpr["POS"].test( selector )
            }
        }
    
        // Compile and execute a filtering function
        // Provide `match` to avoid retokenization if we modified the selector above
        compile( selector, match )(
            seed,
            context,
            xml,
            results,
            rsibling.test( selector )
        );
        return results;
    }
    

    关于jQuery的源码暂时分析到这里,后面还会继续分析。

    相关文章

      网友评论

        本文标题:jQuery源码分析:深入理解js封装技术

        本文链接:https://www.haomeiwen.com/subject/wywwxftx.html