js中构造函数的继承

function Animal(){
    this.species = "动物";
    this.zoomName = '东郊公园';
    this.age = 2;
}
Animal.prototype.age = 3;
function Cat(name, color){
    this.name = name;
    this.color = color;
}
// 怎么使得“猫”继承“动物”呢?

假设有业务场景:需要继承动物的属性,如何实现?

可以用以下四种方式:

  • 构造函数绑定:使用call或者apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

Father.apply(this, arguments); 或者 Father.call(this, arguments);

显然,这种实现方式下,猫的实例对象完全继承了动物的所有属性。

/* 第一种:构造函数绑定 。最简单的方法。
* 使用call或者apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
* 父.apply(this, arguments); 或  父.call(this, arguments); 
*/
/* Cat举例 */
function Cat2(name, color){
//  Animal.apply(this, arguments);
 Animal.call(this, arguments);
 this.name = name;
 this.color = color;
}
var catMao = new Cat2('咪咪', '白色');
console.log(catMao);

  •  prototype模式:使得子类的prototype指向父类的任意一个实例,即:在定义子对象的构造函数定义完成后,加两行:

Son.prototype = new Father ();

Son.prototype.constructor = Son;


/* Cat举例 */

/* 第二种:prototype模式。最常见的方法。
如果“猫”的prototype对象,指向一个Animal的实例,那么所有“猫”的实例,就能继承Animal了。
特别的:
        因为每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性,
     而且任何一个prototype对象都有一个constructor属性,指向它的构造函数,
     如果替换了构造函数o的prototype对象(默认是:o.prototype = {}),
     那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数o。
o.prototype.constructor = o; 
*/
// Cat举例
function Cat3(name, color){
     this.name = name;
     this.color = color;
}
console.log('Cat3****prototype=', Cat3.prototype); // {}
Cat3.prototype = new Animal();
console.log('Cat3****继承Animal后***prototype=;', Cat3.prototype); //  Animal { species: '动物', zoomName: '东郊公园' }
console.log('Cat3*****继承Animal后***构造函数=', Cat3.prototype.constructor); //  [Function: Animal]

/* 因为上一步修改Cat3的prototype,导致继承链紊乱因此手动纠正,使得Cat3的原型的构造函数还是Cat3 */
Cat3.prototype.constructor = Cat3; 
console.log('Cat3****先继承Animal****再纠正回来Cat3***prototype=', Cat3.prototype); //  Animal { species: '动物', zoomName: '东郊公园' }
console.log('Cat3*****先继承Animal****再纠正回来Cat3****构造函数=', Cat3.prototype.constructor); //  [Function: Cat3]

let mao3 = new Cat3('猫3','黑色');
console.log('Cat3继承了Animal后的****cat3实例=', mao3); // { name: '猫3', color: '黑色' }
console.log('Cat3实例描述=', `我的动物园名字:${mao3.zoomName},我是:${mao3.species},我的年龄:${mao3.age},我的名字:${mao3.name},我的颜色:${mao3.color}`); // 我的动物园名字:东郊公园,我是:动物,我的年龄:2,我的名字:猫3,我的颜色:黑色

 

显然,以方式二实现继承后,"猫"的实例对象,本身没有Animal的实例属性(age,zoomName,species)。但是,为什么Cat3实例能够取到他们呢?原因是:原型链继承。即:访问实例本身没有的属性,那么就在它的原型链上查找,即会找到自己从Animal继承的prototype里面,从而可以访问到“age,zoomName,species”这几个属性了。

  •   直接继承prototype:第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们可以让Cat()跳过Animal(),直接继承Animal.prototype。

第一步:将Father(父构造函数)改写成一个空函数,并抽离Father的不变属性,并写入到prototype上。即:​​​​​​​

Father(){}

Father.prototype.commonProName= value;

第二步:将Cat的prototype对象指向Animal的prototype对象,并且纠正Cat的构造函数;这样就完成了继承。即:

Son.prototype=Father.prototype;

Son.prototype.constructor = Son;

function Animal1 () {}
Animal1.prototype.species = '动物1';
Animal1.prototype.zoomName = '东郊公园1';
Animal1.prototype.age = 4;
console.log('Cat继承前的****prototype=',Cat.prototype);
console.log('Cat继承前的****constructor=',Cat.prototype.constructor);

// 2、直接继承prototype:将Cat的prototype对象指向Animal1的prototype对象,并必须纠正Cat的构造函数,这样就完成了继承。
function Cat(name, color){
    this.name = name;
    this.color = color;
}
Cat.prototype.catSpecies = "加菲猫";
Cat.prototype = Animal1.prototype;
console.log('Cat继承Animal1的prototype后*****prototype=',Cat.prototype);
console.log('Cat继承Animal1的prototype后的*****constructor=',Cat.prototype.constructor);

