Prototypal Inheritance
Asymmetrical Reading and Writing of Inherited Members
In classical inheritance, each instance of Author has its own copy of the books array. You could add to it by writing author[1].books.push(‘New Book Title’). That is not initially possible with the object you created using prototypal inheritance because of the way prototype chaining works. A clone is not a fully independent copy of its prototype object; it is a new empty object with its prototype attribute set to the prototype object. When it is just created, author[1].name is actually a link back to the primitive Person.name. This is because of the asymmetry inherent in reading and writing objects linked from the prototype. When you read the value of author[1].name, you are getting the value linked from the prototype, provided you haven’t defined the name attribute directly on the author[1] instance yet. When you write to author[1].name, you are defining a new attribute directly on the author[1] object.
This example illustrates that asymmetry:
|
|
This also illustrates why you must create new copies of data types that are passed by reference. In the previous example, pushing a new value onto the authorClone.books array is actually pushing it to Author.books. This is bad because you just modified the value not only for Author but for any object inheriting from Author that has not yet overwritten the default. You must create
new copies of all arrays and objects before you start changing their members. It is very easy to forget this and modify the value of the prototype object. This should be avoided at all costs; debugging these types of errors can be very time-consuming. In these situations, you can use the hasOwnProperty method to distinguish between inherited members and the object’s actual members.
Sometimes prototype objects will have child objects within them. If you want to override a single value within that child object, you have to recreate the entire thing. This can be done by setting the child object to be an empty object literal and then recreating it, but that would mean that the cloned object would have to know the exact structure and defaults for each child object. In order to keep all objects as loosely coupled as possible, any complex child objects should be created using methods:
|
|
In this example, childObject is recreated and ompoundObjectClone.childObject.num is modified. The problem is that compoundObjectClone must know that childObject has two attributes, with values true and 10. A better approach is to have a factory method that creates the childObject:
|
|
The clone Function
|
|
First the clone function creates a new and empty function, F. It then sets the prototype attribute of F to the prototype object. You can see here the intent of the original JavaScript creators. The prototype attribute is meant to point to the prototype object, and through prototype chaining it provides links to all the inherited members. Lastly, the function creates a new object by calling the new operator on F. The cloned object that is returned is completely empty, except
for the prototype attribute, which is (indirectly) pointing to the prototype oject, by way of the F object.