Section 9.7. Determining Object Type


9.7. Determining Object Type

JavaScript 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 constructor

Once 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 Typing

One 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

 function getType(x) {     // If x is null, return "null"     if (x == null) return "null";     // Next try the typeof operator     var t = typeof x;     // If the result is not vague, return it     if (t != "object")  return t;     // Otherwise, x is an object. Use the default toString( ) method to     // get the class value of the object.     var c = Object.prototype.toString.apply(x);  // Returns "[object class]"     c = c.substring(8, c.length-1);              // Strip off "[object" and "]"     // If the class is not a vague one, return it.     if (c != "Object") return c;     // If we get here, c is "Object".  Check to see if     // the value x is really just a generic object.     if (x.constructor == Object) return c;  // Okay the type really is "Object"     // For user-defined classes, look for a string-valued property named     // classname, that is inherited from the object's prototype     if ("classname" in x.constructor.prototype &&  // inherits classname         typeof x.constructor.prototype.classname == "string") // its a string         return x.constructor.prototype.classname;     // If we really can't figure it out, say so.     return "<unknown type>"; } 

9.7.3. Duck Typing

There 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.[*]

[*] The term "duck typing" has been popularlized by the Ruby programming language. A more formal name is allomorphism.

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

 // Return true if each of the method properties in c.prototype have been // borrowed by o. If o is a function rather than an object, we // test the prototype of o rather than o itself. // Note that this function requires methods to be copied, not // reimplemented.  If a class borrows a method and then overrides it, // this method will return false. function borrows(o, c) {     // If we are an instance of something, then of course we have its methods     if (o instanceof c) return true;     // It is impossible to test whether the methods of a built-in type have     // been borrowed, since the methods of built-in types are not enumerable.     // We return undefined in this case as a kind of "I don't know" answer     // instead of throwing an exception. Undefined behaves much like false,     // but can be distinguished from false if the caller needs to.     if (c == Array || c == Boolean || c == Date || c == Error ||         c == Function || c == Number || c == RegExp || c == String)         return undefined;     if (typeof o == "function") o = o.prototype;     var proto = c.prototype;     for(var p in proto) {         // Ignore properties that are not functions         if (typeof proto[p] != "function") continue;         if (o[p] != proto[p]) return false;     }     return true; } 

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

 // Return true if o has methods with the same name and arity as all // methods in c.prototype. Otherwise, return false.  Throws an exception // if c is a built-in type with nonenumerable methods. function provides(o, c) {     // If o actually is an instance of c, it obviously looks like c     if (o instanceof c) return true;     // If a constructor was passed instead of an object, use its prototype     if (typeof o == "function") o = o.prototype;     // The methods of built-in types are not enumerable, and we return     // undefined.  Otherwise, any object would appear to provide any of     // the built-in types.     if (c == Array || c == Boolean || c == Date || c == Error ||         c == Function || c == Number || c == RegExp || c == String)         return undefined;     var proto = c.prototype;     for(var p in proto) {  // Loop through all properties in c.prototype         // Ignore properties that are not functions         if (typeof proto[p] != "function") continue;         // If o does not have a property by the same name, return false         if (!(p in o)) return false;         // If that property is not a function, return false         if (typeof o[p] != "function") return false;         // If the two functions are not declared with the same number         // of arguments, return false.         if (o[p].length != proto[p].length) return false;     }     // If all the methods check out, we can finally return true.     return true; } 

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

 function isArrayLike(x) {     if (x instanceof Array) return true; // Real arrays are array-like     if (!("length" in x)) return false;  // Arrays must have a length property     if (typeof x.length != "number") return false;  // Length must be a number     if (x.length < 0) return false;                 // and nonnegative     if (x.length > 0) {         // If the array is nonempty, it must at a minimum         // have a property defined whose name is the number length-1         if (!((x.length-1) in x)) return false;     }     return true; } 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net