• 新年总结 我的选择器发展史


    我想,我是国内最熟悉CSS选择器运作机理的人了。新的一年,也该是时间把曾经走过的足迹记录下来,让大家明白,所有成功都是来之不易的。

    注:下面给的链接,许多都可能打不开,因为我并没有把它们公开出来。

    最后附上queryv3.5的源码:

     
    /*
    query selector version 3.5
    Copyright 2010
    Dual licensed under the MIT or GPL Version 2 licenses.
    author "司徒正美(zhongqincheng)"
    http://www.cnblogs.com/rubylouvre/
     */
    (function(window){
        var A_slice = Array.prototype.slice;
        window.dom = {
            UID:1,
            oneObject : function(array,val){
                var result = {},value = val !== void 0 ? val :1;
                for(var i=0,n=array.length;i < n;i++)
                    result[array[i]] = value;
                return result;
            },
            slice:function(nodes,start,end){
                return A_slice.call(nodes,(start || 0),(end || nodes.length))
            },
            isXML : function(context) {
                context = context.ownerDocument || document;
                return context.createElement("p").nodeName !== context.createElement("P").nodeName;
            },
            contains : function(ancestor,node) {
                if (node.compareDocumentPosition)
                    return (node.compareDocumentPosition(ancestor) & 8) === 8;
                if (ancestor.contains)
                    return ancestor.contains(node) && ancestor !== node;
                while (node = node.parentNode)
                    if (node == ancestor) return true;
                return false;
            },
            queryId : function (id, context) {
                var el = (context || document).getElementById(id);
                return el && [el] || []
            },
            queryTag : function(tag,context){
                var flag_skip = tag !== "*",result = [],els = context.getElementsByTagName(tag);
                if(-[1,]){
                    return A_slice.call(els)
                }else{
                    for(var i = 0,ri = 0,el;el = els[i++];)
                        if(flag_skip || el.nodeType === 1){
                            result[ri++] = el
                        }
                }
                return result;
            },
            queryPos : function(selectors,context,flag_xml,name,value){
                var filter = dom.$$filters[name],ret = [],recursion = [], i = 0, ri = 0,nodes,node,selector;
                do{
                    selector = selectors.pop();
                    if(selector != ","){
                        recursion.unshift(selector)
                    }else{
                        selectors.push(selector);
                        break;
                    }
                }while(selectors.length);
                nodes = dom.query(recursion,context,flag_xml);
                //如果value为空白则将集合的最大索引值传进去,否则将exp转换为数字
                var num = (value === ""|| value === void 0) ? nodes.length - 1 : ~~value;
                for (; node = nodes[i];i++){
                    if(filter.call(node,i, num))
                        ret[ri++] = node;
                }
                ret.selectors = selectors
                return ret
            }
        }
        var child_pseudo = "first-child|last-child|only-child|nth-child|nth-last-child";
        var reg_find =/(^[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*)|^(\*)|^#([\w\u00a1-\uFFFF-]+)|^\.([\w\u00a1-\uFFFF-]+)|^:(root)|^:(link)|^:(eq|gt|lt|even|odd|first[^-]|last[^-])\(?(.*)\)?/;
        var reg_swap = /([\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*)(\.[\w\u00a1-\uFFFF-]+|:(?!eq|gt|lt|even|odd|first[^-]|last[^-])\S+(?:\(.*\))?|\[[^\]]*\])/g;
        var reg_split =/^\s+|[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*|[#.:][\w\u00a1-\uFFFF-]+(?:\([^\)]*\))?|\[[^\]]*\]|(?:\s*)[>+~,*](?:\s*)|\s(?=[\w\u00a1-\uFFFF*#.[:])/g;
        var reg_id=  /^#([^,#:\.\s\xa0\u3000\+>~\[\(])+$/;
        var reg_tag =  /^[\w\u00a1-\uFFFF][\w\u00a1-\uFFFF-]*$/;
        var reg_pseudo =  /^:(\w[-\w]*)(?:\((.*)\))?$/;
        var reg_href = /^(?:src|href|style)$/;
        var reg_attribute = /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/ ;
        var one_child = dom.oneObject((child_pseudo+"|"+child_pseudo.replace(/child/g,"of-type")).split("|"));
        var one_position = dom.oneObject("eq|gt|lt|first|last|even|odd".split("|"));
        var documentOrder = !-[1,] ? function (a, b) {
            return (a.sourceIndex - b.sourceIndex);
        }:function (a, b) {
            return (3 - (a.compareDocumentPosition(b) & 6));
        }
        var map_attr = {
            "accept-charset": "acceptCharset",
            accesskey: "accessKey",
            bgcolor: "bgColor",
            cellpadding: "cellPadding",
            cellspacing: "cellSpacing",
            "char": "ch",
            charoff: "chOff",
            "class": "className",
            codebase: "codeBase",
            codetype: "codeType",
            colspan: "colSpan",
            datetime: "dateTime",
            defaultchecked:"defaultChecked",
            defaultselected:"defaultSelected",
            defaultvalue:"defaultValue",
            "for": "htmlFor",
            frameborder: "frameBorder",
            "http-equiv": "httpEquiv",
            ismap: "isMap",
            longdesc: "longDesc",
            maxlength: "maxLength",
            margin"marginWidth",
            marginheight:'marginHeight',
            nohref: "noHref",
            noresize:"noResize",
            noshade: "noShade",
            readonly: "readOnly",
            rowspan: "rowSpan",
            tabindex: "tabIndex",
            usemap: "useMap",
            vspace: "vSpace",
            valuetype: "valueType"
        };
        var queryAttribute = function(el,name,flag_xml){
            var special = map_attr[name];
            if(!flag_xml && special)
                return el[special];
            var flag = reg_href.test(name) ? 2 : 0;
            return el.getAttribute(name,flag) ;
        };
        /**************************特征侦探*****************************************/
        dom.support = {
            sliceNodes : true
        };
        var HTML = document.documentElement;
        var div = document.createElement("div");
        HTML.insertBefore(div, HTML.firstChild);
        var id = new Date - 0
        div.innerHTML = '<a name="'+id+'"></a><b id="'+id+'"></b>';
        dom.support.diffName = document.getElementById(id) !== div.firstChild;
        try{//检测是否支持
            A_slice.call(div.childNodes)
        }catch(e){
            dom.support.sliceNodes = false;
        }
        div.appendChild(document.createComment(''))
        dom.support.diffComment = div.getElementsByTagName("*").length !== 3;
        HTML.removeChild(div)
        /************************根据浏览器特征重写部分函数**************************/
        if(!dom.support.diffName){
            dom.queryId = function(id,root){
                root = root || document;
                if(root.getElementById){
                    var el = root.getElementById(id);
                    return el && el.attributes['id'].value === id ? [el] :[]
                } else {
                    var all = root.all[id];
                    for(var i=0;el=all[i++];){
                        if(el.attributes['id'].value === id)
                            return [el]
                    }
                    return []
                }
            }
        }
        if(!dom.support.sliceNodes){
            dom.slice = function(nodes,start,end){
                var i = nodes.length,result = [];
                while(i--){
                    result[i] = nodes[i];
                }
                return  A_slice.call(result,(start || 0),(end || result.length));
            }
        }
        /****************************过滤器*****************************************/
        var queryPseudoNoExp = function(name,isLast,isOnly){
            var head = "var node = this;{0} while((node=node.{1}))  if(node.{2} === {3}) return false;";
            var body = "var prev = this;while ((prev = prev.previousSibling)) if (prev.{2} === {3}) return false;"
            var foot = "return true;"
            var start = isLast ? "nextSibling": "previousSibling";
            var fills = {
                type : ["var tagName = this.nodeName;",start,"nodeName","tagName"],
                child : [""                           ,start,"nodeType","1"]
            };
            var fn = isOnly ? head+body+foot :head+foot;
            return new Function(fn.replace(/{(\d)}/g, function($,$1){
                return fills[name][$1];
            }));
        }
        var queryPseudoHasExp = function(name,isLast){
            var outer = function(a,b){
                var el = this,parent = el.parentNode;
                if (parent.querytime != dom.querytime ){
                    undefined
                }
                var diff = el.queryIndex - b;
                if ( a === 0 ) {
                    return diff === 0;
                } else {
                    return ( diff % a === 0 && diff / a >= 0 );
                }
            }
            var inner = "var {0}; for (var node = parent.{1}; node; node = node.{2}){ if(node.nodeType === 1){ {3} } } parent.querytime = dom.querytime;";
            var buildIndexByChild = "node.queryIndex = ++index;"
            var buildIndexByType =  "tagName = node.nodeName;if(tagName in cache){ ++cache[tagName]; }else{cache[tagName] = 1;}node.queryIndex = cache[tagName];"
            var start = isLast ? "lastChild" : "firstChild";
            var next =  isLast ? "previousSibling":"nextSibling";
            var fills = {
                type :  ["cache = {},tagName",start,next,buildIndexByType],
                child : ["index = 0"         ,start,next,buildIndexByChild]
            }
            inner = inner.replace(/{(\d)}/g, function($,$1){
                return fills[name][$1];
            });
            return  eval(("[" +outer+"]").replace("undefined",inner))[0];
        }
        dom.$$filters = { //伪类选择器的过滤器
            enabled: function(){//CSS3属性伪类
                return this.disabled === false && this.type !== "hidden";
            },
            disabled: function(){//CSS3属性伪类
                return this.disabled === true;
            },
            checked: function(){//CSS3属性伪类
                return this.checked === true;
            },
            indeterminate:function(){//CSS3属性伪类
                return this.indeterminate = true && this.type === "checkbox"
            },
            selected: function(){//自定义属性伪类
                this.parentNode.selectedIndex;//处理safari的bug
                return this.selected === true;
            },
            empty: function () {//CSS3结构伪类(子元素过滤伪类)
                return !this.firstChild;
            },
            link:function(){//CSS2链接伪类
                return this.nodeName === "A";
            },
            lang: function (reg) {//CSS3语言伪类
                    var el = this;
                    while (el && el.getAttribute){//如果是文档对象就不用往上找了
                        if(reg.test(el.getAttribute("lang")))
                            return true;
                        el = el.parentNode;
                    }
            },
            header: function(){//自定义属性伪类
                return /h\d/i.test( this.nodeName );
            },
            button: function(){//自定义属性伪类
                return "button" === this.type || this.nodeName === "BUTTON";
            },
            input: function(){//自定义属性伪类
                return /input|select|textarea|button/i.test(this.nodeName);
            },
            hidden : function( ) {//自定义可见性伪类
                return this.type === "hidden" || (this.offsetWidth === 0 ) || (!-[1,] && this.currentStyle.display === "none") ;
            },
            visible : function( ) {//自定义可见性伪类
                return this.type !== "hidden" && (this.offsetWidth || this.offsetHeight || (!-[1,] && this.currentStyle.display !== "none"));
            },
            target:function(exp,context){//CSS2.1目标标准
                var id = context.location.hash.slice(1);
                return (this.id || this.name) === id;
            },
            parent : function( ) {//自定义结构伪类
                return !!this.firstChild;
            },
            contains: function(exp) {//自定义内容伪类
                return (this.textContent||this.innerText||'').indexOf(exp) !== -1
            },
            has: function( ) {//自定义结构伪类(子元素过滤伪类,根据子节点的选择器情况进行筛选)
                for(var i =0,node;node = arguments[i++];)
                    if(dom.contains(this,node)){
                        return true;
                    }
                return false;
            },
            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;
            },
            not:function(){},//CSS3反选伪类
            "nth-child"       : queryPseudoHasExp("child",false),//CSS3子元素过滤伪类
            "nth-last-child"  : queryPseudoHasExp("child",true),//CSS3子元素过滤伪类
            "nth-of-type"     : queryPseudoHasExp("type",false),//CSS3子元素过滤伪类
            "nth-last-of-type": queryPseudoHasExp("type",true),//CSS3子元素过滤伪类
            "first-child"     : queryPseudoNoExp("child",false,false),//CSS3子元素过滤伪类
            "last-child"      : queryPseudoNoExp("child",true ,false),//CSS3子元素过滤伪类
            "only-child"      : queryPseudoNoExp("child",true ,true),//CSS3子元素过滤伪类
            "first-of-type"   : queryPseudoNoExp("type" ,false,false),//CSS3子元素过滤伪类
            "last-of-type"    : queryPseudoNoExp("type" ,true ,false),//CSS3子元素过滤伪类
            "only-of-type"    : queryPseudoNoExp("type" ,true ,true)//CSS3子元素过滤伪类
        }
        "text|radio|checkbox|file|password|submit|image|reset".replace(/\w+/g, function(name){
            dom.$$filters[name] = function(){//自定义属性伪类
                return this.type === name;
            }
        });
        /***********************迭代器**************************/
        var makeIterator = function(name){
            var outer = function(nextset){
                var set = this, nodes = this.nodes,tagName = this.tagName, filter = this.filter,args = this.args,
                level = this.level, _level, result = [], testee, uid, pid,yess = {};
                for(var i = 0,ri = 0, node; node = nodes[i++];){
                    uid =  node.uniqueID || (node.uniqueID = dom.UID++);
                    testee = set[uid] || node;
                    undefined;
                }
                nextset.nodes = result;
            }
            var parents = "if(level){_level = level; while(_level-- && ( testee = testee.parentNode));if(!testee)break;}"
            var fills ={
                current : [""            ,""],
                border :  [""            ,""      ,"previousSibling",""     ,"break;"],
                borders : ["yess[pid] = ","break;","previousSibling",""     ,""],
                parent :  ["yess[pid] = ",""      ,"parentNode"     ,""     ,"break;"],
                parents : ["yess[pid] = ","break;","parentNode"     ,parents,""]
            }
            var filter = "if((!tagName || tagName === testee.nodeName) && (!filter || filter.apply(testee,args))){\
                     {0}  result[ri++] = node;nextset[uid] = testee;{1} }".replace(/{(\d)}/g, function($,$1){
                return fills[name][$1];
            });
            var inner = "while((testee = testee.{2})){if(testee.nodeType === 1 ){ {3}"+
            (name === "border" ? "" : "pid = testee.uniqueID || (testee.uniqueID = dom.UID++);if(yess[pid]){ result[ri++] = node;nextset[uid] = testee; break;}")+"FILTER {4} } }"
            if(name === "current"){
                return eval(("[" +outer+"]").replace("undefined",filter))[0];
            }else{
                inner = inner.replace(/{(\d)}/g, function($,$1){
                    return fills[name][$1];
                }).replace("FILTER",filter);
                return  eval(("[" +outer+"]").replace("undefined",inner))[0];
            }
        }
        var iterators = {
            current:makeIterator("current"),
            parent:makeIterator("parent"),
            parents:makeIterator("parents"),
            border:makeIterator("border"),
            borders:makeIterator("borders")
        }
        /***********************适配器*********************************************/
        //通过获取每次的过滤器,迭代器等
        var adapters = {
            "#":function(selector,flag_xml,context,transport){//★★★★(1)ID选择器
                transport.args = [selector.slice(1)];
                transport.filter  =  function(id){
                    return (this.id || this.getAttribute("id")) ===  id;
                }
            },
            ".":function(selector,flag_xml,context,transport){//★★★★(2)类选择器
                transport.args = [new RegExp('(?:^|[ \\t\\r\\n\\f])' + selector.slice(1) + '(?:$|[ \\t\\r\\n\\f])')];
                transport.filter  =  function(reg_class){
                    return  reg_class.test(this.className || this.getAttribute && this.getAttribute("class"));
                }
            },
            "[":function(selector,flag_xml,context,transport){//★★★★(3)属性选择器
                var match = selector.match(reg_attribute);
                transport.args = [match[1], match[2], match[4]];
                transport.filter   = function(name,operator,value){
                    var attrib = queryAttribute(this, name, flag_xml);//取得元素的实际属性值
                     if(!operator)
                        return attrib !== false && attrib+"";
                    switch (operator) {
                        case "=":
                            return attrib === value;
                        case "!=":
                            return attrib !== value;
                        case "~=":
                            return (" " + attrib + " ").indexOf(value) !== -1;
                        case "^=":
                            return attrib.indexOf(value) === 0;
                        case "$=":
                            return attrib.lastIndexOf(value) + value.length === attrib.length;
                        case "*=":
                            return attrib.indexOf(value) !== -1;
                        case "|=":
                            return attrib === value || attrib.substring(0, value.length + 1) === value + "-";
                    }
                }
            },
            ">":function(selector,flag_xml,context,transport){//★★★★(4)亲子
                transport.iterator = iterators.parent;
            },
            "~":function(selector,flag_xml,context,transport){//★★★★(5)兄长
                transport.iterator = iterators.borders;
            },
            "+":function(selector,flag_xml,context,transport){//★★★★(6)相邻
                transport.iterator = iterators.border;
            },
            " ":function(selector,flag_xml,context,transport){//★★★★(7)后代
                transport.iterator = iterators.parents;
            },
            "*":function(selector,flag_xml,context,transport){//★★★★(8)后代
                transport.level = ~~transport.level + 1;
                transport.iterator = iterators.parents;
            },
            ":":function(selector,flag_xml,context,transport){//★★★★(9)伪类
                var match = selector.match(reg_pseudo), name = match[1],value = match[2]||"",nodes;
                if(one_position[name]){//位置伪类
                    nodes = dom.queryPos(transport.selectors,context,flag_xml,name, value);
                    transport.selectors = nodes.selectors;
    
                }else if(name === "has"){
                    transport.args = dom.query(value,context,flag_xml);
                    transport.filter =  dom.$$filters[name];
                    var nextset = {}
                    transport.iterator(nextset);
                    transport.nodes = nextset.nodes;
                    delete transport.filter;
                    return
                }else if( name ==="not"){
                    nodes = dom.query(value,context,flag_xml);
                }else{
                    if(name==="lang")
                        transport.args = [new RegExp("^" + value, "i")];
                    if(one_child[name]){
                        match = (value === "even" && "2n" || value === "odd" && "2n+1" || value.replace(/\s/g,"").replace(/(^|\D+)n/g,"$11n")).split("n");
                        transport.args = [~~match[0],~~match[1]];
                    }
                    transport.filter =  dom.$$filters[name];
                    return
                }
                if(nodes){
                    for(var i = 0, hash = {}, uid , node;node = nodes[i++];){
                        uid = node.uniqueID || (node.uniqueID = dom.UID++);
                        hash[uid] = node;
                    }
                    transport.args = [hash,name === "not"];
                    transport.filter = function(hash,not){
                        return hash[this.uniqueID] ^ not
                    }
                }
            }
        }
        var adapterOfTag = function(selector,flag_xml,context,transport){
            selector =  flag_xml ? selector : selector.toUpperCase();
            transport.tagName = selector
        }
        /*************************获取候选集***************************/
        var getCandidates = function(selectors,context,flag_xml,transport){
            var selector = selectors.pop(), match = selector.match(reg_find), nodes, node;
            if(match){
                if(match[7]){//位置伪类
                    nodes = dom.queryPos(selectors,context,flag_xml, match[7], match[8]);
                }else if(match[1] || match[2] ){//标签或通配符选择器
                    nodes = dom.queryTag(match[1] || match[2],context);
                }else if(match[3] && context.getElementById){//ID选择器
                    node = context.getElementById(match[3]);
                    nodes = node && [node] || [];
                }else if(match[4] && context.getElementsByClassName){//类选择器
                    nodes = dom.slice(context.getElementsByClassName(match[4]));
                }else if(match[5] && context.documentElement){//根伪类
                    nodes = [context.documentElement];
                }else if(match[6] && context.links){//链接伪类
                    nodes = dom.slice(context.links);
                }
            }
            if(!nodes){
                nodes = dom.queryTag("*",context);
                selectors.push(selector);
            }
            transport.selectors = "selectors" in nodes ? nodes.selectors : selectors;
            transport.nodes = nodes;
            transport.iterator = iterators.current;
        }
    
        dom.query = function(selectors, context,flag_xml){
            dom.querytime =  new Date-0;
            context = context || document;
            if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
                return [];
            }
            flag_xml = flag_xml !== void 0 ? flag_xml : dom.isXML(context);
            var result = [],uniqResult = {}, transport = {},parts = [],
            selector, ri = 0,  i = -1, nodes, node, flag_sort ,uid;
            if (typeof selectors === "string" ) {
                selectors = selectors.replace(/^[^#\(]*(#)/, "$1");
                if(arguments.length === 1){
                    if(reg_id.test(selectors))
                        return dom.queryId(selectors.slice(1),context);
                    if(reg_tag.test(selectors)){
                        return dom.queryTag(selectors,context);
                    }
                }
                //将标签选择器与紧跟在它后面的非位置伪类、类、属性相调换
                selectors = selectors.replace(reg_swap, "$2^$1");
                //将选择器群组转换为数组
                selectors.replace(reg_split,function(part){
                    i++
                    if(part == false ){//如果为空白字符串
                        if(i)
                            parts[ri++] = " ";//并且并不是第一个捕获的
                    }else if(part.match(/^\s*([>+~,*])\s*$/)){
                        parts[ri++] = RegExp.$1;
                    }else {
                        parts[ri++] = part;
                    }
                });
                selectors = parts;
    
            }
            //将候选集与选择器数组与下一次要使用的迭代器附于传送器上
            getCandidates(selectors,context,flag_xml,transport);
            selectors = transport.selectors;
            //transport的生存周期从上一次甄选操作到下一次甄选操作
            while(selectors.length){
                selector = selectors.pop();
                if(selector === ","){
                    result = result.concat(transport.nodes);//开始一下选择器群组做准备
                    getCandidates(selectors,context,flag_xml,transport);
                    selectors = transport.selectors ;
                    flag_sort = true;
                }else{
                    (adapters[selector.charAt(0)] || adapterOfTag)(selector,flag_xml,context,transport) ;
                    selectors = transport.selectors || selectors;
                    if(transport.filter || transport.tagName){
                        transport.iterator(uniqResult);//返回新的传输器(兼映射集),它里面附有节点集
                        transport = uniqResult;
                        uniqResult = {};//这是新的传输器
                    }
                }
            }
            result = result.concat(transport.nodes);
            if(result.length > 1 && flag_sort){
                i = ri = 0, nodes= [];
                //减少候选集的个数再进行排序
                for(;node = result[i++];){
                    uid = node.uniqueID || (node.uniqueID = dom.UID++);
                    if (!uniqResult[uid]){
                        uniqResult[uid] = nodes[ri++] = node;
                    }
                }
                result = nodes.sort(documentOrder);
            }
            return result;
        }
    
    })(this);
    
  • 相关阅读:
    扩展VirtualBox虚拟机磁盘容量
    Java实现莱布尼兹问题
    Java实现子序列问题
    Java实现子序列问题
    Java实现子序列问题
    Java实现子序列问题
    Java实现子序列问题
    第九届蓝桥杯JavaC组省赛真题
    第九届蓝桥杯JavaC组省赛真题
    第九届蓝桥杯JavaC组省赛真题
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1923671.html
Copyright © 2020-2023  润新知