JavaScript 原型繼承

JavaScript 中雖然有對象的概念,但它並非一門嚴格意義上的面向對象編程的語言。javascript

儘管 ES6 引入了 class 關鍵字,可是本質上仍然是對原型鏈的操做。java

經過修改 JavaScript 的原型,能夠實現類之間的繼承關係。編程

首先用 function 關鍵字定義一個 ParentClasside

 1 function ParentClass(props) {
 2     this.alpha   = props.alpha || 1.0;
 3     this.color   = props.color || [0.8, 0.8, 0.8];
 4     console.log("ParentClass constructor");
 5 };
 6 
 7 ParentClass.prototype = {
 8     constructor: ParentClass,
 9 
10     init: function(gl) {
11         console.log("ParentClass.proptotype.init");
12     },
13 
14     paint: function(gl, addon) {
15         console.log("ParentClass.proptotype.paint");
16     }
17 }

而且在 ParentClass 的原型上定義了 init 和 paint 函數。注意這裏的構造器仍然是 ParentClass。函數

再用 function 定義 ChildClassthis

1 function ChildClass(props) {
2     ParentClass.call(this, props);
3     this.sides = props.sides || 24;
4     console.log("ChildClass constructor");
5 }

這裏的 ParentClass 的 call 函數至關於在 ChildClass 繼承了 ParentClass 以後調用 super 函數,也就是調用父類 ParentClass 的構造器。spa

固然這裏尚未讓  ChildClass 繼承 ParentClass,若是調用 ChildClass 的 init 方法就會報錯。prototype

 

在調用 ChildClass 對象的方法時,首先就會在 ChildClass 的原型上查找是否有同名的方法,若是找不到方法,就會到原型鏈的上一層查找,直到 Object,code

若是這個時候仍然找不到對應名稱的方法,就會報錯了。原型鏈過長的話就會影響運行速度。對象

 

要作到對象間的繼承,就要修改 ChildClass 的原型鏈,把 ChildClass 的原型鏈指向 ParentClass。

這裏直接給出廖雪峯的繼承代碼

1 /**
2   * this function is copied from liaoxuefeng's javascript tutorial
3 **/
4 inherits = function(child, parent) {
5     var F = function() {};
6     F.prototype = parent.prototype;
7     child.prototype = new F();
8     child.prototype.constructor = child;
9 }

經過一箇中間函數 F,把它的原型鏈指向 parent 的父類,再把子類的原型改成 F 函數 new 出來的對象。child 的構造器固然還應該是 child 本身。

注意這裏的 child 和 parent 應該都是指向 function 的對象(而不是用 new 關鍵字創造的對象)

1 inherits(ChildClass, ParentClass);  // ChildClass 繼承 ParentClass

建立子類的對象

1 var child = new ChildClass({}); 

那麼就會輸出兩行內容,分別是:

ParentClass constructor
ChildClass constructor 

再看看子類中相應的屬性或方法:

1 console.log(child.alpha);  // 1
2 
3 child.paint();  // ParentClass.prototype.paint

默認的 alpha 值是1,paint 繼承於 ParentClass,因此分別輸出 1 和 ParentClass.prototype.paint

 

要重寫 ChildClass 的 paint 方法,直接修改原型上的 paint 屬性,指向別的函數就行了:

1 ChildClass.prototype.paint = function() { console.log("ChildClass.prototpye.paint")};
2 child.paint();     // ChildClass.prototype.paint
3 ChildClass.paint;  // undefined

再調用 child.paint(),輸出的就是 ChildClass.prototype.paint

關於 ChildClass 原型上的屬性,只有在 new 出了對象後,由對象調用,而 ChildClass 自己是沒有 paint 屬性的。

再看一個例子:

1 ChildClass.testPaint = function() {console.log("ChildClass.testPaint")};
2 child = new ChildClass({});
3 child.testPaint  // undefined

若是直接給 ChildClass 添加 testPaint 屬性(方法),new 出來的對象不能訪問相應的屬性(方法)。

那麼就能夠這樣理解,ChildClass 原型上的屬性能夠被 new 出來的對象訪問,至關於 Java 中類中的普通方法;

而直接在 ChildClass 上添加屬性,只能被 ChildClass 訪問,而不能被 new 出來的對象訪問,至關於 Java 中類的靜態方法。

 

另外,我嘗試了下直接將子類(姑且這麼叫吧)的原型直接指向父類的對象中,好像沒有問題:

1 function SecChildClass(props) {
2     ParentClass.call(this, props);
3     this.sides = props.sides || 24;
4     console.log("SecChildClass constructor");
5 }
6 
7 SecChildClass.prototype = new ParentClass({});  //  ParentClass constructor
8 SecChildClass.constructor = SecChildClass;

固然在修改 SecChildClass 的原型鏈,指向 ParentClass 的對象時,就會執行一遍 ParentClass 的構造器,因此這個方式確實並很差。

再看看相應對象的屬性

1 var c2 = new SecChildClass({});
2 //  ParentClass constructor
3 //  SecChildClass constructor
4 
5 c2.alpha;   // 1
6 c2.paint();  // ParentClass.prototype.paint

創造對象時就會輸出兩行內容,由於在子類的構造器裏首先調用的是父類的 call 方法,因此首先執行的是父類的構造器。

值得一提的是,若是在子類的構造器中就調用 inherits 函數,傳入 this 是沒有用的,由於 this 指向的是子類的對象而非 ChildClass,這樣的繼承是沒有效果的。

 

reference

廖雪峯 JavaScript 教程

相關文章
相關標籤/搜索