继承基本概念
继承的概念
继承:即通过一定的方式实现让某个类型A获取另外一个类型B的属性或方法。其中类型A称之为子类型,类型B称之为父类型或超类型。
javaScript中的继承
Object是所有对象的父级|父类型|超类型:js中所有的对象都直接或间接的继承自Object。
继承有两种方式:接口继承和实现继承,在js中只支持实现继承,实现继承主要依赖原型链来完成。
JavaScript中实现继承的几种方式
说明:其他语言中继承通常通过类来实现,js中没有类的概念,js中的继承是某个对象继承另外一个对象,是基于对象的。
原型式继承
原型链继承的方式A
//01 提供一个构造函数
function Person(name,age) {
this.name = name;
this.age = age;
}
//02 设置原型对象的属性
Person.prototype.className = "逍遥派1班";
//03 使用构造函数来创建原型对象
var p1 = new Person("张三",10);
var p2 = new Person("李四",20);
//04 打印p1和p2对象中的className属性
console.log(p1.className);
console.log(p2.className);
//结论:对象p1和p2继承了构造函数原型对象中的属性className
//但是这并不是严格意义上的继承
原型链继承的方式B
//01 提供一个构造函数
function Person(name,age) {
this.name = name;
this.age = age;
}
//02 设置原型对象的属性
Person.prototype = {
constructor:Person,
className:"逍遥派1班"
};
//03 使用构造函数来创建原型对象
var p1 = new Person("张三",10);
var p2 = new Person("李四",20);
//04 打印p1和p2对象中的className属性
console.log(p1.className);
console.log(p2.className);
//结论:对象p1和p2继承了构造函数原型对象中的属性className
//注意:使用原型替换的方式实现继承的时候,原有原型对象中的属性和方法会丢失
原型链继承的方式C
//01 提供超类型|父类型构造函数
function SuperClass() {
this.name = 'SuperClass的名称';
this.showName = function () {
console.log(this.name);
}
}
//02 设置父类型的原型属性和原型方法
SuperClass.prototype.info = 'SuperClass的信息';
SuperClass.prototype.showInfo = function () {
console.log(this.info);
};
//03 提供子类型
function SubClass() {
}
//04 设置继承(原型对象继承)
SubClass.prototype = SuperClass.prototype;
SubClass.prototype.constructor = SubClass;
var sub = new SubClass();
console.log(sub.name); //undefined
console.log(sub.info); //SuperClass的信息
sub.showInfo(); //SuperClass的信息
sub.showName(); //sub.showName is not a function
```
点评:上面的方法可以可以继承超类型中的原型属性和原型方法,但是无法继承实例属性和实例方法
原型链继承
- 实现思想:利用原型(链)让一个对象继承另一个对象的属性和方法
- 实现本质:重写原型对象
原型链
- 每个构造函数都有原型对象
- 每个对象都有自己的构造函数 * 每个构造函数的原型都是一个对象
- 那么这个构造函数的原型对象也有自己的构造函数
- 那么这个构造函数的原型对象的构造函数也有自己的原型对象
- 以上形成一个链式的结构,称之为原型链
原型链中的属性搜索原则
当访问某个对象的成员的时候,会先在自身中查找,如果找到则直接使用
如果在自身中没有找到,则去当前创建当前对象的构造函数的原型对象中查找,如果找到了则直接使用
如果在该原型对象中没有找到,则继续查找原型对象的原型对象(创建该原型对象的构造函数所对应的原型对象),如果找到则直接使用
如果在原型对象的原型对象中也没有找到,则继续向上搜索....
直到Object的原型对象,若还是没有,则返回undefined(属性)或报错(方法)。
基本写法·代码示例
//01 提供超类型|父类型
function SuperClass() {
this.name = 'SuperClass的名称';
this.showName = function () {
console.log(this.name);
}
}
//02 设置父类型的原型属性和原型方法
SuperClass.prototype.info = 'SuperClass的信息';
SuperClass.prototype.showInfo = function () {
console.log(this.info);
};
//03 提供子类型
function SubClass() {
}
//04 设置继承(原型对象继承)
SubClass.prototype = new SuperClass();
SubClass.prototype.constructor = SubClass;
var sub = new SubClass();
console.log(sub.name); //SuperClass的名称
console.log(sub.info); //SuperClass的信息
sub.showInfo(); //SuperClass的信息
sub.showName(); //SuperClass的名称
点评:可以继承父类型中的原型属性|原型方法,以及实例属性和实例方法
原型链继承的注意点和问题
注意点
① 确定原型和实例的关系 instanceof + isPrototypeOf()
② 注意重写原型对象的位置,必须先实现原型继承,然后再设置子对象的原型属性和原型方法
③ 完成继承之后,不能使用字面量的方式来创建原型[因为会切断原型]
问题
① 父对象的实例属性会转换为子类型原型的原型属性,而如果父类型是实例属性是引用类型则会存在共享问题
② 在创建子类型的实例时,不能向父类型的构造函数中传递参数
//01 提供父对象的构造函数
function SuperType() {
//02 在构造函数中中设置实例属性,该属性为引用类型
this.family = ['哥哥','姐姐','爸爸','妈妈'];
};
//03 提供子对象的构造函数
function SubType() {};
//04 设置原型继承
SubType.prototype = new SuperType();
//05 创建父对象构造函数的实例对象,并对内部的实例化属性进行修改
var subDemo1 = new SubType();
var subDemo2 = new SubType();
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈
subDemo1.family.push('爷爷','奶奶');
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
经典继承(借用构造函数)
- 经典继承又称为借用构造函数|伪造继承
基本思想
在子类型构造函数的内部调用超类型|父类型构造函数
说明:需要借助call|apply方法
代码示例
//01 提供父类型(对象)的构造函数
function SuperType(name) {
//02 在构造函数中中设置实例属性,该属性为引用类型
this.family = ['哥哥','姐姐','爸爸','妈妈'];
//实例属性
this.name = name;
};
SuperType.prototype.info = '父类型的原型属性';
//03 提供子类型(对象)的构造函数
function SubType() {
//经典继承|借用构造函数|伪造对象继承
//SuperType.call(this);
//构造参数传递参数
SuperType.call(this,'张老汉');
};
//04 创建父类型的实例对象,并对内部的实例化属性进行修改
var subDemo1 = new SubType();
var subDemo2 = new SubType();
alert(subDemo1.info); //undefined
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈
subDemo1.family.push('爷爷','奶奶');
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈
//测试构造函数传递参数
alert(subDemo1.name);
点评:能够继承父类型的实例属性,但是无法继承父类型的原型属性和原型方法
经典继承的优点
① 解决实例对象共享问题,通过调用父对象的构造函数来实现每个子类型(对象)的实例对象均拥有一份父类型实例属性和方法的副本
② 可以在调用call方法的时候向构造函数传递参数
经典继承的问题
① 冒充继承的方法无法实现函数的重用
② 无法继承父对象的原型属性和原型方法
组合继承
- 组合继承|伪经典继承
基本思想
① 使用原型链实现对原型属性和方法的继承
② 通过伪造(冒充)构造函数来实现对实例属性的继承
代码示例
//01 提供父类型的构造函数
function SuperType(name) {
//在构造函数中中设置实例属性,该属性为引用类型
this.family = ['哥哥','姐姐','爸爸','妈妈'];
//实例属性
this.name = name;
};
//原型方法
SuperType.prototype.showName = function () {
console.log(this.name);
}
//02 提供子类型的构造函数
function SubType(name) {
//冒充|伪造 构造参数传递参数
SuperType.call(this,name);
};
SubType.prototype = SuperType.prototype;
//SubType.prototype = new SuperType();
//02 创建父类型的实例对象,并对内部的实例化属性进行修改
var subDemo1 = new SubType('张三');
var subDemo2 = new SubType('张四');
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈
subDemo1.family.push('爷爷','奶奶');
alert(subDemo1.family); //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
alert(subDemo2.family); //哥哥,姐姐,爸爸,妈妈
//测试构造函数传递参数
subDemo1.showName(); //张三
subDemo2.showName(); //张四