#### 9.6.4 Comparison Methods

9.6.4 Comparison Methods

JavaScript equality operators compare objects by reference, not by value. That is, given two object references, they look to see if both references are to the same object. They do not check to see if two different objects have the same property names and values. It is often useful to be able to compare two distinct objects for equality or even for relative order (as the < and > operators do). If you define a class and want to be able to compare instances of that class, you should define appropriate methods to perform those comparisons.

The Java programming language uses methods for object comparison, and adopting the Java conventions is a common and useful thing to do in JavaScript. To enable instances of your class to be tested for equality, define an instance method named equals(). It should take a single argument and return true if that argument is equal to the object it is invoked on. Of course it is up to you to decide what “equal” means in the context of your own class. For simple classes you can often simply compare the constructor properties to ensure that the two objects are of the same type and then compare the instance properties of the two objects to ensure that they have the same values. The Complex class in Example 9-3 has an equals() method of this sort, and we can easily write a similar one for the Range class:

9.6 Object-Oriented Techniques in JavaScript | 221

// The Range class overwrote its constructor property. So add it now. Range.prototype.constructor = Range;

// A Range is not equal to any nonrange. // Two ranges are equal if and only if their endpoints are equal. Range.prototype.equals = function(that) {

if (that == null) return false; // Reject null and undefined if (that.constructor !== Range) return false; // Reject non-ranges // Now return true if and only if the two endpoints are equal. return this.from == that.from && this.to == that.to;

}

Defining an equals() method for our Set class is somewhat trickier. We can’t just compare the values property of two sets but must perform a deeper comparison:

Set.prototype.equals = function(that) { // Shortcut for trivial case if (this === that) return true;

// If the that object is not a set, it is not equal to this one. // We use instanceof to allow any subclass of Set. // We could relax this test if we wanted true duck-typing. // Or we could strengthen it to check this.constructor == that.constructor // Note that instanceof properly rejects null and undefined values if (!(that instanceof Set)) return false;

// If two sets don't have the same size, they're not equal if (this.size() != that.size()) return false;

// Now check whether every element in this is also in that. // Use an exception to break out of the foreach if the sets are not equal. try {

this.foreach(function(v) { if (!that.contains(v)) throw false; });

return true; | // All elements matched: sets are equal. | |

} catch (x) { | ||

if (x === false) return false; | // An element in this is not in that. | |

throw x; | // Some other exception: rethrow it. | |

} | ||

}; |

It is sometimes useful to compare objects according to some ordering. That is, for some classes, it is possible to say that one instance is “less than” or “greater than” another instance. You might order Range object based on the value of their lower bound, for example. Enumerated types could be ordered alphabetically by name, or numerically by the associated value (assuming the associated value is a number). Set objects, on the other hand, do not really have a natural ordering.

If you try to use objects with JavaScript’s relation operators, such as

The compareTo() method should accept a single argument and compare it to the object on which the method is invoked. If the this object is less than the argument, compareTo() should return a value less than zero. If the this object is greater than the argument object, the method should return a value greater than zero. And if the two objects are equal, the method should return zero. These conventions about the return value are important, and they allow you to substitute the following expressions for relational and equality operators:

Replace this With this

a < b a.compareTo(b) < 0 a <= b a.compareTo(b) <= 0 a > b a.compareTo(b) > 0 a >= b a.compareTo(b) >= 0 a == b a.compareTo(b) == 0 a != b a.compareTo(b) != 0

The Card class of Example 9-8 defines a compareTo() method of this kind, and we can write a similar method for the Range class to order ranges by their lower bound:

Range.prototype.compareTo = function(that) {

return this.from - that.from;

};

Notice that the subtraction performed by this method correctly returns a value less than zero, equal to zero, or greater than zero, according to the relative order of the two Ranges. Because the Card.Rank enumeration in Example 9-8 has a valueOf() method, we could have used this same idiomatic trick in the compareTo() method of the Card class.

The equals() methods above perform type checking on their argument and return false to indicate inequality if the argument is of the wrong type. The compareTo() method does not have any return value that indicates “those two values are not comparable,” so a compareTo() method that does type checking should typically throw an error when passed an argument of the wrong type.

Notice that the compareTo() method we defined for the Range class above returns 0 when two ranges have the same lower bound. This means that as far as compareTo() is concerned, any two ranges that start at the same spot are equal. This definition of equality is inconsistent with the definition used by the equals()method, which requires both endpoints to match. Inconsistent notions of equality can be a pernicious source of bugs, and it is best to make your equals()and compareTo()methods consistent. Here is a revised compareTo() method for the Range class. It is consistent with equals() and also throws an error if called with an incomparable value:

9.6 Object-Oriented Techniques in JavaScript | 223

// Order ranges by lower bound, or upper bound if the lower bounds are equal.

// Throws an error if passed a non-Range value.

// Returns 0 if and only if this.equals(that).

Range.prototype.compareTo = function(that) {

if (!(that instanceof Range))

throw new Error("Can't compare a Range with " + that);

var diff = this.from - that.from; // Compare lower bounds

if (diff == 0) diff = this.to - that.to; // If equal, compare upper bounds

return diff;

};

One reason to define a compareTo() method for a class is so that arrays of instances of that class can be sorted. The Array.sort() method accepts as an optional argument a comparison function that uses the same return-value conventions as the compareTo() method. Given the compareTo()method shown above, it is easy to sort an array of Range objects with code like this:

ranges.sort(function(a,b) { return a.compareTo(b); });

Sorting is important enough that you should consider defining this kind of two-argument comparison function as a class method for any class for which you define a compareTo() instance method. One can easily be defined in terms of the other. For example:

Range.byLowerBound = function(a,b) { return a.compareTo(b); };

With a method like this defined, sorting becomes simpler:

ranges.sort(Range.byLowerBound);

Some classes can be ordered in more than one way. The Card class, for example, defines one class method that orders cards by suit and another that orders them by rank.

欢迎转载,转载请注明来自一手册:http://yishouce.com/book/1/27866.html