無標題文檔

YUI 读码日记之 YAHOO.util.Dom - Part.2

继续 在 YAHOO.util.Dom 中徘徊。由于 YAHOO.util.Dom 多次调用 batch 方法,所以先看看这个函数是怎么写的。有关 batch 的用法,可以参见 这里 ,相关的代码如下

batch: function(el, method, o, override) {
    // 让 el 始终为 HTMLElement
    el = (el && (el.tagName || el.item)) ? el : Y.Dom.get(el); 

    if (!el || !method) {
        return false;
    }
    
    //  确定返回的对象
    var scope = (override) ? o : window;
    
    // 看起来是个 HTMLElement 或者不是 Array
    if (el.tagName || el.length === undefined) {
        return method.call(scope, el, o);
    } 

    var collection = []; 
    for (var i = 0, len = el.length; i < len; ++i) {
        collection[collection.length] = method.call(scope, el[i], o);
    }
    
    return collection;
},
小马补充

batch 是 YUI Dom 库的核心之一。它最大的意义在于,它让 Dom 库的其他大多方法
的第一个参数可以是一个 id / 元素对象 或 一组 id/元素对象,减少了循环的使用。

在这里可以找到 call 与 apply 的用法 。在了解了 batch 以后,下来看 YUI.util.Dom 是怎么使用这一方法的,一口气看两个函数

getStyle: function(el, property) {
    // toCamel 函数后面介绍
    property = toCamel(property);
    
    // 获取节点的样式
    var f = function(element) {
        return getStyle(element, property);
    };
    
    return Y.Dom.batch(el, f, Y.Dom, true);
},
setStyle: function(el, property, val) {
    property = toCamel(property);
    
    // 设置节点的样式
    var f = function(element) {
        setStyle(element, property, val);        
    };
    
    Y.Dom.batch(el, f, Y.Dom, true);
},

有关这两个函数的具体用法,可以看下 相关的文档 。其实从参数上就很容易理解怎么使用。看上面的两个函数有利于理解 YAHOO.util.Dom.batch 的调用方式。

接下来,粗略看下 getXY

getXY: function(el) {
    var f = function(el) {
        // 确定元素是否「肉眼可见」
        if ( (el.parentNode === null || el.offsetParent === null ||
                this.getStyle(el, 'display') == 'none') && 
                            el != el.ownerDocument.body) {
            return false;
        }
        
        return getXY(el);
    };
    
    return Y.Dom.batch(el, f, Y.Dom, true);
},

getX 与 getY 方法也是调用此函数,只是获取返回值的数组元素不一样。由于浏览器的兼容问题,提供给用户的 YAHOO.util.Dom.getXY 也仅仅是判断变量以后,再扔给最为复杂的内部 getXY 函数。

OK,留下太多的「悬念」了,下一期着重将它们解决。

YUI 读码日记之 YAHOO.lang.is*

在 YUI 框架的 %BULID%/yahoo/yahoo.js 中,包含了一系列的变量类型检测方法,它们被分装成 YAHOO.lang.is* 。其中,这些函数的大部分封装都是 typeof 操作符的封装,我个人比较敢兴趣的事 isArray 与 isValue 的两个函数。

YAHOO.lang = YAHOO.lang || {
    isArray: function(o) { 
        if (o) {
           var l = YAHOO.lang;
           // 如果该对象有 length 这个属性,同时支持 splice 方法,
           // 那么就认为它为数组。
           return l.isNumber(o.length) && l.isFunction(o.splice);
        }
        return false;
    },

    isBoolean: function(o) {
        return typeof o === 'boolean';
    },

    isFunction: function(o) {
        return typeof o === 'function';
    },
        
    isNull: function(o) {
        return o === null;
    },
        
    isNumber: function(o) {
        return typeof o === 'number' && isFinite(o);
    },
      
    isObject: function(o) {
        return (o && (typeof o === 'object' || 
                            YAHOO.lang.isFunction(o))) || false;
    },
        
    isString: function(o) {
        return typeof o === 'string';
    },
        
    isUndefined: function(o) {
        return typeof o === 'undefined';
    },
    
    //...

    isValue: function(o) {
        // Infinity fails
        // return (o || o === false || o === 0 || o === '');
        var l = YAHOO.lang;
        return (l.isObject(o) || l.isString(o) || 
                           l.isNumber(o) || l.isBoolean(o));
    }
};

…… 复制粘贴分割线 ……

据悉 ,在 YUI 2.2.0 版本以前,YAHOO.lang.isArray 是这样写的。

isArray: function(obj) { 
    // safari 有 bug,只好处理字符串
    if (obj && obj.constructor && 
                 obj.constructor.toString().indexOf('Array') > -1) {  
        return true;  
    } else {
        return YAHOO.lang.isObject(obj) && obj.constructor == Array;  
    }  
},

而这样的判断数组类型是有缺陷的,比如下面的代码

function myArray() {
   this.name = 'name';  
}  
var o2 = new myArray();  
alert(YAHOO.util.isArray(o2));  // 弹出true  
// 因为 obj.constructor.toString() 中包含 myArray 字样,所以返回true  
  
