zoukankan      html  css  js  c++  java
  • Ext的DomQuery学习笔记

    通过各种途径,得知Ext的选择器很不简单,最大的特点就是利用eval即时生成查询函数,让它在一些选择器类型中速度爆快。因此我觉得非常有必要学习一下Ext的这个模块了。

    从最后一行得知,Ext.query方法是Ext.DomQuery.select的别名,那我们就顺着它的思路看呗。

    select方法,我管它为入口函数。

            select : function(path, root, type){
                if(!root || root == document){
                    root = document;
                }
                if(typeof root == "string"){
                    root = document.getElementById(root);
                }
                var paths = path.split(","),//把选择器按并联选择器分解
                	results = [];
                for(var i = 0, len = paths.length; i < len; i++){
                    var p = paths[i].replace(trimRe, "");//移除左右两边的空白节点
                    if(!cache[p]){
                        cache[p] = Ext.DomQuery.compile(p);//把刚编译出来的查询函数放进缓存体中
                        if(!cache[p]){
                            throw p + " is not a valid selector";
                        }
                    }
                    var result = cache[p](root);//把文档对象传进去,获取目标元素
                    if(result && result != document){//如果能获取元素或并不返回我们原来传入的那个文档对象,
                        results = results.concat(result);//就把它并入最终结果中
                    }
                }
                if(paths.length > 1){//去除重复元素
                    return nodup(results);
                }
                return results;
            },
    

    compile方法,它用eval动态生成查询函数的做法确实让人一亮。

            compile : function(path, type){//
                type = type || "select";
               //用于编译的代码
                var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
                	q = path, //选择器
                    mode,
                    lq,
                	tk = Ext.DomQuery.matchers,
                	tklen = tk.length,
                	mm,
                	//取出关系选择器的自符
                    //modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
                	lmode = q.match(modeRe);
                    //如 alert("> .aaa".match(/^(\s?[\/>+~]\s?|\s|$)/))
                    //弹出 > ,>
                if(lmode && lmode[1]){//如果存在 / >  + ~ 这四个选择器,我们将对编译代码与选择器进行些操作
                    //编译代码将增加,如 mode=">"的字段
                    fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
                    //选择器  > .aaa 将变成 .aaa
                    q = q.replace(lmode[1], "");
                }
                //如果选择器被消耗到以断句符“/”开头,那么移除它,把第二行代入path
                //如 "\
                //     h1[title]"
                //的情形
                while(path.substr(0, 1)=="/"){
                    path = path.substr(1);
                }
    
                while(q && lq != q){//如果选择器q不等于undefined或null
                    lq = q;  //tagTokenRe = /^(#)?([\w-\*]+)/,
                    var tm = q.match(tagTokenRe);// 判定其是ID选择器,标签选择器亦或通配符选择器
                    if(type == "select"){
                        if(tm){
                            if(tm[1] == "#"){//如果是ID选择器,
                                fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';
                            }else{           //如果是标签选择器
                                fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';
                            }
                            q = q.replace(tm[0], "");
                        }else if(q.substr(0, 1) != '@'){
                            fn[fn.length] = 'n = getNodes(n, mode, "*");';
                        }
                    }else{
                        if(tm){
                            if(tm[1] == "#"){
                                fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';
                            }else{
                                fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';
                            }
                            q = q.replace(tm[0], "");
                        }
                    }
                    while(!(mm = q.match(modeRe))){
                        var matched = false;
                        for(var j = 0; j < tklen; j++){
                            var t = tk[j];
                            var m = q.match(t.re);//用matchers里面的正则依次匹配选择器,
                            if(m){ //如果通过则把matchers.select里面的{1},{2}这些东西替换为相应的字符
                                fn[fn.length] = t.select.replace(tplRe, function(x, i){
                                                        return m[i];
                                                    });
                                q = q.replace(m[0], "");//移除选择器相应的部分
                                matched = true;//中止循环
                                break;
                            }
                        }
                        // prevent infinite loop on bad selector
                        if(!matched){
                            throw 'Error parsing selector, parsing failed at "' + q + '"';
                        }
                    }
                    if(mm[1]){//添加编译代码,如 mode="~"的字段
                        fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';
                        q = q.replace(mm[1], "");//移除选择器相应的部分
                    }
                }
                fn[fn.length] = "return nodup(n);\n}";//添加移除重复元素的编译代码
                eval(fn.join(""));//连结所有要编译的代码,用eval进行编译,于是当前作用域使增加一个叫f的函数
                return f;//返回f查询函数
            },
    

    f查询函数的生成依赖于一个叫做matchers的数组对象:

            matchers : [{
                    re: /^\.([\w-]+)/,
                    select: 'n = byClassName(n, null, " {1} ");'
                }, {
                    re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
                    select: 'n = byPseudo(n, "{1}", "{2}");'
                },{
                    re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
                    select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
                }, {
                    re: /^#([\w-]+)/,
                    select: 'n = byId(n, null, "{1}");'
                },{
                    re: /^@([\w-]+)/,
                    select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
                }
            ],
    

    在处理ID选择器时,分为两个函数,分别为查找模式(type="select")与过滤模式。个人觉得它好像不可能处理IE中的getElementById的bug。过滤模式用于反选选择器。

        function quickId(ns, mode, root, id){
            if(ns == root){
               var d = root.ownerDocument || root;
               return d.getElementById(id);//IE下有bug
            }
            ns = getNodes(ns, mode, "*");
            return byId(ns, null, id);
        }
        function byId(cs, attr, id){
            if(cs.tagName || cs == document){
                cs = [cs];
            }
            if(!id){
                return cs;
            }
            var r = [], ri = -1;
            for(var i = 0,ci; ci = cs[i]; i++){
                if(ci && ci.id == id){//这里存在问题,因为IE下表单元素的id值不能为"id",见我的博文《IE6的getElementById bug》
                    r[++ri] = ci;
                    return r;
                }
            }
            return r;
        };
    
    

    处理类选择器

      function byClassName(c, a, v){//c为元素,v为className
            if(!v){
                return c;
            }
            var r = [], ri = -1, cn;
            for(var i = 0, ci; ci = c[i]; i++){
                if((' '+ci.className+' ').indexOf(v) != -1){
                    r[++ri] = ci;
                }
            }
            return r;
        };
    

    根据属性选择器筛选元素,不过在精确获取属性时,对于一些特殊属性无法辨识,具体可参见我的选择器query的属性转换列表。

       function byAttribute(cs, attr, value, op, custom){
            var r = [], 
            	ri = -1, 
            	st = custom=="{",
            	f = Ext.DomQuery.operators[op];
            for(var i = 0, ci; ci = cs[i]; i++){
                if(ci.nodeType != 1){
                    continue;
                }
                var a;
                if(st){
                    a = Ext.DomQuery.getStyle(ci, attr);
                }
                else if(attr == "class" || attr == "className"){
                    a = ci.className;
                }else if(attr == "for"){
                    a = ci.htmlFor;
                }else if(attr == "href"){
                    a = ci.getAttribute("href", 2);
                }else{
                    a = ci.getAttribute(attr);
                }
                if((f && f(a, value)) || (!f && a)){
                    r[++ri] = ci;
                }
            }
            return r;
        };
    

    getStyle方法不说了,它是调用style模块的。看看处理属性选择器的操作符,基本与jQuery的处理方式一下,不过Ext出现比较早,应该是抄它的。以前jQuery是用特慢的xpath模拟。

             operators : {
                "=" : function(a, v){
                    return a == v;
                },
                "!=" : function(a, v){
                    return a != v;
                },
                "^=" : function(a, v){
                    return a && a.substr(0, v.length) == v;
                },
                "$=" : function(a, v){
                    return a && a.substr(a.length-v.length) == v;
                },
                "*=" : function(a, v){
                    return a && a.indexOf(v) !== -1;
                },
                "%=" : function(a, v){
                    return (a % v) == 0;
                },
                "|=" : function(a, v){
                    return a && (a == v || a.substr(0, v.length+1) == v+'-');
                },
                "~=" : function(a, v){
                    return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
                }
            },
    

    看byPseudo方法,它只不过是个适配器,根据伪类的类型返回真正的处理函数。

      function byPseudo(cs, name, value){
            return Ext.DomQuery.pseudos[name](cs, value);
        };
    

    pseudos你可以管它做适配器对象,也可以称之为switch Object。嘛,叫什么都一样,它可以帮我们从无限的if...else if....else if 语句中解放出来。Ext运用的设计模式挺多的,这正是企业应用的特征之一,为以后添加新模块留下后路。

            pseudos : {
                "first-child" : function(c){
                    var r = [], ri = -1, n;
                    for(var i = 0, ci; ci = n = c[i]; i++){//要求前面不能再有元素节点
                        while((n = n.previousSibling) && n.nodeType != 1);
                        if(!n){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "last-child" : function(c){
                    var r = [], ri = -1, n;
                    for(var i = 0, ci; ci = n = c[i]; i++){//要求其后不能再有元素节点
                        while((n = n.nextSibling) && n.nodeType != 1);
                        if(!n){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "nth-child" : function(c, a) {
                    var r = [], ri = -1,
                    	m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
                    	f = (m[1] || 1) - 0, l = m[2] - 0;//和jQuery解析表达式的做法如出一辙
                    for(var i = 0, n; n = c[i]; i++){
                        var pn = n.parentNode;
                        if (batch != pn._batch) {//在父节点上添加一个私有属性_batch,
                            var j = 0;
                            for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
                                if(cn.nodeType == 1){
                                   cn.nodeIndex = ++j;
                                }
                            }
                            pn._batch = batch;
                        }
                        if (f == 1) {//f就是an+b中的a,如果f为1时,那么只取出nodeIndex为b的元素节点即可
                            if (l == 0 || n.nodeIndex == l){
                                r[++ri] = n;
                            }
                            //否则使用以下公式取元素(见实验2)
                        } else if ((n.nodeIndex + l) % f == 0){
                            r[++ri] = n;
                        }
                    }
    
                    return r;
                },
    
                "only-child" : function(c){
                    var r = [], ri = -1;;
                    for(var i = 0, ci; ci = c[i]; i++){
                        if(!prev(ci) && !next(ci)){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "empty" : function(c){
                    var r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        var cns = ci.childNodes, j = 0, cn, empty = true;
                        while(cn = cns[j]){
                            ++j;
                            if(cn.nodeType == 1 || cn.nodeType == 3){
                                empty = false;
                                break;
                            }
                        }
                        if(empty){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "contains" : function(c, v){
                    var r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "nodeValue" : function(c, v){
                    var r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        if(ci.firstChild && ci.firstChild.nodeValue == v){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "checked" : function(c){
                    var r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        if(ci.checked == true){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "not" : function(c, ss){
                    return Ext.DomQuery.filter(c, ss, true);
                },
    
                "any" : function(c, selectors){
                    var ss = selectors.split('|'),
                    	r = [], ri = -1, s;
                    for(var i = 0, ci; ci = c[i]; i++){
                        for(var j = 0; s = ss[j]; j++){
                            if(Ext.DomQuery.is(ci, s)){
                                r[++ri] = ci;
                                break;
                            }
                        }
                    }
                    return r;
                },
    
                "odd" : function(c){
                    return this["nth-child"](c, "odd");
                },
    
                "even" : function(c){
                    return this["nth-child"](c, "even");
                },
    
                "nth" : function(c, a){
                    return c[a-1] || [];
                },
    
                "first" : function(c){
                    return c[0] || [];
                },
    
                "last" : function(c){
                    return c[c.length-1] || [];
                },
    
                "has" : function(c, ss){
                    var s = Ext.DomQuery.select,
                    	r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        if(s(ss, ci).length > 0){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "next" : function(c, ss){
                    var is = Ext.DomQuery.is,
                    	r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        var n = next(ci);
                        if(n && is(n, ss)){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                },
    
                "prev" : function(c, ss){
                    var is = Ext.DomQuery.is,
                    	r = [], ri = -1;
                    for(var i = 0, ci; ci = c[i]; i++){
                        var n = prev(ci);
                        if(n && is(n, ss)){
                            r[++ri] = ci;
                        }
                    }
                    return r;
                }
            }
    

    我们看一下"nth-child"模块,里面用到一个batch变量,它也动态生成的,还为元素添加两个私有属性_batch与nodeIndex。batch是从30803开始,这数字有什么深意吗?难道是Jack Slocum的银行卡密码?!看下面两个实验:

    <!doctype html>
    <html dir="ltr" lang="zh-CN">
      <head>
        <meta charset="utf-8"/>
        <title>Ext.query讲解 by 司徒正美</title>
        <style type="text/css">
        </style>
        <%= javascript_include_tag "ext-base"  %>
        <%= javascript_include_tag "ext-all"  %>
        <script type="text/javascript" charset="utf-8">
          window.onload = function(){
            alert(Ext.query("p span:nth-child(2n)"))
            var p = document.getElementsByTagName("p");
            alert(p[0]._batch)//30804
            alert(p[1]._batch)//30804
            alert(p[2]._batch)//30804
            alert(p[3]._batch)//30804
            alert(Ext.query("strong span:nth-child(1)"))
            var strong = document.getElementsByTagName("strong");
            alert(strong[0]._batch)//30805
          }
        </script>
      </head>
      <body>
        <p class="bbb">一<span class="aaa">二</span><span class="ccc">二</span><span class="bbb">二</span></p>
        <p class="aaa">一<span title="aaa">二</span></p>
        <p title="aaa">一<span title="aaa">二</span></p>
        <p id="na">一<span class="ccc">二</span><strong>Strong<span class="aaa">二</span></strong></p>
      </body>
    </html>
    
    <!doctype html>
    <html dir="ltr" lang="zh-CN">
      <head>
        <meta charset="utf-8"/>
        <title>Ext.query讲解 by 司徒正美</title>
        <style type="text/css">
        </style>
        <%= javascript_include_tag "ext-base"  %>
        <%= javascript_include_tag "ext-all"  %>
        <script type="text/javascript" charset="utf-8">
          window.onload = function(){
            var r = Ext.query("p span:nth-child(2n)")
            alert(r[0].innerHTML)//span2
            alert(r[1].innerHTML)//span4
            alert(r[0].nodeIndex)//2
            alert(r[1].nodeIndex)//4
          }
        </script>
      </head>
      <body>
        <p class="bbb"><span class="aaa">span1</span>
          <span class="aaa">span2</span>
          <span class="ccc">span3</span>
          <span class="bbb">span4</span>
        </p>
        <p class="aaa">一<span title="aaa">二</span></p>
        <p title="aaa">一<span title="aaa">二</span></p>
        <p id="na">一<span class="ccc">二</span><strong>Strong<span class="aaa">二</span></strong></p>
      </body>
    </html>
    

    看反选选择器

            "not" : function(c, ss){//c为上次搜索的结果集,ss为:not(***)中括号里面的内容
              return Ext.DomQuery.filter(c, ss, true);
            },
            filter : function(els, ss, nonMatches){
              ss = ss.replace(trimRe, "");//移除左右两边的空白节点
              if(!simpleCache[ss]){//如果缓存体不存在此选择器(Ext的缓存体蛮多的)
                simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");//动态生成一个查询函数
              }
              var result = simpleCache[ss](els);//求取结果
              return nonMatches ? quickDiff(result, els) : result;//如果为true则调用quickDiff方法,否则直接返回结果集
            },
    

    看它如何进行取反,我以前也是用这种技术,就是利用了数学上全集与子集与补集的关系。quickDiff的第一个参数为子集,第二个参数为全集,既然是取反,当然取其补集。过程是在子集的元素节点中设置一个私有属性_diff,然后在全集范围的元素节点内找那些没有被标记,或标记不相同的元素,放进结果集。

        function quickDiff(c1, c2){//子集,全集
            var len1 = c1.length,
            	d = ++key,
            	r = [];
            if(!len1){
                return c2;
            }
            if(isIE && typeof c1[0].selectSingleNode != "undefined"){
                return quickDiffIEXml(c1, c2);
            }        
            for(var i = 0; i < len1; i++){
                c1[i]._qdiff = d;//往子集元素设置一个私有属性_qdiff,起始数为30803
            }        
            for(var i = 0, len = c2.length; i < len; i++){
                if(c2[i]._qdiff != d){//然后在全集范围内找那些没有被标记,或标记不相同的元素,放进结果集
                    r[r.length] = c2[i];
                }
            }
            return r;
        }
    

    不过对于IE的XML则利用setAttribute来标记私有属性,还要筛选后去除这私有属性。

        function quickDiffIEXml(c1, c2){
            var d = ++key,
            	r = [];
            for(var i = 0, len = c1.length; i < len; i++){
                c1[i].setAttribute("_qdiff", d);
            }        
            for(var i = 0, len = c2.length; i < len; i++){
                if(c2[i].getAttribute("_qdiff") != d){
                    r[r.length] = c2[i];
                }
            }
            for(var i = 0, len = c1.length; i < len; i++){
               c1[i].removeAttribute("_qdiff");
            }
            return r;
        }
    

    其去重也差不多是这样的原理。至于其他的代码没有什么值得好学习了……

  • 相关阅读:
    Educational Codeforces Round 86 (Rated for Div. 2) D. Multiple Testcases
    Educational Codeforces Round 86 (Rated for Div. 2) C. Yet Another Counting Problem
    HDU
    HDU
    HDU
    HDU
    Good Bye 2019 C. Make Good (异或的使用)
    Educational Codeforces Round 78 (Rated for Div. 2) C. Berry Jam
    codeforces 909C. Python Indentation
    codeforces1054 C. Candies Distribution
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1659607.html
Copyright © 2011-2022 走看看