I'm trying to get my head around prototype inheritance in Javascript. I think I got the basic concept, but when I was playing around with this I ran into the following which still has me puzzled.
There is a very similar question and answer here but it doesn't fully answer why this is happening, at least not for me.
I create a new object like this:
var User = {
username: "",
name: {
first: "",
last: ""
}
}
Next I create two "instances" of that object:
var user1 = Object.create(User);
var user2 = Object.create(User);
Now I set the name property like so:
user1.name = { first: "John", last: "Jackson"}
user2.name = { first: "James", last: "Jameson"}
Now I do
alert(user1.name.first) \\ -> John
alert(user2.name.first) \\ -> James
All as expected. So far so good.
However, if I set the name.first property like this:
user1.name.first = "John";
user2.name.first = "James";
and I get
alert(user1.name.first) \\ -> James
alert(user2.name.first) \\ -> James
Clearly now the property is being set on the prototype object User
(or rather the contained name
object) instead of overriding it in the current object user1
. Why does that occur?
Further if I do
user1.name.middle = "Mortimer"
I can now do
alert(User.name.middle) // -> Mortimer
which is not what I would expect. Generally, whenever a property is set on a derived object, that object either already has that property as an ownProperty
in which case the value is simply assigned, or the property is newly created as an ownProperty
on the derived object, overriding the prototype property. Just like happens when I assign to user1.name
.
So why does assigning to an object contained in the prototype object cause such (at least to me) unexpected and counter-intuitive behavior?
The way I understand it, when the assignment is made the first check is to see if user1
has an ownProperty
called name
, which it doesn't. If this were a read operation the prototype
property would now be looked up and User
checked to see if it has ownProperty
name
. But since this is a set operation why walk the prototype chain when usually a missing ownProperty is simply created?
But since this is a set operation why walk the prototype chain when usually a missing ownProperty is simply created?
When you say user1.name.first = "John"
, the user1.name
part has to be resolved before the .first
property can be retrieved or set. And in your example the user1.name
part only exists on the prototype object, so it is that object whose .first
property you are setting.
Similarly, when you say user1.name.middle = "Mortimer"
, again the user1.name
part resolves to the nested object from the prototype, so then you create a .middle
property on that object, which is why User.name.middle
also returns "Mortimer"
.
If you said user1.name.first
and user1.name
could not be resolved (on the current object or in its prototype chain) then you'd have a TypeError: Cannot set property 'first' of undefined
. (You can try that concept with your existing code by saying user1.address.street = "something"
- you'd get the TypeError
, because user1.address
doesn't exist on user1
or its prototype chain.)