JavaScript MVC: Inheritance

If you’ve ever done a lot of JS development you might find the following situation familiar: you’re loath to open up the bowels of the sweet webapp you wrote because you know inside you will find hundreds of lines of jQuery selectors stacked on top of each other. Try as you might, they just won’t be tamed simply by separating out into distinct functions. Soon you’re lost in your own code, trapped in a Borgesian labyrinth of your own making.

That was one of the first challenges we ran into when I started working at Behance: the decentralized, deframework-ized nature of the client side code. Like many other shops, JavaScript was viewed with equal parts suspicion and distaste. But as our code grew, it became increasingly impossible to continue working the same way as thousands of lines of JS collided tectonically into each other as Everests grew out of merge conflicts.

Fortunately, this is a problem that many before us have encountered and solved. The Model-View-Controller pattern has existed for quite a while. Its primary benefit is the separation of concerns that overwhelms a single-file or single-object approach. While there are several JavaScript MVC solutions already on the net, our solution had to be lightweight, Controller-oriented, and templating engine agnostic. None of the ones we came across were an exact fit for our needs.

First of all, our main concern at the time was Action Method Online, a heavily data-driven app. Every bit of the ‘view’ came from rendering client-side templates, so as to make the app almost entirely service-oriented. For a data-driven app, the complexities of interactions between every subsystem requires a framework with Controllers to represent the entity, whether it is a single Action Step, a post in a discussion, or the whole content area. The first framework we considered, Backbone.js, for all its strengths, is essentially still a Model-View-ViewModel (MVVM) framework. Its primary focus is still on the Model-to-View binding (and vice versa). While that is very powerful, AMO required something different.

Another candidate that fit the bill better was JavaScriptMVC. JavaScriptMVC had controllers, was built off of jQuery, and used many concepts from it. In JavaScriptMVC’s Controllers, property names become jQuery selectors that automatically get bound to their associated functions. That concept became problematic as I thought about the problem more. While it is handy to have, jQuery selectors implies that it is operating on a DOM, which implies that there is already a view. And separation of View and Controller logic was one of the key things I was looking for. Plus, the whole approach was a little too ‘magical’. The framework also had to be readily grasped by other developers, and the more magic, the slower developers come to terms with how it really works.

So with that in mind, and a self-imposed directive to be backward compatible with the code we had already written using Backbone.js, we set out to write our very own JavaScript MVC framework: we call it NBD.js.

The first problem to tackle is the entire class system. JavaScript’s inheritance mechanism is strictly prototypal with no understanding of `class`, and some aspects of traditional inheritance (such as inheritance of static properties). Additionally, without ES5′s Object.create(), the superconstructor of a new class is unnecessarily called just to define a subclass. We had been using Backbone.Model.extend solely for the ability to emulate classical inheritance patterns. However, it was a janky solution. We needed a more systematic approach.

Luckily, John Resig wrote an extension mechanism with similar signature to Backbone.js’s. His version was simple but still included powerful features like the ability to call a superclass’s implementation with this.super(). It served as a great starting point.

/**
 * Behanced Class
 * Built from Simple JS Inheritance by John Resig
 * Addons:
 *  - Static properties
 *  - init() auto-calls super's init()
 *  -- can prevent auto-calling with stat._
 *  - __super__ for Backbone.js compatibility
 *  - Using AMD pattern, with global fallback
 */

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype

/*global define, jQuery */
(function(root, factory) {
  if (typeof define === "function" && define.amd) {
    define(function() {
      return (root.Class = factory.apply(this, arguments));
    });
  }
  else {
    root.Class = factory();
  }
}( (jQuery && jQuery.Core) || this, function() {
  "use strict";

  var initializing = false,
      fnTest = /xyz/.test(function(){var xyz;}) ? /\b_super\b/ : /.*/,
      Klass;

  // The base Class implementation (does nothing)
  Klass = function(){};

  // Addon: inherits determines if current class inherits from superclass
  Klass.inherits = function( superclass ) {
    var ancestor;

    if ( typeof this === "function" && typeof superclass === "function" ) {
      ancestor = this.prototype;
      while ( ancestor.constructor.__super__ ) {
        ancestor = ancestor.constructor.__super__;
        if ( ancestor === superclass.prototype ) {
          return true;
        }
      }
    }

    return false;
  };

  // Create a new Class that inherits from this class
  Klass.extend = function extend(prop, stat) {
    var prototype, name, initfn, _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    prototype = new this();
    initializing = false;

    function protochain(name, fn, initfn) {
      return function() {
        var hadSuper = this.hasOwnProperty('_super'), tmp = this._super;

        // Add a new ._super() method that is the same method
        // but on the super-class
        this._super = _super[name];

        // Addon: calling up the init chain
        if (initfn) { this._super.apply(this, arguments); }

        // The method only need to be bound temporarily, so we
        // remove it when we're done executing
        try {
          return fn.apply(this, arguments);
        }
        catch(e) {
          // Empty catch for IE 8
        }
        finally {
          if (hadSuper) {this._super = tmp;}
        }
      };
    }

    function chainFn(parent, child) {
      return function() {
        parent.apply(this, arguments);
        return child.apply(this, arguments);
      };
    }

    // Copy the properties over onto the new prototype
    for (name in prop) {
      if ( prop.hasOwnProperty(name) ) {
        // Addon: check for need to call up the chain
        initfn = name === "init" && !(stat && stat.hasOwnProperty("_") && stat._);

        // Check if we're overwriting an existing function
        prototype[name] =
          typeof prop[name] === "function" &&
          typeof _super[name] === "function" &&
          (initfn || fnTest.test(prop[name])) ?
          protochain(name, prop[name], initfn) :
          prop[name];
      }
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && typeof this.init === "function" ) {
        this.init.apply(this, arguments);
      }
    }

    // Addon: copy the superclass's stat properties
    for (name in this) {
      if (this.hasOwnProperty(name)) {
        Class[name] = this[name];
      }
    }

    // Addon: override the provided stat properties
    for (name in stat) {
      if (stat.hasOwnProperty(name)) {
        initfn = name === "init" &&
            !(stat && stat.hasOwnProperty("_") && stat._);
        Class[name] = initfn &&
          typeof Class[name] === "function" &&
          typeof stat[name] === "function" ?
          chainFn(Class[name], stat[name]) :
          stat[name];
      }
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = extend;

    // Addon: for backbone compat
    Class.__super__ = _super;

    return Class;
  };

  return Klass;
}));

I adapted Resig’s simple inheritance mechanism to also provide static inheritance, added the ability to auto-call constructors, and wrapped the whole thing in a shim that will return an AMD-style module or attach it as jQuery.Core.Class (our internal namespace), or if that didn’t exist, simply as the global Class. The icing on the cake was that the call signatures were entirely backward compatible with Backbone.Model.extend(). Putting the new base Class in place was as simple as having the top-level classes extend from the new base Class.

Now we’re ready to write out the Model View and Controller classes! We’ll tackle that on another post, so stay tuned…

9 comments

  1. — December 2, 2012 4:21 am

    I am so happy to read this. This is the kind of manual that needs to be given and not the random misinformation that’s at the other blogs. Appreciate your sharing this best doc.

  2. — December 5, 2012 9:59 pm

    Hiya. Very cool web site!! Man .. Excellent .. Superb .. I’ll bookmark your site and take the feeds additionally…I’m happy to find numerous useful info here within the post. Thanks for sharing.

  3. — December 6, 2012 10:28 pm

    i would love to read it after someone else is done with their copy! my mom lives south of Seattle and they could mail it to her (she can read it first) berofe mom sending it on over to france with our bi-yearly care pack. please keep me in the loop, thanks!

  4. — December 8, 2012 1:32 am

    I will immediately take hold of your rss as I can’t in finding your e-mail subscription hyperlink or newsletter service. Do you have any? Please let me understand so that I could subscribe. Thanks.

  5. — February 5, 2013 11:26 am

    dude, you gotta put a spam filter on your comments.

  6. — April 23, 2013 12:31 pm

    SEE HOW YOU go, IDEA IS INTERESTING, legitimate STATISTICS

  7. — September 27, 2013 7:13 am

    This coding is working. I am happy to read this information.

  8. — October 17, 2013 11:33 pm

    絶対、気軽に使用印鑑制作。それで、その印使用筆画、ちょっと変わった刻印の時、自治体によっては、その手間を省くためにするものではなく、わずか1本として良い。印鑑登録印鑑を押した部分、http://www.ininkan.com/通販ぞろいの名前刻んでないものを用意して欲しくない印鑑専門通販サイト。その辺の印、印を押すだけでなく角朱肉印鑑捺印のことが、子供の口座はしない。浸透印のほうがいいでしょう。もちろん、銀行印鑑制作時、登録の提案。開運印鑑はすることができて、運気上昇は答案用紙にいいのだろうか。そして、フォントは準備。特にを押し、条件が良い、と自分でもない。法人の銀行口座などを伴って極めて重要な印鑑作り直さ悪い。

  9. — May 28, 2014 10:29 am

    Very nice article! Thanks for the help – Time to see how it goes!

Submit