Javascript [Electronic resources] : The Definitive Guide (4th Edition) نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Javascript [Electronic resources] : The Definitive Guide (4th Edition) - نسخه متنی

David Flanagan

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


8.5 Object-Oriented JavaScript



Although JavaScript
supports a data type we call an object, it
does not have a formal notion of a class. This makes it quite
different from classic object-oriented languages such as C++ and
Java. The common conception about object-oriented programming
languages is that they are strongly typed and support class-based
inheritance. By these criteria, it is easy to dismiss JavaScript as
not being a true object-oriented language. On the other hand,
we've seen that JavaScript makes heavy use of objects and that
it has its own type of prototype-based inheritance. JavaScript is a
true object-oriented language. It draws inspiration from a number of
other (relatively obscure) object-oriented languages that use
prototype-based inheritance instead of class-based inheritance.

Although JavaScript is not a class-based object-oriented language, it
does a good job of simulating the features of class-based languages
such as Java and C++. I've been using the term class informally
throughout this chapter. This section more formally explores the
parallels between JavaScript and true class-based inheritance
languages such as Java and C++.[4]

[4] You should read this
section even if you are not familiar with those languages and that
style of object-oriented programming.

Let's start by defining some basic terminology. An

object , as we've already seen, is a data
structure that contains various pieces of named data and may also
contain various
methods
to operate on those pieces of data. An object groups related values
and methods into a single convenient package, which generally makes
programming easier by increasing the modularity and reusability of
code. Objects in JavaScript may have any number of
properties,
and properties may be dynamically added to an object. This is not the
case in strictly typed languages such as Java and C++. In those
languages, each object has a predefined set of properties,[5] where each property is of
a predefined type. When we are using JavaScript objects to simulate
object-oriented programming techniques, we generally define in
advance the set of properties for each object and the type of data
that each property holds.

[5] They are usually called "fields" in Java and C++,
but we'll refer to them as properties here, since that is the
JavaScript terminology.

In Java and C++, a

class defines the structure
of an object. The class specifies exactly what fields an object
contains and what types of data each holds. It also defines the
methods that operate on an object. JavaScript does not have a formal
notion of a class, but, as we've seen, it approximates classes
with its constructors and their prototype objects.


In both JavaScript and class-based
object-oriented languages, there may be multiple objects of the same
class. We often say that an object is an

instance of its class. Thus, there may be many
instances of any class. Sometimes we use the term

instantiate to describe the process of creating
an object (i.e., an instance of a class).

In Java, it is a
common programming convention to name classes with an initial capital
letter and to name objects with lowercase letters. This convention
helps keep classes and objects distinct from each other in code; it
is a useful convention to follow in JavaScript programming as well.
In previous sections, for example, we've defined the Circle and
Rectangle classes and have created instances of those classes named
c and rect.


The members of a Java class may be of four
basic types: instance properties, instance methods, class properties,
and class methods. In the following sections, we'll explore the
differences between these types and show how they are simulated in
JavaScript.


8.5.1 Instance Properties




Every
object has its own separate copies
of its

instance properties . In other words, if
there are 10 objects of a given class, there are 10 copies of each
instance property. In our Circle class, for example, every Circle
object has a property r that specifies the radius
of the circle. In this case, r is an instance
property. Since each object has its own copy of the instance
properties, these properties are accessed through individual objects.
If c is an object that is an instance of the
Circle class, for example, we refer to its radius as:

c.r 

By default, any object property in JavaScript is an instance
property. To truly simulate object-oriented programming, however, we
will say that instance properties in JavaScript are those properties
that are created and/or initialized in an object by the constructor
function.


8.5.2 Instance Methods




An

instance
method is
much
like an instance property, except that it is a method rather than a
data value. (In Java, functions and methods are not data, as they are
in JavaScript, so this distinction is more clear.) Instance methods
are invoked on a particular object, or instance. The area(
) method of our Circle class is an instance method. It is
invoked on a Circle object c like this:

a = c.area(  ); 

Instance methods use the
this

keyword to refer to the object or instance on which they are
operating. An instance method can be invoked for any instance of a
class, but this does not mean that each object contains its own
private copy of the method, as it does with instance properties.
Instead, each instance method is shared by all instances of a class.
In JavaScript, we define an instance method for a class by setting a
property in the constructor's prototype object to a function
value. This way, all objects created by that constructor share an
inherited reference to the function and can invoke it using the
method invocation syntax shown earlier.


8.5.3 Class Properties




A

