Ruby's Louvre

每天学习一点点算法

导航

我的第五代选择器Icarus

Icarus是我目前匹配精度最高(通过470个单元测试保证精度),速度最快(IE67下力压jQuery,其他浏览器都是使用querySelectorAll不分上下)的选择器,并且它全面支持CSS3的所有新增伪类,支持jQuery所有自定义伪类,并且支持对XML的查找,支持XML带命名空间的元素的查找(jQuery只能支持不带命名空间的,并且非常容易报错)。

Icarus与jQuery1.7在slickspeed中的速度比赛结果(数值最大代表越慢):

IcarusjQuery1.7比率
IE7 443 657 1.5
IE6 975 15701.6

如果对比早期的jQuery.42就更不用说了,是其两倍以上。

Icarus最大亮点是对XML的完美支持,为此它用得到xpath与getElementsByTagName,对于命名空间的支持,请使用"aaa\\:bbb"方式来查找。

以下就是Icarus用到原生查找API:

  • getElementById
  • getElementsByTagName
  • getElementsByTagNameNS
  • getElementsByClassName
  • evaluate (xpath)
  • selectNodes (xpath)
  • querySelectorAll

Icarus的代码量为930行(不依赖dom的其他模块),jQuery的Sizzle为1500行。

Icarus对CSS3伪类是按W3C的规范来查找,比如:not反选伪类,只支持单个表达式,如div:not(.a),不能用div:not(.a.b),要用就需要多个的反选,div:not(.a):not(.b)。

Icarus虽然支持jQuery的自定义伪类,但并不提倡使用,如位置伪类(:first,:last,:even,:odd,:eq...),我们完全可以使用标准的子元素过滤伪类要代替(:last-child,:first-child,:nth-child(even)...),对于jQuery的自定义表单伪类(:text,:radio....),我们也完全可以使用属性选择器来代替(input[type=text],input[type=radio]....)。总之,不标准的东西活不长,希望大家切记。

