HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Improving on John Resig's Simple JavaScript Inheritance: avoiding `new`

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplenewjavascriptresiginheritancejohnavoidingimproving

Problem

I've recently read this article, and I agree that the new keyword is not good practice.

Thus, I've made an improvement on John Resig's Simple JavaScript Inheritance in order to use the Class.create method instead of new:

// The dummy class constructor
function Class() {
   // I remove the initialization procedure in constructor function, 
   // Initialization will done by Class.create which I defined below
}     

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

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

// What I improved
Class.create = function () {
  var instance = new this();
  if (instance.init) {
      instance.init();
  }
  return instance;
}


The initialization case in his article could be rewritten like this:

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  }
});

var p = Person.create();


Have I done this well or not? Please help me check it.

Solution

If you really want to create your own inheritance pattern then John Resig's Simple Inheritance pattern is not a good candidate to build upon. The reason is because John Resig's code is very slow. The reason it's so slow is because of the way _super is handled:

// Copy the properties over onto the new prototype
for (var name in prop) {
  // Check if we're overwriting an existing function
  prototype[name] = typeof prop[name] == "function" && 
    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    (function(name, fn){
      return function() {
        var tmp = this._super;

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

        // The method only need to be bound temporarily, so we
        // remove it when we're done executing
        var ret = fn.apply(this, arguments);        
        this._super = tmp;

        return ret;
      };
    })(name, prop[name]) :
    prop[name];
}


I'll try to explain what's happening in the above code:

  • We loop through each property which we wish to copy onto the newly extended object.



  • If the property is a function and it overrides another function of the same name and it needs to call the overridden function then we replace it with a function which changes the value of this._super within the function to the overridden function for that particular function call.



  • Otherwise we simply copy the property as it is.



This little indirection allows you to call overridden methods using this._super. However it also makes the code very slow. Hence I suggest you don't use John Resig's Simple JavaScript Inheritance.

Since this is a code review site I'm also obliged to point out flaws in your code. The main problem with your code is in the Class.create function:

Class.create = function () {
  var instance = new this();
  if (instance.init) {
      instance.init();
  }
  return instance;
};


Compare that with John Resig's original dummy class constructor:

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


The problem is that any argument applied to Class.create is lost. You need to apply the arguments passed to Class.create to instance.init as follows:

Class.create = function () {
  var instance = new this;
  if (instance.init) instance.init.apply(instance, arguments);
  return instance;
};


Beside that there's not much more scope for improvement if you plan to stick to John Resig's Simple Inheritance Pattern.

If you really want to create your own inheritance pattern in JavaScript then it pays to invest some time learning about how inheritance works in JavaScript. For example the following answer explains prototype-class isomorphism in JavaScript: https://stackoverflow.com/a/17893663/783743

Prototype-class isomorphism simply means that prototypes can be used to model classes. Armed with this knowledge we can write a function which takes an object (a prototype) and returns a class (a constructor function):

function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}


Using the above function we may now create and instantiate classes as follows:

var Person = CLASS({
    constructor: function (isDancing) {
        this.dancing = isDancing;
    },
    dance: function () {
        return this.dancing;
    }
});

var p = new Person(true);


Although this pattern does not have inheritance yet it is a good base to build upon. With a little bit of modification we can make it behave the way we want it to. There are a few important points to note:

  • We want to be able to use the extend and create functions on any function. Hence it's better to add them to Function.prototype instead of creating a separate Class constructor.



  • For a function F it need not be true that F.prototype.constructor === F. Hence we may use F for extending and F.prototype.constructor for creating.



  • Instead of passing a prototype to extend it makes much more sense to pass a blueprint of a prototype instead. This makes looping over the prototype unnecessary.



Taking advantage of the above mentioned points we can implement extend and create as follows:

Function.prototype.extend = function (body) {
    var constructor = function () {};
    var prototype = constructor.prototype = new this;
    body.call(prototype, this.prototype);
    return constructor;
};

Function.prototype.create = function () {
    var instance = new this;
    instance.constructor.apply(instance, arguments);
    return instance;
};


That's all. Using two simple functions we may now create classes as follows:

```
var Person = Object.extend(function () {
this.constructor = function (isDancing) {
this.dancing = isDancing;
};

Code Snippets

// Copy the properties over onto the new prototype
for (var name in prop) {
  // Check if we're overwriting an existing function
  prototype[name] = typeof prop[name] == "function" && 
    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    (function(name, fn){
      return function() {
        var tmp = this._super;

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

        // The method only need to be bound temporarily, so we
        // remove it when we're done executing
        var ret = fn.apply(this, arguments);        
        this._super = tmp;

        return ret;
      };
    })(name, prop[name]) :
    prop[name];
}
Class.create = function () {
  var instance = new this();
  if (instance.init) {
      instance.init();
  }
  return instance;
};
// The dummy class constructor
function Class() {
  // All construction is actually done in the init method
  if ( !initializing && this.init )
    this.init.apply(this, arguments);
}
Class.create = function () {
  var instance = new this;
  if (instance.init) instance.init.apply(instance, arguments);
  return instance;
};
function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

Context

StackExchange Code Review Q#30018, answer score: 2

Revisions (0)

No revisions yet.