`

教你实现 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], null, null, 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 );
            }
        }
    }

分享到:
评论
1 楼 reyesyang 2012-11-01  
以前都没怎么是用过原生的 querySelectorAll 函数,很强大。很详细的文章,收下慢慢阅读。

相关推荐

    jquery实现颜色选择器

    **jQuery 实现颜色选择器** 在网页开发中,颜色选择器是一个常见的交互元素,它允许用户方便地选取颜色。在本教程中,我们将探讨如何利用 jQuery 和相关插件来实现一个功能完备的颜色选择器。 首先,`jQuery` 是一...

    jQuery选择器.rar

    这个名为"jQuery选择器.rar"的压缩包文件显然包含了关于jQuery选择器的相关学习资源,可能包括示例代码、教程文档等。这些内容可能与《锋利的jQuery第二版》这本书中的章节相匹配,该书是jQuery学习的经典参考资料之...

    Jquery选择器教程

    通过熟练掌握这些jQuery选择器,你可以更高效地选取和操作DOM元素,实现更复杂的交互和动态效果。在实际项目中,结合使用选择器,结合jQuery的其他功能,如事件处理和动画效果,可以极大地提升开发效率和代码质量。...

    jquery双向选择器代码

    为了进一步学习和理解,你可以解压文件,查看其中的JavaScript代码,分析它是如何使用jQuery选择器进行双向操作的。此外,也可以查阅jQuery官方文档和在线教程,以获取更全面的知识和实践指导。 总的来说,掌握...

    jquery语法与选择器介绍 带有案例

    ### jQuery选择器 jQuery支持多种选择器,扩展了CSS的选择能力: 1. **基本选择器**:`$('element')`(标签选择器)、`$('#id')`(ID选择器)、`$('.class')`(类选择器)。 2. **组合选择器**:`$('element1, ...

    jQuery选择器的演示

    jQuery选择器可以灵活组合,实现复杂的选择需求。例如,`$("div.myClass#myId input[type='text']")`将选取id为"myId"且类名为"myClass"的div内的所有文本输入框。 通过掌握以上jQuery选择器的用法,你可以更加精确...

    jquery 城市三字码 选择器

    标题中的“jquery 城市三字码 选择器”是指使用jQuery库开发的一个功能,它允许用户在前端界面中选择城市,同时返回该城市的三字码。在国际航空运输协会(IATA)的标准中,每个城市都有一个三位字母的代码,方便在...

    JQuery教程全集

    - **The jQuery Wrapper**:当使用 JQuery 选择器选择 DOM 元素时,返回的是一个称为“JQuery 对象”的封装对象,这个对象提供了丰富的 API 来操作 DOM。 - **Utility Functions**:JQuery 提供了一系列实用函数,...

    jQuery中的基本选择器用法学习教程

    CSS选择器可能在某些浏览器中不兼容,而jQuery选择器提供了更好的跨浏览器兼容性。 - 例如,CSS的子选择器`#box &gt; p`在IE6中可能不工作,但jQuery的`$('#box &gt; p').css('color', 'red')`则能兼容IE6。 5. **jQuery...

    jQuery自定义月份选择插件

    我们可以创建一个名为`$.fn.monthPicker`的方法,这个方法将会扩展jQuery对象,使得任何jQuery选择器都能调用它。在函数内部,我们需要初始化插件的状态,例如当前选中的年份和月份,并为元素绑定事件监听器,如点击...

    jquery的选择器的学习教程

    这个学习教程将引导你深入理解jQuery选择器的强大与灵活性,帮助你在最短时间内提升JavaScript开发效率。** jQuery选择器分为基本选择器、层级选择器、属性选择器、伪类选择器和组合选择器五大部分。 1. **基本...

    jquery 颜色选择插件

    首先,`jquery.miniColors.css`是插件的主题样式文件,包含了颜色选择器的布局和样式定义。在网页中引入此CSS文件,可以确保颜色选择器的外观与网页设计保持一致。文件中可能包括了颜色选择面板的尺寸、背景色、按钮...

    JQuery选择器

    **jQuery选择器详解** 在Web开发中,jQuery库极...为了深入学习jQuery选择器,你可以参考压缩包文件中的`ch06-jQuery选择器`资源,其中可能包含详细的讲解和示例代码,通过实践操作,你将更好地理解和应用这些选择器。

    jQuery中文资料电子书教程

    - `jQuery选择器.doc` 文件可能详细介绍了jQuery的各种选择器,包括基础选择器(如ID、类、元素名)、层次选择器(如后代、子元素、相邻兄弟、后续兄弟)、属性选择器以及表单选择器等。理解并熟练运用选择器可以...

    jQuery 遍历css选择器

    jQuery选择器的强大之处在于它们可以组合使用,以实现复杂的元素选取。例如,选取类名为`highlight`并且`data-role`属性以`'link'`开头的所有元素: ```javascript $("[data-role^='link'].highlight").doSomething...

    jquery教程chm格式

    2. jQuery选择器:jQuery提供了丰富的选择器,如ID选择器(#id),类选择器(.class),元素选择器(element),以及组合选择器等,用于快速定位DOM元素。 二、DOM操作 3. 获取元素:$.fn.find()、$.fn.children()、$.fn...

    jQuery学习教程.CHM

    1. **jQuery选择器**:jQuery提供了丰富的选择器,如ID选择器(#id)、类选择器(.class)、元素选择器(tagname)等,以及组合选择器,使得选取DOM元素变得更加便捷。 2. **DOM操作**:jQuery封装了对DOM的操作,如`$...

    jquery教程 文档 手册

    2. **jQuery选择器**:jQuery支持CSS选择器,如`id`、`class`、`tag`等,用于选取DOM元素。例如,选取ID为"myElement"的元素: ```javascript $("#myElement"); ``` 3. **DOM操作**:jQuery提供了一系列方法来...

    jquery教程 15天学会jquery(完整版)

    2. **选择器**:了解jQuery的各种选择器,如ID选择器(`#id`)、类选择器(`.class`)、元素选择器(`element`)等,以及如何组合使用它们。 3. **链式操作**:理解jQuery对象的链式调用,如何在一个方法调用后立即执行另...

Global site tag (gtag.js) - Google Analytics