javascript|繼承 JavaScript指令碼語言是一種功能強大的物件導向的語言。本文描述了如何在JavaScript中實現繼承。
Prototype
JavaScript沒有實作類別繼承,但可以通過prototype實現。每個JavaScript類中都包含一個prototype對象,當訪問一個對象的屬性和方法時,它首先從當前對象尋找,如果該屬性在JavaScript類定義,則直接存取;否則,從類的prototype對象中尋找,如果找到則直接存取,否則從prototype的prototype對象中尋找,以此類推,直接prototype對象為空白。換句話說,當前對象繼承了它所在類的prototype的所有屬性和方法。
但是,prototype對象是屬於一個類的,而一個類又可以建立多個對象,因而,訪問繼承來的屬性時讀取和寫入是有區別的。當讀取一個繼承的屬性時,按上面所述的步驟尋找屬性;當寫入一個屬性時,則從prototype中複製資料到當前對象;再次讀取時,訪問的是當前對象的屬性。對於方法不存在這種問題。
實現繼承
JavaScript類的prototype預設類型是Object類,所以可以說,JavaScript中所有類都從Object類繼承。我們可以修改一個類的prototype屬性,使它指向一個其它對象,從而實現繼承。實現繼承通常有兩種方式:
- 修改一個類的prototype屬性,使其指向一個父類對象
- 修改一個類的prototype對象的constructor屬性,為其指定一個父類的建構函式
兩種實現方法基本相同,但存在一些差別。代碼清單1採用第一種方式,代碼清單2採用第二種方式。
//定義基類Shapefunction Shape(x, y) {this.x = x;this.y = y;this.fromOrigin = function() { //定義方法return Math.sqrt(this.x * this.x + this.y * this.y);}}//可以採用這種方式/*Shape.prototype.fromOrigin = function() {return Math.sqrt(this.x * this.x + this.y * this.y);}*///定義子類Circlefunction Circle(x, y, r) {Circle.prototype.constructor(x, y); //調用父類建構函式this.r = r;}Circle.prototype = new Shape(); //綁定父物件//測試var c = new Circle(5, 5, 10);print(c);c.x = 3; //修改屬性c.y = 4;print(c);//列印function print(c) {document.write("Circle.prototype.x=" + Circle.prototype.x, "<br/>");document.write("Circle.prototype.y=" + Circle.prototype.y, "<br/>");document.write("c.x=" + c.x, "<br/>");document.write("c.y=" + c.y, "<br/>");document.write("c.r=" + c.r, "<br/>");document.write("c.fromOrigin()="+ c.fromOrigin(), "<br/>");}清單1:修改一個類的prototype屬性
輸出結果;
Circle.prototype.x=5Circle.prototype.y=5c.x=5c.y=5c.r=10c.fromOrigin()=7.0710678118654755Circle.prototype.x=5Circle.prototype.y=5c.x=3c.y=4c.r=10c.fromOrigin()=5
上面的代碼中,通過Circle.prototype = new Shape(); 綁定父物件,Circle對象包含了一個Shape所有的屬性和方法。
第一段輸出時,我們建立了一個Circle對象,在Circle的建構函式中調用父類Shape的建構函式,從而初始化了Shape中的x、y。當訪問c.x時,實際訪問的Circle.prototype引用的Shape對象的x。
第二段輸出時,我們修改了Circle對象的x和y,此時將Circle.prototype引用的Shape對象的x和y複製到Circle對象中,對Circle對象的修改不影響Circle.prototype引用的Shape對象。
fromOrigin方法既可以在Shape的建構函式中指定,也可以在Shape.prototype中指定。
//定義基類Shapefunction Shape(x, y) {this.x = x;this.y = y;this.fromOrigin = function() { //定義方法return Math.sqrt(this.x * this.x + this.y * this.y);}}//不能採用這種方式/*Shape.prototype.fromOrigin = function() {return Math.sqrt(this.x * this.x + this.y * this.y);}*///定義子類Circlefunction Circle(x, y, r) {Circle.prototype.constructor(x, y); //調用父類建構函式this.r = r;}Circle.prototype.constructor = Shape; //綁定prototype的建構函式為Shape(x, y)//測試var c = new Circle(5, 5, 10);print(c);c.x = 3; //修改屬性c.y = 4;print(c);//列印function print(c) {document.write("Circle.prototype.x=" + Circle.prototype.x, "<br/>");document.write("Circle.prototype.y=" + Circle.prototype.y, "<br/>");document.write("c.x=" + c.x, "<br/>");document.write("c.y=" + c.y, "<br/>");document.write("c.r=" + c.r, "<br/>");document.write("c.fromOrigin()="+ c.fromOrigin(), "<br/>");//document.write(Circle.prototype instanceof Object);}清單2:修改一個類的prototype對象的constructor屬性
清單2的輸出結果與清單1的輸出結果相同。
上面的代碼中,通過Circle.prototype.constructor = Shape; 綁定prototype的建構函式為Shape(x, y)。
fromOrigin方法只能在Shape的建構函式中指定,不可以在Shape.prototype中指定。原因是Circle.prototype.constructor = Shape;只指定的Circle.prototype的建構函式,Circle的prototype對象的類型沒有改變,仍然是Object類型,所以在Shape.prototype綁定的方法對Circle不可見。
結論
JavaScript是一種物件導向的語言,雖然表面上看起來不是很明顯(其中的頂級函數屬於Global對象)。我們可以通過JavaScript類的prototype來實現繼承,文中提到兩種方法以及它們的細微差別。
參考資料
David Flanagan, JavaScript: The Definitive Guide, 4th Edition, O'Reilly, 2001。