zoukankan      html  css  js  c++  java
  • 新年总结 我的选择器发展史

    我想,我是国内最熟悉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);
    
  • 相关阅读:
    Centos 7 zabbix 实战应用
    Centos7 Zabbix添加主机、图形、触发器
    Centos7 Zabbix监控部署
    Centos7 Ntp 时间服务器
    Linux 150命令之查看文件及内容处理命令 cat tac less head tail cut
    Kickstart 安装centos7
    Centos7与Centos6的区别
    Linux 150命令之 文件和目录操作命令 chattr lsattr find
    Linux 发展史与vm安装linux centos 6.9
    Linux介绍
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1923671.html
Copyright © 2011-2022 走看看