面向对象的程序设计-3-继承


写在前面

  都知道,当我们读取一个对象的属性或方法的时候,会优先在这个对象上面找,如果在这个对象上找不到就会遍历他的原型,还没找到?--->原型的原型,又没找到?-->继续往上。。。

  这便是原型链的功用。下面,我探讨了一下原型链的使用与扩展,依靠原型链实现继承。

  至于什么是继承? 我的理解是,一个对象可以直接使用另一个对象的属性和方法。

 

  本文结构:

直接使用原型链 借用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承

  其中的继承方式层层递进,不断进化完善缺点。

  进化过程:  1 → 2 → 3↘

                → → 6

           4 → 5 ↗

一、直接使用原型链

  1.回顾我的上一遍文章中写的,构造函数、原型、实例之间的关系::

    每一个构造函数都有prototype指针指向一个原型对象,原型对象有一个constructor指针指向构造函数,而实例通过[[prototype]]指针共享一个原型对象。

  2.原型链继承的实现:把一个构造函数的原型对象等于另一种类型(另一种构造函数)的实例。

function SuperType() {
    this.property = 'aaa';
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType() {
    this.subproperty = 'bbb';
}
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}
var instance = new SubType();
console.log(instance)

  关系图解

 

   执行的结果:SubType.prototype 指向了一个 SuperType 的实例。并通过 SubType.prototype.getSubValue 为实例添加了方法。

  当最终实例 instance 调用 getSuperValue 方法的时候。 先遍历实例instance对象,没找到--->搜索instance.prototype(也是SuperType的一个实例),还没找到--->搜索instnce.prototype.prototype (也是SuperType.prototype);

  3.注意:

    3.1 别忘记默认的原型

      原型链可以无限长(当然为了性能,不要搞太长)。但是去到最后必然是 Object.prototype

    3.2 确定原型与实例的关系:原型链中的出现的原型都算该实例的原型

       instance instanceOf SubType // true   

         SuperType.isPrototypeOf(instance) // true 

      3.3 谨慎地定义方法: 因为是修改了SubType的原型对象,因此必须在替换之后才定义原型上的方法: SubType.prototype.getSubTypeValue() 并且不能使用字面量,不然又换了一个新对象。

  4.直接使用原型链继承的优缺点

     SuperType构造函数中若使用了引用类型值。就会放映在SubType的原型上。在原型上出现引用类型,容易被实例修改。影响所有的实例。

     使用 SubType 创建对象的时候,不能再给SuperType传递参数了。因为SubType.prototype 已经是一个SuperType的实例了。

 

 二、借用构造函数实现继承

  为解决直接使用原型链继承中不能给 SuperType 传递参数的问题以及引用类型值问题而生。

function SuperType(name) {
    this.colors = ['red','blue','green'];
    this.name = name;
}

function SubType(name) {
    SuperType.call(this, name);
}

var instance1 = new SubType('jody');
var instance2 = new SubType('miaowwwww');
instance1.colors.push('black');
console.log(instance1)
console.log(instance2);
console.log(instance2.name)

 

   首先要知道,构造函数只不过是在特定的环境(this)中执行代码,并为它定义属型而已。因此,大可以在SubType中执行以下SuperType,这样一来,SubType 就获取 SuperType 的属性和方法。

   优点:解决了原型链继承的引用类型问题,以及 传递参数的问题

   缺点:若仅仅使用借用构造函数,无法避免构造函数模式的问题,(方法都在实例中,方法的复用性降低了),并且instance1 并不是SuperType 的一个实例,无法使用instanceOf,isPrototypeOf.

 

三、组合继承

  鉴于借用构造函数继承的缺点,当然不会仅仅借用构造函数啦。

  组合继承又称伪经典继承,结合了原型链和借用构造函数 两种技术。(相信,大家看完借用构造函数的缺点就已经想到了)

function SuperType(name) {
    this.colors = ['red','blue','green'];
    this.name = name;
}

SuperType.prototype.sayName = function() {
    console.log(this.name);
}
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

