今天要用js實現一些用戶端功能,考慮到商務邏輯,使用OO的開發方式會很方便,於是認真查看了相關的幾篇文章,有一些心得體會。
首先是定義類。js中定義類是使用function,執行個體化使用new操作符:
function class1() {
this.a = 'class1';
this.m1 = function() {
alert('class1.m1');
}
}
class1.prototype.m2 = function() {
alert('class1.m2');
}
var c1 = new class1();
在js中,Function和Object是兩個最基礎的類,js中的任何對象、執行個體、函數都同時是Function和Object的執行個體,這可以用instanceof來驗證,這一點有特殊的作用,後面的一些魔法全靠這一點支撐。
function有兩種用法,一種如上,是常用的形式;另一種是運行時動態建立,如
var add = new Function("x", "y", "return (x+y)");
建立的結果是一個Function對象。這可以解釋我之前看到的“奇怪”的用法。如上,this.m1 = function() {...}的用法中,實際上後面的function句自動產生了一個匿名的function對象,然後賦值給m1屬性,隨即class1就具有了m1方法。之後又用class1.prototype.m2 = function(){...}句定義了m2方法,實際上也是得到了匿名的function對象,然後賦值給m2屬性。當然,這兩種定義方法的形式是由區別的,後面會講到。既然得到的是一個對象,可以使用變數指代,當然就可以作為函數調用的參數了,也就可以輕鬆而自然的實現函數回調了。這比C#之類的語言實現回調更方便自然。
這裡再多囉嗦一句,js類在定義的時候可以定義屬性,如樣本中的a屬性。除此之外,執行個體的屬性是可以動態添加的。如果使用c1.b = 'b',則c1自動具有了屬性b。這種動態添加的屬性是不會擴散到其他執行個體的,即如果有另一個執行個體c2,則c2仍然只有屬性a而無屬性b。
如上樣本,任何類(class1)的函數有兩種實現方式,一種的內聯的方式,即在定義function的時候,將方法寫在body中;另一種是使用prototype,可以在任何地方定義。對於類的執行個體(class1的執行個體c1),同時擁有這兩種方式定義的方法。因為內聯定義的優先順序高,在執行個體調用方法時,首先尋找內聯定義,然後轉到prototype的定義中尋找。因此,prototype定義的方法會被內聯定義覆蓋掉。如果真的發生了方法覆蓋,要想使用prototype定義的方法,至少有兩種方法(以下假設class1中使用同時使用了上述兩種形式定義了方法m1)。
第一種是使用delete,首先delete掉m1方法,根據優先順序,自然delete掉的是內聯的m1方法。什麼,不明白為什麼還能delete掉方法?首先。js是動態語言;其次,記住方法也只是一個function執行個體,在class1中只是一個特殊屬性而已。
var c1 = new class1();
delete c1.m1; //去掉內聯的m1方法
c1.m1(); //調用prototype的m1方法
第二種是使用apply方法。至於apply的使用方法,可以查看js手冊。
var c1 = new class1();
class1.prototype.m.apply(c1); //使用prototype中定義的m方法代替c1的m方法
用new執行個體化的對象仍然同時是Function對象和Object對象,包括這兩者的所有執行個體屬性和方法。這裡不是很好理解,想想看,我們定義的類同時是Function對象和Object對象的執行個體,然後執行個體化後,執行個體也同時是Function對象和Object對象的執行個體,有沒有一點像父親和兒子的爸爸是同一個人?呵呵,有點噁心了,還希望高手出來詳細剖析一下Function對象和Object對象以及js裡面的類型關係。
這裡把js和ruby做個比較。js和ruby都有一個預設的全域對象,js中是Global對象,所用定義的全域變數和函數都是Global的成員。從這一點看,js也有一點oo的感覺(至少是形式上):-)。ruby中,同時存在class變數和執行個體變數,js也是。這種設計是指令碼語言的優勢,可以在運行時隨時改變class的定義,非常靈活和強大。同時兩種指令碼中的所有類型都不是封閉的,而是開放的,使用者可以給語言內建的所有類型添加額外的屬性和方法。
另外閑扯一下有趣的話題:js的反射。js中任何對象(function和object)都可以使用如下方式遍曆所有成員:
var info = typeof(obj)+'\n';
for(p in obj) {
info += p + '\n';
}
alert(info);
同樣使用反射,可以這樣調用方法和屬性:
alert(c1['a']);
c1['m1']();