Операторы сравнения в JavaScript сравнивают объекты по ссылке, а не по значе_ нию. Так, если имеются две ссылки на объекты, то выясняется, ссылаются они на один и тот же объект или нет, но не выясняется, обладают ли разные объекты оди_ наковыми свойствами с одинаковыми значениями.1 Часто бывает удобным иметь возможность выяснить эквивалентность объектов или даже определить порядок их следования (например, с помощью операторов отношения < и >). Если вы опре_ деляете класс и хотите иметь возможность сравнивать экземпляры этого класса, вам придется определить соответствующие методы, выполняющие сравнение.
В языке программирования Java сравнение объектов производится с помощью методов, и подобный подход можно с успехом использовать в JavaScript. Чтобы иметь возможность сравнивать экземпляры класса, можно определить метод эк_ земпляра с именем equals(). Этот метод должен принимать единственный аргу_ мент и возвращать true, если аргумент эквивалентен объекту, метод которого был вызван. Разумеется, вам решать, что следует понимать под словом «эквива_ лентен» в контексте вашего класса. Обычно для определения того, равны ли объ_ екты, сравниваются значения свойств экземпляров двух объектов. Класс Complex из примера 9.2 как раз обладает таким методом equals().
Иногда возникает необходимость реализовать операции сравнения, чтобы выяс_ нить порядок следования объектов. Так, для некоторых классов вполне можно сказать, что один экземпляр «меньше» или «больше» другого. Например, поря_ док следования объектов класса Complex определяется на основе значения, воз_ вращаемого методом magnitude(). В то же время для объектов класса Circle слож_ но определить смысл слов «меньше» и «больше» – следует ли сравнивать вели_ чину радиуса или нужно сравнивать координаты X и Y? А может быть, следует учитывать величины всех трех параметров?
При попытке сравнения JavaScript_объектов с помощью операторов отношения, таких как < и <=, интерпретатор сначала вызовет методы valueOf() объектов, и ес_ ли методы вернут значения элементарных типов, сравнит эти значения. По_ скольку класс Complex имеет метод valueOf(), который возвращает вещественную часть комплексного числа, экземпляры класса Complex можно сравнивать как обычные вещественные числа, не имеющие мнимой части.2 Это может совпадать или не совпадать с вашими намерениями. Чтобы сравнивать объекты для выяс_ нения порядка их следования по вашему выбору, вам необходимо (опять же, сле_ дуя соглашениям, принятым в языке программирования Java) реализовать ме_ тод с именем compareTo().
Метод compareTo() должен принимать единственный аргумент и сравнивать его с объектом, метод которого был вызван. Если объект this меньше, чем объект,
1 То есть являются эквивалентными копиями_экземплярами одного класса. – При' меч. науч. ред.
2 При этом получается результат, очень странный для всякого, кто работает в об_ ластях применения комплексной математики. Это хороший пример того, как ско_ ропалительное определение метода valueOf() (да и любого метода, особенно из чис_ ла базовых) в дальнейшем может преподносить пользователю большие сюрпри_ зы, не согласующиеся с его логикой восприятия. – Примеч. науч. ред.
9.4. Общие методы класса Object
представленный аргументом, метод compareTo() должен возвращать значение меньше нуля. Если объект 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
Вот одна из возможных реализаций метода compareTo() для класса Complex из при_ мера 9.2, в которой сравниваются комплексные числа по их модулям:
Complex.prototype.compareTo = function(that) {
// Если аргумент не был передан или он не имеет метода
// magnitude(), необходимо сгенерировать исключение.
// Как вариант – можно было бы вернуть значение _1 или 1,
// чтобы как_то обозначить, что комплексное число всегда меньше
// или больше, чем любое другое значение.
if (!that || !that.magnitude || typeof that.magnitude != "function") throw new Error("неверный аргумент в Complex.compareTo()");
// Здесь используется свойство операции вычитания, которая
// возвращает значение меньшее, большее или равное нулю.
// Этот прием можно использовать во многих реализациях метода compareTo(). return this.magnitude() _ that.magnitude();
}
Одна из причин, по которым может потребоваться сравнивать экземпляры клас_ са, – возможность сортировки массива этих экземпляров в некотором порядке. Метод Array.sort() может принимать в виде необязательного аргумента функ_ цию сравнения, которая должна следовать тем же соглашениям о возвращаемом значении, что и метод compareTo(). При наличии метода compareTo() достаточно просто организовать сортировку массива комплексных чисел примерно следую_ щим образом:
Сортировка имеет большое значение, и потому следует рассмотреть возможность реализации статического метода compare() в любом классе, где определен метод экземпляра compareTo(). Особенно если учесть, что первый может быть легко реа_ лизован в терминах второго, например:
При наличии этого метода сортировка массива может быть реализована еще проще:
complexNumbers.sort(Complex.compare);
Обратите внимание: реализации методов compare() и compareTo() не были включе_ ны в определение класса Complex из примера 9.2. Дело в том, что они не согласу_ ются с методом equals(), который был определен в этом примере. Метод equals() утверждает, что два объекта класса Complex эквивалентны, если их веществен_ ные и мнимые части равны. Однако метод compareTo() возвращает нулевое значе_ ние для любых двух комплексных чисел, которые имеют равные модули. Числа 1+0i и 0+1i имеют одинаковые модули и эти два числа будут объявлены равны_ ми при вызове метода compareTo(), но метод equals() утверждает, что они не рав_ ны. Таким образом, если вы собираетесь реализовать методы equals() и compa_ reTo() в одном и том же классе, будет совсем нелишним их как_то согласовать. Несогласованность в понимании термина «равенство» может стать источником пагубных ошибок. Рассмотрим реализацию метода compareTo(), который согла_ суется с существующим методом equals():1
// При сравнении комплексных чисел в первую очередь сравниваются
// их вещественные части. Если они равны, сравниваются мнимые части Complex.prototype.compareTo = function(that) {
var result = this.x _ that.x; // Сравнить вещественные части
// с помощью операции вычитания
if (result == 0) // Если они равны...
result = this.y _ that.y; // тогда сравнить мнимые части
// Теперь результат будет равен нулю только в том случае,