задача: понять, зачем нужна функция ASSetPropFlags() и как ей правильно пользоваться.

Рецепт рассказывает, как пользоваться ASSetPropFlags() и уточняет один момент, который неправильно истолковывается во многих материалах, посвященных этой функции.

Сейчас назвать функцию ASSetPropFlags() недокументированной довольно сложно... Поисковые системы по запросу "ASSetPropFlags" выдают сотни результатов. Однако, несмотря на то, что эта функция всем давно известна и часто используется разработчиками, в официальной документации к Флэшу о ней ничего не сказано. Поэтому можно сказать, что она недокументирована. Формально. :)

назад к списку уроков и рецептов

1. что это и зачем

2. как это работает

3. популярная ошибка

4. пример использования


1. Что это и зачем

Функция ASSetPropFlags() позволяет поставить/снять защиту свойств и методов объекта от нескольких действий: от изменения, от удаления и от перебора их циклом for..in. Эта функция используется самим флэш плеером на стадии инициализации и защищает некоторые встроенные объекты. Например, вы знаете, что есть такие глобальные встроенные классы, как Mouse и Key, и знаете, что ссылки на них хранятся в объекте _global, благодаря чему, они доступны в любом коде ролика.
trace("класс Mouse : "+Mouse);
trace("класс Key : "+Key);
trace("класс _global.Mouse : "+_global.Mouse);
trace("класс _global.Key : "+_global.Key);
В Output видим:
класс Mouse : [object Object]
класс Key : [object Object]
класс _global.Mouse : [object Object]
класс _global.Key : [object Object]
Это говорит нам о том, что такие объекты действительно есть, и ссылки на них действительно есть в объекте _global.

А теперь попробуем посмотреть, а что еще хранится в _global:
for (var i in _global) {
    trace(i);
}
И в Output видим... Ничего, вообще-то, не видим. Никаких Mouse и Key. Они просто скрыты от перебора for..in, при этом оставаясь доступными поименно. Функция ASSetPropFlags() позволяет нам, во-первых, достигнуть аналогичного эффекта для любых объектов, во-вторых, снимать защиту с предопределенных объектов.

в начало

2. Как это работает

Синтаксис ASSetPropFlags():
ASSetPropFlags(объект,массив имен,флаги защиты[,флаги обнуления]);

Подробнее о параметрах:
  • Объект — объект любого типа, внутри которого нужно поставить или снять защиту.
  • Массив имен — массив имён свойств и/или методов объекта, которые нужно защитить или с которых нужно снять защиту. В качестве этого аргумента можно передать null, и тогда действие распространится на все свойства и методы объекта.
  • Флаги защиты — число, три младших бита которого считаются флагами защиты. То есть, передать можно любое число, но действовать будут только три его младших бита.
    Действие флагов защиты:   бит 0 — защита от for..in
      бит 1 — защита от удаления
      бит 2 — защита от изменения
    Таким образом, число 1 (в двоичном виде: 001) установит защиту от for..in, но снимет защиту от удаления и изменения. А число 7 (в двоичном виде: 111) установит защиту от всех напастей. Число же 878 (в двоичном виде: 1101101110) установит защиту от изменения и от удаления, но снимет защиту от for..in.
  • Флаги обнуления (необязательный параметр) — число, три младших бита которого говорят функции, какие ранее установленные флаги разрешается обнулить. Аналогично предыдущему параметру, число может быть передано любое: только три младших бита будут играть роль.

    Эти флаги нужны для того, чтобы при последовательных вызовах ASSetPropFlags() не скинуть случайно флаги, установленные до этого. Например, вы установили защиту от удаления, передав число 2 (в двоичном виде 010) в качестве флагов защиты. Потом вы захотели поставить еще и защиту от изменения, и вызвали ASSetPropFlags() еще раз, передав число 4 (в двоичном виде 100) в качетве флага защиты. Как функция должна понять, хотите вы к установленным раньше флагам до бавить еще и флаг защиты от изменения, или же хотите оставить только его, скинув флаги удаления и for..in? Для того, чтобы снять неоднозначность, и существуют флаги обнуления.
    Действие флагов
    обнуления:
      бит 0 — разрешается ли обнулить флаг for..in
      бит 1 — разрешается ли обнулить флаг удаления
      бит 2 — разрешается ли обнулить флаг
                    изменения
    Если вы опустите при вызове параметр "флаги защиты", функция будет считать, что вы передали 0: никакие флаги защиты обнулять нельзя.
