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

我想,我是国内最熟悉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);
原文地址:https://www.cnblogs.com/rubylouvre/p/1923671.html