учебники, программирование, основы, введение в,

 

Эмулируем множественное наследование

Основания для оптимизма
Отсутствие множественного наследования - это одна из тех вещей, которая сильно раздражает программистов, переходящих с С++ на Java. (Есть еще довольно объемистый список таких вещей. Например, отсутствие перегрузки операторов и типизованных коллекций. А также отсутствие параметров по умолчанию в функциях и т.д.) К счастью, множество недостатков ActionScript первой версии слегка компенсируется тем, что какие-то вещи можно в той или иной степени эмулировать. Например, параметры по умолчанию эмулировать совсем просто:
function defArgExample(_text, _prefix){
if (_prefix != undefined) trace(_prefix + ": " + _text);
else trace(_text);
return;
}
defArgExample("building 8");
defArgExample("building 8", "address");

Этот код выводит
building 8
address: building 8

Или можно сделать даже так:
function defArgExample1(_text){
var tmpString;
if (arguments.length > 1){

   for (var i=arguments.length; -i>=1;) tmpString += arguments[i] + ": ";
trace(tmpString + _text);
}
else trace(_text);

return;
}

defArgExample1("building 9");
defArgExample1("building 9", "address");
defArgExample1("building 9", "address", "official data");

в результате чего получим:
building 9
address: building 9
official data: address: building 9

Вот и все - параметры по умолчанию готовы. Ранее вы могли видеть, как были эмулированы статические и приватные поля и методы. Так что возможности для эмуляции "всего на свете" интерпретатор ActionScript предоставляет весьма богатые. А это, в свою очередь, дает нам некоторые основания для оптимизма и в задаче об эмуляции множественного наследования.
Переходим к делу
Сразу отметим: авторы осознают, что проекты такого размера, которые требуют полноценного множественного наследования, на Флэш МХ вряд ли будут делаться. Хотя: как знать? Так или иначе, данная лекция является, скорее, демонстрацией возможностей языка ActionScript (а также и ECMA-Script, который, напоминаем, в виде JavaScript используется в браузерах). И хотя полноценное множественное наследование вам, скорее всего, не понадобится, возможно некоторые приемы, которыми мы сейчас воспользуемся, когда-нибудь используете и вы.
Теперь поставим задачу более строго. Конечно, не составит никакого труда просто скопировать все методы и поля из одного класса в другой с помощью кода наподобие следующего.
for (var name in sourceObj){
destObj[name] = sourceObj[name];
}

При этом надо взять в качестве sourceObj не что иное, как sourceClass.prototype; аналогично, в качестве destObj берем destClass.prototype. Более того, видимо, именно так и следует поступить, если все, что вам нужно от одного из базовых классов - это его поля и методы. Совсем другая ситуация возникает, если серьезная инициализационная работа производится в конструкторах, которые в свой черед вызывают базовые конструкторы и т.д. В таком случае цепочку __proto__ "сворачивать", копируя все методы, нельзя. Но возникает другая идея: а нельзя ли скопировать эту цепочку целиком? Скопировать каждую из цепочек (для каждого из базовых классов), а затем составить эти "поезда" друг за другом!
Идея хорошая, но давайте посмотрим, какие трудности нам придется преодолеть в процессе ее реализации.
Во-первых, надо ли копировать цепочки? Нельзя ли просто установить ссылку __proto__ самого последнего из базовых классов (для определенности будем дальше говорить "самого верхнего класса") одной из цепочек так, чтобы она указывала на самый нижний класс в другой цепочке? К сожалению, это невозможно, ибо приведет к "порче" ряда классов из первой цепочки - у них вдруг появятся базовые, которых отродясь не бывало. А ведь эти классы запросто могут использоваться безо всякого отношения к нашему множественному наследованию. Конечно, лишние базовые классы - это не то же самое, что недостающие; поведение изменится лишь в небольшом числе специальных случаев; но все равно неприятно. Так что копировать нужно.
Во-вторых, самый первый из конструкторов каждой субцепочки (так мы будем называть всю цепочку одного из базовых классов нашего "множественного наследника") надо будет вызывать по отдельности. И специальным образом формировать для каждого из таких конструкторов массив аргументов. Впрочем, здесь нет ничего необычного, ведь примерно то же самое происходит при вызове базовых конструкторов в С++.
В-третьих, как только мы начинаем работать с множественным наследованием, немедленно возникает проблема дублирования базовых классов. Что делать, если один и тот же класс повторяется в двух различных субцепочках? Один из естественных ответов на этот вопрос - не делать ничего. Поскольку у нас в субцепочках будет все-таки не сам класс, а его копии, такая ситуация ничем особенным не грозит. Однако есть два неудобства: а) занимается лишнее место в памяти и б) конструкторы этого класса в разных субцепочках могут делать вещи, которые конфликтуют друг с другом. В С++ обе эти проблемы решаются введением виртуальных базовых классов. Нам нужно будет предусмотреть механизм, который позволит иметь вместо двух совпадающих классов в разных субцепочках один класс, причем его конструктор будет вызываться прямо из конструктора "множественного наследника", как это делается для виртуальных базовых классов в С++.
В-четвертых, нужно уметь как следует работать с системными базовыми классами. Скажем, к последней субцепочке можно прикрепить сам системный класс, а не его копию (поскольку к нему в "хвост" уже ничего крепиться не будет). Наконец, если в разных субцепочках - разные системные базовые классы, надо уметь правильно их скопировать, включая скрытые поля и методы. Для этого, конечно, придется на короткое время все скрытое "приоткрыть", однако потом нужно будет аккуратно спрятать все обратно.

Как это будет работать
Теперь мы приступим к описанию функции multipleInherit, которая сможет решить все вышеописанные проблемы. Эта функция будет принимать следующие аргументы.

  1. Функцию, которая будет "прообразом" конструктора для создаваемого класса.
  2. Массив базовых классов (причем массив двумерный, о чем речь пойдет далее).
  3. Третий аргумент - необязательный. Он представляет собой массив, в котором хранится системный базовый класс (его нужно прикрепить к самой последней субцепочке - без копирования), а также функция вызова его конструктора, передающая туда необходимые аргументы.

