前言:
继承是面向对象语言中的一个最为人津津乐道的概念。许多oop语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法前面,而实现继承则继承实际
的方法。如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要依靠原型链来实现。
继承方式:
1.原型链继承
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
}
function SubType(){
this.subproperty=false;
}
//通过创建SuperType的实例继承了SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue()); //true
缺点:
- (1)包含引用类型值的原型属性会被所有实例共享,这会导致对一个实例的修改会影响另一个实例;
- (2) 在创建子类型的实例时,不能向父类的构造函数中传递参数。
下面的例子说明原型链继承的缺点(1)
function SuperType(){
this.colors=["red", "blue", "green"];
}
function SubType(){
}
//继承了SuperType
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
var instance2=new SubType();
alert(instance2.colors); //red,blue,green,black
2.盗用构造函数继承,经典继承
在解决原型中包含引用类型值所带来的问题中,使用盗用构造函数技术来解决。
借用构造函数的基本思想,即在子类型构造函数的内部调用父类型构造函数。
函数只不过是在特定环境中执行代码的对象,因此使用apply()
和call()
方法可以在新创建的对象上执行构造函数。
function SuperType(name){
this.name=name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this,"mary");
//实例属性
this.age=22;
}
var instance=new SubType();
alert(instance.name); //mary
alert(instance.age); //29
缺点:
- (1)无法避免构造函数模式存在的问题,方法都在构造函数中定义,因此无法复用函数;
- (2)子类不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。
3.组合继承
组合继承,指的是将原型链和盗用构造函数的技术组合到一起。
思路是使用原型链实现对原型方法的继承,而通过盗用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。
function SuperType(name){
this.name=name;
this.colors=["red", "blue", "green"];
}
SuperType.prototype.sayName=function(){
alert(this.name);
};
function SubType(name, age){
//继承属性 使用借用构造函数实现对实例属性的继承
SuperType.call(this,name); // 第一次调用 SuperType
this.age=age;
}
//继承方法 使用原型链实现
SubType.prototype=new SuperType(); // 第二次调用 SuperType
SubType.prototype.constructor=SubType;
subType.prototype.sayAge=function(){
alert(this.age);
};
var instance1=new SubType("mary", 22);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //mary
instance1.sayAge(); //22
var instance2=new SubType("greg", 25);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //greg
instance2.sayAge(); //25
组合继承避免了原型链和盗用构造函数的缺点,融合了他们的优点,是JavaScript中最常用的继承模式。
缺点:无论在什么情况下,都会调用两次父类构造函数:一次是在子类构造函数中调用,一次是在子类原型创建时调用。
4.原型式继承:
原型式继承式借助原型可以基于已有的对象创建新对象。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
在object函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上来说,object对传入其中的对象执行了一次浅复制。
var person = {
name: 'Gaosirs',
friends: ['Shelby', 'Court']
}
var anotherPerson = object(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court']
在这个例子中,person作为另一个对象的基础,把person传入object中,该函数就会返回一个新的对象。
这个对象将person作为原型,所以它的原型中就包含一个基本类型和一个引用类型。
这意味着如果还有另外一个对象关联了person,anotherPerson修改数组friends的时候,也会体现在这个对象中。
Object.create()
方法
ES5通过Object.create()
方法规范了原型式继承,可以接受两个参数,一个是用作新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object一样,除了object不能接受第二个参数以外。
var person = {
name: 'Gaosirs',
friends: ['Shelby', 'Court']
}
var anotherPerson = Object.create(person)
console.log(anotherPerson.friends) // ['Shelby', 'Court']
5.寄生式继承
寄生式继承的思路寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程中函数,该函数在内部以某种方式来增强对象,最后返回对象。
function createAnother(o) {
var clone = Object.create(o) // 创建一个新对象
clone.sayHi = function() { // 添加方法
console.log('hi')
}
return clone // 返回这个对象
}
var person = {
name: 'GaoSirs'
}
var anotherPeson = createAnother(person)
anotherPeson.sayHi()
缺点:使用寄生式继承来为对象添加函数,会因为做不到函数复用而降低效率,这个与构造函数模式类似。
6.寄生组合式继承
在前面说的组合模式(原型链+构造函数)中,继承的时候需要调用两次父类构造函数。
使用寄生组合式继承,可以规避这些问题。
这种模式通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
本质上就是使用寄生式继承来继承父类的原型,再将结果指定给子类型的原型。
function inheritPrototype(subType,superType){
var prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
该函数实现了寄生组合模式的最简单形式。
这个函数接受两个参数,一个子类,一个父类。
第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三步将子类的原型指向这个副本。
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, job) {
// 继承属性
SuperType.call(this, name)
this.job = job
}
// 继承
inheritPrototype(SubType, SuperType)
var instance = new SubType('Gaosirs', 'student')
instance.sayName()
补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示可以更容易理解。
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name,job) {
//继承属性
SuperType.call(this,name)
this.job = job
}
//继承
SubType.prototype = Object.create(SuperType.prototype)
//修复constructor
SubType.prototype.constructor = SubType
var instance = new SubType('Gaosirs','student')
instance.sayName()
优点:只调用一次supertype构造函数,因此避免再subtype.prototype上创建不必要的,多余的属性,与此同时,原型链还能保持不变,还能正常使用instanceof和isPrototypeOf(),因此,寄生组合式继承被认为是引用类型最理想的继承方式。
7.ES6中Class… extends关键字实现继承
基本用法:Class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰很多。
class Colorpoint extends Point {
constructor(x,y,color){
super(x,y); //调用父类的constructor(x,y)
this.color = color
}
toString(){
//调用父类的方法
return this.color + ' ' + super.toString();
}
}
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。因此,只有调用super之后,才可以使用this关键字。
prototype
和 __proto__
:一个继承语句同时存在两条继承链:一条实现属性继承,一条实现方法的继承。
class A extends B {}
A.__proto__ === B;
A.prototype.__proto__ === B.prototype;