2

I thougt it would be fun to rewrite my zero padding extension to the Number.prototype into a genuine getter/setter pattern. Not very difficult. I ended up with this code (addSetter and addGetter are just wrapper functions using prototype.__defineGetter__ / __defineSetter__.

Number.addSetter('leftPad0',
  function(v){
    var  len = (String(v).length - String(this).length)+1;
    this._leftPad0 = new Array(len).join('0')+this;
  }
);

Number.addGetter('leftPad0',
  function(){
    return this._leftPad0;
  }
);

Works fine for real Number Objects:

var a = new Number(977);
a.leftPad0 = 10000;
alert('a => '+a.leftPad0); // a => 00977

But not for Number literals:

var b = 977;
b.leftPad0 = 10000;
alert('b => '+b.leftPad0); // b => undefined

So, doesn't b reach the setter? Or if it reaches the setter, isn't it a Number?

I logged this in the console from within the setter function:

this.constructor === Number // true
this instanceof Number //true

Or is the getter not reached, or when it's reached would the literal not be an instance of Number? I logged the same within the getter. All fine and true too.

So, what may be the reason that we are not able to use this pattern using a Number literal? Or have I missed something?

Note: this doesn't occur if I use this prototype extension ('monkey patch'):

Number.prototype.leftPad = function(base){
  var  len = (String(base).length - String(this).length)+1;
  return new Array(len).join('0')+this;
}

alert( (977).leftPad(10000) ); // 00977

[edit] I still wonder if we have to call this a bug, or if it's following the/a standard. Anyway, I deviced my own object for this now:

function NumPL(val,pval,chr){
   if (!(this instanceof NumPL)){
      return new NumPL(val,pval,chr);
   }
   this._value = new Number(val||0);
   this._padValue = pval || 10;
   this.chr = chr || '0';
}

NumPL.prototype = {
    get value(){
        return this._value;
    },
    set padValue(v) {
        this._padValue = v%10 === 0 ? v : 10;
    },
    set value(v) {
        this._value = v;
    },
    get padLeft(){
       var  len = (String(this._padValue).length - 
                   String(this._value).length)+1;
       return new Array(len).join(this.chr)+this._value;
    }
}
// Usage
var a = NumPL(977,10000);
alert(a.padLeft); //=> 00977
// or a real world example
var dat = new Date,
    datshort = [dat.getFullYear(),
                NumPL(dat.getMonth()+1).padLeft,
                NumPL(dat.getDate()).padLeft]
               .join('/');
 alert(datshort); //=> 2011/05/19
KooiInc
  • 119,216
  • 31
  • 141
  • 177

2 Answers2

4

A literal number is not a Number:

typeof 3
// "number"

typeof new Number(3)
// "object"

So there is no prototype to modify for numeric literals.

However, when you try to set a property on a numeric literal, it looks like the literal is wrapped in a Number object first. Then, the setter is called on that object and this will be an object inside the setter function. And then, the literal is unwrapped when the setter returns and this unwrapping leaves the property (which was set on the object wrapper) behind.

Number.prototype.__defineSetter__('leftPad0',
    function(v) {
        alert('In setter: ' + v + ' ' + (this instanceof Number));
        var len = (String(v).length - String(this).length) + 1;
        this._leftPad0 = new Array(len).join('0') + this;
    }
);

var a = new Number(977);
a.leftPad0 = 10000;
alert('a => ' + a._leftPad0);

var b = 977;
b.leftPad0 = 10000;
alert('b => ' + b._leftPad0);

http://jsfiddle.net/ambiguous/H77qk/2/

I get four alerts out of the above in several browsers.

Sorry but I don't have an authoritative reference for this behavior, just empirical evidence.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • I know that, but if i alert `typeof this` for `b` within the setter or getter, I get `object`, really – KooiInc May 18 '11 at 20:05
  • @Kooilnc: This is interesting: http://jsfiddle.net/ambiguous/H77qk/2/ Looks like the literal is being wrapped in a Number for the property setting so you get an object for `this`, then it gets unwrapped immediately after. Number literals are immutable after all. – mu is too short May 18 '11 at 20:23
  • @shortmu: scratch my last comment, looks like a `jsdb` (http://www.jsdb.org/) artefact. Had to run it in a jsfiddle first I suppose – KooiInc May 18 '11 at 20:25
  • @shortmu thanks, but ehr, the link? I was fiddling too and getting there (using your answer): http://jsfiddle.net/Vc92K/ (addSetter/addGgetter are in there too) – KooiInc May 18 '11 at 20:37
  • 1
    More fun to show the immediate wrapping and un-wrapping of number literals: http://jsfiddle.net/Bd2M3/ –  May 18 '11 at 20:50
  • @Kooilnc: Yeah, looks like it is getting wrapped in a Number for both setting and getting the property but the property is set on the wrapper object, then the wrapper is peeled off to get the literal back, the property that was set was set on the wrapper though and so is lost during the unwrapping and the property doesn't stick. – mu is too short May 18 '11 at 20:51
  • @cwolves: I cleaned up your fiddle (http://jsfiddle.net/ambiguous/Bd2M3/1/) but yeah, looks like it gets wrapped and unwrapped on every property access (either setting or getting). – mu is too short May 18 '11 at 20:56
  • @shortmu, @cwolves: the boxing/unboxing behavior is best shown in this fiddle I think: http://jsfiddle.net/DWvgn/ – KooiInc May 18 '11 at 21:27
2

var b = 9; means that b is a reference to a primitive type, not an object of type Number.

var b = new Number(9) creates an object of type Number, which will contain the function added to the Number prototype, not a primitive number type.

Demian Brecht
  • 21,135
  • 5
  • 42
  • 46
  • Right but it is more complicated than that, there seems to be some implicit object wrapping and unwrapping going on: http://jsfiddle.net/ambiguous/H77qk/2/ – mu is too short May 18 '11 at 20:31