Ext源码解析:2, DomQuery.js

阅读更多
from http://www.beyondrails.com/blogs/19/edit

在 Extjs Introduction中提到:
引用

DomQuery is 2~3 times faster than jQuery/dojo/Mootools, Prototype is the most slowest one!

Speed Test测试页面: http://extjs.com/playpen/slickspeed/

Ext的DomQuery为啥这么快呢?
一是因为DomQuery的byId/byTag/byClassName/byAttribute/byPseudo等基本查询方法实现的比较好
二是因为DomQuery良好的结构和模块设计
三是因为DomQuery有一个查询缓存

DomQuery的Dom查询器分四种类型:
1,Element Selector
2,Attribute Selector
3,Pseudo Class Selector
4,CSS Value Selector

Ext的查询方法是Ext.query(String selector, [Node root]) : Array
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(",");
    var 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;
}

可以看到,Ext对于selector做了一个cache,缓存结果为Ext.DomQuery.compile方法返回的一个function
返回的function接收一个参数root来指示从那个Dom元素开始查询
compile : function(path, type){
    type = type || "select";

    var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"];
    var q = path, mode, lq;
    var tk = Ext.DomQuery.matchers;
    var tklen = tk.length;
    var mm;

    // accept leading mode switch
    var lmode = q.match(modeRe);
    if(lmode && lmode[1]){
        fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
        q = q.replace(lmode[1], "");
    }
    // strip leading slashes
    while(path.substr(0, 1)=="/"){
        path = path.substr(1);
    }

    while(q && lq != q){
        lq = q;
        var tm = q.match(tagTokenRe);
        if(type == "select"){
            if(tm){
                if(tm[1] == "#"){
                    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);
                if(m){
                    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]){
            fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';
            q = q.replace(mm[1], "");
        }
    }
    fn[fn.length] = "return nodup(n);\n}";
    eval(fn.join(""));
    return f;
}

而compile方法会使用正则表达式匹配selector,然后分别去选择调用quickId/getNodes/byId/byTag/byClassName等特定查询模式的实现function
正则表达式匹配selector:
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}")}};'
    }
]

看看quickId/byId的实现:
function quickId(ns, mode, root, id){
    if(ns == root){
       var d = root.ownerDocument || root;
       return d.getElementById(id);
    }
    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){
            r[++ri] = ci;
            return r;
        }
    }
    return r;
};

如果是在默认的document下查找一个指定id的元素,则直接调用document.getElementById
否则用getNodes得到所有的子Dom元素,再用byId来匹配Id,第一个匹配上的返回

操作符匹配:
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;
    }
}

pseudo class匹配:
first-child
last-child
nth-child
only-child
empty
contains
nodeValue
checked
not
any
odd
even
nth
first
last
has
next
prev

你可能感兴趣的:(EXT,正则表达式,jQuery,Cache,Mootools)