教你实现 jQuery 的选择器

做网站免不了用到JS,如果大量用到选择一个JS框架是个不错的选择,今天我就抛砖引玉说说jQuery的选择器,了解它的原理是提高效率的最佳途径。每当你看到jQuery独特的语法时是否有想过它是如何实现的呢?是否觉得相当复杂让你都不敢去想呢,今天就让我们揭开jQuery选择器的神秘面纱。

一、选择器入口

 

var Utils = {
        find : 
function (expr,context){
            
return selector.quick(expr,context || document);
        }
}

 

 

expr就是表达式了,如下(注:我所说的都是jQuery的原理):

 

代码
//效率最高,直接映射到document.getElementById
Utils.find("#id"
 
//支持querySelectorAll否则调用context.getElementsByName然后遍历getAttribute("name")等于a的
Utils.find("input[name=a]")

//不支持querySelectorAll,调用context.getElementsByName然后遍历getAttribute("name")等于a的,然后筛选出选中的
Utils.find(
"input:checked[name=a]")

//支持querySelectorAll否则映射到context.getElementsByTagName
Utils.find("input")

//支持querySelectorAll否则调用context.getElementsByTagName("*")然后筛选出className包含className的标签
Utils.find(".cssName")

 

 

context 上下文哦,主要是在这个里面查找哦

代码
var selector = {};
  selector.quick 
= function(expr,context){
    
//#id
    if/^#([\w-]+)$/.test( expr ) ){
        
return context.getElementById( expr.substr(1) );
    }
    
//TAG
    else if ( /^\w+$/.test( expr ) ) {
        
return selector.array( context.getElementsByTagName( expr ));
    }
    
try
    {
        
return selector.array(context.querySelectorAll(expr));//ie6 ie7都不支持哦
    }catch(e) {}
    
return selector.array(selector(expr,context,isXML(context)));//不支持就只能用最原始的啦
}

 

 

querySelectorAll 是什么?这个可是主角啊,jQuery独特的语法其实就是参考自它的,标准且主流的浏览器都支持了,比如ie8,ff等等,然而ie6、ie7都不支持哦,所以我们需要自己来实现,后面很多代码其实都是为了解决不支持它而写的,可恨啊。。。


查看资料:
http://www.w3.org/TR/selectors-api/

http://www.w3.org/TR/css3-selectors/

http://msdn.microsoft.com/zh-cn/library/cc304114(en-us,VS.85).aspx

 

看看document.querySelectorAll都支持什么语法 

 

代码
var a = document.querySelectorAll("#a, #b, #c");
var b = document.querySelectorAll("#frm p #a");
var c = document.querySelectorAll("body p.cssName");
var d = document.querySelectorAll("p.abc, #a");
var e = document.querySelectorAll("input");
var f = document.querySelector("body p[id=aa] span");
var g = document.querySelectorAll("ul.nav>li");
var h = document.querySelectorAll("input[checked=checked]");
//.....

 

 

 

二、选择器实现

 

 

 

 

代码
var selector = function(expr, context, isXML){
        
var set, match;
        
if ( !expr ) {
            
return [];
        }
        
for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
            
var type = Expr.order[i], match;
        
            
if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
                
var left = match[1];
                match.splice(
1,1);

                
if ( left.substr( left.length - 1 ) !== "\\" ) {
                    match[
1= (match[1|| "").replace(/\\/g, "");
                    set 
= Expr.find[ type ]( match, context, isXML );
                    
if ( set != null ) {
                        expr 
= expr.replace( Expr.match[ type ], "" );
                        
break;
                    }
                }
            }
        }
        
if ( !set ) {
            set 
= context.getElementsByTagName("*");//效率非常低下,获取所有的标签,然后调用filter筛选出需要的
        }
        
return filter(expr,set);
    };

selector.array 
= function(array){
    
var ret = [];
    
if ( Object.prototype.toString.call(array) === "[object Array]" ) {
        Array.prototype.push.apply( ret, array );
    } 
else {
        
if ( typeof array.length === "number" ) {
            
for ( var i = 0, l = array.length; i < l; i++ ) {
                ret.push( array[i] );
            }
        } 
else {
            
for ( var i = 0; array[i]; i++ ) {
                ret.push( array[i] );
            }
        }
    }

    
return ret;
}

