How do I do JavaScript Prototype Inheritance (chain of prototypes)


This is a question for the guru of JavaScript. I'm trying to do work with JavaScript prototype model more elegant. Here is my utility code (it provides real chain of prototypes and correct work with instanceof operator):

function Class(conf) {
  var init = conf.init || function () {};
  delete conf.init;

  var parent = conf.parent || function () {};
  delete conf.parent;

  var F = function () {};
  F.prototype = parent.prototype;
  var f = new F();
  for (var fn in conf) f[fn] = conf[fn];
  init.prototype = f;

  return init;
};

It allows me to do such thigns:

var Class_1 = new Class({
  init: function (msg) { // constructor
    this.msg = msg;
  },

  method_1: function () {
    alert(this.msg + ' in Class_1::method_1');
  },

  method_2: function () {
    alert(this.msg + ' in Class_1::method_2');
  }
});

var Class_2 = new Class({
  parent: Class_1,

  init: function (msg) { // constructor
    this.msg = msg;
  },

  // method_1 will be taken from Class_1

  method_2: function () { // this method will overwrite the original one
    alert(this.msg + ' in Class_2::method_2');
  },

  method_3: function () { // just new method
    alert(this.msg + ' in Class_2::method_3');
  }
});

var c1 = new Class_1('msg');
c1.method_1(); // msg in Class_1::method_1
c1.method_2(); // msg in Class_1::method_2

var c2 = new Class_2('msg');
c2.method_1(); // msg in Class_1::method_1
c2.method_2(); // msg in Class_2::method_2
c2.method_3(); // msg in Class_2::method_3

alert('c1 < Class_1 - ' + (c1 instanceof Class_1 ? 'true' : 'false')); // true
alert('c1 < Class_2 - ' + (c1 instanceof Class_2 ? 'true' : 'false')); // false

alert('c2 < Class_1 - ' + (c2 instanceof Class_1 ? 'true' : 'false')); // true
alert('c2 < Class_2 - ' + (c2 instanceof Class_2 ? 'true' : 'false')); // true

My question is: Is there more simple way to do this?

Yes, there is a better way to do this.

var call = Function.prototype.call;

var classes = createStorage(),
    namespaces = createStorage(),
    instances = createStorage(createStorage);


function createStorage(creator){
  var storage = new WeakMap;
  creator = typeof creator === 'function' ? creator : Object.create.bind(null, null, {});
  return function store(o, v){
    if (v) {
      storage.set(o, v);
    } else {
      v = storage.get(o);
      if (!v) {
        storage.set(o, v = creator(o));
      }
    }
    return v;
  };
}

function Type(){
  var self = function(){}
  self.__proto__ = Type.prototype;
  return self;
}

Type.prototype = Object.create(Function, {
  constructor: { value: Type,
                 writable: true,
                 configurable: true },
  subclass: { value: function subclass(scope){ return new Class(this, scope) },
              configurable: true,
              writable: true }
});

function Class(Super, scope){
  if (!scope) {
    scope = Super;
    Super = new Type;
  }

  if (typeof Super !== 'function') {
    throw new TypeError('Superconstructor must be a function');
  } else if (typeof scope !== 'function') {
    throw new TypeError('A scope function was not provided');
  }

  this.super = Super;
  this.scope = scope;

  return this.instantiate();
}

Class.unwrap = function unwrap(Ctor){
  return classes(Ctor);
};

Class.prototype.instantiate = function instantiate(){
  function super_(){
    var name = super_.caller === Ctor ? 'constructor' : super_.caller.name;
    var method = Super.prototype[name];

    if (typeof method !== 'function') {
      throw new Error('Attempted to call non-existent supermethod');
    }

    return call.apply(method, arguments);
  }

  var Super = this.super,
      namespace = namespaces(Super),
      private = instances(namespace)

  var Ctor = this.scope.call(namespace, private, super_);
  Ctor.__proto__ = Super;
  Ctor.prototype.__proto__ = Super.prototype;
  namespaces(Ctor, namespace);
  classes(Ctor, this);
  return Ctor;
}

example usage:

var Primary = new Class(function(_, super_){
  var namespace = this;
  namespace.instances = 0;

  function Primary(name, secret){
    this.name = name;
    _(this).secret = secret;
    namespace.instances++;
  }

  Primary.prototype.logSecret = function logSecret(label){
    label = label || 'secret';
    console.log(label + ': ' + _(this).secret);
  }

  return Primary;
});


var Derived = Primary.subclass(function(_, super_){

  function Derived(name, secret, size){
    super_(this, name, secret);
    this.size = size;
  }

  Derived.prototype.logSecret = function logSecret(){
    super_(this, 'derived secret');
  }

  Derived.prototype.exposeSecret = function exposeSecret(){
    return _(this).secret;
  }

  return Derived;
});

var Bob = new Derived('Bob', 'is dumb', 20);
Bob.logSecret();
console.log(Bob);
console.log(Bob.exposeSecret());

After some research I've concluded there is no more simple way to do this.