Road to Skill程序员

JavaScript 的动态原型组合继承

2016-03-25  本文已影响161人  混沌边缘的伊卡洛斯
动态原型组合继承

JavaScript ES5 的类继承

Zakas久负盛名的《JavaScript高级程序设计(第三版)》完成于2012年,当时ES5刚刚成为标准,ES6还没有浮出水面,class还只是是保留字,由于ES5对类的支持不够完善,特别是类的构造和继承,Zakas用了整整一章来解释,第六章第三部分在讨论类继承时,提到了6种继承方式:

  1. 原型链
  2. 借用构造函数
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合式继承

作者最为推崇的是第三种组合继承方式,但是在后续的章节中,也提到了这种方式的缺点,那就是父类的构造函数实际上被调用了两次,在后续的寄生组合继承模式中有一些改进,用如下函数消除多余的一次原型副本创建(见原书P172):

function inheritPrototype(subType, superType){
    var proto = object(superType.prototype);
    proto.constructor = subType;
    subType.prototype = proto;
}

这个函数创建一个父类原型副本,将父类的方法传递给了子类,但在这个模式中,虽然省略了创建父类型的副本,但还是创建了父类原型的一个副本,能不能连父类型原型副本的构造步骤也省略呢?

在其他面向对象语言中,子类重用父类的方法一般是通过函数指针的方法实现的,JavaScript的函数没有签名,子类不能直接获得函数方法,但是可以显示地将父类方法赋予给子类,在这个思路下,我尝试修改inheritPrototype函数,将父类原型的函数直接赋予给子类原型,既达到了重用的目的,又避免了多余的副本创建,代码示例如下:

function inheritPrototype(sub, base) {
  for (var propertyName in base.prototype) {
    if (typeof base.prototype[propertyName] === 'function') {
      sub.prototype[propertyName] = base.prototype[propertyName];
    }
  }
}

这个函数遍历父类原型中所有的方法,然后将方法直接赋予给子类原型,这样子类就可以完全重用父类的功能代码,也避免了创建对象。

改造之后,不仅达到了预期,还可以使用构造函数来创建类的实例,类的行为和其他的OO语言也没有区别,完整代码如下所示,在nodejs环境中运行一切Ok。

'use strict';
//===================
main();
//===================
function main() {

    var tmp1 = new Person('Dani', 28);
    tmp1.sayName();
    tmp1.sayAge();

    var tmp2 = new Worker('Kayden', 34, 'Actress');
    tmp2.sayName();
    tmp2.sayAge();
    tmp2.sayJob();

    var tmp3 = new Worker('Luna', 30, 'Writer');
    tmp3.friends.push('Sunny');
    tmp3.sayName();
    tmp3.sayAge();
    tmp3.sayJob();

    return 0;
}

function printf(value) {
    console.log(value);
}

// sub类继承base类原型的所有方法
function inheritPrototype(sub, base) {
  for (var propertyName in base.prototype) {
    if (typeof base.prototype[propertyName] === 'function') {
      printf('Subtype inherited a function: ' + propertyName);
      sub.prototype[propertyName] = base.prototype[propertyName];
    }
  }
}

//父类
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Jenna', 'Carrera', 'Tori'];
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function() {
            printf(this.name);
        };
        Person.prototype.sayAge = function() {
            printf(this.age);
        };
    }
}

// 子类
function Worker(name, age, job) {
    Person.call(this, name, age);
    this.job = job;
    if (typeof this.sayName !== 'function') {
        // 组合继承方式,为了重用父类方法,构造了一个多余的父类
        // Worker.prototype = new Person(); 
        
        // 寄生组合方式,改造就在此函数之内
        inheritPrototype(Worker, Person);

        Worker.prototype.sayJob = function() {
            printf(this.job);
        };
    }
}

写在后面的话

ES6已经完全支持class,构造和继承变得非常简单,和其他OO语言已经大同小异了,不过考虑到目前还有很多浏览器对ES6的支持不够完善,ES5的类继承还会陪伴我们走过很长一段时间。

上一篇 下一篇

猜你喜欢

热点阅读