Как мы обсуждали ранее, все объекты в JavaScript наследуются от класса Object. Специализированные классы, такие как встроенный класс String или определенный пользователем класс Complex, определяют свои собственные свойства и методы, но кроме этого все объекты, независимо от своего класса, также поддерживают свойства и методы, определенные классом Object. По причине их универсальности эти свойства и методы представляют особый интерес.
Свойство constructor
Начиная с JavaScript 1.1 любой объект имеет свойство constructor, которое ссылается на функцию-конструктор, использованную для инициализации объекта. Например, если объект o создается с помощью конструктора Complex(), то свойство o.constructor ссылается на функцию Complex:
var o = new Complex(1,2);
o.constructor == Complex; // Равно true
Каждый объект Complex (или объект любого другого типа), конечно, не имеет собственного уникального свойства constructor; вместо этого данное свойство наследуется от объекта-прототипа. Как говорилось ранее в этой главе, для каждой определяемой программистом функции JavaScript создает объект-прототип и присваивает его свойству prototype конструктора. Однако ранее мы не раскрывали тот факт, что объект-прототип изначально не пуст. При создании в нем имеется свойство constructor, ссылающееся на функцию-конструктор, то есть для любой функции f выражение f.prototype.constructor всегда равно f (если мы не установили его равным чему-то другому).
Функция-конструктор определяет класс объекта, поэтому свойство constructor может применяться для определения типа любого заданного объекта. Например, тип неизвестного объекта можно определить таким способом:
if ((typeof o == "object") && (o.constructor == Date))
// Какие-то действия с объектом Date...
Однако существование свойства constructor не всегда гарантировано. Например, автор класса может заменить объект-прототип конструктора совершенно новым объектом, который не имеет свойства constructor.
Метод toString()
Метод toString() не требует аргументов; он возвращает строку, каким-либо образом представляющую тип и/или значение объекта, для которого он вызывается. JavaScript вызывает этот метод объекта во всех тех случаях, когда ему требуется преобразовать объект в строку. Например, это происходит, когда используется оператор + для конкатенации строк с объектом или при передаче объекта такому методу, как alert() или document.write().
Метод toString() по умолчанию не очень информативен. Например, следующие строки кода заставляют броузер вывести строку «[object Object]»:
c = new Circle(1, 0, 0);
document.write(c);
Этот метод по умолчанию не отображает особенно полезной информации, поэтому многие классы определяют свои собственные версии метода toString(). Например, когда массив преобразуется в строку, мы получаем список элементов массива, каждый из которых преобразуется в строку, а когда в строку преобразуется функция, мы получаем исходный код этой функции.
Идея метода toString() состоит в том, что каждый класс объектов имеет свое собственное особое строковое представление, поэтому он должен определять соответствующий метод toString() для преобразования объектов в строковую форму. Поэтому, определяя класс, необходимо определить для него специальный метод toString(), чтобы экземпляры класса могли быть преобразованы в осмысленные строки. Строка должна содержать информацию о
преобразуемом объекте, так как это полезно для отладочных целей. Если способ преобразования в строку выбран правильно, он также может быть полезным в самих программах.
В следующем коде показан метод toString(), который мы можем определить для класса Circle из примера 8.5:
Circle.prototype.toString = function () {
return "[Круг радиуса " + this.r + ", с центром в ("
+ this.x + ", " + this.y + ").]";
}
После определения такого метода toString() типичный объект Circle может быть преобразован в строку «Круг с радиусом, равным 1, с центром в точке (0,0)». Вернувшись к примеру 8.6, мы увидим, что для нашего класса комплексных чисел Complex определяется метод toString().
Интересная особенность метода toString(), определенного классом Object по умолчанию, состоит в том, что он открывает некоторую внутреннюю информацию о типе встроенных объектов. Этот метод toString() всегда возвращает строку в следующей форме:
[object класс]
Здесь класс – это внутренний тип объекта, и он обычно соответствует имени функции-конструктора этого объекта. Например, для объектов Array класс будет равен «Array», для объектов Function класс будет равен «Function», а для объектов Date класс будет равен «Date». Для встроенного объекта Math класс будет равен «Math», а всех объектов Error (в том числе экземпляров различных подклассов Error) – «Error». Для объектов клиентского JavaScript и любых других объектов, определенных реализацией JavaScript, класс будет величиной, определяемой реализацией (например, «Window», «Document» или «Form»). Для объектов, определяемых пользователем, таких как определенные выше в этой главе классы Circle и Complex, класс всегда равен «Object».
Обратите внимание, что значение класс предоставляет полезную информацию, которую нельзя получить с помощью оператора typeof (возвращающего для всех объектов либо «Object», либо «Function»). Значение класс предоставляет ту же информацию, что и описанное выше свойство constructor, но это значение предоставляется в форме строки, а не в форме функцииконструктора. Однако получить это значение можно только при помощи метода toString(), определенного по умолчанию в классе Object. Некоторые классы часто определяют собственные версии этого метода, поэтому мы не можем просто вызвать метод toString() объекта:
o.toString() // Может вызвать специализированный метод toString() данного объекта
Вместо этого мы должны с помощью объекта Object.prototype.toString явно сослаться на функцию toString(), определенную по умолчанию, и обратиться к методу apply() этой функции, чтобы вызвать ее для требуемого объекта:
Object.prototype.toString.apply(o); // Всегда вызывает toString(),
// определенную по умолчанию
Этот прием позволяет определить функцию, предоставляющую расширенные возможности определения типа:
// Расширенная функция для определения типа. Возвращает строку, описывающую
// тип x. Обратите внимание, что для всех типов объектов, определенных
// пользователем, она возвращает "Object"function Typeof(x) {
// Начнем с оператора typeof
var t = typeof x;
// Если результат является информативным, возвращаем его
if (t != "object") return t;
// В противном случае x – это объект. Получаем его класс, чтобы попытаться
// узнать, что это за объект.
var c = Object.prototype.toString.apply(x); // Возвращает "[object class]"
c = c.substring(8, c.length-1); // Отбрасываем "[object" и "]"
return c;
}
Метод toLocaleString()
В ECMAScript v3 и JavaScript 1.5 класс Object в дополнение к методу toString() определяет метод toLocaleString(). Назначение этого метода состоит в получении локализованного строкового представления объекта. Метод toLocaleString() по умолчанию, определяемый классом Object, сам не выполняет никакой локализации; он всегда возвращает в точности такую же строку, что и toString(). Однако подклассы могут определять собственные версии
метода toLocaleString(). В ECMAScript v3 классы Array, Date и Number определяют методы toLocaleString(), которые возвращают локализованные значения.
Метод valueOf()
Метод valueOf() во многом похож на метод toString(), но вызывается, когда JavaScript требуется преобразовать объект в какое-либо элементарное значение, отличное от строки – обычно в число. Когда это возможно, функция должна возвращать элементарное значение, каким-либо образом представляющее значение объекта, на который ссылается ключевое слово this.
По определению объекты не являются элементарными значениями, поэтому большинство объектов не имеют эквивалентного элементарного типа. Поэтому метод valueOf(), определяемый по умолчанию классом Object, не выполняет преобразования и просто возвращает объект, с которым он был вызван. Такие классы, как Number и Boolean имеют очевидные элементарные эквиваленты, поэтому они переопределяют метод valueOf() так, чтобы он возвращал соответствующие значения. Именно поэтому объекты Number и Boolean могут вести себя во многом так же, как эквивалентные им элементарные значения.
Иногда можно определить класс, имеющий какой-то разумный элементарный эквивалент. В этом случае может потребоваться определить для этого класса специальный метод valueOf(). Если мы вернемся к примеру 8.6, то увидим, что метод valueOf() определен для класса Complex. Этот метод просто возвращает вещественную часть комплексного числа. Поэтому в числовом контексте объект Complex ведет себя так, как будто он является вещест-венным числом без мнимой составляющей. Рассмотрим, например, следующий код:
var a = new Complex(5,4);
var b = new Complex(2,1);
var c = Complex.subtract(a,b); // c это комплексное число {3,3}
var d = a - b; // d это число 3
При наличии метода valueOf() следует соблюдать одну предосторожность: в случае преобразования объекта в строку метод valueOf() иногда имеет приоритет перед методом toString(). Поэтому, когда для класса определен метод valueOf() и надо, чтобы объект этого класса был преобразован в строку, может потребоваться явно указать на это, вызвав метод toString(). Продолжим пример с классом Complex:
alert("c = " + c); // Используется valueOf(); показывает "c = 3"
alert("c = " + c.toString()); // Показывает "c = {3,3}"
Метод hasOwnProperty()
Метод hasOwnProperty() возвращает true, если для объекта определено не унаследованное свойство с именем, указанным в единственном строковом аргументе метода. В противном случае он возвращает false. Например:
var o = new Object();
o.hasOwnProperty("undef"); // false: свойство не определено
o.hasOwnProperty("toString"); // false: toString – это унаследованное свойство
Math.hasOwnProperty("cos"); // true: объект Math имеет свойство cos
Метод propertyIsEnumerable()
Метод propertyIsEnumerable() возвращает true, если в объекте определено свойство с именем, указанным в единственном строковом аргументе метода, и это свойство будет перечислено циклом for/in. В противном случае метод возвращает false. Например:
var o = { x:1 };
o.propertyIsEnumerable("x"); // true: свойство существует
// и является перечисляемым
o.propertyIsEnumerable("y"); // false: свойство не существует
o.propertyIsEnumerable("valueOf"); // false: свойство не является перечисляемым
Обратите внимание, спецификация ECMAScript утверждает, что property-IsEnumerable() рассматривает только свойства, определенные непосредственно объектом, а не унаследованные свойства. Это неприятное ограничение делает данную функцию менее полезной, так как возвращаемое значение false может означать, что либо свойство не перечисляемое, либо оно перечисляемое, но наследуется от надкласса.
Метод isPrototypeOf()
Метод isPrototypeOf() возвращает true, если объект представляет собой объект-прототип аргумента. В противном случае метод возвращает false. Этот метод используется аналогично свойству constructor. Например:
var o = new Object();
Object.prototype.isPrototypeOf(o); // true: o.constructor == Object
Object.isPrototypeOf(o); // false
o.isPrototypeOf(Object.prototype); // false
Function.prototype.isPrototypeOf(Object); // true: Object.constructor == Function