function Obj() {  
    this.name = 'name';  
}  
var o = new Obj();  
o.constructor = Array;
alert(YAHOO.util.isArray(o));  // 弹出 true  
// 因为在 JavaScript 里,constructor 也是属性
// 可以动态指定,所以返回 true

因此,在 YUI 的后续版本,YAHOO.lang.isArray 被修改成了目前的这个样子

isArray: function(o) { 
    if (o) {
       var l = YAHOO.lang;
       // 如果该对象有 length 这个属性,同时支持 splice 方法,
       // 那么就认为它为数组。
       return l.isNumber(o.length) && l.isFunction(o.splice);
    }
    return false;
},

新的实现用了另外的思路:如果该对象有 length 这个属性,同时支持 splice 方法,那么就认为它为数组。当然,它依然有漏洞,我们仍然可以创建一个对象,使其拥有 length 属性和 splice 方法。但我觉得现在的实现更为合理,因为一来可能性不大,二来避免了诡异的浏览器的 BUG 。

再看 YUI 2.3.0 后引入的 YAHOO.lang.isValue,其实就是判断参数是否是一个有意义的值,只要参数不是 null/undefined/NaN,那么都返回 true。(注意这和一般的判断真假的不同就是, 0/false/''(空字符串) 这些都算是有效的值),所以 YAHOO.lang.isValue 非常适合用来判断表单域的值是否为有效值。

YUI 读码日记之 YAHOO.util.Dom - Part.1

DOM 操作是学习 Javascript 很重点的一块。YUI 提供了丰富的 DOM 操作接口,它们定义在 %BUILD%/dom/dom.js 中(封装成 YAHOO.util.Dom)。由于 DOM 操作比较重要,我计划将其分成几个 Part 分析。

先绕开头部很多的 if...else (其实就是定义 toCamel 与 getStyle 两个函数) - 由于浏览器的实现不统一,才造成如此麻烦的情况,回头可以聊聊这些代码。

下面我们在 YAHOO.util.Dom 类中看看有哪些宝藏。目前 思想已经逐步的分裂 ,我看见个函数说个函数。

// 基本上可以认为是 document.getElementById 的翻版
get: function(el) {
    // 如果已经是 HTMLElement ,那么就直接返回
    if (el && (el.nodeType || el.item)) {
        return el;
    }
    
    // 如果是字符串,那么就返回有这个 ID 的 Element
    if (YAHOO.lang.isString(el) || !el) {
        return document.getElementById(el);
    }
    
    // 看起来是个数组,循环调用自身,获取 Eelement
    if (el.length !== undefined) {
        var c = [];
        for (var i = 0, len = el.length; i < len; ++i) {
            c[c.length] = Y.Dom.get(el[i]);
        }
        
        return c;
    }

    return el;
},

这段代码写得非常的精妙。坦白的说,上述代码中的循环体,如果不加思索的话,恐怕就会写成

for (var i = 0, len = el.length; i < len; ++i) {
    c[c.length] = document.getElementById(el[i]);
}

虽然也能正常工作,但是前面的判断就失去了意义。

继续走马观花,现在看看 getElementsByClassName 的内部机制。有关 getElementsByClassName 的详细调用,可以参看 YUI 文档

getElementsByClassName: function(className, tag, root, apply) {
    // 获取 tag 标签,默认为所有(「*」)
    tag = tag || '*';
    // 指定跟节点名
    root = (root) ? Y.Dom.get(root) : null || document; 
    if (!root) {
        return [];
    }
    
    // 初始化节点信息
    var nodes = [],
        elements = root.getElementsByTagName(tag),
        re = getClassRegEx(className);

    // 滤掉不符合规则的节点
    for (var i = 0, len = elements.length; i < len; ++i) {
        if ( re.test(elements[i].className) ) {
            // 你一定很奇怪为什么用 nodes.length 而不是用 i
            // 仔细考虑下 :^)
            nodes[nodes.length] = elements[i];
            // 执行回调函数
            if (apply) {
                apply.call(elements[i], elements[i]);
            }
        }
    }
    
    return nodes;
},

教科书式的 DOM 节点获取和过滤,初始化数据以及操作数据都显得非常的严谨而且正规,YUI 的代码让我有几分的「安全感」。类似的,再来一个 getElementsBy 函数 ,相应代码如下

getElementsBy: function(method, tag, root, apply) {
    // 与上述函数相同,略
    tag = tag || '*';
    root = (root) ? Y.Dom.get(root) : null || document; 

    if (!root) {
        return [];
    }

    var nodes = [],
        elements = root.getElementsByTagName(tag);
    
    for (var i = 0, len = elements.length; i < len; ++i) {
        // 根据自定义函数返回值判断节点的属性
        if ( method(elements[i]) ) {
            nodes[nodes.length] = elements[i];
            if (apply) {
                apply(elements[i]);
            }
        }
    }
    
    return nodes;
},

OK,今天就先到这里。

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 要知道作为码农取名是件很难的事情,所以不想在取名这事情上太费心思。

作为八零后,自认为还仅存点点可能不怎么被理解的幽默感,以及对平淡生活的追求和向往。 为了避免不必要的麻烦,声明本站所输出的内容以及观点仅代表个人,不代表自己所服务公司或组织的任何立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

分类

搜索

文章