tags:

views:

290

answers:

1

Given the following JavaScript "class" definition:

var Quota = function(totalMinutes){
    this.totalMinutes = parseInt(totalMinutes || 0, 10);
};

Quota.prototype.valueOf = function(){
    return this.totalMinutes;
};

Quota.prototype.toString = function(format){
    format = format || "hh:mm";

    return format.replace.call(this, /hh?|mm?/g, function(match){
        switch (match) {
            case "hh":
                return this.totalMinutes * 60;
            case "mm":
                return this.totalMinutes;
        }
    });
};

Can you please explain exactly why the below call to toString()...

var q1 = new Quota(60);
console.log( q1.toString() );

...results in the following error being raised:

InternalError: too much recursion { message="too much recursion", more...}

I'm running the code (Firefox 3.5.7 + Firebug 1.5) in the Firebug Console. Ideally I'd like to know where is the recursive call back to toString() and your suggestions for how the replace function could be executed here via call or apply

+5  A: 
return format.replace.call(this, /hh?|mm?/g, function(match)

format.replace tries to call this.toString, which ends in an infinite recursion. As requested, here's a proof this happens: http://jsbin.com/eweli/:

var Quota = function(totalMinutes){
    this.totalMinutes = parseInt(totalMinutes || 0, 10);
};

Quota.prototype.toString = function(format){
    alert ("Quota.prototype.toString is called");
};

var q1 = new Quota(60);
var a = "string".replace.call(q1,'hello','world');

Try this instead:

return format.replace(/hh?|mm?/g, function(match)

Edit

Problems aside, the best way I've found to allow the function to access the current quota is to create a variable outside it's closure:

Quota.prototype.toString = function(format){
    format = format || "hh:mm";
    var quota = this;
    return format.replace(/hh|mm/g, function(match){
        switch (match) {
            case "hh":
                return quota.totalMinutes / 60;
            case "mm":
                return quota.totalMinutes;
        }
        return match;
    });
};
Kobi
The first argument to call is just what to use as the this parameter, I don't see how toString is called again on q1? None of your solutions allow the calls to this.totalMinutes in the body of the inner function to resolve correctly either?
Peter McGrattan
@Peter - see test case to prove `replace` calls `toString`.
Kobi
After edit: Yes there are lots of easier ways of writing the method. The question is more theory based: what I'm really trying to get to is how to call replace in this way and have the inner function be able to resolve this.totalMinutes to the instance of Quota, this is mentioned in the last sentence.
Peter McGrattan
Interesting, I haven't seen any documentation that `call` calls toString on the object you pass as it's first parameter but your test case does indeed prove that it does. The question emphasised in my above comment remains outstanding?
Peter McGrattan
@Peter - `call` definitely does not call `toString`. `replace` is a string function, however, so it makes perfect sense it will call `toString` on its argument. I don't think you can control `this` on the function `replace` is getting though, it takes `(match, index, full string)`. Maybe if you changed its prototype too, but now we're just being silly.
Kobi
@Kobi - Yes, that's the only way I was able to sensibly access the current Quota object within the closure as well. I understand what's happening with `call` and `toString` now also, marking as answer - many thanks!
Peter McGrattan