As we discussed earlier, all objects in JavaScript inherit from the Object class. While more specialized classes, such as the built-in String class or a user-defined Complex class, define properties and methods of their own, all objects, whatever their class, also support the properties and methods defined by the Object class. Because of their universality, these properties and methods are of particular interest.
Starting with JavaScript 1.1, every object has a constructor property that refers to the constructor function used to initialize the object. For example, if I create an object o with the Complex( ) constructor, the property o.constructor refers to Complex:
var o = new Complex(1,2); o.constructor == Complex; // Evaluates to true
Each Complex object (or object of whatever type) does not have its own unique constructor property, of course; instead, this property is inherited from the prototype object. As discussed earlier in this chapter, JavaScript creates a prototype object for each constructor function you define and assigns that object to the prototype property of the constructor. What I did not reveal earlier, however, is that the prototype object is not initially empty. When created, it includes a constructor property that refers to the constructor function. That is, for any function f, f.prototype.constructor is always equal to f (unless we set it to something else).
Since the constructor function defines the class of an object, the constructor property can be a powerful tool for determining the type of any given object. For example, you might use code like the following to determine the type of an unknown object:
if ((typeof o == "object") && (o.constructor == Date)) // Then do something with the Date object...
The existence of the constructor property is not always guaranteed, however. The author of a class might replace the prototype object of a constructor with an entirely new object, for example, and the new object might not have a valid constructor property.
The toString( ) method takes no arguments; it returns a string that somehow represents the type and/or value of the object on which it is invoked. JavaScript invokes this method of an object whenever it needs to convert the object to a string. This occurs, for example, when you use the + operator to concatenate a string with an object or when you pass an object to a method such as alert( ) or document.write( ).
The default toString( ) method is not very informative. For example, the following lines of code simply cause the browser to display the string "[object Object]":[32]
[32]In client-side JavaScript in Netscape, if the language attribute of the <script> tag is explicitly set to "JavaScript1.2", the toString( ) method behaves differently: it displays the names and values of all the fields of the object, using object literal notation. This violates the ECMAScript specification.
c = new Circle(1, 0, 0); document.write(c);
Because this default method does not display much useful information, many classes define their own versions of toString( ). For example, when an array is converted to a string, we obtain a list of the array elements, themselves each converted to a string, and when a function is converted to a string, we obtain the source code for the function.
The idea behind toString( ) is that each class of objects has its own particular string representation, so it should define an appropriate toString( ) method to convert objects to that string form. Thus, when you define a class, you should define a custom toString( ) method for it so that instances of the class can be converted to meaningful strings. The string should contain information about the object being converted, as this is useful for debugging purposes. If the string conversion is chosen carefully, it can also be useful in programs themselves.
The following code shows a toString( ) method we might define for the Circle class of Example 8-5:
Circle.prototype.toString = function ( ) { return "[Circle of radius " + this.r + ", centered at (" + this.x + ", " + this.y + ").]"; }
With this toString( ) method defined, a typical Circle object might be converted to the string "[Circle of radius 1, centered at (0,0).]".
If you look back at Example 8-6, you'll see that it defines a toString( ) method for our Complex class of complex numbers.
One interesting feature of the default toString( ) method defined by the Object class is that it reveals some internal type information about built-in objects. 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, Array objects have a class of "Array", Function objects 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"). User-defined objects, such as the Circle and Complex classes defined earlier in this chapter, always have a class of "Object".
Note that this class value provides useful information that is not supplied by the typeof operator (which returns either "Object" or "Function" for all objects). The class value provides information like that provided by the constructor property described earlier, but the class value provides it in the form of a string, instead of in the form of a constructor function. The only way to obtain this class value, however, is through the default toString( ) method defined by Object. Because classes often define their own versions of this method, we cannot simply invoke the toString( ) method of an object:
o.toString() // May invoke a customized toString( ) method for the object
Instead, we have to refer explicitly to the default toString( ) function as the Object.prototype.toString object and use the apply( ) method of the function to invoke it on the desired object:
Object.prototype.toString.apply(o); // Always invokes the default toString( )
We can use this technique to define a function that provides enhanced "type of" functionality:
// An enhanced "type of" function. Returns a string that describes the // type of x. Note that it returns "Object" for any user-defined object types. function Typeof(x) { // Start with 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. Get its class value to try to // find out what kind of object it is. var c = Object.prototype.toString.apply(x); // Returns "[object class]" c = c.substring(8, c.length-1); // Strip off "[object" and "]" return c; }
In ECMAScript v3 and JavaScript 1.5, the Object class defines a toLocaleString( ) method in addition to its toString( ) method. The purpose of this method is to return a localized string representation of the object. The default toLocaleString( ) method defined by Object doesn't do any localization itself; it always return exactly the same thing as toString( ). Subclasses, however, may define their own versions of toLocaleString( ). In ECMAScript v3, the Array, Date, and Number classes do define toLocaleString( ) methods that return localized values.
The valueOf( ) method is much like the toString( ) method, but it is called when JavaScript needs to convert an object to some primitive type other than a string -- typically, a number. Where possible, the function should return a primitive value that somehow represents the value of the object referred to by the this keyword.
By definition, objects are not primitive values, so most objects do not have a primitive equivalent. Thus, the default valueOf( ) method defined by the Object class performs no conversion and simply returns the object on which it is invoked. Classes such as Number and Boolean have obvious primitive equivalents, so they override the valueOf( ) method to return appropriate primitive values. This is why Number and Boolean objects can behave so much like their equivalent primitive values.
Occasionally, you may define a class that has some reasonable primitive equivalent. In this case, you may want to define a custom valueOf( ) method for the class. If you refer back to Example 8-6, you'll see that we defined a valueOf( ) method for the Complex class. This method simply returned the real part of the complex number. Thus, when a Complex object is used in a numeric context, it behaves as if it were a real number without its imaginary component. For example, consider the following code:
var a = new Complex(5,4); var b = new Complex(2,1); var c = Complex.subtract(a,b); // c is the complex number {3,3} var d = a - b; // d is the number 3
One note of caution about defining a valueOf( ) method: the valueOf( ) method can, in some circumstances, take priority over the toString( ) method when converting an object to a string. Thus, when you define a valueOf( ) method for a class, you may need to be more explicit about calling the toString( ) method when you want to force an object of that class to be converted to a string. To continue with the Complex example:
alert("c = " + c); // Uses valueOf( ); displays "c = 3" alert("c = " + c.toString( )); // Displays "c = {3,3}"
The hasOwnProperty( ) method returns true if the object locally defines a noninherited property with the name specified by the single string argument. Otherwise, it returns false. For example:
var o = new Object( ); o.hasOwnProperty("undef"); // false: the property is not defined o.hasOwnProperty("toString"); // false: toString is an inherited property Math.hasOwnProperty("cos"); // true: the Math object has a cos property
The propertyIsEnumerable( ) method returns true if the object defines a property with the name specified by the single string argument to the method and if that property would be enumerated by a for/in loop. Otherwise, it returns false. For example:
var o = { x:1 }; o.propertyIsEnumerable("x"); // true: property exists and is enumerable o.propertyIsEnumerable("y"); // false: property doesn't exist o.propertyIsEnumerable("valueOf"); // false: property isn't enumerable
Note that the ECMAScript specification states that propertyIsEnumerable( ) considers only properties defined directly by the object, not inherited properties. This unfortunate restriction makes the function less useful, because a return value of false may mean either that the property is not enumerable or that it is enumerable but is an inherited property.
The isPrototypeOf( ) method returns true if the object is the prototype object of the argument. Otherwise, it returns false. Using this method is similar to using the constructor property of an object. For example:
var o = new Object( ); Object.prototype.isPrototypeOf(o); // true: o.constructor == Object Object.isPrototypeOf(o); // false o.isPrototypeOf(Object.prototype); // false Function.prototype.isPrototypeOf(Object); // true: Object.constructor == Function
Copyright © 2003 O'Reilly & Associates. All rights reserved.