Беларусь, рогачёв |
Классы
Обсуждение результатов
Давайте посмотрим, что еще не сделано в нашей функции копирования. Во-первых, копируются не сами функции, а ссылки на них. Можно ли скопировать объект -функцию? Да, хотя и с некоторыми ограничениями. Ведь существует механизм генерации функций внутри других функций. Мы можем создать новую функцию и заставить ее вызывать старую. Как правило, делать такое незачем, если только наша функция- объект не имеет в себе поля, которые мы собираемся изменять. Пример оправданного копирования подобного рода мы можем найти в конце лекции о наследовании.
Во-вторых, не сделано копирование массивов. Копирования ссылки на массив недостаточно, если массив мы собираемся изменять. Хорошо бы определять заранее, что мы имеем дело с массивом, и копировать его при помощи метода slice (заставляя этот метод выдать фрагмент массива размером с целый массив). Копирование массивов также реализовано в примере, рассмотренном в лекции о наследовании (параграф "Наследование от необычных типов", подпараграф "Наследование от функции").
Копировать массив можно и другим способом. Скоро мы узнаем, как снимать "защиту" с системных полей и методов во встроенных объектах. После снятия такой защиты вполне можно будет скопировать массив, как любой другой объект. Правда, подобные действия надо выполнять с осторожностью, кроме того, они требуют использования недокументированных функций. Так что способ копирования массивов с помощью slice более предпочтителен.
Также отметим, что мы не в состоянии подобным образом копировать клипы. Даже упомянутое только что снятие защиты не поможет нам скопировать многие поля, а тем более - содержимое клипа (то, что в нем нарисовано).
Теперь обсудим, хорошо ли использовать подобную функцию копирования при создании новых объектов класса.
Во-первых, скопированный таким образом объект занимает много места. Мы ведь не смотрим, какие поля и методы будут изменяться, а какие - нет, размножаем все подряд. Особенно это касается рекурсивного копирования. Но и без рекурсии, как мы увидим далее, мы используем много лишней памяти, если наш объект занимает нижнее место в длинной иерархии классов. Наконец, если мы создаем много объектов за короткое время, то нам будет мешать тот факт, что для копирования всех полей времени уходит довольно много.
Есть изящное решение всех этих проблем. Нужно хранить в самом объекте только те поля и методы, которые изменились по сравнению с прототипом (или вовсе в прототипе отсутствуют). А за всеми прочими методами и полями обращаться прямо в прототип. Но для того, чтобы реализовать это, объект должен иметь возможность каким-то образом связаться с прототипом и "вытащить" из него все, что нужно. А есть ли в объекте ссылка на прототип? Если мы пройдемся по полям объекта в цикле for...in, мы не обнаружим ничего необычного. Тем не менее, в документации написано, что такая ссылка есть и называется она __proto__ (обратите внимание на два подчеркивания с каждой стороны). Почему же for...in ничего не сообщает нам о ней? Дело в том, что эта ссылка, как и ряд других, имеет специальный атрибут, скрывающий ее от итерирования в цикле for...in. Однако нам сейчас важно все подобные ссылки разыскать, и, оказывается, существуют средства для работы и изменения подобных атрибутов ссылок. Средства эти, правда, недокументированные, но воспользоваться ими в исследовательских целях сейчас самое время.
Применение функции ASSetPropFlags для копирования скрытых полей и методов
Функция 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
Из нашего примера видно, что при конфликте третьего и четвертого аргумента (то есть при попытке установить и сбросить один и тот же флаг) приоритетом обладает третий.