//判断是否是xml
var isXML = function(elem){
    
// documentElement is verified for cases where it doesn't yet exist
    // (such as loading iframes in IE - #4833) 
    var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
    
return documentElement ? documentElement.nodeName !== "HTML" : false;
};

 

其实上面都实现大半了,下面会有很多代码其实都是为了解决不支持querySelectorAll 而写的。看代码吧,其实不难。

 

代码
var Expr = {
    order: [ 
"ID""NAME""TAG" ],
    match: {
        ID: 
/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
        CLASS: 
/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
        NAME: 
/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
        ATTR: 
/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
        TAG: 
/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
        CHILD: 
/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
        POS: 
/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
        PSEUDO: 
/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
    },
    leftMatch: {},
    attrMap: {
        
"class""className",
        
"for""htmlFor"
    },
    attrHandle: {
        href: 
function(elem){
            
return elem.getAttribute("href");
        }
    },
    relative: {
        
"+"function(checkSet, part){
            
var isPartStr = typeof part === "string",
                isTag 
= isPartStr && !/\W/.test(part),
                isPartStrNotTag 
= isPartStr && !isTag;

            
if ( isTag ) {
                part 
= part.toLowerCase();
            }

            
for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
                
if ( (elem = checkSet[i]) ) {
                    
while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

                    checkSet[i] 
= isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
                        elem 
|| false :
                        elem 
=== part;
                }
            }

            
if ( isPartStrNotTag ) {
                Sizzle.filter( part, checkSet, 
true );
            }
        },
        
">"function(checkSet, part){
            
var isPartStr = typeof part === "string";

            
if ( isPartStr && !/\W/.test(part) ) {
                part 
= part.toLowerCase();

                
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
                    
var elem = checkSet[i];
                    
if ( elem ) {
                        
var parent = elem.parentNode;
                        checkSet[i] 
= parent.nodeName.toLowerCase() === part ? parent : false;
                    }
                }
            } 
else {
                
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
                    
var elem = checkSet[i];
                    
if ( elem ) {
                        checkSet[i] 
= isPartStr ?
                            elem.parentNode :
                            elem.parentNode 
=== part;
                    }
                }

                
if ( isPartStr ) {
                    Sizzle.filter( part, checkSet, 
true );
                }
            }
        },
        
""function(checkSet, part, isXML){
            
var doneName = done++, checkFn = dirCheck;

            
if ( typeof part === "string" && !/\W/.test(part) ) {
                
var nodeCheck = part = part.toLowerCase();
                checkFn 
= dirNodeCheck;
            }

            checkFn(
"parentNode", part, doneName, checkSet, nodeCheck, isXML);
        },
        
"~"function(checkSet, part, isXML){
            
var doneName = done++, checkFn = dirCheck;

            
if ( typeof part === "string" && !/\W/.test(part) ) {
                
var nodeCheck = part = part.toLowerCase();
                checkFn 
= dirNodeCheck;
            }

            checkFn(
"previousSibling", part, doneName, checkSet, nodeCheck, isXML);
        }
    },
    find: {
        ID: 
function(match, context, isXML){
            
if ( typeof context.getElementById !== "undefined" && !isXML ) {
                
var m = context.getElementById(match[1]);
                
return m ? [m] : [];
            }
        },
        NAME: 
function(match, context){
            
if ( typeof context.getElementsByName !== "undefined" ) {
                
var ret = [], results = context.getElementsByName(match[1]);

                
for ( var i = 0, l = results.length; i < l; i++ ) {
                    
if ( results[i].getAttribute("name"=== match[1] ) {
                        ret.push( results[i] );
                    }
                }

                
return ret.length === 0 ? null : ret;
            }
        },
        TAG: 
function(match, context){
            
return context.getElementsByTagName(match[1]);
        }
    },
    preFilter: {
        CLASS: 
function(match, curLoop, inplace, result, not, isXML){
            match 
= " " + match[1].replace(/\\/g, ""+ " ";

            
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]/g, " ").indexOf(match) >= 0) ) {
                        
if ( !inplace ) {
                            result.push( elem );
                        }
                    } 
