es5实现继承的6种方式

对关于继承的内容做一番整理。是早期编写的文章,直接看到最后的结论即可。后面可以优化一下行文的思路。

把结论图拷贝放到本文的最前面:

寄生组合式继承 才是最终的主角;而其余的几个“继承方式”,都被它借鉴了优点,所以仅仅是作为铺垫介绍。

js-inherit-sumary

原型链继承

  • 优点:js 原型链继承 适用于存放父类的方法
  • 缺点:
    • 【缺点1】不适合存放父类的属性。即,子类实例之间共享属性就容易造成属性混淆使用。
      • (参考更多的方式,我们知道定义属性应改进为在构造函数中,而不是在原型对象中。)
    • 【缺点2】(因为创造链条是 父构造函数->父实例(即子类原型)->子实例,)子类原型上存在父构造函数定义的属性(这部分属性若改为由子构造函数控制定义,会好一点)

该式的典型代码:

function Super(){
    this.value = true;
}
Super.prototype.getValue = function(){
    return this.value;
};
function Sub(){}
//Sub继承了Super:
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

var instance = new Sub();
console.log(instance.getValue());//true

再而,缺点1 演示:

function Super(){
    this.colors = ['red','blue','green'];
}
function Sub(){};
//Sub继承了Super
Sub.prototype = new Super();
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);//'red,blue,green,black'
var instance2 = new Sub();
console.log(instance2.colors);//'red,blue,green,black'

原型式继承

我们先来介绍Object.createObject.create前后,2者从表现上看好像是“浅拷贝”,但是“属性位于哪个对象上”,这点明显和“浅拷贝”不同。

function mycreate(o){ // Object.create的实现原理
    function F(){};
    F.prototype = o;
    return new F();
}

var superObj = { // 基于已有的对象创建新对象
    colors: ['red','blue','green']
};
var subObj1 = mycreate(superObj); // 等价于:subObj1 = Object.create(superObj)
subObj1.colors.push("black");

var subObj2 = object(superObj);
subObj2.colors.push("white");

console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]

前面只是使用Object.create实现「实例对象“浅拷贝”实例对象」;我们也可以应用到「原型对象“浅拷贝”原型对象」以实现“类”之间的继承,而这就是“原型式继承”:

function Parent() {}
function Child() {}

Child.prototype = Object.create(Parent.protoptype);
child.prototype.constructor = Child; // 要补上的。同时应改为 defineProperty 设置该 constructor 属性

var child = new Child();

实际上,Object.create()方法规范化了原型式继承:

  • (原型式继承只是将原型链继承进行简单的改造:改造了1个缺点,仍剩下另1个缺点:)
    • 改造了1个缺点后:Child.prototype = Object.create(Parent.protoptype):没有调用new Parent,因而对于Child.prototype ,可以不继承父类的实例上的属性(包括Parent构造函数将赋予的属性)
    • 仍然残留1个缺点:子类实例之间共享属性就容易造成属性混淆使用

寄生式继承

寄生式继承创建一个仅用于封装继承过程的函数,该函数在内部以各种定义属性来增强对象,最后返回对象:

function parasite(original){
    var clone = Object.create(original);//通过调用函数创建一个新对象
    clone.sayHi = function(){ //以各种定义属性来增强这个对象。(若再参考其他方式,更适合改为抽取到其他地方来定义属性)
        console.log("hi");
    };
    return clone;//返回这个对象
}
var superObj = {
    colors: ['red','blue','green']
};
var subObj1 = parasite(superObj);
// 上一句,2者都在原型链上平移1位。即相当于 Child.prototype = Object.create(Parent.protoptype)
subObj1.colors.push('black');
var subObj2 = parasite(superObj);
subObj2.colors.push('white');

console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]
  • 寄生式继承实际上只是原型式继承的再包装,缺点依旧。
    • 总而言之,现有改进链条为: 原型链式继承 --> 原型式继承 --> 寄生式继承
  • ( 随着之后的代码改造,将clone.sayHi抽取到其他地方来定义,才会效果更好)

借用构造函数

借用构造函数(constructor stealing)的技术(有时候也叫做伪类继承或经典继承)。

