What’s happening in this code with Number objects holding properties and incrementing the number?

OP here. Funny to see this on Stack Overflow :)

Before stepping through the behaviour, its important to clarify a few things:

  1. Number value and Number object (a = 3 vs a = new Number(3)) are very different. One is a primitive, the other is an object. You cannot assign attributes to primitives, but you can to objects.

  2. Coercion between the two is implicit.

    For example:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
    
  3. Every Expression has a return value. When the REPL reads and executes an expression, this is what it displays. The return values often don't mean what you think and imply things that just aren't true.

Ok, here we go.

Original image of the JavaScript code

The pledge.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Define a function dis and call it with 5. This will execute the function with 5 as the context (this). Here it is coerced from a Number value to a Number object. It is very important to note that were we in strict mode this would not have happened.

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Now we set the attribute five.wtf to 'potato', and with five as an object, sure enough it accepts the Simple Assignment.

> five * 5
25
> five.wtf
'potato'

With five as an object, I ensure it can still perform simple arithmetic operations. It can. Do its attributes still stick? Yes.

The turn.

> five++
5
> five.wtf
undefined

Now we check five++. The trick with postfix increment is that the entire expression will evaluate against the original value and then increment the value. It looks like five is still five, but really the expression evaluated to five, then set five to 6.

Not only did five get set to 6, but it was coerced back into a Number value, and all attributes are lost. Since primitives cannot hold attributes, five.wtf is undefined.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

I again attempt to reassign an attribute wtf to five. The return value implies it sticks, but it in fact does not because five is a Number value, not a Number object. The expression evaluates to 'potato?', but when we check we see it was not assigned.

The prestige.

> five
6

Ever since the postfix increment, five has been 6.


There are two different ways to represent a number:

var a = 5;
var b = new Number(5);

The first is a primitive, the second an object. For all intents and purposes both behave the same, except they look different when printed to the console. One important difference is that, as an object, new Number(5) accepts new properties just like any plain {}, while the primitive 5 does not:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

As for the initial dis.call(5) part, please see How does the "this" keyword work?. Let's just say that the first argument to call is used as the value of this, and that this operation forces the number into the more complex Number object form.* Later on ++ forces it back into the primitive form, because the addition operation + results in a new primitive.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

A Number object accepts new properties.

> five++

++ results in a new primitive 6 value...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

...which does not have and does not accept custom attributes.

* Note that in strict mode the this argument would be treated differently and would not be converted to a Number. See http://es5.github.io/#x10.4.3 for the implementation details.


There's Coercion in the JavaScript World - A Detective Story

Nathan, you have no idea what you've uncovered.

I've been investigating this for weeks now. It all started on a stormy night last October. I accidentally stumbled upon the Number class - I mean, why in the world did JavaScript have a Number class?

I wasn't prepared for what I was going to find out next.

It turns out that JavaScript, without telling you, has been changing your numbers to objects and your objects to numbers right under your nose.

JavaScript was hoping no one would catch on, but people have been reporting strange unexpected behavior, and now thanks to you and your question I have the evidence I need to blow this thing wide open.

This is what we've found out so far. I don't know if I should even be telling you this - you might want to turn off your JavaScript.

> function dis() { return this }
undefined

When you created that function, you probably had no idea what was going to happen next. Everything looked fine, and everything was fine - for now.

No error messages, just the word "undefined" in console output, exactly what you would expect. After all, this was a function declaration - it isn't supposed to return anything.

But this was just the beginning. What happened next, no one could have predicted.

> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Yeah I know, you expected a 5, but that's not what you got, was it - you got something else - something different.

The same thing happened to me.

I didn't know what to make of it. It drove me nuts. I couldn't sleep, I couldn't eat, I tried to drink it away, but no amount of Mountain Dew would make me forget. It just didn't make any sense!

That's when I found out what was really going on - it was coercion, and it was happening right there in front of my eyes, but I was too blind to see it.

Mozilla tried to bury it by putting it where they knew nobody would look - their documentation.

After hours of recursively reading and re-reading and re-re-reading I found this:

"... and primitive values will be converted to objects."

It was right there plain as can be spelled out in Open Sans font. It was the call() function - how could I be so stupid?!

