For any object in JavaScript, there are three special methods that control the way the object is manipulated. Each of these methods is automatically invoked by JavaScript to manipulate the object in some way. By providing a custom definition of the method, you can control the way an object is manipulated. The methods are toString(), which is invoked to convert the object to a string, valueOf(), which is invoked to convert the object to a number or other nonobject type, and assign(), which is invoked to assign a value to the object. These three methods are detailed in the sections below.
The toString() method takes no arguments and returns a string, which should somehow represent the type and/or value of the object referred to by this. JavaScript invokes this method whenever it needs to convert an 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 like document.write(). The default toString() method for user-defined objects is not very informative. For example, the following lines of code simply cause the browser to display the string "[object Object]":
c = new Circle(1, 0, 0); document.write(c);
You can define your own toString() method so that your objects can be converted to more meaningful strings that contain more information about the object being converted. This is very useful when debugging programs, and if the string conversions are chosen carefully, it can also be useful in the programs themselves.
The toString() method is an excellent candidate, of course, for inclusion in a prototype object when defining a class of JavaScript objects. We might write and register a toString() method for our Circle class as follows:
function Circle_toString() { return "[Circle of radius " + this.r + ", centered at (" + this.x + ", " + this.y + ").]"; } Circle.prototype.toString = Circle_toString();
With this toString() method defined, a typical Circle object might be converted to "[Circle of radius 1, centered at (0,0).]".
The valueOf() method is much like the toString() method, but is called when JavaScript needs to convert an object to some type other than an object or a string, typically a number. It takes no arguments, and should return a number, Boolean, or function that somehow represents the "value" of the object referred to by the this keyword.
Most objects are more complicated than number or Boolean values, and so the valueOf() method is not often used. In fact, its main purpose is for use with the Number, Boolean, and Function objects, for which it returns the corresponding number, Boolean, or function value. For most objects, the default valueOf() method simply returns the object itself; this is a way of indicating that the object could not be converted to any nonobject type. You may occasionally find circumstances in which you can meaningfully convert an object to a primitive type, and in these cases, you may want to provide a custom definition of the valueOf() method.
Suppose, for example, that you define a class of Complex objects that represent complex numbers. This class will define methods for arithmetic on complex numbers, but you'd still like to be able to use your Complex objects with the regular arithmetic operators, as if they were real numbers. You might do so with code like that shown in Example 7.6.
function Complex(x,y) { this.x = x; // real part of complex number this.y = y; // imaginary part of complex number } // force the prototype object to be created new Complex(0,0); // define some methods Complex.prototype.valueOf =index.html new Function("return this.x"); Complex.prototype.toString = new Function("return '{'+this.x+','+this.y+'}'"); // create new complex number object c =index.html new Complex(4,1); // Now rely on the valueOf() operator to treat it like a real number. // Note that this wouldn't work with the + operator--that would convert // the object to a string and do string concatenation. x =index.html c * 2; // x =index.html 8 x =index.html Math.sqrt(c); // x =index.html 2
The assign() method is a new feature of Navigator 3.0, and supports a kind of C++-style "operator overloading" for the = operator. The assign() method of an object is invoked when that object appears on the left-hand side of an assignment operator. It is passed one argument, which is the value on the right-hand side of the operator. The purpose of the method is in some fashion to assign the value passed as an argument to the object referred to by the this keyword. The default version of this method simply performs an assignment, replacing the object on the left-hand side of the operator with the new value from the right-hand side. You would define a custom assign() method when you want the assignment to behave differently.
One use of the assign() method is to implement an assignment with side effects. Client-side JavaScript does this with the Location object stored in the Window.location property. When a string containing a URL is assigned to this Location object, two things happen. First, the URL is parsed, and its various components are assigned to the properties of the Location object. And second, and more importantly, the web browser reads the contents of the new URL and displays them. This all occurs as the side effect of an assignment, and is implemented with a custom assign() method.
Another use of the assign() method is to make objects read-only. If you define an assign method that does nothing, then no one will be able to change the value of the variable that holds your object. For example:
// give an object an empty assign() method function no_op() { /* do nothing */ } o = new Object(); o.assign = no_op; // Now, no one can overwrite o. It will always contain the object we created. o = 3; // has no effect o = new Date(); // has no effect // Note, though that we can assign properties to o: o.x = 3; // this works fine
This technique can be extended to print issue a warning if any attempt is made to overwrite the object. You might do it with an assign() method defined like this:
function warn_on_assign(value) { alert('Attempt to set a read-only variable to:\n' + value); } ReadOnlyClass.prototype.assign = warn_on_assign;
Finally, the assign() method can be used to change the very way that assignment is done. Objects are usually assigned "by reference". That is, when one object is assigned to another, the contents of the object are not copied; instead, a reference to the new object merely overwrites a reference to the old. (The concept of assignment "by reference" is explained in detail in Chapter 9, Further Topics in JavaScript.) If you want the contents of an object to be copied when you assign one to another, you can do so with an assign() method like the following:
function assign_properties(value) { // if the value is an object, copy it property by property // otherwise, do nothing and leave the variable unchanged. if (typeof value == "object") for (prop in value) this[prop] = value[prop]; } MyClass.prototype.assign = assign_properties;
The assign() method is one of the most obscure and least elegant features of JavaScript. The JavaScript developers at Microsoft did not support it in Internet Explorer 3.0, and don't plan to support it in future versions of the language either. Even the JavaScript designers at Netscape aren't happy with assign(); they are thinking about providing similar functionality through a cleaner, more general mechanism in a future version of JavaScript. For these reasons, the assign() method may be one of the least portable features of JavaScript, and you should think twice before writing code that relies upon it.