一: 理解類的實現機制
在JavaScript中可以使用function關鍵字來定義一個“類”,如何為類添加成員。在函數內通過this指針引用的變量或者方法都會成為類的成員,例如:
function class1(){
var s="abc";
this.p1=s;
this.method1=function(){
alert("this is a test method");
}
}
var obj1=new class1();
通過new class1()獲得對象obj1,對象obj1便自動獲得了屬性p1和方法method1。
在JavaScript中,function本身的定義就是類的構造函數,使用new創建對象的過程。
(1)當解釋器遇到new操作符時便創建一個空對象;
(2)開始運行class1這個函數,並將其中的this指針都指向這個新建的對象;
(3)因為當給對象不存在的屬性賦值時,解釋器就會為對象創建該屬性,例如在class1中,當執行到this.p1=s這條語句時,就會添加一個屬性p1,並把變量s的值賦給它,這樣函數執行就是初始化這個對象的過程,即實現構造函數的作用;
(4)當函數執行完后,new操作符就返回初始化后的對象。
通過這整個過程,JavaScript中就實現了面向對象的基本機制。由此可見,在JavaScript中,function的定義實際上就是實現一個對象的構造器,是通過函數來完成的。這種方式的缺點是:
(1)將所有的初始化語句、成員定義都放到一起,代碼邏輯不夠清晰,不易實現復雜的功能。
(2)每創建一個類的實例,都要執行一次構造函數。構造函數中定義的屬性和方法總被重復的創建,例如:
this.method1=function(){
alert("this is a test method");
}
這里的method1每創建一個class1的實例,都會被創建一次,造成了內存的浪費。另一種類定義的機制:prototype對象,可以解決構造函數中定義類成員帶來的缺點。
二:使用prototype對象定義類成員
一種為類添加成員的機制:prototype對象。當new一個function時,該對象的成員將自動賦給所創建的對象,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
this.prop=1;
}
//使用函數的prototype屬性給類定義新成員
class1.prototype.showProp=function(){
alert(this.prop);
}
//創建class1的一個實例
var obj1=new class1();
//調用通過prototype原型對象定義的showProp方法
obj1.showProp();
//-->
</script>
prototype是一個JavaScript對象,可以為prototype對象添加、修改、刪除方法和屬性。從而為一個類添加成員定義。
了解了函數的prototype對象,現在再來看new的執行過程。
(1)創建一個新的對象,並讓this指針指向它;
(2)將函數的prototype對象的所有成員都賦給這個新對象;
(3)執行函數體,對這個對象進行初始化操作;
(4)返回(1)中創建的對象。
和new的執行過程相比,多了用prototype來初始化對象的過程,這也和prototype的字面意思相符,它是所對應類的實例的原型。這個初始化過程發生在函數體(構造器)執行之前,所以可以在函數體內部調用prototype中定義的屬性和方法,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
this.prop=1;
this.showProp();
}
//使用函數的prototype屬性給類定義新成員
class1.prototype.showProp=function(){
alert(this.prop);
}
//創建class1的一個實例
var obj1=new class1();
//-->
</script>
和上一段代碼相比,這里在class1的內部調用了prototype中定義的方法showProp,從而在對象的構造過程中就彈出了對話框,顯示prop屬性的值為1。
需要注意,原型對象的定義必須在創建類實例的語句之前,否則它將不會起作用,例如:
<script language="JavaScript" type="text/javascript">
<!--
//定義一個只有一個屬性prop的類
function class1(){
this.prop=1;
this.showProp();
}
//創建class1的一個實例
var obj1=new class1();
//在創建實例的語句之后使用函數的prototype屬性給類定義新成員,只會對后面創建的對象有效
class1.prototype.showProp=function(){
alert(this.prop);
}
//-->
</script>
這段代碼將會產生運行時錯誤,顯示對象沒有showProp方法,就是因為該方法的定義是在實例化一個類的語句之后。
由此可見,prototype對象專用於設計類的成員,它是和一個類緊密相關的,除此之外,prototype還有一個重要的屬性:constructor,表示對該構造函數的引用,例如:
function class1(){
alert(1);
}
class1.prototype.constructor(); //調用類的構造函數
這段代碼運行后將會出現對話框,在上面顯示文字“1”,從而可以看出一個prototype是和一個類的定義緊密相關的。實際上:class1.prototype.constructor==class1。
三:一種JavaScript類的設計模式
類可以在function定義的函數體中添加成員,又可以用prototype定義類的成員,編程的代碼顯得混亂。如何以一種清晰的方式來定義類呢?下面給出了一種類的實現模式。
在JavaScript 中,由於對象靈活的性質,在構造函數中也可以為類添加成員,在增加靈活性的同時,也增加了代碼的復雜度。為了提高代碼的可讀性和開發效率,可以采用這種定義成員的方式,而使用prototype對象來替代,這樣function的定義就是類的構造函數,符合傳統意義類的實現:類名和構造函數名是相同的。例如:
function class1(){
//構造函數
}
//成員定義
class1.prototype.someProperty="sample";
class1.prototype.someMethod=function(){
//方法實現代碼
}
雖然上面的代碼對於類的定義已經清晰了很多,但每定義一個屬性或方法,都需要使用一次class1.prototype,不僅代碼體積變大,而且易讀性還不夠。為了進一步改進,可以使用無類型對象的構造方法來指定prototype對象,從而實現類的成員定義:
//定義一個類class1
function class1(){
//構造函數
}
//通過指定prototype對象來實現類的成員定義
class1.prototype={
someProperty:"sample",
someMethod:function(){
//方法代碼
},
…//其他屬性和方法.
}
上面的代碼用一種很清晰的方式定義了class1,構造函數直接用類名來實現,而成員使用無類型對象來定義,以列表的方式實現了所有屬性和方法,並且可以在定義的同時初始化屬性的值。這也更象傳統意義面向對象語言中類的實現。只是構造函數和類的成員定義被分為了兩個部分,這可看成JavaScript中定義類的一種固定模式,這樣在使用時會更加容易理解。
注意:在一個類的成員之間互相引用,必須通過this指針來進行,例如在上面例子中的 someMethod方法中,如果要使用屬性someProperty,必須通過this.someProperty的形式,因為在JavaScript 中每個屬性和方法都是獨立的,它們通過this指針聯系在一個對象上。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title></title> <script language="JavaScript" type="text/javascript"> /function Person() { // console.log("這里可以寫一些類成員初始化的代碼"); //在這進行初始化值 this.personName = "孫麗媛"; this.personAge = "20"; } //創建類 //先在執行這個代碼 后返回構造函數 進行賦值 最后輸出 Person.prototype.personName = ""; Person.prototype.personAge = 0; Person.prototype.showInfo = function () { alert(this.personName + "," + this.personAge); } var person=new Person(); person.showInfo(); function Person(personName,personAge) { // console.log("這里可以寫一些類成員初始化的代碼"); //在這進行初始化值 this.personName = personName; this.personAge = personAge; } //創建類 //先在執行這個代碼 后返回構造函數 進行賦值 最后輸出 Person.prototype.personName = ""; Person.prototype.personAge = 0; Person.prototype.showInfo = function () { alert(this.personName + "," + this.personAge); } var person=new Person("sun",15); person.showInfo(); function Person(personName,personAge){ console.log("可以做一些初始化的工作"); this.personName=personName; this.personAge=personAge; } Person.prototype={ personName:"zhang", personAge:18, shouInfo:function(){ alert(this.personName+","+this.personAge) } }; var p=new Person("lisi",30); p.shouInfo(); </script> </head> <body> </body> </html>
四: 實現類的公有成員
前面定義的任何類成員都屬於公有成員的范疇,該類的任何實例都對外公開這些屬性和方法。
五:實現類的私有成員
私有成員即在類的內部實現中可以共享的成員,不對外公開。JavaScript中並沒有特殊的機制來定義私有成員,但可以用一些技巧來實現這個功能。
這個技巧主要是通過變量的作用域性質來實現的,在JavaScript中,一個函數內部定義的變量稱為局部變量,該變量不能夠被此函數外的程序所訪問,卻可以被函數內部定義的嵌套函數所訪問。在實現私有成員的過程中,正是利用了這一性質。
在類的構造函數中可以為類添加成員,通過這種方式定義的類成員,實際上共享了在構造函數內部定義的局部變量,這些變量就可以看作類的私有成員
例如:
<script language="JavaScript" type="text/javascript">
<!--
function class1(){
var pp=" this is a private property"; //私有屬性成員pp
function pm(){ //私有方法成員pm,顯示pp的值
alert(pp);
}
this.method1=function(){
//在公有成員中改變私有屬性的值
pp="pp has been changed";
}
this.method2=function(){
pm(); //在公有成員中調用私有方法
}
}
var obj1=new class1();
obj1.method1(); //調用公有方法method1
obj1.method2(); //調用公有方法method2
//-->
</script>
這樣,就實現了私有屬性pp和私有方法pm。運行完class1以后,盡管看上去pp和pm這些局部變量應該隨即消失,但實際上因為class1是通過new來運行的,它所屬的對象還沒消失,所以仍然可以通過公開成員來對它們進行操作。
注意:這些局部變量(私有成員),被所有在構造函數中定義的公有方法所共享,而且僅被在構造函數中定義的公有方法所共享。這意味着,在prototype中定義的類成員將不能訪問在構造體中定義的局部變量(私有成員)。
要使用私有成員,是以犧牲代碼可讀性為代價的。而且這種實現更多的是一種JavaScript技巧,因為它並不是語言本身具有的機制。但這種利用變量作用域性質的技巧,卻是值得借鑒的。
實例: 借助局部變量來模擬分裝類的私有方法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title></title> <script language="JavaScript" type="text/javascript"> function Person(){ //局部變量加—— var _age=10; function _changeAge(age){ if(age<20){ age = 20; } _age = age; } this.setAge=function(age){ _changeAge(age); } this.getAge=function(){ return _age; } } var p=new Person(); var age= p.getAge(); alert(age); p.setAge(100); age=p.getAge(); alert(age); </script> </head> <body> </body> </html>
六: 實現類的靜態成員
靜態成員屬於一個類的成員,它可以通過“類名.靜態成員名”的方式訪問。在JavaScript中,可以給一個函數對象直接添加成員來實現靜態成員,因為函數也是一個對象,所以對象的相關操作,對函數同樣適用。例如:
function class1(){//構造函數
}
//靜態屬性
class1.staticProperty="sample";
//靜態方法
class1.staticMethod=function(){
alert(class1.staticProperty);
}
//調用靜態方法
class1.staticMethod();
通過上面的代碼,就為類class1添加了一個靜態屬性和靜態方法,並且在靜態方法中引用了該類的靜態屬性。
如果要給每個函數對象都添加通用的靜態方法,還可以通過函數對象所對應的類Function來實現,例如:
//給類Function添加原型方法:show ArgsCount
Function.prototype.showArgsCount=function(){
alert(this.length); //顯示函數定義的形參的個數
}
function class1(a){
//定義一個類
}
//調用通過Function的prototype定義的類的靜態方法showArgsCount
class1. showArgsCount ();
由此可見,通過Function的prototype原型對象,可以給任何函數都加上通用的靜態成員,這在實際開發中可以起到很大的作用,比如在著名的prototype-1.3.1.js框架中,就給所有的函數定義了以下兩個方法:
//將函數作為一個對象的方法運行
Function.prototype.bind = function(object) {
var __method = this;
return function() {
__method.apply(object, arguments);
}
}
//將函數作為事件監聽器
Function.prototype.bindAsEventListener = function(object) {
var __method = this;
return function(event) {
__method.call(object, event || window.event);
}
}
這兩個方法在prototype-1.3.1框架中起了很大的作用