// 继承方法、
SubType.prototype = new SuperType();    // 为了使用SuperType.prototype上的方法,所以要添加
SubType.prototype.constructor = SubType; //new 的时候要调用这个constructor?并没有,这个属性只是标记这个对象是这个类型,多一个判断方法而已。这里仅仅是补全替换prototype导致的constructor的缺失
SubType.prototype.sayAge = function() {    // 在SubType原型上,同时也是SuperType的实例
    console.log(this.age);
}

var instance1 = new SubType('jody', 22);
console.log(instance1)

 

    这种方式确实完美解决了传递参数,引用类型,公共方法重用的缺点。同时也可以被 instanceOf 和 isPrototypeOf() 识别。

    但是它产生了新的问题:SubType实例含有(colors,name)属性,SubType.prototype也有(colors,name)这部分属性冗余了。(这个问题将在 六、组合式继承中解决)

    

 四、原型式继承

// 原型式继承即通过object函数实现
function object(o) {
    function F() {};
    F.prototype = o;
    return new F();
}

// 实例
var person = {
    name: 'miaowwwww',
    friends: ['aaa', 'bbb']
};

var person1 = object(person);
person1.name = 'jody';
person1.friends.push('ccc');
console.log(person1)

 

   原型式继承跟原型链是一个理念(把原型指向另一种类型的对象(实例))。把空构造函数的原型指向一个对象,然后创建空构造函数的实例,并放回。

  4.1 object函数在es5 中得到了实现:Object.create(obj, defineProperties) :第二个参数而可选,设置属性,及其描述,描述默认false

var person2 = Object.create(person, {
    name:{
        value: 'dd',
        writable: false
    },
    age: {
        value: 22
    }
})
console.log(person2)

  4.2 缺点:跟原型链一样,是原型上的引用类型值

 

五、寄生式继承

  5.1 寄生式是在原型式的基础上的改进。   —_— 只是把定义实例属性的逻辑放到一个函数里面了而已

function craateAnother(original) {
    var clone = Object.create(original);
    clone.sayName = function() {
        console.log(this.name);
    };
    return clone;
}
var person = { name: 'jody', friends: ['aa','bb']};
var person1 = createAnother(person);

 

   5.2 缺点:公共的属性和方法(如:sayName)不能复用。

 

六、寄生组合式继承

   这是 三、组合继承 和 五、寄生式继承的技术组合。(组合继承使用寄生式继承修改了,组合继承的缺点。

  1.先贴出组合继承的代码理解一下:

    组合继承的缺点:两次调用SuperType(),  导致的 SubType.prototype产生了冗余的(name,colors...)等SuperType的属性。

function SuperType(name) {
    this.name = name;
    this.colors = ['aa','bb'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}
function SubType(name, age) {
    SuperType.call(this, name);        //    第二次执行SuperType
    this.age = age;
}
SubType.prototype = new SuperType();    //第一次执行SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    console.log(this.age);
}

 

   既然知道 SubType.prototype = new SuperType(); 只是为了获取SuperType.prototype。 那么可以绕过执行SuperType,直接获取SuperType.prototype。(这便是寄生式的作用)

  2 寄生式继承的用处 : 使用Object.create() 创建一个空的实例,并且该实例的prototype 指向了 SuperType.prototype. (干净,无冗余属性)

function inheritPrototype(subType, superType) {
    var emptyInstance = Object.create(superType.prototype);
    emptyInstance.constructor = subType; //补充constructor的缺失
    subType.prototype = emptyInstance;
}

 

  3.寄生组合式继承代码

function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType; //补充constructor的缺失
    subType.prototype = prototype;
}
function SuperType(name) { this.name = name; this.colors = ['aa','bb']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); // SubType.prototype = SuperType.prototype; //不可以这么做,因为sayAge会添加在SuperType.prototype中 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var instance = new SubType('jody', 22); console.log(instance) console.log(instance.name)

 

 

   至此: 寄生组合式继承被认为是引用类型最理想的继承范式。

 

 备注:(接受指导与批评)

   本文是阅读高级程序设计(第三版)P162~P174 的理解与总结。