最近更新: 2009-11-12

JavaScript的類別定義擴充能力

我在試探不同程式語言的中介編程與反射能力系列文章的第三篇PHP的中介編程與反射能力示範中提到 JavaScript 也可以透過 prototype 的操作實現直接擴充類別定義的能力,這種能力在 Ruby 中稱為 open class。本文是為了示範 JavaScript 此能力所做的補充。

本文的範例源碼係延續自JavaScript的中介編程與反射能力示範,除了加上 JavaScript 操作 prototype 擴充類別定義的程式碼外,順便也修改了原本的程式結構,令它可以直接透過建構子所接受的 Hash Table 內容,決定實體的可用資料欄位。不像前一版本顯著列出欄位清單。

/**
 * define a constructor named 'Class'
 */
Class = new Function;

/**
 * foreach
 * @param f  Your iterator: F(key value)
 */
Class.prototype.foreach = function(f) {
    for (var p in this.props) {
        f(p, this.props[p]);
    }
}

/**
 * accessor
 * @param props 要使用預設存取器的屬性列表(hash table).
 */
Class.prototype.accessor = function(props) {
    var p, sourceText;
    for (p in props) {
        sourceText = "  \
            if (v == undefined) \
                return this.props.{XXX}; \
            else    \
                this.props.{XXX} = v;   \
            ";

        this[p] = new Function("v", sourceText.replace(/{XXX}/g, p));
        // Script解譯器會自動將上面的源碼解釋為:
        //  this[p] = function(v) {
        //      if (v == undefined)
        //          return this.props.?
        //      else
        //          this.props.? = v;
        //  }
    }
}

/**
 * 此資料類別直接透過建構子所接受的 Hash Table 內容,決定
 * 實體的可用資料欄位。不像前一版本顯著列出欄位清單。
 * @param args  hash table
 */
function Data(args) {
    if (args == undefined)
        return;

    this.props = {}

    for (var p in args) {
        this.props[p] = args[p];
    }

    this.accessor(this.props);
}
Data.prototype = new Class;
//定義 Data 的原型是 Class 。亦即定義 Data 為 Class 的衍生類


var d1 = new Data({
    id: 1,
    title: 'rock',
    content: 'hello world',
    timestamp: (new Date()).toLocaleString()
});

print("Properties of d1");
d1.foreach( function(name, value){
    print( name + " : " + value );
});

//別種資料結果
var d2 = new Data({
    'id': 1,
    'title': 'rock',
    'create_timestamp': (new Date()).toLocaleString(),
    'update_timestamp': (new Date()).toLocaleString(),
    'table': 'Data',
    'gid': 100
});

print("\n---- Accessor demo ----");
d2.title('javascript'); // setter
print( d2.title() ); //getter

print("\nProperties of d2");
d2.foreach( function(name, value) {
    print( name + " : " + value );
});

print( "---- More ----" );

try {
    d1.sayHello();
}
catch(e) {
    print( " * Sorry, they can not speak.\n" );
}

// 直接操作 Data.prototype ,添加新的定義。
Data.prototype.sayHello = function() {
    print( "Hello, I am " + this.props.title );
}

// 現在它們能說 hello 了

print( " * Now, they can say hello." );
d1.sayHello();
d2.sayHello();

d2.jump = function() {
    print( "I am " + this.title() + ", and I can jump.");
}

try {
    d1.jump();
}
catch(e) {
    print(" * Sorry, " + d1.title() + " can not jump.");
}
d2.jump();

我一開始想讓 d1, d2 向各位說聲 hello ,可惜我忘了加上去,所以系統補抓錯誤後告訴我它們不會說話。Ok, 那我就直接擴充它們的原型 (即 Data.prototype) ,加上說 hello 的能力。現在它們兩個都會說 hello 了。

我再替 d2 加上跳躍的能力,但不替 d1 加上這能力。最後再叫它們兩個跳一次,一如我所預期,只有 d2 跳起來。

這個版本和 PHP的中介編程與反射能力示範 的最後實作範例一樣,都不再明列類別可用的欄位內容,改為允許動態數量的欄位。每個資料個體都可以擁有不同的資料欄位,但我們不必定義更多的類別。

如果我們想為 Data 類別增加更多的行為能力,我們既不需要用到繼承,也不需要回到原始定義處修改。我們需要做的就只是打開 Data.prototype ,直接加上我們想要的行為。毫不拖泥帶水。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/10683081.html