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

    相关链接:

    第一代选择器

    第二代选择器

    第三代选择器

    第四代选择器

    速度比赛

  • 相关阅读:
    hi.baidu.com 百度流量统计
    Autofac is designed to track and dispose of resources for you.
    IIS Manager could not load type for module provider 'SharedConfig' that is declared in administration.config
    How to create and manage configuration backups in Internet Information Services 7.0
    定制swagger的UI
    NSwag在asp.net web api中的使用,基于Global.asax
    NSwag Tutorial: Integrate the NSwag toolchain into your ASP.NET Web API project
    JS变量对象详解
    JS执行上下文(执行环境)详细图解
    JS内存空间详细图解
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2243838.html
Copyright © 2011-2022 走看看