jquery 源码分析七

     这篇主要是分析Sizzle引擎中最关键的一个函数Sizzle,这个函数接受四个参数selector,context,results,seed,这四个参数的意义分别是选择器,上下文环境,保存结果数组,还有一个未知。。

     由于这个函数里面涉及了许多相关函数,所以先对主函数进行分析,并只是列出相关函数的作用。相关函数的分析会放在文章末尾。接下来先看主函数:

  1 function Sizzle( selector, context, results, seed ) {
  2     var match, elem, m, nodeType,
  3         i, groups, old, nid, newContext, newSelector;
  4         //各种常量定义
  5     //设置合理的root节点,在网页中为document
  6     if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
  7         setDocument( context );
  8     }
  9     //确定上下文环境和结果数组
 10     context = context || document;
 11     results = results || [];
 12     //如果无selector或者selector不为字符串,则直接返回results
 13     if ( !selector || typeof selector !== "string" ) {
 14         return results;
 15     }
 16     //如果上下文环境的节点不是element或者document的话,就直接返回 空数组
 17     if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
 18         return [];
 19     }
 20 
 21     //documentIsHTML 用于确定工作环境是否是html
 22     if ( documentIsHTML && !seed ) {
 23 
 24         // rquickExpr = /^(?:#([w-]+)|(w+)|.([w-]+))$/
 25         // 用于快速的检测是否是单一的ID,CLASS或TAG选择符
 26         if ( (match = rquickExpr.exec( selector )) ) {
 27             // ID选择
 28             if ( (m = match[1]) ) {
 29                 if ( nodeType === 9 ) {
 30                     elem = context.getElementById( m );
 31                     // 检测是否有parentNode存在,有些浏览器会返回已经被删除了的节点
 32                     if ( elem && elem.parentNode ) {
 33                         // 处理在IE, Opera, 和Webkit 会返回name等于 m 的元素
 34                         if ( elem.id === m ) {
 35                             results.push( elem );
 36                             return results;
 37                         }
 38                     } else {
 39                         return results;
 40                     }
 41                 } else {
 42                     // 如果context不是document的话,要检测context对elem的包含情况。
 43                     if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
 44                         contains( context, elem ) && elem.id === m ) {
 45                         results.push( elem );
 46                         return results;
 47                     }
 48                 }
 49 
 50             // TAG选择
 51             } else if ( match[2] ) {
 52                 push.apply( results, context.getElementsByTagName( selector ) );
 53                 return results;
 54 
 55             // CLASS选择
 56             } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
 57                 push.apply( results, context.getElementsByClassName( m ) );
 58                 return results;
 59             }
 60         }
 61 
 62         // querySelectorAll,处理复杂的选择
 63         // rbuggyQSA用于检测querySelectAll的怪异特性
 64         if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
 65             //expando是sizzle中唯一特征码
 66             nid = old = expando;
 67             newContext = context;
 68             newSelector = nodeType === 9 && selector;
 69 
 70             
 71             // querySelectorAll在element为根元素时行为很奇怪
 72             // 我们可以给根元素一个特定的ID值
 73             // 这样在selector最前面加上ID,就可以做到很好的筛选
 74             // IE8中不支持在对象元素上用
 75             if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
 76                 //tokenize的作用是将selector中的筛选符分割开来并放到相应数组中
 77                 //例:tokenize('div#a')
 78                 // --->[[
 79                 //      {matches:{0:div,index:0,input:'div#a'},
 80                 //       type:'TAG',
 81                 //       value:'div'},
 82                 //       {matches:{0:a,index:0,input:'div#a'},
 83                 //       type:'ID',
 84                 //       value:'#a'}
 85                 //      ]]
 86                 groups = tokenize( selector );
 87 
 88                 //如果context存在id,那么就是用原来的id,否则就用expando
 89                 if ( (old = context.getAttribute("id")) ) {
 90                     nid = old.replace( rescape, "\$&" );
 91                 } else {
 92                     context.setAttribute( "id", nid );
 93                 }
 94                 nid = "[id='" + nid + "'] ";
 95 
 96                 //将所有的选择符拼接
 97                 i = groups.length;
 98                 while ( i-- ) {
 99                     groups[i] = nid + toSelector( groups[i] );
100                 }
101                 //探测是否有a+div或者[class~='abc']类似的情况,rsibling = /[+~]/
102                 newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
103                 newSelector = groups.join(",");
104             }
105             //newSelector存在的话
106             if ( newSelector ) {
107                 try {
108                     //直接在context下用querySelectorAll,并将结果压入数组
109                     push.apply( results,
110                         newContext.querySelectorAll( newSelector )
111                     );
112                     return results;
113                 } catch(qsaError) {
114                 } finally {
115                     //最后删除添加的id
116                     if ( !old ) {
117                         context.removeAttribute("id");
118                     }
119                 }
120             }
121         }
122     }
123 
124     // 其它情况都用select,这个函数以后分析
125     return select( selector.replace( rtrim, "$1" ), context, results, seed );
126 }