My number was no longer a number at all. The moment I passed it into call(), it became something else. It became... an object.

I couldn't believe it at first. How could this be true? But I couldn't ignore the evidence that was mounting up around me. It's right there if you just look:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtf was right. Numbers can't have custom properties - we all know that! It's the first thing they teach you at the academy.

We should have known the moment we saw the console output - this was not the number we thought it was. This was an impostor - an object passing itself off as our sweet innocent number.

This was... new Number(5).

Of course! It made perfect sense. call() had a job to do, he had to invoke a function, and to do that he needed to populate this, he knew he couldn't do that with a number - he needed an object and he was willing to do anything to get it, even if that meant coercing our number. When call() saw the number 5, he saw an opportunity.

It was the perfect plan: wait until no one was looking and swap out our number for an object that looks just like it. We get a number, the function gets invoked, and no one would be the wiser.

It really was the perfect plan, but like all plans, even perfect ones, there was a hole in it, and we were about to fall right into it.

See, what call() didn't understand was that he wasn't the only one in town who could coerce numbers. This was JavaScript after all - coercion was everywhere.

call() took my number, and I wasn't going to stop until I pulled the mask off of his little impostor and expose him to the whole Stack Overflow community.

But how? I needed a plan. Sure it looks like a number, but I know it's not, there's gotta be a way to prove that. That's it! It looks like a number, but can it act like one?

I told five I need him to become 5 times larger - he didn't ask why and I didn't explain. I then did what any good programmer would do: I multiplied. Surely there was no way he could fake his way out of this.

> five * 5
25
> five.wtf
'potato'

Damn it! Not only did five multiply just fine wtf was still there. Damn this guy and his potato.

What the hell was going on? Was I wrong about this whole thing? Is five really a number? No, I must be missing something, I know it, there's something I must be forgetting, something so simple and basic that I'm completely overlooking it.

This was not looking good, I had been writing this answer for hours and I was still no closer to making my point. I couldn't keep this up, eventually people would stop reading, I had to think of something and I had to think of it fast.

Wait that's it! five wasn't 25, 25 was the result, 25 was a completely different number. Of course, how could I forget? Numbers are immutable. When you multiply 5 * 5 nothing gets assigned to anything you just create a new number 25.

That must be what's happening here. Somehow when I multiply five * 5, five must be getting coerced into a number and that number must be the one used for the multiplication. It's the results of that multiplication that gets printed to the console, not the value of five itself. five never gets assigned anything - so of course it doesn't change.

So then how do I get five to assign himself the result of an operation. I got it. Before five even had a chance to think, I yelled "++".

> five++
5

Aha! I had him! Everybody knows 5 + 1 is 6, this was the evidence I needed to expose that five was not a number! It was an impostor! A bad impostor that didn't know how to count. And I could prove it. Here's how a real number acts:

> num = 5
5
> num++
5

Wait? What was going on here? sigh I got so caught up in busting five that I forget how post operators work. When I use the ++ at the end of five I'm saying return the current value, then increment five. It's the value before the operation occurs that gets printed to the console. num was in fact 6 and I could prove it:

>num
6

Time to see what five really was:

>five
6

...it was exactly what it should be. five was good - but I was better. If five were still an object that would mean it would still have the property wtf and I was willing to bet everything it didn't.

> five.wtf
undefined

Aha! I was right. I had him! five was a number now - it wasn't an object anymore. I knew the multiplication trick wouldn't save it this time. See five++ is really five = five + 1. Unlike the multiplication, the ++ operator assigns a value to five. More specifically, it assigns it the results of five + 1 which just like in the case of multiplication returns a new immutable number.

I knew I had him, and just to make sure he couldn't squirm his way out of it. I had one more test up my sleeve. If I was right, and five was really a number now, then this wouldn't work:

> five.wtf = 'potato?'
'potato?'

He wasn't going to fool me this time. I knew potato? was going to be printed to the console because that's output of the assignment. The real question is, will wtf still be there?

> five.wtf
undefined

Just as I suspected - nothing - because numbers can't be assigned properties. We learned that the first year at the academy ;)

Thanks Nathan. Thanks to your courage in asking this question I can finally put all this behind me and move on to a new case.

Like this one about the function toValue(). Oh dear god. Nooo!

Tags:

Javascript