ES5实现继承的六种方式
原型链
利用原型链让一个引用类型继承另一个引用类型的属性和方法。
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); console.log(instance.getSuperValue()); console.log(instance instanceof SubType); console.log(instance instanceof SuperType); console.log(instance instanceof Object); console.log(SubType.prototype.isPrototypeOf(instance)); console.log(SuperType.prototype.isPrototypeOf(instance)); console.log(Object.prototype.isPrototypeOf(instance));
|
缺点:
- 来自原型对象的引用属性是所有实例共享的。
- 创建子类实例时,无法向父类构造函数传参。
举例如下:
function SuperType () { this.colors = ['red', 'blue', 'green']; }
function SubType () {
} SubType.prototype = new SuperType();
var instance1 = new SubType(); instance1.colors.push('black'); console.log(instance1.colors); var instance2 = new SubType(); console.log(instance2.colors);
|
借用构造函数
在子类构造函数的内部通过call()以及apply()调用父类构造函数。
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green'];
this.getName = function () { return this.name; } }
function SubType (name) { SuperType.call(this, name);
this.age = 20; }
var instance1 = new SubType('Tom'); instance1.colors.push('black'); console.log(instance1.name); console.log(instance1.getName()); console.log(instance1.age); console.log(instance1.colors); var instance2 = new SubType('Peter'); console.log(instance2.name); console.log(instance2.getName()); console.log(instance2.age); console.log(instance2.colors);
|
可以看到,借用构造函数实现继承,解决了原型链继承的两个问题,既可以在新建子类实例的时候给父类构造函数传递参数,也不会造成子类实例共享父类引用变量。
但是你注意到了吗,这里我们把父类方法也写在了SuperType()构造函数里面,可以像前面一样写在SuperType.prototype上吗?
答案是不可以,必须写在SuperType()构造函数里面。因为这里是通过调用SuperType.call(this)来实现继承的,并没有通过new生成一个父类实例,所以如果写在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; }
SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); };
var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); console.log(instance1.colors); instance1.sayName(); instance1.sayAge();
var instance2 = new SubType('Peter', 30); console.log(instance2.colors); instance2.sayName(); instance2.sayAge();
|
缺点: 调用了两次父类构造函数,一次通过SuperType.call(this)调用,一次通过new SuperType()调用。
原型式继承
不使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。
function object (o) { function F() {} F.prototype = o; return new F(); }
var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] };
var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person); yetAnotherPerson.name = 'Linda'; yetAnotherPerson.friends.push('Barbie');
console.log(anotherPerson.friends); console.log(yetAnotherPerson.friends); console.log(person.friends);
|
Object.create()在传入一个参数的情况下与前面写的object()方法的行为相同
var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] };
var anotherPerson = Object.create(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob');
var yetAnotherPerson = Object.create(person, { name: { value: 'Linda', enumerable: true } }); yetAnotherPerson.friends.push('Barbie');
console.log(anotherPerson.friends); console.log(yetAnotherPerson.friends); console.log(person.friends);
|
缺点:和原型链继承一样,所有子类实例共享父类的引用类型。
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数内部以某种形式来做增强对象,最后返回对象
function object (o) { function F() {} F.prototype = o; return new F(); }
function createAnother (o) { var clone = object(o); clone.sayHi = function () { console.log('Hi'); } return clone; }
var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] };
var anotherPerson = createAnother(person); anotherPerson.sayHi(); anotherPerson.friends.push('Rob'); console.log(anotherPerson.friends); var yerAnotherPerson = createAnother(person); console.log(yerAnotherPerson.friends);
|
缺点: 和原型链式继承一样,所有子类实例共享父类引用类型。 和借用构造函数继承一样,每次创建对象都会创建一次方法。
寄生组合式继承
将寄生式继承和组合继承相结合,解决了组合式继承中会调用两次父类构造函数的缺点。
组合继承在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors;它们都是 SuperType 的实例属性,只不过现在位于 SubType的原型中。当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
其背后的基本思路是:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类的prototype,然后再将结果指定给子类的prototype。
function object(o) { function F() { } F.prototype = o; return new F(); }
function inheritPrototype(SubType, SuperType) { var prototype = object(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); };
var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); instance1.sayName(); console.log(instance1.colors);
var instance2 = new SubType('Peter', 30); instance2.sayAge(); instance2.sayName(); console.log(instance2.colors);
|
寄生组合式继承的高效率体现在它只调用了一次SuperType构造函数,并且因此避免了再SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变。因此,还能够正常使用instanceof和isPrototypeOf()。
ES6实现继承
class SuperType { constructor(name) { this.name = name; this.colors = ["red", "blue", "green"]; }
sayName() { console.log(this.name); }; }
class SubType extends SuperType { constructor(name, age) { super(name);
this.age = age; }
sayAge() { console.log(this.age); } }
var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); instance1.sayName(); console.log(instance1.colors);
var instance2 = new SubType('Peter', 30); instance2.sayAge(); instance2.sayName(); console.log(instance2.colors);
|