無標題文檔

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,今天就先到这里。

YUI 读码日记之 YAHOO.namespace

YUI 提供了类似于 Java 的 命名空间 机制。%BULID%/yahoo/yahoo.js 文件中,YAHOO 基类定义好了以后,第一个定义的方法就是 namespace(在 YUI 2.5 中,第 75 行)。下面是 YUI 手册上的介绍

namespace

static Object namespace ( arguments )
Returns the namespace specified and creates it 
if it doesn't exist

YAHOO.namespace("property.package");
YAHOO.namespace("YAHOO.property.package");

Either of the above would create YAHOO.property, then 
YAHOO.property.package Be careful when naming packages. 
Reserved words may work in some browsers and not others. 
For instance, the following will fail in Safari:

YAHOO.namespace("really.long.nested.namespace");

This fails because "long" is a future reserved word in 
ECMAScript

Parameters:
    arguments <String*> 1-n namespaces to create 

Returns: Object
    A reference to the last namespace object created

下面看下相应的代码。

YAHOO.namespace = function() {
    var a=arguments, o=null, i, j, d;

    // 根据参数的长度循环获取属性或方法名   
    for (i=0; i<a.length; i=i+1) {
        d=a[i].split(".");
        o=YAHOO;

        // 避免定义 YAHOO.YAHOO
        for (j=(d[0] == "YAHOO") ? 1 : 0; j<d.length; j=j+1) {
            // 加入相应的方法或属性
            o[d[j]]=o[d[j]] || {};
            o=o[d[j]];
        }
    }
    
    // 返回 YAHOO 本身
    return o;
};

根据文档,YUI 默认会添加 "util"、"widget"、"example" 三个命名空间,相应的代码如下。

(function() {
    // 添加默认的命名空间
    YAHOO.namespace("util", "widget", "example");

    if ("undefined" !== typeof YAHOO_config) {
        var l=YAHOO_config.listener,ls=YAHOO.env.listeners,unique=true,i;
        if (l) {
            // if YAHOO is loaded multiple times we need to check to see if
            // this is a new config object.  If it is, add the new component
            // load listener to the stack
            for (i=0;i<ls.length;i=i+1) {
                if (ls[i]==l) {
                    unique=false;
                    break;
                }
            }
            if (unique) {
                ls.push(l);
            }
        }
    }
})();

从 namespace 的函数写法可以得知,在 定义 Javascript 类的方法或者属性 也可以使用类似于数组的声明方式。比如

var o = {};
o.method = function () {
    alert("hello, world!");
}
o.method();

就可以写成

var o = {};
o["method"] = function () {
    alert("hello, world!");
}
o.method();

但请小心使用此种特性,毕竟数组操作与类的声明显性的区分,有利于以后的代码维护。

更新,感谢 小马 的指证:

「但请小心使用此种特性,毕竟数组操作与类的声明显性的区分」

    * {} 不是类,是对象
    * Javascript 里的对象就是关联数组
    * 什么时候使用 o.method() ,什么时候使用 o['method']() ,依据实际应用环境

建议结合 《JavaScript 权威指南》读 YUI,会让你读出更深刻的知识。

YUI 读码日记之 YAHOO.env.ua

正如 YAHOO.env.ua 字面所指(ua 就是 user-agent 的意思),这篇文章主要分析 YUI 如何检测浏览器的类型与版本。有关 YAHOO.en.ua 的文档 在这里说明

目前为止,检测浏览器与版本主要有两大主流的做法。其一,就是分析浏览器提供的 user-agent 字符串;其二,就是根据其功能判别。YUI 使用第一种判断方式,也就是分析 user-agent 字符串。

下面是 YUI 相应的代码实现,经过本人精简以后并加了点注释,这段程序在 %BUILD%/yahoo/yahoo.js 中。

YAHOO.env.ua = function() {
    var o={ie:0, opera:0, gecko:0, webkit:0, mobile: null};
    var ua=navigator.userAgent, m;

    // 是否为基于 KHTML 引擎的浏览器,比如 Konqueror 。
    if ((/KHTML/).test(ua)) {
        o.webkit=1;
    }

    // 检测 Apple 系列的浏览器,包括移动版本
    m=ua.match(/AppleWebKit\/([^\s]*)/);
    if (m&&m[1]) {
        o.webkit=parseFloat(m[1]);

        // YUI 是我见过的唯一一个检测移动浏览器的框架 :^)
        if (/ Mobile\//.test(ua)) {
            o.mobile = "Apple"; // iPhone or iPod Touch
        } else {
            m=ua.match(/NokiaN[^\/]*/);
            if (m) {
                o.mobile = m[0]; // Nokia N-series, ex: NokiaN95
            }
        }
    }

    if (!o.webkit) { // 检测其他浏览器
        // 检测 Opera
        m=ua.match(/Opera[\s\/]([^\s]*)/);
        if (m&&m[1]) {
            o.opera=parseFloat(m[1]);
            // 是否为 Opera 移动版本
            m=ua.match(/Opera Mini[^;]*/);
            if (m) {
                o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
            }
        } else {
            // 检测 IE 和 Mozilla
            m=ua.match(/MSIE\s([^;]*)/);
            if (m&&m[1]) {
                o.ie=parseFloat(m[1]);
            } else {
                m=ua.match(/Gecko\/([^\s]*)/);
                if (m) {
                    o.gecko=1;
                    m=ua.match(/rv:([^\s\)]*)/);
                    if (m&&m[1]) {
                        o.gecko=parseFloat(m[1]);
                    }
                }
            }
        }
    }
    
    return o;
}();

