A recent tweet contained this snippet of JavaScript.
Can someone please explain what is happening in it step by step?
> function dis() { return th
First off it looks like this is being run through the nodejs console.
1.
function dis() { return this }
creates the function dis(), but because it wasn't set as a var
there was no value to return so undefined
was the output, even though dis()
was defined. On a sidenote, this
wasn't returned because the function wasn't executed.
2.
five = dis.call(5)
This returns javascript's Number
object because you just set the function dis()
's this
value to the primitive five.
3.
five.wtf = 'potato'
The first returns "potato"
because you just set the property wtf
of five
to 'potato'
. Javascript returns the value of a variable you set, making it easy to chain multiple variables and set them to the same value like this: a = b = c = 2
.
4.
five * 5
This returns 25
because you just multiplied the primitive number 5
to five
. The value of five
was determined by the value of the Number
object.
5.
five.wtf
I skipped this line before because I would be repeating it here.
It just returns the value of the property wtf
that you set above.
6.
five++
As @Callum said, ++
will convert the type to number
from the same value from the object Number {[[PrimitiveValue]]: 5}}
.
Now because five
is a number
, you can't set properties to it anymore until you do something like this:
five = dis.call(five)
five.wtf = "potato?"
or
five = { value: 6, wtf: "potato?" }
Also note that the second way will have different behavior than using the first method because it is defining a generic object instead of the Number
object that was created before.
I hope this helps, javascript likes to assume things, so it can get confusing when changing around from the Number
object to a primitive number
.
You can check what type something is by using the typeof
keyword, writing
typeof five
after you initialize it returns 'object'
, and after you do five++
it returns 'number'
.
@deceze describes the difference between the Number object and the primitive number extremely well.
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
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.
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
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
JavaScript scopes are made of Execution Contexts. Each Execution Context has a Lexical Environment (external/globally scoped values), a Variable Environment (locally scoped values), and a this binding.
The this binding is a very important part of the Execution Context. Using call
is one way to alter the this binding, and doing so will automatically create an object to populate the binding with.
Function.prototype.call() (from MDN)
Syntax
fun.call(thisArg[, arg1[, arg2[, ...]]])
thisArg
The value of this provided for the call to fun. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode code, null and undefined will be replaced with the global object and primitive values will be converted to objects. (emphasis mine)
Once it is evident that 5 is being converted into new Number(5)
, the rest should be rather obvious. Note that other examples will also work so long as they are primitive values.
function primitiveToObject(prim){
return dis.call(prim);
}
function dis(){ return this; }
//existing example
console.log(primitiveToObject(5));
//Infinity
console.log(primitiveToObject(1/0));
//bool
console.log(primitiveToObject(1>0));
//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />