Javascript Prototypal Inheritance & object properties shadowing

与世无争的帅哥 提交于 2019-12-20 10:09:29

问题


var person = { name :"dummy", personal_details: { age : 22, country : "USA" } };

var bob = Object.create(person);

bob.name = "bob";
bob.personal_details.age = 23;


console.log(bob.personal_details === person.personal_details);
// true : since it does not shadow object of prototype object

console.log(bob.name  === person.name);
// false : since it shadows name

////now
bob.personal_details  = {a:1};
console.log(bob.personal_details === person.personal_details); 

//false

When object bob tries to override "name" property of person then, it gets shadowed in bob itself.

But in case of personal_details the same rule is violated.

I am quite curious to know why is it like so ??

Here is the link to jsbin : http://jsbin.com/asuzev/1/edit


回答1:


What is going on can be illustrated quite easily with a few examples:

Accessing personal_details via the prototype chain

var person = { name :"dummy", personal_details: { age : 22, country : "USA" } }
var bob = Object.create(person)

bob.name = "bob"
bob.personal_details.age = 23

Which outputs like:

console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }

console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }

Age 23 is now set on the person object because bob.personal_details is a direct reference to person.personal_details via bob's prototype chain. Once you navigate down the object structure you are working directly with the person.personal_details object.


Overriding a prototyped property with a local property

However if you override bob's personal_details property with another object, that prototype link will be overridden by a more local property.

bob.personal_details = { a: 123 }

Now the output is:

console.log( bob );
/// { name :"bob", personal_details: { a : 123 } }

console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }

So by accessing bob.personal_details—from now on—you are referencing the { a: 123 } object not the original { age : 23, country : "USA" } object of person. All changes made will occur on that object and basically have nothing to do with bob or the person object.


The Prototype chain

To make things interesting, what do you think happens when you do this, after all the above:

delete bob.personal_details

You end up reviving the original prototype link to person.personal_details (because you've removed the local property you added), so a console log would reveal:

console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }

Basically the JavaScript engine will work its way back along the prototype chain, until it finds the property or method you are requesting on each prototype object. The further up the chain an item is set, will mean it will override the others later on down.

Now another question, what happens if you fire off the following again?

delete bob.personal_details

Nothing, there is no longer an actual property assigned to bob called personal_details, delete will only work on the current object and not follow down the prototype chain.


A different way of looking at it

Another way of looking at how the prototype chain works is basically imagining a stack of objects. When JavaScript scans for a particular property or method it would read downwards through the following structure:

bob :          {  }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

So as an example, say I wanted to access bob.toString. toString is a method that exists on JavaScript's base object Object which is the base prototype for nearly everything. When the interpreter gets a read-request for a particular method or property on an object it will follow this chain of events:

  1. Does bob have a property called toString? No.
  2. Does bob.__proto__ i.e. person have a property called toString? No.
  3. Does bob.__proto__.__proto__ i.e. Object have a property called toString? Yes
  4. Return the reference to function(){ return '[Object object]'; }

Once it reaches point 4 the interpreter will have returned the reference to the toString method found on Object. If the property hadn't been found on Object an error of undefined property would most likely have been fired (because it is the last in the chain).

Now if we take the example before, and this time define a toString method on bob - so:

bob :          { toString: function(){ return '[Bob]'; } }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

If we try and read-access the toString method on bob again, this time we get:

  1. Does bob have a property called toString? Yes.

The process stops at the first hurdle and returns the toString method from bob. This means that bob.toString() will return [Bob] rather than [Object object].

As has been succinctly stated by Phant0m write requests on an object follow a different path and will never travel down the prototype chain. Understanding this is to work out the difference between what is a read, and what is a write request.

bob.toString                   --- is a read request
bob.toString = function(){}    --- is a write request
bob.personal_details           --- is a read request
bob.personal_details = {}      --- is a write request
bob.personal_details.age = 123 --- is a read request, then a write request.

The last item is the one that is causing confusion. And the process would follow this route:

  1. Does bob have a property called personal_details? No.
  2. Does person have a property called personal_details? Yes.
  3. Return the reference to { age: 22 } which is stored floating somewhere in memory.

Now a new process is started because each part of an object navigation or assignment is a new request for a property or method. So, now we have our personal_details object we switch to a write request, because a property or variable on the left had side of an = equals is always an assignment.

  1. Write the value 123 to the property age on the object { age: 22 }

So the original request could be seen as something like this:

(bob.personal_details)            --- read
    (personal_details.age = 123)  --- write

If bob had owned its own property of personal_details the process would have been the same but the target object being written to would have been different.


And finally...

Put along the lines of your question:

Its difficult to digest that properties on prototype objects are treated as READ_ONLY, but if property is an object then one can get hold of it and can freely modify its properties !! Is my understanding right ?

Prototyped properties are seemingly read-only, but only when accessed directly as a property of the object that is inheriting them -- because those properties don't actually exist on the inheriting object at all. If you navigate down to the prototype object itself it can then be treated just as any normal object (with read and write) because that is exactly what it is — a normal object. It might be confusing initially, but this is the nature or prototype inheritance, it's all about how you access the properties you're working with.




回答2:


You don't assign to a property of bob in the second case, so how could you override it?

In the case of bob.name = "bob", you bind bob's own name property.

In the second case, you don't. You access bob's personal_details property–via the prototype. You then assign to a property of that object, all connection to bob is lost by that time.

Think of it like this:

bob.personal_details.age = 23;
// equivalent
expr = bob.personal_details;
expr.age = 23; // As you can see, there's no assignment to bob

It doesn't violate any rules, because the case is completely different.




回答3:


I hope that the diagram bellow is clear enough, but I'll try to explain in short what's happening.

When you create new object with Object.create you create an object with prototype the first argument of the method create. So you create bob with prototype which points to the person object. All properties of person are accessible by bob through the reference to it's prototype.

In the next picture you change bob's name. It's now bob so simply you create new slot or property of bob which name is name (now the interpreter won't check the prototype chain when looking for property name it'll directly find that bob has such property).

In the third one you change bob.personal_details.age which affects to person.personal_details.age because simply that's the same object.

At last you set the property personal_details, now bob has slot personal_details, it's not a prototype property it's a reference to another object - the anonymous one.



来源:https://stackoverflow.com/questions/14379208/javascript-prototypal-inheritance-object-properties-shadowing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!