作者:木易杨说
連接:https://juejin.im/post/6844903696111763470
來源:掘金
// 以下為圖一
function Animal() {
this.haveHead = true
}
Animal.prototype.sleep = function () {
console.log("zzZ");
}
function Fish(name) {
this.name = name
this.haveCheek = true
}
// 透過原型鍊繼承!! (以下為圖二)
Fish.prototype = new Animal()
// 以下為圖三
let salmon = new Fish('salmon')
console.log(salmon);
圖一
圖二
這個時候的Fish.prototype是Animal的實例對象
圖三
如果有多個實例,操作其中一個實例有可能會影響到其他實例
代碼:
function Animal() {
this.hobbies = ["sleep", "watch"];
}
function Fish() { }
Fish.prototype = new Animal();
var salmon = new Fish();
instance1.hobbies.push("swim");
console.log(instance1.hobbies);
var salmon2 = new Fish();
console.log(instance2.hobbies);
// 主要原因是因為這一行,他改的其實是instance.__proto__.hobbies(意思就是改變Fish.prototype.hobbies(內存地址相同))
instance1.hobbies.push("swim");
// 因此假設我們繼續創建實例會繼續繼承已經改變的原型鍊
繼承自被改變的Fish.prototype
function Animal() {
this.hobbies = ["sleep", "watch"];
}
Animal.prototype.walk = function () {
console.log('走走走');
}
function Fish() {
// 構造函數繼承
Animal.call(this) // 執行Animal函數(這裡的this是實例本身)
}
var instance1 = new Fish();
instance1.hobbies.push("swim");
console.log(instance1);
var instance2 = new Fish();
console.log(instance2.hobbies);
- 雖然確實不會改變其他實例,但是我們會發現 (Animal.prototype) 不會被 fish 繼承
- 浪費性能,每個子類都存在父類實例函數的副本
利用原型鍊繼承方法讓原型鍊繼承 prototype 跟 __ proto __
利用構造函數讓實例本身也繼承屬性
簡單來說就是透過實例(是一個對象)本身也繼承屬性(比方說Animal裡面的name以及hobbies) ,解決原型鍊繼承實例修改可能會不小心改到原型鍊的問題
function Animal(name) {
this.name = name;
this.hobbies = ["sleep", "watch"];
}
Animal.prototype.sayName = function () {
console.log(this.name);
};
function Fish(name, age) {
// 讓Fish的實例也繼承Animal屬性
// 比方說下面
// let instance1 = new Fish("Salmon", 29);
// instance自身會等於對象(這樣我們push就不會push到原型鍊)
// {
// name: 'Salmon',
// age: 29,
// hobbies: ["sleep", "watch"]
// }
Animal.call(this, name);
this.age = age;
}
// 繼承原型鍊
Fish.prototype = new Animal();
// 重寫Fish.prototype的constructor属性(方便辨識prototype屬於誰)
Fish.prototype.constructor = Fish;
Fish.prototype.sayAge = function () {
console.log(this.age);
};
let salmon = new Fish("Salmon1", 15);
console.log(salmon);
salmon.hobbies.push("swim");
console.log(salmon.hobbies); // ["sleep", "watch", "swim"]
salmon.sayName(); //"Salmon1";
salmon.sayAge(); //15
let salmon2 = new Fish("salmon2", 10);
console.log(salmon2.hobbies); // ["sleep", "watch"]
salmon2.sayName(); //"salmon2";
salmon2.sayAge(); //10
因為調用了兩次構造函數
代碼:
function Fish(name, age) { // 第一次 Animal.call(this, name); this.age = age; } // 第二次 Fish.prototype = new Animal();
所以會讓原型鍊與實例都有相同的屬性名
運用一個空的構造函數為媒介,將傳入的對象賦值給空構造函數的prototype,並創建實例
function object(obj) {
function F() { }
F.prototype = obj;
return new F();
}
let student = {
year: 2020,
hobbies: ["sleep", "play"]
};
let Mike = object(student);
Mike.name = "Mike";
Mike.hobbies.push("sing");
let Mary = object(student);
Mary.name = "Mary";
Mary.hobbies.push("read");
console.log(student.hobbies);
跟原型鍊繼承一樣,因為
Mike.hobbies.push("sing"); // 改變的其實是原型鍊,所以其他實例會被影響
跟上一個繼承法比,其實就只是再封裝一個函數,添加實例的方法或是屬性
function object(obj) {
function F() { }
F.prototype = obj;
return new F();
}
function createAnother(original) {
var clone = object(original); // clone是一個實例
clone.sayHi = function () { // 其實就只是在實例對象裡添加函數或是屬性
alert("hi");
};
return clone; // 返回整個實例
}
let student = {
year: 2020,
hobbies: ["sleep", "play"]
};
var Mike = createAnother(student);
console.log(Mike);
Mike.sayHi(); //"hi"
- 還是沒解決實例的修改可能會讓其他實例跟著修改
- 無法傳參(student 都是固定的)
借用構造函數以及原型式繼承
附註:
function object(obj) { function F() { } F.prototype = obj; return new F(); } // 等價於 Object.create()
function inheritPrototype(subType, superType) {
// 拷貝父類型的prototype
var prototype = Object.create(superType.prototype);
// 讓之後的子類實例可以更明確知道由誰(子類)創造
prototype.constructor = subType;
// 將拷貝父類型的prototype賦值給子類型的prototype
subType.prototype = prototype;
}
// 父類
function Animal(name) {
this.name = name;
this.hobbies = ["sleep", "watch"];
}
Animal.prototype.sayName = function () {
console.log(this.name);
};
// 子類
function Fish(name, age) {
Animal.call(this, name); // 利用構造函數進行解決可以傳參還有可能改寫原型鍊的問題
this.age = age;
}
// 繼承(不再是透過Fish.prototype = new Animal(...))
inheritPrototype(Fish, Animal);
Fish.prototype.sayAge = function () {
console.log(this.age);
}
var Salmon1 = new Fish("Salmon1", 15);
var Salmon2 = new Fish("Salmon2", 21);
Salmon1.hobbies.push("rock");
Salmon2.hobbies.push("jazz");
console.log(Salmon1);
console.log(Salmon2);
解決組合繼承的缺點
核心關鍵:
// 透過自己封裝的函數繼承
inheritPrototype(Fish, Animal);
// 不再是利用(因為創建Animal實例會導致Fish的prototype也創建Animal的屬性)
Fish.prototype = new Animal()
非常完美(可以說是最成熟的方法,也是現在庫實現的方法)
Object.create方法
做的事情其實跟new很像 (但不會賦予子類型的proto屬性,就是利用這點改進組合繼承的缺點)
function Foo() {
}
let bar = Object.create(Foo.prototype)
console.log(bar);
function Fish() {
Animal.call(this);
marineLife.call(this);
}
// 繼承一個類
Fish.prototype = Object.create(Animal.prototype);
// 混合其它(淺拷貝marineLife.prototype到Fish.prototype)
// 目的是讓Fish可以使用marineLife方法
Object.assign(Fish.prototype, marineLife.prototype);
// 重新指定constructor
Fish.prototype.constructor = Fish;
// 開始定義自己的方法
Fish.prototype.myMethod = function() {
// do something
};
更全面的分析可參考