Беларусь, рогачёв |
Эмулируем множественное наследование
Код вспомогательных функций
Итак, пора заканчивать разговоры и разбирать код. Далее приведены подробно откомментированные функции, с помощью которых реализуется множественное наследование. Начинаем с функции, которая реализует копирование. Сделаем сразу функцию, которая может работать в двух режимах: с копированием скрытых полей и методов и без него. (Для множественного наследования мы будет использовать первый из них.) Вот код этой функции.
/*=======================================================* * Функция 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(""); }8.1.
Теперь сделаем небольшой тестовый пример. Для удобства этот и два предыдущих фрагмента кода можно разместить в трех последовательных ключевых кадрах нового флэш-ролика, только не забудьте в четвертом кадре поставить 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. Так что данная тонкость для нас не будет важна.
Код основной функции
Переходим к основной функции множественного наследования multipleInherit. Ее аргументы мы описали ранее; еще раз напомним, что в нее передаются функция-заготовка для конструктора, массив базовых классов (состоящий из субмассивов, каждый из которых содержит всю нужную информацию о конкретном базовом классе) и, если надо, массив с информацией о некопируемом системном базовом классе. Вот код этой функции.
/*=======================================================* * Функция multipleInherit - реализует множественное наследование * *=======================================================*/ // В функции multipleInherit надо скопировать цепочки __proto__ // для всех используемых классов. При составлении цепочки // __proto__ нужно клонировать объекты-функции конструкторов. _global.multipleInherit = function(constr, bases, systemBase){ // Определяем "константы" для обращения к субмассивам аргумента // bases var baseClassNum = 0, argsFuncNum = 1, stopClassNum = 2, stopInclusiveNum = 3; // Если базовых классов нет, выходим if (bases == null || bases.length == 0) return null; // Если systemBase не указан, то это Object if (systemBase == null || systemBase[baseClassNum] == null){ systemBase = [Object, function (){}]; } // Заводим локальные переменные. // Префиксы при именах переменных означают три уровня классов: // prev - этот класс мы уже вставили в цепочку (и к нему // прицепляем следующие), cur - этот класс мы вставляем в // цепочку как раз сейчас, next - этот класс мы будем вставлять // в цепочку на следующем этапе. В свою очередь, "корень" source // обозначает копируемую цепочку, а dest - цепочку новую. // Поскольку мы заводим только необходимые переменные, наборы для // исходной и для новой цепочек получились не вполне симметричны. // Переменные для исходной цепочки var prevSourceConstr; // Предыдущий класс (наследник текущего) var curSourceConstr; // Текущий класс (его конструктор) var curSourceProt; // Прототип последнего готового класса var nextSourceProt; // Прототип следующего класса // Переменные для новой цепочки var prevDestProt; // Прототип последнего готового класса var prevDestConstr; // Конструктор последнего готового класса var curDestConstr; // Конструктор приготовляемого класса var curDestProt; // Прототип приготовляемого класса // Здесь мы будем держать скопированные (и модифицированные) // базовые конструкторы var baseConstructorsReady = new Array(); // Создаем новый класс - он будет результатом нашей работы. // В его конструкторе будет размещена функция вызова базовых // конструкторов. Именно этот класс и возвращает // функция multipleInherit var ClassToReturn = function(){ // Сохраняем ссылку на базовый конструктор, поскольку // эту ссылку нам придется изменять. var baseConstr = this.__proto__.__constructor__; // Вызываем конструкторы базовых классов, // начиная с "наиболее базовых". // Если есть systemBase, то вызываем конструктор для него if (systemBase != null && systemBase[baseClassNum] != null){ // Вызов делается через функцию, передающую аргументы // (через вызов super() в ней), поэтому устанавливаем // в качестве базового конструктора именно системный // базовый класс. this.__proto__.__constructor__ = systemBase[baseClassNum]; // Собственно вызов конструктора вышеописанным способом. systemBase[argsFuncNum].apply(this, arguments); } // Далее вызываем конструкторы остальных базовых классов. // Снова таки, начинаем с классов, // ближайших к Object (или systemBase) for (var i=bases.length - 1; i>=0; i-){ // Вызываем конструктор baseConstructorsReady[i].apply( this, bases[i][argsFuncNum].apply(this, arguments) ); } // Восстанавливаем ссылку на конструктор базового класса this.__proto__.__constructor__ = baseConstr; // И потом вызываем исходный конструктор constr.apply(this, arguments); } // Цепляем массивы с исходными функциями и классами // к создаваемому классу. Это позволит нам в дальнейшем // создать удобные утилиты. // Для той же цели цепляем и исходную функцию-конструктор ClassToReturn.bases = bases; ClassToReturn.constr = constr; // Готовимся копировать цепочки __proto__ ClassToReturn.prototype = new Object(); prevDestProt = ClassToReturn.prototype; prevSourceConstr = null; prevDestConstr = ClassToReturn; ClassToReturn.constr.prototype = prevDestProt; // Этот флаг позволит настроить цепочку классов так, чтобы // все вызовы первых конструкторов в каждой субцепочке // (для каждого из непосредственных базовых классов) // производить исключительно из только что созданного нами // конструктора класса-наследника var isFirstInSubChain = true; for(var i=0; i<bases.length; i++){ curSourceConstr = bases[i][baseClassNum]; curSourceProt = curSourceConstr.prototype; isFirstInSubChain = true; // Цикл для одной субцепочки (то есть для цепочки // __proto__ одного из базовых классов) while(curSourceProt){ // Проверяем, не надо ли оборвать цепочку if( !bases[i][stopInclusiveNum] && bases[i][stopClassNum] == curSourceConstr ) break; if( bases[i][stopInclusiveNum] && bases[i][stopClassNum] == prevSourceConstr ) break; if ( curSourceConstr == systemBase[baseClassNum] || curSourceConstr == Object ) break; // В системных классах constructor прототипа // указывает на сам класс (в отличие от классов, // сделанных самостоятельно). Проверяем этот вариант: if ( curSourceConstr.prototype.constructor == curSourceConstr && ( curSourceProt.constructor == systemBase[baseClassNum] || curSourceProt.constructor == Object ) ) break; // Заводим новый объект и копируем в него функции из // текущего прототипа исходной цепочки curDestProt = new Object(); // При этом нам надо временно отцепить "хвост", чтобы // не копировать его содержимое nextSourceProt = curSourceProt.__proto__; curSourceProt.__proto__ = undefined; // Копируем, учитывая скрытые поля (если есть) copyObjectExt(curSourceProt, curDestProt, true); // Восстанавливаем исходную цепочку curSourceProt.__proto__ = nextSourceProt; // Теперь надо приготовить конструктор новому классу curDestConstr = function(){ // Вызываем исходный конструктор. // Он будет прикреплен к данному объекту-функции. // Только перед его вызовом надо временно изменить // ссылку на базовый конструктор в this - чтобы // она указывала на класс, базовый к вызываемому // конструктору, а не к классу, объектом которого // является this this.__proto__.__constructor__ = arguments.callee.__constructor__; arguments.callee.curSourceConstr.apply(this, arguments); // Значение this.__proto__.__constructor__ будет // восстановлено далее в конструкторе this } // Теперь прикрепляем к созданному объекту-функции // прототип и исходный конструктор curDestConstr.prototype = curDestProt; curDestConstr.curSourceConstr = curSourceConstr; // Готовый конструктор цепляем к предыдущему прототипу prevDestProt.constructor = curDestConstr; // Его же (конструктор) надо прикрепить в качестве // базового конструктора к производному классу // (если только мы не в начале субцепочки; а если в // начале, то вместо этого записываем в массив // сгенерированный конструктор). Потом (перед вызовом) // прикрепленный к конструктору производного класса // базовый конструктор мы достанем, // используя конструкцию argumetns.callee if (!isFirstInSubChain){ prevDestConstr.__constructor__ = curDestConstr; } else baseConstructorsReady[i] = curDestConstr; // Готовый объект цепляем в цепочку. prevDestProt.__proto__ = curDestProt; // Сдвигаемся на один шаг по цепочке // (в направлении базовых классов) prevSourceConstr = curSourceConstr; curSourceConstr = curSourceProt.constructor; // Только теперь, получив ссылку на новый исходный // конструктор, можно менять ссылку на исходный прототип curSourceProt = curSourceProt.__proto__; prevDestProt = curDestProt; prevDestConstr = curDestConstr; // Приводим значение флага в соответствие // с нашим новым положением в субцепочке isFirstInSubChain = false; } // Закончили цикл одной субцепочки } // Закончили цикл одной серии базовых классов (виртуальных // или нет) // Осталось добавить базовый класс systemBase prevDestProt.__proto__ = systemBase[baseClassNum].prototype; // Все готово, возвращаем результат return ClassToReturn; }8.2.