Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

javascript

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!


01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01 declares a function dis that returns the context object. What this represents changes depending on whether you're using strict mode or not. The entire example has different results if the function were declared as:

> function dis() { "use strict"; return this }

This is detailed in section 10.4.3 in the ES5 specification

  1. If the function code is strict code, set the ThisBinding to thisArg.
  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.
  3. Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).

02 is the return value of the function declaration. undefined should be self explanatory here.

03 the variable five is initialized with the return value of dis when called in the context of the primitive value 5. Because dis is not in strict mode, this line is identical to calling five = Object(5).

04 The odd Number {[[PrimitiveValue]]: 5} return value is the representation of the object that wraps the primitive value 5

05 the five object's wtf property is assigned a string value of 'potato'

06 is the return value of the assignment and should be self explanatory.

07 the five object's wtf property is being examined

08 as five.wtf was previously set to 'potato' it returns 'potato' here

09 the five object is being multiplied by the primitive value 5. This is no different from any other object being multiplied and is explained in section 11.5 of the ES5 specification. Of particular note is how objects are cast to numeric values, which is covered in a few sections.

9.3 ToNumber:

  1. Let primValue be ToPrimitive(input argument, hint Number).
  2. Return ToNumber(primValue).

9.1 ToPrimitive:

Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

8.12.8 [[DefaultValue]]:

Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".

  1. If IsCallable(valueOf) is true then,

    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
    2. If val is a primitive value, return val.

This is all a roundabout way of saying that the object's valueOf function gets called and the return value from that function is used in the equation. If you were to change the valueOf function you could change the results of the operation:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10 as fives valueOf function was unchanged, it returns the wrapped primitive value 5 so that five * 5 evaluates to 5 * 5 which results in 25

11 the five object's wtf property is evaluated again despite having been unchanged from when it was assigned on 05.

12 'potato'

13 the Postfix Increment Operator is called on five, which gets the numeric value (5, we covered how earlier), stores the value so that it can be returned, adds 1 to the value (6), assigns the value to five, and returns the stored value (5)

14 as before, the returned value is the value before it was incremented

15 the wtf property of the primitive value (6) stored at the variable five is accessed. Section 15.7.5 of the ES5 specification defines this behavior. Numbers get the properties from Number.prototype.

16 Number.prototype doesn't have a wtf property, so undefined is returned

17 five.wtf is assigned a value of 'potato?'. Assignment is defined in 11.13.1 of the ES5 specification. Basically the value assigned is returned but not stored.

18 'potato?' was returned by the assignment operator

19 again five, which has a value of 6 is accessed, and again Number.prototype doesn't have a wtf property

20 undefined as explained above

21 five is accessed

22 6 is returned as explained in 13


It's pretty simple.

function dis () { return this; }

This returns the this context. So, if you do call(5) you're passing the number as an object.

The call function doesn't supply arguments, the first argument you give is the context of this. Usually if you want it on it's on context, you give it {} so dis.call({}), which means this in the function is an empty this. However, if you pass 5 it seems it will be converted to an object. See .call

So the return is object

When you do five * 5, JavaScript sees the object five as the primitive type, so is equivalent to 5 * 5. Interestingly, do '5' * 5, it still equals 25, so JavaScript is clearly casting under the hood. No changes to the underlying five type is done on this line

But when you do ++ it will convert the object to the primitive number type thus removing the .wtf property. Because you are affecting the underlying type


The primitive values can't have property. But when you try to access to a property on primitive value, it transparently transtype to a temporary Number object.

So:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6

Declare function dis. Function returns its context

function dis() { return this }
undefined

Call dis with context 5. Primitive values are boxed when passed as context in strict mode (MDN). So five now is object (boxed number).

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

Declare wtf property on five variable

five.wtf = 'potato'
"potato"

Value of five.wtf

five.wtf
"potato"

five is boxed 5, so it's number and object at the same time (5 * 5 = 25). It doesn't changes five.

five * 5
25

Value of five.wtf

five.wtf
"potato"

Unboxing five here. five now is just primitive number. It prints 5, and then add 1 to five.

five++
5

five is primitive number 6 now, there are no properties in it.

five.wtf
undefined

primitives cannot have properties, you can't set this

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

you can't read this, because it was not set

five.wtf
undefined

five is 6 because of post incrementing above

five
6