JavaScript 繼承
前言
理解對象和繼承是我們學習設計模式,甚至是閱讀各種框架源碼的第一步。上一篇文章,筆者已經把JavaScript對象進行了梳理,今天我們來一起學習繼承。
繼承
原型鏈
基本思想:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法,也就是將一個原型對象指向另一個引用類型的實例。
Example:
function Parent() {
this.property = true
}
Parent.prototype.getParentValue = () => this.property
function Child() {
this.childProperty = false
}
Child.prototype = new Parent()
Child.prototype.getChildValue = () => this.childProperty
let instance = new Child()
console.log(instance.getParentValue()) // true
Child
通過創建 Parent
實例,並將實例賦值給 Child.prototype
的方式,繼承了 Parent
。本質是利用實例重寫了原型對象。這樣,Child.prototype
擁有了 Parent
的全部屬性和方法,同時其內部指針也指向了 Parent.prototype
。
通過實現原型鏈,本質上拓展了原型搜索機制。
添加方法(謹慎):
字類型有時需要重寫超類型中的某個方法,或新增超類型中不存在的方法,一定要將給原型添加方法的代碼放在被替換原型的語句後面。
Example:
function Parent() {
this.property = true;
}
Parent.prototype.getParentValue = () => this.property
function Child() {
this.childProperty = false;
}
//繼承了 SuperType
Child.prototype = new Parent();
//添加新方法
Child.prototype.getChildValue = () => this.childProperty
//重寫超類型中的方法
Child.prototype.getParentValue = () => false
let instance = new ChildType();
console.log(instance.getParentValue()); //false
原型鏈繼承存在的問題:
- 創建子類型實例時,不能向父類的構造函數中傳遞參數
- 父子構造函數的原型對象之間存在共享問題
example:
function Parent(){
this.colors = ["red", "blue", "green"];
}
function Child() {}
Child.prototype = new Parent();
let instanceChild = new Child();
instance1.colors.push("black");
console.log(instanceChild.colors); //"red", "blue", "green","black"
//當我們改變colors的時候, 父構造函數的原型對象的也會變化
let instanceParent = new Parent();
console.log(instanceParent.colors); //"red", "blue", "green","black"
構造函數
基本思想:在子類構造函數的內部,調用父類的構造函數
Example:
function Parent(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
function Child() {
Parent.call(this, "jerry"); // 繼承了 Parent
this.age = 22;
}
let instanceChild = new Child();
instanceChild.colors.push("black");
console.log(instanceChild.name); // jerry
console.log(instanceChild.colors); //"red", "blue", "green","black"
let instanceParent = new Parent();
console.log(instanceParent.colors); //"red", "blue", "green"
構造函數問題:
- 方法都在構造函數中定義,因此函數很難複用
- 在父類原型對象中定義的方法,子類無法繼承
組合繼承
簡單的說:原型鏈+構造函數
基本思想:原型鏈實現對原型屬性和方法的繼承,構造函數實現對實例屬性的繼承
Example:
function Parent(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = () => { console.log(this.name) }
function Child(name, age) {
Parent.call(this, name); // 繼承屬性
this.age = age;
}
// 繼承方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
Child.prototype.sayAge = () => { console.log(this.age) }
let instanceChild = new Child("jerry", 23);
instanceChild.colors.push("black");
console.log(instanceChild.colors); //"red", "blue", "green","black"
instanceChild.sayName(); // jerry
instanceChild.sayAge(); // 23
存在問題:
- 若再添加一個子類型,給其原型單獨添加一個方法,那麼其他子類型也同時擁有了這個方法,因爲它們都指向同一個父類型的原型
- 無論在什麼情況下都會調用兩次父類的構造函數,我們不得不在調用子類構造函數時,重寫這些屬性。
原型式繼承
基本思想: 本質是對繼承對象執行了一次淺拷貝。
example:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
// ES5內置了object.create()方法可以以上方法的功能。
let parent = {
name: "jerry",
friends: ["marry", "sandy"]
}
let Child1 = object(parent);
Child1.name = "barbie";
Child1.friends.push("Rob");
let Child2 = object(parent);
Child2.name = "Cos";
Child2.friends.push("Linda");
console.log(parent.friends); //"marry", "sandy","Rob","Linda"
存在問題:
- 與原型鏈繼承一樣,父子對象之間存在共享問題
- 無法實現複用
寄生式繼承
基本思路:在原型式繼承外面套了一層函數,在該函數內部增強對象。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
// 殼子
function createAnother(original) {
let clone = object(original);
clone.sayHi = () => { console.log("Hi") }
return clone;
}
存在問題:
- 複用率賊低
寄生組合式繼承
基本思想: 利用構造函數來繼承屬性,利用原型鏈的混成形式來繼承方法。
example:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(child, parent) {
let prototype = object(parent.prototype)
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = () => { console.log(this.name) }
function Child(name, age) {
Parent.call(this, name); // 繼承屬性
this.age = age;
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = () => this.age
目前來說,這是最好的繼承方式。
結束
懶癌發作,明明早就寫好了,一直把他丟棄在電腦硬盤裏。幾周之後,我掙扎着打開MWeb,稍做修葺,趕個結束語。算是畫上一個句號吧。最後,不得不說,高級3,真是一本不可多得的好書,把對象、繼承講得如此清晰明瞭。
參考
- 《JavaScript高級程序設計》第3版