jQuery and JavaScript inheritance

A couple of months ago I found this interesting post on JavaScript inheritance which shows an alternate way to get the same results achieved through the methods proposed by Douglas Crockford and Kevin Lindsey. This solution is based on the work of Troels Knak-Nielsen and is as follows:

function copyPrototype(descendant, parent) {
    var sConstructor = parent.toString();
    var aMatch = sConstructor.match( /\s*function (.*)\(/ );
    if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
    for (var m in parent.prototype) {
        descendant.prototype[m] = parent.prototype[m];
    }
}

The author of the post then quotes Troels who says:

First off – I fully understand and agree to the point of not extending language functionality. That said I think the point in relation to javascript and inheritance is often misunderstood. It is central to javascript that it is a "typeless object-oriented language". This goes for ECMAscript3.0 all the same. Where people most often get it wrong is when they mistake prototypes for classes – they are not.

In a typed OO-language the objects code lies with the class – objects are just instances. In js, the code lies in the object. Therefore, the object is not tied to the prototype. It is ok in js to manipulate the prototype runtime, even after objects has been instantiated. Consider doing that with a typed language.

Anyway. I have been working a lot with js recently, and found that one-level inheritance is simple enough using prototypes, but further inheritance cause trouble. I guess you came to the same conclusion with JPSpan. My first googling on the subject turned up a lot of wierd hacks, that I don’t appreciate. I eventually invented a solution witch I use, and witch I’m very happy with. It may look as a hack, but it works so well, that I kind of forgive it for that.

To defend myself further I’ll point out that it’s not a language extension, if you just understand what it does. It’s merely a utility that extends a prototype – Which should not be confused with class inheritance (though the result is the same).

Apparently jQuery comes up with a somewhat similar solution for handling inheritance in JavaScript: the $.extend() method. The inner structure of this method is the following:

jQuery.extend = jQuery.fn.extend = function() {
 // copy reference to target object
 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;

 // Handle a deep copy situation
 if ( typeof target === "boolean" ) {
  deep = target;
  target = arguments[1] || {};
  // skip the boolean and the target
  i = 2;
 }

 // Handle case when target is a string or something (possible in deep copy)
 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
  target = {};
 }

 // extend jQuery itself if only one argument is passed
 if ( length === i ) {
  target = this;
  --i;
 }

 for ( ; i < length; i++ ) {
  // Only deal with non-null/undefined values
  if ( (options = arguments[ i ]) != null ) {
   // Extend the base object
   for ( name in options ) {
    src = target[ name ];
    copy = options[ name ];

    // Prevent never-ending loop
    if ( target === copy ) {
     continue;
    }

    // Recurse if we're merging object literal values or arrays
    if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
     var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
      : jQuery.isArray(copy) ? [] : {};

     // Never move original objects, clone them
     target[ name ] = jQuery.extend( deep, clone, copy );

    // Don't bring in undefined values
    } else if ( copy !== undefined ) {
     target[ name ] = copy;
    }
   }
  }
 }

 // Return the modified object
 return target;
};

As you can see, the jQuery's approach varies significantly in many aspects, since this method is able to recognize the type of the values passed in and react accordingly. The final result is always an object whose properties and methods are actually the sum of the objects passed to the method. For example, if we have two objects like these:

var A = {
    foo: 'Foo',
    bar: function() {
        alert(this.foo);
    }
};

function B() {
    this.loc = location.href;
}

We can create an object named C with a single statement:

var C = $.extend(A, B);
C.bar(); // alerts 'Foo'
alert(C.loc); // alerts the current page location

This method is actually much simpler in its practical use than the previous one, mostly because it allows us to get more control over what happens behind the scenes of JavaScript inheritance. Further, it prevents beginners from getting confused by the impressive amount of technical details which come along with JavaScript inheritance.

Leave a Reply

Note: Only a member of this blog may post a comment.