javascript 中關於繼承的描述:
許多物件導向語言支援兩種繼承的方式:介面繼承與實作繼承。介面繼承只繼承方法簽名,而實作繼承則繼承實際的方法。在 javascript 中由於函數沒有簽章也就無法實作介面繼承,而只支援實作繼承,而且實作繼承主要透過原型鏈來實現的。
先引用下官方文件對原型鏈的描述:其基本想法是利用原型讓一個引用型別繼承另一個引用型別的屬性與方法。要理解這個概念要先弄清楚建構函數,原型,和實例的關係:每個建構函數(只要是函數)都有一個prototype 屬性該屬性指向一個物件(這個物件就是建構函數的原型物件);原型物件(只要是物件)中都有一個constructor 屬性該屬性指向一個建構子;而實例中都包含一個指向原型物件的內部指標[[Prototype]]。說白了就是原型鏈的建構是透過將一個類型的實例賦值給另一個建構函數的原型來實現的。這樣子類型就可以存取定義在超類型上的所有屬性和方法了。每個對像都有自己的原型對象,以原型對象為模板從原型對象繼承屬性和方法,原型對像也可以有自己的原型並從中繼承屬性和方法,一層一層,以此類推,這種關係被稱為原型鏈它解釋了為何一個物件會擁有定義在其他物件上的屬性和方法。
javascript 中實作繼承的六種方式:
1、原型鏈
2、使用建構子
## )
4、原型式繼承
#
6、寄生組合式繼承(使用組合繼承與寄生式繼承)
1、原型鏈
#
// 实现原型链的一种基本模式 function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } // 继承,用 SuperType 类型的一个实例来重写 SubType 类型的原型对象 SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); // true
PS:SubType 繼承了 SuperType,而繼承是透過創建 SuperType 的實例,並將該實例賦值給 SubType 的原型實現的。實現的本質是重寫子類型的原型對象,並以一個新類型的實例取代。子類型的新原型物件中有一個內部屬性 [[Prototype]] 指向了 SuperType 的原型,還有一個從 SuperType 原型繼承過來的屬性 constructor 指向了 SuperType 建構子。最終的原型鍊是這樣的:instance 指向SubType 的原型,SubType 的原型又指向SuperType 的原型,SuperType 的原型又指向Object 的原型(所有函數的預設原型都是Object 的實例,因此預設原型都會包含一個內部指針,指向Object.prototype)。
原型鏈的缺點:
1、透過原型來實現繼承時,原型實際上會變成另一個類型的實例。於是,原先的實例屬性也就順理成章地變成了現在的原型屬性,並且會被所有的實例共享。這樣理解:在超型別建構函式中定義的參考型別值的實例屬性,會在子型別原型上變成原型屬性被所有子型別實例所共用。
2、建立子類型的實例時,無法傳遞參數給超類型的建構子。
2、借用建構子(也稱為偽造物件或經典繼承)
// 在子类型构造函数的内部调用超类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上 function SuperType(){ // 定义引用类型值属性 this.colors = ["red","green","blue"]; } function SubType(){ // 继承 SuperType,在这里还可以给超类型构造函数传参 SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("purple"); alert(instance1.colors); // "red,green,blue,purple" var instance2 = new SubType(); alert(instance2.colors); // "red,green,blue"
PS:透過使用apply() 或call() 方法,我們實際上是在將要創建的SubType 實例的環境下呼叫了SuperType 建構子。這樣一來,就會在新 SubType 物件上執行 SuperType() 函數中定義的所有物件初始化程式碼。結果 SubType 的每個實例都會有自己的 colors 屬性的副本了。
借用建構函式的優點是解決了原型鏈實作繼承存在的兩個問題;借用建構函式的缺點是方法都在建構函式中定義,因此函式復用就無法實作了。而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有型別都只能使用建構函式模式。
3、組合繼承(也稱偽經典繼承)
// 将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。 这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。 function SuperType(name){ this.name = name; this.colors = ["red","green","blue"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age){ // 借用构造函数方式继承属性 SuperType.call(this,name); this.age = age; } // 原型链方式继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("luochen",22); instance1.colors.push("purple"); alert(instance1.colors); // "red,green,blue,purple" instance1.sayName(); instance1.sayAge(); var instance2 = new SubType("tom",34); alert(instance2.colors); // "red,green,blue" instance2.sayName(); instance2.sayAge();
PS:組合繼承避免了原型鍊和借用建構函式的缺陷,融合了它們的優點,成為javascript 中最常用的繼承模式。而且,使用 instanceof 運算子和 isPrototype() 方法也能夠用於識別基於組合繼承所建立的物件。但是,它也有自己的不足。最大的問題是無論在什麼情況下,都會呼叫兩次超類型建構函數:一次是在建立子類型原型的時候,另一次是在子類型建構函式內部。
4、原型式繼承
// 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。 1、自定义一个函数来实现原型式继承 function object(o){ function F(){} F.prototype = o; return new F(); }
PS:在object() 函數內部,先建立一個臨時性的建構函數,然後將傳入的物件當作這個建構函數的原型,最後傳回這個臨時型別的一個新實例。實質上,object() 對傳入其中的物件執行了一次淺複製。
2、使用 Object.create() 方法實作原型式繼承。這個方法接收兩個參數:一是用作新物件原型的物件和一個為新物件定義額外屬性的物件。在傳入一個參數的情況下,此方法與 object() 方法作用一致。
在傳入第二個參數的情況下,指定的任何屬性都會覆寫原型物件上的同名屬性。
var person = { name: "luochen", colors: ["red","green","blue"] }; var anotherPerson1 = Object.create(person,{ name: { value: "tom" } }); var anotherPerson2 = Object.create(person,{ name: { value: "jerry" } }); anotherPerson1.colors.push("purple"); alert(anotherPerson1.name); // "tom" alert(anotherPerson2.name); // "jerry" alert(anotherPerson1.colors); // "red,green,blue,purple" alert(anotherPerson2.colors); // "red,green,bule,purple";
PS:只是想讓一個物件與另一個物件類似的情況下,原型式繼承是完全可以勝任的。但是缺點是:包含引用類型值的屬性總是會共用對應的值。
5、寄生式繼承
// 创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象 function createPerson(original){ var clone = Object.create(original); // 通过 Object.create() 函数创建一个新对象 clone.sayGood = function(){ // 增强这个对象 alert("hello world!!!"); }; return clone; // 返回这个对象 }
PS:在主要考慮物件而不是自訂類型和建構函數的情況下,寄生式繼承也是一個有用的模式。此模式的缺點是做不到函數重複使用。
6、寄生組合式繼承
// 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型 function SuperType(name){ this.name = name; this.colors = ["red","green","blue"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,age){ SuperType.call(this,name); this.age = age; } // 创建超类型原型的一个副本 var anotherPrototype = Object.create(SuperType.prototype); // 重设因重写原型而失去的默认的 constructor 属性 anotherPrototype.constructor = SubType; // 将新创建的对象赋值给子类型的原型 SubType.prototype = anotherPrototype; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("luochen",22); instance1.colors.push("purple"); alert(instance1.colors); // "red,green,blue,purple" instance1.sayName(); instance1.sayAge(); var instance2 = new SubType("tom",34); alert(instance2.colors); // "red,green,blue" instance2.sayName(); instance2.sayAge();
PS:這個範例的高效率體現在它只呼叫一次SuperType 建構函數,並且因此避免了在SubType.prototype 上面建立不必要,多餘的屬性。同時,原型鏈還能維持不變;因此也能夠正常使用 instance 運算元和 isPrototype() 方法。
以上是JavaScript中關於繼承的六種實作方式的詳細內容。更多資訊請關注PHP中文網其他相關文章!