構造函數、原型和實例之間的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個原型對象的指針。es6
原型鏈繼承核心: 將父類的實例做爲子類的原型。markdown
繼承的本質就是複製,即重寫原型對象,代之以一個新類型的實例,app
function Foo(name){
this.color = ['red','blue','black']
this.name = name
};
Foo.prototype.sayName = function () {
return ('my name is '+this.name)
}
function Car(age){
this.age = age
}
Car.prototype = new Foo('jack')
a = new Car(16)
b = new Car()
b.color.push('white') // 在Car的實例b的color屬性上添加了一個white
console.log(a.name,a.age)
console.log(a.color,a.sayName())
複製代碼
咱們在打印實例a的color屬性意外的發現多出來一個白色,這是由於在原型鏈的繼承中,引用類型會被全部的實例所共享,多個實例對引用類型的操做會被篡改。而不是單獨屬於本身的一份,dom
這也是原型鏈繼承存在的問題:函數
問題1:原型中包含的引用類型屬性將被全部實例共享;post
問題2:子類在實例化的時候不能給父類構造函數傳參;性能
爲此咱們想出來構造函數式繼承方法優化
基本思想:this
借用構造函數的基本思想就是利用call或者apply把父類中經過this指定的屬性和方法複製(借用)到子類建立的實例中。 由於this對象是在運行時基於函數的執行環境綁定的。也就是說,在全局中,this等於window,而當函數被做爲某個對象的方法調用時,this等於那個對象。 call、apply 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。spa
不知道this的指向的話能夠仔細看看這篇關於this的完整講解
function SuperType(){
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.getColor = function () {
return this.colors
}
function SubType(){
// 繼承了 SuperType
SuperType.call(this);
}
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"
instance2.getColor() // 報錯 TypeError: instance2.getColor is not a function
複製代碼
當咱們想要使用父類原型上的方法時報錯,發現順着原型鏈上去查找,找不到getColor這個方法, 這是由於子類的實例不能繼承原型屬性/方法
核心:使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(沒用到原型)
缺點: 方法都在構造函數中定義, 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法, 沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能
原理:使用原型鏈實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承。 結合原型繼承和構造函數的繼承方法,對之進行優化,達到便可繼承原型的方法,引用類型也不會被全部實例所共享。
function Person (name) {
this.name = name;
this.friends = ['小李','小紅'];
};
Person.prototype.getName = function () {
return this.name;
};
function Parent (age) {
Person.call(this,'老明'); //第二次調用構造函數
this.age = age;
};
Parent.prototype = new Person('老明'); //這一步也很關鍵
var result = new Parent(24); // 第一次調用構造函數
console.log(result.name); //老明
result.friends.push("小智"); //
console.log(result.friends); //['小李','小紅','小智']
console.log(result.getName()); //老明
console.log(result.age); //24
var result1 = new Parent(25); //經過借用構造函數都有本身的屬性,經過原型享用公共的方法
console.log(result1.name); //老明
console.log(result1.friends); //['小李','小紅']
複製代碼
優勢:
結合原型鏈和借用構造函數繼承兩種方法,取長補短。實現了函數複用,又可以保證每一個子類不會共享父類的引用類型屬性。
缺點:
調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype,{}); // 建立對象,建立父類原型的一個副本
prototype.constructor = subType; // 加強對象,彌補因重寫原型而失去的默認的constructor 屬性
subType.prototype = prototype; // 指定對象,將新建立的對象賦值給子類的原型
}
// 父類初始化實例屬性和原型屬性
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);
this.age = age;
}
// 將父類原型指向子類
inheritPrototype(SubType, SuperType);
// 新增子類原型屬性
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
複製代碼
這個例子的高效率體如今它只調用了一次SuperType 構造函數,而且所以避免了在SubType.prototype 上建立沒必要要的、多餘的屬性。於此同時,原型鏈還能保持不變; 所以,還可以正常使用instanceof 和isPrototypeOf() 這是最成熟的方法,也是如今庫實現的方法
在 es6 中,官方給出了 class 關鍵字來實現面向對象風格的寫法,但本質上是寄生組合式繼承的語法糖。
class Person {
constructor(age) {
this.age_ = age;
}
sayAge() {
console.log(this.age_);
}
// 靜態方法
static create() {
// 使用隨機年齡建立並返回一個 Person 實例
return new Person(Math.floor(Math.random()*100));
}
}
// 繼承普通構造函數
class Doctor extends Person {}
const doctor = new Doctor(32);
doctor.sayAge(); // 32
複製代碼
1.ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.apply(this)).
2.ES6的繼承機制徹底不一樣,實質上是先建立父類的實例對象this(因此必須先調用父類的super()方法),而後再用子類的構造函數修改this。
3.ES5的繼承時經過原型或構造函數機制來實現。
4.ES6經過class關鍵字定義類,裏面有構造方法,類之間經過extends關鍵字實現繼承。