相信許多小夥伴們對繼承這個概念並不陌生,也是前端技術中較爲基礎和重點的地方,可是每每還有許多你並不真正瞭解的地方,先回答我下面這幾個問題:前端
構造函數繼承的思想特別簡單,就是在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,所以能夠經過使用apply()和call()方法也能夠在新建立的對象上執行構造函數。數組
function SuperType(){
this.colors = ['red', 'block', 'white'];
}
function SubType(){
// 繼承了SuperType
SuperType.call(this);
}
let child1 = new SubType();
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'block', 'white','yellow']
let child2 = new SubType();
console.log(child2.colors); // ['red', 'block', 'white']
複製代碼
對於原型鏈來講,構造函數有一個比較大的優點,就是能夠在子類型的構造函數中向超類型構造函數傳遞參數。bash
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this, '周元');
this.age = 24;
}
let child = new SubType();
console.log(child.name); // 周元
console.log(child.age); // 24
複製代碼
缺點
app
方法都是在構造函數中定義的,沒法函數複用,並且子類型不能繼承超類型原型中定義的方法。實踐中也會不多使用構造函數繼承。函數
原型鏈繼承的基本思想就是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。post
這裏先回顧一下構造函數、原型和實例的關係。
每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。ui
function SuperType(){
this.colors = ['red', 'block', 'white'];
}
function SubType(){}
//繼承了SuperType
SubType.prototype = new SuperType();
let child1 = new SubType();
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'block', 'white','yellow']
let child2 = new SubType();
console.log(child2.colors); // ['red', 'block', 'white', 'yellow']
複製代碼
這裏SuperType構造函數定義了一個colors屬性,該屬性包含一個數組類型,SuperType的每一個實例都會有各自包含本身數組的colors屬性。當SubType經過原型繼承了SuperType以後,SubType.prototype就變成了Supertype的一個實例,所以它也擁有一個本身的colors屬性。
原型鏈繼承 解決了構造函數繼承沒法函數複用的問題,可是同時也出現了一些其餘的問題。this
好比:不能夠向超類型的構造函數傳遞參數以及實例共享的問題。實踐中也會不多使用原型鏈繼承。spa
組合繼承其實也叫作僞經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊兒,發揮兩者之長的一種繼承手段。prototype
具體思路就是使用原型鏈實現對原型屬性和原型方法的繼承,而經過構造函數來實現對實例屬性的繼承。
function SuperType(name) {
this.name = name;
this.colors = ['red', 'black', 'white']
}
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);
}
let child1 = new SubType('周元', 24);
child1.colors.push('yellow');
console.log(child1.colors); // [ 'red', 'black', 'white', 'yellow' ]
child1.sayName(); //周元
child1.sayAge(); // 24
let child2 = new SubType('夭夭', 23);
child2.colors.push('green');
console.log(child2.colors); // [ 'red', 'black', 'white', 'green' ]
child2.sayName(); //夭夭
child2.sayAge(); // 23
複製代碼
組合繼承的方式避免了原型鏈繼承和構造函數繼承的缺陷,融合了他們的優勢,成爲JS最經常使用的繼承方式。
可是這種繼承方式也是存在着一點小缺點,就是不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。
原型式繼承的基本思想就是能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
複製代碼
let SuperType = {
name: '吞吞' ,
friends: ['周元', '夭夭', '蒼淵']
}
let SubType1 = Object.create(SuperType);
SubType1.name = '趙牧神';
SubType1.friends.push('九宮');
let SubType2 = Object.create(SuperType);
SubType2.name = '郗菁';
SubType2.friends.push('趙仙隼');
console.log(SuperType); // { name: '吞吞', friends: [ '周元', '夭夭', '蒼淵', '九宮', '趙仙隼' ] }
複製代碼
下面咱們再來看一下添加第二個參數的效果
var person = {
name: '蘇幼微',
friends: ['武瑤', '武煌']
}
var child = Object.create(person, {
name: {
value: '葉冰凌'
}
});
console.log(child); // {name: '葉冰凌'}
複製代碼
關於原型式繼承的缺點和原型鏈繼承相同,就是會共享實例。
寄生式繼承的基本思路與寄生構造函數和工廠模式相似,就是建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後在返回這個對象。
function createAnother(original){
var clone = Object.create(original); // 經過調用函數建立一個對象
clone.sayHi = function(){ // 以某種方式來加強這個對象
console.log('h1');
}
return clone; // 返回這個對象
}
var person = {
name: '伊秋水'
}
var newPerson = createAnother(person);
newPerson.sayHi(); // hi
複製代碼
在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。可是使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用而下降效率,這一點和構造函數繼承相似。
寄生組合繼承的基本思想就是借用構造函數來繼承屬性,經過原型鏈的混合形式來繼承方法,並且沒必要爲了指定子類型的原型而調用超類型的構造函數。咱們所須要的就是超類型原型的一個副本而已。本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。
function inheritprototype( subType, superType ) {
var prototype = Object.create(superType.prototype); // 建立對象
prototype.constructor = subType; // 指定原型
subType.prototype = prototype; // 指定對象
}
複製代碼
inheritprototype函數實現了寄生組合式繼承的最簡單形式,主要是分爲三步:
1.建立超類型原型的一個副本。
2.爲建立的副本添加constructor屬性。
3.將新建立的對象賦值給子類型的原型。
而後咱們就能夠這樣使用:
function SuperType(name) {
this.name = name;
this.colors = ['周擎天', '秦玉'];
}
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 subType1 = new SubType('綠蘿', 22);
subType1.sayName(); // 綠蘿
subType1.sayAge(); // 22
複製代碼
寄生組合式繼承只調用一次超類型構造函數,避免在SubType prototype上面建立沒必要要的、多餘的屬性。開發者廣泛認爲寄生組合式繼承是引用類型最理想的繼承方式。
ES6繼承的核心就是經過extends來實現繼承。
class SuperType {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
class SubType extends SuperType {
constructor(name, grade) {
super(name); // 記得用super調用父類的構造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
var subType1 = new SubType('周元', 1);
subType1.hello(); // Hello, 周元!
subType1.myGrade(); // I am at grade 1
複製代碼
使用class繼承的時候,咱們須要注意一下幾點
- 子類必須在constructor方法中調用super方法。
- 只有在調用super()以後,纔可使用this關鍵字
這裏在稍微提一下class的特色
- class 聲明會提高,但不會初始化賦值。
- class 聲明內部會啓用嚴格模式。
- class 的全部方法(包括靜態方法和實例方法)都是不可枚舉的。
- class 的全部方法(包括靜態方法和實例方法)都沒有原型對象 prototype,因此也沒有[[construct]],不能使用 new 來調用.
- 必須使用 new 調用 class。
- class 內部沒法重寫類名。
綜上 就是我對於前端實現繼承的幾種方式的一點小看法,文中若有錯誤,歡迎在評論區指正,若是這篇文章幫助到了你,歡迎點贊👍和關注,😀。