Возвращать функция multipleInherit станет сгенерированный в ней объект-функцию, который и будет готовым конструктором нового класса. В поле prototype этого объекта-функции нужно будет заводить новые методы производного класса.
Поговорим подробнее об аргументах multipleInherit. Первый из них (функцию) мы назвали прообразом конструктора, поскольку из настоящего конструктора (его генерирует multipleInherit) для того, чтобы произвести инициализацию, будет вызвана именно эта функция. Поэтому в рассматриваемой функции вы должны предусмотреть все необходимые инициализационные действия (за исключением вызова конструкторов базовых классов - для этого мы будем применять отдельный механизм).
Второй аргумент - массив базовых классов. Однако, массив этот двумерный, поскольку для каждого базового класса нужно указать еще некоторую дополнительную информацию. Таким образом, каждый базовый класс описывается своим массивом, в котором может быть до четырех элементов. Набор таких массивов, объединенных в один большой массив - это и есть второй аргумент. О смысле элементов маленьких массивов мы скажем чуть позже.
Наконец, третий аргумент имеет смысл использовать, если скажем, некие два класса (из всей массы классов, от которых вы наследуетесь) являются наследниками одного и того же системного класса. Тогда лучшее, что вы можете сделать - прицепить этот системный класс в конце цепочки __proto__, причем исходный класс, а не его копию. (В результате получается нечто вроде виртуального базового класса.) Поскольку в этом случае непонятно, с какими аргументами вызывать конструктор системного класса - с теми, что передаются через цепочку первого базового класса, или второго - необходимо предусмотреть функцию для генерации правильного набора аргументов конструктора. Эта функция имеет еще одну особенность, о которой мы скажем далее. Сам системный класс и эту функцию при вызове multipleInherit надо поместить в массив (в нем, таким образом, будет всего два элемента), и этот массив и передать в качестве третьего аргумента в multipleInherit.
Чтобы окончательно понять, как именно будет вызываться функция multipleInherit, нам остается проанализировать, из чего состоят маленькие массивы, которые являются составными частями второго аргумента - списка базовых классов. Итак, первым элементом такого массива является сам базовый класс. Вторым - функция, которая подготавливает аргументы для его конструктора. Ей будет передан тот же список аргументов, что и конструктору "множественного наследника" при создании экземпляра этого класса. А вернуть такая функция должна массив аргументов, которые будут переданы конструктору базового класса, к коему "приписана" эта функция. (И хотя эта последняя похожа на аналогичную функцию, которую мы делаем для системного базового класса, они все же существенно отличаются - дальше мы поговорим об этом подробнее). Третий - необязательный - аргумент - это класс, на котором нужно остановиться при копировании субцепочки предков данного класса. Скажем, если мы хотим эмулировать виртуальный базовый класс, субцепочки всех его наследников надо оборвать в тот момент, когда мы доберемся до класса, который хотим сделать виртуальным базовым (копируем мы цепочки, естественно, начиная "снизу" - с производных классов). И, кроме того, нужно будет в списке базовых классов указать тот, который мы хотим сделать виртуальным базовым (причем указать после его производных). Мы увидим, как все это работает, разбирая примеры.
По поводу третьего элемента маленького массива может возникнуть вопрос: где мы, собственно останавливаемся при копировании? То есть включаем в цепочку указанный класс, или же нет? Оказывается, полезными в разных случаях будут оба варианта. Поэтому четвертый элемент маленького массива и будет указывать, как нам поступать: если он равен true, то указанный класс в цепочку включаем, если false - нет.
Проблемы
По ходу дела нам придется преодолеть ряд специфических трудностей. Трудность первая: мы не можем вызывать конструкторы базовых классов напрямую, поскольку это не позволит оборвать цепочку вызовов super() в нужном месте. Поэтому, копируя цепочки __proto__, мы заменяем конструкторы, вызывая из них старые при помощи apply. (И если вам кажется, что это может не сработать, вы правы: с этим связана вторая проблема, более серьезная. Но пойдем по порядку). Так вот, нам нужно сообразить, где хранить ссылки на эти новые конструкторы. Конечно, для этого есть стандартное поле constructor; но если речь идет об "основных" базовых классах, из которых мы и делаем нашего "множественного наследника", то к их новым конструкторам необходимо иметь моментальный доступ из готового конструктора "множественного наследника" (поскольку именно оттуда мы будем их вызывать). Так что для них придется создать отдельный массив.
Трудность вторая, по сравнению с которой первая кажется пустячной: вызывая конструкторы базовых классов как функции, мы делаем это с помощью кода наподобие baseConstr.apply(this, baseArguments). С одной стороны, никакого выбора у нас нет: действительно, базовый конструктор должен подействовать на this и никак иначе. С другой стороны, при вызове super() в базовом конструкторе мы оказываемся в двусмысленной ситуации: ведь super вызывает функцию-конструктор, записанную в __constructor__ у прототипа текущего класса; а текущим классом мы сами указали this, а вовсе не тот базовый, конструктор которого вызывали. Поначалу возникает впечатление, будто бы трудность эта (вызванная тем, что мы пренебрегли системными средствами и вызываем конструкторы "вручную") непреодолима. Затем появляется слабая надежда: записать в this.__proto__. __constructor__ ссылку на ту функцию-конструктор, которая нам нужна, перед вызовом super(). Как ни удивительно, это срабатывает, и мы можем двигаться дальше.
Трудность третья: вызвать конструктор системного базового класса аналогично остальным конструкторам нам не удастся. Это связано именно с тем, что конструкторы большинства системных классов (вроде Array или String) работают немного по-другому, чем обычные функции-конструкторы. В частности, они одновременно являются и функциями-операторами для преобразования типа. Видимо, потому-то сии функции определяют, вызваны ли они через new или непосредственно (в принципе, определить это довольно просто: при вызове из new значение arguments.caller равно null). Так или иначе, вызов конструкторов системных классов через apply не срабатывает корректно. Трудность кажется непреодолимой, однако нас снова выручит искусственный прием. Нужно лишь вспомнить, что наследоваться от системного класса обыкновенным образом вполне возможно. А это значит, что вызов его конструктора через super должен работать (несмотря на то, что arguments.caller при вызове через super выдает уже не null; так что здесь вступают в игру какие-то дополнительные встроенные механизмы). Итак, каким бы образом ни работал вызов конструктора системного базового класса через super, нам нужно воспользоваться именно этим методом. Но ведь настраивать ссылку на "текущий базовый конструктор" мы уже умеем! Так что необходимо сделать следующее. Функция, формирующая аргументы для системного базового класса, должна не возвращать массив с ними, а выполнять вызов наподобие super(arg1, arg2). А перед вызовом этой функции мы установим ссылку this.__proto__.__constructor__ указывающей на системный базовый класс. Вот пример, который подтвердит, что такой пр ием сработает. Выполним следующий код:
class1 = function(arg){this.a = 100; this.b = arg;}
class2 = function(argClass, otherArgsArray){
argClass.apply(this, otherArgsArray);
}
func = function(someArgsArray){
super(someArgsArray[0], someArgsArray[1]);
}
class3 = function(argClass, otherArgsArray){
this.__proto__.__constructor__ = argClass;
func.apply(this, [otherArgsArray]);
}
b = new class2(class1, [1000]);
c = new class2(Array, [111, 222]);
b1 = new class3(class1, [1000]);
c1 = new class3(Array, [111, 222]);

