什么是继承?
通俗的理解,子女会继承父母的一些体态样貌特征,拥有一些与原型相同的属性就是继承,JS中的继承就是让子类对象拥有其父类对象的属性和方法。
JS中继承原型的方式有6种:
1.原型链继承,让子类构造函数的prototype属性指向其父类构造函数的实例;
优点:简单易实现
缺点:
1.实例化时,无法修改父类构造函数中的传参;
2.构造函数中的私有引用属性被共享!!!!这可能引起很多未知错误,原型中的属性本身就是设计成需要共享的,但构造函数内部定义的属性是每个实例自身的属性,如果被共享将引起不必要的问题,类似于实例1的心脏停止跳动,导致实例2死亡!!诸多文章中提到了引用属性被共享,但是并没有提到是构造函数内部的引用属性,新手注意!!!!!导致这个问题的原因是生成的实例查不到私有属性到原型链上去查找,最终所有实例都查到原型上的这一属性,导致使用共同引用,如子类构造函数中私有属性对父类属性形成覆盖则不会出现该问题
function animal(num){
this.eyesNum = num;
this.leg = {leftLeg:"长",rightLeg:"短"}
return this;
};
animal.prototype.sex = {x:"雄性",y:"雌性"};
animal.prototype.say = function(){console.log("oyoyoy!")};
function fish(color){
this.color = color;
//若此处进行如下覆盖则不会有私有属性共享问题,原因是实例查不到私有属性,到原型链上去查找,最终所有实例都查到原型上的这一属性,导致使用共同引用。
// this.leg = {leftLeg:"长",rightLeg:"短"}
return this;
}
fish.prototype = new animal(2);
//这里注意一个构造函数prototype(显式原型)的constructor属性指向这个函数本身
//那么上方语句将原型指向父类的实例,父类的实例的原型对象的constructor指向的是父类构造函数,这与规则中定义的不符,为了保证链式查找可靠性,所以要将其指向修复为子类构造函数!!!!!
fish.prototype.constructor = fish;
var fish1 = new fish("黑");
var fish2 = new fish("白");
fish1.leg.leftLeg="短"
console.log(fish1.leg,fish2.leg,fish1.leg===fish2.leg)
//上述打印中,构造函数中私有的引用属性被共享!!!!这可能引起很多未知错误,原型中的属性本身就是设计成需要共享的,但构造函数内部定义的属性是每个实例自身的属性,如果被共享将引起不必要的问题,类似与实例1的心脏停止跳动,导致实例2死亡!!诸多文章中提到了引用属性被共享,但是并没有提到是构造函数内部的引用属性,新手注意!!!!!
2.构造函数继承,在子类构造函数中使用call,apply调用父类构造函数并改变其this指向为子类;
优点:子类构建时可以向父类传参
缺点:无法复用父类原型属性和方法
function animai(num){
this.eyesNum = num;
this.leg = {leftLeg:"长",rightLeg:"短"}
}
animai.prototype.say = function(){
console.log("bioibibi")
}
function fish(...args){
animai.apply(this,args)
this.color = args[1]
}
var fish1 = new fish(2,"黑");
var fish2 = new fish(4,"白");
fish1.leg.leftLeg = "短"
console.log(fish1.eyesNum,fish1.color);
//由于构造函数每次执行都重新运行私有方法,所以各个实例私有属性相互独立。而且并没有与父类建立原型链上的关联,本就不会出现找不到私有方法到原型中查找的场景
console.log(fish1.leg,fish2.leg,fish1.leg===fish2.leg)
console.log(fish1.sex,fish1.say,"fish1.sex无法打印,显示undefined")
3.原型链与构造函数同时使用,即组合继承;
优点:既可以向父类传参,又可以共享父类原型方法与属性
缺点:调用两次构造函数
function animal(num){
this.eyesNum = num;
this.leg= {leftLeg:"短",rightLeg:"长"}
};
animal.prototype.sex = {x:"男",y:"女"};
function fish(color,...args){
animal.apply(this,args);
this.color = color;
}
fish.prototype = new animal(2);
//如不修复则为animal!!!
fish.prototype.constructor = fish;
var fish1 = new fish("黑",4);
var fish2 = new fish("白",3);
fish1.leg.leftLeg = "长";
//此时打印,各实例私有属性相互独立,原型属性可查,结合了两者优点,唯一不足在创建实例过程中调用了两次构造函数,由于赋值原型的同时使用了apply方法,子类和父类中各有一套私有属性,增加了内存开销,不够优雅
console.log(fish1.leg,fish2.leg,fish1,fish2,fish2.sex)
4.原型式继承(工厂模式原型链继承),与原型链继承一字之差,是对原型链实现方式的工厂模式包装,他们的结果是一样的;
与原型链继承的区别:
原型链继承是在创建构造函数时,在外部将该构造函数原型直接指向父级实例;
原型式继承可以不必拥有自身的构造函数,采用工厂模式封装继承过程,定义一个方法,临时创建构造函数,浅拷贝一个父类实例,ECMAScript 5 新增的 Object.create(),就是工厂模式的原型链继承。
优点:父类原型属性和方法可以复用
缺点:子类构建时无法向父类传参,私有引用属性共享
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function animal(num){
this.eyesNum = num;
this.leg= {leftLeg:"短",rightLeg:"长"}
};
animal.prototype.sex = {x:"男",y:"女"};
var o = new animal(2);
var obj1 = object(o);
var obj2 = object(o);
obj1.leg.leftLeg = "长";
//通过打印可见,obj1是一个没有私有属性的对象,并且显示其为工厂函数中F类的一个实例,其所有属性都通过F类的原型链去查找,并且存在私有应用属性共享的问题
console.log(obj1,obj2,o,obj1.leg,obj2.leg)
5.寄生式继承(能够自定义属性的工厂模式原型链继承!我理解为有寄生虫的原型链继承),在原型式继承基础之上,封装了一层,用于给子类添加更多方法;
为什么叫寄生?寄生,生物学概念,强调2种生物一起生活,寄生体拥有了主体的部分功能,主体也获得寄生体部分能力,比如寄生虫本身没有飞行的能力,而寄生在鸟类身上则变相拥有了飞行的能力,鸟类无法携带某些特殊病菌,而寄生虫可以携带,这就相当于鸟类拥有了携带某些特殊病菌的能力,在JS继承概念中我把寄生理解为一种拓展,子类在实例化以后再拓展父类没有的功能就叫寄生,寄生与进化不同就区别于寄生虫在生物存在以后才能寄生,而进化是写在生物DNA之中未出生以前就已经定好了属性,这种在构造函数与原型之外额外给该类对象添加方法的行为,好比把寄生虫放到到主体上,寄生虫 +鸟 = 瘟疫鸟,寄生功能+普通对象 = 寄生对象(多功能对象)
优点:能给子类增加自定义方法;
// 工厂模式原型链继承封装,这个方法用于创建一个新对象并且连接原型链
function object(o){
function F(){};
F.prototype = o;
return new F();
}
//父类 构造
function animal(num){
this.eyesNum = num;
this.leg= {leftLeg:"短",rightLeg:"长"}
};
animal.prototype.sex = {x:"男",y:"女"};
//添加寄生虫,创建一个封装基础过程的函数,该函数内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function addParasite(name){
//生成普通对象
var obj = object(o);
//添加寄生功能
obj.sickFn= function(){
//此处形成闭包
console.log("让其他人生病 "+ name +"!")
}
//返回寄生对象
return obj;
}
//创建父类实例
var o = new animal(2);
//含寄生虫的子类实例
var obj1 = addParasite("天花");
var obj2 =addParasite("新冠");
obj1.leg.leftLeg = "长";
//通过打印可见,obj1是一个没有私有属性的对象,并且显示其为工厂函数中F类的一个实例,其所有属性都通过F类的原型链去查找,并且存在私有应用属性共享的问题
console.log(obj1,obj2,o,obj1.leg,obj2.leg)
6.寄生组合式继承(个人理解为能够强化子类并且以工厂模式设计的组合式继承),最常用的模式;
优点:集各模式所长, 总体来说就是解决了 组合模式 两次调用父类构造方法的弊端,减少子类父类属性 重复的问题,减少了内存占用!!!!;
缺点:实现代码相对来说复杂,冗长,需要封装一下才好
// 工厂模式原型链继承封装,这个方法用于创建一个新对象并且连接原型链
function inheritPrototype(Super,Sub){
// 本方法用于创造一个单纯用于传承父类原型方法的类,将子类原型指向这个只包含父类原型方法的实例相当于对父类 原型属性进行一个浅拷贝
//新建一个单纯用于链接原型链的构造函数 (原型衔接类)
function F(){};
//将该类原型指向父类原型
F.prototype = Super.prototype;
//将子类原型指向一个只包含父类原型属性的额实例(浅拷贝)
Sub.prototype = new F();
// 修复子类原型中错误的构造函数指向,当前是指向F需要修改为sub,这里相当于寄生,强化Sub.prototype!!!寄生就是强化 !
Sub.prototype.constructor = Sub;
}
//父类构造
function animal(num){
this.eyesNum = num;
this.leg = {leftLeg:"短",rightLeg:"长"};
}
animal.prototype.sex = {x:"男",y:"女 "};
//子类构造
function fish(color,...args){
animal.apply(this,args);
this.color = color;
}
fish.sayFn = function(){
console.log("咕噜咕噜")
}
//像子类添加寄生功能
inheritPrototype(animal,fish)
var fish1 = new fish("黑",4);
var fish2 = new fish("白",3);
console.log(fish1,fish2)
网友评论