new方法的缺陷在于每個實(shí)例的屬性都是獨(dú)立的箱叁,無法共享,像一些函數(shù)的屬性我們是希望共享的惕医,所以就有了prototype的出現(xiàn)
在設(shè)計(jì)繼承的時(shí)候我們希望達(dá)到的效果是實(shí)例屬性都是各種獨(dú)自擁有的耕漱,但是放在prototype上的屬性是需要共享的,在后面評判各種繼承方式的優(yōu)缺點(diǎn)也是會參考這兩點(diǎn)的
六種方式:
1抬伺、原型鏈繼承
// 原型鏈繼承 子類的原型指向父類的實(shí)例
// 由于原型鏈繼承共享屬性實(shí)例屬性的缺點(diǎn)螟够,屬于引用類型傳值,引用副本實(shí)例屬性的修改必然會引起其他副本實(shí)例屬性的修改妓笙,所以不常使用;
// 另一個缺點(diǎn)在于不能向父類構(gòu)造函數(shù)隨時(shí)傳遞參數(shù)能岩,很不靈活
function SuperType() {
this.colors = ['red', 'blue', 'green'];
}
function SubType() {}
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors);
let instance2 = new SubType();
console.log(instance2.colors);
2、借用構(gòu)造函數(shù)繼承
// 借用構(gòu)造函數(shù)繼承, 在執(zhí)行Child構(gòu)造函數(shù)的時(shí)候捧灰,子類的實(shí)例各自得到一份構(gòu)造函數(shù)的副本,屬于值傳遞毛俏,所以子類之間的屬性修改是互不相關(guān)的炭庙;
// 缺點(diǎn):單獨(dú)使用無法達(dá)到函數(shù)復(fù)用煌寇,因?yàn)槊恳粋€函數(shù)和屬性都需要在構(gòu)造函數(shù)中定義焕蹄,沒法復(fù)用阀溶,即沒有父類prototype上的函數(shù)腻脏,只有不能共用的實(shí)例屬性
// 而且instanceof操作無法確定子類實(shí)例和父類之間的關(guān)系鸦泳,因?yàn)樽宇惖膒rototype和父類無關(guān)
function Parent() {
this.colors = ['red', 'blue', 'green'];
}
function Child() {
Parent.call(this);
}
let instance3 = new Child();
instance3.colors.push('white');
console.log(instance3.colors);
let instance4 = new Child();
console.log(instance4.colors);
3永品、組合式繼承
把父類的一個實(shí)例設(shè)為子類的prototype,然后在子類的構(gòu)造函數(shù)內(nèi)調(diào)用父類的構(gòu)造函數(shù)鼎姐,調(diào)用這樣父類的實(shí)例屬性出現(xiàn)了兩個地方钾麸,調(diào)用父類構(gòu)造函數(shù)的時(shí)候?qū)崿F(xiàn)了子類實(shí)例對子類prototype上父類實(shí)例屬性的覆蓋炕桨,達(dá)到了較好的效果饭尝,既能傳參献宫,也可以實(shí)現(xiàn)是否共享的控制钥平,唯一的問題在于調(diào)用了兩次父類的構(gòu)造函數(shù)姊途,父類的實(shí)例屬性在子類prototype上浪費(fèi)了
// 組合繼承模式 常用 原型鏈繼承+構(gòu)造函數(shù)繼承
// 原型鏈繼承共享屬性(屬性方法和屬性)涉瘾, 構(gòu)造函數(shù)繼承父類構(gòu)造函數(shù)的實(shí)例屬性
// 缺點(diǎn): 調(diào)用了兩次父類構(gòu)造函數(shù)吭净,生成了兩份實(shí)例睡汹,一個子類實(shí)例寂殉,一個父類實(shí)例囚巴,父類實(shí)例作為prototype使用
function Person(name, age) {
this.name = name;
this.age = age;
this.action = ['speak', 'run', 'eat'];
console.log('我被調(diào)用了');
}
Person.prototype.say = function () {
console.log(`my name is ${this.name} and I am ${this.age} years old!`);
};
function Student(name, age, score) {
Person.call(this, name, age); // 借用構(gòu)造函數(shù), 第一次調(diào)用父類構(gòu)造函數(shù)
this.score = score;
}
Student.prototype = new Person(); // 原型鏈繼承, 第二次調(diào)用父類構(gòu)造函數(shù)
Student.prototype.constructor = Student; // 將實(shí)例的原型上的構(gòu)造函數(shù)指定為當(dāng)前子類的構(gòu)造函數(shù)
Student.prototype.showScore = function () {
console.log(`my score is ${this.score}`);
};
let xiaoming = new Student('xiaoming', 23, '88');
xiaoming.action.push('panio');
console.log(xiaoming.action);
xiaoming.say();
xiaoming.showScore();
let xiaohua = new Student('xiaohua', 24, '99');
console.log(xiaohua.action);
xiaohua.say();
xiaohua.showScore();
4友扰、原型式繼承
利用一個空對象作為中介,將某個對象直接賦值給空對象構(gòu)造函數(shù)的原型村怪。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
object()對傳入其中的對象執(zhí)行了一次淺復(fù)制秽浇,將構(gòu)造函數(shù)F的原型直接指向傳入的對象甚负。之所以要這樣創(chuàng)造一個空對象柬焕,就是為了看后續(xù)繼續(xù)添加屬性的過程不會污染原來的對象
var person = {
name: "Nicholas",
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");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
缺點(diǎn):
原型鏈繼承多個實(shí)例的引用類型屬性指向相同梭域,存在篡改的可能斑举。
無法傳遞參數(shù)
另外病涨,ES5中存在Object.create()的方法富玷,能夠代替上面的object方法,因?yàn)閛bject方法其本質(zhì)就是一個淺復(fù)制的過程赎懦。
個人認(rèn)為這種繼承方式很不好雀鹃,每次繼承都直接返回一個對象励两,要繼續(xù)在對象上面擴(kuò)展黎茎,麻煩当悔,下面這種寫法或許更好
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
// extend函數(shù)做的就是創(chuàng)造一個空對象作為Child的prototype工三,該空對象的__proto__又指向Parent
5先鱼、寄生式繼承
核心:在原型式繼承的基礎(chǔ)上,增強(qiáng)對象焙畔,返回的也直接是一個對象,所以寄生式繼承就相當(dāng)于一個工廠函數(shù)串远,里面對被繼承的對象進(jìn)行加強(qiáng),丟進(jìn)去要繼承的對象澡罚,出來一個已經(jīng)加強(qiáng)過的新對象
function createAnother(original){
var clone = object(original); // 通過調(diào)用 object() 函數(shù)創(chuàng)建一個新對象
clone.sayHi = function(){ // 以某種方式來增強(qiáng)對象
alert("hi");
};
return clone; // 返回這個對象
}
函數(shù)的主要作用是為構(gòu)造函數(shù)新增屬性和方法,以增強(qiáng)函數(shù)
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺點(diǎn)(同原型式繼承):
原型鏈繼承多個實(shí)例的引用類型屬性指向相同留搔,存在篡改的可能更胖。
無法傳遞參數(shù)
6隔显、寄生組合式繼承
// 最好的方法却妨,最理想的方法 寄生組合式繼承
// 解決了兩次調(diào)用父類構(gòu)造函數(shù)問題
function Person_1(name, age) {
this.name = name;
this.age = age;
this.action = ['speak', 'run', 'eat'];
console.log('我被調(diào)用了');
}
Person_1.prototype.say = function () {
console.log(`my name is ${this.name} and I am ${this.age} years old!`);
};
function Student_1(name, age, score) {
Person_1.call(this, name, age); // 借用構(gòu)造函數(shù), 第一次調(diào)用父類構(gòu)造函數(shù)
this.score = score;
}
Student_1.prototype = Object.create(Person_1.prototype);
Student_1.prototype.constructor = Student_1;
Student_1.prototype.showScore = function () {
console.log(`my score is ${this.score}`);
};
let xiaoming_1 = new Student('xiaoming_1', 23, '78');
xiaoming_1.action.push('panio');
console.log(xiaoming_1.action);
xiaoming_1.say();
xiaoming_1.showScore();
let xiaohua_1 = new Student('xiaohua_1', 24, '89');
console.log(xiaohua_1.action);
xiaohua_1.say();
xiaohua_1.showScore();