0

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?

Community
  • 1
  • 1
IamNaN
  • 6,654
  • 5
  • 31
  • 47

3 Answers3

1

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.)

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
1

Since you've already read over similar questions and answers and still seem to be having trouble understanding the behavior, I'll try to make my explanation as clear as possible. Here is where I think you are going wrong (emphasis mine):

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.

The problem here is that you assume that user.name.first counts as "a property . . . set on a derived object" (an instance of User). However, this is simply not the case. In JavaScript, inheritance of properties is only shallow (a single layer deep). user.name is just an object value shared by reference through the prototype, so modifications to it from one place are reflected everywhere.

Think of user1.name and user2.name like firstReference and secondReference in the following example snippet, and hopefully the behavior will seem a bit clearer to you.

var User = {
  username: "",
  name: {
    first: "",
    last: ""
  }
}

var firstReference = User.name
var secondReference = User.name

firstReference.name.first = 'First!'

console.log(secondReference.name) //=> 'First!' (logical and expected result)
gyre
  • 16,369
  • 3
  • 37
  • 47
0

The Object.create method creates an object using the first parameter as a prototype, and the second, optional, parameter is an additional object of own properties.

I think the problem here is that name is, by definition, in the prototype, and so not an own property.

If you want separate properties, then you should use the second parameter. The prototype is where you store methods and shared properties.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

has more details.

The key is that you want both prototype and own properties.

Manngo
  • 14,066
  • 10
  • 88
  • 110