基本思想相当简单,即在子类构造函数的内部调用父类构造函数。

在代码层面,通过使用apply()和call()方法令新创建的对象this上调用构造函数:

function Super(){
    this.colors = ['red','blue','green'];
}
function Sub(){
    //继承了Super
    Super.call(this);
}
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);// ['red','blue','green','black']
var instance2 = new Sub();
console.log(instance2.colors);// ['red','blue','green']

相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类构造函数中向父类构造函数传递参数:

function Super(name){
    this.name = name;
}
function Sub(){
    //继承了Super,同时还传递了参数
    Super.call(this,"bai");
    //实例属性
    this.age = 29;
}
var instance = new Sub();
console.log(instance.name);//"bai"
console.log(instance.age);//29 

缺点:但是,如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——“方法”都在构造函数中定义,每创建一次实例就重复创建一次实例“方法”,没有复用“方法”。

组合继承

组合继承(combination inheritance)有时也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

  • (具体步骤:)
    • 通过原型链实现对原型属性和方法的继承
    • 通过借用构造函数来实现对实例属性的继承
  • 具体的效果:
    • 既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己独自的属性:【下方代码】
function Super(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
    console.log(this.name);
};
function Sub(name,age){
    //继承属性
    Super.call(this,name);// 子类构造函数内部,调用了一次父类的构造函数
    this.age = age;
}
//继承方法
Sub.prototype = new Super(); // 创建子类原型,调用了一次父类的构造函数
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    console.log(this.age);
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();//29

var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();//27
  • “寄生组合继承”相当于是它的进化版,取其优点去其缺点
    • 取 组合继承 的优点:组合了原型链和构造函数式。
    • 也消除了 组合继承 的缺点:
      • 【缺点1】会调用两次父类构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。
        • 子类最终会包含父类对象的全部实例属性,因而不得不在调用子类构造函数时重写这些属性【见代码】
      • 【缺点2】利用了原型链继承。即没有用到它的升级版–寄生式继承(即原型式继承+小包装得来) 来取缔原型链继承
function Super(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
    return this.name;
};
function Sub(name,age){
     // 第二次调用Super(),Sub.prototype又得到了name和colors两个属性,并对上次得到的属性值进行了覆盖
    Super.call(this,name);
    this.age = age;
}
//第一次调用Super(),Sub.prototype得到了name和colors两个属性
Sub.prototype = new Super(); 
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    return this.age;
};  
var instance1 = new Sub("bai",29);// 第二次调用Super()

寄生组合式继承

  • (前文提到:寄生式继承=原型式继承+小包装 )
  • 寄生组合式继承,可以从各自2个角度理解它的产生:
    • 可以说是 寄生式继承,参考 组合式继承的形式,而与 构造函数式继承组合,得到
    • 也可以说是,基于组合式继承,将其中的 原型链式继承 改造为 寄生式继承(或原型式继承),得到
  • 寄生组合式继承 受以上各种“方式”的启发,又消除了 以上各种“方式”的缺点
function parasite(original){
    var clone = Object.create(original);//通过调用函数创建一个新对象
    return clone;//返回这个对象
}

function Super(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
    return this.name;
};
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
Sub.prototype = parasite(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    return this.age;
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();//29

var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();//27

精简一点,则如下所示:

function Super(name){
  	// 父类的实例属性:
    this.name = name; 
    this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){// 父类的原型属性
    return this.name;
};


function Sub(name,age){
    Super.call(this,name); // 得到父类的实例属性
    this.age = age;
}
if(!Object.create){
  Object.create = function(proto){
    function F(){};
    F.prototype = proto;
    return new F;
  }
}
Sub.prototype = Object.create(Super.prototype);// 只继承了父类的原型属性
Sub.prototype.constructor = Sub;

var instance1 = new Sub("bai",29);// 得到父类的实例属性
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"

var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"

总结

js-inherit-sumary

文章目录
  1. 原型链继承
    1. 原型式继承
  2. 寄生式继承
  3. 借用构造函数
  4. 组合继承
  5. 寄生组合式继承
  6. 总结