JavaScript中的多態

一、JS的“類”

 
之所以對js的“類”加上引號是因爲他更多是一種思想,就像“面向對象”同樣是一種思想一樣。沒有人很肯定的認爲js是面向對象的,但也沒人能反駁,因爲面向對象的編程思想在js處完全可以實現,但是具體做法又似乎與C#,java等面嚮對象語言略有不同。這些都不重要,我們只有明白一點即可-----js可以使用面向對象的思想編程。
 
js的”類“是一個抽象的含義,與C#,Java不同,他不會有類似class的關鍵詞專門強調“類”。js的“類”的聲明方式與他自己的函數聲明方式相同-----function。
 
如:function a(){}
a便是一個js“類”。
 
類的內部應該有函數纔對(C#中叫“方法”): 
function a(){
	var v=1;
        this.getValue=function(){
         return v;
    }
}

 
getValue是類型a中的一個公共函數。
在外部可以這樣實例化並使用它:
var b=new a();
alert(b.getValue());
彈窗結果爲1.

那既然有個公共函數,那私有函數呢,改造a類:
 function a(){
	var v=1;
	setValue();
	function setValue(){
		v=9;
	}
	this.getValue=function(){
		return v;
	}
}
var b=new a();
alert(b.getValue());
彈窗結果爲9.
 
a類中的setValue便是私有函數,他在外部無法像公共函數getValue那樣調用,他只供a類內部調用。
但是私有函數依然是有用的,像a中的setValue()他改變了局部變量v的值,影響了公共函數getValue()的返回值。
 
剛剛提到局部變量,示例中的v是一個a內部使用的局部變量,他也是私有的,外界無法直接調用。但是依然可以通過設置公共函數調用(如示例的getValue())。
這種做法類似於C#中的屬性(get{return v;})。
我們繼續改造示例:
 function a(){
	var v=1; 
	this.setValue=function (value){
		v=value;
	}
	this.getValue=function(){
		return v;
	}
}
var b=new a();
b.setValue(99);
alert(b.getValue());

這時彈框爲99.
我們通過公共函數setValue改變了v的值,又通過公共函數getValue獲取了v的值。兩者結合便是一個完整對私有變量v的屬性。
 

二、JS的“繼承”

面向對象缺少不了繼承。js繼承的實現方式也是與C#等不同。他有自已的一套實現(或者說“模擬”)方法。

1.prototype實現繼承(原型繼承)

prototype是js中一個特殊之處,每個類型都有一個prototype變量(即便是自定義的類型),他有一個專門的名字叫做“原型”。
 
之所以說原型特殊,是因爲js的一個機制:
當你實例化一個類型,並試圖調用該實例調用一個函數/變量的時候,js會先在該類型本身“查找”指明的函數/變量,若沒有,則會在該類型的原型(prototype)上繼續查找
直到prototype的引用出現死循環或prototype爲空爲止。
 
借用這個特殊性,我們可以實現js特色的繼承,示例:
 
function a(){ 
	function getData(){
		return 100;
	}
	this.getValue=function(){
		return 123;
	}
        this.value=9999;
}	

aa.prototype=new a();//類a的實例是aa的原型
function aa(){
	
}
var b=new aa(); 

alert(b.getValue());//彈框爲123
alert(b.value);//彈框爲9999
//alert(b.getData());//無法繼承非公共函數
 
如上,類a的實例便是aa的原型。
類aa中並沒有getValue函數,但是aa的實例依然可以調用該函數,這便是js的原型繼承。
但是同時我們發現,對於私有的getData函數,aa類時無法繼承的。(這與C#等面向對象編程語言是一致的)
同樣的,這種方式公共變量也可以同時繼承(如示例中的value變量)。
 
原型繼承的缺陷:
1.對一個被繼承的原型,若是修改其中的引用類型屬性的值,其他所有繼承於該原型的類的實例都會受到影響。(有時這是優點,但更多時候是缺陷)
2.只能繼承構造函數不含參的父類。
 
針對第一個缺陷,示例:
 
function a(){ 
	this.value=[100,200];
}	

aa.prototype=new a();
function aa(){
	
} 
var b=new aa(); 
var e=new aa();
b.value.push(300,400);
alert(b.value);//彈框100,200,300,400
alert(e.value);//彈框100,200,300,400 
	
 
如上,a類的公共變量value是一個數組(數組是一種引用類型)
我們只給b對象的value值做了修改,卻發現連e對象的value一起改變了。e對象作爲aa類的一個新實例,本不該受到影響,卻意外出現這種情況,這便是原型繼承最重要的缺陷所致。
 
即便有缺陷,卻不影響原型繼承作爲js的一種最常見繼承方式的存在。
 
 

2.對象冒充式繼承(使用call或apply函數實現)

使用js提供的call()及apply()函數實現繼承。這兩者的第一個參數都是要實現繼承的子類對象(一般傳“this”),剩下的參數略有不同:

call(this, parameter1, parameter2, parameter3...  )

apply(this,array)
 
除了this外,剩下的參數將會傳遞給父類構造函數,供父類使用。示例:
 
function a(data1,data2){ 
	this.value=data1+data2;
}	

function aa(data1,data2){
	 a.call(this,data1,data2); 
}   
function bb(data1,data2){
	a.apply(this,new Array(data1,data2)); 
}
var b=new aa(10,20); 
var e=new bb(100,200); 
alert(b.value);//彈框30
alert(e.value);//彈框300
 
優點:
這種繼承方式,可以彌補原型繼承的一個缺陷:不能繼承有參的父類。
同時,他可以實現多重繼承(繼承多個父類)。
 
缺陷:
若是多重繼承,後繼承的父類會覆蓋繼承自其它父類的同名函數/變量。示例:
function parentA(data1,data2){ 
	this.value=data1+data2;
}	
function parentB(data1,data2){ 
	this.value=data1*data2;
}	
function aa(data1,data2){
	 parentA.call(this,data1,data2); 
	 parentB.call(this,data1,data2); 
}  
var b=new aa(10,20);  
alert(b.value);//彈框200 

 
 

三、JS的封裝與多態

除了繼承,封裝與多態也是面向對象思想的組成部分。JS的封裝、多態也是通過屬性的靈活應用“模擬”實現的。
通過在類中設置公共屬性,並在子類中實現,就可以模擬封裝。
而多態的體現,則更爲簡單,在子類中直接實現同名函數即可覆蓋(override)父類函數。JS中沒有類似C#中的virtualde 關鍵字,所有父類函數都可以直接覆蓋。
示例:
function calc(value1,value2){
	this.data1=value1;
	this.data2=value2; 
	this.GetResult;
	this.toString=function(){
		if(this.GetResult)
			return 	this.GetResult()+"";
		return "0";
	}
}
 
function sumCalc(value1,value2){
	calc.call(this,value1,value2)
	this.GetResult=function(){ 
		return this.data1+this.data2;
	}
}
function productCalc(value1,value2){
	calc.call(this,value1,value2)
	this.GetResult=function(){ 
		return this.data1*this.data2;
	}
}
var s=new sumCalc(2,3);
alert(s.toString());  //彈框5

var p=new productCalc(2,3);
alert(p.toString());  //彈框6
如上,sumCalc類與productCalc類都繼承並實現了calc類,並實現了“抽象函數”GetResult()。這就是JS封裝的實現方式。
另外,JS中的所有類都繼承於Object,而Object有自己的toString()函數。所以,上面calc類的toString()函數實際上覆蓋了原有的函數----多態的體現。
 

四、JS中的“委託”

委託是C#中的術語,類似C++中的函數指針。即將函數賦予一個函數對象,JS中也可以實現類似的功能。
示例:
function calc(value1,value2){
	this.data1=value1;
	this.data2=value2; 
	this.GetResult;
	this.ToString=function(){
		if(this.GetResult)
			return 	this.GetResult(this.data1,this.data2)+"";
		return "0";
	}
}
function GetSum(value1,value2){
	return value1+value2;
}
var c=new calc(2,3);
c.GetResult=GetSum; 
alert(c.ToString());  //彈框爲5

如上,GetResult函數被賦予了一個新函數GetSum(),當calc類執行GetResult()函數時,便會調用該委託的函數-----GetSum()。
 
 
 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章