https://cloud.tencent.com/developer/article/1408283javascript
https://cloud.tencent.com/developer/article/1195938java
https://cloud.tencent.com/developer/article/1359936markdown
https://cloud.tencent.com/developer/article/1079079app
https://cloud.tencent.com/developer/article/1408335函數
要弄懂extends繼承以前,先來複習一下構造函數、原型對象和實例之間的關係。this
代碼表示:spa
function F(){} var f = new F(); // 構造器 F.prototype.constructor === F; // true F.__proto__ === Function.prototype; // true Function.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true // 實例 f.__proto__ === F.prototype; // true F.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true
筆者畫了一張圖表示:prototype
繼承的幾種思路:設計
其實理解繼承,主要是理解構造函數,實例屬性和原型屬性的關係。要想實現繼承,將不一樣的對象或者函數聯繫起來,總共就如下幾種思路:對象
而後,1,2思路結合,實例屬性繼承用借用構造函數保證獨立性,方法繼承用原型鏈保證複用性,就是組合模式。 4,2思路結合,或者說3,4與1,2思路結合,實例屬性繼承用借用構造函數保證獨立性,方法繼承用原型複製加強的方式,就是寄生組合模式。
本文不許備深刻細節,主要是對《JavaScript高級程序設計中》介紹的JS如何實現繼承作一個總結,畢竟好記性不如爛筆頭。文末會附帶一張神圖,搞清楚這張圖,原型鏈也就沒有什麼問題了。
基本思想:
利用原型鏈讓一個引用類型繼承另外一個引用類型的屬性和方法。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; // 子類 SubType function SubType () { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subProperty; }; // 實例 var instance = new SubType(); console.log(instance); console.log(instance.getSuperValue()); // true console.log(instance instanceof SubType); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof Object); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(Object.prototype.isPrototypeOf(instance)); // true 複製代碼
缺點:
1. 來自原型對象的引用屬性是全部實例共享的。
2. 建立子類實例時,沒法向父類構造函數傳參。
舉例以下:
// 1. 來自原型對象的引用屬性是全部實例共享的
// 父類 function SuperType () { this.colors = ['red', 'blue', 'green']; } // 子類 function SubType () { } SubType.prototype = new SuperType(); // 實例 var instance1 = new SubType(); instance1.colors.push('black'); console.log(instance1.colors); // ['red', 'blue', 'green', 'black'] var instance2 = new SubType(); console.log(instance2.colors); // ['red', 'blue', 'green', 'black'] // 由於修改colors是修改的SubType.prototype.colors,因此全部的實例都會更新 複製代碼
// 2. 建立子類實例時,沒法向父類構造函數傳參
// 調用父類是在 SubType.prototype = new SuperType() // 新建子類實例調用 new SubType() // 因此沒法再new SubType() 的時候給父類 SuperType() 傳參 複製代碼
基本思想:
在子類構造函數的內部經過call()以及apply()調用父類構造函數。
// 父類 SuperType
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green']; this.getName = function () { return this.name; } } // 子類 function SubType (name) { // 繼承了SuperType,同時還傳遞了參數 SuperType.call(this, name); // 實例屬性 this.age = 20; } // 實例 var instance1 = new SubType('Tom'); instance1.colors.push('black'); console.log(instance1.name); // "Tom" console.log(instance1.getName()); // "Tom" console.log(instance1.age); // 20 console.log(instance1.colors); // ['red', 'blue', 'green', 'black'] var instance2 = new SubType('Peter'); console.log(instance2.name); // "Peter" console.log(instance2.getName()); // "Peter" console.log(instance2.age); // 20 console.log(instance2.colors); // ['red', 'blue', 'green'] 複製代碼
能夠看到,借用構造函數實現繼承,解決了原型鏈繼承的兩個問題,既能夠在新建子類實例的時候給父類構造函數傳遞參數,也不會形成子類實例共享父類引用變量。
可是你注意到了嗎,這裏咱們把父類方法也寫在了SuperType()構造函數裏面,能夠像前面同樣寫在SuperType.prototype上嗎?
答案是不能夠,必須寫在SuperType()構造函數裏面。由於這裏是經過調用SuperType.call(this)來實現繼承的,並無經過new生成一個父類實例,因此若是寫在prototype上,子類是沒法拿到的。
缺點:
1. 若是方法都在構造函數中定義,那麼就沒法複用函數。每次構建實例時都會在實例中保留方法函數,形成了內存的浪費,同時也沒法實現同步更新,由於每一個實例都是單獨的方法函數。若是方法寫在prototype上,就只會有一份,更新時候會作到同步更新。
基本思想:
將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。
使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。
// 父類
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function () { console.log(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 () { console.log(this.age); }; // 實例 var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); console.log(instance1.colors); // ['red', 'blue', 'green', 'black'] instance1.sayName(); // "Tom" instance1.sayAge(); // 20 var instance2 = new SubType('Peter', 30); console.log(instance2.colors); // ['red', 'blue', 'green'] instance2.sayName(); // "Peter" instance2.sayAge(); // 30 複製代碼
缺點:
1. 調用了兩次父類構造函數,一次經過SuperType.call(this)調用,一次經過new SuperType()調用。
基本思想:
不使用嚴格意義上的構造函數,藉助原型能夠基於已有的對象建立新的對象,同時還沒必要所以建立自定義類型。
// 在object函數內部,先建立了一個臨時的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。
// 從本質上講,object()對傳入其中的對象執行了一次淺複製。 function object (o) { function F() {} F.prototype = o; return new F(); } var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] }; var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); var yetAnotherPerson = object(person); yetAnotherPerson.name = 'Linda'; yetAnotherPerson.friends.push('Barbie'); console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] 複製代碼
ECMAScript5中新增了一個方法Object.create(prototype, descripter)接收兩個參數:
在傳入一個參數的狀況下,Object.create()與前面寫的object()方法的行爲相同。
var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] }; var anotherPerson = Object.create(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); var yetAnotherPerson = Object.create(person, { name: { value: 'Linda', enumerable: true } }); yetAnotherPerson.friends.push('Barbie'); console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] 複製代碼
缺點:
1. 和原型鏈繼承同樣,全部子類實例共享父類的引用類型。
基本原理:
寄生式繼承是與原型式繼承緊密相關的一種思路,建立一個僅用於封裝繼承過程的函數,該函數內部以某種形式來作加強對象,最後返回對象。
function object (o) { function F() {} F.prototype = o; return new F(); } function createAnother (o) { var clone = object(o); clone.sayHi = function () { console.log('Hi'); } return clone; } var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi" anotherPerson.friends.push('Rob'); console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob'] var yerAnotherPerson = createAnother(person); console.log(yerAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob'] 複製代碼
缺點:
1. 和原型鏈式繼承同樣,全部子類實例共享父類引用類型。
2. 和借用構造函數繼承同樣,每次建立對象都會建立一次方法。
基本思想:
將寄生式繼承和組合繼承相結合,解決了組合式繼承中會調用兩次父類構造函數的缺點。
組合繼承是JavaScript最經常使用的繼承模式,它最大的問題就是不管在什麼狀況下,都會調用兩次父類構造函數:一次是在建立子類原型的時候,另外一次是在子類構造函數內部。
// 組合繼承
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); }; 複製代碼
組合繼承在第一次調用SuperType構造函數時,SubType.prototype會獲得兩個屬性:name和colors;它們都是 SuperType 的實例屬性,只不過如今位於 SubType的原型中。當調用SubType構造函數時,又會調用一次SuperType構造函數,這一次又在新對象上建立了實例屬性name和colors。因而,這兩個屬性就屏蔽了原型中的兩個同名屬性。
所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
其背後的基本思路是:沒必要爲了指定子類型的原型而調用父類的構造函數,咱們須要的無非就是父類原型的一個副本而已。本質上,就是使用寄生式繼承來繼承父類的prototype,而後再將結果指定給子類的prototype。
寄生組合式繼承的基本模型以下:
function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 建立對象 prototype.constructor = SubType; // 加強對象 SubType.prototype = prototype; // 指定對象 } 複製代碼
實現一個完整的寄生組合式繼承:
function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 建立對象 prototype.constructor = SubType; // 加強對象 SubType.prototype = prototype; // 指定對象 } // 父類 function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; // 子類 function SubType(name, age) { // 繼承父類實例屬性 SuperType.call(this, name); // 子類實例屬性 this.age = age; } // 繼承父類方法 inheritPrototype(SubType, SuperType); // 子類方法 SubType.prototype.sayAge = function () { console.log(this.age); }; // 實例 var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType('Peter', 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"] 複製代碼
寄生組合式繼承的高效率體如今它只調用了一次SuperType構造函數,而且所以避免了再SubType.prototype上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變。所以,還可以正常使用instanceof和isPrototypeOf()。
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承方式。
function Person(name,age){
this.name = name;
this.age = age;
this.show = function(){
console.log(this.name+", "+this.age);
}
}
function Student(name,age){
this.student = Person; //將Person類的構造函數賦值給this.student
this.student(name,age); //js中其實是經過對象冒充來實現繼承的
delete this.student; //移除對Person的引用
}
var s = new Student("小明",17);
s.show();
var p = new Person("小花",18);
p.show();
// 小明, 17
// 小花, 18
// 父類
class SuperType { constructor(name) { this.name = name; this.colors = ["red", "blue", "green"]; } sayName() { console.log(this.name); }; } // 子類 class SubType extends SuperType { constructor(name, age) { // 繼承父類實例屬性和prototype上的方法 super(name); // 子類實例屬性 this.age = age; } // 子類方法 sayAge() { console.log(this.age); } } // 實例 var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType('Peter', 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"] 複製代碼
用ES6的語法來實現繼承很是的簡單,下面是把這段代碼放到Babel裏轉碼的結果圖片:
能夠看到,底層其實也是用寄生組合式繼承來實現的。
ES5實現繼承有6種方式:
寄生組合式繼承是你們公認的最好的實現引用類型繼承的方法。
ES6新增class和extends語法,用來定義類和實現繼承,底層也是採用了寄生組合式繼承。
附圖: