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

后端 未结 10 1318
孤街浪徒
孤街浪徒 2020-12-07 06:57

A recent tweet contained this snippet of JavaScript.

Can someone please explain what is happening in it step by step?

> function dis() { return th         


        
相关标签:
10条回答
  • 2020-12-07 07:12

    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.

    0 讨论(0)
  • 2020-12-07 07:16

    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
    
    0 讨论(0)
  • 2020-12-07 07:18

    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.

    0 讨论(0)
  • 2020-12-07 07:20

    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

    0 讨论(0)
  • 2020-12-07 07:20

    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
    
    0 讨论(0)
  • 2020-12-07 07:21

    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" />

    0 讨论(0)
提交回复
热议问题