Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008 |
Наследование во Flash MX
Работа с прототипами различных уровней
Итак, мы видим, что имея объект, можно обратиться как к его собственным полям, так и к полям прототипа, полям прототипа прототипа и т.д. Причем, такое обращение может осуществляться как автоматически (при поиске поля или функции), так и вручную при помощи цепочки ссылок вида __proto__.__proto__.__proto__ и т.д. Похоже, мы злоупотребляем здесь выражением "и так далее", но лишь по той причине, что это наилучший способ выражать мысли при разговоре о цепочке прототипов произвольной длины.
Так вот, возникает очевидное желание, во-первых, научиться выводить на печать список полей объектов с указанием того, из какого именно прототипа (или же из самого объекта) взято каждое поле. Во-вторых, поскольку в процессе выполнения нашего первого желания не обойтись без использования оператора for...in, нужно разобраться, как ведет себя этот оператор по отношению к полям объекта и его прототипов различного уровня.
Как работает for...in, если есть базовые классы
Прежде всего, еще раз напомним одну особенность работы оператора for...in, о которой мы уже писали во второй лекции. А именно, поля, созданные позже, встречаются при переборе раньше (за исключением созданных в фигурных скобках в процессе инициализации объекта). Логично, что и естественный порядок перебора прототипов (то есть сначала находим все свойства в самом объекте, потом ищем в прототипе, потом в прототипе прототипа и так далее) меняется на противоположный. А, возможно, в начале работы оператора for...in Флэш производит перебор полей в прямом порядке, складывает все имена в какой-нибудь буфер и лишь потом начинает выполнять тело цикла, вынимая имена из буфера в обратном порядке. Удивительно, что в онлайн-документации ни слова про этот обратный порядок не сказано. Но, как бы то ни было, мы сейчас продемонстрируем все вышеописанные свойства оператора for...in на простом примере. Запускаем следующий код:
obj1 = {a: 1, b: 2}; obj2 = {b: 3, c: 4}; obj3 = {c: 5, d: 6}; // Вместо нормального наследование просто // определяем __proto__ - в данном случае // это ничего не меняет obj3.__proto__ = obj2; obj2.__proto__ = obj1; for(name in obj3){ trace("obj3." + name + " = " + obj3[name]); }
и получаем в результате:
obj3.a = 1 obj3.b = 3 obj3.c = 5 obj3.d = 6
Итак, мы видим, что соответствующие переменные взяты из тех объектов, где они впервые появились в цепочке __proto__ - считая от того объекта, у которого мы запрашиваем переменную. Можно сказать и по-другому: переменные берутся из объекта "самого производного " класса, в котором они были переопределены. Все же первый вариант лучше, ведь мы сейчас, фактически, не создавали никаких классов, а сделали только несколько объектов, связанных цепочкой ссылок __proto__. Также мы видим и описанный выше "обратный порядок перебора": до поля a дольше всего добираться по цепочке __proto__, а выводится оно первым. (И алфавитный порядок здесь ни при чем; вы можете переименовать переменные и убедиться, что от их имен порядок вывода не зависит.) Забавно, что инициализировать ссылки __proto__ для создания цепочки можно прямо при создании объектов при помощи примерно такого кода:
obj1 = {a: 1, b: 2}; obj2 = {__proto__: obj1, b: 3, c: 4}; obj3 = {__proto__: obj2, c: 5, d: 6}; for(var name in obj3){ trace("obj3." + name + " = " + obj3[name]); }
Этот код дает точно такой же результат, что и предыдущий.
Пример: копирование массива с помощью рекурсивного снятия защиты
В качестве более интересного примера использования for...in с учетом цепочки __proto__ мы рассмотрим прием копирования объектов (в том числе системных) с предварительным рекурсивным снятием защиты. Снятие защиты позволит нам скопировать практически все системные поля. В частности, сейчас мы увидим, что этот прием позволяет скопировать массив нестандартным способом. Это не значит, конечно, что новый способ чем-то лучше - наоборот, он хуже по многим соображениям. Но интересно то, что этот способ в принципе существует.
Итак, следующий код рекурсивно снимает защиту со всех полей объектов, имеющихся в цепочке __proto__ (не делайте так в ваших программах, если не уверены, что вам это нужно). И затем копирует все поля заданного объекта (и объектов в его цепочке __proto__ ) в другой объект.
// Эта функция снимает защиту со всех полей // всех объектов в цепочке __proto__ аргумента. _global.unhideAll = function(obj){ ASSetPropFlags(obj,null,0,1); // На null можно не проверять, поскольку // null == undefined, хотя и null !== undefined. if (obj.__proto__ != undefined) unhideAll(obj.__proto__); } _global.copyObj = function(toObj, fromObj){ // снимаем защиту от for...in unhideAll(fromObj); // и затем копируем все, что видит for...in - // то есть все, кроме переопределенных переменных // или функций for (var fieldName in fromObj){ toObj[fieldName] = fromObj[fieldName]; } } // Проверяем, удается ли скопировать таким образом массив. someObj = {}; arr = [2, 6, 0]; copyObj(someObj, arr); // Убеждаемся, что массив не только скопировался, но и // работают его методы. trace("Отсортированный массив = [" + someObj.sort() + "]");
На выходе получим
Отсортированный массив = [0,2,6]
Так что у нас действительно произошло полноценное копирование. Все же напомним, что скопировать массив можно быстрее и удобнее при помощи метода slice, написав примерно следующее: copy_array = source_array.slice(0, source_array.length);