`
Fangrn
  • 浏览: 818312 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Ext的DomQuery学习笔记

    博客分类:
  • j2ee
阅读更多

通过各种途径,得知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的银行卡密码?!看下面两个实验:

  
    
    Ext.query讲解 by 司徒正美
    
    
    <%= javascript_include_tag "ext-base"  %>
    <%= javascript_include_tag "ext-all"  %>
    
  
    

Strong

  
    
    Ext.query讲解 by 司徒正美
    
    
    <%= javascript_include_tag "ext-base"  %>
    <%= javascript_include_tag "ext-all"  %>
    
  
    

span1 span2 span3 span4

Strong

看反选选择器

        "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;
    }

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

分享到:
评论

相关推荐

    Ext API详解--笔记

    这篇笔记将深入探讨Ext Js的核心API,涵盖多个关键模块。 1. **Ext.Element**: `Ext.Element`是Ext Js中的基础元素操作类,它封装了对DOM元素的各种操作,如尺寸调整、样式修改、事件处理等。在`EXT核心API详解...

    ext学习文档

    ### EXT学习文档知识点详解 #### 1. EXT简介 EXT是一个功能强大的JavaScript库,用于构建交互式的Web应用程序。它提供了一系列工具和API,使得开发者能够更容易地创建动态且丰富的用户界面。EXT支持多种浏览器,并...

    整理的Ext API详解

    "EXT核心API详解(四)-Ext.DomQuery DomHelper Template.txt"介绍了Ext.DomQuery,这是一个高效的DOM选择器引擎,类似于jQuery的$.selector。DomHelper则是用于创建和操作DOM结构的工具,而Template则是用于动态生成...

    Ext 学习中文手册

    学习利用模板(Templates)的格式化功能 63 正式开始 63 下一步 64 事件处理 64 非常基础的例子 64 处理函数的作用域 64 传递参数 65 类设计 66 对象创建 66 使用构造器函数 66 方法共享 66 表单组件入门 67 表单体 ...

    Ext官方中文教程(可打包下载)

    DomQuery基础 Ext中的事件 简述模板 模板(Templates)的函数 教你创建Ext UI控件 事件的处理 Ext中的继承 Ext的类设计 Ajax通讯 JSON处理方法 函数的原型扩展 组件的使用: Tab标签页 Ext 1.x中的布局 Grid...

    extapi

    "EXT核心API详解(四)-Ext.DomQuery DomHelper Template.txt"介绍了Ext.DomQuery,这是一个强大的CSS选择器引擎,类似于jQuery的选择器,用于高效地选取DOM元素。DomHelper则是一个便捷的DOM元素构建工具,可以生成...

    EXT核心API详解(第一部分)

    EXT核心API是EXT JS库的重要组成部分,它提供了一系列丰富的组件和功能,用于构建富客户端Web应用。...学习EXT核心API的第一部分是掌握EXT JS开发的基础,后续部分会进一步深入到组件、布局、数据绑定等方面。

    EXT核心API详解

    7、Ext.Element类 ………………………… 7 8、Ext.DomQuery类 ………………… 13 9、Ext.DomHelper类 …………………… 14 10、Ext.Template类 …………………… 14 11、Ext.EventManager类 ……………… 15 12、Ext...

    EXT中文手册.pdf

    5. **DOM查询和Ext DomQuery**:Ext框架的DomQuery提供了一种基于CSS选择器的方式来查询DOM节点,这在处理动态生成的DOM结构时非常有用。例如,`Ext.select('p').highlight()`可以选中所有段落元素,并给它们添加...

    Ext2.2 中文手册

    ### Ext2.2 中文手册知识点总结 #### 1. Ext简介 - **定义与特点**:Ext 是一款基于 JavaScript 的开源...以上知识点总结涵盖了 Ext2.2 中文手册中的主要内容,从基本概念到具体实践,为初学者提供了全面的学习指南。

    ext核心api详解(2)

    EXT 核心 API 详解(四) - Ext.DomQuery/DomHelper/Template EXT JS 是一个专注于构建前端用户界面的JavaScript框架,它独立于后台技术,主要利用Ajax技术实现丰富的交互效果。本文将深入探讨EXT JS的核心API,特别...

    Ext+JS高级程序设计.rar

    1.3 DomQuery详解 20 1.4 模板介绍 23 1.5 实用功能 24 1.6 定时执行代码 25 1.7 本章小结 26 第2章 Ext Core实例系统设计 27 2.1 需求分析 27 2.2 系统设计 28 2.3 功能结构图 29 2.4 开发与运行环境 31 2.5 数据库...

    ext-word文档

    ### EXT中文手册与ExtJS基础知识概览 #### EXT简介 ExtJS是一款强大的开源JavaScript框架,专为Web应用程序的开发而设计。它提供了丰富的用户界面组件和工具,支持开发者快速构建高性能、可定制化的Web应用。ExtJS...

    Ext Js权威指南(.zip.001

    1.1 学习ext js必需的基础知识 / 1 1.2 json概述 / 3 1.2.1 认识json / 3 1.2.2 json的结构 / 3 1.2.3 json的例子 / 4 1.2.4 在javascript中使用json / 4 1.2.5 在.net中使用json / 8 1.2.6 在java中使用...

    ext自学宝典

    **Ext自学宝典**不仅涵盖了Ext JS的基本概念和入门操作,还深入探讨了核心对象`Ext.Element`和多元素选择工具`DomQuery`的使用。通过本文档的学习,开发者可以快速掌握Ext JS的关键特性,为构建复杂、高性能的Web...

    EXT JS 3.0 Core Class Diagram

    4. `Ext.DomQuery`: 用于高效地选择DOM元素的工具,类似于jQuery的`$`。 - `matchers`, `pseudos`: 匹配规则和伪类选择器。 - `compile()`: 编译查询表达式。 - `filter()`: 过滤元素集合。 - `is()`: 检查元素...

    EXT核心API详解.doc

    8. **Ext.DomQuery类**:快速高效的DOM选择器引擎,类似于jQuery的选择器功能。 9. **Ext.DomHelper类**:用于创建和修改DOM元素的工具类,提供了更简洁的API。 10. **Ext.Template类**:模板类,可以用来生成动态...

Global site tag (gtag.js) - Google Analytics