js里的继承
主要是依赖原型链来实现的。
1.原型链继承
构造函数、原型和实例之间的关系为:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,让这个原型对象(子的原型)等于要继承的引用类型(父)的实例,通过层层指向关系就可以实现原型链。
原型链继承实现的额本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于父类(SuperType)实例中的所有属性和方法,现在也存在于子类的prototype(SubType.prototype)中。在确认继承关系之后,我们给SubType.prototype添加一个方法,这样就在继承了SuperType的属性和方法的基础上又添加了一个新的方法。
原型链的继承是由外向内的,通过通过__proto__属性连接,形成链式。
原型链继承是通过prototype原型属性实现继承。
//父对象,构造函数通常用大写开头区别于普通函数
function Person(a){
this.name=a;
this.age=null;
this.sex ="男";
this.sleep = function () {
return "睡觉";
}
}
//子对象
function Student(){
this.job="学生";
}
//原型链继承
console.log(Student.prototype);//结果为图1
Student.prototype=new Person("素文");
console.log(Student.prototype);//结果为图2
//实例化对象
var student=new Student();
console.log(student);//结果为图3
console.log(student instanceof Student);//true子类对象的实例是自身
console.log(student instanceof Person);//true子类对象的实例是父类
图1
图2
图3
1.1原型链继承的特点:
子类的实例继承后包含自身的实例属性,父类里面的构造属性和方法以及父类的原型属性和方法。
1.2原型链继承的缺点:
- 不能继承子类的原型属性和方法,这里的原型属性和方法被父类的覆盖了(如图1与图2所示)。
- 只能进行单继承,不能多继承
- 继承之后,原型对象上的属性全是共享的。
- 子类不能直接向父类传递参数(指的是不能在实例化子类对象的时候传递参数,只能在原型链继承父类的时候传递)
2.构造函数继承
2.1实质:在子类型构造函数的内部调用超类型构造函数。
这里注意理解,**函数是在特定环境中执行代码的对象。**因为其是对象所以可以通过apply和call方法,在新创建的对象上执行(父类)构造函数(这里使用的是上述两个方法替换对象参数实现的继承,即将父类的实例内容添加子类原型链中)。
//父类
function Plan(){
this.name=arguments[0];
this.water= function () {
console.log("储水");
}
}
//父类
function Type(t) {
this.type=t;
}
//子类
function Flower(sun,n,t){
this.sunlike=sun;
Plan.apply(this,[n]);//第二个参数是集合型
Type.call(this,t);//第二个参数是序列型
}
var flower = new Flower(false,"剑兰","花");
console.log(flower);//图4
console.log(flower instanceof Flower);//true
console.log(flower instanceof Plan);//false
console.log(flower instanceof Type);//false
图4
2.2特点:
- 可以实现多继承。
- 可以向父类传递参数。
- 构造继承 无法继承父类里的原型属性。
- 子类的实例是本身,不是父类。
3.实例继承:
在子类内部直接构造父类的实例。
//父类
function Person(n,s){
this.name=n;
this.sex=s;
this.sleep= function(){
return "八小时";
}
}
//子类
function Child(n,s){
return new Person(n,s);
}
var child=Child();//调用方式1
console.log(child);
var child=new Child("悦雯","女");//调用方式2
console.log(child);
console.log(child instanceof Person);//true
console.log(child instanceof Child);//false
3.1实例继承的特点
- 不限定调用的方式。既可以使用函数调用的方式,也可以使用实例化对象的方式。
- 可以直接由子类向父类传递参数。
- 子类的实例是父类而不是子类本身。
- 子类的原型对象还是自身默认的原型对象。
- 不能渠道子类的构造属性和方法,可以取到父类的构造属性与方法。
4.拷贝继承
4.1实质:
将父类里的属性和方法拷贝给子类。具体为在子类中实例化父类对象,将父类的实例化属性和方法通过遍历拷贝给子类的原型对象。
function Animale(){
this.name=arguments[0];
this.sleep= function () {
return "睡觉";
}
}
function Type(){
this.type = arguments[0];
}
function Cat(n,s,t){
this.sex=s;
this.eat= function () {
return "进食";
}
Cat.prototype.weight="5kg";
//拷贝继承
var animale = new Animale(n);
for (var key in animale)
{
Cat.prototype[key]=animale[key];
}
var type = new Type(t);
for(var key in type)
{
Cat.prototype[key]=type[key];
}
}
var cat = new Cat("小黑","公","猫科");
console.log(cat);//结果如图5所示
console.log(cat instanceof Cat);//true
console.log(cat instanceof Type);//false
console.log(cat.__proto__);//自身的原型属性和方法
console.log(cat.__proto__.__proto__);//objec的原型属性和方法
图5
4.2特点:
- 可以支持多继承。
- 子类对象的实例是它本身,而不是父类。
- 可以向父类传递参数。
- 继承的时候需要不断的实例化父类对象,占内存。
5.组合继承:
5.1实质:
原型链继承+构造继承
function Person(n){
this.name=n;
this.eat= function () {
return this.name+"吃饭";
}
}
Person.prototype={
constructor:Person,
color:null
}
function Child(a,s,n)
{
this.age=a;
this.sex=s;
this.sleep= function () {
return "睡觉";
}
Person.call(this,n);//构造
}
Child.prototype=new Person("阳");//调用了两次父类的构造函数
var child=new Child(14,"男","朝阳");
console.log(child);//图6
console.log(child instanceof Person);
图6
5.2特点:
- 可以实现多继承
- 可以向父类传递参数。
- 没有实现原型对象属性的共享。(当有同名的方法或属性,换句话说,在实例中有的方法和属性,在原型对象中也有,在读取时会先从实例中找,然后再原型对象中找。如果实例中找到了,就不会再看原型对象。)
- 子类的实例既是本身也是父类。
- 父类的构造函数在一次子类实例化中要执行两次。
6.寄生组合继承
6.1实质
改进组合继承中,两次调用父类构造函数的缺点。将父类的继承分成两部分,即父类的实例属性和方法及父类的原型属性和方法,两部分分开继承。父类的实例属性和方法用构造继承,父类的原型属性和方法,利用寄生继承。
原理:将父类的原型对象给一个空对象的prototype,再把空对象和子类的原型对象关联。
function Person(n){
this.name=n;
this.sleep= function () {
return this.name+"睡觉";
}
}
Person.prototype={
constructor:Person,
color:null
}
function Child(a,n){
this.age=a;
this.eat= function () {
return this.name+"吃饭";
}
Person.call(this,n);//构造继承
}
//寄生,自执行函数
(function(){
var fn= function () {};//空对象
fn.prototype=Person.prototype;
Child.prototype=new fn();
Child.prototype.weight="45kg";
})();
var child= new Child(13,"宋询");
console.log(child);
console.log(child instanceof Child);//true
console.log(child instanceof Person);//true
图6
6.2特点
- 可以在子类实例化的时候向父类传递参数。
- 只调用一次父类构造函数,也避免了在子类的原型对象上创建不必要的属性。
- 可以在寄生过程中为子类添加原型属性。
总的来说,寄生组合式继承是引用类型最理想的继承范式。