Например, вы хотите установить флаги защиты от изменения и от удаления (флаги защиты: 110). При этом вы не хотите изменить текущее значение флага for..in. Вы можете сделать это так:
// ставим защиту от изменения и от удаления
ASSetPropFlags(объект, массив имён, 6);
Здесь флаги обнуления вообще опущены, что означает, что ничего обнулять нельзя. Несмотря на то, что младший бит числа 6 — ноль, флаг for..in не будет снят. Другой пример:
// ставим защиту от удаления
ASSetPropFlags(объект, массив имён, 2);
// убираем защиту от удаления, 
// но ставим защиту от изменения и от for..in
ASSetPropFlags(объект, массив имён, 5, 2);
Первая строчка ставит защиту от удаления, поскольку флаг защиты равен 2 (двоичное 010). Во втором вызове флаг защиты равен 5 (двоичное 101), что означает, что вы хотите установить защиту от изменения и защиту от for..in. Флаг обнуления здесь равен 2 (двоичное 010), что означает, что разрешается снять флаг удаления, установленный ранее.

Совет. Для того, чтобы не задумываться каждый раз, какое именно число нужно передать, чтобы обеспечить желаемое значение флагов, используйте функцию parseInt(). Допустим, вы хотите установить флаг защиты от изменения, и обнулить флаги удаления и for..in, вы можете написать следующее:
var protect = parseInt("100", 2);
var clear = parseInt("011", 2);
ASSetPropFlags(объект, массив имён, protect, clear);
Естесственно, вы можете быстро сообразить что за десятичное число требуется передать и сделать запись короче:
ASSetPropFlags(объект, массив имён, 4, 3);
Но задумываться о том, какие флаги устанавливаются, вам придется каждый раз при чтении этого кода. А когда флаги записаны нулями и единицами — вы просто читаете, что происходит, хотя и тратите на это два вызова parseInt().
в начало

3. Популярная ошибка

Во многих материалах об ASSetPropFlags() говорится, что четвертый параметр функции — булево значение (true/false), которое показывает, можно ли скидывать установленные ранее флаги защиты. Обычно приводится такой пример:
// неправильно!
ASSetPropFlags(_global,null,6,true);

for(var i in _global){
    trace(i)
}
При этом в Output мы увидим всё, что содержится в объекте _global. Якобы, это иллюстрирует возможность снятия защиты от for..in при помощи флага защиты равного 6 и флага обнуления равного true. Защита от for..in действительно снимется...
Но здесь допущено две ошибки:
  • В качестве флага обнуления передано булево значение. ASSetPropFlags(), ожидая на этом месте число, приводит true к типу Number, и в результате получается 1. Единица и используется в качестве флага обнуления, что означает, что можно обнулить только флаг for..in.
  • В добавок к снятию флага for..in, в этом примере еще и ставятся флаги защиты от удаления и от изменения, ведь 6 в двоичном виде — 110. Иллюстрируя снятие флага for..in, совершенно незачем устанавливать два других флага. :)
Внимание. В четвертом параметре флажки передаются так же, как и в третьем, только назначение у них другое. Вероятно, ошибка с типом четвертого параметра — дань каким-то более ранним реализациям ASSetPropFlags() в предыдущих версиях флэш плеера. Так или иначе, во флэш плеере версии 7 и 8 ASSetPropFlags() работает так, как описано в данном материале. (В шестом плеере, подозреваю, то же. :) )
// правильно!
ASSetPropFlags(_global,null,0,1);

for(var i in _global){
    trace(i)
}
В качестве флагов защиты передан ноль (двоичное 000), что означает, что мы хотим снять все флаги. В качестве флагов обнуления передана 1 (двоичное 001), что означает, что обнулить разрешается только флаг for...in.


в начало

4. Пример использования

Убедимся, что это действительно работает. Для наглядности создадим переменные, хранящие флажки, причем будем следовать совету, данному выше, и запишем флаги через parseInt().
// переменные, хранящие флажки:

// overwrite flag - флаг изменения
var o = parseInt("100", 2);

// delete flag - флаг удаления
var d = parseInt("010", 2);

// for flag - флаг for..in
var f = parseInt("001", 2);

// создадим объект для экспериментов и несколько свойств в нем
var test_obj = {};
test_obj.readonly = "свойство только для чтения";
test_obj.godmode = "это не получится удалить";
test_obj.stealth = "свойство скрыто от for..in";