根据代码的流程,我们可以很容易理解 YUI 判断浏览器的逻辑。而我在之前也分析过 jQuery 在这方面是如何做的 ,可以对比参照下。

从代码量上考虑,jQuery 的代码是非常的精简的,而 YUI 「看起来」更像是我们平常一般的写法:保守、但是有效。而我个人更倾向于 jQuery 的写法,当然 YUI 这这样的判断流程自然也是有它的道理的。

请允许我多事一下,下面是本人根据 jQuery 的代码「重写」 YUI 的 YAHOO.env.ua ,让大家见笑了。

YAHOO.env.ua = function() {
    var ua = navigator.userAgent.toLowerCase();
    var version = (ua.match( /.+(?:rv|it|ra|ie)/: / ) || [])[1];
    return {
        webkit: /webkit/.test(ua) ? version : 0,
        opera: /opera/.test(ua) ? version : 0,
        ie: /msie/.test(ua) && !/opera/.test(ua) ? version : 0,
        gecko: /mozilla/.test(ua)&&!/(compatible|webkit)/.test(ua) ? version : 0,
        mobile: / mobile//.test(ua) || ua.match(/nokian1*/) || 
                                       ua.match(/opera mini2*/) ? version : 0
    };
}();

  1. /
  2. ;

YUI 读码日记之 YAHOO.util.later

由于项目的需要,不得不重新认识一个 Javascript 代码框架, YUI 。在本人印象中,与 jQuery 的「 作用链 」处理方式不同,YUI 更像是 Java 的处理方式 - 由大堆的类封装,并提供调用。

接下来的日子,我会抽空将 YUI 的代码阅读一遍,并作一些摘录。这些笔记就形成了一系列没有「创意的文章」,我称之为「YUI 读码日记」。

首先,开始的是 YAHOO 这个基类。YUI 框架的功能都是以 YAHOO 为基类封装而成,因此 %BUILD%/yahoo/yahoo.js 是理解此框架代码的重头戏。

在阅读代码的过程中,本人对于 YAHOO.lang.later 机制比较感兴趣,它提供的功能是延时运行某个函数(或者是类的方法)。下面是它的介绍(具体的 YAHOO.lang 文档可以从 这里获得 )。

later

void later ( when , o , fn , data , periodic )

Executes the supplied function in the context of the supplied 
object 'when' milliseconds later. Executes the function a single
 time unless periodic is set to true.

Parameters:

    when <int> the number of milliseconds to wait until the fn
 is executed 

    o <object> the context object 

    fn <Function|String> the function to execute or the name 
of the method in the 'o' object to execute 

    data <object> [Array] data that is provided to the function.
 This accepts either a single item or an array. If an array is 
provided, the function is executed with one parameter for each 
array item. If you need to pass a single array parameter, it 
needs to be wrapped in an array [myarray]

    periodic <boolean> if true, executes continuously at supplied 
interval until canceled 

Returns: void
    a timer object. Call the cancel() method on this object to 
stop the timer.

程序员似乎更喜欢看代码去理解上述的功能,代码如下(注, later 是 YAHOO.lang 的一个方法)。

later: function(when, o, fn, data, periodic) {
    // 初始化变量
    when = when || 0; 
    o = o || {};
    var m=fn, d=data, f, r;

    // 如果仅仅提供了 fn 的函数名称,判断 fn 是否
    // 为 o 的一个方法。
    if (YAHOO.lang.isString(fn)) {
        m = o[fn];
    }

    if (!m) {
        throw new TypeError("method undefined");
    }
    
    // 将 data 转换成数据,并供 apply 调用
    if (!YAHOO.lang.isArray(d)) {
        d = [data];
    }

    f = function() {
        m.apply(o, d);
    };

    // 判断是否重复运行
    r = (periodic) ? setInterval(f, when) : setTimeout(f, when);

    // 返回 timer 类,可以使用 cancel 方法取消该定时器
    return {
        interval: periodic,
        cancel: function() {
            if (this.interval) {
                clearInterval(r);
            } else {
                clearTimeout(r);
            }
        }
    };

可以看得出,YAHOO.lang.later 写得非常的明了,并且很容易理解。最后,有关 setTimeout 与闭包的相关应用,可以 参看这里

我的照片

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

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

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

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

分类

搜索

文章