Just JavaScript - Life Without Type - Duck Testing And Prototype Construction |
Written by Ian Elliot | |||||
Thursday, 07 May 2015 | |||||
Page 3 of 4
The Prototype ConstructorThere is an argument that the way that the constructor is used in JavaScript is misguided as it doesn't really make the best of the prototype idea. The constructor approach makes use of a prototype object to supply an initial set of properties and then generates more properties to augment the object. When you call the constructor it creates a completely new object with those custom properties and this is inefficient and not elegant. For example, if you have a prototype object
and a constructor that makes use of it:
Now consider creating two instances using the constructor:
both objects have a complete copy of the sum function but they share the same x and y properties as they are provided by the prototype. The new objects only get their own x and y if they store a new value in either - this is how the prototype idea works. A few moments thought reveals that it is silly putting the function into the constructor - it should be in the prototype:
Now when you create two objects using A
both objects initially share everything that the prototype supplies and the actual object returned by the constructor is empty {}.
This is one of the motivations for using prototypes - to avoid stamping out complete copies of an object every time it is constructed. So why not take it to its logical conclusion and put everything in the prototype? You can think of the empty object that the constructor creates as waiting to be used to store any properties that have to be promoted to own properties, an example of the copy on write principle, because something is stored in them or to store any dynamic properties that are created as the program runs. In other words the prototype holds all of initial properties of the object created by A and the prototype completely defines the object created by the constructor. This is a very simple and yet powerful idea - put everything that defines and object in the prototype, why not? This is the idea of a prototype constructor and it makes object construction very easy and it is a natural fit with the idea that the prototype chain defines subtype and type. It also overcomes the problem introduced in the previous section because now the object being constructed is part of its prototype chain. For example, to create the previous object complete with sum function you would use:
Notice that the constructor doesn't do a thing its all in the prototype and there is only ever one instance of the prototype object.
In this case the sum method and the values of x and y are provided by the prototype. If you assign to x or y then it becomes an own property and isn't supplied by the prototype.
If you don't like the untidy way that creating the prototype is outside of a function you can wrap all of the prototype constructors inside an immediately invoked function that returns the constructor :
This looks complicated but is always follows the same form. Create and return a function, the constructor that does nothing and use its prototype to define the object. Note once again that no matter how many instances A creates they all share the same prototype object. The only new object created when you create an instance of A is a new empty object {} which is waiting to store any instance/own properties that are created when you store a value in a prototype property. How does prototype inheritance work if you use a prototype constructors? It all works very naturally lets create a B constructor that inherits from A:
We now just create an instance of A to act as the prototype. If you think about it for a moment an instance of A is an empty object with a prototype that provides its properties. So we are back to where we started with an empty object that we can add properties to necessary to create a derived object. In this case we simply add a z property. Also notice that all instances of B share the same prototype object and hence the complete prototype chain as before the only new object created is an empty object ready to store any own properties.. You can try this out with
and you will see that b has properties provided by its prototype and the earlier prototype. You can also add the setting of the constructor property on the prototype but this isn't particularly useful. The point is that the object being constructed is always the first prototype in the prototype chain and not the empty object returned by the constructor so you really don't need to know what the constructor is. So for example the B constructor creates an empty object {} with the following prototype chain.
That is you can determine both type and subtype using nothing but isPrototypeOf. For example:
is true and can be interpreted to mean that b is an instance of B. As you can now regard an object as being in its prototype chain it solves the problem of the last section where testing for things that inherit from animal didn't include the animal object itself. We also have
is also true and this can be interpreted to mean that b is also an instance of A i.e. a subtype of A. This approach also makes the otherwise strange instanceof operator work as it should. For example:
is true and this really does mean that b has all of the properties of an object constructed by B. Of course it works because it it exactly equivalent to
Both also work correctly when the inheritance hierarchy branches. For example, if you create a C that also inherits from A:
then
are both true but
is false which is what you would expect - but compare with the same example in the previous chapter where things don't work as expected. Using the prototype constructor approach makes instanceof work correctly and allow you to test for the type and subtype using just the prototype chain. |
|||||
Last Updated ( Tuesday, 25 August 2015 ) |