Здесь "базовым классом" являются по очереди class1 и Array, а "производными" - class2 и class3. Мы пишем термины "базовый" и "производный" в кавычках, поскольку цепочку __proto__ мы здесь не создаем, а лишь проверяем, как работают разные нестандартные способы вызова базовых конструкторов. При этом конструктор "базового класса" в class2 вызывается при помощи apply, а в class3 - при помощи вспомогательной функции, внутри которой стоит вызов super(). К вспомогательной функции мы здесь прибегаем, чтобы создать "условия, приближенные к боевым", то есть сделать код максимально похожим на тот, что будет использоваться при реализации множественного наследования. Итак, запустив этот код на исполнение, а затем нажав Ctrl+Alt+V (вывод переменных), получим:
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.class1 = [function '__constructor__']
Variable _level0.class2 = [function]
Variable _level0.func = [function]
Variable _level0.class3 = [function] {
prototype:[object #5, class 'Object'] {
__constructor__:[function '__constructor__']
}
}
Variable _level0.b = [object #7] {
a:100,
b:1000
}
Variable _level0.c = [object #8] {}
Variable _level0.b1 = [object #9] {
a:100,
b:1000
}
Variable _level0.c1 = [object #10] [
0:111,
1:222
]

Отсюда видно, что при использовании в качестве базового класса созданного нами class1 работают оба способа (вызов и через apply, и через super). А вот для системного класса Array подходит только последний способ.
Наконец, при написании функции, реализующей множественное наследование, будет полезно сделать некоторые приготовления на будущее. Далее мы поговорим о том, какие могут возникнуть у пользователя пожелания, когда он соберется наследоваться от сделанного при помощи multipleInherit класса. Особенно если это наследование в свою очередь будет множественным. Оказывается, чтобы этот процесс был наиболее удобным, полезно сохранить ссылку на массив базовых классов. Что мы и сделаем, прикрепив ссылку в качестве поля к возвращаемому из multipleInherit готовому конструктору.

Код вспомогательных функций
Итак, пора заканчивать разговоры и разбирать код. Далее приведены подробно откомментированные функции, с помощью которых реализуется множественное наследование. Начинаем с функции, которая реализует копирование. Сделаем сразу функцию, которая может работать в двух режимах: с копированием скрытых полей и методов и без него. (Для множественного наследования мы будет использовать первый из них.) Вот код этой функции.
/*=======================================================*
* Функция copyObjectExt - вспомогательная функция для копирования *
*=======================================================*/

// Главная особенность функции copyObjectExt - флаг copyWithHidden.
// Если он установлен в false, то копируются все поля и методы,
// кроме скрытых.
// Если же установить этот флаг в true, то копируются и скрытые
// зметоды, а исключением __proto__, constructor и
// __constructor__, которые мы устанавливаем вручную. После
// копирования те методы, что ранее были скрыты от for...in,
// скрываются обратно.
_global.copyObjectExt = function(sourceObj, destObj, copyWithHidden){
// Если объект, в который нужно все копировать,
// не задан, он создается.
if (destObj == null) destObj = new Object();
if (!copyWithHidden){
// Копируем нерекурсивно - вложенные объекты в прототипах,
// как правило, не используются. Если они вам нужны -
// поставьте здесь рекурсивное копирование.
// Мы, правда, далее заводим поля у обектов-функций,
// выполняющих роль конструктора. Но конструкторы мы здесь
// не копируем вовсе.
for (var name in sourceObj){
// Если защита снята заранее, все поля будут видны,
// но копировать надо не все.
if (name != "__proto__" &&
name != "constructor" &&
name != "__constructor__") destObj[name] = sourceObj[name];
}
// В некоторых случаях при отладке будет полезно узнать,
// что рассматриваемый объект - копия.
if (DEBUG) destObj["isACopy"] = "THAT'S A COPY";
}
else{
// Копируем сначала открытые поля, чтобы запомнить,
// какие были открыты.
var tempObj = copyObjectExt(sourceObj, null, false);
// Снимаем защиту со всех полей
ASSetPropFlags(sourceObj, null, 0, 1);
// Копируем все поля
copyObjectExt(sourceObj, destObj, false);
// Ставим защиту обратно: сначал на все поля вообще
ASSetPropFlags(sourceObj, null, 1);
ASSetPropFlags(destObj, null, 1);
// Потом открываем те поля, что должны быть открыты
for (var name in tempObj){
ASSetPropFlags(sourceObj, name, 0, 1); 
ASSetPropFlags(destObj, name, 0, 1);     
}
}

return destObj;
}

Для того чтобы посмотреть, что получается в результате работы этой функции копирования, нам придется усовершенствовать функцию dumpObj, которой мы пользовались раньше. В новом варианте рядом с именем функции (поля) будет выводиться комментарий о том, что она является скрытой (если это действительно так). Если функция скрытой не является, комментария не будет никакого. Код новой функции dumpObj таков:
// Печатаем имя и значение поля, при этом предпринимаем усилия,
// чтобы null и undefined преобразовывались в "\null" и
// "\undefined", а не в пустую строку.
_global.printField = function(name, value, comment){
if (value === undefined) value = "\\undefined";
if (value === null) value = "\\null";
trace(name + " " + comment + ": " + value);
}
// Печатаем все, что доступно функции for...in.
// Если передана информация об открытых и неопределенных полях
// (то есть tempObj и undefinedList), используем ее, добавляя,
// когда нужно, комментарий "<hidden>" к имени поля.
_global.doPrintFields =
function(obj, str, tempProto, tempObj, undefinedList)
{
trace(":::::::::::  " + str + "  ::::::::::::");
trace("::::::::::::::::::::::::::::::::::::::::::::::::");
for (var name in obj){
// Принимаем меры для того, чтобы наши действия
// с обнулением __proto__ не отражались на выводимой
// информации.

var comment = "";
if (tempObj != null){
if (
tempObj[name + "_notInObjectSuffix"] === undefined
&& !undefinedList[name + "_notInObjectSuffix"]
){
comment = "<hidden>"; 
}
}

if (name == "__proto__") printField(name, tempProto,
comment);
else printField(name, obj[name], comment);         
}

trace("::::::::::::::::::::::::::::::::::::::::::::::::");
}
// Чтобы увидеть скрытые поля, снимаем с них защиту.
// Попутно запоминаем, какие поля были открытыми и
// в каких было записано undefined, хотя они и существовали
_global.printFieldsByForIn = function(obj, str, tempProto, dontRecoverHidden){
if (dontRecoverHidden){
doPrintFields(obj, str, tempProto);
}
else{
// Копируем сначала открытые поля, чтобы запомнить,
// какие были открыты
var tempObj = new Object();
var undefinedList = new Object();
var tempWOSuffixes = new Object();
for (var name in obj){
// Добавляем суффиксы, чтобы не перепутать с функциями
// Object, которые в tempObj, конечно, есть.
// В отличие от случая с функцией копирования,
// пренебрегать этим нельзя, потому что до Object'а мы
// все равно доберемся, причем именно когда он будет
// в "незащищенном" состоянии.
tempObj[name + "_notInObjectSuffix"] = obj[name];
tempWOSuffixes[name] = obj[name];
if (obj[name] === undefined)
undefinedList[name + "_notInObjectSuffix"] = true;
}
// Снимаем защиту со всех полей
ASSetPropFlags(obj, null, 0, 1);
// Выводим содержимое полей
doPrintFields(obj, str, tempProto, tempObj, undefinedList);
// Ставим защиту обратно: сначалa на все поля вообще
ASSetPropFlags(obj, null, 1);
// Потом открываем те поля, что должны быть открыты
for (var name in tempWOSuffixes){
if (
tempObj[name + "_notInObjectSuffix"] !== undefined
|| undefinedList[name + "_notInObjectSuffix"]
){
ASSetPropFlags(obj, name, 0, 1);        
}
}
}
}
// В этой рекурсивной функции мы используем фокус с
// "отцеплением цепочки" __proto__ - иначе оператором
// for...in были бы выведены вперемешку поля и методы от
// разных классов цепочки.
_global.printAllFields = function(obj, name, dontRecoverHidden){
var tempProto = obj.__proto__;
obj.__proto__ = null;
printFieldsByForIn(obj, name, tempProto, dontRecoverHidden);
obj.__proto__ = tempProto;
// Проверка на null не нужна: null == undefined, хотя
// отличить их и можно при помощи оператора ===.
if (obj.__proto__ != undefined)
printAllFields(obj.__proto__, name + ".__proto__");
}
// А эта функция просто вызывает основную рабочую функцию и
// добавляет "элементы оформления" (в текстовом виде, разумеется).
_global.dumpObj = function(obj, name, dontRecoverHidden){
trace("=============================================");
if (name == undefined) name = "<Dumped object>";
printAllFields(obj, name, dontRecoverHidden);
trace("=============================================");
trace("");
}

Теперь сделаем небольшой тестовый пример. Для удобства этот и два предыдущих фрагмента кода можно разместить в трех последовательных ключевых кадрах нового флэш-ролика, только не забудьте в четвертом кадре поставить stop().
// cn1 - означает "constructor number 1".
// Похожие классы (в том числе с другими номерами)
// мы будем использовать далее в тестовых целях.
cn1 = function(a, b){
super(a + "_cn1", b + "_cn1");
trace("constr cn1: " + a + " | " + b);  
}
cn1.prototype = new Array();
cn1.prototype.cn1_f = function(){trace("func: cn1_f");}
cn1.prototype.cn1_g = function(){trace("func: cn1_g");}
// Имя означает "object number 1".
on1 = new cn1(5, 6);
on1_copy = copyObjectExt(on1, null, true);
trace("\n Строчка, расположенная выше - это результат работы
конструктора");
trace("\n А теперь выведем поля и методы интересующих нас
объектов. \n");
dumpObj(on1, "on1");
dumpObj(on1_copy, "on1_copy");
trace("\n Проверяем, что мы не испортили скрытие полей и
методов. \n");
dumpObj(on1, "on1");
trace("\n А теперь раскроем все методы класса Array. \n");
ASSetPropFlags(Array.prototype, null, 0, 7);
on1_copy2 = copyObjectExt(on1, null, true);
dumpObj(on1_copy2, "on1_copy2");
dumpObj(Array.prototype, "Array.prototype");

Запуск этого примера дает следующий результат:
constr cn1: 5 | 6
Строчка, расположенная выше - это результат работы конструктора
А теперь выведем поля и методы интересующих нас объектов.
==========================================================
:::::::::::  on1  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : 6_cn1
0 : 5_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn <hidden>: [type Function]
reverse <hidden>: [type Function]
sort <hidden>: [type Function]
toString <hidden>: [type Function]
splice <hidden>: [type Function]
join <hidden>: [type Function]
slice <hidden>: [type Function]
unshift <hidden>: [type Function]
shift <hidden>: [type Function]
concat <hidden>: [type Function]
pop <hidden>: [type Function]
push <hidden>: [type Function]
__proto__ <hidden>: [object Object]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ : \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================

==========================================================
:::::::::::  on1_copy  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
length <hidden>: 2
0 : 5_cn1
1 : 6_cn1
cn1_f : [type Function]
cn1_g : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1_copy.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ <hidden>: \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================

Проверяем, что мы не испортили скрытие полей и методов.
==========================================================
:::::::::::  on1  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : 6_cn1
0 : 5_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn <hidden>: [type Function]
reverse <hidden>: [type Function]
sort <hidden>: [type Function]
toString <hidden>: [type Function]
splice <hidden>: [type Function]
join <hidden>: [type Function]
slice <hidden>: [type Function]
unshift <hidden>: [type Function]
shift <hidden>: [type Function]
concat <hidden>: [type Function]
pop <hidden>: [type Function]
push <hidden>: [type Function]
__proto__ <hidden>: [object Object]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1.__proto__.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ <hidden>: \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================

А теперь раскроем все методы класса Array.
==========================================================
:::::::::::  on1_copy2  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
length <hidden>: 2
0 : 5_cn1
1 : 6_cn1
cn1_f : [type Function]
cn1_g : [type Function]
push : [type Function]
pop : [type Function]
concat : [type Function]
shift : [type Function]
unshift : [type Function]
slice : [type Function]
join : [type Function]
splice : [type Function]
toString : [type Function]
sort : [type Function]
reverse : [type Function]
sortOn : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  on1_copy2.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ <hidden>: \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
==========================================================
:::::::::::  Array.prototype  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn : [type Function]
reverse : [type Function]
sort : [type Function]
toString : [type Function]
splice : [type Function]
join : [type Function]
slice : [type Function]
unshift : [type Function]
shift : [type Function]
concat : [type Function]
pop : [type Function]
push : [type Function]
__proto__ : [object Object]
constructor : [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  Array.prototype.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ <hidden>: \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================

Давайте разберемся, что же мы получили. Во-первых, новая функция dumpObj работает и не портит изначально скрытые объекты. Во-вторых, функция копирования тоже работает аккуратно и копирует с учетом скрытия. В-третьих, поскольку ASSetPropFlags не действует на поля из базовых классов, скрытые функции базовых классов не копируются. Если же их раскрыть (как мы дальше поступили с классом Array), то копируется все. Эту особенность следует учитывать при пользовании функцией копирования. Впрочем, при реализации множественного наследования мы будем ею пользоваться, предварительно "отцепляя цепочку" базовых классов путем установки __proto__ в null. Так что данная тонкость для нас не будет важна.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика