这篇是关于Sizzle.filter部分的详细分解,前提是先看懂Sizzle.filter的分发过程。
回顾一下Sizzle.filter的过程:
1 2 3 4
| match = preFilter() if(match){ filter() }
|
当preFilter返回为false的时候,filter就被短路了。
过滤CLASS
怎么判断一个元素时候含有某个类,源码中的方法:
1 2 3
| function checkClass(elem,className,not){ return not ^ (' ' + elem.className + ' ').indexOf(' '+ className +' ') > -1; }
|
注意那个not参数,前面有提到过的,取补集而已。
checkClass(elem,className) --->hasClass(elem,className)
checkClass(elem,className,true) --->hasNoClass(elem,className)
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div id="test" class="a b c d"></div> <script type="text/javascript"> function checkClass(elem,className,not){ return not ^ (' ' + elem.className + ' ').indexOf(' '+ className +' ') > -1; } var test = document.getElementById('test'); console.log(checkClass(test,'a')); console.log(checkClass(test,'b')); console.log(checkClass(test,'e'));
var test = document.getElementById('test'); console.log(checkClass(test,'a',true)); console.log(checkClass(test,'b',true)); console.log(checkClass(test,'e',true)); </script>
|
对照
1
| Expr.match.CLASS = /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/;
|
获得的一个类似[‘.className’,’className’]的数组,取做match,经过层层处理之后,传递给preFilter.CLASS的形式主要是[‘.className’,’className’,?,?]的形式,因此在CLASS处理部分,只需要match的前两个参数就可以了。
因此在CLASS的preFilter部分,其大致处理形式就是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| preFilter.CLASS = function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; };
|
在上面的步骤中,需要注意的是curLoop已经result是两个数组,属于引用类型,任何改变都会反映到外部的集合中。已经得到了过滤的结果,因此返回false,无需再执行其他步骤。
至于filter.CLASS就是单纯的判断是否含有某个类而已:
1 2 3
| filter.CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ").indexOf( match ) > -1; },
|
上面的代码已经很清晰了。
过滤ID
1
| ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/
|
match的形式如[‘#id’,’id’]
这个步骤就很简单了,
preFilter处理match[1]的格式问题
1 2 3
| preFilter.ID: function( match ) { return match[1].replace( rBackslash, "" ); },
|
filter判断元素的id是不是给定的id
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
| filter.ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, ```
一般来说,要判断id的情况并不多,因为id一般都在Sizzle.find被处理掉了,再来过滤id严重拖慢速度,expr表达式写得估计有问题。
### 过滤TAG
同过滤ID
### 过滤ATTR
过滤attr的过程在filter部分的时候作为实例带过,此处忽略
### 过滤Child
Child有点复杂了,可以从选择符的形式着手。
举个栗子:
```html <div> <p class="tab" id="a1">1</p> <p class="tab" id="a2">2</p> <p class="tab" id="a3">3</p> </div> <div> <p class="tab" id="a4">4</p> </div> <script type="text/javascript"> console.log(Sizzle('p:only-child')); console.log(Sizzle('p:last-child')); console.log(Sizzle('p:first-child')); console.log(Sizzle('p:nth-child(1)')); console.log(Sizzle('p:nth-child(odd)')); console.log(Sizzle('p:nth-child(even)')); console.log(Sizzle('p:nth-child(n)')); console.log(Sizzle('p:nth-child(2n+1)')); </script>
|
在上面的几个形式中,可以分为两组:
only-child,last-child,first-child分为一组A
nth-child单独为一组B。
虽然由 nth-child 可以完全得到组A,但是组A是特殊形式的简便方式。
另外,这两种方式的代码形式的主要是组A不能再带位置参数,组B必须后接一个位置参数。
比如‘last-child(2)’是不行的,‘nth-child’是不行的,这个看名字就可以理解。
先看
1
| CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
|
匹配(match)的分组就是match = [‘nth-child’,’nth’,’2n+1’]或者match = [‘first-child’,’first’,undefined]的形式。
其中nth那种的位置表达式可能比较多,因此preFilter中处理的问题就是把nth形式中的各种形式转换成统一的形式。
转换原理
even 转换成 2n,odd 转换成 2n+1,5 转换成 0n+5 ,可得最后要得到的形式就是 an+b 的形式。
1 2 3 4 5
| match[2] === "even" && "2n"; match[2] === "odd" && "2n+1;" !/\D/.test( match[2] ) && "0n+" + match[2]; match[2];
|
现在形式统一了,只需要提取出an+b中相应的a和b就可以了,提取方式当然用正则,注意a、b都可能为负数。
1
| var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec('an+b');
|
获得的test分组类似[‘an+b’,’-‘,’a’,’+b’],对于实例‘-2n-1’就是[‘-2n-1’,’-‘,’2’,’-1’]
现在每个数组都是字符串,需要时数字才便于处理,因此我们先将数字字符串转换为纯数字,字符串转换为数字也挺简单:
因此这个基本步骤就是:
1 2 3 4
| var posStr = match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]; var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(posStr); var temp1 = (test[1] + (test[2] || 1)) - 0; var temp2 = test[3] - 0;
|
看具体源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| CHILD: function( match ) { if ( match[1] === "nth" ) { if ( !match[2] ) { Sizzle.error( match[0] ); } match[2] = match[2].replace(/^\+|\s*/g, ''); var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } else if ( match[2] ) { Sizzle.error( match[0] ); }
match[0] = done++; return match; },
|
过滤POS
过滤PSEUDO