Wednesday, July 10, 2013

More Simple JavaScript Inheritance

I like John Resig's Simple JavaScript Inheritance ... except that I hate having to refactor my JavaScript code to use it.

As John suggests, suppose that I have some code:

function Person(isDancing){
  this.dancing = isDancing;
}

Person.prototype.dance = function(){
  return this.dancing;
};

function Ninja(){
  this.dancing = false;
}

Ninja.prototype.dance = function(){
  return this.dancing;
};

Ninja.prototype.swingSword = function(){
  return true;
};

That's normal JavaScript object code.  Now, I decide to use John Resig's "Simple JavaScript Inheritance".  John Resig suggests that I rewrite my code like this:

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

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});

Changes are in red.  The implementations stay the same but the code syntax is different.  It's a bit of a hassle if you have little bit of code and, for a lot of code, it's even more of a hassle.

Why can't I reuse my code as-is with only a few modifications?  Something like this:

function Person(isDancing){
  this.dancing = isDancing;
}

Person.prototype.dance = function(){
  return this.dancing;
};

Person = Class.extend(Person);

function Ninja(){
  this._super( false );
}

Ninja.prototype.dance = function(){
  // Call the inherited version of dance()
  return this._super();
};

Ninja.prototype.swingSword = function(){
  return true;
};

Ninja = Person.extend(Ninja);


Instead of rewriting the code into a new code syntax, I only add an extend() call at the end of each new JavaScript class.  Once I've done that, if I want, I can call this._super() where ever I need to.


I make this possible using a slight modification to John Resig's script.  By checking to see if the argument passed to extend() is a function, instead of an object, the script can handle both syntaxes: John Resig's original syntax and the traditional JavaScript object syntax.

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * Init-by-function modification
 * By Daniel Howard http://www.svexpertise.com/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

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

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
    if ( typeof prop == 'function' ) {
      prop.prototype.init = prop;
      for (var name in prop)
        prop.prototype[name] = prop[name];
      prop = prop.prototype;
    }
   
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
   
    // 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];
    }
   
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
   
    // 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 = arguments.callee;
   
    return Class;
  };
})();

When a function is detected, instead of an object, the function is first saved to the init property.  Then, all enumerable function properties (which will behave pretty much like static variables in classical class-based systems) are saved to the function's prototype object.  Finally, and here's the magic pixie dust, the function's prototype object is used as the argument which the rest of the code generates the JavaScript class from.  John Resig's code behaves the same but the function's prototype object becomes "the class object" instead of being directly passed as the original argument.

Person = Class.extend(Person);
...
Ninja = Person.extend(Ninja);

The extend() calls above take the JavaScript constructor functions and return the new JavaScript class.  By assigning the return value to the function (variable), the new JavaScript class replaces the traditional JavaScript object creation function.

With this modification and technique, you can write traditional JavaScript object code and, if you later want to use John Resig's "Simple JavaScript Inheritance", you can add it with ease.

No comments:

Post a Comment