Perfection kills

Exploring Javascript by example

Semantic constructors

July 16th, 2008 by kangax (Permalink)

One of the easiest ways to inspect an object is to type convert it to a string:

function foo() { 
  return 'foo'; 
} 
foo + ''; // "function foo(){ return 'foo'; }" 
// or 
foo.toString(); 
// or 
String(foo);

When using Class.create from prototype.js, I often get annoyed by a meaningless result of constructor’s toString:

var Person = Class.create({
  initialize: function(name) {
    this.name = name;
  },
  speak: function(msg) {
    return this.name + ' says: ' + msg;
  }
})
 
Person + ''; // "function klass() { this.initialize.apply(this, arguments); }"
 
var Employee = Class.create(Person, {
  initialize: function($super, dept) {
    $super();
    this.dept = dept;
  }
})
 
Employee + ''; // "function klass() { this.initialize.apply(this, arguments); }"

As you can see, constructor’s default toString tells little about what’s going on under the hood. In fact, Class.create returns a generic constructor-function which only calls initialize method of a prototype. In other words it acts as a proxy. One of the ways to see actual constructor’s code is to inspect “initialize” method directly:

// nothing new here
Person + ''; // "function klass() { this.initialize.apply(this, arguments); }"
 
// and the actual code
Person.prototype.initialize + ''; // "function (name) { this.name = name; }"

This is quite verbose, don’t you think? Let’s monkey patch Class.create a little:

Class.create = (function(original) {
  var fn = function() {
    var result = original.apply(null, arguments);
    result.toString = function() { return result.prototype.initialize.toString() };
    return result;
  };
  fn.toString = function(){ return original.toString() };
  return fn;
})(Class.create);

We simply redefine original Class.create with an “enhanced” version. New version “binds” toString of prototype.initialize as toString of constructor-function. As a bonus, it also takes care of a “wrapped” Class.create.toString itself – trying to be as unobtrusive as possible:

// monkey-patch Class.create first...
 
// Person.toString now yields much more informative result
Person + ''; // "function (name) { this.name = name; }"
 
// Class.create code seems to be unchanged
Class.create + ''; // outputs actual Class.create code
Categories: Class.create 6 Comments »

Comments (6)

  1. Gravatar

    Pedro De Almeida said:

    This is really an interesting hack for developers that need more debugging functionalities. You should may be propose this feature to be part of the next Prototype release (by the way, are you a Prototype Team member? You seem to be actively working on it…)!

    I don’t know if it’s feasible but your concept could be extended a little more: bind toString of prototype.initialize to another function that will output not only constructor toString but also all other class methods (which lacks terribly when you inspect objects with Firebug extension for Firefox):

    // Monkey-patch Class.create first…
    // Define Person class..

    // Person.toString now outputs class members information:
    Person + ”;

    /* “[klass {
    initialize: function(name) {
    this.name = name;
    },
    speak: function(msg) {
    return this.name + ' says: ' + msg;
    }
    }]” */

    Anyway, nice work (as well)!

  2. Gravatar

    f1codz said:

    im tagging this website for my del.icio.us account .. ppl this so t of sharing of info is what makes internet so wonderful .. thnx kangax

  3. Gravatar

    perfection kills said:

    JavaScript – http://perfectionkills.com/2008/01/22/prototype-1602-cheat-sheet/
    Inline script thread
    name: TypeError
    message: Statement on line 9: Cannot convert undefined or null to Object
    Backtrace:
    Line 9 of linked script http://twitter.com/javascripts/blogger.js: In function twitterCallback2
    document.getElementById(‘twitter_update_list’).innerHTML = statusHTML;
    Line 1 of linked script http://twitter.com/statuses/user_timeline/kangax.json?callback=twitterCallback2&count=5
    twitterCallback2([]);
    stacktrace: n/a; see ‘opera:config#UserPrefs|Exceptions Have Stacktrace’

  4. Gravatar

    fearphage said:

    Any reason for fn.toString = function(){ return original.toString() }; instead of fn.toString = original.toString;? Just curious

  5. Gravatar

    kangax (article author) said:

    @fearphage

    Good question : )
    `Function.prototype.toString` seems to be context-dependent (at least in FF2+). Take a look:

    function foo(){};
    function bar(){};
    foo.toString = bar.toString;
    // `toString` is called within `foo`’s context
    foo.toString(); // function foo(){};
    foo.toString = function(){ return bar.toString() };
    foo.toString(); // function bar(){};
    // Also
    bar.toString.call(foo); // function foo(){}

Trackbacks

  1. Ajaxian » Semantic Constructors said:

    [...] monkey patch by kangax allows you to get sense from inspecting a constructor setup via [...]

Leave a Comment

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

Please, don't forget to escape your input (<, > and &). Wrap code sections with <pre>