ES5有6种方式可以实现继承,分别为:
1. 原型链继承
原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
function SuperType(){
this.name = "xinran001";
this.colors = ['pink','blue','green'];
}
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(){
this.age = 22;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function(){
return this.age;
}
SubType.prototype.constructor = SubType;
let instancel = new SubType();
instancel.colors.push('yellow');
console.log( instancel.getName() );//'xinran001'
console.log( instancel.colors );//['pink','blue','green','yellow']
let instance2 = new SubType();
console.log( instance2.colors ); //['pink','blue','green','yellow']
缺点:
- 通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。
- 在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。
2. 借用构造函数
借用构造函数的技术,其基本思想为:在子类型的构造函数中调用超类型构造函数。
function SuperType(name){
this.name = name;
this.colors = ['pink','blue','green'];
this.getColors = function(){
return this.colors;
}
}
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(name){
SuperType.call(this,name);
this.age = 22;
}
let instancel = new SubType('xinran001');
instancel.colors.push('yellow');
console.log( instancel.colors );//['pink','blue','green','yellow']
console.log( instancel.getColors() );//["pink", "blue", "green", "yellow"]
console.log( instancel.getName );//undefined
let instance2 = new SubType('Jack');
console.log( instance2.colors ); //['pink','blue','green']
console.log( instance2.getColors() );//["pink", "blue", "green"]
console.log( instance2.getName );//undefined
优点:
- 可以向超类传递参数
- 解决了原型中包含引用类型值被所有实例共享的问题
缺点:
- 方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。
3. 组合继承(原型链 + 借用构造函数)
组合继承指的是将原型链和借用构造函数技术组合到一块,从而发挥二者之长的一种继承模式。基本思路:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性。
function SuperType(name){
this.name = name;
this.colors = ['pink','blue','green'];
}
SuperType.prototype.getName = function(){
return 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(){
return this.age;
}
let instancel = new SubType('xinran001',20);
instancel.colors.push('yellow');
console.log( instancel.colors );//['pink','blue','green','yellow']
console.log( instancel.sayAge() );//20
console.log( instancel.getName() );//xinan001
let instance2 = new SubType('Jack',18);
console.log( instance2.colors ); //['pink','blue','green']
console.log( instance2.sayAge() );//18
console.log( instance2.getName() );//Jack
console.log(new SuperType('po'));
缺点:
- 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
优点:
- 可以向超类传递参数
- 每个实例都有自己的属性
- 实现了函数复用
4. 原型式继承
原型继承的基本思想:
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在object()函数内部,新建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例,从本质上讲,object()对传入的对象执行了一次浅拷贝。
ECMAScript5通过新增Object.create()方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象(可以覆盖原型对象上的同名属性),在传入一个参数的情况下,Object.create()和object()方法的行为相同。
var person = {
name:'xinran001',
hobbies:['reading','photography']
}
var personl = Object.create(person);
personl.name = 'jack';
personl.hobbies.push('coding');
var person2 = Object.create(person);
person2.name = 'Echo';
person2.hobbies.push('running');
console.log(person.hobbies);//["reading", "photography", "coding", "running"]
console.log(person.name);//xinran001
console.log(personl.hobbies);//["reading", "photography", "coding", "running"]
console.log(personl.name);//jack
console.log(person2.hobbies);//["reading", "photography", "coding", "running"]
console.log(person2.name);//Echo
在没有必要创建构造函数,仅让一个对象与另一个对象保持相似的情况下,原型式继承是可以胜任的。
缺点:
- 同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
5. 寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother(original){
var clone = object(original);//通过调用函数创建一个新对象
clone.sayHi = function(){//以某种方式增强这个对象
console.log('hi');
}
return clone;//返回这个对象
}
var person = {
name:'xinran001',
hobbies:['reading','photography']
}
var personl = createAnother(person);
personl.sayHi();//hi
personl.hobbies.push('coding');
console.log(personl.hobbies);//["reading", "photography", "coding"]
console.log(person);//{hobbies:["reading", "photography", "coding"],name: "xinran001"}
基于person返回了一个新对象personl,新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。在考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
缺点:
- 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
- 同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
6. 寄生组合式继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,基本思路:
不必为了指定子类型的原型而调用超类型的构造函数,我们需要的仅是超类型原型的一个副本,本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:
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;//指定对象
}
- 创建超类型原型的一个副本
- 为创建的副本添加 constructor 属性
- 将新创建的对象赋值给子类型的原型
至此,我们就可以通过调用 inheritPrototype 来替换为子类型原型赋值的语句:
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 = ['pink','blue','green'];
}
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
return this.age;
}
let instancel = new SubType('xinran001',20);
instancel.colors.push('yellow');
console.log( instancel.colors );//['pink','blue','green','yellow']
console.log( instancel.sayAge() );//20
console.log( instancel.getName() );//xinan001
let instance2 = new SubType('Jack',18);
console.log( instance2.colors ); //['pink','blue','green']
console.log( instance2.sayAge() );//18
console.log( instance2.getName() );//Jack
console.log(new SuperType('po'));
优点:
- 只调用了一次超类构造函数,效率更高。避免在SuberType.prototype上面创建不必要的、多余的属性,与其同时,原型链还能保持不变。
- 因此寄生组合继承是引用类型最理性的继承范式。