代码改变世界

淘宝Kissy框架分析【八】

2010-06-20 19:18  BlueDream  阅读(1394)  评论(0编辑  收藏  举报

这一节将进行KISSY的选择器 selector.js分析.

【程序源码】

J1616.add('selector', function(J, undefined) {
    var doc = document,
        DOM = J.DOM,
        SPACE = ' ',
        ANY = '*',
        REG_ID = /^#[\w-]+$/,
        REG_QUERY = /^(?:#([\w-]+))?\s*([\w-]+|\*)?\.?([\w-]+)?$/;

    function query(selector, context) {
        var match, t, ret = [], id, tag, cls, i, len;
        // #id
        // tag
        // .cls
        // #id tag
        // #id .cls
        // tag.cls
        // #id tag.cls
        // #id.cls
        if (J.isString(selector)) {
            selector = J.trim(selector);

            // 优先处理selector 为 #id 的情况
            if (REG_ID.test(selector)) {
                t = getElementById(selector.slice(1));
                if (t) ret = [t]; // #id无效时 返回空数组 
            } 
            // selector为其他6种
            else if ((match = REG_QUERY.exec(selector))) {
                id = match[1];
                tag = match[2];
                cls = match[3];
                // $('#id', context[如果前面含有ID选择器,则无需限定环境,直接ID就是最直接的: 否则限定])
                if ((context = id ? getElementById(id) : tuneContext(context))) {
                    
                    // #id .cls | #id tag.cls | .cls | tag.cls
                    if (cls) {
                        if (!id || selector.indexOf(SPACE) !== -1) { // 排除 #id.cls
                            ret = getElementsByClassName(cls, tag, context);
                        } 
                        // #id.cls
                        else {
                            t = getElementById(id);
                            if (t && DOM.hasClass(t, cls)) {
                                ret = [t];
                            } 
                        }
                    }
                    
                    // #id tag | tag
                    else if (tag) {
                        ret = getElementsByTagName(context, tag);
                    } 
                } 
            }
            
            // 分组选择器
            else if (selector.indexOf(',') > -1) {
                if (doc.querySelectorAll) {
                    ret = doc.querySelectorAll(selector);
                } else {
                    var parts = selector.split(','), r = [];
                    for (i = 0, len = parts.length; i < len; ++i) {
                        r = r.concat(query(parts[i]), context);
                    }
                    ret = uniqueSort(r);
                }
            } 

            // 采用外部选择器
            else if (J.ExternalSelector) {
                return J.ExternalSelector(selector, context);
            } 

            // 依旧不支持, 抛异常
            else {
                error(selector);
            }
        }
        
        // 传入的Selector是Node
        else if (selector && selector.nodeType) {
            ret = [selector];
        } 
        
        // 传入的Selector是NodeList或Array
        else if (selector && (selector.item || J.isArray(selector))) {
            ret = selector;
        } 
        
        // 传入的 selector是其他值, 返回空数组

        // 将NodeList转为普通数组
        if (ret.item) {
            ret = J.makeArray(ret);
        } 
        return ret;
    }

    // 调整 context 为合理值
    function tuneContext(context) {
        if (context === undefined) {
            context = doc;
        } 

        else if (J.isString(context) && REG_ID.test(context)) {
            context = getElementById(context.slice(1));
        } 

        else if (context && context.nodeType !== 1 && context.nodeType !== 9) {
            context = null;
        } 
        return context;
    }

    // query #id
    function getElementById(id) {
        return doc.getElementById(id);
    }

    // query tag
    function getElementsByTagName(el, tag) {
        return el.getElementsByTagName(tag);
    }

    // 如果byTagName(el, '*') IE下会连注释节点一起筛选出来
    // 下面用特性检测 将getElementsByTagName覆盖

    (function() {
        var div = doc.createElement('div');
        div.appendChild(doc.createComment(''));

        if (div.getElementsByTagName(ANY).length > 0) {
            getElementsByTagName = function(el, tag) {
                var ret = el.getElementsByTagName(tag);
                if (tag === ANY) {
                    var t = [], i = 0, j = 0, node;
                    while ((node = ret[i++])) {
                        // filter
                        if (node.nodeType === 1) {
                            t[j++] = node
                        } 
                    }
                    ret = t;
                }
                return ret;
            };
        } 
    })();

    // query .cls
    function getElementsByClassName(cls, tag, context) {
        var els = context.getElementsByClassName(cls),
            ret = els, i = 0, j = 0, len = els.length, el;

        if (tag && tag !== ANY) {
            ret = [];
            tag = tag.toUpperCase();
            for (; i < len; ++i) {
                el = els[i];
                if (el.tagName === tag) {
                    ret[j++] = el
                } 
            }
        } 
        return ret;
    }

    if (!doc.getElementsByClassName) {
        // 降级使用 querySelectorAll
        if (doc.querySelectorAll) {
            getElementsByClassName = function(cls, tag, context) {
                return context.querySelectorAll((tag ? tag : '') + '.' + cls);
            }
        } 
        // 降级到普通方法
        else {
            getElementsByClassName = function(cls, tag, context) {
                var els = context.getElementsByTagName(tag || ANY),
                    ret = [], i = 0, j = 0, len = els.length, el, t;

                cls = SPACE + cls + SPACE;
                for (; i < len; ++i) {
                    el = els[i];
                    t = el.className;
                    if (t && (SPACE + t + SPACE).indexOf(cls) > -1) {
                        ret[j++] = el;
                    } 
                }
                return ret;
            }
        }
    } 

    // 对于分组选择器, 需要进行去重和排序
    function uniqueSort(results) {
        var hasDuplicate = false;

        // 按照DOM位置排序
        results.sort(function(a, b) {
            var ret = a.sourceIndex - b.sourceIndex;
            if (ret === 0) {
                hasDuplicate = true;
            } 
            return ret;
        });

        if (hasDuplicate) {
            for (var i = 1; i < results.length; i++) {
                if (results[i] === results[i - 1]) {
                    results.splice(i--, i);
                } 
            }
        } 

        return results;
    }

    // throw exception
    function error(msg) {
        J.error('Unsupport selector: ' + msg);
    }

    J.query = query;
    J.get = function(selector, context) {
        return query(selector, context)[0] || null;
    };

    J.mix(DOM, {
        query: query,

        get: J.get,

        filter: function(selector, filter) {
            var elems = query(selector), match, tag, cls, ret = [];

            // 默认仅支持最简单的 tag.cls形式
            if (J.isString(filter) && (match = REG_QUERY.exec(filter)) && !match[1]) {
                tag = match[2];
                cls = match[3];
                filter = function(elem) {
                    return !((tag && elem.tagName !== tag.toUpperCase()) || (cls && !DOM.hasClass(elem, cls)));
                }
            } 

            if (J.isFunction(filter)) {
                ret = J.filter(elems, filter);
            } 

            // 其他复杂的filter, 采用外部选择器
            else if (filter && J.ExternalSelector) {
                ret = J.ExternalSelector._filter(selector, filter);
            } 

            // selector为空 或者 不支持
            else {
                error(filter);
            }
            return ret;
        },

        test: function(selector, filter) {
            var elems = query(selector);
            return DOM.filter(elems, filter).length === elems.length;
        }
    });
});