Semantic constructors
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
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
toStringof prototype.initialize to another function that will output not only constructortoStringbut also all other class methods (which lacks terribly when you inspect objects with Firebug extension for Firefox):Anyway, nice work (as well)!
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
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’
fearphage said:
#Any reason for
fn.toString = function(){ return original.toString() };instead offn.toString = original.toString;? Just curiouskangax (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(){}