class property
in Java is a property that is associated with a class itself, rather
than with each instance of a class. No matter how many instances of
the class are created, there is only one copy of each class property.
Just as instance properties are accessed through an instance of a
class, class properties are accessed through the class itself.
Number.MAX_VALUE is an example of a class property
in JavaScript: the MAX_VALUE property is accessed
through the Number class. Because there is only one copy of each
class property, class properties are essentially global. What is nice
about them, however, is that they are associated with a class and
they have a logical niche, a position in the JavaScript namespace,
where they are not likely to be overwritten by other properties with
the same name. As is probably clear, we simulate a class property in
JavaScript simply by defining a property of the constructor function
itself. For example, to create a class property
Circle.PI to store the mathematical constant

pi , we can do the following:

Circle.PI = 3.14; 

Circle is a constructor function, but because
JavaScript functions are objects, we can create properties of a
function just as we can create properties of any other object.


8.5.4 Class Methods




Finally, we come to class methods. A

class method is a method associated with a class
rather than with an instance of a class; they are invoked through the
class itself, not through a particular instance of the class. The
Date.parse( ) method (which you can look up in the
core reference section of this book) is a class method. You always
invoke it through the Date constructor object, rather than through a
particular instance of the Date class.

Because class
methods are not invoked through a particular object, they cannot
meaningfully use the this keyword -- this
refers to the object for which an instance method is invoked. Like
class properties, class methods are global. Because they do not
operate on a particular object, class methods are generally more
easily thought of as functions that happen to be invoked through a
class. Again, associating these functions with a class gives them a
convenient niche in the JavaScript namespace and prevents namespace
collisions. To define a class method in JavaScript, we simply make
the appropriate function a property of the constructor.


8.5.5 Example: The Circle Class


Example 8-5 is a reimplementation of our
Circle class that contains
examples of each of these four basic types of members.

Example 8-5. Defining instance and class properties and methods

function Circle(radius) {   // The constructor defines the class itself.
// r is an instance property, defined and initialized in the constructor.
this.r = radius;
}
// Circle.PI is a class property--it is a property of the constructor function.
Circle.PI = 3.14159;
// Here is a function that computes a circle's area.
function Circle_area( ) { return Circle.PI * this.r * this.r; }
// Here we make the function into an instance method by assigning it
// to the prototype object of the constructor.
// Note: with JavaScript 1.2, we can use a function literal to
// define the function without naming it Circle_area.
Circle.prototype.area = Circle_area;
// Here's another function. It takes two Circle objects as arguments and
// returns the one that is larger (i.e., has the larger radius).
function Circle_max(a,b) {
if (a.r > b.r) return a;
else return b;
}
// Since this function compares two Circle objects, it doesn't make sense as
// an instance method operating on a single Circle object. But we don't want
// it to be a standalone function either, so we make it into a class method
// by assigning it to the constructor function:
Circle.max = Circle_max;
// Here is some code that uses each of these fields:
var c = new Circle(1.0); // Create an instance of the Circle class
c.r = 2.2; // Set the r instance property
var a = c.area(); // Invoke the area( ) instance method
var x = Math.exp(Circle.PI); // Use the PI class property in our own computation
var d = new Circle(1.2); // Create another Circle instance
var bigger = Circle.max(c,d); // Use the max( ) class method


8.5.6 Example: Complex Numbers


Example 8-6 is



another example, somewhat more
formal than the last, of defining a class of objects in JavaScript.
The code and the comments are worth careful study. Note that this
example uses the function literal syntax of JavaScript 1.2. Because
it requires this version of the language (or later), it does not
bother with the JavaScript 1.1 compatibility technique of invoking
the constructor once before assigning to its prototype object.

Example 8-6. A complex number class

