8.3 Methods
A
method is nothing more than a JavaScript
function that is invoked through an object. Recall that functions are
data values and that there is nothing special about the names with
which they are defined -- a function can be assigned to any
variable, or even to any property of an object. If we have a function
f and an object o, we can
define a method named m with the following line:
o.m = f;Having defined the method m( ) of the object
o, we invoke it like this:
o.m( );
Or, if m( ) expects two arguments, we might invoke
it like this:
o.m(x, x+2);
Methods have
one very important property: the object through which a method is
invoked becomes the value of the this keyword
within the body of the method. For example, when we invoke
o.m( ), the body of the method can refer to the
object o with the this keyword.
The discussion of the this keyword should begin to
clarify why we use methods at all. Any function that is used as a
method is effectively passed an extra argument -- the object
through which it is invoked. Typically, a method performs some sort
of operation on that object, so the method invocation syntax is a
particularly elegant way to express the fact that a function is
operating on an object. Compare the following two lines of code:
rect.setSize(width, height);
setRectSize(rect, width, height);
These two lines may perform exactly the same operation on the object
rect, but the method invocation syntax in the
first line more clearly indicates the idea that it is the object
rect that is the primary focus, or
target, of the operation. (If the first line
does not seem a more natural syntax to you, you are probably new to
object-oriented programming. With a little experience, you will learn
to love it!)
While it is useful to think of
functions and methods differently, there
is not actually as much difference between them as there initially
appears to be. Recall that functions are values stored in variables
and that variables are nothing more than properties of a global
object. Thus, when you invoke a function, you are actually invoking a
method of the global object. Within such a function, the
this keyword refers to the global object. Thus,
there is no technical difference between functions and methods. The
real difference lies in design and intent: methods are written to
operate somehow on the this object, while
functions usually stand alone and do not use the
this object.
The typical usage of methods is more
clearly illustrated through an example. Example 8-2
returns to the Rectangle objects of Example 8-1 and
shows how a method that operates on Rectangle objects can be defined
and invoked.
Example 8-2. Defining and invoking a method
// This function uses the this keyword, so it doesn't make sense to
// invoke it by itself; it needs instead to be made a method of some
// object that has "width" and "height" properties defined.
function compute_area( )
{
return this.width * this.height;
}
// Create a new Rectangle object, using the constructor defined earlier.
var page = new Rectangle(8.5, 11);
// Define a method by assigning the function to a property of the object.
page.area = compute_area;
// Invoke the new method like this:
var a = page.area( ); // a = 8.5*11 = 93.5
One shortcoming is evident in Example 8-2: before
you can invoke the area(
)
method for the rect object, you must assign that
method to a property of the object. While we can invoke the
area( ) method on the particular object named
page, we can't invoke it on any other
Rectangle objects without first assigning the method to them. This
quickly becomes tedious. Example 8-3 defines some
additional Rectangle methods and shows how they can automatically be
assigned to all Rectangle objects with a
constructor function.
Example 8-3. Defining methods in a constructor
// First, define some functions that will be used as methods.
function Rectangle_area( ) { return this.width * this.height; }
function Rectangle_perimeter( ) { return 2*this.width + 2*this.height; }
function Rectangle_set_size(w,h) { this.width = w; this.height = h; }
function Rectangle_enlarge( ) { this.width *= 2; this.height *= 2; }
function Rectangle_shrink( ) { this.width /= 2; this.height /= 2; }
// Then define a constructor method for our Rectangle objects.
// The constructor initializes properties and also assigns methods.
function Rectangle(w, h)
{
// Initialize object properties.
this.width = w;
this.height = h;
// Define methods for the object.
this.area = Rectangle_area;
this.perimeter = Rectangle_perimeter;
this.set_size = Rectangle_set_size;
this.enlarge = Rectangle_enlarge;
this.shrink = Rectangle_shrink;
}
// Now, when we create a rectangle, we can immediately invoke methods on it:
var r = new Rectangle(2,2);
var a = r.area( );
r.enlarge( );
var p = r.perimeter( );
The technique shown in Example 8-3 also has a
shortcoming. In this example, the Rectangle( )
constructor sets seven properties of each and every Rectangle object
it initializes, even though five of those properties have constant
values that are the same for every rectangle. Each property takes up
memory space; by adding methods to our Rectangle class, we've
more than tripled the memory requirements of each Rectangle object.
Fortunately, JavaScript has a solution to this problem: it allows an
object to inherit properties from a prototype object. The next
section describes this technique in detail.