//dom.query v5 开发代号Icarus
(function(global,DOC){
    var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
    dom.define("query", function(){
        dom.mix(dom,{
            //http://www.cnblogs.com/rubylouvre/archive/2010/03/14/1685360.
            isXML : function(el){
                var doc = el.ownerDocument || el
                return doc.createElement("p").nodeName !== doc.createElement("P").nodeName;
            },
            // 第一个节点是否包含第二个节点
            contains:function(a, b){
                if(a.compareDocumentPosition){
                    return !!(a.compareDocumentPosition(b) & 16);
                }else if(a.contains){
                    return a !== b && (a.contains ? a.contains(b) : true);
                }
                while ((b = b.parentNode))
                    if (a === b) return true;
                return false;
            },
            //获取某个节点的文本,如果此节点为元素节点,则取其childNodes的所有文本,
            //为了让结果在所有浏览器下一致,忽略所有空白节点,因此它非元素的innerText或textContent
            getText : function() {
                return function getText( nodes ) {
                    for ( var i = 0, ret = "",node; node = nodes[i++];  ) {
                        // 对得文本节点与CDATA的内容
                        if ( node.nodeType === 3 || node.nodeType === 4 ) {
                            ret += node.nodeValue;
                        //取得元素节点的内容
                        } else if ( node.nodeType !== 8 ) {
                            ret += getText( node.childNodes );
                        }
                    }
                    return ret;
                }
            }(),
     
            unique :function(nodes){
                if(nodes.length < 2){
                    return nodes;
                }
                var result = [], array = [], uniqResult = {}, node = nodes[0],index, ri = 0
                //如果支持sourceIndex我们将使用更为高效的节点排序
                //http://www.cnblogs.com/jkisjk/archive/2011/01/28/array_quickly_sortby.html
                if(node.sourceIndex){//IE opera
                    for(var i = 0 , n = nodes.length; i< n; i++){
                        node = nodes[i];
                        index = node.sourceIndex+1e8;
                        if(!uniqResult[index]){
                            (array[ri++] = new String(index))._ = node;
                            uniqResult[index] = 1
                        }
                    }
                    array.sort();
                    while( ri )
                        result[--ri] = array[ri]._;
                    return result;
                }else {
                    var sortOrder = node.compareDocumentPosition ? sortOrder1 : sortOrder2;
                    nodes.sort( sortOrder );
                    if (sortOrder.hasDuplicate ) {
                        for ( i = 1; i < nodes.length; i++ ) {
                            if ( nodes[i] === nodes[ i - 1 ] ) {
                                nodes.splice( i--, 1 );
                            }
                        }
                    }
                    sortOrder.hasDuplicate = false;
                    return nodes;
                }
            }
        });
        var reg_combinator  = /^\s*([>+~,\s])\s*(\*|(?:[-\w*]|[^\x00-\xa0]|\\.)*)/
        var trimLeft = /^\s+/;
        var trimRight = /\s+$/;
        var reg_quick = /^(^|[#.])((?:[-\w]|[^\x00-\xa0]|\\.)+)$/;
        var reg_comma       = /^\s*,\s*/;
        var reg_sequence = /^([#\.:]|\[\s*)((?:[-\w]|[^\x00-\xa0]|\\.)+)/;
        var reg_pseudo        = /^\(\s*("([^"]*)"|'([^']*)'|[^\(\)]*(\([^\(\)]*\))?)\s*\)/;
        var reg_attrib      = /^\s*(?:(\S?=)\s*(?:(['"])(.*?)\2|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/
        var reg_attrval  = /\\([0-9a-fA-F]{2,2})/g;
        var reg_sensitive       = /^(title|id|name|class|for|href|src)$/
        var reg_backslash = /\\/g;
        var reg_tag  = /^((?:[-\w\*]|[^\x00-\xa0]|\\.)+)/;//能使用getElementsByTagName处理的CSS表达式
        if ( trimLeft.test( "\xA0" ) ) {
            trimLeft = /^[\s\xA0]+/;
            trimRight = /[\s\xA0]+$/;
        }

        var hash_operator   = {
            "=": 1, 
            "!=": 2, 
            "|=": 3,
            "~=": 4, 
            "^=": 5, 
            "$=": 6, 
            "*=": 7
        }

        function sortOrder1( a, b ) {
            if ( a === b ) {
                sortOrder1.hasDuplicate = true;
                return 0;
            }
            if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
                return a.compareDocumentPosition ? -1 : 1;
            }
            return a.compareDocumentPosition(b) & 4 ? -1 : 1;
        };

        function sortOrder2( a, b ) {//处理旧式的标准浏览器与XML
            if ( a === b ) {
                sortOrder2.hasDuplicate = true;
                return 0;
            }
            var al, bl,
            ap = [],
            bp = [],
            aup = a.parentNode,
            bup = b.parentNode,
            cur = aup;
            //如果是属于同一个父节点,那么就比较它们在childNodes中的位置
            if ( aup === bup ) {
                return siblingCheck( a, b );
            // If no parents were found then the nodes are disconnected
            } else if ( !aup ) {
                return -1;

            } else if ( !bup ) {
                return 1;
            }
            // Otherwise they're somewhere else in the tree so we need
            // to build up a full list of the parentNodes for comparison
            while ( cur ) {
                ap.unshift( cur );
                cur = cur.parentNode;
            }

            cur = bup;

            while ( cur ) {
                bp.unshift( cur );
                cur = cur.parentNode;
            }

            al = ap.length;
            bl = bp.length;

            // Start walking down the tree looking for a discrepancy
            for ( var i = 0; i < al && i < bl; i++ ) {
                if ( ap[i] !== bp[i] ) {
                    return siblingCheck( ap[i], bp[i] );
                }
            }
            // We ended someplace up the tree so do a sibling check
            return i === al ?
            siblingCheck( a, bp[i], -1 ) :
            siblingCheck( ap[i], b, 1 );
        };
        function siblingCheck( a, b, ret ) {
            if ( a === b ) {
                return ret;
            }
            var cur = a.nextSibling;

            while ( cur ) {
                if ( cur === b ) {
                    return -1;
                }
                cur = cur.nextSibling;
            }
            return 1;
        };
        var slice = Array.prototype.slice;
        function makeArray( nodes, result, flag_multi ) {  
            nodes = slice.call( nodes, 0 );
            if ( result ) {
                result.push.apply( result, nodes );
            }else{
                result = nodes;
            }
            return  flag_multi ? dom.unique(result) : result;
        };
        //IE56789无法使用数组方法转换节点集合
        try {
            slice.call( dom.html.childNodes, 0 )[0].nodeType;
        } catch( e ) {
            function makeArray( nodes, result ,flag_multi) {
                var ret = result || [], ri = ret.length;
                for(var i = 0,el ; el = nodes[i++];){
                    ret[ri++] = el
                }
                return flag_multi ? dom.unique(ret) : ret;
            }
        }
        function _toHex(x, y) {
            return String.fromCharCode(parseInt(y, 16));
        }
        function parse_nth(expr) {
            var orig = expr
            expr = expr.replace(/^\+|\s*/g, '');//清除无用的空白
            var match = (expr === "even" && "2n" || expr === "odd" && "2n+1" || !/\D/.test(expr) && "0n+" + expr || expr).match(/(-?)(\d*)n([-+]?\d*)/);
            return parse_nth[ orig ] = {
                a: (match[1] + (match[2] || 1)) - 0, 
                b: match[3] - 0
            };
        }
        function getElementsByTagName(tagName, els, flag_xml) {
            var method = "getElementsByTagName", elems = [], uniqResult = {}, prefix
            if(flag_xml && tagName.indexOf(":") > 0 && els.length && els[0].lookupNamespaceURI){
                var arr = tagName.split(":");
                prefix = arr[0];
                tagName = arr[1];
                method = "getElementsByTagNameNS";
                prefix = els[0].lookupNamespaceURI(prefix);
            }
            switch (els.length) {
                case 0:
                    return elems;
                case 1:
                    //在IE67下,如果存在一个name为length的input元素,下面的all.length返回此元素,而不是长度值
                    var all =  prefix ? els[0][method](prefix,tagName) : els[0][method](tagName);
                    for(var i = 0, ri = 0, el; el = all[i++];){
                        if(el.nodeType === 1){//防止混入注释节点
                            elems[ri++] = el
                        }
                    }
                    return elems;
                default:
                    for(i = 0, ri = 0; el = els[i++];){
                        var nodes = prefix ? el[method](prefix,tagName) : el[method](tagName)
                        for (var j = 0, node; node = nodes[j++];) {
                            var uid = dom.getUid(node);
                           
                            if (!uniqResult[uid]) {
                                uniqResult[uid] = elems[ri++] = node;
                            }
                        }
                    }
                    return elems;
            }
        }
        //IE9 以下的XML文档不能直接设置自定义属性
        var attrURL = dom.oneObject('action,cite,codebase,data,href,longdesc,lowsrc,src,usemap', 2);
        var bools = dom["@bools"] = "autofocus,autoplay,async,checked,controls,declare,disabled,defer,defaultChecked,"+
        "contentEditable,ismap,loop,multiple,noshade,open,noresize,readOnly,selected"
        var boolOne = dom.oneObject(bools.toLowerCase() ); 
        var getHTMLText = new Function("els","return els[0]."+ (dom.html.textContent ? "textContent" : "innerText") );
        //检测各种BUG(fixGetAttribute,fixHasAttribute,fixById,fixByTag)
        var fixGetAttribute,fixHasAttribute,fixById,fixByTag;
        new function(){
            var select = DOC.createElement("select");
            var option = select.appendChild( DOC.createElement("option") );
            option.setAttribute("selected","selected")
            option.className ="x"
            fixGetAttribute = option.getAttribute("class") != "x";
            select.appendChild( DOC.createComment("") );
            fixByTag = select.getElementsByTagName("*").length == 2
            var all = DOC.getElementsByTagName("*"), node, nodeType, comments = [], i = 0, j = 0;
            while ( (node = all[i++]) ) {  
                nodeType = node.nodeType;
                nodeType === 1 ? dom.getUid(node) : 
                nodeType === 8 ? comments.push(node) : 0;  
            }
            while ( (node = comments[j++]) ) {   
                node.parentNode.removeChild(node);
            }
            fixHasAttribute = select.hasAttribute ? !option.hasAttribute('selected') :true;
        
            var form = DOC.createElement("div"),
            id = "fixId" + (new Date()).getTime(),
            root = dom.html;
            form.innerHTML = "<a name='" + id + "'/>";
            root.insertBefore( form, root.firstChild );
            fixById = !!DOC.getElementById( id ) ;
            root.removeChild(form )
        };

        //http://www.atmarkit.co.jp/fxml/tanpatsu/24bohem/01.html
        //http://msdn.microsoft.com/zh-CN/library/ms256086.aspx
        //https://developer.mozilla.org/cn/DOM/document.evaluate
        //http://d.hatena.ne.jp/javascripter/20080425/1209094795
        function getElementsByXPath(xpath,context,doc) {
            var result = [];
            try{
                if(global.DOMParser){//IE9支持DOMParser,但我们不能使用doc.evaluate!global.DOMParser
                    var nodes = doc.evaluate(xpath, context, null, 7, null);
                    for (var i = 0, n = nodes.snapshotLength; i < n; i++){
                        result[i] =  nodes.snapshotItem(i)
                    } 
                }else{
                    nodes = context.selectNodes(xpath);
                    for (i = 0, n = nodes.length; i < n; i++){
                        result[i] =  nodes[i]
                    } 
                }
            }catch(e){
                return false;
            }
            return result;
        };
        /**
         * 选择器
         * @param {String} expr CSS表达式
         * @param {Node}   context 上下文(可选)
         * @param {Array}  result  结果集(内部使用)
         * @param {Array}  lastResult  上次的结果集(内部使用)
         * @param {Boolean}flag_xml 是否为XML文档(内部使用)
         * @param {Boolean}flag_multi 是否出现并联选择器(内部使用)
         * @param {Boolean}flag_dirty 是否出现通配符选择器(内部使用)
         * @return {Array} result
         */
        //http://webbugtrack.blogspot.com/
        var Icarus = dom.query = function(expr, contexts, result, lastResult, flag_xml,flag_multi,flag_dirty){
            result = result || [];
            contexts = contexts || DOC;
            var pushResult = makeArray;
            if(!contexts.nodeType){//实现对多上下文的支持
                contexts = pushResult(contexts);
                if(!contexts.length)
                    return result
            }else{
                contexts = [contexts];
            }
            var rrelative = reg_combinator,//保存到本地作用域
            rquick = reg_quick,
            rBackslash = reg_backslash, rcomma = reg_comma,//用于切割并联选择器
            context = contexts[0],
            doc = context.ownerDocument || context,
            rtag = reg_tag,
            flag_all, uniqResult, elems, nodes, tagName, last, ri, uid;
            //将这次得到的结果集放到最终结果集中
            //如果要从多个上下文中过滤孩子
            expr = expr.replace(trimLeft, "").replace(trimRight, "");  
            flag_xml = flag_xml !== void 0 ? flag_xml : dom.isXML(doc);
       
            if (!flag_xml && doc.querySelectorAll2) {
                var query = expr;
                if(contexts.length > 2 || doc.documentMode == 8  && context.nodeType == 1  ){
                    if(contexts.length > 2 )
                        context = doc;
                    query = ".fix_icarus_sqa "+query;//IE8也要使用类名确保查找范围
                    for(var i = 0, node; node = contexts[i++];){
                        if(node.nodeType === 1){
                            node.className = "fix_icarus_sqa " + node.className;
                        }
                    }
                }
                if(doc.documentMode !== 8  || context.nodeName.toLowerCase() !== "object"){
                    try{
                        return pushResult( context.querySelectorAll(query), result, flag_multi);
                    }catch(e){
                    }finally{
                        if(query.indexOf(".fix_icarus_sqa") === 0 ){//如果为上下文添加了类名,就要去掉类名
                            for(i = 0; node = contexts[i++];){
                                if(node.nodeType === 1){
                                    node.className =  node.className.replace("fix_icarus_sqa ","");
                                }
                            }
                        }
                    }
                }
            }
            var match = expr.match(rquick);
            if(match ){//对只有单个标签,类名或ID的选择器进行提速
                var value = match[2].replace(rBackslash,""), key = match[1];
                if (  key == "") {//tagName;
                    nodes = getElementsByTagName(value,contexts,flag_xml);
                } else if ( key === "." && contexts.length === 1 ) {//className,并且上下文只有1个
                    if(flag_xml){//如果XPATH查找失败,就会返回字符,那些我们就使用普通方式去查找
                        nodes = getElementsByXPath("//*[@class='"+value+"']", context, doc);
                    }else if(context.getElementsByClassName){
                        nodes = context.getElementsByClassName( value );
                    }
                }else if ( key === "#" && contexts.length === 1){//ID,并且上下文只有1个
                    if( flag_xml){
                        nodes = getElementsByXPath("//*[@id='"+value+"']", context, doc);
                    //基于document的查找是不安全的,因为生成的节点可能还没有加入DOM树,比如dom("<div id=\"A'B~C.D[E]\"><p>foo</p></div>").find("p")
                    }else if(context.nodeType == 9){
                        node = doc.getElementById(value);
                        //IE67 opera混淆表单元素,object以及链接的ID与NAME
                        //http://webbugtrack.blogspot.com/2007/08/bug-152-getelementbyid-returns.html
                        nodes = !node ? [] : !fixById ? [node] : node.getAttributeNode("id").nodeValue === value ? [node] : false;
                    }
                }
                if(nodes ){
                    return pushResult( nodes, result, flag_multi );
                }
            }
            //执行效率应该是内大外小更高一写
            lastResult = contexts;
            if(lastResult.length){
                loop:
                while (expr && last !== expr) {
                    flag_dirty = false;
                    elems = null;
                    uniqResult = {};
                    //处理夹在中间的关系选择器(取得连接符及其后的标签选择器或通配符选择器)
                    if (match = expr.match(rrelative)) {
                        expr = RegExp.rightContext;
                        elems = [];
                        tagName = (flag_xml ? match[2] : match[2].toUpperCase()).replace(rBackslash,"") || "*";
                        i = 0;
                        ri = 0;
                        flag_all = tagName === "*";// 表示无需判定tagName
                        switch (match[1]) {//根据连接符取得种子集的亲戚,组成新的种子集
                            case " "://后代选择器
                                if(expr.length || match[2]){//如果后面还跟着东西或最后的字符是通配符
                                    elems = getElementsByTagName(tagName, lastResult, flag_xml);
                                }else{
                                    elems = lastResult;
                                    break loop
                                }
                                break;
                            case ">"://亲子选择器
                                while((node = lastResult[i++])){
                                    for (node = node.firstChild; node; node = node.nextSibling){
                                        if (node.nodeType === 1 && (flag_all || tagName === node.nodeName)){
                                            elems[ri++] = node;
                                        }
                                    }
                                }
                                break;
                            case "+"://相邻选择器
                                while((node = lastResult[i++])){
                                    while((node = node.nextSibling)){
                                        if (node.nodeType === 1) {
                                            if (flag_all || tagName === node.nodeName)
                                                elems[ri++] = node;
                                            break;
                                        }
                                    }
                                }
                                break;
                            case "~"://兄长选择器
                                while((node = lastResult[i++])){
                                    while((node = node.nextSibling)){
                                        if (node.nodeType === 1 && (flag_all || tagName === node.nodeName)) {
                                            uid = dom.getUid(node);
                                            if (uniqResult[uid]){
                                                break;
                                            }else {
                                                uniqResult[uid] = elems[ri++] = node;
                                            }
                                        }
                                    }
                                }
                                elems = dom.unique(elems);
                                break;
                        }
                    }else if(match = expr.match(rtag)){//处理位于最开始的或并联选择器之后的标签选择器或通配符
                        expr = RegExp.rightContext;
                        elems = getElementsByTagName(match[1].replace(rBackslash,""), lastResult, flag_xml);
                    }
                   
                    if(expr){
                        var arr = Icarus.filter(expr, elems, lastResult, doc, flag_xml);
                        expr = arr[0];
                        elems = arr[1];
                        if (!elems) {
                            flag_dirty = true;
                            elems = getElementsByTagName("*", lastResult, flag_xml);
                        }
                        if (match = expr.match(rcomma)) {
                            expr = RegExp.rightContext;
                            pushResult(elems, result);
                            return Icarus(expr, contexts, result, [], flag_xml, true, flag_dirty);
                        }else{
                            lastResult = elems;
                        }
                    }
                    
                }
            }
            if (flag_multi) {
                if (elems.length){
                    return pushResult(elems, result,flag_multi);
                }
            }else if (DOC !== doc || fixByTag && flag_dirty) {
                for (result = [], ri = 0, i = 0; node = elems[i++]; )
                    if (node.nodeType === 1)
                        result[ri++] = node;
                return result
            }
            return elems;
        }
        var onePosition = dom.oneObject("eq|gt|lt|first|last|even|odd".split("|"));

        dom.mix(Icarus, {
            //getAttribute总会返回字符串
            //http://reference.sitepoint.com/javascript/Element/getAttribute
            getAttribute : !fixGetAttribute ?
            function(elem, name) {
                return elem.getAttribute(name) || '';
            } :
            function(elem, name, flag_xml) {
                if(flag_xml)
                    return elem.getAttribute(name) || '';
                name = name.toLowerCase();
                //http://jsfox.cn/blog/javascript/get-right-href-attribute.html
                if(attrURL[name]){//得到href属性里原始链接,不自动转绝对地址、汉字和符号都不编码
                    return  elem.getAttribute(name, 2) || ''
                }
                if(elem.tagName === "INPUT" && name == "type"){
                    return elem.getAttribute("type") || elem.type;//IE67无法辩识HTML5添加添加的input类型,如input[type=search],不能使用el.type与el.getAttributeNode去取。
                }
                //布尔属性,如果为true时则返回其属性名,否则返回空字符串,其他一律使用getAttributeNode
                var attr = boolOne[name] ? (elem.getAttribute(name) ? name : '') :
                (elem = elem.getAttributeNode(name)) && elem.value || '';
                return reg_sensitive.test(name)? attr :attr.toLowerCase();
            },
            hasAttribute : !fixHasAttribute ?
            function(elem, name, flag_xml) {
                return flag_xml ?  !!elem.getAttribute(name) :elem.hasAttribute(name);
            } :
            function(elem, name) {
                //http://help.dottoro.com/ljnqsrfe.php
                name = name.toLowerCase();
                //如果这个显式设置的属性是"",即使是outerHTML也寻不见其踪影
                elem = elem.getAttributeNode(name);
                return !!(elem && (elem.specified || elem.nodeValue));
            },
            filter : function(expr, elems, lastResult, doc, flag_xml, flag_get){
                var rsequence = reg_sequence,
                rattrib = reg_attrib ,
                rpseudo = reg_pseudo,
                rBackslash = reg_backslash,
                rattrval  = reg_attrval,
                pushResult = makeArray,
                toHex = _toHex,
                _hash_op  = hash_operator,
                parseNth = parse_nth,
                match ,key, tmp;
                while ( match = expr.match(rsequence)) {//主循环
                    expr = RegExp.rightContext;     
                    key = ( match[2]|| "").replace(rBackslash,"");
                    if (!elems) {//取得用于过滤的元素
                        if (lastResult.length === 1 && lastResult[0] === doc){
                            switch (match[1]) {
                                case "#":
                                    if (!flag_xml) {//FF chrome opera等XML文档中也存在getElementById,但不能用
                                        tmp = doc.getElementById(key);
                                        if (!tmp) {
                                            elems = [];
                                            continue;
                                        }
                                        //处理拥有name值为"id"的控件的form元素
                                        if (fixById ? tmp.id === key : tmp.getAttributeNode("id").nodeValue === key) {
                                            elems = [tmp];
                                            continue;
                                        }
                                    }
                                    break;
                                case ":":
                                    switch (key) {
                                        case "root":
                                            elems = [doc.documentElement];
                                            continue;
                                        case "link":
                                            elems = pushResult(doc.links || []);
                                            continue;
                                    }
                                    break;
                            }
                        }
                        elems = getElementsByTagName("*", lastResult, flag_xml);//取得过滤元
                    }
                    //取得用于过滤的函数,函数参数或数组
                    var filter = 0, flag_not = false, args; 
                    switch (match[1]) {
                        case "#"://ID选择器
                            filter = ["id", "=", key];
                            break;
                        case "."://类选择器
                            filter = ["class", "~=", key];
                            break;
                        case ":"://伪类选择器
                            tmp = Icarus.pseudoAdapter[key];
                            if (match = expr.match(rpseudo)) {
                                expr = RegExp.rightContext;
                                if(!!~key.indexOf("nth")){
                                    args = parseNth[match[1]] || parseNth(match[1]);
                                }else{
                                    args = match[3] || match[2] || match[1]
                                }
                            }
                            if (tmp){
                                filter = tmp;
                            }else if (key === "not") {
                                flag_not = true;
                                if (args === "*"){//处理反选伪类中的通配符选择器
                                    elems = [];
                                }else if(reg_tag.test(args)){//处理反选伪类中的标签选择器
                                    tmp = [];
                                    match = flag_xml ? args : args.toUpperCase();
                                    for (var i = 0, ri = 0, elem; elem = elems[i++];)
                                        if (match !== elem.nodeName)
                                            tmp[ri++] = elem;
                                    elems = tmp;
                                }else{
                                    var obj =  Icarus.filter(args, elems, lastResult, doc, flag_xml, true) ;
                                    filter = obj.filter;
                                    args   = obj.args;
                                }
                            }
                            else{
                                throw 'An invalid or illegal string was specified : "'+ key+'"!'
                            }
                            break
                        default:
                            filter = [key.toLowerCase()];  
                            if (match = expr.match(rattrib)) {
                                expr = RegExp.rightContext;
                                if (match[1]) {
                                    filter[1] = match[1];//op
                                    filter[2] = match[3] || match[4];//对值进行转义
                                    filter[2] = filter[2] ? filter[2].replace(rattrval, toHex).replace(rBackslash,"") : "";
                                }
                            }
                            break;
                    }
                    if(flag_get){
                        return {
                            filter:filter,
                            args:args
                        }
                    }
                    //如果条件都俱备,就开始进行筛选 
                    if (elems.length && filter) {
                        tmp = [];
                        i = 0;
                        ri = 0;
                        if (typeof filter === "function") {//如果是一些简单的伪类
                            if(onePosition[key]){
                                //如果args为void则将集合的最大索引值传进去,否则将exp转换为数字
                                args =  args === void 0 ? elems.length - 1 : ~~args;
                                for (; elem = elems[i];){
                                    if(filter(i++, args) ^ flag_not)
                                        tmp[ri++] = elem;
                                }
                            }else{
                                while((elem = elems[i++])){
                                    if ((!!filter(elem, args)) ^ flag_not)
                                        tmp[ri++] = elem;
                                }
                            }
                        }else if (typeof filter.exec === "function"){//如果是子元素过滤伪类
                            tmp = filter.exec({
                                not: flag_not, 
                                xml: flag_xml
                            }, elems, args, doc);
                        } else {
                            var name = filter[0], op = _hash_op[filter[1]], val = filter[2]||"", flag, attr;
                            if (!flag_xml && name === "class" && op === 4) {//如果是类名
                                val = " " + val + " ";
                                while((elem = elems[i++])){
                                    var className = elem.className;
                                    if (!!(className && (" " + className + " ").indexOf(val) > -1) ^ flag_not){
                                        tmp[ri++] = elem;
                                    }
                                }
                            } else {
                                if(!flag_xml && op && val && !reg_sensitive.test(name)){
                                    val = val.toLowerCase();
                                }
                                if (op === 4){
                                    val = " " + val + " ";
                                }
                                while((elem = elems[i++])){
                                    if(!op){
                                        flag = Icarus.hasAttribute(elem,name,flag_xml);//[title]
                                    }else if(val === "" && op > 3){
                                        flag = false
                                    }else{
                                        attr = Icarus.getAttribute(elem,name,flag_xml);
                                        switch (op) {
                                            case 1:// = 属性值全等于给出值
                                                flag = attr === val;
                                                break;
                                            case 2://!= 非标准,属性值不等于给出值
                                                flag = attr !== val;
                                                break;
                                            case 3://|= 属性值以“-”分割成两部分,给出值等于其中一部分,或全等于属性值
                                                flag = attr === val || attr.substr(0, val.length + 1) === val + "-";
                                                break;
                                            case 4://~= 属性值为多个单词,给出值为其中一个。
                                                flag = attr  && (" " + attr + " ").indexOf(val) >= 0;
                                                break;
                                            case 5://^= 属性值以给出值开头
                                                flag = attr  && attr.indexOf(val) === 0 ;
                                                break;
                                            case 6://$= 属性值以给出值结尾
                                                flag = attr  &&attr.substr(attr.length - val.length) === val;
                                                break;
                                            case 7://*= 属性值包含给出值
                                                flag = attr  && attr.indexOf(val) >= 0;
                                                break;
                                        }
                                    }
                                    if (flag ^ flag_not)
                                        tmp[ri++] = elem;
                                }
                            }
                        }
                        elems = tmp;
                    }
                }
                return [expr, elems];
            }
        });

        //===================构建处理伪类的适配器=====================
        var filterPseudoHasExp = function(strchild,strsibling, type){
            return {
                exec:function(flags,lastResult,args){
                    var result = [], flag_not = flags.not,child = strchild, sibling = strsibling,
                    ofType = type, cache = {},lock = {},a = args.a, b = args.b, i = 0, ri = 0, el, found ,diff,count;
                    if(!ofType && a === 1 && b === 0 ){
                        return flag_not ? [] : lastResult;
                    }
                    var checkName = ofType ? "nodeName" : "nodeType";
                    for (; el = lastResult[i++];) {
                        var parent = el.parentNode;
                        var pid =  dom.getUid(parent);
                        if (!lock[pid]){
                            count = lock[pid] = 1;
                            var checkValue = ofType ? el.nodeName : 1;
                            for(var node = parent[child];node;node = node[sibling]){
                                if(node[checkName] === checkValue){
                                    pid = dom.getUid(node);
                                    cache[pid] = count++;
                                }
                            }
                        }
                        diff = cache[dom.getUid(el)] - b;
                        found =  a === 0 ? diff === 0 : (diff % a === 0 && diff / a >= 0 );
                        (found ^ flag_not) && (result[ri++] = el);
                    }
                    return  result;
                }
            };
        };
        function filterPseudoNoExp(name, isLast, isOnly) {
            var A = "var result = [], flag_not = flags.not, node, el, tagName, i = 0, ri = 0, found = 0; for (; node = el = lastResult[i++];found = 0) {"
            var B = "{0} while (!found && (node=node.{1})) { (node.{2} === {3})  && ++found;  }";
            var C = " node = el;while (!found && (node = node.previousSibling)) {  node.{2} === {3} && ++found;  }";
            var D =  "!found ^ flag_not && (result[ri++] = el);  }   return result";

            var start = isLast ? "nextSibling" : "previousSibling";
            var fills = {
                type: [" tagName = el.nodeName;", start, "nodeName", "tagName"],
                child: ["", start, "nodeType", "1"]
            }
            [name];
            var body = A+B+(isOnly ? C: "")+D;
            var fn = new Function("flags","lastResult",body.replace(/{(\d)}/g, function ($, $1) {
                return fills[$1];
            }));
            return {
                exec:fn
            }
        }

        function filterProp(str_prop, flag) {
            return {
                exec: function (flags, elems) {
                    var result = [], prop = str_prop, flag_not = flag ? flags.not : !flags.not;
                    for (var i = 0,ri = 0, elem; elem = elems[i++];)
                        if ( elem[prop] ^ flag_not)
                            result[ri++] = elem;//&& ( !flag || elem.type !== "hidden" )
                    return result;
                }
            };
        };
        Icarus.pseudoAdapter = {
            root: function (el) {//标准
                return el === (el.ownerDocument || el.document).documentElement;
            },
            target: {//标准
                exec: function (flags, elems,_,doc) {
                    var result = [], flag_not = flags.not;
                    var win = doc.defaultView || doc.parentWindow;
                    var hash = win.location.hash.slice(1);       
                    for (var i = 0,ri = 0, elem; elem = elems[i++];)
                        if (((elem.id || elem.name) === hash) ^ flag_not)
                            result[ri++] = elem;
                    return result;
                }
            },
            "first-child"    : filterPseudoNoExp("child", false, false),
            "last-child"     : filterPseudoNoExp("child", true,  false),
            "only-child"     : filterPseudoNoExp("child", true,  true),
            "first-of-type"  : filterPseudoNoExp("type",  false, false),
            "last-of-type"   : filterPseudoNoExp("type",  true,  false),
            "only-of-type"   : filterPseudoNoExp("type",  true,  true),//name, isLast, isOnly
            "nth-child"       : filterPseudoHasExp("firstChild", "nextSibling",     false),//标准
            "nth-last-child"  : filterPseudoHasExp("lastChild",  "previousSibling", false),//标准
            "nth-of-type"     : filterPseudoHasExp("firstChild", "nextSibling",     true),//标准
            "nth-last-of-type": filterPseudoHasExp("lastChild",  "previousSibling", true),//标准
            empty: {//标准
                exec: function (flags, elems) {   
                    var result = [], flag_not = flags.not, check
                    for (var i = 0, ri = 0, elem; elem = elems[i++];) {
                        if(elem.nodeType == 1){
                            if (!elem.firstChild ^ flag_not)
                                result[ri++] = elem;
                        }
                    }
                    return result;
                }
            },
            link: {//标准
                exec: function (flags, elems) {
                    var links = (elems[0].ownerDocument || elems[0].document).links;
                    if (!links) return [];
                    var result = [],
                    checked = {},
                    flag_not = flags.not;
                    for (var i = 0, ri = 0,elem; elem = links[i++];)
                        checked[dom.getUid(elem) ] = 1;
                    for (i = 0; elem = elems[i++]; )
                        if (checked[dom.getUid(elem)] ^ flag_not)
                            result[ri++] = elem;
                    return result;
                }
            },
            lang: {//标准 CSS2链接伪类
                exec: function (flags, elems, arg) {
                    var result = [], reg = new RegExp("^" + arg, "i"), flag_not = flags.not;
                    for (var i = 0, ri = 0, elem; elem = elems[i++]; ){
                        var tmp = elem;
                        while (tmp && !tmp.getAttribute("lang"))
                            tmp = tmp.parentNode;
                        tmp = !!(tmp && reg.test(tmp.getAttribute("lang")));
                        if (tmp ^ flag_not)
                            result[ri++] = elem;
                    }
                    return result;
                }
            },
            active: function(el){
                return el === el.ownerDocument.activeElement;
            },
            focus:function(el){
                return (el.type|| el.href) && el === el.ownerDocument.activeElement;
            },
            indeterminate : function(node){//标准
                return node.indeterminate === true && node.type === "checkbox"
            },
            //http://www.w3.org/TR/css3-selectors/#UIstates
            enabled:  filterProp("disabled", false),//标准
            disabled: filterProp("disabled", true),//标准
            checked:  filterProp("checked", true),//标准
            contains: {
                exec: function (flags, elems, arg) {
                    var res = [], elem = elems[0], fn = flags.xml ? dom.getText: getHTMLText,
                    flag_not = flags.not;
                    for (var i = 0, ri = 0, elem; elem = elems[i++]; ){
                        if ((!!~fn( [elem] ).indexOf(arg)) ^ flag_not)
                            res[ri++] = elem;
                    }
                    return res;
                }
            },
            //自定义伪类
            selected : function(el){
                el.parentNode.selectedIndex;//处理safari的bug
                return el.selected === true;
            },
            header : function(el){
                return /h\d/i.test( el.nodeName );
            },
            button : function(el){
                return "button" === el.type || el.nodeName === "BUTTON";
            },
            input: function(el){
                return /input|select|textarea|button/i.test(el.nodeName);
            },
            parent : function( el ) {
                return !!el.firstChild;
            },
            has : function(el, expr){//孩子中是否拥有匹配expr的节点
                return !!dom.query(expr,[el]).length;
            },
            //与位置相关的过滤器
            first: function(index){
                return index === 0;
            },
            last: function(index, num){
                return index === num;
            },
            even: function(index){
                return index % 2 === 0;
            },
            odd: function(index){
                return index % 2 === 1;
            },
            lt: function(index, num){
                return index < num;
            },
            gt: function(index, num){
                return index > num;
            },
            eq: function(index, num){
                return index ===  num;
            },
            hidden : function( el ) {
                return el.type === "hidden" || (!el.offsetWidth && !el.offsetHeight) || (el.currentStyle && el.currentStyle.display === "none") ;
            }
        }
        Icarus.pseudoAdapter.visible = function(el){
            return  !Icarus.pseudoAdapter.hidden(el);
        }

        "text,radio,checkbox,file,password,submit,image,reset".replace(dom.rword, function(name){
            Icarus.pseudoAdapter[name] = function(el){
                return (el.getAttribute("type") || el.type) === name;//避开HTML5新增类型导致的BUG,不直接使用el.type === name;
            }
        });
       
    });

})(this,this.document);
//2011.10.25重构dom.unique
//2011.10.26支持对拥有name值为id的控件的表单元素的查找,添加labed语句,让元素不存在时更快跳出主循环
//2011.10.30让属性选择器支持拥有多个中括号与转义符的属性表达式,如‘input[name=brackets\\[5\\]\\[\\]]’
//2011.10.31重构属性选择器处理无操作部分,使用hasAttribute来判定用户是否显示使用此属性,并支持checked, selected, disabled等布尔属性
//2011.10.31重构关系选择器部分,让后代选择器也出现在switch分支中
//2011.11.1 重构子元素过滤伪类的两个生成函数filterPseudoHasExp filterPseudoNoExp
//2011.11.2 FIX处理 -of-type家族的BUG
//2011.11.3 添加getAttribute hasAttribute API
//2011.11.4 属性选择器对给出值或属性值为空字符串时进行快速过滤
//2011.11.5 添加getElementsByXpath 增加对XML的支持
//2011.11.6 重构getElementsByTagName 支持带命名空间的tagName
//2011.11.6 处理IE67与opera9在getElementById中的BUG
//2011.11.7 支持多上下文,对IE678的注释节点进行清除,优化querySelectorAll的使用
//2011.11.8 处理分解nth-child参数的BUG,修正IE67下getAttribute对input[type=search]的支持,重构sortOrder标准浏览器的部分
//调整swich...case中属性选择器的分支,因为reg_sequence允许出现"[  "的情况,因此会匹配不到,需要改为default
//修改属性选择器$=的判定,原先attr.indexOf(val) == attr.length - val.length,会导致"PWD".indexOf("bar]")也有true
//2011.11.9 增加getText 重构 getElementById与过滤ID部分
//2011.11.10 exec一律改为match,对parseNth的结果进行缓存



下面是Icarus对命名空间的支持演示,例子是inline SVG,由于IE9不支持,请在高版本的标准浏览器中看。在IE10支持SVG后,SVG的应用就大大增多了,因此命名空间的支持是必须的。


 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 
        <head>
            <title>icarus svg by 司徒正美</title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <script src="https://files.cnblogs.com/rubylouvre/icarus.js"></script>
            <script>
            
                window.onload = function(){
                    alert(dom.query("svg\\:feOffset:first")[0].tagName)
                }
            </script>
        </head>
        <body>
        <svg:svg height="0">
            <!-- Create the filter. Make sure it uses the sRGB colorspace or you're in for some nasty surprises. -->
            <svg:filter color-interpolation-filters="sRGB" id="perspDisp" filterUnits="userSpaceOnUse" x="0%" y="0%" width="512" height="512"  >
                <!-- Move the video 128px to the bottom/right so that the displacement filter can reach 128px to the top/left without reaching beyond the image -->
                <svg:feOffset
                    x="128" y="128" width="256" height="256"
                    dx="128" dy="128"
                    result="displacement"
                    />
                <!-- This actually loads our texture-->
                <svg:feImage
                    id="textureLoader"
                    x="0" y="0" width="256" height="256"
                    xlink:href="texture.tinman.png"
                    />
                <!-- Tile the texture to fill the whole viewport so that the displacement filter can also reach 128px to the bottom/right without leaving the texture -->
                <svg:feTile
                    x="0" y="0" width="512" height="512"
                    result="texture"
                    />
                <!-- Apply the displacement -->
                <svg:feDisplacementMap
                    x="128" y="128" width="256" height="256"
                    in="texture"  in2="displacement"
                    scale="255"
                    xChannelSelector="R" yChannelSelector="G"
                    />
                <!-- Apply the alpha of the displacement map to the final image, so that whatever is transparent in the map is also transparent in the final image -->
                <svg:feComposite
                    in2="displacement"
                    operator="in"
                    />
                <!-- Move the image back to the top/left -->
                <svg:feOffset
                    x="0" y="0" width="256" height="256"
                    dx="-128" dy="-128"
                    />
            </svg:filter>
        </svg:svg>


    </body>
</html>

后话,javascript的选择器基本是为了兼容IE678(IE8的querySelectoAll支持种类太少),以后的选择器基本上是用querySelectorAll与evaluate与matchesSelector来写了,CSS4也很快到来了,带来更多伪类,因此jQuery的自定义伪类基本没有存在的必要。

相关链接:

第一代选择器

第二代选择器

第三代选择器

第四代选择器

速度比赛

posted on 2011-11-10 12:44  司徒正美  阅读(13863)  评论(26编辑  收藏  举报