/*
* Complex.js:
* This file defines a Complex class to represent complex numbers.
* Recall that a complex number is the sum of a real number and an
* imaginary number and that the imaginary number i is the
* square root of -1.
*/
/*
* The first step in defining a class is defining the constructor
* function of the class. This constructor should initialize any
* instance properties of the object. These are the essential
* "state variables" that make each instance of the class different.
*/
function Complex(real, imaginary) {
this.x = real; // The real part of the number
this.y = imaginary; // The imaginary part of the number
}
/*
* The second step in defining a class is defining its instance
* methods (and possibly other properties) in the prototype object
* of the constructor. Any properties defined in this object will
* be inherited by all instances of the class. Note that instance
* methods operate implicitly on the this keyword. For many methods,
* no other arguments are needed.
*/
// Return the magnitude of a complex number. This is defined
// as its distance from the origin (0,0) of the complex plane.
Complex.prototype.magnitude = function( ) {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
// Return a complex number that is the negative of this one.
Complex.prototype.negative = function( ) {
return new Complex(-this.x, -this.y);
};
// Convert a Complex object to a string in a useful way.
// This is invoked when a Complex object is used as a string.
Complex.prototype.toString = function( ) {
return "{" + this.x + "," + this.y + "}";
};
// Return the real portion of a complex number. This function
// is invoked when a Complex object is treated as a primitive value.
Complex.prototype.valueOf = function( ) { return this.x; }
/*
* The third step in defining a class is to define class methods,
* constants, and any needed class properties as properties of the
* constructor function itself (instead of as properties of the
* prototype object of the constructor). Note that class methods
* do not use the this keyword: they operate only on their arguments.
*/
// Add two complex numbers and return the result.
Complex.add = function (a, b) {
return new Complex(a.x + b.x, a.y + b.y);
};
// Subtract one complex number from another.
Complex.subtract = function (a, b) {
return new Complex(a.x - b.x, a.y - b.y);
};
// Multiply two complex numbers and return the product.
Complex.multiply = function(a, b) {
return new Complex(a.x * b.x - a.y * b.y,
a.x * b.y + a.y * b.x);
};
// Here are some useful predefined complex numbers.
// They are defined as class properties, where they can be used as
// "constants." (Note, though, that they are not actually read-only.)
Complex.zero = new Complex(0,0);
Complex.one = new Complex(1,0);
Complex.i = new Complex(0,1);


8.5.7 Superclasses and Subclasses




In Java,
C++, and other class-based object-oriented languages, there is an
explicit concept of the

class hierarchy . Every
class can have a

superclass from which it inherits properties and
methods. Any class can be extended, or subclassed, so that the
resulting

subclass inherits its behavior. As
we've seen, JavaScript supports prototype inheritance instead of
class-based inheritance. Still, JavaScript analogies to the class
hierarchy can be drawn. In JavaScript, the Object class is the most generic, and all
other classes are specialized versions, or subclasses, of it. Another
way to say this is that Object is the superclass of all the built-in
classes. All classes inherit a few basic methods (described later in
this chapter) from Object.

We've learned that objects inherit
properties from the prototype
object of their constructor. How do they also inherit properties from
the Object class? Remember that the prototype object is itself an
object; it is created with the Object( )
constructor. This means the prototype object itself inherits
properties from Object.prototype! So, an object of
class Complex inherits properties from the
Complex.prototype object, which itself inherits
properties from Object.prototype. Thus, the
Complex object inherits properties of both objects. When you look up
a property in a Complex object, the object itself is searched first.
If the property is not found, the
Complex.prototype object is searched next.
Finally, if the property is not found in that object, the
Object.prototype object is searched.

Note that because the Complex prototype object is searched before the
Object prototype object, properties of
Complex.prototype hide any properties with the
same name in Object.prototype. For example, in the
class definition shown in Example 8-6, we defined a
toString( )
method in the Complex.prototype object.
Object.prototype also defines a method with this
name, but Complex objects never see it because the definition of
toString( ) in
Complex.prototype is found first.

The classes we've shown in this chapter are all direct
subclasses of Object. This is typical of JavaScript programming;
there is not usually any need to produce a more complex class
hierarchy. When necessary, however, it is possible to subclass any
other class. For example, suppose we want to produce a subclass of
Complex in order to add some more methods. To do this, we simply have
to make sure that the prototype object of the new class is itself an
instance of Complex, so that it inherits all the properties of
Complex.prototype:

// This is the constructor for the subclass.
function MoreComplex(real, imaginary) {
this.x = real;
this.y = imaginary;
}
// We force its prototype to be a Complex object. This means that
// instances of our new class inherit from MoreComplex.prototype,
// which inherits from Complex.prototype, which inherits from
// Object.prototype.
MoreComplex.prototype = new Complex(0,0);
// Now add a new method or other new features to this subclass.
MoreComplex.prototype.swap = function( ) {
var tmp = this.x;
this.x = this.y;
this.y = tmp;
}

There is one subtle shortcoming to the subclassing technique shown
here. Since we explicitly set
MoreComplex.prototype to an object of our own
creation, we overwrite the prototype object provided by JavaScript
and discard the constructor property we are given.
This constructor property, described later in this
chapter, is supposed to refer to the constructor function that
created the object. A MoreComplex object, however,
inherits the constructor property of its
superclass, rather than having one of its own. One solution is to set
this property explicitly:

MoreComplex.prototype.constructor = MoreComplex; 

Note, however, that in JavaScript 1.1, the
constructor property is read-only and cannot be
set in this way.

/ 844