|Written by Ian Elliot|
|Thursday, 07 May 2015|
Page 2 of 4
Trying The Duck
OK so you know that the property exists.
You know that it is the right sort of property.
It can still all go horribly wrong.
A function might not have the right parameters and it might not return the correct sort of thing.
You could check each of the parameters and even the return type but if you don't know where the object came from it could be completely broken even though all of the surface detail checks out.
Perhaps the simplest way to implement duck testing is to simply put the whole of the property access inside a try-catch.
This catches all of the possible problems with the property access including things that type checking couldn't possibly get.
Of course there are some problems with this approach.
The first is that try catch statements slow code down - not by much but they make optimization harder. This probably means you should keep such checking outside of loops - check a property access once.
Another problem is more theoretical. Exceptions that are handled by try-catch really should be exceptions and not just logical conditions that you are too lazy to test for. However in this case if your worry is that the object that you have been passed is in some way faulty then using an exception is reasonable.
Try-catch really does seem like the total solution to the problem with only minor disadvantages.
If you cannot determine the type of object a variable references at runtime then it is reasonable to enclose any use of its properties in a try-catch.
Of course this doesn't tell you what you should do when you discover that you have the wrong type of object - but this is a problem common to all languages.
Prototype Testing - getPrototypeOf and isPrototypeOf
Duck testing individual properties is fine but sometimes you just want to check that an object supports a block of properties.
In the previous chapter the idea of testing to see if a particular object was in the prototype chain was introduced via the strange instanceof operator.
The idea is that you can treat an object as a bag of properties and if you know that a particular object is in the prototype chain you can reasonably rely on its properties being available.
In ES5 there are two new features which make prototype chain testing much easier and much more direct.
Until ES5 there was no way to discover an object's imediate prototype but now you can use the getPrototypeOf(obj) method to directly access the internal prototype property of any object.
Notice that getProtypeOf returns a reference to the prototype object.
So if and object d has a prototype chain given by:
will display the properties of c. A common way of saying this is that d is a subtype of c or even that d is an instance of c.
This is very handy, but in most cases you are not just interested in the imediate prototype. You really don't care where an object is in the prototype chain you usually just want to know that it is in the prototype chain. You could use getPrototypeOf to recurse through the prototype chain e.g.
returns an instance of B, but why bother when there is isPrototypeOf(obj).
This will test to see if the prototype is in the prototype chain of obj.
is true if the object proto is in the prototype chain of obj.
The main problem with using isPrototypeOf is getting a reference to the prototype object in the first place.
So if you have a prototype chain
will be true is b is in the prototype chain of d.
Notice that in this case for this to be true b has to be a reference to the instance that is actually part of the prototype chain. You can't simply create a new instance of B. That is you can't simply do:
The instance of B that is the prototype isn't the same instance as referenced by b. It isn't enough that the objects are of the same "type" they have to be the same object.
What you have to do is keep a reference to the prototype when you create it.
Now you can do
and the result will be true.
Another common pattern is the use of a constructor's prototype property to see if its prototype is in the object's prototype chain:
In this case it is tempting to interpret a true result as indicating that B is d's constructor but this is obviously not the case. All it indicates is that the prototype of B is in d's prototype chain.
Prototype Chain Checking Problems
At this point you have the machinery to check that any particular object is in the prototype chain.
This at first seems to be very useful.
If it so happens that your design lends itself to a hierarchical structure with each new object inheriting from the objects that came before and each object elaborating on the basic object then you can write functions that accept an object from any point in the hierarchy down - just as you can in a class based language.
To use an old and completely unrealistic example suppose you have an animal object and dog and cat objects which have animal in their prototype chain and a kitten and puppy object which has cat and dog objects as their prototype respectively.
That is the prototype chains are:
This is perfect hierarchical inheritance and you can write a function that demands that the object is an instance of an animal i.e. has all of the properties of an animal object using
This will accept any cat, kitten, puppy or dog object and as long as you only use methods that are defined in animal it all works.
However do you notice another problem?
This method doesn't accept an animal object even though you would expect it to because animal has all of the properties you are testing for in the other objects.
The reason is of course that animal isn'tt part of its own prototype chain. Its properties are defined as own properties in the animal object.
To test animal or any subtype of animal you have to test that animal is in the prototype chain or the constructor was Animal.
This is messy but can be done, as long as you remember to always set the constructor property on each of the prototypes. either the constructor or in the prototype chain.
The problem is that when you use a constructor in the usual way the object itself isn't regarded as part of its own prototype chain and so you have to test for it separately.
A much better solution is to use a prototype constructor approach.
|Last Updated ( Tuesday, 25 August 2015 )|