• 我的第五代选择器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的自定义伪类基本没有存在的必要。

    相关链接:

    第一代选择器

    第二代选择器

    第三代选择器

    第四代选择器

    速度比赛

  • 相关阅读:
    NetStat
    Linux远程目录挂载
    Mysql服务彪高排查方式及索引的正确使用步骤
    Linux查看哪些进程占用的系统 buffer/cache 较高 (hcache,lsof)命令
    防止sql注入的最好方式
    Fortify---Detail--Sql注入
    百亿级数据处理优化
    半年的总结和思考,继续前行
    Protoc Buffer 优化传输大小的一个细节
    RPC
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2243838.html
Copyright © 2020-2023  润新知