9.7. Determining Object TypeJavaScript is loosely typed, and JavaScript objects are even more loosely typed. There are a number of techniques you can use to determine the type of an arbitrary value in JavaScript. The most obvious technique is the typeof operator, of course (see Section 5.10.2. for details). typeof is useful primarily for distinguishing primitive types from objects. There are a few quirks to typeof. First, remember that typeof null is "object", while typeof undefined is "undefined". Also, the type of any array is "object" because all arrays are objects. However, the type of any function is "function", even though functions are objects, too. 9.7.1. instanceof and constructorOnce you have determined that a value is an object rather than a primitive value or a function, you can use the instanceof operator to learn more about it. For example, if x is an array, the following evaluates to true: x instanceof Array The left side of instanceof is the value to be tested, and the right side should be a constructor function that defines a class. Note that an object is an instance of its own class and of any superclasses. So, for any object o, o instanceof Object is always TRue. Interestingly, instanceof works for functions, so for any function f, all these expressions are true: typeof f == "function" f instanceof Function f instanceof Object If you want to test whether an object is an instance of one specific class and not an instance of some subclass, you can check its constructor property. Consider the following code: var d = new Date( ); // A Date object; Date extends Object var isobject = d instanceof Object; // evaluates to true var realobject = d.constructor==Object; // evaluates to false 9.7.2. Object.toString( ) for Object TypingOne shortcoming of the instanceof operator and the constructor property is that they allow you to test an object only against classes you already know about. They aren't useful to inspect unknown objects, as you might do when debugging, for example. A useful trick that uses the default implementation of the Object.toString( ) method can help in this case. As shown in Chapter 7, Object defines a default toString( ) method. Any class that does not define its own method inherits this default implementation. An interesting feature of the default toString( ) method is that it reveals some internal type information about built-in objects. The ECMAScript specification requires that this default toString( ) method always returns a string of the form: [object class] class is the internal type of the object and usually corresponds to the name of the constructor function for the object. For example, arrays have a class of "Array", functions have a class of "Function", and Date objects have a class of "Date". The built-in Math object has a class of "Math", and all Error objects (including instances of the various Error subclasses) have a class of "Error". Client-side JavaScript objects and any other objects defined by the JavaScript implementation have an implementation-defined class (such as "Window", "Document", or "Form"). Objects of user-defined types, such as the Circle and Complex classes defined earlier in this chapter, always have a class of "Object", however, so this toString( ) technique is useful only for built-in object types. Since most classes override the default toString( ) method, you can't invoke it directly on an object and expect to find its class name. Instead, you refer to the default function explicitly in Object.prototype and use apply( ) to invoke it on the object whose type you're interested in: Object.prototype.toString.apply(o); // Always invokes the default toString( ) This technique is used in Example 9-6 to define a function that provides enhanced "type of" functionality. As noted earlier, the toString( ) method does not work for user-defined classes, so in this case, the function checks for a string-value property named classname and returns its value if it exists. Example 9-6. Enhanced typeof testing
9.7.3. Duck TypingThere is an old saying: "If it walks like a duck and quacks like a duck, it's a duck!" Translated into JavaScript, this aphorism is not nearly so evocative. Try it this way: "If it implements all the methods defined by a class, it is an instance of that class." In flexible, loosely typed languages like JavaScript, this is called duck typing: if an object has the properties defined by class X, you can treat it as an instance of class X, even if it was not actually created with the X( ) constructor function.[*]
Duck typing is particularly useful in conjunction with classes that "borrow" methods from other classes. Earlier in the chapter, a Rectangle class borrowed the implementation of an equals( ) method from another class named GenericEquals. Thus, you can consider any Rectangle instance to also be an instance of GenericEquals. The instanceof operator will not report this, but you can define a method that will. Example 9-7 shows how. Example 9-7. Testing whether an object borrows the methods of a class
The borrows( ) method of Example 9-7 is relatively strict: it requires the object o to have exact copies of the methods defined by the class c. TRue duck typing is more flexible: o should be considered an instance of c as long as it provides methods that look like methods of c. In JavaScript, "look like" means "have the same name as" and (perhaps) "are declared with the same number of arguments as." Example 9-8 shows a method that tests for this. Example 9-8. Testing whether an object provides methods
As an example of when duck typing and the provides( ) method are useful, consider the compareTo( ) method described in Section 9.4.3. compareTo( ) is not a method that lends itself to borrowing, but it would still be nice if we could easily test for objects that are comparable with the compareTo( ) method. To do this, define a Comparable class: function Comparable( ) {} Comparable.prototype.compareTo = function(that) { throw "Comparable.compareTo( ) is abstract. Don't invoke it!"; } This Comparable class is abstract: its method isn't designed to actually be invoked but simply to define an API. With this class defined, however, you can check if two objects can be compared like this: // Check whether objects o and p can be compared // They must be of the same type, and that type must be comparable if (o.constructor == p.constructor && provides(o, Comparable)) { var order = o.compareTo(p); } Note that both the borrows( ) and provides( ) functions presented in this section return undefined if passed any core JavaScript built-in type, such as Array. This is because the properties of the prototype objects of the built-in types are not enumerable with a for/in loop. If those functions did not explicitly check for built-in types and return undefined, they would think that these built-in types have no methods and would always return true for built-in types. The Array type is one that is worth considering specially, however. Recall from Section 7.8. that many array algorithms (such as iterating over the elements) can work fine on objects that are not true arrays but are array-like. Another application of duck typing is to determine which objects look like arrays. Example 9-9 shows one way to do it. Example 9-9. Testing for array-like objects
|