| Беларусь, рогачёв |
Классы
Можно ли создать статическое поле в прототипе
Кое-что похожее на статическое поле создать можно без особых усилий. Во всяком случае, это будет некое хранилище данных, доступных для всех экземпляров класса. Выглядеть это будет так:
_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).