Чтобы наиболее наглядно проиллюстрировать, что такое
Итак, начнем мы с того, что создадим пока не
lift = {};
lift.currentFloor = 1; // Текущий этаж
lift.floorToGo = 1; // Этаж назначения
lift.minFloor = 1; // Минимальный этаж
lift.maxFloor = 9; // Максимальный этаж
lift.doorsAreOpen = true; // Двери открыты
lift.goto = function(where){
trace("-----------");
// Округляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where),
this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();//Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
lift.setDoorsOpen = function(open){
if (this.doorsAreOpen != open){
trace(open ? "Открываем двери." : "Закрываем двери.")
}
this.doorsAreOpen = open;
}
lift.go = function(){
this.setDoorsOpen(false);
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
trace("Этаж " + i);
this.currentFloor = i;
}
this.setDoorsOpen(true);
}
// Тестируем
lift.goto(5);
lift.goto(3);
lift.goto(10);
lift.goto(9);
На выходе получаем:
----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Открываем двери. ----------- ----------- Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Открываем двери. ----------- ----------- Закрываем двери. Этаж 3 Этаж 4 Этаж 5 Этаж 6 Этаж 7 Этаж 8 Этаж 9 Открываем двери. ----------- ----------- Ничего не делаем. -----------
Лифт работает! Теперь посмотрим, сможем ли мы построить
Наличие lift2 = lift к добру не приведет - мы получим лишь еще одну ссылку на тот же самый
lift2 = lift;
// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);
получим:
lift: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. ----------- lift2: ----------- Закрываем двери. Этаж 3 Этаж 4 Этаж 5 Открываем двери. -----------
Действительно, мы имеем дело только с одним лифтом - на пятый этаж lift2 поехал не с первого этажа (как было бы, если бы мы в самом деле создали новый лифт) а с третьего.
Таким образом, нам нужно как-то скопировать
// Функция для копирования
_global.newObject = function(objectPrototype){
var tempObject = {}; // Создаем новый объект
for(fieldName in objectPrototype) // Копируем (?) поля
tempObject[fieldName] = objectPrototype[fieldName];
return tempObject;
}
// Копируем лифт
lift2 = newObject(lift);
// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);
Добавив этот код к коду из предыдущего подпараграфа (и убрав старые "пробные запуски" лифта), получаем на выходе:
lift: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. ----------- lift2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Открываем двери. -----------
Действительно, лифты ездят независимо друг от друга.
Теперь, когда мы создали новый lift, мы сможем несколько изменить его. Например, заставить ездить на 16 этаж и в подвал. А также издавать звуковой сигнал при закрывании дверей. Вот та часть кода, которая эта делает:
// Копируем лифт
lift2 = newObject(lift);
// Meняем параметры
lift2.minFloor = 0;
lift2.maxFloor = 16;
// "Переопределение виртуальной функции"
lift2.setDoorsOpenImpl = lift2.setDoorsOpen;
lift2.setDoorsOpen = function(open){
if (!open this.doorsAreOpen) trace("Динь!");
this.setDoorsOpenImpl(open);
}
// Проверяем
trace("lift:");
lift.goto(0); // Пробуем съездить в подвал на старом лифте
trace("lift2:");
lift2.goto(0); // И на новом
На выходе получаем:
lift: ----------- Ничего не делаем. ----------- lift2: ----------- Динь! Закрываем двери. Этаж 1 Этаж 0 Открываем двери. -----------
Полное впечатление, что lift2 наследован от lift и в нем переопределена функция управления дверьми. С этой точки зрения каждый
Теперь, когда мы научились делать "почти
Итак, мы можем:
Заметим, что в примере про лифт нам пришлось вручную устанавливать параметры нового лифта. То есть
Давайте обеспечим наш lift this будет обращением к , подробнее о котором далее). Приведенных только что сведений достаточно, чтобы написать следующий код:
_global.lift = function (minFloor, maxFloor){
this.minFloor = minFloor;
this.maxFloor = maxFloor;
}
Как мы уже говорили, . Но откуда оператор возьмет
Еще раз посмотрим, какая задача стоит перед нами. У нас имеется имя , зная это имя, легко отыщет
Конечно же, есть! prototype и положить туда ссылку на ), оператор в качестве prototype функции- lift, нам надо добавить вот что:
// Конструктор
_global.lift = function (minFloor, maxFloor){
this.minFloor = minFloor;
this.maxFloor = maxFloor;
}
// Ссылка на прототип
_global.lift.prototype = lift;
// Создаем 2 объекта
l1 = new _global.lift(2, 5);
l2 = new _global.lift(0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
После запуска этого кода получим в консоли:
l1: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Можно сделать следующие комментарии по поводу нашей работы. Во-первых, в _global. Иначе нам мешал бы уже имеющийся в _root (в котором был размещен наш код) lift. Логично было бы не создавать конфликта имен, а сразу помещать все prototype, тем более, что пустой lift = {}; необязательна.
В этом подпараграфе мы переработаем наш код с тем, чтобы все нужное для нашего
Итак, открываем новый Флэш-файл и пишем там вот что:
// Конструктор
_global.lift = function (minFloor, maxFloor){
// Проверяем, не перепутал ли пользователь максимальный
// и минимальный этажи
if (minFloor < maxFloor){
this.minFloor = Math.round(minFloor);
this.maxFloor = Math.round(maxFloor);
}
else{ // Если перепутал, корректируем
this.minFloor = Math.round(maxFloor);
this.maxFloor = Math.round(minFloor);
}
// Устанавливаем лифт на 1 этаж (но если лифт туда не должен
// ездить, корректируем начальный этаж в нужную сторону).
this.currentFloor = Math.min(Math.max(1, this.minFloor), this.maxFloor);
this.doorsAreOpen = true;
}
// Функции - те же, что и раньше, но размещаем их в прототипе
_global.lift.prototype.goto = function(where){
trace("-----------");
// Oкругляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where),
this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();//Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
_global.lift.prototype.setDoorsOpen = function(open){
if (this.doorsAreOpen != open){
trace(open ? "Открываем двери." : "Закрываем двери.")
}
this.doorsAreOpen = open;
}
_global.lift.prototype.go = function(){
this.setDoorsOpen(false);
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
trace("Этаж " + i);
this.currentFloor = i;
}
this.setDoorsOpen(true);
}
// Создаем 2 объекта
l1 = new lift(2, 5);
l2 = new lift(0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
На выходе получаем:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Теперь, как видно, мы можем писать не new _global.lift, а просто new lift, поскольку lift мы не создавали, а сразу разместили все нужные prototype функции-
Мы можем добавить в doorsAreOpen и присвоить ему там значение true. Это соответствовало бы тому факту, что лифты нашей конструкции после монтажа стоят с открытыми дверьми.
Фактически, наиболее важные вещи для создания собственных this! Иначе Флэш решит, что вы обращаетесь к переменной того _root ). Поэтому, если вы написали очевидный код и он не работает, проверьте, чтобы все слова this были на месте. Обычно при такой проверке обнаруживается, что вы забыли поставить вовсе не один, а штук пять this 'ов.
Читая предыдущий параграф вы могли заметить, что никакой особенной поддержки языка для реализации всего, что там написано, не требуется. То есть, оператор , конечно, делает полезные вещи, но с тем же успехом эти вещи можно сделать и вручную. Итак, попробуем эмулировать все, что мы знаем об операторе , хотя наши знания пока и неполны (а кое в чем могут оказаться ошибочными - ведь мы неявно предполагаем, что оператор полностью копирует
Для того, чтобы обеспечить функциональность, похожую на функциональность настоящего оператора , нам понадобится вспомогательная функция , должна помимо lift напишем следующее:
_global.newFunc = function (constr, args){
var tmp = {}; // Или var tmp = new Object();
// Копируем прототип:
copyOneLevelObj(tmp, constr.prototype);
// Выделяем подмассив с аргументами, которые надо передать в
// конструктор (все, кроме нулевого - это сам конструктор)
// и вызываем его:
constr.apply(tmp, arguments.slice(1));
return tmp;
}
_global.copyOneLevelObj = function (toObj, fromObj){
for (var fieldName in fromObj){
toObj[fieldName] = fromObj[fieldName];
}
}
// Создаем 2 объекта
l1 = newFunc(lift, 2, 5);
l2 = newFunc(lift, 0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
Тестировочные операторы оставляем такими же, как раньше, и убеждаемся, что на выходе получается то же самое:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
А это значит, что не только создались наши
(В данном примере был использован вызов apply. Если вы подзабыли детали этого способа - просмотрите еще раз в лекции о функциях конец параграфа про arguments. Там же описывается работа функций apply и call. А более подробные примеры их применения вы найдете в лекции о наследовании - в параграфе про вызов функции с явным указанием базового
То, что с нашей функцией
Надо заметить, что, совершенствуя функцию newFunc в этом направлении, мы удаляемся от оригинала. Здесь уже пора приоткрыть краешек тайны и сказать о том, что настоящий оператор вовсе не копирует
Вот код рекурсивной функции copyObj. Мы проверим его работу в отдельном файле.
_global.copyObj = function(toObj, fromObj){
for (var fieldName in fromObj){
var type = typeof(fromObj[fieldName]);
// Ссылки на объекты требуют копирования этих объектов
if (type == "object"){
// Параметризованные функции и клипы игнорируем
toObj[fieldName] = {};
copyObj(toObj[fieldName], fromObj[fieldName]);
}else{
toObj[fieldName] = fromObj[fieldName];
}
}
}
obj1 = {dataObj: {dataStr: "Some data"}, x: 10};
obj2 = {y: 5};
copyObj(obj2, obj1);
obj1.dataObj.dataStr = "Another data";
Этот пример ничего не выводит в консоль, но, нажав Ctrl+Alt+V ( List Variables ), мы получим:
Global Variables:
Variable _global.copyObj = [function 'copyObj']
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.obj1 = [object #2, class 'Object'] {
x:10,
dataObj:[object #3, class 'Object'] {
dataStr:"Another data"
}
}
Variable _level0.obj2 = [object #4, class 'Object'] {
y:5,
dataObj:[object #5, class 'Object'] {
dataStr:"Some data"
},
x:10
}
Все obj1 скопировались в obj2, включая obj1, значение obj2 не изменилось. Отсюда мы можем сделать вывод, что рекурсивная функция newFunc можно заменить вызов copyOneLevelObj на вызов copyObj (приводить результаты проверки нового варианта newFunc в примере с лифтами мы не будем, поскольку лифты не содержат пока ссылок на другие
Давайте посмотрим, что еще не сделано в нашей функции
Во-вторых, не сделано slice (заставляя этот
Копировать массив можно и другим способом. Скоро мы узнаем, как снимать "защиту" с системных slice более предпочтителен.
Также отметим, что мы не в состоянии подобным образом копировать клипы. Даже упомянутое только что снятие защиты не поможет нам скопировать многие
Теперь обсудим, хорошо ли использовать подобную функцию
Во-первых, скопированный таким образом
Есть изящное решение всех этих проблем. Нужно хранить в самом for...in, мы не обнаружим ничего необычного. Тем не
менее, в документации написано, что такая ссылка есть и называется она __proto__ (обратите внимание на два подчеркивания с каждой стороны). Почему же for...in ничего не сообщает нам о ней? Дело в том, что эта ссылка, как и ряд других, имеет специальный атрибут, скрывающий ее от итерирования в цикле for...in. Однако нам сейчас важно все подобные ссылки разыскать, и, оказывается, существуют средства для работы и изменения подобных атрибутов ссылок. Средства эти, правда, недокументированные, но воспользоваться ими в исследовательских целях сейчас самое время.
Функция ASSetPropFlags, о которой сейчас пойдет речь, - это недокументированная функция для работы с атрибутами
Глобальная функция ASSetPropFlags имеет четыре параметра. Первый из них - это ссылка на null, и тогда указанные нами действия будут производиться со всеми hidden (скрытие от for...in ), атрибут protect delete (защита от удаления) или атрибут protect overwrite (защита от перезаписи). Первому из атрибутов соответствует самый младший бит (соответствующий 20, то есть 1), второму - следующий (21 = 2), третьему - бит, соответствующий 22 = 4. Прочие биты этого параметра не используются, или, точнее, если и используются, то для внутренних секретных целей Флэш. Нам они не понадобятся. Что же касается четвертого параметра функции, то он полностью аналогичен третьему, но определяет те атрибуты, которые будут стерты у соответствующего ASSetPropFlags для того, чтобы стирать у различных hidden. А пока что посмотрим на тестовых примерах, как работает установка различных атрибутов. Для того чтобы проверять, установлен или нет какой-либо атрибут, нам придется пойти на некоторые ухищрения: удачное удаление говорит, что не установлена защита от удаления, удачная замена значения сообщает, что нет защиты от перезаписи. Для проверки, установлен ли атрибут hidden мы могли бы попробовать отыскать данное for...in. Но, оказывается, можно обойтись и без этого: существует недокументированная функция isPropertyEnumerable, которая записана в Object.prototype и, таким образом, может быть вызвана в качестве
// Возвращает массив с данными о том, какие атрибуты
// установлены у переменной
_global.getAttributesReport = function(obj, prop){
var overProt = false, delProt = false;
// hidden проверяем напрямую
var hidden = !obj.isPropertyEnumerable(prop);
var tmp = obj[prop];
if (tmp !== undefined){
// проверяем защиту от перезаписи
obj[prop] = !tmp;
if (obj[prop] == tmp) overProt = true;
// проверяем защиту от удаления
delete obj[prop];
if (obj[prop] !== undefined) delProt = true;
obj[prop] = tmp;
}
return [hidden, delProt, overProt];
}
// Выводит информацию об атрибутах в развернутом виде
_global.traceAttributesReport = function(obj, prop){
var report = getAttributesReport(obj, prop);
trace("======== property: " + prop + " ===========");
trace("hidden = " + report[0]);
trace("delete protection = " + report[1]);
trace("overwrite protection = " + report[2]);
}
obj1 = {a: 10, b: 20, c: "str"};
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем полю b атрибуты hidden и delete protected
ASSetPropFlags(obj1, "b", 3);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем полю c атрибут overwrite protected
ASSetPropFlags(obj1, "c", 4);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем всем полям сразу атрибуты hidden и
// overwrite protected и пытаемся одновременно снять hidden
ASSetPropFlags(obj1, null, 5, 1);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Снимаем у всех полей атрибут hidden
ASSetPropFlags(obj1, null, 0, 1);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// А теперь выводим атрибуты у двух
// недокументированных функций.
traceAttributesReport(_global, "ASSetPropFlags");
traceAttributesReport(Object.prototype, "isPropertyEnumerable");
После запуска этого кода мы получаем в консоли вот какие надписи:
false,false,false false,false,false false,false,false ----------------- false,false,false true,true,false false,false,false ----------------- false,false,false true,true,false false,false,true ----------------- true,false,true true,true,true true,false,true ----------------- false,false,false false,true,true false,false,false ----------------- ======== property: ASSetPropFlags =========== hidden = true delete protection = false overwrite protection = false ======== property: isPropertyEnumerable =========== hidden = true delete protection = true overwrite protection = false
Из нашего примера видно, что при конфликте третьего и четвертого аргумента (то есть при попытке установить и сбросить один и тот же флаг) приоритетом обладает третий.
Теперь, конечно, трудно удержаться от того, чтобы снять флаги hidden со всех for...in. Для начала проделаем это с _global, а также посмотрим, какие Object.prototype. Вот код, который выводит в консоль все интересующие нас сведения.
trace("----------------------");
trace("|||||||||| Object.prototype |||||||||||");
trace("----------------------");
ASSetPropFlags(Object.prototype, null, 0, 1);
for (var name in Object.prototype) trace(name);
trace("----------------------");
trace("|||||||||| _global |||||||||||");
trace("----------------------");
ASSetPropFlags(_global, null, 0, 1);
for (var name in _global) trace(name);
В результате получаем вот что:
---------------------- |||||||||| Object.prototype ||||||||||| ---------------------- toLocaleString isPropertyEnumerable isPrototypeOf hasOwnProperty toString valueOf addProperty unwatch watch constructor ---------------------- |||||||||| _global ||||||||||| ---------------------- CustomActions MMSave Cookie System Accessibility Video Stage TextFormat TextField Button Key Mouse Selection XML XMLNode Sound Math Array String Date Boolean Number o clearInterval setInterval isFinite isNaN updateAfterEvent trace parseFloat parseInt unescape escape ASSetNative ASSetPropFlags LocalConnection SharedObject Microphone Camera NetStream NetConnection Color AsBroadcaster XMLSocket LoadVars MovieClip Infinity NaN Function Object ASconstructor ASnative
Мы видим, что те
Казалось бы, мы можем теперь снять защиту с любого trace. Также мы не можем вмешаться в работу некоторых других механизмов, например, в работу механизма ссылок на _parent у (правда, можно завести переменную с именем _parent и она скроет, хотя и не переопределит, системную ссылку _parent ; удалив эту переменную, мы увидим информацию в системной ссылке _parent в целости и сохранности).
Тем не менее, большинство , с которыми мы познакомимся в лекции 10). Причем эти . Аналогичным образом можно заменять toString во всех Object ). Думаем, не стоит говорить, что пользоваться этими возможностями следует с особой осторожностью. Особенно если фрагментами системы (библиотекой, программными компонентами), написанными вами, будут п
ользоваться другие программисты. Ведь они, как правило, будут рассчитывать на то поведение
Теперь мы можем, наконец, приступить к исследованиям скрытых механизмов реализации наследования и порождения Object, но имеются у вновь создаваемых
a = new Object(); ASSetPropFlags(a, null, 0, 1); for (name in a) trace(name); На выходе получаем: __constructor__ constructor __proto__
Поскольку в этот раз мы не раскрывали Object.prototype, то их мы сейчас не видим (более подробное объяснение этого факта мы сможем дать лишь после того, как окончательно разберемся с механизмом наследования). Так что три
Итак, мы увидели ряд
Как мы уже говорили, про это for...in __proto__ любого prototype __proto__ решает эту пр
облему - оно есть в каждом
Впрочем, на самом-то деле, имея constructor в каждом
Итак, мы видим, что, имея myObject, мы можем обратиться к его myObject.__proto__, или myObject.constructor.prototype.
Интересно, что это
myObject = new Object();
myObject1 = {};
trace(myObject.__constructor__);
trace(myObject1.__constructor__);
На выходе мы получим:
[type Function] undefined
Только в обнаружилось __constructor__. То есть мы нашли способ выяснить, создан ли Object с помощью фигурных скобок или с помощью ! Но предназначение __constructor__, разумеется, отнюдь не в этом. Предназначено оно для использования при наследовании и вызова super. Собственно, ссылка на базовый
Мы разобрались, что означают и куда указывают ссылки, создаваемые в . Но для чего они используются?
Прежде чем ответить на этот вопрос, давайте еще раз вспомним, в чем основные недостатки функции , которую мы уже реализовали, и почему настоящая функция вряд ли может работать таким образом. Итак, мы уже говорили, что вариант с рекурсивным
Поможет решить нашу проблему механизм, известный под названием copy on write, то есть
a = {x: 10, y:20};
b = {x: 15, z:25};
c = {x: 21, t:31};
// Эмуляция настоящего __proto__
c.proto = b;
b.proto = a;
_global.getFieldValByName = function(name){
if (this[name] != undefined) return this[name];
if (this.proto != undefined)
return getFieldValByName.call(this.proto, name);
return undefined;
}
trace("b.x = " + getFieldValByName.call(b, "x"));
trace("c.y = " + getFieldValByName.call(c, "y"));
trace("----- На самом деле -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);
c.__proto__ = b;
b.__proto__ = a;
trace("----- После установки __proto__ -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);
Запустив этот код, обнаружим в консоли следующее:
b.x = 15 c.y = 20 ----- На самом деле ----- b.x = 15 c.y = ----- После установки __proto__ ----- b.x = 15 c.y = 20
То есть наша эмуляция механизма работы __proto__ вполне соответствует реальному положению вещей. (Заметим только, что для аналогичного вызова функций нам надо было бы передать по цепочке ссылку на первоначальный apply ). Более того, мы видим, что безо всякого использования , одной только установкой __proto__ мы устроили нечто вроде наследования! Впрочем, с подробным анализом открывающихся здесь возможностей мы подождем до следующей лекции.
Итак, как мы теперь понимаем, оператор производит пять операций.
__proto__, чтобы она указывала на constructor - она будет указывать на __constructor__ - она будет указывать на А теперь, когда мы знаем детали работы , мы можем исправить его эмуляцию. Давайте перепишем работу newFunc из последнего примера с лифтами и посмотрим, что получится.
_global.newFunc = function (constr, args){
var tmp = {}; // Или var tmp = new Object();
// Устанавливаем ссылки принадлежности к классу:
tmp.__proto__ = constr.prototype;
tmp.constructor = constr;
// Детали работы этой строчки мы разберем
// в лекции о наследовании.
tmp.__constructor__ = constr.prototype.constructor;
// Выделяем субмассив с аргументами, которые надо
// передать в конструктор (все, кроме нулевого -
// это сам конструктор) и вызываем его:
constr.apply(tmp, arguments.slice(1));
return tmp;
}
// Мы не переписываем сюда весь код лифта,
// но напоминаем содержание тестового примера
// Создаем 2 объекта
l1 = newFunc(lift, 2, 5);
l2 = newFunc(lift, 0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
Запустив пример с этой исправленной функцией, получим:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Таким образом, видим, что лифты ездят как надо, хотя мы не скопировали в них ни единого
Кое-что похожее на
_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 задавать. Во всех вариантах дополнительная функция добавляется в Function.prototype и становится доступной как
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, в отличие от обычного поведения, возвращает значение, которое будет этой внутренней переменной присвоено.
Запускаем код на выполнение и получаем:
Возвращаем: Тестовое значение Возвращаем: ВТОРОЕ ТЕСТОВОЕ ЗНАЧЕНИЕ Возвращаем: ТРЕТЬЕ ТЕСТОВОЕ ЗНАЧЕНИЕ
Забавный эффект: начальное значение addStaticProperty_GS строчку propVal = argSetter(propVal).
Аппетит приходит во время еды; теперь мы можем вспомнить о том, что в C++ и Java бывают также и this в этих addStaticMethod, которая приведена ниже.
// Для тестирования нам надо добавлять не только статические
// методы, но и свойства. Здесь используем более простой
// вариант кода, без пользовательских getter'ов setter'ов
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);
}
// Прячем метод от for...in, наподобие системных методов
// В принципе, этого можно и не делать.
ASSetPropFlags(Function.prototype, "addStaticProperty", 1);
// Простейший вариант добавления статического метода
Function.prototype.addStaticMethod = function(name, staticMethodEngene) {
var theClass = this;
var staticMethod = function() {
return staticMethodEngene.apply(theClass,
arguments);
};
this[name] = staticMethod;
this.prototype[name] = staticMethod;
}
// Прячем метод от for...in, наподобие системных методов
// В принципе, этого можно и не делать.
ASSetPropFlags(Function.prototype, "addStaticMethod", 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);
// Тестируем статический метод
b.someStr = "Некоторая строка";
f = function(){
trace("this.testProp.toLowerCase = " + this.testProp.toLowerCase());
trace("this.someStr = " + this.someStr);
}
SomeClass.addStaticMethod("printTest", f);
trace("");
trace("--- b.printTest() ----");
b.printTest();
trace("--------------");
trace("");
trace("--- SomeClass.printTest() ----");
SomeClass.printTest();
trace("------------------");
trace("");
trace("b.someStr = " + b.someStr);
Выводит этот код, как и ожидалось,
Тестовое значение Второе тестовое значение Третье тестовое значение --- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = -------------- --- SomeClass.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = ------------------ b.someStr = Некоторая строка
Вы видите, что к someStr у нашего testProp ему замечательно видно.
Для более глубокого понимания происходящих здесь вещей попробуйте угадать, что выведет вызов b.printTest() в том случае, когда мы добавим f не с помощью addStaticMethod, а с помощью addStaticProperty. Для того чтобы побольше запутать вас, приведем сначала неправильный способ рассуждений. Он состоит в том, что getter возвращает addStaticProperty. Тогда this указывает на этот контекст, в котором отсутствуют как testProp, так и someStr. Таким образом, вместо их значений мы получим пустоту. Следующая мысль, которая приходит в голову, - это то, что getter, возвращая ссылку на this. (Для сравнения загляните в лекцию 5, параграф "Функция как Function.) Вывод тот же самый - мы увидим пустое место вместо значений строк. Однако запустите наш код, заменив вызов addStaticMethod на addStaticProperty. И вы увидите, что будет выведен, в частности, следующий фрагмент:
--- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = Некоторая строка --------------
Правильное же решение, которое полностью избавляет от всех сомнений, состоит в том, что Flash MX делает поведение b.printTest(), то в функцию printTest в качестве this будет передана именно ссылка на b. А то, что ссылка на printTest хранится где-то в другом месте (не в b ) и нам ее возвращает getter - это нас волновать уже не должно.
Что делать, если мы хотим изменить поведение addStaticMethod и передать ему в качестве первого аргумента то же самое имя this внутри нее - ссылка на addStaticMethod. Итак, новая версия addStaticMethod от Гролео, позволяющая просто присваивать новые
Function.prototype.addStaticMethod = function(name, staticMethodEngene) {
var theClass = this;
var getter = function() {
return staticMethodEngene;
};
// Setter должен сгенерировать новый метод - который вызовет
// newMethod,
// передав ему theClass в качестве this.
var setter = function(newMethod) {
staticMethodEngene = function() {
return newMethod.apply(theClass, arguments);
};
};
setter(staticMethodEngene);
this.addProperty(name, getter, setter);
this.prototype.addProperty(name, getter, setter);
}
Заменим на этот вариант addStaticMethod из предыдущего примера; и добавим в конце примера следующий проверочный код:
g = function(){
trace("Функция g вступает в работу.");
trace("this.testProp.toUpperCase = " + this.testProp.toUpperCase());
trace("this.someStr = " + this.someStr);
}
trace("");
SomeClass.printTest = g;
trace("Произведена замена функции: SomeClass.printTest = g;");
trace("");
trace("--- b.printTest() ----");
b.printTest();
trace("--------------");
Запускаем все это и получаем:
Тестовое значение Второе тестовое значение Третье тестовое значение --- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = -------------- --- SomeClass.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = ------------------ b.someStr = Некоторая строка Произведена замена функции: SomeClass.printTest = g; --- b.printTest() ---- Функция g вступает в работу. this.testProp.toUpperCase = ТРЕТЬЕ ТЕСТОВОЕ ЗНАЧЕНИЕ this.someStr = --------------
Итак, setter сработал как надо: сгенерировал новую функцию, которая вызывает g, передавая ей с помощью apply ссылку на g эта ссылка используется в качестве this. Таким образом, мы действительно получили доступ к статическому testProp и не получили - к обычному b (а именно, к someStr ).
Как вы, наверное, помните, даже с помощью недокументированных приемов нам не удается полностью скрыть addProperty, getter и setter вызываются неявно при обращении к Object ), в результате чего все
Итак, сначала мы рассмотрим, как делаются первые два варианта приватных addExplicitPrivateProperty, а для второго - addImplicitPrivateProperty:
// Будем исследовать этой функцией объекты, чтобы
// убедиться, что приватные свойства там не спрятаны.
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
// Приватное свойство с явными getter'ом и setter'ом
Object.prototype.addExplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this["get" + name] = function() {
return getter.call(this, propVal);
};
this["set" + name] = function(newVal) {
propVal = setter.call(this, newVal);
};
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addExplicitPrivateProperty", 1);
// Приватное свойство с неявными getter'ом и setter'ом
Object.prototype.addImplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this.addProperty(name,
function() {
return getter.call(this, propVal);
},
function(newVal) {
propVal = setter.call(this, newVal);
}
);
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addImplicitPrivateProperty", 1);
// Ключевое слово var стоит на случай переноса кода в функцию
var getter = function(propVal){
return "Значение: " + propVal;
}
// Ключевое слово var стоит на случай переноса кода в функцию
var setter = function(newVal){
return newVal.toString().toUpperCase();
}
// Тестируем
a = new Object();
b = new Object();
a.addExplicitPrivateProperty("testText", getter, setter);
b.addImplicitPrivateProperty("testText", getter, setter);
// Используем отсутствие чувствительности
// идентификаторов к регистру (более точные имена
// функций - settestText и gettestText).
a.setTestText("Текст для объекта а");
trace(a.getTestText());
b.testText = "А такой текст будет в объекте b";
trace(b.testText);
trace("");
trace("----- Содержимое объекта а ------");
dumpObj(a);
trace("----------------------");
trace("");
trace("----- Содержимое объекта b ------");
dumpObj(b);
trace("----------------------");
На выходе этот код дает:
Значение: ТЕКСТ ДЛЯ ОБЪЕКТА А Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B ----- Содержимое объекта а ------ settestText: [type Function] gettestText: [type Function] __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ---------------------- ----- Содержимое объекта b ------ testText: Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ----------------------
Мы видим, что dumpObj (в случае b testText, которое мы видим - это созданное нами
Но как быть, если у нас есть несколько разных
_global.CodedStringContainer = function(baseForCoding, stringToStore){
// Секретные данные
var uncoded_str = stringToStore;
// Эти данные несекретны, но просто не нужны снаружи
var spaceCode = 32;
var maxCode = 127;
var diff = maxCode - spaceCode;
// Для использования в сгенерированной внутри приватной функции
var thisVar = this;
// Эта функция в принципе могла бы быть и публичной, нарушить
// целостность данных она не позволяет. Но, с другой стороны,
// снаружи она явно не нужна
var updateCodedString = function(){
// не забудем, что в приватной функции (которая не сохранена
// в this) ключевое слово this указывает не туда, куда обычно.
thisVar.coded_str = "";
// Кодируем
for (var i=0; i<uncoded_str.length; i++){
var charCode = uncoded_str.charCodeAt(i);
charCode = (charCode - spaceCode + baseForCoding)%diff
+ spaceCode);
thisVar.coded_str += String.fromCharCode(charCode);
}
}
// В принципе, такой интерфейс делать необязательно,
// можно удовольствоваться установкой в конструкторе
this.setUncodedString = function(str){
uncoded_str = str;
updateCodedString();
}
// Проверка - знаем ли мы "пароль".
this.isStringEqualToUncoded = function(str){
return uncoded_str === str;
}
// кодируем переданную строчку
updateCodedString();
}
_global.CodedStringContainer.prototype.getCodedString = function(){
return this.coded_str;
}
// Проверяем, что получилось
container = new CodedStringContainer(20, "MyString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
trace("---------------");
// Меняем строку
container.setUncodedString("YourString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
В результате выполнения этого кода увидим вот что:
a.g)'}#{
true
false
---------------
m$*'g)'}#{
false
true
Так что строка шифруется, заменяется, проверяется, но ни ее, ни число, используемое для шифрования, вы посмотреть не сможете, ибо они спрятаны в контексте вызова updateCodedString ), сама не обязана быть приватной (хотя мы ее и сделали таковой). Главное, чтобы она была сгенерирована прямо внутри
Чтобы наиболее наглядно проиллюстрировать, что такое
Итак, начнем мы с того, что создадим пока не
lift = {};
lift.currentFloor = 1; // Текущий этаж
lift.floorToGo = 1; // Этаж назначения
lift.minFloor = 1; // Минимальный этаж
lift.maxFloor = 9; // Максимальный этаж
lift.doorsAreOpen = true; // Двери открыты
lift.goto = function(where){
trace("-----------");
// Округляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where),
this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();//Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
lift.setDoorsOpen = function(open){
if (this.doorsAreOpen != open){
trace(open ? "Открываем двери." : "Закрываем двери.")
}
this.doorsAreOpen = open;
}
lift.go = function(){
this.setDoorsOpen(false);
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
trace("Этаж " + i);
this.currentFloor = i;
}
this.setDoorsOpen(true);
}
// Тестируем
lift.goto(5);
lift.goto(3);
lift.goto(10);
lift.goto(9);
На выходе получаем:
----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Открываем двери. ----------- ----------- Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Открываем двери. ----------- ----------- Закрываем двери. Этаж 3 Этаж 4 Этаж 5 Этаж 6 Этаж 7 Этаж 8 Этаж 9 Открываем двери. ----------- ----------- Ничего не делаем. -----------
Лифт работает! Теперь посмотрим, сможем ли мы построить
Наличие lift2 = lift к добру не приведет - мы получим лишь еще одну ссылку на тот же самый
lift2 = lift;
// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);
получим:
lift: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. ----------- lift2: ----------- Закрываем двери. Этаж 3 Этаж 4 Этаж 5 Открываем двери. -----------
Действительно, мы имеем дело только с одним лифтом - на пятый этаж lift2 поехал не с первого этажа (как было бы, если бы мы в самом деле создали новый лифт) а с третьего.
Таким образом, нам нужно как-то скопировать
// Функция для копирования
_global.newObject = function(objectPrototype){
var tempObject = {}; // Создаем новый объект
for(fieldName in objectPrototype) // Копируем (?) поля
tempObject[fieldName] = objectPrototype[fieldName];
return tempObject;
}
// Копируем лифт
lift2 = newObject(lift);
// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);
Добавив этот код к коду из предыдущего подпараграфа (и убрав старые "пробные запуски" лифта), получаем на выходе:
lift: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. ----------- lift2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Открываем двери. -----------
Действительно, лифты ездят независимо друг от друга.
Теперь, когда мы создали новый lift, мы сможем несколько изменить его. Например, заставить ездить на 16 этаж и в подвал. А также издавать звуковой сигнал при закрывании дверей. Вот та часть кода, которая эта делает:
// Копируем лифт
lift2 = newObject(lift);
// Meняем параметры
lift2.minFloor = 0;
lift2.maxFloor = 16;
// "Переопределение виртуальной функции"
lift2.setDoorsOpenImpl = lift2.setDoorsOpen;
lift2.setDoorsOpen = function(open){
if (!open this.doorsAreOpen) trace("Динь!");
this.setDoorsOpenImpl(open);
}
// Проверяем
trace("lift:");
lift.goto(0); // Пробуем съездить в подвал на старом лифте
trace("lift2:");
lift2.goto(0); // И на новом
На выходе получаем:
lift: ----------- Ничего не делаем. ----------- lift2: ----------- Динь! Закрываем двери. Этаж 1 Этаж 0 Открываем двери. -----------
Полное впечатление, что lift2 наследован от lift и в нем переопределена функция управления дверьми. С этой точки зрения каждый
Теперь, когда мы научились делать "почти
Итак, мы можем:
Заметим, что в примере про лифт нам пришлось вручную устанавливать параметры нового лифта. То есть
Давайте обеспечим наш lift this будет обращением к , подробнее о котором далее). Приведенных только что сведений достаточно, чтобы написать следующий код:
_global.lift = function (minFloor, maxFloor){
this.minFloor = minFloor;
this.maxFloor = maxFloor;
}
Как мы уже говорили, . Но откуда оператор возьмет
Еще раз посмотрим, какая задача стоит перед нами. У нас имеется имя , зная это имя, легко отыщет
Конечно же, есть! prototype и положить туда ссылку на ), оператор в качестве prototype функции- lift, нам надо добавить вот что:
// Конструктор
_global.lift = function (minFloor, maxFloor){
this.minFloor = minFloor;
this.maxFloor = maxFloor;
}
// Ссылка на прототип
_global.lift.prototype = lift;
// Создаем 2 объекта
l1 = new _global.lift(2, 5);
l2 = new _global.lift(0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
После запуска этого кода получим в консоли:
l1: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Можно сделать следующие комментарии по поводу нашей работы. Во-первых, в _global. Иначе нам мешал бы уже имеющийся в _root (в котором был размещен наш код) lift. Логично было бы не создавать конфликта имен, а сразу помещать все prototype, тем более, что пустой lift = {}; необязательна.
В этом подпараграфе мы переработаем наш код с тем, чтобы все нужное для нашего
Итак, открываем новый Флэш-файл и пишем там вот что:
// Конструктор
_global.lift = function (minFloor, maxFloor){
// Проверяем, не перепутал ли пользователь максимальный
// и минимальный этажи
if (minFloor < maxFloor){
this.minFloor = Math.round(minFloor);
this.maxFloor = Math.round(maxFloor);
}
else{ // Если перепутал, корректируем
this.minFloor = Math.round(maxFloor);
this.maxFloor = Math.round(minFloor);
}
// Устанавливаем лифт на 1 этаж (но если лифт туда не должен
// ездить, корректируем начальный этаж в нужную сторону).
this.currentFloor = Math.min(Math.max(1, this.minFloor), this.maxFloor);
this.doorsAreOpen = true;
}
// Функции - те же, что и раньше, но размещаем их в прототипе
_global.lift.prototype.goto = function(where){
trace("-----------");
// Oкругляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where),
this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();//Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
_global.lift.prototype.setDoorsOpen = function(open){
if (this.doorsAreOpen != open){
trace(open ? "Открываем двери." : "Закрываем двери.")
}
this.doorsAreOpen = open;
}
_global.lift.prototype.go = function(){
this.setDoorsOpen(false);
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
trace("Этаж " + i);
this.currentFloor = i;
}
this.setDoorsOpen(true);
}
// Создаем 2 объекта
l1 = new lift(2, 5);
l2 = new lift(0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
На выходе получаем:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Теперь, как видно, мы можем писать не new _global.lift, а просто new lift, поскольку lift мы не создавали, а сразу разместили все нужные prototype функции-
Мы можем добавить в doorsAreOpen и присвоить ему там значение true. Это соответствовало бы тому факту, что лифты нашей конструкции после монтажа стоят с открытыми дверьми.
Фактически, наиболее важные вещи для создания собственных this! Иначе Флэш решит, что вы обращаетесь к переменной того _root ). Поэтому, если вы написали очевидный код и он не работает, проверьте, чтобы все слова this были на месте. Обычно при такой проверке обнаруживается, что вы забыли поставить вовсе не один, а штук пять this 'ов.
Читая предыдущий параграф вы могли заметить, что никакой особенной поддержки языка для реализации всего, что там написано, не требуется. То есть, оператор , конечно, делает полезные вещи, но с тем же успехом эти вещи можно сделать и вручную. Итак, попробуем эмулировать все, что мы знаем об операторе , хотя наши знания пока и неполны (а кое в чем могут оказаться ошибочными - ведь мы неявно предполагаем, что оператор полностью копирует
Для того, чтобы обеспечить функциональность, похожую на функциональность настоящего оператора , нам понадобится вспомогательная функция , должна помимо lift напишем следующее:
_global.newFunc = function (constr, args){
var tmp = {}; // Или var tmp = new Object();
// Копируем прототип:
copyOneLevelObj(tmp, constr.prototype);
// Выделяем подмассив с аргументами, которые надо передать в
// конструктор (все, кроме нулевого - это сам конструктор)
// и вызываем его:
constr.apply(tmp, arguments.slice(1));
return tmp;
}
_global.copyOneLevelObj = function (toObj, fromObj){
for (var fieldName in fromObj){
toObj[fieldName] = fromObj[fieldName];
}
}
// Создаем 2 объекта
l1 = newFunc(lift, 2, 5);
l2 = newFunc(lift, 0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
Тестировочные операторы оставляем такими же, как раньше, и убеждаемся, что на выходе получается то же самое:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
А это значит, что не только создались наши
(В данном примере был использован вызов apply. Если вы подзабыли детали этого способа - просмотрите еще раз в лекции о функциях конец параграфа про arguments. Там же описывается работа функций apply и call. А более подробные примеры их применения вы найдете в лекции о наследовании - в параграфе про вызов функции с явным указанием базового
То, что с нашей функцией
Надо заметить, что, совершенствуя функцию newFunc в этом направлении, мы удаляемся от оригинала. Здесь уже пора приоткрыть краешек тайны и сказать о том, что настоящий оператор вовсе не копирует
Вот код рекурсивной функции copyObj. Мы проверим его работу в отдельном файле.
_global.copyObj = function(toObj, fromObj){
for (var fieldName in fromObj){
var type = typeof(fromObj[fieldName]);
// Ссылки на объекты требуют копирования этих объектов
if (type == "object"){
// Параметризованные функции и клипы игнорируем
toObj[fieldName] = {};
copyObj(toObj[fieldName], fromObj[fieldName]);
}else{
toObj[fieldName] = fromObj[fieldName];
}
}
}
obj1 = {dataObj: {dataStr: "Some data"}, x: 10};
obj2 = {y: 5};
copyObj(obj2, obj1);
obj1.dataObj.dataStr = "Another data";
Этот пример ничего не выводит в консоль, но, нажав Ctrl+Alt+V ( List Variables ), мы получим:
Global Variables:
Variable _global.copyObj = [function 'copyObj']
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.obj1 = [object #2, class 'Object'] {
x:10,
dataObj:[object #3, class 'Object'] {
dataStr:"Another data"
}
}
Variable _level0.obj2 = [object #4, class 'Object'] {
y:5,
dataObj:[object #5, class 'Object'] {
dataStr:"Some data"
},
x:10
}
Все obj1 скопировались в obj2, включая obj1, значение obj2 не изменилось. Отсюда мы можем сделать вывод, что рекурсивная функция newFunc можно заменить вызов copyOneLevelObj на вызов copyObj (приводить результаты проверки нового варианта newFunc в примере с лифтами мы не будем, поскольку лифты не содержат пока ссылок на другие
Давайте посмотрим, что еще не сделано в нашей функции
Во-вторых, не сделано slice (заставляя этот
Копировать массив можно и другим способом. Скоро мы узнаем, как снимать "защиту" с системных slice более предпочтителен.
Также отметим, что мы не в состоянии подобным образом копировать клипы. Даже упомянутое только что снятие защиты не поможет нам скопировать многие
Теперь обсудим, хорошо ли использовать подобную функцию
Во-первых, скопированный таким образом
Есть изящное решение всех этих проблем. Нужно хранить в самом for...in, мы не обнаружим ничего необычного. Тем не
менее, в документации написано, что такая ссылка есть и называется она __proto__ (обратите внимание на два подчеркивания с каждой стороны). Почему же for...in ничего не сообщает нам о ней? Дело в том, что эта ссылка, как и ряд других, имеет специальный атрибут, скрывающий ее от итерирования в цикле for...in. Однако нам сейчас важно все подобные ссылки разыскать, и, оказывается, существуют средства для работы и изменения подобных атрибутов ссылок. Средства эти, правда, недокументированные, но воспользоваться ими в исследовательских целях сейчас самое время.
Функция ASSetPropFlags, о которой сейчас пойдет речь, - это недокументированная функция для работы с атрибутами
Глобальная функция ASSetPropFlags имеет четыре параметра. Первый из них - это ссылка на null, и тогда указанные нами действия будут производиться со всеми hidden (скрытие от for...in ), атрибут protect delete (защита от удаления) или атрибут protect overwrite (защита от перезаписи). Первому из атрибутов соответствует самый младший бит (соответствующий 20, то есть 1), второму - следующий (21 = 2), третьему - бит, соответствующий 22 = 4. Прочие биты этого параметра не используются, или, точнее, если и используются, то для внутренних секретных целей Флэш. Нам они не понадобятся. Что же касается четвертого параметра функции, то он полностью аналогичен третьему, но определяет те атрибуты, которые будут стерты у соответствующего ASSetPropFlags для того, чтобы стирать у различных hidden. А пока что посмотрим на тестовых примерах, как работает установка различных атрибутов. Для того чтобы проверять, установлен или нет какой-либо атрибут, нам придется пойти на некоторые ухищрения: удачное удаление говорит, что не установлена защита от удаления, удачная замена значения сообщает, что нет защиты от перезаписи. Для проверки, установлен ли атрибут hidden мы могли бы попробовать отыскать данное for...in. Но, оказывается, можно обойтись и без этого: существует недокументированная функция isPropertyEnumerable, которая записана в Object.prototype и, таким образом, может быть вызвана в качестве
// Возвращает массив с данными о том, какие атрибуты
// установлены у переменной
_global.getAttributesReport = function(obj, prop){
var overProt = false, delProt = false;
// hidden проверяем напрямую
var hidden = !obj.isPropertyEnumerable(prop);
var tmp = obj[prop];
if (tmp !== undefined){
// проверяем защиту от перезаписи
obj[prop] = !tmp;
if (obj[prop] == tmp) overProt = true;
// проверяем защиту от удаления
delete obj[prop];
if (obj[prop] !== undefined) delProt = true;
obj[prop] = tmp;
}
return [hidden, delProt, overProt];
}
// Выводит информацию об атрибутах в развернутом виде
_global.traceAttributesReport = function(obj, prop){
var report = getAttributesReport(obj, prop);
trace("======== property: " + prop + " ===========");
trace("hidden = " + report[0]);
trace("delete protection = " + report[1]);
trace("overwrite protection = " + report[2]);
}
obj1 = {a: 10, b: 20, c: "str"};
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем полю b атрибуты hidden и delete protected
ASSetPropFlags(obj1, "b", 3);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем полю c атрибут overwrite protected
ASSetPropFlags(obj1, "c", 4);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Устанавливаем всем полям сразу атрибуты hidden и
// overwrite protected и пытаемся одновременно снять hidden
ASSetPropFlags(obj1, null, 5, 1);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// Снимаем у всех полей атрибут hidden
ASSetPropFlags(obj1, null, 0, 1);
trace(getAttributesReport(obj1, "a"));
trace(getAttributesReport(obj1, "b"));
trace(getAttributesReport(obj1, "c"));
trace("-----------------");
// А теперь выводим атрибуты у двух
// недокументированных функций.
traceAttributesReport(_global, "ASSetPropFlags");
traceAttributesReport(Object.prototype, "isPropertyEnumerable");
После запуска этого кода мы получаем в консоли вот какие надписи:
false,false,false false,false,false false,false,false ----------------- false,false,false true,true,false false,false,false ----------------- false,false,false true,true,false false,false,true ----------------- true,false,true true,true,true true,false,true ----------------- false,false,false false,true,true false,false,false ----------------- ======== property: ASSetPropFlags =========== hidden = true delete protection = false overwrite protection = false ======== property: isPropertyEnumerable =========== hidden = true delete protection = true overwrite protection = false
Из нашего примера видно, что при конфликте третьего и четвертого аргумента (то есть при попытке установить и сбросить один и тот же флаг) приоритетом обладает третий.
Теперь, конечно, трудно удержаться от того, чтобы снять флаги hidden со всех for...in. Для начала проделаем это с _global, а также посмотрим, какие Object.prototype. Вот код, который выводит в консоль все интересующие нас сведения.
trace("----------------------");
trace("|||||||||| Object.prototype |||||||||||");
trace("----------------------");
ASSetPropFlags(Object.prototype, null, 0, 1);
for (var name in Object.prototype) trace(name);
trace("----------------------");
trace("|||||||||| _global |||||||||||");
trace("----------------------");
ASSetPropFlags(_global, null, 0, 1);
for (var name in _global) trace(name);
В результате получаем вот что:
---------------------- |||||||||| Object.prototype ||||||||||| ---------------------- toLocaleString isPropertyEnumerable isPrototypeOf hasOwnProperty toString valueOf addProperty unwatch watch constructor ---------------------- |||||||||| _global ||||||||||| ---------------------- CustomActions MMSave Cookie System Accessibility Video Stage TextFormat TextField Button Key Mouse Selection XML XMLNode Sound Math Array String Date Boolean Number o clearInterval setInterval isFinite isNaN updateAfterEvent trace parseFloat parseInt unescape escape ASSetNative ASSetPropFlags LocalConnection SharedObject Microphone Camera NetStream NetConnection Color AsBroadcaster XMLSocket LoadVars MovieClip Infinity NaN Function Object ASconstructor ASnative
Мы видим, что те
Казалось бы, мы можем теперь снять защиту с любого trace. Также мы не можем вмешаться в работу некоторых других механизмов, например, в работу механизма ссылок на _parent у (правда, можно завести переменную с именем _parent и она скроет, хотя и не переопределит, системную ссылку _parent ; удалив эту переменную, мы увидим информацию в системной ссылке _parent в целости и сохранности).
Тем не менее, большинство , с которыми мы познакомимся в лекции 10). Причем эти . Аналогичным образом можно заменять toString во всех Object ). Думаем, не стоит говорить, что пользоваться этими возможностями следует с особой осторожностью. Особенно если фрагментами системы (библиотекой, программными компонентами), написанными вами, будут п
ользоваться другие программисты. Ведь они, как правило, будут рассчитывать на то поведение
Теперь мы можем, наконец, приступить к исследованиям скрытых механизмов реализации наследования и порождения Object, но имеются у вновь создаваемых
a = new Object(); ASSetPropFlags(a, null, 0, 1); for (name in a) trace(name); На выходе получаем: __constructor__ constructor __proto__
Поскольку в этот раз мы не раскрывали Object.prototype, то их мы сейчас не видим (более подробное объяснение этого факта мы сможем дать лишь после того, как окончательно разберемся с механизмом наследования). Так что три
Итак, мы увидели ряд
Как мы уже говорили, про это for...in __proto__ любого prototype __proto__ решает эту пр
облему - оно есть в каждом
Впрочем, на самом-то деле, имея constructor в каждом
Итак, мы видим, что, имея myObject, мы можем обратиться к его myObject.__proto__, или myObject.constructor.prototype.
Интересно, что это
myObject = new Object();
myObject1 = {};
trace(myObject.__constructor__);
trace(myObject1.__constructor__);
На выходе мы получим:
[type Function] undefined
Только в обнаружилось __constructor__. То есть мы нашли способ выяснить, создан ли Object с помощью фигурных скобок или с помощью ! Но предназначение __constructor__, разумеется, отнюдь не в этом. Предназначено оно для использования при наследовании и вызова super. Собственно, ссылка на базовый
Мы разобрались, что означают и куда указывают ссылки, создаваемые в . Но для чего они используются?
Прежде чем ответить на этот вопрос, давайте еще раз вспомним, в чем основные недостатки функции , которую мы уже реализовали, и почему настоящая функция вряд ли может работать таким образом. Итак, мы уже говорили, что вариант с рекурсивным
Поможет решить нашу проблему механизм, известный под названием copy on write, то есть
a = {x: 10, y:20};
b = {x: 15, z:25};
c = {x: 21, t:31};
// Эмуляция настоящего __proto__
c.proto = b;
b.proto = a;
_global.getFieldValByName = function(name){
if (this[name] != undefined) return this[name];
if (this.proto != undefined)
return getFieldValByName.call(this.proto, name);
return undefined;
}
trace("b.x = " + getFieldValByName.call(b, "x"));
trace("c.y = " + getFieldValByName.call(c, "y"));
trace("----- На самом деле -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);
c.__proto__ = b;
b.__proto__ = a;
trace("----- После установки __proto__ -----");
trace("b.x = " + b.x);
trace("c.y = " + c.y);
Запустив этот код, обнаружим в консоли следующее:
b.x = 15 c.y = 20 ----- На самом деле ----- b.x = 15 c.y = ----- После установки __proto__ ----- b.x = 15 c.y = 20
То есть наша эмуляция механизма работы __proto__ вполне соответствует реальному положению вещей. (Заметим только, что для аналогичного вызова функций нам надо было бы передать по цепочке ссылку на первоначальный apply ). Более того, мы видим, что безо всякого использования , одной только установкой __proto__ мы устроили нечто вроде наследования! Впрочем, с подробным анализом открывающихся здесь возможностей мы подождем до следующей лекции.
Итак, как мы теперь понимаем, оператор производит пять операций.
__proto__, чтобы она указывала на constructor - она будет указывать на __constructor__ - она будет указывать на А теперь, когда мы знаем детали работы , мы можем исправить его эмуляцию. Давайте перепишем работу newFunc из последнего примера с лифтами и посмотрим, что получится.
_global.newFunc = function (constr, args){
var tmp = {}; // Или var tmp = new Object();
// Устанавливаем ссылки принадлежности к классу:
tmp.__proto__ = constr.prototype;
tmp.constructor = constr;
// Детали работы этой строчки мы разберем
// в лекции о наследовании.
tmp.__constructor__ = constr.prototype.constructor;
// Выделяем субмассив с аргументами, которые надо
// передать в конструктор (все, кроме нулевого -
// это сам конструктор) и вызываем его:
constr.apply(tmp, arguments.slice(1));
return tmp;
}
// Мы не переписываем сюда весь код лифта,
// но напоминаем содержание тестового примера
// Создаем 2 объекта
l1 = newFunc(lift, 2, 5);
l2 = newFunc(lift, 0, 3);
// Тестируем
trace("l1:");
l1.goto(4);
trace("l2:")
l2.goto(4);
Запустив пример с этой исправленной функцией, получим:
l1: ----------- Закрываем двери. Этаж 2 Этаж 3 Этаж 4 Открываем двери. ----------- l2: ----------- Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Открываем двери. -----------
Таким образом, видим, что лифты ездят как надо, хотя мы не скопировали в них ни единого
Кое-что похожее на
_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 задавать. Во всех вариантах дополнительная функция добавляется в Function.prototype и становится доступной как
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, в отличие от обычного поведения, возвращает значение, которое будет этой внутренней переменной присвоено.
Запускаем код на выполнение и получаем:
Возвращаем: Тестовое значение Возвращаем: ВТОРОЕ ТЕСТОВОЕ ЗНАЧЕНИЕ Возвращаем: ТРЕТЬЕ ТЕСТОВОЕ ЗНАЧЕНИЕ
Забавный эффект: начальное значение addStaticProperty_GS строчку propVal = argSetter(propVal).
Аппетит приходит во время еды; теперь мы можем вспомнить о том, что в C++ и Java бывают также и this в этих addStaticMethod, которая приведена ниже.
// Для тестирования нам надо добавлять не только статические
// методы, но и свойства. Здесь используем более простой
// вариант кода, без пользовательских getter'ов setter'ов
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);
}
// Прячем метод от for...in, наподобие системных методов
// В принципе, этого можно и не делать.
ASSetPropFlags(Function.prototype, "addStaticProperty", 1);
// Простейший вариант добавления статического метода
Function.prototype.addStaticMethod = function(name, staticMethodEngene) {
var theClass = this;
var staticMethod = function() {
return staticMethodEngene.apply(theClass,
arguments);
};
this[name] = staticMethod;
this.prototype[name] = staticMethod;
}
// Прячем метод от for...in, наподобие системных методов
// В принципе, этого можно и не делать.
ASSetPropFlags(Function.prototype, "addStaticMethod", 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);
// Тестируем статический метод
b.someStr = "Некоторая строка";
f = function(){
trace("this.testProp.toLowerCase = " + this.testProp.toLowerCase());
trace("this.someStr = " + this.someStr);
}
SomeClass.addStaticMethod("printTest", f);
trace("");
trace("--- b.printTest() ----");
b.printTest();
trace("--------------");
trace("");
trace("--- SomeClass.printTest() ----");
SomeClass.printTest();
trace("------------------");
trace("");
trace("b.someStr = " + b.someStr);
Выводит этот код, как и ожидалось,
Тестовое значение Второе тестовое значение Третье тестовое значение --- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = -------------- --- SomeClass.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = ------------------ b.someStr = Некоторая строка
Вы видите, что к someStr у нашего testProp ему замечательно видно.
Для более глубокого понимания происходящих здесь вещей попробуйте угадать, что выведет вызов b.printTest() в том случае, когда мы добавим f не с помощью addStaticMethod, а с помощью addStaticProperty. Для того чтобы побольше запутать вас, приведем сначала неправильный способ рассуждений. Он состоит в том, что getter возвращает addStaticProperty. Тогда this указывает на этот контекст, в котором отсутствуют как testProp, так и someStr. Таким образом, вместо их значений мы получим пустоту. Следующая мысль, которая приходит в голову, - это то, что getter, возвращая ссылку на this. (Для сравнения загляните в лекцию 5, параграф "Функция как Function.) Вывод тот же самый - мы увидим пустое место вместо значений строк. Однако запустите наш код, заменив вызов addStaticMethod на addStaticProperty. И вы увидите, что будет выведен, в частности, следующий фрагмент:
--- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = Некоторая строка --------------
Правильное же решение, которое полностью избавляет от всех сомнений, состоит в том, что Flash MX делает поведение b.printTest(), то в функцию printTest в качестве this будет передана именно ссылка на b. А то, что ссылка на printTest хранится где-то в другом месте (не в b ) и нам ее возвращает getter - это нас волновать уже не должно.
Что делать, если мы хотим изменить поведение addStaticMethod и передать ему в качестве первого аргумента то же самое имя this внутри нее - ссылка на addStaticMethod. Итак, новая версия addStaticMethod от Гролео, позволяющая просто присваивать новые
Function.prototype.addStaticMethod = function(name, staticMethodEngene) {
var theClass = this;
var getter = function() {
return staticMethodEngene;
};
// Setter должен сгенерировать новый метод - который вызовет
// newMethod,
// передав ему theClass в качестве this.
var setter = function(newMethod) {
staticMethodEngene = function() {
return newMethod.apply(theClass, arguments);
};
};
setter(staticMethodEngene);
this.addProperty(name, getter, setter);
this.prototype.addProperty(name, getter, setter);
}
Заменим на этот вариант addStaticMethod из предыдущего примера; и добавим в конце примера следующий проверочный код:
g = function(){
trace("Функция g вступает в работу.");
trace("this.testProp.toUpperCase = " + this.testProp.toUpperCase());
trace("this.someStr = " + this.someStr);
}
trace("");
SomeClass.printTest = g;
trace("Произведена замена функции: SomeClass.printTest = g;");
trace("");
trace("--- b.printTest() ----");
b.printTest();
trace("--------------");
Запускаем все это и получаем:
Тестовое значение Второе тестовое значение Третье тестовое значение --- b.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = -------------- --- SomeClass.printTest() ---- this.testProp.toLowerCase = третье тестовое значение this.someStr = ------------------ b.someStr = Некоторая строка Произведена замена функции: SomeClass.printTest = g; --- b.printTest() ---- Функция g вступает в работу. this.testProp.toUpperCase = ТРЕТЬЕ ТЕСТОВОЕ ЗНАЧЕНИЕ this.someStr = --------------
Итак, setter сработал как надо: сгенерировал новую функцию, которая вызывает g, передавая ей с помощью apply ссылку на g эта ссылка используется в качестве this. Таким образом, мы действительно получили доступ к статическому testProp и не получили - к обычному b (а именно, к someStr ).
Как вы, наверное, помните, даже с помощью недокументированных приемов нам не удается полностью скрыть addProperty, getter и setter вызываются неявно при обращении к Object ), в результате чего все
Итак, сначала мы рассмотрим, как делаются первые два варианта приватных addExplicitPrivateProperty, а для второго - addImplicitPrivateProperty:
// Будем исследовать этой функцией объекты, чтобы
// убедиться, что приватные свойства там не спрятаны.
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
// Приватное свойство с явными getter'ом и setter'ом
Object.prototype.addExplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this["get" + name] = function() {
return getter.call(this, propVal);
};
this["set" + name] = function(newVal) {
propVal = setter.call(this, newVal);
};
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addExplicitPrivateProperty", 1);
// Приватное свойство с неявными getter'ом и setter'ом
Object.prototype.addImplicitPrivateProperty = function(name, getter, setter) {
var propVal;
this.addProperty(name,
function() {
return getter.call(this, propVal);
},
function(newVal) {
propVal = setter.call(this, newVal);
}
);
}
// По традиции скрываем то, что мы добавили в системные объекты
ASSetPropFlags(Object.prototype, "addImplicitPrivateProperty", 1);
// Ключевое слово var стоит на случай переноса кода в функцию
var getter = function(propVal){
return "Значение: " + propVal;
}
// Ключевое слово var стоит на случай переноса кода в функцию
var setter = function(newVal){
return newVal.toString().toUpperCase();
}
// Тестируем
a = new Object();
b = new Object();
a.addExplicitPrivateProperty("testText", getter, setter);
b.addImplicitPrivateProperty("testText", getter, setter);
// Используем отсутствие чувствительности
// идентификаторов к регистру (более точные имена
// функций - settestText и gettestText).
a.setTestText("Текст для объекта а");
trace(a.getTestText());
b.testText = "А такой текст будет в объекте b";
trace(b.testText);
trace("");
trace("----- Содержимое объекта а ------");
dumpObj(a);
trace("----------------------");
trace("");
trace("----- Содержимое объекта b ------");
dumpObj(b);
trace("----------------------");
На выходе этот код дает:
Значение: ТЕКСТ ДЛЯ ОБЪЕКТА А Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B ----- Содержимое объекта а ------ settestText: [type Function] gettestText: [type Function] __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ---------------------- ----- Содержимое объекта b ------ testText: Значение: А ТАКОЙ ТЕКСТ БУДЕТ В ОБЪЕКТЕ B __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ----------------------
Мы видим, что dumpObj (в случае b testText, которое мы видим - это созданное нами
Но как быть, если у нас есть несколько разных
_global.CodedStringContainer = function(baseForCoding, stringToStore){
// Секретные данные
var uncoded_str = stringToStore;
// Эти данные несекретны, но просто не нужны снаружи
var spaceCode = 32;
var maxCode = 127;
var diff = maxCode - spaceCode;
// Для использования в сгенерированной внутри приватной функции
var thisVar = this;
// Эта функция в принципе могла бы быть и публичной, нарушить
// целостность данных она не позволяет. Но, с другой стороны,
// снаружи она явно не нужна
var updateCodedString = function(){
// не забудем, что в приватной функции (которая не сохранена
// в this) ключевое слово this указывает не туда, куда обычно.
thisVar.coded_str = "";
// Кодируем
for (var i=0; i<uncoded_str.length; i++){
var charCode = uncoded_str.charCodeAt(i);
charCode = (charCode - spaceCode + baseForCoding)%diff
+ spaceCode);
thisVar.coded_str += String.fromCharCode(charCode);
}
}
// В принципе, такой интерфейс делать необязательно,
// можно удовольствоваться установкой в конструкторе
this.setUncodedString = function(str){
uncoded_str = str;
updateCodedString();
}
// Проверка - знаем ли мы "пароль".
this.isStringEqualToUncoded = function(str){
return uncoded_str === str;
}
// кодируем переданную строчку
updateCodedString();
}
_global.CodedStringContainer.prototype.getCodedString = function(){
return this.coded_str;
}
// Проверяем, что получилось
container = new CodedStringContainer(20, "MyString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
trace("---------------");
// Меняем строку
container.setUncodedString("YourString");
trace(container.getCodedString());
trace(container.isStringEqualToUncoded("MyString"));
trace(container.isStringEqualToUncoded("YourString"));
В результате выполнения этого кода увидим вот что:
a.g)'}#{
true
false
---------------
m$*'g)'}#{
false
true
Так что строка шифруется, заменяется, проверяется, но ни ее, ни число, используемое для шифрования, вы посмотреть не сможете, ибо они спрятаны в контексте вызова updateCodedString ), сама не обязана быть приватной (хотя мы ее и сделали таковой). Главное, чтобы она была сгенерирована прямо внутри
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.