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

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种:

(1)构造继承

(2)原型继承

(3)实例继承

(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新创建了一个自定义对象,因此在匿名对象内部需要增加这部分代码:

constructor: jQuery

确保原型对象指回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进行扩展)。
因此,我们可以将其进行合并,即:

Query.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区别
Function.apply( obj, args )
obj:这个对象将替代Function类里的this对象
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; }

分享