`

教你实现 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 函数,很强大。很详细的文章,收下慢慢阅读。

相关推荐

    css与jquery教程

    这个函数接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组元素。 jQuery 的核心功能都是通过这个函数实现的。 jQuery中的一切都构建于这个函数之上,或者说都是在以某种方式使用这个函数。这个函数最...

    jQuery详细教程

    提示:jQuery 使用的语法是 XPath 与 CSS 选择器语法的组合。在本教程接下来的章节,您将学习到更多有关选择器的语法。 文档就绪函数 您也许已经注意到在我们的实例中的所有 jQuery 函数位于一个 document ready ...

    【动力节点】jquery高清视频教程

    本套Java视频教程循序渐进地对jQuery的各种选择器、函数和方法调用进行了详细的讲解,更结合了大量的案例进行分析。001_动力节点_jQuery视频 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。

    Learning+jquery中文版

    jQuery 中选择器表达式让你找到页面上所有的元素,你将会使用选择器表达 式来样式化页面上不同的元素,有时候可以不是纯 css。 在第三章里,你会学习如何触发事件,浏览器发事件时,你将会使用 jQuery 的事件处理机 ...

    jquery-location-picker:jQuery位置选择器插件

    #jquery#location-picker,#maps,#autocomplete,#geocode,#OpenStreetMap,#OSM,#OpenLayers特征位置按地址搜索地址自动完成在地图上选择位置并点击从位置反向地址地理编码使用以下方法初始化位置选择器...

    Web前端Ajax&JQuery视频教程课件

    本套Java教程涵盖Ajax的实现原理,XMLHttpRequest实现Ajax,回调函数,Ajax数据交换格式(HTML、XML、JSON),Ajax...本视频教程循序渐进地对jQuery的各种选择器、函数和方法调用进行了详细的讲解,更结合了大量的案例。

    从零开始学习JQuery

    从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 从零开始学习jQuery (五) 事件与事件对象 从零开始学习jQuery (六) ...

    JQuery快速入门教程

    课程目标什么是jQuery 是一个JavaScript的函数库 jQuery是一个轻量级的JavaScript库 浏览器兼容比较好 jQuery可以简化JavaScript代码适用人群...jQuery选择器的使用jQuery链式语法使用jQuery 代码优化设计jQuery动画

    jquery+ajax实现省市区三级联动效果简单示例

    本文实例讲述了jquery+ajax实现省市区三级联动效果。分享给大家供大家参考,具体如下: 一直想学习下Ajax,没时间,汗,这借口太牵强了.下了点教程在手机里,翻了好几遍了,没实战一次. 最近的项目里需要Ajax实现效果,就...

    浅析php如何实现爬取数据原理

    QueryList使用jQuery选择器来做采集,让你告别复杂的正则表达式;QueryList具有jQuery一样的DOM操作能力、Http网络操作能力、乱码解决能力、内容过滤能力以及可扩展能力;可以轻松实现诸如:模拟登陆、伪造浏览器、...

    JavaScript实战

    5.4.4 理解jQuery选择 136 5.5 向页面添加内容 138 替换和删除选择 141 5.6 设置和读取标签属性 142 5.6.1 类 142 5.6.2 读取和改变CSS属性 143 5.6.3 一次改变多个CSS属性 145 5.7 读取、设置和删除HTML属性 146 ...

    H5仿微信界面教程(一)

    jQuery WeUI 是WeUI的一个jQuery实现版本,除了实现了官方插件之外,它还提供了如下拉刷新、日历、地址选择器等丰富的拓展组件。jQuery WeUI 中的JS组件均是以JQuery 插件的形式提供,使用非常方便,并且可以和React...

    JavaScript插件化开发教程 (一)

    一,开篇分析 Hi,大家!今天这系列文章主要是说说如何开发基于“JavaScript”的插件式开发,我想很多人对”插件“这个词并不陌生...另一种是jQuery对象级别的方法,即挂在jQuery原型下的方法,这样通过选择器获取的jQu

    PHP100视频教程全集112集BT种子【PHP经典】

    PHP100视频教程107:JQuery 之选择器、事件器详解 PHP100视频教程108:JQuery之各类动画效果的实现 PHP100视频教程109:JQuery 之 Ajax 开发详解 PHP100视频教程110:Jquery案例 之 双下拉框内容移动 PHP100...

    史上最好传智播客就业班.net培训教程60G 不下会后悔

    HTML基础加强、css(包含Div+CSS布局)、JavaScript、Dom(事件、window...JQuery函数、隐式迭代、链式编程、id选择器、tag选择器、CSS选择器、层次选择器、表单选择器、过滤选择器、复合选择器、节点导航、节点操作、...

    平板显示发展史

    关于一个选择器XML的小程序 C++控制台计算器(能识别括号) Java面试宝典 VC写的蝴蝶会动的时钟 清华大学C语言课件【超详细_很强大】 Struts,Hibernate,Spring集成开发宝典.pdf asp.net mvc教程 jquery-ui-1.9.2....

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    │ Java面试题39.jQuery中的常用选择器.mp4 │ Java面试题40.jQuery中页面加载完毕事件.mp4 │ Java面试题41.jQuery中Ajax和原生js实现Ajax的关系.mp4 │ Java面试题42.简单说一下html5.mp4 │ Java面试题43.简单说...

    轻量化的Java网络爬虫 GECCO.zip

    Gecco整合了jsoup、httpclient、fastjson、spring、htmlunit、redission等优秀框架,让您只需要配置一些jquery风格的选择器就能很快的写出一个爬虫。Gecco框架有优秀的可扩展性,框架基于开闭原则进行设计,对修改...

    cytofviz:CyTOF数据上的简单PCA和RSKM聚类

    CyTOF数据可视化器 这是PCA和健壮的稀疏K均值(RSKM)的快速javascript实现。 实际上,这不一定是CyTOF数据,而是任何高维数据集。 请记住,一切都在浏览器中完成,因此选择一个大文件可能需要一些时间来进行分析。 ...

    老男孩Python完美实战课程 14期视频教程 28周Python视频教程 1-14周部分

    │ │ ├03 python s14 day 11 jQuery选择器eq和nth-child的区别_rec.mp4 │ │ ├04 python s14 day 11 jQuery表单选择器_rec.mp4 │ │ ├05 python s14 day 11 jQuery操作属性、css和返回顶部实例_rec.mp4 │ │ ...

Global site tag (gtag.js) - Google Analytics