Search code examples
javascriptobjectobject-literal

Literal instance creation behavior varies


I have object literal within that i have another literal . The parent instances are setting the values fine, but not the inner one. I am confused with this behavior.
Why the behavior of object literal is different ?

here is the http://jsfiddle.net/Lq6frf76/

var vehicle={
    tires : 0,
    seats : { 
        total :0  
    }
}


var Car = Object.create(vehicle);
Car.tires=4;
Car.seats.total = 4 ;

var bike = Object.create(vehicle);
bike.tires=2;
bike.seats.total = 2 ;




console.log(Car.tires);     --->> it is not overwritten not consistent 
console.log(Car.seats.total); -->> it is overwritten here 

console.log(bike.tires);
console.log(bike.seats.total);

enter image description here

This is not a duplicate question

  1. I knew the alternatives
  2. I knew inner literal will override I saw lot of examples
  3. What I don't know why the same behavior is not repeated for the parent object or in other words

    why car.tires value is showing correctly instead of overwritten in my example


Solution

  • Edit: Added some more at the end and made a correction

    I feel like we need to put down an answer here. I apologize if this is bad etiquette (and if this is the wrong answer). Please let me know.

    The way prototypical inheritance works in javascript:
    When you read a property of an object, lets say the tires property of your Car object, first it checks if Car has that property itself. What that means is, is there a tires property directly attached to the Car object? Or in other words, is Car something like this:

    {
        tires: 0,
        ...
    }
    

    And the answer is no. You might think Object.create() does this, similar to what you might do in a constructor, i.e.

    function Vehice(t) {
        this.tires = t;
        ...
    };
    var Car = new Vehicle(4);
    

    All Object.create() does is make the object you pass in the prototype of the object it returns to you.

    So after Object.create(), your object looks something like this

    {
        __proto__: //reference to vehicle
    }
    

    So when it doesn't find tires directly attached to the object, it then looks at the prototype, in this case a reference to the vehicle object you created earlier. The prototype (vehicle) does have the tires property, so it returns that value.

    Correction

    So what happens when you say Car.tires = 6? When you write a value to an object property, things work differently. Javascript only looks to see if the object itself has that property to write to. If the object itself doesn't have that property, it first checks down the prototype chain to make sure the property is not an inherited readonly property. If it isn't, it creates that property on the object itself (so long as it's legal to write that property to the object). So now you have something that looks like this:

    {
        tires: 6,
        __proto__: //reference to vehicle
    }
    

    End Correction

    Now when you read the tires property on the Car object, it first sees the tires property on the Car object itself and returns that value, never needing to look at the prototype.

    This is why you can set the tires property on one vehicle and not effect the value of the others.

    Now for the seats.total property. When you read from the seats property, everything works as before. There is no seats property on the Car object itself, so it looks to the prototype, which does have it. Then it checks the object referred to by seats to see if it has a total property directly attached to it, and it does, so it just returns that value.

    Now when you want to write to the total property, things are once again different. The statement is this: Car.seats.total = 4. What happens here is that you are setting the total property on the object referenced by the seats property.

    Javascript first has to find the object referenced by seats. It does this by checking to see if it is a property of the Car object itself. It is not, so it checks the prototype, where it finds the seats property. Now it can write the total property to the seats object as we saw before, but notice that this is happening to the seats property of the prototype, which is a referene to the vehicle object defined before and shared by bike.

    In other words, javascript does not see that you are writing to a property of the Car object, so it doesn't create a seats property on the Car object and assign it a new object, and then a total property on that object and assign it 4.

    Now, when you try to read bike's seats property, javascript first looks to see if bike has a seats property directly attached to it. It does not, so it looks to bike's prototype, which does have it and returns the modified vehicle.seats object.

    I hope this clarifies what is happening and answers your questions. Let me know if any part isn't clear, and I hope someone will correct me if I'm just plain wrong!

    Addendum: I think coming from a traditional OO language the way it works for tires is what you would expect to happen: You inherit the initial value, but if you change it, you don't expect it to change for every other instance of vehicle.

    The way javascript does this is very space economical. By only initially having the prototype have the property, if you have a thousand objects inheriting from vehicle and only 2 of them set the tires property, you've only used up space for three Numbers instead of a thousand.

    As for the way it works with seats, I suppose they could have designed the language to detect if you were setting a property on an inherited object (seats) and then copy the entire object into the instance. My guess is that this would actually make the system less flexible and more complicated. If you wanted Car to have it's own version of seats, you could specify it like so:

    var car2 = Object.create(vehicle, {
        seats: {
            value: {
                count: vehicle.seats.total
            }
        }
    });
    

    or like so:

    var car2 = Object.create(vehicle);
    car2.seats = { count: vehicle.seats.total }; //create a new object like the one attached to vehicle
    

    Does that last example make it clearer what's happening?

    To me, in this case, an explicit constructor version of this would be more natural, but with javascript we can use constructors or `Object.create(), or various combinations of them to do what we need.

    Ultimately though, Every set happens in two parts. First, find the object we are setting the property on; Last, set the property. Everything before that final . is just there to do the first step:

    Car.seats.count = 10 means set the count property of the object referred to by Car.seats to 10. What object is Car.seats? We are now following the rules of reading a property, not writing one. Car doesn't have a seats property, so see if it inherits one. If it doesn't, Car.seats will return undefined, and trying to set a property on undefined will throw an error. If it does inherit it, Car.seats will return the object from somewhere down the prototype chain, in this case the object referred to by vehicle. This object has a count property so just set it to 10.

    Car.tires = 4 means set the tires property of the object referred to by Car to 4. Car doesn't have a tires property, so make sure we are allowed to create one and then do so.

    I hope this clarifies things. Sorry for writing so much, but doing so has really helped solidify these concepts for me. Plus I learned some ECMAScript 5 stuff that i've been blissfully ignoring :-)