// перебор до защиты
trace("> до защиты - перебор for..in")
for (var i in test_obj) {
    trace(i+" = "+test_obj[i]);
}

// самое главное
ASSetPropFlags(test_obj, ["readonly"], o);
ASSetPropFlags(test_obj, ["godmode"], d);
ASSetPropFlags(test_obj, ["stealth"], f);

// попробуем удалить, изменить, перебрать for..in

// перебор
trace("\n> результат защиты - перебор for..in")
for (var i in test_obj) {
    trace(i+" = "+test_obj[i]);
}

// изменение
test_obj.readonly += "- изменено";
test_obj.godmode += "- изменено";
test_obj.stealth += "- изменено";

trace("\n> результат защиты - изменение")
trace("readonly = "+test_obj.readonly);
trace("godmode = "+test_obj.godmode);
trace("stealth = "+test_obj.stealth);

// удаление
delete test_obj.readonly;
delete test_obj.godmode;
delete test_obj.stealth;

trace("\n> результат защиты - удаление")
trace("readonly = "+test_obj.readonly);
trace("godmode = "+test_obj.godmode);
trace("stealth = "+test_obj.stealth);
Результат наших трудов в окне Output:
> до защиты - перебор for..in
stealth = свойство скрыто от for..in
godmode = это не получится удалить
readonly = свойство только для чтения

> результат защиты - перебор for..in
godmode = это не получится удалить
readonly = свойство только для чтения

> результат защиты - изменение
readonly = свойство только для чтения
godmode = это не получится удалить- изменено
stealth = свойство скрыто от for..in- изменено

> результат защиты - удаление
readonly = undefined
godmode = это не получится удалить- изменено
stealth = undefined


Иллюстрация на тему флагов обнуления:
var o = parseInt("100", 2);
var d = parseInt("010", 2);
var f = parseInt("001", 2);

var test_obj = {test:"свойство"};

// защитим от удаления и изменения все свойства test_obj
ASSetPropFlags(test_obj, null, o|d);

trace("изначально: test_obj.test = "+test_obj.test);

test_obj.test += "- изменено";
trace("\nизменение: test_obj.test = "+test_obj.test);

for (var i in test_obj) {
    trace("перебор: test_obj.test = "+test_obj[i]);
}

delete test_obj.test;
trace("удаление: test_obj.test = "+test_obj.test);
В Output видим:
изначально: test_obj.test = свойство

изменение: test_obj.test = свойство
перебор: test_obj.test = свойство
удаление: test_obj.test = свойство
Попытки изменить и удалить свойство test не увенчались успехом, но цикл for..in нашел это свойство, потому что защиту от перебора мы не ставили. Обратите внимание, здесь используется бинарное сложение флагов: o|d.

Теперь, допустим, мы хотим поставить защиту от перебора, но отменить защиту от удаления и от изменения.
var o = parseInt("100", 2);
var d = parseInt("010", 2);
var f = parseInt("001", 2);

var test_obj = {test:"свойство"};

// защитим от удаления и изменения все свойства test_obj
ASSetPropFlags(test_obj, null, o|d);

trace("изначально: test_obj.test = "+test_obj.test);

test_obj.test += "- изменено";
trace("\n1:изменение: test_obj.test = "+test_obj.test);

for (var i in test_obj) {
    trace("1:перебор: test_obj.test = "+test_obj[i]);
}

delete test_obj.test;
trace("1:удаление: test_obj.test = "+test_obj.test);

ASSetPropFlags(test_obj, null, f, o|d);

// проверим

test_obj.test += "- изменено";
trace("\n2:изменение: test_obj.test = "+test_obj.test);

for (var i in test_obj) {
    trace("2:перебор: test_obj.test = "+test_obj[i]);
}

delete test_obj.test;
trace("2:удаление: test_obj.test = "+test_obj.test);
В Output видим:
изначально: test_obj.test = свойство

1:изменение: test_obj.test = свойство
1:перебор: test_obj.test = свойство
1:удаление: test_obj.test = свойство

2:изменение: test_obj.test = свойство- изменено
2:удаление: test_obj.test = undefined
Видим, что после второго вызова ASSetPropFlags(), свойство test удалось и изменить и удалить... А вот цикл for..in его не нашел.


в начало
назад к списку уроков и рецептов