Prototypes, Constructor Functions And Taxidermy


When overriding the base `Backbone.View` to create your own view types from the base views, there are some issues that you may run in to with inheritance. The inheritance of constructor functions can be broken in strange ways, and code that you override in the constructor or other base ModelView or CollectionView functions may not be called when you expect it to be. This can be a tremendously frustrating problem, as the code you write looks correct, but it does not fire like you expect.

Fortunately, the fix is simple. Unfortunately, it’s not obvious. Worse, though, is that the simple but not obvious nature of the fix often makes it look like the solution is unnecessary.

The Problem

The short version of the problem is that replacing the constructor method on Backbone.View.prototype doesn’t work. The prototype.constructor attribute is part of the JavaScript prototypal inheritance system, and isn’t something you should replace at all. Doing so is essentially trying to replace the type itself, but only replacing a specific reference to the type. Backbone provides the ability to specify a constructor in it’s type definitions, and it uses that function as the constructor function for the type that is returned. But this is only a convenience if not a coincidental naming of a method.

Taxidermy On Prototypes

Replacing the prototype.constructor is a bit like taxidermy – the end result of “stuffing” a dead bear may still look like a bear on the outside, but it’s not really a bear anymore. It just looks like one. In the case of prototype.constructor though, this is especially dangerous because you can break code that relies on type checking or prototypal inheritance features that look at the prototype.constructor.

Visualize this through code, again:

function MyObject(){}

MyObject.prototype.constructor = function(){};

console.log(MyObject.prototype.constructor);
console.log(MyObject.prototype.constructor === MyObject);

Prototype constructor replacement

The prototype.constructor is no longer pointing to MyObject, so the original MyObject “constructor function” is not being applied to the new object instance.

Super Solution

All of the problems associated with the prototype.constructor replacement may be a bit disheartening. But there is hope, and a fairly simple solution.

There are two things you will need, to solve the problem of overriding a Backbone object’s constructor function without any other code having to extend from it directly.

  1. A new type with the “super-constructor” pattern (where a type calls back to the prototype.constructor manually)
  2. A complete replacement of the base view who’s constructor you want to replace.

By creating a new type that calls back to the original type’s constructor, you can ensure the correct chain of inheritance and constructors is handled. Then to get your new type in place without forcing others to extend from your type, you will need to replace the type from which your plugin extends, prior to any other code using it.

The Super-Constructor Pattern

The constructor function of a Backbone object looks like it would be even easier to replace than a normal method. If you extend from Backbone.View, you don’t need to access the prototype. You only need to apply Backbone.View as a function, to the current object instance.

Once you have your type set up, you will need to replace the original type with your new type. By doing this, any new type that tries to extend from the original named type, will get yours instead of the original. But you can’t just replace Backbone.View directly.

// define the new type
var MyBaseView = Backbone.View.extend({
  constructor: function(){
    var args = Array.prototype.slice.apply(arguments);
    Backbone.View.apply(this, args);
  }
});

// replace Backbone.View with the new type
Backbone.View = MyBaseView;

The args line is in the super-constructor example to ensure compatibility with older browsers. Some versions of IE, for example, will throw an error if the arguments object is null or undefined, and you pass it in to the apply method of another function. To work around this, you can slice the arguments object in to a proper array. This will return an empty array if the arguments is null or undefined, allowing older versions of IE to work properly.

Unfortunately, this setup will fail horribly. When the call to Backbone.View.apply is made, it will find your new type’s constructor sitting in Backbone.View, causing an infinite loop.

Correctly Overriding The Constructor Function

To fix the view replacement and associated problems, you need to store a reference to the original Backbone.View separately from the new view type. Then you will need to call this reference from your constructor function, and not the Backbone.View named function, directly.

(function(){
  // store a reference to the original view
  var Original = Backbone.View;

  var MyView = Original.extend({
    // override the constructor, and all the original
    constructor: function(){
      var args = Array.prototype.slice.call(arguments);
      Original.apply(this, args);
    }
  });

  // Replace Backbone.View with the new one
  Backbone.View = MyView;
})();

Now the Backbone.View has been replaced with your view type, while still maintaining a reference to the original so that it can be called when needed. Provided the file that includes this code is loaded prior to any other code extending from Backbone.View, all views will receive any functionality defined in MyView – which is exactly what you wanted in the Automation ID view.

An Excerpt From Building Backbone Plugins

This blog post is an excerpt and preview of Chapter 5 in my Building Backbone plugins e-book. The complete chapter includes a more meaningful problem statement and solution set: trying to inject behavior in to a Backbone application without requiring any part of the application to know about the new behavior. It’s an interesting and common problem, and the solution as shown here is simple but not at all obvious.

For more information about the e-book, including additional samples of the content, head over to BackbonePlugins.com.

My Smart Phone Made Me Dumb. I Fixed It.