背景:
面向对象中,比如在java里有类的单继承,接口的多继承,使得子类可以共享父类的属性、方法。
回顾:
前面js的面向对象中已经讲到了如何模拟面向对象中里如何创建类模板、然后通过类模板创建类实例对象。那么接下来就是讲如何模拟类之间的继承了。
1、原型链
利用构造函数的一个属性prototype即原型(prototype)让一个引用类型继承另外一个引用类型的属性和方法。
已有经验:
1 构造函数.prototype = 原型对象
2 原型对象.constructor = 构造函数(模版)
3 原型对象.isPrototypeOf(实例对象) 判断实例对象的原型 是不是当前对象
4 构造函数 实例对象 (类和实例)
通过构造函数的原型属性(prototype)可以为类模板添加属性、方法使得通过类模板创建的类实例可以共享这些属性和方法。那么还能不能通过构造函数的原型属性实现类之间的属性、方法继承呢?就是采用原型链。
测试1、
// 父类构造函数 sup
function Sup(name){
this.name = name;
}
// 父类的原型对象
Sup.prototype = {
constructor : Sup ,
sayName : function(){
alert(this.name);
}
};
// 子类构造函数 sub
function Sub(age){
this.age = age ;
}
//让子类的原型对象等于父类的实例实现了js的继承
//1 显然此时的原型对象(Sub.prototype)将包含一个指向另一个原型(Sup.prototype)的指针
// sup的实例对象 和 sup的原型对象 有一个关系
//2 相应的另一个原型中也包含着一个指向另一个构造函数的指针
// 子类的原型对象的构造器变成了父类的构造器
Sub.prototype = new Sup('张三');
alert(Sub.prototype.constructor);
结果:输出
function Sup(name){
this.name = name;
}
此时输出的不再是子类构造函数模板,而是父类构造函数模板。因为子类构造函数的原型对象指向的是父类的构造函数的实例。
测试2、
function Sup(name) {
this.name = name;
}
Sup.prototype = {
constructor: Sup,
sayName: function () {
alert(this.name);
}
}
function Sub(age) {
this.age = age;
}
Sub.prototype = new Sup('张三');//原型对象属性、方法继承;类模板继承2个方面。
// alert(Sub.prototype.constructor);
var sub1 = new Sub();
alert(sub1.name);
sub1.sayName()
结果:输出张三、张三
通过让其中的一个构造函数原型对象指向另一构造函数的实例让2个类发生了关系。此时子类的原型对象的构造属性即是父类的模板。
Sub.prototype = new Sup(‘张三’);这句代码使得通过子类创建的对象可以取得父类的构造函数模板且还可以取得父类原型对象的属性、方法。
2、继承的几种方式
以上提到通过原型链继承。它的特点是即继承了父类的构造函数模板,又继承了父类的原型对象。那么能不能只继承父类构造函数模板或者原型对象呢?
测试1:原型继承(简单继承)
//父类
function Person(name,age) {
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(sex) {
this.sex = sex;
}
//让2个类之间发生关系即继承
Boy.prototype = new Person('z3');
var b = new Boy();
alert(b.name);
alert(b.id);
结果:输出z3、10
此时通过原型对象同时继承了类模板、原型对象
测试2:类继承(只继承模版,不继承原型对象) (借用构造函数的方式继承)
//父类
function Person(name,age) {
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(name,age,sex) {
//调用父类的构造函数
Person.call(this,name,age);
this.sex = sex;
}
var b = new Boy('z3',20,'男');
alert(b.name);
alert(b.age);
alert(b.sex);
alert(b.id);
结果:输出z3、20、男、undefined
解析:此时用的方法不再是通过原型链继承了,而是通过在子类的构造函数模板中使用call去调用父类的构造函数,子类构造函数里并没有name、age属性,只有sex属性,但是子类的实例对象调用name、age属性时却输出了z3、20,说明我们调用了父类构造函数模板。
其次,最后b.id输出了undefined,说明子类不再继承父类的原型对象的属性了。即实现了类继承(只继承模版,不继承原型对象) (借用构造函数的方式继承)。
测试3:混合继承
原型继承+借用构造函数继承 = 混合继承
需要调用父类的构造函数模板的时候就使用借用构造函数继承,想调用父类原型对象的属性、方法的时候就采用原型对象继承。二者结合起来就是比较完美的了。可以做到类似像java里面的继承的特点了,子类创建的对象先在自身的成员变量找,找不到再去父类找。
//父类
function Person(name, age) {
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
Person.prototype.sayName = function () {
alert(this.name);
alert(this.age);
}
//子类
function Boy(name, age, sex) {
//调用父类的构造函数
Person.call(this, name, age);
this.sex = sex;
}
//原型继承,构造函数里并没有传参数。只剩下父类的实例 和 父类的原型对象的关系了
Boy.prototype = new Person();
var b = new Boy('z3', 20, '男');
alert(b.name);
alert(b.age);
b.sayName();
结果:输出z3、20、z3、20。前2个是调用了自身构造函数模板得到的结果,后2个是通过原型链继承得到了父类原型对象的方法的结果。
总结:
本篇主要讲述了js里面向对象中模拟继承。
继承的几种方式有:原型链继承(简单继承)、借用父类构造函数继承(类继承)、混合继承(原型链继承+借用父类构造函数继承)。