继承是面向对象编程的一个基本特征,在ES6之前,Javascript并没有提供接口去实现继承,于是很多人就使用Javascript的特性原型链
来实现继承,继承方式主要分成6种类型。在ES6之后,可以使用extends
关键字直接实现继承。
原型链继承
原型链继承的思路主要是利用原型链的思路,使子类的原型对象prototype
指向父类实例的原型指针对象__proto__
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function SuperType() { this.property = true; }
SuperType.prototype.getSuperValue = function() { return this.property; }
function SubType() { }
SubType.prototype = new SuperType()
var instance = new SubType() console.log(instance.getSuperValue());
|
缺点
- 导致引用对象实例被共享,即修改一个引用对象会导致另一个对象也跟着修改
借用构造函数继承
借用构造函数主要是利用的是this
指向的思想,在子类的构造函数中,使父类显示绑定子类的this
指针,因此子类的this
指针就添加了父类的实例。借用构造函数解决了原型链继承引用对象共享的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function SuperType() { this.color = ['yello', 'blue', 'green']; }
function SubType() { SuperType.call(this); }
var instance1 = new SubType(); var instance2 = new SubType(); instance1.color.push('red'); console.log(instance1.color);
console.log(instance2.color);
|
缺点
组合继承
组合继承的思想就是把原型链继承
与借用构造函数
结合起来,然后综合使用它们的优点。实现方式是继承父类的属性时,使用借用构造函数;继承原型属性时就使用原型链方式,但是使用完后需要把子类的constructor
对象指回子类的构造函数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function SuperType(name) { this.name = name; this.color = ['red', 'green', 'blue']; }
SuperType.prototype.sayName = function() { console.log(this.name); }
function SubType(name, age) { SuperType.call(this, name); this.age = age; }
SubType.prototype = new SuperType();
SubType.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); }
var ins1 = new SubType('peter', 28); var ins2 = new SubType('mary', 29); ins1.color.push('yellow'); console.log(ins1.color); ins1.sayAge(); ins1.sayName(); console.log(ins2.color); ins2.sayAge(); ins2.sayName();
|
缺点
- 需要调用两次父类构造函数
- 实例对象和原型对象属性name、color(ins1.name,ins1.proto.name)
原型式继承
先创建一个空白的构造函数,然后传入对象作为这个构造函数的原型,最后这个构造函数的实例。本质就是对传入的对象进行一次浅拷贝。ES5的Object.create()就是原型式继承的一种实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function object(obj) { function F() {} F.prototype = obj return new F(); }
var person = { name: 'peter', friends: ['mary', 'jhon', 'leo'] }
var anotherPerson = object(person); anotherPerson.name = 'greg'; anotherPerson.friends.push('rob');
var yetAnotherPerson = object(person); yetAnotherPerson.name = 'linda'; yetAnotherPerson.friends.push('barbie');
console.log(person.friends); console.log(person.name); console.log(anotherPerson.friends); console.log(anotherPerson.name); console.log(yetAnotherPerson.friends); console.log(yetAnotherPerson.name);
|
缺点
寄生式继承
寄生式继承的思想主要是在原型式继承
的基础上,使用工厂模式
进行封装,最后返回新的实例。
1 2 3 4 5 6
| function createAnother(original) { var clone = object(original); clone.sayHi = function() { console.log('hi'); } }
|
缺点
寄生组合式继承
这个继承的思想和组合继承
类似,但是该模式是组合寄生继承
和借用构造函数继承
组合起来,避免调用2次父类的构造函数,从而达到性能优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function inheritPrototype(subType, superType) { const prototype = Object.create(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green']; }
SuperType.prototype.sayName = function() { console.log(this.name); }
function SubType(name, age) { SuperType.call(this, name); this.age = age; }
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() { console.log(this.age); }
const a = new SubType('peter', 2); a.sayName(); a.sayAge();
|
ES6的extends
ES6继承的核心思想其实和寄生组合继承
的思想是一致的,但是ES6的继承是先创建父类的实例对象this
,然后用子类的构造函数修改this
,因为子类没有自己的this
对象,所以必须先调用父类的super方法(ES5的是可以先调用this
的),否则就会报错。