// 注意:既然替换了某个prototype对象,必须要使得新的prototype加上构造函数,并且constructor指向原来的构造函数。

Cat.prototype.constructor = Cat;
console.log('Cat继承Animal1的prototype后,纠正的*****constructor=',Cat.prototype.constructor);

var mao4 = new Cat('猫4','蓝色');
console.log('Cat继承后的****cat实例=', mao4);
console.log('Cat4实例描述=', `我是可爱的${mao4.species},来自${mao4.zoomName}动物园,我${mao4.age}岁了;我属于${mao4.catSpecies},取名为${mao4.name},有着漂亮的${mao4.color}毛。`);

 思考:

为什么猫的种类为undefined了?

分析:

在prototype赋值语句执行后,Cat本身的protptype被完整替换了,所以继承后的prototype里面再无“catSpecies”属性,仅是有Animal.prototype包含的属性。那么自然也访问不到了。

解决:

可以将它们两句调换位置,即可:

Cat.prototype = Animal1.prototype;

Cat.prototype.catSpecies = "加菲猫";

特点:效率高,省内存,副作用大

副作用:

Son.prototype与Father.prototype指向同一个对象,当修改任何一个是都会影响另一个,这是我们不想看到的。

那如何避免这个副作用呢?请看第四种:

  • 利用空对象作为Son和Father的中介,实现继承。即:

逐步分析:

/* 第四种: 用空对象作为Son和Father的继承中介。弥补了第三种方式存在的副作用。*/
function Animal(){
    this.species = "动物";
    this.zoomName = '东郊公园';
    this.age = 2;
}
Animal.prototype.animalProp = 3;
function Cat(name, color){
    this.name = name;
    this.color = color;
}
// 1、定义一个空函数,进而可以产生一个prortotype空对象。
var F = function (){};
// 2、使得空对象的prototype与Father.prototype指向同一个对象(完全拷贝prototype),即F继承了Animal。
F.prototype = Animal.prototype; // 直接继承prototype
F.prototype.fProp="123123"; // 此时也改了Animal的prototype
// 检查F的prototype和constructor
console.log('F继承Animal的prototype后的*****prototype=',F.prototype);
console.log('F继承Animal的prototype后的*****constructor=',F.prototype.constructor);

// 3、使Cat的prototype指向F的一个实例。它是完全删除了Cat的prototype 对象原先的值,然后赋予一个新值(F的空prototype)。那么Cat就可以继承到F。
Cat.prototype = new F(); 
// 检查Cat的prototype和constructor
console.log('Cat继承F的prototype后的*****prototype=',Cat.prototype);
console.log('Cat继承F的prototype后的*****constructor=',Cat.prototype.constructor);
// 4、纠正构造函数指向原来的函数。
Cat.prototype.constructor = Cat;
//改变Cat的prototype
Cat.prototype.catSize="small"; // 此时,仅仅改了自己的prototype
Cat.uber = Animal.prototype;
console.log('Cat继承F的prototype后纠正的*****constructor=',Cat.prototype.constructor);

// 检查大家的prototype
console.log('Animal最后的prototype=',Animal.prototype);
console.log('F*****最后的prototype=',F.prototype);
console.log('Cat最后的prototype=',Cat.prototype);
var cat5 = new Cat();
// 检查一下Cat实例的属性有哪些
for(let key in cat5){
    console.log('猫的属性枚举****************:',key);
    // name、color、constructor来自Cat
    // catSize来自Cat.prototype
    // ...
}
console.log(cat5.species); // undefined,无法访问Animal的私有属性

 

cat5的属性:构造函数(Cat)里面私有的,constructor,继承自F的prototype里面的,继承自Animal的prototype里面的。 

那么,第四种方法,可以简化实现为以下的内容: 

function Animal(){
    this.species = "动物";
    this.zoomName = '东郊公园';
    this.age = 2;
}
Animal.prototype.animalProp = 3;
function Cat(name, color){
    this.name = name;
    this.color = color;
}

function extendsHandle(Father,Son){
    console.log('handler:',Father,Son);
    var F = function(){};
    F.prototype = Father.prototype;
    F.prototype.fProp = "123";
    Son.prototype = new F();
    Son.prototype.constructor = Son;
    Son.prototype.sonProp="abc";
    Son.uber= Father.prototype;// uber属性类似super。
}
extendsHandle(Animal, Cat);
var cat5 =new Cat('猫5', "绿色");
console.log('Cat的实例:', cat5);
// cat5没有Animal的三个私有属性。
console.log('Cat5的动物园名称:', cat5.zoomName);

for(let key in cat5){
 console.log('Cat的实例属性有:', key);
}

希望大家多多批评指正。谢谢!

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值