我们这里这里主要讨论js中的主要继承方式,包括构造函数继承,原型继承,组合式继承(原型继承和构造函数继承的组合),寄生继承(原型继承的变形)和寄生组合式继承(寄生继承和构造函数继承的组合)。
- 构造函数继承
function SuperType(name) {
this.name = name
this.colors = ['red', 'green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name)
this.name = name;
this.age = age;
}
var sub1 = new SubType('zyp1', 18)
sub1.colors.push('yellow')
console.log('sub1', sub1)
var sub2 = new SubType('zyp2', 18)
console.log('sub2', sub2)
得到的sub1和sub2的结果如下:
可以看到,构造函数继承就是在子类中调用父类的方法,从而实现继承父类的实例属性,它有2个优点:1.每个子类实例继承的父类的实例属性在堆空间中是占用不同内存的,因此修改一个子类实例继承自父类的实例属性并不会对另一个子类的实例造成任何影响(这里的colors属性);2.可以通过在子类构造函数中调用父类构造函数时向其传递参数(这里的name属性)。但是这里也有一个很大的问题:子类不可以继承父类原型上的原型方法。那么就谈不上方法复用了,因此一般不会单独使用构造函数继承。
2. 原型继承
function SuperType() {
this.name = 'zyp1'
this.colors = ['red', 'green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
this.name = name;
this.age = age;
}
SubType.prototype = new SuperType()
SubType.prototype.sayHi = function() {
console.log('hi')
}
var sub1 = new SubType('zyp1', 18)
sub1.colors.push('yellow')
console.log('sub1', sub1)
sub1.sayName()
var sub2 = new SubType('zyp2', 18)
console.log('sub2', sub2)
sub2.sayName()
得到的sub1和sub2结果如下:
可以看到,原型继承就是将父类的实例赋给子类的原型对象,这样父类的实例属性就变成了子类的原型属性,同时子类也可以继承父类的原型方法和原型属性(如果有的话)。但是这种方法同样有一个重大缺点,就是这种方式继承得到的父类的实例属性如果是引用类型的话,子类实例对其进行的操作是会相互影响的(如这里的colors),因为我们知道对象的原型属性是共用的。因此一般情况下也不会单独使用原型继承。
3. 组合式继承
function SuperType() {
this.name = 'zyp1'
this.colors = ['red', 'green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name)
this.name = name;
this.age = age;
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType //原型继承会将子类原型上的constructor属性丢失,这里加上
SubType.prototype.sayHi = function() {
console.log('hi')
}
var sub1 = new SubType('zyp1', 18)
sub1.colors.push('yellow')
console.log('sub1', sub1)
sub1.sayName()
var sub2 = new SubType('zyp2', 18)
console.log('sub2', sub2)
sub2.sayName()
得到的sub1和sub2结果如下:
可以看到,组合式继承就是将构造函数继承和原型继承进行了组合,构造函数继承实现对父类实例属性的继承,原型继承实现对原型方法和原型属性的继承。但是这里也有一个缺点就是父类构造函数被调用了2次,产生的结果就是父类的实例属性同时存在于子类实例的实例属性和原型属性中。
4. 寄生式继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
var obj = {
name: 'zyp',
age: 18,
color: ['red', 'green'],
}
function createAnother(o) {
var clone = object(o)
clone.sayHi = function() {
alert('hi')
}
return clone
}
var clone1 = createAnother(obj)
clone1.color.push('yellow')
console.log('clone1',clone1)
var clone2 = createAnother(obj)
console.log('clone2',clone2)
得到的clone1和clone2的结果如下:
可以看到,单纯的寄生式继承实际上就是用object函数封装了子类继承父类的过程,其实实际上就是实现了原型继承,那么显然跟原型继承一样,就是这种方式继承得到的父类的实例属性如果是引用类型的话,子类实例对其进行的操作是会相互影响的(如这里的color)。
5. 寄生组合式继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.age);
}
var sub1 = new SubType('zyp1', 18)
sub1.colors.push('yellow')
console.log('sub1', sub1)
var sub2 = new SubType('zyp2', 18)
console.log('sub2', sub2)
得到的sub1和sub2的结果如下:
寄生组合式继承通过构造函数实现对实例属性的继承,通过寄生继承的方式来实现对原型方法和原型属性的继承,该方法规避了组合继承方式中调用父类构造函数两次造成的问题,因此这种方式得到的子类实例中仅出现一次父类的实例属性,目前被认为是最佳实践。