前言:
繼承 是 OO 語言中的一個最為人津津樂道的概念。許多 OO 語言都支持兩種繼承方式:接口繼承 和 實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。如前所述,由於函數沒有簽名,在 ECMAScript 中無法實現接口繼承。
ECMAScript 只支持實現繼承,而且其 實現繼承 主要依靠 原型鏈 來實現的。
繼承方式:
1、原型鏈繼承
ECMAScript 中將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType(){ this.subproperty=false; } //通過創建SuperType的實例繼承了SuperType SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SubType(); alert(instance.getSuperValue()); //true
缺點:
(1)、包含引用類型值的原型屬性會被所有實例共享,這會導致對一個實例的修改會影響另一個實例;
(2)、在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
由於這兩個問題的存在,實踐中很少單獨使用原型鏈
下面例子清楚的說明了第一個問題
function SuperType(){ this.colors=["red", "blue", "green"]; } function SubType(){ } //繼承了SuperType SubType.prototype=new SuperType(); var instance1=new SubType(); instance1.colors.push("black"); alert(instance1.colors); //red,blue,green,black var instance2=new SubType(); alert(instance2.colors); //red,blue,green,black
2、借用 構造函數 實現繼承
在解決原型中包含引用類型值所帶來的問題中,使用借用構造函數技術來解決。
借用構造函數的基本思想,即在子類型構造函數的內部調用超類型構造函數。
函數只不過是在特定環境中執行代碼的對象,因此通過使用 apply() 和 call() 方法可以在新創建的對象上執行構造函數。
function SuperType(name){ this.name=name; } function SubType(){ //繼承了SuperType,同時還傳遞了參數 SuperType.call(this,"mary"); //實例屬性 this.age=22; } var instance=new SubType(); alert(instance.name); //mary alert(instance.age); //29
缺點:
(1)、無法避免構造函數模式存在的問題,方法都在構造函數中定義,因此無法復用函數;
(2)、在超類型的原型中定義的方法,對子類型而言是不可見的。
因此這種技術很少單獨使用。
3、組合繼承
組合繼承,指的是將原型鏈和借用構造函數的技術組合到一起。
思路是使用原型鏈實現對原型方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。
這樣,既通過在原型上定義方法實現了函數的復用,又能夠保證每個實例都有它自己的屬性。
function SuperType(name){ this.name=name; this.colors=["red", "blue", "green"]; } SuperType.prototype.sayName=function(){ alert(this.name); }; function SubType(name, age){ //繼承屬性 使用借用構造函數實現對實例屬性的繼承 SuperType.call(this,name); // 第一次調用 SuperType this.age=age; } //繼承方法 使用原型鏈實現 SubType.prototype=new SuperType(); // 第二次調用 SuperType SubType.prototype.constructor=SubType; subType.prototype.sayAge=function(){ alert(this.age); }; var instance1=new SubType("mary", 22); instance1.colors.push("black"); alert(instance1.colors); //red,blue,green,black instance1.sayName(); //mary instance1.sayAge(); //22 var instance2=new SubType("greg", 25); alert(instance2.colors); //red,blue,green instance2.sayName(); //greg instance2.sayAge(); //25
組合繼承避免了原型鏈和借用構造函數的缺點,融合了他們的優點,是JavaScript中最常用的繼承模式。
缺點:無論在什么情況下,都會調用兩次超類型構造函數,一次是在創建子類型原型的時候,一次是在子類型構造函數的內部
4、原型式繼承
原型式繼承是借助原型可以基於已有的對象創建新對象,同是還不比因此創建自定義類型。
function object(o) { function F() {} F.prototype = o return new F() }
在 object 函數內部,先創建一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回這個臨時類型的一個新實例。
本質上來說,object對傳入其中的對象執行了一次淺復制。
var person = { name: 'Gaosirs', friends: ['Shelby', 'Court'] } var anotherPerson = object(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
這種模式要去你必須有一個對象作為另一個對象的基礎。
在這個例子中,person作為另一個對象的基礎,把person傳入object中,該函數就會返回一個新的對象。
這個新對象將person作為原型,所以它的原型中就包含一個基本類型和一個引用類型。
所以意味着如果還有另外一個對象關聯了person,anotherPerson修改數組friends的時候,也會體現在這個對象中。
Object.create()方法
ES5通過Object.create()方法規范了原型式繼承,可以接受兩個參數,一個是用作新對象原型的對象和一個可選的為新對象定義額外屬性的對象,行為相同,基本用法和上面的object一樣,除了object不能接受第二個參數以外。
var person = { name: 'Gaosirs', friends: ['Shelby', 'Court'] } var anotherPerson = Object.create(person) console.log(anotherPerson.friends) // ['Shelby', 'Court']
5、寄生式繼承
寄生式繼承的思路寄生構造函數和工廠模式類似,即創建一個僅用於封裝繼承過程中的函數,該函數在內部已某種方式來增強對象,最后返回對象。
function createAnother(o) { var clone = Object.create(o) // 創建一個新對象 clone.sayHi = function() { // 添加方法 console.log('hi') } return clone // 返回這個對象 } var person = { name: 'GaoSirs' } var anotherPeson = createAnother(person) anotherPeson.sayHi()
缺點:使用寄生式繼承來為對象添加函數,會因為做不到函數復用而降低效率,這個與構造函數模式類似。
6、寄生組合式繼承
在前面說的組合模式(原型鏈+構造函數)中,繼承的時候需要調用兩次父類構造函數。
使用寄生組合式繼承,可以規避這些問題。
這種模式通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
本質上就是使用寄生式繼承來繼承父類的原型,在將結果指定給子類型的原型。
function inheritPrototype(subType, superType) { var prototype = Object.create(superType.prototype); // 創建對象 prototype.constructor = subType; // 增強對象 subType.prototype = prototype; // 指定對象 }
該函數實現了寄生組合模式的最簡單形式。
這個函數接受兩個參數,一個子類,一個父類。
第一步創建父類原型的副本,第二步將創建的副本添加constructor屬性,第三部將子類的原型指向這個副本。
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { console.log(this.name) } function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job } // 繼承 inheritPrototype(SubType, SuperType) var instance = new SubType('Gaosirs', 'student') instance.sayName()
補充:直接使用Object.create來實現,其實就是將上面封裝的函數拆開,這樣演示可以更容易理解。
function SuperType(name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { console.log(this.name) } function SubType(name, job) { // 繼承屬性 SuperType.call(this, name) this.job = job } // 繼承 SubType.prototype = Object.create(SuperType.prototype) // 修復constructor SubType.prototype.constructor = SubType var instance = new SubType('Gaosirs', 'student') instance.sayName()
優點:只調用了一次 supertype 構造函數,因此避免在subtype.prototype上創建不必要的,多余的屬性,與此同時,原型鏈還能保持不變,還能正常使用instanceof 和isPrototypeOf(),因此,寄生組合式繼承被認為是引用類型最理想的繼承范式。
7、ES6中 Class ... extends 關鍵字實現繼承
基本用法:Class之間通過使用extends關鍵字,這比通過修改原型鏈實現繼承,要方便清晰很多
class Colorpoint extends Point { constructor(x,y,color){ super(x,y); //調用父類的constructor(x,y) this.color = color } toString(){ //調用父類的方法 return this.color + ' ' + super.toString(); } }
子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然后對其進行加工,如果不調用super方法,子類就得不到this對象。因此,只有調用super之后,才可以使用this關鍵字。
prototype 和__proto__:
一個繼承語句同時存在兩條繼承鏈:一條實現屬性繼承,一條實現方法的繼承
class A extends B{} A.__proto__ === B; //繼承屬性 A.prototype.__proto__ == B.prototype;//繼承方法
隨筆整理自
https://www.cnblogs.com/annika/p/9073572.html
感謝博主分享!