YUI 读码日记之 YAHOO.util.laterFebruary 28, 2008

由于项目的需要,不得不重新认识一个 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 与闭包的相关应用,可以 参看这里

燃烧脑细胞时间February 27, 2008

烧脑细胞时间,请分别说明下述代码的输出。

代码一

var hello = new function() {
    return "hello, world!"
}; 
alert(hello);

代码二

function hello() {
    return "hello, world!";
}
var hello_sample = new hello();
alert(hello_sample);

代码三

var hello = new function() {
    return new String("hello, world!")
};
alert(hello);

代码四

var hello = new function() {
    return function() {
        return 'hello, world!';
    };
};
alert(hello);

代码五

var hello = function() {return "hello, world!"}();
alert(hello);

可以参考 怿飞 Blog 上的 一篇文章

面向对象的 Javascript (继承篇)February 20, 2008

继续我们的 Javascript 面向对象之旅。 上次 已经提到过怎么去声明个 Javascript 类了,这篇主要是说明如何去继承 Javascript 类。

相对于其他语言而言,Javascript 类的机制显得尤其的宽松。这似乎是把双刃剑,使用不当就有可能割伤自己的手指。Javascript 没有严格意义上的抽象类的概念,这就意味着任何的类都可以被实例化。

在开始下面的文章前,我们首先重新认识下上篇我们已经定义好的类。

function Car(sColor) {
    this.color = sColor;
    this.showColor = function() {
        alert(this.color);
    }
}

是的,看起来非常好理解。那么接下来就开始在 Car 上加装点东西使它变成更具体化。

对象冒充

对象冒充(object masquerading)是日常 coding 中最常见的一种类继承的手法。其原理就是,构造函数使用 this 关键字给所有的属性和方法赋值。

因为构造函数只是一个函数,所以可以使基类的构造函数成为继承类的一个方法。这样,继承类就能收到基类构造函数中定义的属性和方法。例如上述的 Car 基类就可以这样继承。

function Trunk(sColor, sName) {
    this.Car = Car;
    this.Car(sColor);
    delete this.Car;

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    }
}

在这里要需要注意一点,新属性和新的方法比必须在删除了新方法的代码后定义,否则可能会覆盖继承类的相关属性和方法。

深刻理解 Javascript 的 this 和函数机制是非常有用的。依次类推,我们就可以给让一个类继承多个基类(多重继承)。

call() 方法

了解对象冒充的机制以后使用 call() 方法就非常容易理解了。从代码量上理解,它仅仅是将上述方法的三条语句合并成一条(是的,没有任何的不同)。

function Trunk(sColor, sName) {
    //this.Car = Car;
    //this.Car(sColor);
    //delete this.Car;
    Car.call(this, sColor);

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    }
}

有关如何使用 call() 的其他信息,可以访问 这里获得

apply() 方法

相对于 call() 方法,自然对于 apply() 方法也同样能够运行。只不过其参数是以数组的形式传递,请参考下面的代码。

function Trunk(sColor, sName) {
    //this.Car = Car;
    //this.Car(sColor);
    //delete this.Car;
    Car.apply(this, [sColor]);

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    }
}

当然,继承类的参数顺序与基类的参数顺序完全一致才可以传递参数对象。即便只有一个参数,也需要使用数组传递(比如上述的例子)。

原型链

上篇文章 已经介绍了使用原型方式定义类,而原型链扩展了这种方式,这种继承的方式非常的有趣。其实 prototype 对象是个模板,要实例化的对象都以这个模板为基础(我个人将这里的「模板」理解为指针)。

总而言之,prototype 对象的任何属性和方法都会被传递给那个类的所有实例,而原型链就是利用这种功能来实现继承机制,且看下面的代码实现。

function Car() {
}
Car.prototype.color = "red";
Car.prototype.showColor = function () {
    alert(this.color);
}

Trunk.prototype = new Car;

从代码中可以得知,调用 Car 的构造函数时,没有给它传递任何的参数。这在原型链中视标准的做法,要确保构造函数没有任何的参数。

原型链的弊端就是不支持多重继承,同时与艳星连会用另一类型的对象重写基类的 prototype 属性(就犹如上篇类的声明说所言的道理一样,所以我个人将其理解为指针)。

混合模式

同类的声明机制一样,两种方法(对象冒充和原型链)也可以结合在一起实现继承机制。比如下面是一个非常「完美的」类声明。

function Car(sColor) {
    this.color = sColor;
    if (typeof Car._initialized == "undefined") {
        Car.prototype.showColor = function() {
            alert(this.color);
        };
        Car._initialized = true;
    }
}

要继承这个类必须使用两种方式同时使用,应为类的声明中也使用了两种方式。

function Trunk(sColor, sName) {
    Car.call(this, sColor);
    this.name = sName;
}

Trunk.prototype = new Car();
Trunk.prototype.showName = function () {
    alert(this.showName);
}

由于混合方式使用了原型链,所以 instanceof 运算符仍然能够正确的运行。

理论方面的东西就到这里为止,接下来计划说点实际的东西。不过还是调下大家的胃口,我打算安排在下一篇文章中说明,敬请期待。

未完待续

Yahoo 统计