Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Prototypal Inheritance & object properties shadowing

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

like image 395
Ashish Avatar asked Jan 17 '13 12:01

Ashish


People also ask

What is prototypal inheritance JavaScript?

When we read a property from object , and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called “prototypal inheritance”.

What is the biggest advantage of prototypal inheritance in JavaScript?

One of the most important advantages of prototypal inheritance is that you can add new properties to prototypes after they are created. This allows you to add new methods to a prototype which will be automatically made available to all the objects which delegate to that prototype.

How is prototypal inheritance different?

By now you must have realized the difference between classical inheritance and prototypal inheritance. Classical inheritance is limited to classes inheriting from other classes. However prototypal inheritance includes not only prototypes inheriting from other prototypes but also objects inheriting from prototypes.

What is the main difference between prototypal and class inheritance?

The most important difference between class- and prototype-based inheritance is that a class defines a type which can be instantiated at runtime, whereas a prototype is itself an object instance.


3 Answers

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.

like image 141
Pebbl Avatar answered Nov 15 '22 20:11

Pebbl


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.

like image 28
phant0m Avatar answered Nov 15 '22 21:11

phant0m


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.

enter image description here

like image 28
Minko Gechev Avatar answered Nov 15 '22 21:11

Minko Gechev