selector是jQuery主打的一个模块,可以说jQuery的强大就是其selector。目前其的selector已经分成另外的一个(sizzle)[]项目了。
在jQuery中采用调用sizzle来实现selector,接下来详细分析。
模块结构
jQuery的selector模块依赖如下:
selector -> selector-sizzle -> sizzle
主要实现是sizzle。selector-sizzle只是定义了一些jQuery常用的sizzle的变量。selector直接就是一个申明依赖。
Sizzle简介
Sizzle也是采用类似jQuery结构的写法,但是其并未采用模块化编写。
在官方文档中申明支持如下浏览器:
- IE6+
- FF3.0+
- Chrome 5+
- Safari 3+
- Opera 9+
可以说主流的浏览器都主持。
sizzle听说是业界中世界最快类库。其很多方法采用原生支持的,然后按照结构优化进行解析。
sizzle常规解析是由右向左解析,例如:ul li a
中sizzle是选择a,然后再a的候选集中剔除不满足li、ul的。这样做的好处是只遍历了一次文档,然后都是指针操作。
工厂方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| * Sizzle工厂方法 * @param {[type]} selector css 选择字符串 * @param {[type]} context 上下文 默认是当前文档 * @param {[type]} results 结果对象 匹配结果会添加在其中 * @param {[type]} seed 候选结果集 */ function Sizzle( selector, context, results, seed ) { var match, elem, m, nodeType, i, groups, old, nid, newContext, newSelector; if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; results = results || []; if ( !selector || typeof selector !== "string" ) { return results; } if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { return []; } if ( documentIsHTML && !seed ) { if ( (match = rquickExpr.exec( selector )) ) { if ( (m = match[1]) ) { if ( nodeType === 9 ) { elem = context.getElementById( m ); if ( elem && elem.parentNode ) { if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } } else { if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } console.log('----2222---'); console.log(support.qsa); console.log(rbuggyQSA.test( selector )); if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { nid = old = expando; newContext = context; newSelector = nodeType === 9 && selector; if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { groups = tokenize( selector ); if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); } nid = "[id='" + nid + "'] "; i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; newSelector = groups.join(","); } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch(qsaError) { } finally { if ( !old ) { context.removeAttribute("id"); } } } } } return select( selector.replace( rtrim, "$1" ), context, results, seed ); }
|
根据工厂方法来分析,sizzle首先会采用原生方法判断,如果支持原生方法会直接采用原生方法实现。只有当浏览器不支持原生方法时候才采用自定义的方法(select)实现。
其中关键的是检测相关方法是否原生支持,主要有support.getElementsByClassName、support.qsa。还有就是bug列表rbuggyQSA和自定义选择方法select了。
采用querySelectorAll是否会有一个问题,比如当指定context的时候查找应该是不包含本身的,可以querySelectorAll确是包含的,以至于产生了一个问题。Sizzle采用querySelectorAll时候,如果上下文是html的tag时候,自己会构造一个id然后作为上下文然后转化为上下文为document进行查找,最后删除这个id。这样有效的排除的这个问题。
还有Sizzle是如何保证速度的呢?不难看出一个条件(id、class、tag)的时候采用原生方法getElementById、getElementsByClassName、getElementById。多条件的时候基于querySelectorAll。这些方法中有的在一些浏览器中依然有bug,Sizzle采用检测的方式来处理bug。根据bug产生情况,又可能有不同的调用。总的来说就是尽量采用原生方法,实在不行就才采用自定义的实现。
检测
Sizzle的检测都是基于一个主函数,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function assert( fn ) { var div = document.createElement("div"); try { return !!fn( div ); } catch (e) { return false; } finally { if ( div.parentNode ) { div.parentNode.removeChild( div ); } div = null; } }
|
看出检测节点都是document下的一个节点,在检测完成后会清除。
接下来看一下getElementsByClassName的检测方法:
1 2 3 4 5 6 7 8 9 10 11 12
| support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { div.innerHTML = "{% raw %}<div class='a'></div><div class='a i'></div>{% endraw %}"; div.firstChild.className = "i"; return div.getElementsByClassName("i").length === 2; });
|
直接使用了rnative变量(正则)来验证,然后进行实际调用验证。
qsa方法也是一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| rbuggyQSA = []; if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { assert(function( div ) { div.innerHTML = "<select><option selected=''></option></select>"; if ( !div.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } }); assert(function( div ) { var input = doc.createElement("input"); input.setAttribute( "type", "hidden" ); div.appendChild( input ).setAttribute( "t", "" ); if ( div.querySelectorAll("[t^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } if ( !div.querySelectorAll(":enabled").length ) { rbuggyQSA.push( ":enabled", ":disabled" ); } div.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } ... rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
|
从中看出针对很多bug进行了检测,最后形成了一个正则,现在再看工厂方法中的rbuggyQSA.test(selector)
是不是感觉豁然开朗。
篇幅太长了,下一篇再接着分析吧。
参考文档