1.原型鏈繼承
?原型鏈繼承所帶來的問題:
??① 引用類型的屬性被所有實(shí)例共享。
??② 在創(chuàng)建 Child 的實(shí)例時(shí),不能向Parent傳參
例子:
function Parent (age) {
this.names = ['kevin', 'daisy'];
this.age = age;
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child('age');
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
console.log(child1.age);// undefined
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
2.構(gòu)造函數(shù)(經(jīng)典)繼承
?構(gòu)造函數(shù)(經(jīng)典)繼承的優(yōu)點(diǎn):
??① 避免了引用類型的屬性被所有實(shí)例共享。
??② 可以在 Child 中向 Parent 傳參
?缺點(diǎn):
??① 無法繼承原型鏈上的屬性和方法。
例子:
function Parent (name) {
this.name = name;
this.age = ['10','12'];
//a方法定義在構(gòu)造函數(shù)
this.a = function(){
console.log("a");
}
}
//方法定義在原型鏈
Parent.prototype.b = function(){
console.log("b");
}
function Child (name) {
Parent.call(this, name); //或者用.apply()方法
}
var child1 = new Child('kevin');
child1.age.push('16');
console.log(child1.name); // kevin
console.log(child1.age); //['10','12','16']
console.log(child1.a()); //a
console.log(child1.b()); //報(bào)錯(cuò):child1.b is not a function;即無法繼承原型鏈上的屬性和方法
var child2 = new Child('daisy');
child2.age.push('22');
console.log(child2.name); // daisy
console.log(child2.age); //['10','12','22']
3.組合繼承(原型鏈繼承和經(jīng)典繼承融合起來)
?組合繼承的優(yōu)點(diǎn):
??① 彌補(bǔ)了上述兩種方法存在的問題
?缺點(diǎn):
??① 會調(diào)用兩次父構(gòu)造函數(shù)。一次是設(shè)置子類型實(shí)例的原型的時(shí)候;一次在創(chuàng)建子類型實(shí)例的時(shí)候。
例子:
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name); //注意這里的坑,在這里,我們又會調(diào)用了一次 Parent 構(gòu)造函數(shù)。如果我們打印 child1 對象,我們會發(fā)現(xiàn) Child.prototype 和 child1 都有一個(gè)屬性為colors,屬性值為[‘red’, ‘blue’,‘green’]。
this.age = age;
}
Child.prototype = new Parent(); //父類的構(gòu)造函數(shù)是被執(zhí)行了兩次的,第一次:Child.prototype = new Parent();第二次:實(shí)例化的時(shí)候會被執(zhí)行;
//優(yōu)化(就變成了后面說的寄生組合繼承):
//Child.prototype = Object.create(Parent.prototype);
/*Object.create方法是一種創(chuàng)建對象的方式
1.方法內(nèi)部定義一個(gè)新的空對象obj
2.將obj._proto _的對象指向傳入的參數(shù)proto
3.返回一個(gè)新的對象 ;
Object.create方法底層實(shí)現(xiàn)實(shí)際上用到了之后我們將要說的原型式方法,關(guān)于Object.create方法更詳細(xì)的介紹可以自己查資料;
*/
/*Object.create的底層實(shí)現(xiàn)
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};*/
Child.prototype.constructor = Child; //注意這里的坑,如果不加,console.log(child1.constructor.name) //Parent 實(shí)例化的時(shí)候構(gòu)造函數(shù)指向的是父類
var child1 = new Child('kevin', '18'); //第二次調(diào)用父構(gòu)造函數(shù)
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
console.log(child1 instanceof Child) //true;
console.log(child1 instanceof Parent) //true;
//注意:
console.log(child1.constructor.name); //Child;不加這個(gè)Child.prototype.constructor = Child;打印結(jié)果是Parent
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
4.原型式繼承
?就是 ES5 Object.create 的模擬實(shí)現(xiàn),將傳入的對象作為創(chuàng)建的對象的原型。
?缺點(diǎn):
??① 包含引用類型的屬性值始終都會共享相應(yīng)的值,這點(diǎn)跟原型鏈繼承一樣。
例子:
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: 'kevin',
friends: ['daisy', 'kelly']
}
var person1 = createObj(person);
var person2 = createObj(person);
person1.name = 'person1';
console.log(person2.name); // kevin
person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]
修改person1.name的值,person2.name的值并未發(fā)生改變,并不是因?yàn)閜erson1和person2有獨(dú)立的 name 值,而是因?yàn)閜erson1.name = 'person1',給person1添加了 name 值,并非修改了原型上的 name 值。
5.寄生組合式繼承
?引用《JavaScript高級程序設(shè)計(jì)》中對寄生組合式繼承的夸贊就是:
??這種方式的高效率體現(xiàn)它只調(diào)用了一次 Parent 構(gòu)造函數(shù),并且因此避免了在 Child.prototype 上面創(chuàng)建不必要的、多余的屬性。與此同時(shí),原型鏈還能保持不變;因此,還能夠正常使用 instanceof 和 isPrototypeOf。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式。
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
// 關(guān)鍵的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
//上面三步等價(jià)于:Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
console.log(child1);
console.log(child1 instanceof Child) //true;
console.log(child1 instanceof Parent) //true;
console.log(Child.prototype.isPrototypeOf(child1)); // true
console.log(Parent.prototype.isPrototypeOf(child1)); // true
console.log(child1.constructor.name); //Child,注意不加Child.prototype.constructor = Child;結(jié)果是Parent
6.ES6-Class繼承
主要是了解幾個(gè)關(guān)鍵字:
??① class關(guān)鍵字。②extends關(guān)鍵字。③super關(guān)鍵字
例子:
class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName(){
console.log("the name is:"+this.name);
}
}
class Worker extends Person{
constructor(name, age,job) {
super(name, age);
this.job = job;
}
sayJob(){
console.log("the job is:"+this.job)
}
}
var worker = new Worker('tcy',20,'teacher');
console.log(worker.age); //20
worker.sayJob();//the job is:teacher
worker.sayName();//the name is:tcy
??上面說到子類的構(gòu)造函數(shù)constructor中super方法實(shí)現(xiàn)對父類構(gòu)造函數(shù)的調(diào)用。在調(diào)用時(shí)需要注意兩點(diǎn):
????1、子類構(gòu)造函數(shù)中必須調(diào)用super方法,否則在新建對象時(shí)報(bào)錯(cuò)。
????2、子類構(gòu)造函數(shù)中必須在使用this前調(diào)用super,否則報(bào)錯(cuò)。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName(){
console.log("the name is:"+this.name);
}
}
//情況1:
class Worker1 extends Person{
constructor(name, age,job) {
//報(bào)錯(cuò)
}
sayJob(){
console.log("the job is:"+this.job)
}
}
//情況2:
class Worker2 extends Person{
constructor(name, age,job) {
this.name = name;
super(name, age);//報(bào)錯(cuò)
this.job = job;
}
sayJob(){
console.log("the job is:"+this.job)
}
}
??· super函數(shù)除了表示父類的構(gòu)造函數(shù),也可以作為父類的對象使用,用于子類調(diào)用父類的方法。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName(){
console.log("the name is:"+this.name);
}
}
class Worker extends Person{
constructor(name, age,job) {
super(name, age);
this.job = job;
}
sayJob(){
console.log("the job is:"+this.job)
}
sayName(){
super.sayName();//調(diào)用父類的方法,
console.log("the worker name is:"+this.name)
}
}
var worker = new Worker('tcy',20,'teacher');
worker.sayJob();//the job is:teacher
worker.sayName();//the name is:tcy the worker name is:tcy
· super調(diào)用屬性:(有坑)
坑:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName(){
console.log("the name is:"+this.name);
}
}
class Worker extends Person{
constructor(name, age,job) {
super(name, age);
this.job = job;
}
sayJob(){
console.log("the job is:"+this.job)
}
sayName(){
console.log(super.name);//調(diào)用父類的屬性,undefined
//super.name報(bào)了undefined,表示沒有定義。super是指向父類的prototype對象,即Person.prototype,父類的方法是定義在父類的原型中,而屬性是定義在父類對象上的,所以需要把屬性定義在原型上。
console.log("the worker name is:"+this.name)
}
}
需要把屬性定義在原型上:
class Person {
constructor(name, age) {
Person.prototype.name = name;//定義到原型上
this.age = age;
}
sayName(){
console.log("the name is:"+Person.prototype.name); //注意這里不是this.name
}
}
class Worker extends Person{
constructor(name, age,job) {
super(name, age);
this.job = job;
}
sayJob(){
console.log("the job is:"+this.job)
}
sayName(){
console.log(super.name);//調(diào)用父類的原型屬性,tcy
console.log("the worker name is:"+this.name)
}
}
var worker = new Worker('tcy',20,'teacher');
worker.sayJob();//the job is:teacher
worker.sayName();//tcy,the worker name is:tcy
· this指向的問題:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
this.job = 'doctor';//父類定義的job
}
sayName(){
console.log("the name is:"+this.name);
}
sayJob(){
console.log("the person job is:"+this.job);
}
}
class Worker extends Person{
constructor(name, age,job) {
super(name, age);
this.job = job;
}
sayJob(){
super.sayJob();//the person job is:teacher
}
sayName(){
console.log(super.name);//調(diào)用父類的原型屬性,tcy
}
}
var worker = new Worker('tcy',20,'teacher');
worker.sayJob();//the job is:teacher
父類定義了this.job的值為"doctor",子類定義了this.job值為'teacher',調(diào)調(diào)用父類的方法sayJob(),結(jié)果輸出的是子類的值。子類在調(diào)用父類構(gòu)造函數(shù)時(shí),父類的原型this值已經(jīng)指向了子類,即Person.prototype.call(this),故輸出的子類的值。
最優(yōu)解:
??繼承的最優(yōu)解其實(shí)是要看當(dāng)前應(yīng)用場景的,最符合預(yù)期的場景就是,需要共享的,無論是靜態(tài)的還是動態(tài)的,把它們放在parent的原型上,需要隔離的,把它們放在parent上,然后子類通過調(diào)用parent的構(gòu)造方法來初始化為自身的屬性,這樣,才是真正的“最佳繼承設(shè)計(jì)方式”。
參考鏈接
如何回答關(guān)于 JS 的繼承
JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn)
ES6系列教程第六篇--Class繼承
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。