3.10 工具方法
3.10.1 Sizzle.uniqueSort( results )
工具方法Sizzle.uniqueSort( results )负责对元素集合中的元素按照出现在文档中的顺序进行排序,并删除重复元素。
相关代码如下所示:
4026 Sizzle.uniqueSort = function( results ) {
4027 if ( sortOrder ) {
4028 hasDuplicate = baseHasDuplicate;
4029 results.sort( sortOrder );
4030
4031 if ( hasDuplicate ) {
4032 for ( var i = 1; i < results.length; i++ ) {
4033 if ( results[i] === results[ i - 1 ] ) {
4034 results.splice( i--, 1 );
4035 }
4036 }
4037 }
4038 }
4039
4040 return results;
4041 };
第4029行:调用数组方法sort()对数组中的元素进行排序。其中,sortOrder( a, b )是比较函数,负责比较元素a和元素b在文档中的位置。如果比较函数sortOrder( a, b )遇到相等的元素,即重复元素,会设置变量hasDuplicate为true。关于比较函数sortOrder( a, b )的具体说明请参见3.10.2节。
第4031~4037行:如果变量hasDuplicate为true,表示存在重复元素,则遍历数组results,比较相邻元素是否相等,如果相等则删除。
第4028行:开始排序和去重时,先设置变量hasDuplicate的默认值为变量baseHas
Duplicate,变量baseHasDuplicate指示了JavaScript引擎在排序时是否会进行优化。
相关代码如下所示:
3864 hasDuplicate = false,
3865 baseHasDuplicate = true,
3870 // Here we check if the JavaScript engine is using some sort of
3871 // optimization where it does not always call our comparision
3872 // function. If that is the case, discard the hasDuplicate value.
3873 // Thus far that includes Google Chrome.
3874 [0, 0].sort(function() {
3875 baseHasDuplicate = false;
3876 return 0;
3877 });
第3874~3877行:检查JavaScript引擎在排序时是否会进行优化。在早期的Chrome浏览器中,排序时如果遇到相等的元素,不会调用比较函数,新版本中已经取消了这一优化。如果遇到相等元素便不调用比较函数,此时变量baseHasDuplicate默认为true,即只能假设数组中含有重复元素;如果遇到相等元素时仍然会调用比较函数,则变量baseHasDuplicate将被设置为false,这种情况下需要在比较函数中判断是否含有重复元素。
读者可以访问http://bugs.jquery.com/ticket/5380查看该bug的描述。
3.10.2 sortOrder( a, b )
函数sortOrder( a, b )负责比较元素a和元素b在文档中的位置。如果元素a在元素b之前,则返回-1;如果元素a在元素b之后,则返回1;如果元素a与元素b相等,则返回0。
函数sortOrder( a, b )通过调用原生方法compareDocumentPosition()或比较原生属性source
Index来实现。原生方法compareDocumentPosition()用于比较两个元素的文档位置;原生属性sourceIndex则返回元素在文档中的序号,返回值等于该元素在document.getElementsBy
TagName('*')返回的数组中的下标。更多信息请访问http://www.quirksmode.org/dom/w3c_core.html。
函数sortOrder( a, b )执行的3个关键步骤如下:
1)如果浏览器支持原生方法compareDocumentPosition(),则调用该方法比较元素位置。
2)如果浏览器支持原生属性sourceIndex,则用该属性比较元素位置。
3)否则比较祖先元素的文档位置。
下面来看看该函数的源码实现。
1.?浏览器支持原生方法compareDocumentPosition()的情况
如果浏览器支持原生方法compareDocumentPosition(),则调用该方法比较元素位置。相关代码如下所示:
4805 var sortOrder, siblingCheck;
4806
4807 if ( document.documentElement.compareDocumentPosition ) {
4808 sortOrder = function( a, b ) {
4809 if ( a === b ) {
4810 hasDuplicate = true;
4811 return 0;
4812 }
4813
4814 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
4815 return a.compareDocumentPosition ? -1 : 1;
4816 }
4817
4818 return a.compareDocumentPosition(b) & 4 ? -1 : 1;
4819 };
4820
2.?浏览器支持原生属性sourceIndex的情况
如果浏览器支持原生属性sourceIndex,则用该属性比较元素位置。
相关代码如下所示:
4821 } else {
4822 sortOrder = function( a, b ) {
4823 // The nodes are identical, we can exit early
4824 if ( a === b ) {
4825 hasDuplicate = true;
4826 return 0;
4827
4828 // Fallback to using sourceIndex (in IE) if it's available on both nodes
4829 } else if ( a.sourceIndex && b.sourceIndex ) {
4830 return a.sourceIndex - b.sourceIndex;
4831 }
4832
3.否则比较祖先元素的文档位置
(1)元素a和元素b是兄弟元素的情况
相关代码如下所示:
4833 var al, bl,
4834 ap = [],
4835 bp = [],
4836 aup = a.parentNode,
4837 bup = b.parentNode,
4838 cur = aup;
4839
4840 // If the nodes are siblings (or identical) we can do a quick check
4841 if ( aup === bup ) {
4842 return siblingCheck( a, b );
4843
第4836~4837行、第4841~4842行:变量aup是元素a的父元素,变量bup是元素b的父元素。如果变量aup与变量bup相等,说明元素a和元素b是兄弟元素,则调用函数siblingCheck()比较元素a和元素b的文档位置。函数siblingCheck( a, b, ret )负责比较兄弟元素的文档位置,稍后会看到该函数的源码实现和分析。
(2)没有找到父元素的情况
相关代码如下所示:
4844 // If no parents were found then the nodes are disconnected
4845 } else if ( !aup ) {
4846 return -1;
4847
4848 } else if ( !bup ) {
4849 return 1;
4850 }
4851
第4845~4850行:如果元素a没有父元素,则认为元素a不在文档中,返回-1,元素a将排在元素b之前;如果元素b没有父元素,则认为元素b不在文档中,返回1,元素b将排在元素a之前。
(3)查找元素a和元素b的祖先元素
相关代码如下所示:
4852 // Otherwise they're somewhere else in the tree so we need
4853 // to build up a full list of the parentNodes for comparison
4854 while ( cur ) {
4855 ap.unshift( cur );
4856 cur = cur.parentNode;
4857 }
4858
4859 cur = bup;
4860
4861 while ( cur ) {
4862 bp.unshift( cur );
4863 cur = cur.parentNode;
4864 }
4865
(4)比较祖先元素的文档位置
相关代码如下所示:
4866 al = ap.length;
4867 bl = bp.length;
4868
4869 // Start walking down the tree looking for a discrepancy
4870 for ( var i = 0; i < al && i < bl; i++ ) {
4871 if ( ap[i] !== bp[i] ) {
4872 return siblingCheck( ap[i], bp[i] );
4873 }
4874 }
4875
第4866~4874行:从最顶层的祖先元素开始向下遍历,如果祖先元素ap[i]与bp[i]不是同一个元素,那么它们必然是兄弟元素,此时可以通过比较两个祖先元素的文档位置,来确定元素a和元素b的相对位置。
(5)元素a和元素b的文档深度不一致的情况
相关代码如下所示:
4876 // We ended someplace up the tree so do a sibling check
4877 return i === al ?
4878 siblingCheck( a, bp[i], -1 ) :
4879 siblingCheck( ap[i], b, 1 );
4880 };
4881
第4877~4878行:如果元素a的文档深度较小,此时元素a与元素b的祖先元素bp[i]要么是兄弟元素,要么是同一个元素,可以调用函数siblingCheck( a, b, ret )比较文档位置。
第4877~4879行:如果元素b的文档深度较小,此时元素a的祖先元素ap[i]与元素b要么是兄弟元素,要么是同一个元素,可以调用函数siblingCheck( a, b, ret )比较文档位置。
(6)siblingCheck( a, b, ret )
函数siblingCheck( a, b, ret )负责比较兄弟元素的文档位置。该函数从元素a向后遍历(nextSibling),如果遇到元素b,说明元素a在元素b之前,则返回-1;如果一直没遇到,说明元素a在元素b之后,则返回1。
相关代码如下所示:
4882 siblingCheck = function( a, b, ret ) {
4883 if ( a === b ) {
4884 return ret;
4885 }
4886
4887 var cur = a.nextSibling;
4888
4889 while ( cur ) {
4890 if ( cur === b ) {
4891 return -1;
4892 }
4893
4894 cur = cur.nextSibling;
4895 }
4896
4897 return 1;
4898 };
4899 }
3.10.3 Sizzle.contains( a, b )
工具方法Sizzle.contains( a, b )负责检测元素a是否包含元素b。该方法通过调用原生方法contains()或compareDocumentPosition()实现。原生方法contains()用于检测一个元素是否包含另一个元素;原生方法compareDocumentPosition()用于比较两个元素的文档位置,更多信息请访问以下网址:
http://ejohn.org/blog/comparing-document-position/
http://www.quirksmode.org/dom/w3c_core.html#miscellaneous
相关代码如下所示:
5242 if ( document.documentElement.contains ) {
5243 Sizzle.contains = function( a, b ) {
5244 return a !== b && (a.contains ? a.contains(b) : true);
5245 };
5246
5247 } else if ( document.documentElement.compareDocumentPosition ) {
5248 Sizzle.contains = function( a, b ) {
5249 return !!(a.compareDocumentPosition(b) & 16);
5250 };
5251
5252 } else {
5253 Sizzle.contains = function() {
5254 return false;
5255 };
5256 }
3.10.4 Sizzle.error( msg )
工具方法Sizzle.error( msg )用于抛出一个含有选择器表达式语法错误信息的异常。
相关代码如下所示:
4178 Sizzle.error = function( msg ) {
4179 throw new Error( "Syntax error, unrecognized expression: " + msg );
4180 };
3.10.5 Sizzle.getText( elem )
工具方法Sizzle.getText( elem )用于获取元素集合中所有元素合并后的文本内容。
相关代码如下所示:
4182 /**
4183 * Utility function for retreiving the text value of an array of DOM nodes
4184 * @param {Array|Element} elem
4185 */
4186 var getText = Sizzle.getText = function( elem ) {
4187 var i, node,
4188 nodeType = elem.nodeType,
4189 ret = "";
4190
4191 if ( nodeType ) {
4192 if ( nodeType === 1 || nodeType === 9 ) {
4193 // Use textContent || innerText for elements
4194 if ( typeof elem.textContent === 'string' ) {
4195 return elem.textContent;
4196 } else if ( typeof elem.innerText === 'string' ) {
4197 // Replace IE's carriage returns
4198 return elem.innerText.replace( rReturn, '' );
4199 } else {
4200 // Traverse it's children
4201 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
4202 ret += getText( elem );
4203 }
4204 }
4205 } else if ( nodeType === 3 || nodeType === 4 ) {
4206 return elem.nodeValue;
4207 }
4208 } else {
4209
4210 // If no nodeType, this is expected to be an array
4211 for ( i = 0; (node = elem[i]); i++ ) {
4212 // Do not traverse comment nodes
4213 if ( node.nodeType !== 8 ) {
4214 ret += getText( node );
4215 }
4216 }
4217 }
4218 return ret;
4219 };
第4191~4207行:如果参数elem是元素,则尝试读取属性textContent或innerText,如果不支持则遍历子元素,递归调用工具函数getText( elem )来获取每个子元素的文本内容,并合并;如果参数elem是Text节点或CDATASection节点,则直接返回节点值nodeValue。
第4208~4217行:否则认为参数elem是元素集合,遍历该元素集合,递归调用函数getText(elem)获取每个元素的文本内容,并合并。
第4218行:最后返回合并后的文本内容。