Не только классы, определенные пользователем, имеют объекты_прототипы. Встроенные классы, такие как String и Date, также имеют объекты_прототипы, и вы можете присваивать им значения. Например, следующий фрагмент опреде_ ляет новый метод, доступный всем объектам String:
// Возвращает true, если последним символом является значение аргумента c String.prototype.endsWith = function(c) {
return (c == this.charAt(this.length_1))
}
Определив новый метод endsWith() в объекте_прототипе String, мы сможем обра_ титься к нему следующим образом:
Против такого расширения возможностей встроенных типов можно привести достаточно сильные аргументы: в случае расширения некоторого встроенного типа, по сути, создается самостоятельная версия базового прикладного Java_ Script_интерфейса. Любые другие программисты, которые будут читать или со_ провождать ваш код, могут прийти в недоумение, встретив методы, о которых они ранее не слышали. Если только вы не собираетесь создавать низкоуровне_ вую JavaScript_платформу, которая будет воспринята многими другими про_ граммистами, лучше оставить прототипы встроенных объектов в покое.
Обратите внимание: никогда не следует добавлять свойства к объекту Object.pro_ totype. Любые добавляемые свойства и методы становятся перечислимыми для цикла for/in, поэтому добавив их к объекту Object.prototype, вы сделаете их до_ ступными во всех JavaScript_объектах. Пустой объект {}, как предполагается, не имеет перечислимых свойств. Любое расширение Object.prototype превратит_ ся в перечислимое свойство пустого объекта, что, скорее всего, приведет к нару_ шениям в функционировании программного кода, который работает с объекта_ ми как с ассоциативными массивами.
Техника расширения встроенных объектов, о которой сейчас идет речь, гаранти_ рованно работает только в случае применения к «родным» объектам базового языка JavaScript. Когда JavaScript встраивается в некоторый контекст, напри_ мер в веб_броузер или в Java_приложение, он получает доступ к дополнитель_ ным «платформозависимым» объектам, таким как объекты веб_броузера, пред_
9.2. Прототипы и наследование
ставляющие содержимое документа. Эти объекты, как правило, не имеют ни конструктора, ни прототипа и потому недоступны для расширения.
Один из случаев, когда можно расширять прототипы встроенных объектов дос_ таточно безопасно и даже желательно, – это добавление стандартных методов прототипов в старых несовместимых реализациях JavaScript, где эти свойства и методы отсутствуют. Например, метод Function.apply() в Microsoft Internet Exp_ lorer версий 4 и 5 не поддерживается. Это достаточно важная функция, поэтому иногда вам может встретиться код, который добавляет эту функцию:
// Если функция Function.apply() не реализована, можно добавить
// этот фрагмент, основанный на разработках Аарона Будмана (Aaron Boodman). if (!Function.prototype.apply) {
Вызвать эту функцию как метод заданного объекта с указанными
параметрами. Для этих целей здесь используется функция eval()
Function.prototype.apply = function(object,
parameters) {
var f
=
this;
// Вызываемая функция
var
o
=
object || window;
//
Объект,
через который выполняется вызов
var
args = parameters || []; //
Передаваемые аргументы
// Временно превратить функцию в метод объекта o.
// Для этого выбирается имя метода, которое скорее всего отсутствует o._$_apply_$_ = f;
// Вызов метода выполняется с помощью eval().
// Для этого необходимо сконструировать строку вызова.
// В первую очередь собирается список аргументов.
var stringArgs = [];
for(var i = 0; i < args.length; i++) stringArgs[i] = "args[" + i + "]";
// Объединить строки с аргументами в единый список,
// разделив аргументы запятыми.
var arglist = stringArgs.join(",");
// Теперь собрать всю строку вызова метода
var methodcall = "o._$_apply_$_(" + arglist + ");";
// С помощью функции eval() вызвать метод var result = eval(methodcall);
// Удалить метод из объекта
delete o._$_apply_$_;
// И вернуть результат return result;
};
}
В качестве еще одного примера рассмотрим новые методы массивов, реализован_ ные в Firefox 1.5 (см. раздел 7.7.10). Если необходимо задействовать метод Ar_ ray.map() и при этом желательно сохранить совместимость с платформами, где этот метод не поддерживается, можно воспользоваться следующим фрагментом:
// Array.map() вызывает функцию f для каждого элемента массива
// и возвращает новый массив, содержащий результаты каждого вызова функции.
// Если map() вызывается с двумя аргументами, функция f вызывается как метод
172 Глава 9. Классы, конструкторы и прототипы
// второго аргумента. Функции f() передается 3 аргумента. Первый представляет
// значение элемента массива, второй – индекс элемента, третий – сам массив.
// В большинстве случаев достаточно передать только первый аргумент.
if (!Array.prototype.map) {
Array.prototype.map = function(f, thisObject) { var results = [];
for(var len = this.length, i = 0; i < len; i++) { results.push(f.call(thisObject, this[i], i, this));