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);
}
希望大家多多批评指正。谢谢!
|