else if ( inplace ) {
                        curLoop[i] 
= false;
                    }
                }
            }

            
return false;
        },
        ID: 
function(match){
            
return match[1].replace(/\\/g, "");
        },
        TAG: 
function(match, curLoop){
            
return match[1].toLowerCase();
        },
        CHILD: 
function(match){
            
if ( match[1=== "nth" ) {
                
// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
                var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
                    match[
2=== "even" && "2n" || match[2=== "odd" && "2n+1" ||
                    
!/\D/.test( match[2] ) && "0n+" + match[2|| match[2]);

                
// calculate the numbers (first)n+(last) including if they are negative
                match[2= (test[1+ (test[2|| 1)) - 0;
                match[
3= test[3- 0;
            }

            
// TODO: Move to normal caching system
            match[0= done++;

            
return match;
        },
        ATTR: 
function(match, curLoop, inplace, result, not, isXML){
            
var name = match[1].replace(/\\/g, "");
            
            
if ( !isXML && Expr.attrMap[name] ) {
                match[
1= Expr.attrMap[name];
            }

            
if ( match[2=== "~=" ) {
                match[
4= " " + match[4+ " ";
            }

            
return match;
        },
        PSEUDO: 
function(match, curLoop, inplace, result, not){
            
if ( match[1=== "not" ) {
                
// If we're dealing with a complex expression, or a simple one
                if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
                    match[
3= Sizzle(match[3], nullnull, curLoop);
                } 
else {
                    
var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
                    
if ( !inplace ) {
                        result.push.apply( result, ret );
                    }
                    
return false;
                }
            } 
else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
                
return true;
            }
            
            
return match;
        },
        POS: 
function(match){
            match.unshift( 
true );
            
return match;
        }
    },
    filters: {
        enabled: 
function(elem){
            
return elem.disabled === false && elem.type !== "hidden";
        },
        disabled: 
function(elem){
            
return elem.disabled === true;
        },
        checked: 
function(elem){
            
return elem.checked === true;
        },
        selected: 
function(elem){
            
// Accessing this property makes selected-by-default
            // options in Safari work properly
            elem.parentNode.selectedIndex;
            
return elem.selected === true;
        },
        parent: 
function(elem){
            
return !!elem.firstChild;
        },
        empty: 
function(elem){
            
return !elem.firstChild;
        },
        has: 
function(elem, i, match){
            
return !!Sizzle( match[3], elem ).length;
        },
        header: 
function(elem){
            
return /h\d/i.test( elem.nodeName );
        },
        text: 
function(elem){
            
return "text" === elem.type;
        },
        radio: 
function(elem){
            
return "radio" === elem.type;
        },
        checkbox: 
function(elem){
            
return "checkbox" === elem.type;
        },
        file: 
function(elem){
            
return "file" === elem.type;
        },
        password: 
function(elem){
            
return "password" === elem.type;
        },
        submit: 
function(elem){
            
return "submit" === elem.type;
        },
        image: 
function(elem){
            
return "image" === elem.type;
        },
        reset: 
function(elem){
            
return "reset" === elem.type;
        },
        button: 
function(elem){
            
return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
        },
        input: 
function(elem){
            
return /input|select|textarea|button/i.test(elem.nodeName);
        }
    },
    setFilters: {
        first: 
function(elem, i){
            
return i === 0;
        },
        last: 
function(elem, i, match, array){
            
return i === array.length - 1;
        },
        even: 
function(elem, i){
            
return i % 2 === 0;
        },
        odd: 
function(elem, i){
            
return i % 2 === 1;
        },
        lt: 
function(elem, i, match){
            
return i < match[3- 0;
        },
        gt: 
function(elem, i, match){
            
return i > match[3- 0;
        },
        nth: 
function(elem, i, match){
            
return match[3- 0 === i;
        },
        eq: 
function(elem, i, match){
            
return match[3- 0 === i;
        }
    },
    filter: {
        PSEUDO: 
function(elem, match, i, array){
            
var name = match[1], filter = Expr.filters[ name ];

            
if ( filter ) {
                
return filter( elem, i, match, array );
            } 
else if ( name === "contains" ) {
                
return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
            } 
else if ( name === "not" ) {
                
var not = match[3];

                
for ( var i = 0, l = not.length; i < l; i++ ) {
                    
if ( not[i] === elem ) {
                        
return false;
                    }
                }

                
return true;
            } 
else {
                Sizzle.error( 
"Syntax error, unrecognized expression: " + name );
            }
        },
        CHILD: 
function(elem, match){
            
var type = match[1], node = elem;
            
switch (type) {
                
case 'only':
                
case 'first':
                    
while ( (node = node.previousSibling) )     {
                        
if ( node.nodeType === 1 ) { 
                            
return false
                        }
                    }
                    
if ( type === "first" ) { 
                        
return true
                    }
                    node 
= elem;
                
case 'last':
                    
while ( (node = node.nextSibling) )     {
                        
if ( node.nodeType === 1 ) { 
                            
return false
                        }
                    }
                    
return true;
                
case 'nth':
                    
var first = match[2], last = match[3];

                    
if ( first === 1 && last === 0 ) {
                        
return true;
                    }
                    
                    
var doneName = match[0],
                        parent 
= elem.parentNode;
    
                    
if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
                        
var count = 0;
                        
for ( node = parent.firstChild; node; node = node.nextSibling ) {
                            
if ( node.nodeType === 1 ) {
                                node.nodeIndex 
= ++count;
                            }
                        } 
                        parent.sizcache 
= doneName;
                    }
                    
                    
var diff = elem.nodeIndex - last;
                    
if ( first === 0 ) {
                        
return diff === 0;
                    } 
else {
                        
return ( diff % first === 0 && diff / first >= 0 );
                    }
            }
        },
        ID: 
function(elem, match){
            
return elem.nodeType === 1 && elem.getAttribute("id"=== match;
        },
        TAG: 
function(elem, match){
            
return (match === "*" && elem.nodeType === 1|| elem.nodeName.toLowerCase() === match;
        },
        CLASS: 
function(elem, match){
            
return (" " + (elem.className || elem.getAttribute("class")) + " ")
                .indexOf( match ) 
> -1;
        },
        ATTR: 
function(elem, match){
            
var name = match[1],
                result 
= Expr.attrHandle[ name ] ?
                    Expr.attrHandle[ name ]( elem ) :
                    elem[ name ] 
!= null ?
                        elem[ name ] :
                        elem.getAttribute( name ),
                value 
= result + "",
                type 
= match[2],
                check 
= match[4];

            
return result == null ?
                type 
=== "!=" :
                type 
=== "=" ?
                value 
=== check :
                type 
=== "*=" ?
                value.indexOf(check) 
>= 0 :
                type 
=== "~=" ?
                (
" " + value + " ").indexOf(check) >= 0 :
                
!check ?
                value 
&& result !== false :
                type 
=== "!=" ?
                value 
!== check :
                type 
=== "^=" ?
                value.indexOf(check) 
=== 0 :
                type 
=== "$=" ?
                value.substr(value.length 
- check.length) === check :
                type 
=== "|=" ?
                value 
=== check || value.substr(0, check.length + 1=== check + "-" :
                
false;
        },
        POS: 
function(elem, match, i, array){
            
var name = match[2], filter = Expr.setFilters[ name ];

            
if ( filter ) {
                
return filter( elem, i, match, array );
            }
        }
    }
};

for ( var type in Expr.match ) {
    Expr.match[ type ] 
= new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
    Expr.leftMatch[ type ] 
= new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){
        
return "\\" + (num - 0 + 1);
    }));
}

 

 

jQuery 不是获取的多个元素可以each遍历么?看看如何实现哦

 

代码
if (!Object.prototype.each) {
    Array.prototype.each 
= function (fn) {
        
if ( this.length > 0 ) {
            
for ( var i = 0; i < this.length; i++ ) {
                fn.call( 
this[i], i );
            }
        }
    }
}

 

 

posted @ 2010-08-20 00:44  江湖小子  阅读(1174)  评论(0编辑  收藏  举报