接下来就是对里面那些相关函数的分析,这里面的涉及到的函数或变量主要有:setDocument,documentIsHTML,contains,toSelector,testContext,rbuggyQSA,tokenize

rbuggyQSA,tokenize,setDocument因为太绕(我连开5,6个jquery一起读了,然后再用chrome来追踪才读出来的,泪奔。。。),所以这次先不讲了。

首先是documentIsHTML,废话不多说,直接上代码:

1 documentIsHTML = !isXML( doc );
1 isXML = Sizzle.isXML = function( elem ) {
2     // 首先是确定根元素是否有documentElement
3     // 然后是确定documentElement节点名字是不是HTML
4     var documentElement = elem && (elem.ownerDocument || elem).documentElement;
5     return documentElement ? documentElement.nodeName !== "HTML" : false;
6 };

然后是contains,contains是在setDocument中定义的,因为contains是Sizzle中的全局变量,所以就可以到处用了。

 1     // rnative = /^[^{]+{s*[native w/用于检测是否有原生函数
 2     // docElem = 根元素.documentElement
 3     // hasCompare = rnative.test( docElem.compareDocumentPosition );
 4     contains = hasCompare || rnative.test( docElem.contains ) ?
 5         //首先是有compareDocumentPosition或者contains的情况
 6         function( a, b ) {
 7             var adown = a.nodeType === 9 ? a.documentElement : a,
 8                 bup = b && b.parentNode;
 9             //先判断是否a直接包含b.parentNode
10             //否则,在确定bup后,先尝试用contains
11             //再用compareDocumentPosition
12             //compareDocumentPosition返回16表示a包含bup
13             //故要用&16将其转化为0或非0数字
14             return a === bup || !!( bup && bup.nodeType === 1 && (
15                 adown.contains ?
16                     adown.contains( bup ) :
17                     a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
18             ));
19         } :
20         //b循环向上取parent,若与a相同,则返回true
21         function( a, b ) {
22             if ( b ) {
23                 while ( (b = b.parentNode) ) {
24                     if ( b === a ) {
25                         return true;
26                     }
27                 }
28             }
29             //循环不到,就返回false
30             return false;
31         };

然后是toSelector:

 1 //循环,将每个对象下的value值合并到selector
 2 function toSelector( tokens ) {
 3     var i = 0,
 4         len = tokens.length,
 5         selector = "";
 6     for ( ; i < len; i++ ) {
 7         selector += tokens[i].value;
 8     }
 9     return selector;
10 }

对于selector有空格的情况,toknize函数解析的时候会将空格也独立解析成一项,所以在这边合并时空格仍旧会出现,不会被丢弃。

最后是testContext,检测context是否为空,并且context下是否有getElementsByTagName方法,代码如下:

1 function testContext( context ) {
2     return context && typeof context.getElementsByTagName !== strundefined && context;
3 }

呼,搞定~

原文地址:https://www.cnblogs.com/cyITtech/p/3598410.html