Россия, Москва, Московский государственный университет им. М. В. Ломоносова, 1989 |
Классы
Можно ли создать статическое поле в прототипе
Кое-что похожее на статическое поле создать можно без особых усилий. Во всяком случае, это будет некое хранилище данных, доступных для всех экземпляров класса. Выглядеть это будет так:
_global.myClass = function(){} _global.myClass.prototype.staticField = 5; a = new myClass(); b = new myClass(); // Значение статического свойства читаем как обычно trace("a.staticField = " + a.staticField); // А изменяем только напрямую через __proto__ a.__proto__.staticField = 10; trace("b.staticField = " + b.staticField);
Этот код выводит вот что:
a.staticField = 5 b.staticField = 10
Как и следовало ожидать. Однако нужно соблюдать осторожность: если мы попробуем изменить staticField напрямую через объект (например, a.staticField = 10 ), мы, разумеется, внесем изменения лишь в сам объект, а не в прототип. И другим объектам этого класса изменения будут недоступны. Можно исправить ситуацию, заведя не просто статическое поле, а свойство с функциями доступа. Получается вот что:
_global.myClass = function(){} // setter и getter размещаем в прототипе _global.myClass.prototype.setStField = function(val){this.__proto__.stFieldData = val;}; _global.myClass.prototype.getStField = function(){return this.__proto__.stFieldData;}; // И прямо в прототипе создаем свойство _global.myClass.prototype.addProperty( "staticField", _global.myClass.prototype.getStField, _global.myClass.prototype.setStField ); // Устанавливаем начальное значение _global.myClass.prototype.staticField = 5; a = new myClass(); b = new myClass(); // Значение статического свойства читаем как обычно trace("a.staticField = " + a.staticField); // И записывать теперь можно тоже как обычно a.staticField = 10; trace("b.staticField = " + b.staticField);
На выходе получаем, как и в прошлый раз
a.staticField = 5 b.staticField = 10
К сожалению, недостатки есть и у этого варианта. Вспомним: в С++ и в Java к статической переменной можно обращаться не только через объект, но и через класс. Мы, конечно, можем написать что-то вроде MyClass.prototype.staticField = 10, но... нельзя ли, чтоб было совсем похоже на С++ или Java (особенно на Java)? Без всякого слова prototype? Можно! Добро пожаловать за этим в следующий параграф!
Способы эмуляции статических и приватных полей и методов
Этот параграф может быть пропущен при первом чтении. Заинтересует же он прежде всего тех, кто хочет непременно эмулировать на Флэше любимые элементы семантики Java и C++. А также любителей головоломок.
Статическое поле (свойство)
Усовершенствовать статические поля из предыдущего параграфа можно по двум направлениям:
- Устроить так, чтобы все-таки можно было обращаться к статическому полю только через имя класса.
- Спрятать получше переменную в прототипе, чтобы к ней нельзя было обратиться напрямую. (Это нужно, если ваши getter и setter делают какую-то дополнительную работу, кроме простой выдачи и установки значения; и вы хотите, чтобы весь доступ к свойству шел только через них.)
Вот три варианта кода. Один решает только первую из указанных проблем, второй решает обе (хотя делает он тривиальные getter и setter ). Наконец, третий позволяет getter и setter задавать. Во всех вариантах дополнительная функция добавляется в Function.prototype и становится доступной как метод любого объекта -функции (в том числе - конструктора класса ). Отметим также, что использованные здесь идеи принадлежат Тимоти Гролео (Timothee Groleau), его прекрасные статьи, упомянутые в списке литературы мы всячески рекомендуем. Итак, сначала самый простой вариант:
Function.prototype.addStaticProperty = function(name, propVal){ // Устанавливаем начальное значение this.prototype[name] = propVal; // Сохраняем ссылку на нужный нам объект-функцию // (конструктор) в переменной, к которой будет иметь // доступ сгенерированная функция var thisVar = this; var getter = function() { return thisVar.prototype[name]; } var setter = function(newVal) { thisVar.prototype[name] = newVal; } // Как сам конструктор, так и прототип должны получить // это свойство (ссылающееся на одни и те же данные). this.addProperty(name, getter, setter); this.prototype.addProperty(name, getter, setter); } // Прячем метод от for...in, наподобие системных методов // В принципе, этого можно и не делать. ASSetPropFlags(Function.prototype, "addStaticProperty", 1); // Тестируем _global.SomeClass = function(){} SomeClass.addStaticProperty("testProp", "Тестовое значение"); a = new SomeClass(); b = new SomeClass(); trace(a.testProp); SomeClass.testProp = "Второе тестовое значение"; trace(b.testProp) b.testProp = "Третье тестовое значение"; trace(SomeClass.testProp);
На выходе получим:
Тестовое значение Второе тестовое значение Третье тестовое значение
Так что статическое свойство успешно работает. Теперь давайте скроем данные, к которым обращается свойство, - поместим их не в прототип, а в контекст вызова addStaticProperty. Вот так:
Function.prototype.addStaticProperty = function(name, propVal){ // Храним значение прямо в контексте вызова // addStaticProperty, прямо в аргументее ее. var getter = function() { return propVal; } var setter = function(newVal) { propVal = newVal; } // Как сам конструктор, так и прототип должны получить // это свойство (ссылающееся на одни и те же данные). this.addProperty(name, getter, setter); this.prototype.addProperty(name, getter, setter); }
Весь остальной текст программки оставляем таким же; тестовый запуск дает тот же самый результат. Наконец, если мы хотим сами установить getter и setter, нам придется поступить следующим образом:
// Суффикс GS в названии функции означает "Getter, Setter" Function.prototype.addStaticProperty_GS = function (name, propVal, argGetter, argSetter){ // Храним значение прямо в контексте вызова // addStaticProperty, прямо в аргументее ее. var getter = function() { return argGetter(propVal); } var setter = function(newVal) { propVal = argSetter(newVal); } // Как сам конструктор, так и прототип должны получить // это свойство (ссылающееся на одни и те же данные). this.addProperty(name, getter, setter); this.prototype.addProperty(name, getter, setter); } // Прячем метод от for...in, наподобие системных методов // В принципе, этого можно и не делать. ASSetPropFlags(Function.prototype, "addStaticProperty", 1); // Ссылка будет локальной, только если мы поместим // весь этот код в функцию. Но мы хотим подчеркнуть, // что testGetter по смыслу - временная переменная. var testGetter = function(intrinsicVar){ return "Возвращаем: " + intrinsicVar; } // Комментарий, который мы написали к testGetter, // относится и сюда тоже. var testSetter = function(newValue){ return newValue.toUpperCase(); } // Тестируем _global.SomeClass = function(){} SomeClass.addStaticProperty_GS ("testProp", "Тестовое значение", testGetter, testSetter); a = new SomeClass(); b = new SomeClass(); trace(a.testProp); SomeClass.testProp = "Второе тестовое значение"; trace(b.testProp) b.testProp = "Третье тестовое значение"; trace(SomeClass.testProp);
Обратите внимание, что getter в данном случае принимает аргумент: значение внутренней переменной, в которой хранятся данные; setter, в отличие от обычного поведения, возвращает значение, которое будет этой внутренней переменной присвоено.
Запускаем код на выполнение и получаем:
Возвращаем: Тестовое значение Возвращаем: ВТОРОЕ ТЕСТОВОЕ ЗНАЧЕНИЕ Возвращаем: ТРЕТЬЕ ТЕСТОВОЕ ЗНАЧЕНИЕ
Забавный эффект: начальное значение свойства не прошло через функцию- setter и поэтому не приведено к верхнему регистру. Если вы считаете, что подобное поведение провоцирует ошибки (а не "дает некоторую гибкость", например), вы легко можете это исправить, вставив в конце функции addStaticProperty_GS строчку propVal = argSetter(propVal).