在js高程中有介绍:
OO语言支持两种继承方式
- 接口继承:只继承方法签名
- 实现继承:继承实际的方法(ECMAScript支持)
实现继承主要是依靠原型链
原型式继承 | 组合继承 | 寄生式继承 | 寄生组合继承 |
---|---|---|---|
不用在预先定义构造函数的情况下实现继承,本质是执行给定对象的浅复制,而复制得到的副本还可以进行改造 | 使用原型链共享属性和方法,通过借用构造函数继承实例 | 基于某个对象创建一个对象,增强对象,然后返回对象 | 为了解决组合继承由于多次调用超类型构造函数而导致的低效率问题 |
一、原型链继承
1. 原型链继承核心
将父类的实例作为子类的原型
2. 原型链
原型链的基本构建
通过将一个类型的实例赋给另一个构造函数的原型去实现原型链
这样子类型可以访问超类型的所有属性和方法
存在的问题是:
子类型能够访问超类型所有的属性和方法
解决方案:在子类型构造函数的内部调用超类型的构造函数
可以做到每个实例都有自己的属性,同时还能保证只使用构造函数模式来定义类型
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象
原型对象包括一个指向构造函数的指针
实例包括一个指向原型对象的内部指针
3. 原型链实现继承
function SuperType(){ //父代
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType(); //子代继承父代
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType(); //实例
console.log(instance.getSuperValue()) //true
instance.getSuperValue() 时会经历三个搜索步骤:
- 搜索实例
- 搜索SubType.prototype
- 搜索SuperType.prototype
a. 确定原型和实例的关系
- instanceof
console.log(instance instanceof Object) //true
console.log(instance instanceof SuperType) //true
console.log(instance instanceof SubType) //true
- isPrototypeof()
只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
console.log(Object.prototype.isPrototypeOf(instance)) //true
console.log(SuperType.prototype.isPrototypeOf(instance)) //true
console.log(SubType.prototype.isPrototypeOf(instance)) //true
b. 不要使用对象字面量创建原型方法
function SuperType(){ //父代
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType(); //子代继承父代
//对象字面量创建(导致上一行代码失效)
SubType.prototype = {
getSubValue : function(){
return false;
},
someOtherMethod : function(){
return false;
}
}
var instance = new SubType(); //实例
console.log(instance.getSuperValue()) //true
对象字面量创建的是一个object 的实例,并非SuperType 的实例,导致SubType和SuperType 之间的联系被切断,他们之间已经没有关系了
c. 特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
d. 缺点
- 没有办法在不影响所有对象实例的情况下,向超类型的构造函数中传递参数
- 无法实现多重继承
- 来自原型对象的所有属性被所有实例共享
- 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
二、构造函数继承
1. 构造函数继承核心
在子类型构造函数的内部调用超类型的构造函数,等于是复制父类的实例属性给子类(没有用到原型)
function SuperType(){
this.color = ["red", "blue", "green"];
}
function SubType(){
SuperType.call(this); //继承了SuperType
}
var instance = new SubType();
instance.color.push("black");
console.log(instance.color); //["red", "blue", "green","black"]
实际上在SubType 中调用了SuperType 构造函数,执行了SuperType 中定义的所有对象初始化代码
优势:
- 可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this, "welkin"); //继承了SuperType
this.age = 20; // 实例属性
}
var instance = new SubType();
console.log(instance.name); //welkin
console.log(instance.age); //20
- 解决了子类实例共享父类引用属性的问题
- 可以实现多继承(call 多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型的属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
三、组合继承
1. 核心思想
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
通过调用父类构造,继承父类的属性并保存传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function a(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
a.prototype.sayName = function(){
console.log(this.name);
}
function b(name, age){
a.call(this, name);//将a 的实例赋给b 的原型
this.age = age;
}
b.prototype = new a();
b.prototype.constructor = b;
b.prototype.sayAge = function(){ //在该原型上定义了方法
console.log(this.age);
}
var instance = new b("welkin", 20);
instance.colors.push("black");
console.log(instance.colors); //["red", "blue", "green", "black"]
instance.sayAge(); //20
instance.sayName(); //welkin
特点:
- 可以继承实力属性的方法,也可以继承原型属性的方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享的问题
- 可传参,函数可以复用
四、寄生式继承
1. 寄生继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后返回增强对象的那个方式
function a(original){
var clone = object(original); //通过函数调用创建一个新的对象
clone.sayHi = function(){ //以某种方式来增强这个对象
console.log("hi");
}
return clone; //返回这个对象
}
缺点:
- 不能做到函数调用
- 无论在什么情况下都会调用两次超类型构造函数
- 创建子类型
- 子类型构造函数内部
- 子类型最终会包含超类型对象的全部实例属性,但我们在调用子类型构造函数时,重写这些属性
2. 寄生组合继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承
核心:通过寄生方式,砍掉父类的实例属性,在调用两次父类的构造时,不用初始化两次实例方法的属性,避免组合继承的缺点
//组合继承
function a(name){
this.name = name;
this.color = ["red", "blue", "green"];
}
a.prototype.sayName = function(){
console.log(this.name);
}
function b(name, age){
a.call(this, name); //第二次调用a
this.age = age;
}
b.prototype = new a(); //第一次调用a
b.prototype.constructor = b;
b.prototype.sayAge = function(){
console.log(this.age);
}
小结
创建对象的几种模式:
- 工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
- 构造函数模式:可以创建自定义引用类型,可以像创建内置对象实例一样使用new 操作符(缺点是,每个成员无法得到复用,包括函数)
- 原型模式:使用构造函数的prototype 属性来指定应该共享的属性和方法。
- 组合使用原型模式和构造模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法