问题
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:
- Does
bob
have a property calledtoString
? No. - Does
bob.__proto__
i.e.person
have a property calledtoString
? No. - Does
bob.__proto__.__proto__
i.e.Object
have a property calledtoString
? Yes - 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:
- Does
bob
have a property calledtoString
? 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:
- Does
bob
have a property calledpersonal_details
? No. - Does
person
have a property calledpersonal_details
? Yes. - 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.
- Write the value
123
to the propertyage
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