Мы уже познакомились с процессом создания объектов во Флэш МХ и теперь знаем, что любой объект легко поддается модификации. Но ведь прототипом объекта всегда тоже является какой-то объект! Значит и прототип можно модифицировать. К чему это приводит, мы сейчас увидим.
В предыдущей лекции прототип объекта первоначально был у нас пустым, и каждый метод в него мы добавляли "вручную". Предположим однако, что мы написали
function MyClass(){}
MyClass.prototype = new MyBaseClass();
и затем уже модифицируем имеющийся прототип. В результате многократного применения подобной операции возникает структура, подобная изображенной на рис.7.1.
(рис 7.1)
Вертикальные стрелки символизируют создание нового объекта при помощи вызова оператора new.
Нетрудно убедиться в том, что у нас получилось самое настоящее
Предположим, что мы написали obj.a, то есть запросили значение поля a у объекта obj. Где Флэш будет искать значение этого поля? Сначала, разумеется, он посмотрит среди полей, которые мы непосредственно завели в объекте obj. Если ничего не найдено, то, как мы знаем, Флэш принимается за поиски в прототипе, который он находит с помощью ссылки __proto__. Но поиски в прототипе идут по той же самой схеме, то есть если непосредственно в прототипе искомая ссылка не нашлась, то мы идем в прототип прототипа (относительно исходного объекта это __proto__.__proto__ ) и так далее вплоть до прототипа типа Object (который автоматически попадает в "корень" этой цепочки, даже если у самого
Можно ли как-нибудь узнать, добавлено ли некоторое поле непосредственно в исследуемый объект или же оно найдено где-то в __proto__? Оказывается, можно, хотя и приходится для этого использовать недокументированную функцию Object.hasOwnProperty. Вот код, который демонстрирует все, что нам нужно.
prot = {a: 5, b: 6};
constr = function(){}
constr.prototype = prot;
obj = new constr();
obj.a = 7;
trace("obj.a = " + obj.a);
trace("obj.b = " + obj.b);
trace("Has own property a ? " + obj.hasOwnProperty("a"));
trace("Has own property b ? " + obj.hasOwnProperty("b"));
obj.b = undefined;
delete obj.a;
trace("-------------");
trace("obj.a = " + obj.a);
trace("obj.b = " + obj.b);
trace("Has own property a ? " + obj.hasOwnProperty("a"));
trace("Has own property b ? " + obj.hasOwnProperty("b"));
Вот что получается в результате:
obj.a = 7 obj.b = 6 Has own property a ? true Has own property b ? false ------------- obj.a = 5 obj.b = Has own property a ? false Has own property b ? true
Итак, что же произошло? Мы создали объект prot, который использовали в качестве прототипа объекта типа constr. Собственно говоря, тут еще не было prot - это единичный объект, не описывающий тип. Если бы мы непременно хотели устроить Base, а затем объект этого класса использовать в качестве прототипа класса constr. Впрочем, мы пока что иллюстрируем не constr тот факт, что мы используем эту функцию в качестве конструктора, а то, что эта функция-объект задает класс, нам не очень важно. В конце концов, мы могли обойтись вовсе без конструктора, а написать obj.__proto__ =
prot. Можете убедиться, что этот вариант тоже работает; более подробно мы его разберем, когда будем говорить об
Так или иначе, мы получили объект со своими собственными полями и с полями, унаследованными из прототипа. Мы видим, что функция Object.hasOwnProperty работает так, как ожидалось. Заодно убеждаемся в том, что уничтожить поле и присвоить ему значение undefined - это разные вещи. В последнем случае значения из прототипа скрываются свежеприсвоенным значением undefined.
Итак, мы видим, что имея объект, можно обратиться как к его собственным полям, так и к полям прототипа, полям прототипа прототипа и т.д. Причем, такое обращение может осуществляться как автоматически (при поиске поля или функции), так и вручную при помощи цепочки ссылок вида __proto__.__proto__.__proto__ и т.д. Похоже, мы злоупотребляем здесь выражением "и так далее", но лишь по той причине, что это наилучший способ выражать мысли при разговоре о цепочке прототипов произвольной длины.
Так вот, возникает очевидное желание, во-первых, научиться выводить на печать список полей объектов с указанием того, из какого именно прототипа (или же из самого объекта) взято каждое поле. Во-вторых, поскольку в процессе выполнения нашего первого желания не обойтись без использования оператора 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);
В этом небольшом разделе мы поговорим о том, какие есть удобные способы вывести полную таблицу полей и методов объекта - таблицу, в которой все переменные и функции рассортированы по принадлежности к одному из прототипов в __proto__.
Для системных объектов вывести все их содержимое в правильном порядке довольно просто (поскольку содержимое прототипа всегда скрыто). Алгоритм здесь такой: при помощи for...in выводим поля, непосредственно принадлежащие объекту. Возможно, их надо будет сначала раскрыть при помощи ASSetPropFlags. Затем в качестве текущего объекта выбираем прототип, то есть содержимое ссылки __proto__. И повторяем вышеописанную операцию. Так действуем до тех пор, пока в __proto__ хоть что-нибудь есть (на деле мы всегда упираемся в Object ). C произвольным объектом такая операция не пройдет, поскольку в for...in сразу же обнаружатся поля из прототипов различных уровней вложенности. Можно, конечно, разыскать все эти прототипы заранее и скрыть все присутствующие в них поля и методы. Но если уж применять подобные "силовые приемы", то лучше воспользоваться временным "отцеплением цепочки", о котором мы сейчас расскажем.
Идея этого метода состоит в следующем: мы временно записываем в поле __proto__ значение null (сохранив куда-нибудь старое содержимое поля). В результате в нашем объекте останутся доступными только его собственные поля - мы как бы отключаем все __proto__ к прежнему состоянию, затем в качестве текущего объекта берем тот, на который ссылается __proto__, и повторяем все эти операции уже для него. В результате мы не только получим полный список полей, рассортированный по прототипам, в которых эти поля лежат. Мы еще и сможем увидеть в этом списке те поля, которые были переопределены где-то в __proto__ ближе к исследуемому объекту (тому, с которого мы начали всю процедуру). Таким образом, эта методика дает самую полную информацию о содержимом всех
// Печатаем имя и значение поля, но не смешиваем
// пустую строку и undefined
_global.printField = function(name, value){
if (value == undefined) value = "undefined";
trace(name + ": " + value)
}
// Печатаем все, что доступно функции for...in
// Чтобы увидеть скрытые поля, снимаем с них защиту
// Если не предпринять никаких мер, эта функция
// выведет как непосредственные поля и методы объекта,
// так и открытое содержимое его прототипов всех уровней.
_global.printFieldsByForIn = function(obj, str, tempProto){
trace("::::::::::: " + str + " ::::::::::::");
trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
// Снимаем "защиту" со скрытых полей.
ASSetPropFlags(obj, null, 0, 1);
for(var name in obj){
// Принимаем меры для того, чтобы наши действия
// с обнулением __proto__ не отражались на выводимой
// информации.
if (name == "__proto__") printField(name, tempProto);
else printField(name, obj[name]);
}
trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
// В этой рекурсивной функции мы, собственно, и реализуем
// вышеописанный фокус с "отцеплением цепочки" __proto__ .
_global.printAllFields = function(obj, name){
var tempProto = obj.__proto__;
obj.__proto__ = null;
printFieldsByForIn(obj, name, tempProto);
obj.__proto__ = tempProto;
// Проверка на null не нужна: null == undefined, хотя
// отличить их и можно при помощи оператора ===.
if (obj.__proto__ != undefined)
printAllFields(obj.__proto__, name + ".__proto__");
}
// А эта функция просто вызывает основную рабочую функцию и
// добавляет "элементы оформления" (в текстовом виде, разумеется).
_global.dumpObj = function(obj, name){
trace("================================================");
if (name == undefined) name = "<Dumped object>";
printAllFields(obj, name);
trace("================================================");
trace("");
}
// Теперь тестируем функцию dumpObj на разных объектах.
// Сначала на простейшем объекте с одним полем.
myObject = new Object();
myObject.a = 10;
dumpObj(myObject, "myObject");
// Затем тестируем на массиве.
someArray = [1, 2, 4, 7];
dumpObj(someArray, "someArray");
// Далее тестируем на примитивной сроке.
a = "Примитивная строка";
dumpObj(a, "Примитивная строка");
// И, наконец, на системном объекте _root, который
// представляет из себя корневой MovieClip.
dumpObj(_root, "_root");
Выводит этот код очень большое количество текста, но текст этот того стоит - ведь это внутренности разных системных объектов, что может быть интереснее? Вот что выводится в консоль:
========================================================== ::::::::::: myObject :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: a: 10 __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: myObject.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: someArray :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 3: 7 2: 4 1: 2 0: 1 __proto__: constructor: [type Function] length: 4 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: someArray.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: someArray.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: Примитивная строка :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: Примитивная строка.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: substr: [type Function] split: [type Function] substring: [type Function] slice: [type Function] lastIndexOf: [type Function] indexOf: [type Function] concat: [type Function] charCodeAt: [type Function] charAt: [type Function] toLowerCase: [type Function] toUpperCase: [type Function] toString: [type Function] valueOf: [type Function] __proto__: [object Object] constructor: [type Function] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: Примитивная строка.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: _root :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] a: Примитивная строка someArray: 1,2,4,7 myObject: [object Object] __proto__: [object Object] constructor: [type Function] $appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/ $version: WIN 6,0,21,0 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: _root.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: createTextField: [type Function] clear: [type Function] endFill: [type Function] lineStyle: [type Function] curveTo: [type Function] lineTo: [type Function] moveTo: [type Function] beginGradientFill: [type Function] beginFill: [type Function] createEmptyMovieClip: [type Function] stopDrag: [type Function] startDrag: [type Function] removeMovieClip: [type Function] duplicateMovieClip: [type Function] gotoAndStop: [type Function] gotoAndPlay: [type Function] prevFrame: [type Function] nextFrame: [type Function] stop: [type Function] play: [type Function] setMask: [type Function] getDepth: [type Function] attachVideo: [type Function] attachAudio: [type Function] getBytesLoaded: [type Function] getBytesTotal: [type Function] getBounds: [type Function] hitTest: [type Function] globalToLocal: [type Function] localToGlobal: [type Function] swapDepths: [type Function] attachMovie: [type Function] loadMovie: [type Function] loadVariables: [type Function] unloadMovie: [type Function] getURL: [type Function] meth: [type Function] tabIndex: undefined enabled: true useHandCursor: true __proto__: [object Object] constructor: [type Function] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: _root.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] :::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Предоставив вам самостоятельно разглядывать разнообразные недокументированные функции, названия которых у нас сейчас распечатались (наряду со всем остальным), мы дадим все же несколько комментариев. Во-первых, разумеется, необязательно было снимать защиту со всех полей и методов. Если вы будете использовать подобную функцию для распечатки содержимого собственных классов, мы рекомендуем вам закомментировать соответствующую строчку.
Во-вторых, вы можете заметить, что наш фокус с обнулением __proto__ не сработал на клипе _root - в него все равно попали методы из класса Object. (Почему только из Object? Дело в том, что непосредственный прототип объекта _root - это , но снять защиту с его методов мы к моменту вызова dumpObj для _root еще не успели. А с Object защита уже была снята.) В остальных случаях такого не произошло. Отсюда вывод: все дело в том, что _root - слишком важный системный объект, чтобы позволять его поведению зависеть от того, что лежит у него в __proto__. (А, может, дело в том, как именно было удобнее всего реализовать корневой клип.) Во всяком случае, о том, что за объект является прототипом клипа _root, система Flash MX догадывается без обращения к полю _root.__proto__ и поэтому изменение его значения ничего не дает. О том, что это п
оведение не является общим для других клипов, можно узнать, вызвав функцию dumpObj для произвольного клипа. Для этого можно в конец вышеприведенного кода вставить такие строчки:
_root.createEmptyMovieClip("newClip", 1);
dumpObj(newClip, "mc");
Только что раскрытые функции Object и в секции, где выводятся собственные поля и методы клипа newClip, не обнаруживаются.
Вы уже знаете два способа обратиться из apply или call (при этом используется явное имя класса и ссылка prototype ). Можно также добраться до нужных функций прямо из объекта через this.__proto__.__proto__ (а потом опять использовать apply ). Этот способ хуже, поскольку при вызове функции, содержащей такой код, из классов, this.__proto__.__proto__ будет другим.
Но есть в системе Флэш МХ и механизм, который специально предназначен для решения рассматриваемой задачи. Этот механизм программистам, применяющим в своей работе язык Java, прекрасно известен. Основан он на использовании ключевого слова . С помощью этого ключевого слова можно вызвать конструктор . Список аргументов ничем не отличается от списка аргументов любой другой функции. При этом конструктор вызывается не как обычная функция (в которой this означает объект, в котором лежит ссылка на нее), а именно как конструктор ( this указывает на вновь созданный объект). Если же нужно обратиться к какому-либо полю или методу или (что, как правило, применяется при вызове базового варианта переопределяемой виртуальной функции).
Обратите внимание, что - это не просто ссылка, также как и this, поскольку имеет специальное значение в полиморфных классах. Ведь , использованный в методе некоторого класса, всегда будет означать ссылку на его непосредственного предка, даже когда вызов произошел из далекого имеет совсем другое значение). Вы можете представлять себе это так: компилятор Флэш, видя в коде ключевое слово , вставляет на его место нечто вроде ссылки непосредственно на
Последняя особенность "ссылки" вытекает из предыдущих: перед ней не надо писать this.
Мы теперь знаем, что обратиться к методу . Но в длинных не только неудобны - они еще и не работают, поскольку не является полем. Вот пример, доказывающий это.
superclass = function(){}
superclass.prototype = new Object();
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){super.say();}
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
subclass2 = function(){super.say();}
subclass2.prototype = new subclass1();
subclass2.prototype.say = function(text){trace(text +
"subclass2")}
subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text +
"subclass3")}
subclass3.prototype.say2 = function(text){super.say(text);}
subclass3.prototype.say3 = function(text){super.super.say(text);}
subclass3.prototype.say4 = function(text){super.super.super.say(text);}
a = new subclass3();
trace("---------------");
a.say("type = ");
a.say2("type = ");
a.say3("type = ");
a.say4("type = ");
Эта программа выводит следующее:
superclass subclass1 subclass2 --------------- type = subclass3 type = subclass2
Мы видим, что все цепочки типа не сработали. Что же делать? В С++ в таких случаях можно было явно указать имя Function по имени call и apply. В случае, когда мы переопределяем функцию apply, где аргументы не перечисляются друг за другом, а передаются целиком в объекте arguments.В примере, приведенном в начале параграфа, описание класса subclass3 можно сделать таким образом:
subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text +
"subclass3")}
subclass3.prototype.say2 = function(text){subclass2.
prototype.say.apply(this, arguments);}
subclass3.prototype.say3 = function(text){subclass1.
prototype.say.apply(this, arguments);}
subclass3.prototype.say4 = function(text){superclass.
prototype.say.apply(this, arguments);}
На сей раз вывод программы будет таким:
superclass subclass1 subclass2 --------------- type = subclass3 type = subclass2 type = subclass1 type = superclass
То есть теперь все работает как надо.
Чтобы завершить эту тему, скажем еще о том, что вызов конструкторов далеких предков (если они почему-то не были вызваны своими непосредственными потомками) вам тоже придется производить, пользуясь вышеописанным способом.
При анализе двух предыдущих примеров вы могли заметить, что на печать выводится несколько строк, которые мы "не заказывали". В частности, над горизонтальной чертой выводится не только строка subclass2, соответствующая рождению объекта типа subclass3 (в иллюстративных целях мы печатали в конструкторе имя и subclass1. Понятно, откуда они взялись: ведь прототипы мы создавали при помощи оператора new. Значит, при этом отработали конструкторы соответствующих классов. И, с одной стороны, такое поведение представляется логичным. Ведь конструктор предназначен для инициализации объекта; раз мы создаем какой-то объект, его надо инициализировать корректно, а, значит, следует вызвать конструктор. Особенно важным подобное поведение представляется в случае, когда в конструкторе происходит добавление в объект каких-либо новых методов или же делается запрос
системных ресурсов (в случае Флэш это может быть создание одного или нескольких клипов). С другой стороны, мы можем рассматривать прототип всего лишь как хранилище методов класса. В таком случае всякие
В некоторых случаях бывает необходимо проверить, является ли данный объект наследником некоторого класса. Хотя в грамотно спроектированной instanceof. Пример: if (arg instanceof
Оператор instanceof - это один из способов отличить примитивные типы от объектных (второй способ - попробовать завести у объекта данного типа поля). Рассмотрим такой код:
a = new Number(5); b = 5; a.x = 3; b.x = 4; trace(a instanceof Number); trace(b instanceof Number); trace(a.x); trace(b.x);
То есть мы производим одни и те же действия над объектами а и b, причем первый из них - объектного типа, а второй - примитивного. А вот и результат:
true false 3 undefined
Мы видим, что объект b к типу Number не принадлежит и попытка завести у него поля хотя и не приводит к ошибке, но и результата никакого не дает.
Наконец, чтобы отличить переменную, в которой записано примитивное значение 5 от Number(5), мы можем проверить, на что указывает поле __proto__ нашего объекта. В случае Number оно указывает на Number.prototype, в противном случае его значением будет undefined. Эти рассуждения наводят на мысль о том, что функцию, подобную instanceof, можно сделать самостоятельно. И действительно, в
function emulated_instanceof (theObject, theClass){
while ((theObject = theObject.__proto__) != null) {
if (theObject == theClass.prototype) {
return true;
}
}
return false;
}
Давайте разберем этот пример подробнее, поскольку на нем вполне можно проверить, правильно ли вы представляете себе суть прототипной модели и Object, некий произвольный объектный тип. Хотя, конечно, передать туда можно все, что угодно, но ожидает наша функция именно наследника Object. А вот второй аргумент имеет тип Function. Это - функция-конструктор класса, который мы предполагаем theObject (что и будем проверять). Теперь посмотрим на условие остановки цикла. На первый взгляд, всякий объект имеет свой прототип... Всякий - кроме самого первого, то есть Object.prototype! Действительно, оператор trace(Object.prototype.__proto__ == undefined) выдает true (как, впрочем, и операт
ор trace(Object.prototype.__proto__ == null). Таким образом, мы собираемся добраться до самого низа theObject = theObject.__proto__ в условии цикла всякий раз заменяет объект его прототипом. Затем следует проверка - не является ли этот прототип также прототипом некоторого класса? Если да, то все в порядке, это и значит, что наш объект этому классу принадлежит. Если же нет, то следует проверить не сам текущий объект, а уже его прототип, и так далее, пока цепочка не приведет нас к Object 'у. Таким образом, рассматриваемая функция действительно полностью идентична оператору instanceof.
Прототипная модель
Вот совсем простой пример изменения работы
_global.Base = function(a, b){
this.a = a;
this.b = b;
}
_global.Base.prototype.printAll = function(){
for (var name in this){
trace("this." + name + " = " + this[name]);
}
}
_global.Derived = function(a, b, c, d){
super(a, b);
this.c = c;
this.d = d;
}
_global.Derived.prototype = new Base();
b = new Base(10, 20);
d = new Derived(15, 25, 35, 45);
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
_global.Base.prototype.printAll = function(){
trace("Function is obsolete!");
}
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
На выходе получаем:
================ b =============== this.printAll = [type Function] this.b = 20 this.a = 10 ================================== ================ d =============== this.printAll = [type Function] this.d = 45 this.c = 35 this.b = 25 this.a = 15 ================================== ================ b =============== Function is obsolete! ================================== ================ d =============== Function is obsolete! ==================================
Функция действительно заменилась - как в
Чаще всего динамическое изменение Object.
В качестве простого примера мы сейчас внедрим в Object слегка модифицированную функцию printAll(), которая фигурировала у нас в предыдущем подпараграфе. Мы лишь добавим ей аргумент - строку с именем объекта, поля которого она выводит. Вот каким в результате будет код нашего примера:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}
a = [1, 3, 5, 7];
a.printAll("a");
На выходе получим следующее:
a.printAll = [type Function] a.3 = 7 a.2 = 5 a.1 = 3 a.0 = 1
Таким образом, создав совершенно произвольный массив, мы обнаружили в нем функцию, добавленную в класс Object. Согласитесь, что инструмент, который мы только что опробовали, весьма мощный, и открывающиеся перспективы поражают нетренированное воображение. Еще раз отметим, что добавить методы в
А теперь пару слов о "правилах хорошего тона". Мы видим, что при перечислении полей произвольного объекта обнаруживается свежедобавленная функция printAll. Ничего хорошего в этом нет; особенно такие вещи будут раздражать программистов, использующих ваш код, у которых вдруг ваши функции начнут появляться в каждом отладочном дампе полей произвольного объекта. Поэтому следует воспользоваться известной нам недокументированной функцией ASSetPropFlags и спрятать новый метод так же, как спрятаны системные. Правильный код будет вот таким:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}
// Прячем новую функцию от for...in
ASSetPropFlags(Object.prototype, "printAll", 1);
a = [1, 3, 5, 7];
a.printAll("a");
и на выходе мы, разумеется, получим
a.3 = 7 a.2 = 5 a.1 = 3 a.0 = 1
То есть теперь выводятся только необходимые нам данные, без всяких следов самой функции printAll.
Еще один Function. Мы с вами уже сталкивались с этим приемом, когда обсуждали способы создания статических и приватных свойств. Теперь мы рассмотрим еще один пример. Предположим, создавая свои функции, вы заводите у каждой функции-объекта поле name (с тем, чтобы использовать его в отладочных целях). Это удобно ровно до тех пор, пока вы не решите записать ссылку на функцию-объект в поле с другим именем. (Зачем такое может понадобиться? Например, вы хотите, чтобы два ваших класса из разных name у каждой из этих функций-объектов должно быть свое. А ведь мы собрались сделать две ссылки, ссылающиеся на один и тот же объект - понятно, что в таком случае сделать поля name разными будет невозможно. Выходит, объекты нам придется сделать разными. (Но, в конечном счете, должна вызываться одна и та же функция). Конечно, создавать новый объект-функцию при помощи конструкции = function всякий раз вручную вовсе несложно. Тем не менее, вам может показаться более удобным заранее создать в Function.prototype метод clone, который заодно и поле name установит в нужное значение. (Аналогичным образом приходится действовать во многих случаях, когда мы храним какую-либо информацию в полях объектов-функций). Итак, вот код, который реализуем намеченный нами алгоритм.
Function.prototype.clone = function(newName, owner){
// Сохраняем сылку на клонируемую функцию
// для использования внутри функции-клона.
var thisFunc = this;
var newFunc = function(){
// Добавляем аргументы, чтобы поддержать отладочные возможности.
return thisFunc.apply(owner,
arguments.concat(["cloned_func", arguments]));
}
for (var fieldName in this){
newFunc[fieldName] = this[fieldName];
}
newFunc.name = newName;
return newFunc;
}
// Прячем новую функцию от for...in
ASSetPropFlags(Function.prototype, "clone", 1);
// Отладочная функция для определения имени и аргументов
// функции, находящейся в стеке вызовов на 2 уровня выше.
// (Для того, чтобы узнать, что творится на 1 уровень выше
// писать специальную функцию не нужно).
_global.whereWeAre = function(caller_args){
var funcName, argsNum = caller_args.length;
var firstCallerArgs = caller_args;
trace("--------------------------");
// Ищем объект arguments той функции, которая вызвала
// отлаживаемую (внутрь отлаживаемой мы поместим вызов
// функции whereWeAre). Если использовались отладочные
// аргументы, то найти его просто.
if (firstCallerArgs[firstCallerArgs.length - 2] == "caller_args"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];
// Учтем возможность клонирования функций. Мы должны пройти
// по цепочке клонов до самой последней функции-клона,
// имя которой нам и необходимо узнать.
while(firstCallerArgs[firstCallerArgs.length - 2] == "cloned_func"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];
}
// Найдя нужную функцию, запоминаем ее имя,
// а также количество аргументов.
funcName = firstCallerArgs.callee.name + " ";
argsNum = firstCallerArgs.length;
}
else{
// Однако, возможно, что отлаживаемая функция была вызвана
// без отладочных аргументов. В таком случае доступно только
// имя вызывающей функции, но не ее аргументы.
funcName = firstCallerArgs.caller.name;
if (funcName != undefined){
trace("Мы находимся в функции " + funcName);
}
else{
trace("Имя вызывающей функции недоступно.");
}
trace("Агрументы вызывающей функции недоступны.");
trace("==============================================\n");
return;
}
var hasArgs = "с аргументами:";
// Отладочные аргументы не считаются
if (argsNum <= 0) hasArgs = "без аргументов."
trace("Мы находимся в функции " + funcName + hasArgs);
for(var i=0; i<argsNum; i++){
trace("arguments[" + i + "]=
" + firstCallerArgs[i]);
}
trace("==============================================\n");
}
// Теперь тестируем, что у нас получилось.
// Для пробы вызываем whereWeAre просто так.
whereWeAre();
// Эта функция будет играть роль отлаживаемой.
// В нее мы помещаем вызов whereWeAre.
function f(){
trace("Вызвана внутренняя функция f()");
whereWeAre(arguments);
}
// Эта функция будет играть роль внешней.
// Ее имя и аргументы мы будем стараться вывести с помощью
// whereWeAre. И ее же мы будем клонировать.
function g(a, b){
trace("Вызвана внешняя функция");
f(a+1, b+1, "caller_args", arguments);
}
g.name = "g()";
// Клонируем.
g1 = g.clone("g1()", this);
// Вызываем обычную и клонированную функции.
g(1, 2);
g1(3, 4);
// А в этой функции мы проверим, как whereWeAre справится
// с отсутствием отладочных аргументов.
strangeFunc = function(){
trace("Вызвана еще одна функция, в которой не учтены");
trace("наши новые отладочные возможности.");
f();
}
strangeFunc.name = "strangeFunc()";
strangeFunc("Some argument");
Вот что мы получаем, запустив этот код на выполнение:
-------------------------- Имя вызывающей функции недоступно. Агрументы вызывающей функции недоступны. ==================================================== Вызвана внешняя функция Вызвана внутренняя функция f() -------------------------- Мы находимся в функции g() с аргументами: arguments[0]= 1 arguments[1]= 2 ==================================================== Вызвана внешняя функция Вызвана внутренняя функция f() -------------------------- Мы находимся в функции g1() с аргументами: arguments[0]= 3 arguments[1]= 4 ==================================================== Вызвана еще одна функция, в которой не учтены наши новые отладочные возможности. Вызвана внутренняя функция f() -------------------------- Мы находимся в функции strangeFunc() Агрументы вызывающей функции недоступны. ====================================================
Видим, что for...in (иначе, как мы уже говорили, они будут появляться при переборе полей любого объекта этого или
Мы познакомились с тем интересным способом (называемым "прототипным
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Вот обычное наследование
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запустим это код и получим:
constructor of superclass constructor of superclass superclass --------------- type = subclass1
Мы еще раз убедились, что обычное prototype. То есть при создании самого (и этим фактом мы регулярно пользовались). Во-вторых, нам надо добиться, чтобы объект, на который указывает prototype, воспринимался как объект определенного класса ( subclass1.prototype.__proto__ = . Наконец, мы говорили, что выполнение оператора new не только создает новый объект и устанавливает ему свойство __proto__, но и инициализирует новому объекту еще два свойства по имени constructor и __constructor__. Свойство constructor нам сейчас не понадобится, а вот без __constructor__ не работает вызов базового конструктора через . Соответственно, именно на конструктор __constructor__. Итак, модифицируем вышеприведенный код следующим образом:
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Следующие две строки - это и есть альтернативное наследование
subclass1.prototype.__proto__ = superclass.prototype;
subclass1.prototype.__constructor__ = superclass;
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запускаем этот код и получаем:
constructor of superclass superclass --------------- type = subclass1
Этот способ cExtends, что является сокращением от custom extends), которая будет делать то, что мы написали выше. Эту функцию можно положить прямо в прототип класса Function, чтобы его можно было вызвать из любого объекта-функции. Вот как это делается:
Function.prototype.cExtends = function(base){
this.prototype.__proto__ = base.prototype;
this.prototype.__constructor__ = base;
}
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Сокращенная форма альтернативного наследования
subclass1.cExtends(superclass);
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запустив этот код, мы получим то же самое, что и в прошлый раз - функция cExtends замечательным образом сработала. Но нет предела совершенству - можно еще улучшить нашу замечательную функцию cExtends. Во-первых, неплохо бы сделать, чтобы поле __constructor__ было скрыто - так же, как и в том случае, когда его создает оператор new. Для этого надо в cExtends добавить строчку
ASSetPropFlags(this.prototype, "__constructor__", 1);
Для __proto__ ничего такого делать не надо - это свойство уже присутствует (и является скрытым) в prototype с самого начала. Во-вторых, вы, наверное, помните, что в прошлой лекции мы обсуждали, как сделать статические (принадлежащие целому классу) свойства. При этом свойства у нас хранились в полях функции-конструктора класса. Можно ли сделать эти статические свойства наследуемыми? Оказывается, да, и для этого функцию cExtends нужно модифицировать следующим образом:
Function.prototype.cExtends = function(base){
this.prototype.__proto__ = base.prototype;
this.prototype.__constructor__ = base;
ASSetPropFlags(this.prototype, "__constructor__", 1);
this.__proto__ = base;
}
В самом деле, после этой модификации функция-объект, для которой был вызван метод cExtends, становится как бы наследником функции-объекта, являющейся конструктором
Приведенный только что вариант функции довольно романтическую историю изобретения __proto__ - невозможность вызвать базовый конструктор через - затыкается с помощью недокументированной ссылки __constructor__. После этого подвига даже выдвигаласть идея предложить Эдвардсу пост магистра Йоды (невзирая на то, что Эдвардс наверняка не подходит, ибо настоящий Йода должен быть исключительно мал ростом и очень уродлив). Так или иначе, альянс одержал убедительную победу.
Однако, похоже, что империя нанесла ответный удар. Сайт FlashСoders закрыт (а ведь на него указывали едва ли не все ссылки Интернета, касающиеся недокументированных возможностей Флэш). Закрыты и остальные сайты подобного плана (например сайт, с которого можно было скачать
Еще остался список рассылки Flashcoders, подписаться на который можно здесь: , а вот здесь находится его архив переписки.
Так что не будем унывать - во-первых, мы-то с вами знаем все, что нужно, дабы устраивать всякие необычные фокусы во Флэше (без них настоящему программисту и жизнь не мила)! Во-вторых, документированных возможностей гораздо больше, чем недокументированных, так что потери не так уж велики. В-третьих, вы, наверное, в курсе, чем закончились "Звездные войны". Победа будет за нами!
Зачем нужны подобные изощрения? Мы отдаем себе отчет, что применять то, что описано в этом параграфе (а, возможно, и в предыдущем), вам, скорее всего, не придется. И при первом чтении параграф этот вполне можно пропустить. Тем не менее, упустить шанс показать, какие проблемы нас ждут при попытке реализовать
Давайте возьмем кусочек приведенного в этой лекции примера new Object() на new Number(5) (число 5 здесь, очевидно, выбрано случайно). Посмотрим на две вещи: работает ли Number. Пишем следующий код:
superclass = function(){}
superclass.prototype = new Number(5);
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){super.say();}
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
trace("\nНаследование работает. Теперь тестируем, ");
trace("сохранились ли у наследников свойства Number.\n");
x = new superclass();
trace("x + 4 = " + (x + 4));
trace("x = " + x);
trace("Теперь выводим просто х с помощью trace(x)");
trace(x);
trace("\nА теперь смотрим, как ведет себя x.__proto__");
trace("x.__proto__ = " + x.__proto__);
trace("x.__proto__ + 4 = " + (x.__proto__ + 4));
На выходе немедленно получаем:
superclass --------------- type = subclass1 Наследование работает. Теперь тестируем, сохранились ли у наследников свойства Number. x + 4 = 4 x = Теперь выводим просто х с помощью trace(x) [type Object] А теперь смотрим, как ведет себя x.__proto__ x.__proto__ = 5 x.__proto__ + 4 = 9
Видим, что Number у наследников никак не проявляются. (На всякий случай мы проверили, что у прототипа все в порядке с этими свойствами). Заметьте, что для Number при помощи new. Только так мы можем получить не read-only объект типа Number, в который можно добавить функции.
Для начала, конечно, неплохо бы сообразить, что может нам дать подобное Math. Так что действительно здесь уместно
Иерархия у нас будет такая: базовый объект, представляющий собой двуместный оператор (или выражение из двух частей, связанное этим оператором). От него, при помощи конкретизации вида оператора будут произведены наследники (в данном случае - выражения со сложением и с делением). И уже от них - конкретные классы-функции, в которых заданы первая и вторая части выражения.
Нужно заметить, что нас поджидают две сложности. Первая: оператор new не может создать новый объект типа Function. К счастью, вместо него можно использовать оператор function, создающий функции "на лету". Вторая сложность: до __proto__, даже правильно выстроенная, нам здесь не поможет: она обеспечивает работу this, но сама ссылка this внутри функции указывает не на объект этой функции, а на объект, к которому она принадлежит. Так что ссылку на производную функцию-объект в базовую нужно передавать "вручную". К счастью, этот механизм мы можем встроить в момент создания нового объекта в функции newFunc, которая в этой программе будет выполнять роль оператора new в пр
именении к функциям.
Вот код, который реализует изложенные выше идеи:
/* ==================================================== */
/* === Функция для создания новых объектов-функций ====*/
/* ==================================================== */
// Функция, аналогичная оператору new
// Передавать второй аргумент необязательно,
// это делается здесь только в отладочных целях
_global.newFunc = function(somefunc, name){
var f = function(){
// Мы должны передать исходный объект
// (ведь у нас нет здесь полиморфного this)
var newArgs = arguments;
// Проверяем, что мы в самом дальнем подклассе
if (arguments[arguments.length - 1] !=
"Derived class arg passed")
// И передаем в аргументах ссылку на себя
// и информацию об этом
newArgs = arguments.concat(
arguments.callee, "Derived class arg passed"
);
// При реальном использовании надо убрать эти
// отладочные вызовы
trace("------------");
trace("function: " + arguments.callee.name);
trace("arguments = " + arguments);
trace("newArgs = " + newArgs);
trace("------------");
// Вызываем исходную функцию для нашего объекта
return somefunc.apply(this, newArgs);
}
// Копируем все поля и подобъекты, которые есть у нашей
// функции (это тоже могут быть функции)
copyObj(f, somefunc);
// В отладочных целях присваиваем строковое поле с именем
if (name != undefined) f.name = name;
return f;
}
/* ==================================================== */
/* ========= Важная утилита - функция копирования ===== */
/* ===================================================== */
// Усовершенствованная функция рекурсивного копирования объектов
_global.copyObj = function(toObj, fromObj){
for (var fieldName in fromObj){
var type = typeof(fromObj[fieldName]);
// Ссылки на объекты требуют копирования этих объетов
if (type == "object" ){ // клипы игнорируем
if ((fromObj[fieldName]) instanceof Array){
// Если это массив, его надо скопировать с помощью slice
toObj[fieldName] = fromObj[fieldName].slice(
0, fromObj[fieldName].length
);
}
else{ // Иначе вызываем copyObj рекурсивно
toObj[fieldName] = {};
copyObj(toObj[fieldName], fromObj[fieldName]);
}
}
else if (type == "function"){
// Объекты-функции копируем отдельно
toObj[fieldName] = newFunc(fromObj[fieldName]);
}
else{ // Ссылки на примитивные объекты
toObj[fieldName] = fromObj[fieldName];
}
}
}
/* ==================================================== */
/* ========= Определяем базовую класс-функцию ========= */
/* ==================================================== */
// Исходная ("базовая") функция-объект
_global.twoExprFunc = function(x, funcThis, derPassed){
// Выводим отладочную информацию о том, куда мы попали
trace("function: " + arguments.callee.name);
trace("arguments = " + arguments);
trace("Original object: = " + funcThis.name);
// Исходный объект подкласса. Если сюда не передали
// информацию о производном классе-функции, для которого
// функция вызвана, значит она вызвана сама по себе.
if (funcThis == undefined) var funcThis = arguments.callee;
// Вместо this, который в функциях работает в расчете
// на обычные объекты, используем полученный в аргументах
// указатель funcThis - указатель на реальный подкласс,
// для которого внешний код произвел вызов функции.
return funcThis.resFunc(funcThis.firstExpr(x), funcThis.secondExpr(x));
}
// Устанавливаем отладочную строчку с именем вручную, поскольку
// эту функцию мы не создавали с помощью newFunc
twoExprFunc.name = "twoExprFunc";
// Методы базового объекта-функции
twoExprFunc.setResFunction = function(resFunc){
trace("********** settingResFunction: " + resFunc.name + " ***********");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace("");
this.resFunc = resFunc;
}
// Строчку с именем ставим вручную
twoExprFunc.setResFunction.name = "setResFunction";
twoExprFunc.setExpressionsFuncs = function(firstExpr, secondExpr){
trace("************* settingExpressions **************");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace("");
this.firstExpr = firstExpr;
this.secondExpr = secondExpr;
}
// Строчку с именем ставим вручную
twoExprFunc.setExpressionsFuncs.name = "setExpressionsFuncs";
/* ==================================================== */
/* ========= Определяем производные класс-функции ===== */
/* ===================================================== */
plusFunc = newFunc(twoExprFunc, "plusFunc");
var resFuncPlus = function(a, b){
return a + b;
}
// Строчку с именем ставим вручную
resFuncPlus.name = "resFuncPlus";
plusFunc.setResFunction(resFuncPlus);
divFunc = newFunc(twoExprFunc, "divideFunc");
var resFuncDiv = function(a, b){
return a / b;
}
// Строчку с именем ставим вручную
resFuncDiv.name = "resFuncDiv";
divFunc.setResFunction(resFuncDiv);
particularFunc = newFunc(plusFunc, "particularFunc");
particularFunc.setExpressionsFuncs(Math.round, Math.sin);
// В результате particularFunc вычисляет sin(x) + round(x)
anotherFunc = newFunc(divFunc, "anotherFunc");
anotherFunc.setExpressionsFuncs(Math.abs, function(x)
{return x*x*x});
// В результате anotherFunc вычисляет sign(x)/x^2, где sign(x)
// дает 1 для положительных чисел и -1 - для отрицательных.
// В случае x == 0 наша anotherFunc не определена (NaN).
/* ==================================================== */
/* =========== Проверка работоспособности ============= */
/* ==================================================== */
trace(
":::::::::::::::::::: particularFunc(Math.PI/2) = " +
particularFunc(Math.PI/2) + " ::::::::::::::::::::"
); // Аргумент здесь чуть больше полутора, синус его равен 1,
// так что в результате должно получиться 3.
trace("");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace(
":::::::::::::::::::: anotherFunc(-2) = " +
anotherFunc(-2) + " ::::::::::::::::::::"
); // Должно получиться -1/4.
Запускаем этот код. Он выводит в консоль довольно много отладочной информации, попробуем в ней разобраться. Вот содержимое консоли после запуска:
------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncPlus *********** ------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncDiv *********** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: particularFunc arguments = 1.5707963267949 newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ ------------ function: plusFunc arguments = 1.5707963267949,[type Function],Derived class arg passed newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = 1.5707963267949,[type Function],Derived class arg passed Original object: = particularFunc ------------ function: resFuncPlus arguments = 2,1 newArgs = 2,1,[type Function],Derived class arg passed ------------ :::::::::::::::::::: particularFunc(Math.PI/2) = 3 :::::::::::::::::::: ------------ function: anotherFunc arguments = -2 newArgs = -2,[type Function],Derived class arg passed ------------ ------------ function: divideFunc arguments = -2,[type Function],Derived class arg passed newArgs = -2,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = -2,[type Function],Derived class arg passed Original object: = anotherFunc ------------ function: resFuncDiv arguments = 2,-8 newArgs = 2,-8,[type Function],Derived class arg passed ------------ :::::::::::::::::::: anotherFunc(-2) = -0.25 ::::::::::::::::::::
Разбирать содержимое консоли начнем с конца. Видим, что нужный результат для anotherFunc получен, и что были последовательно совершены вызовы основной функции, начиная от объекта-функции anotherFunc, через divideFunc и заканчивая twoExprFunc. Та в свою очередь вызвала определенный в divideFunc метод resFunc (а именно, конкретную функцию resFuncDiv ). Причем, поскольку этот метод вызывался на самом деле через самого последнего наследника, то есть через anotherFunc, то была вызвана копия, сделанная при помощи newFunc (отсюда и отладочная информация), а эта копия уже, в свою очередь, вызвала оригинальный метод.
Примерно то же самое произошло и при вызове particularFunc, который тоже дал правильное значение. Заметим, что вызовы setResFunction и setExpressionsFuncs также предваряются выводом большого количества отладочной информации, так как эти методы были изначально заведены еще в самом newFunc.
Естественно, когда мы сталкиваемся с таким большим числом скопированных функций, сразу возникает вопрос, который мы вынесли в заголовок следующего подпараграфа.
То есть не копировать поля, а просто выстроить __proto__? Ответ на этот вопрос такой: можно, но осторожно. При использовании newFunc вызов copyObj(f, somefunc); заменить на f.__proto__ = somefunc; после чего на выходе получим:
********** settingResFunction: resFuncPlus *********** ********** settingResFunction: resFuncDiv *********** ************* settingExpressions ************** ************* settingExpressions ************** ------------ function: particularFunc arguments = 1.5707963267949 newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ ------------ function: plusFunc arguments = 1.5707963267949,[type Function],Derived class arg passed newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = 1.5707963267949,[type Function],Derived class arg passed Original object: = particularFunc :::::::::::::::::::: particularFunc(Math.PI/2) = 3 :::::::::::::::::::: ------------ function: anotherFunc arguments = -2 newArgs = -2,[type Function],Derived class arg passed ------------ ------------ function: divideFunc arguments = -2,[type Function],Derived class arg passed newArgs = -2,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = -2,[type Function],Derived class arg passed Original object: = anotherFunc :::::::::::::::::::: anotherFunc(-2) = -0.25 ::::::::::::::::::::
То есть все сработало правильно; видно также, что методы объектов-функций (такие, как setResFunction и setExpressionsFuncs ) не копировались при помощи newFunc и в результате не выводят про своем вызове отладочную информацию.
Конечно, осталось еще довольно много работы. Нужно организовать хранение конструктора и его вызов при вызове newFunc (в нашем примере мы не пользовались конструкторами). Хорошо бы переписать наш пример с тем, чтобы функции-объекты (которые подвержены изменениям) создавать в конструкторе при помощи newFunc, тогда можно будет и разветвленные выражения с изменяемыми частями создавать при помощи rebel - . Однако все подобные вещи мы уже проделывали в предыдущих параграфах этой лекции. Так что наведение глянца предоставляется читателю в качестве упражнения. А мы переходим к дальнейшим "фокусам".
И этот параграф при первом чтении тоже вполне можно пропустить. Нельзя сказать, что он полностью оправдывает свой заголовок. Во-первых, мы долго размышляли над уместностью в названии буквы "р". Во-вторых, содержимое сего параграфа никак не сложнее, скажем, предыдущего; однако, в отличие от него, не несет в себе уж совсем никакой практической пользы. Все, что вы можете от него получить, - это острые программистские ощущения и (ложную) уверенность, что уж теперь-то вы понимаете все детали работы Флэш МХ. Но уж острые ощущения будут что надо, поверьте!
Мы много раз использовали словосочетание "контекст вызова функции" (по-английски его также называют activation object, то есть "объект активации"). Мы даже убедились, что это действительно единый объект, доступный во вложенных функциях через this. Давайте же исследуем его - как исследовали мы другие системные объекты! А заодно - и все остальное, что попадется по дороге. Для этого мы применим вот такой код:
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
o1 = {k:10, l:20};
a1 = [5, 6, 7];
trace("******* a1 ********");
dumpObj(a1);
trace("*******************");
trace("");
var f = function(){
trace("---- arguments -----");
dumpObj(arguments);
trace("--------------");
var x = 5;
var y = "Текстовая локальная переменная";
var o2 = {k:15, l:25};
var getActivation = function(){
return this;
}
return getActivation();
}
trace("Снаружи this = " + this);
trace("");
trace("==== Контекст вызова f ====");
dumpObj(f(1000, [1, 2, 3]));
trace("===========================");
trace("");
trace(":::::::: _level0 ::::::::");
dumpObj(this);
trace("::::::::::::::::::::::::::");
Посмотрим, как этот код справляется с нашей основной задачей: разобраться, что скрывается внутри контекста вызова функции. Контекст вызова f доступен при вызове getActivation через this. (Здесь важно, что ссылку getActivation мы пометили как var ).
Обратите внимание, что на функции, сгенерированные внутри кадра какого-то клипа (в данном случае, _level0 ), не распространяется это правило. То есть в них this также указывает на _level0, а не на какой-то "контекст вызова кадра". Чтобы это подчеркнуть, мы перед f нарочно поставили var.
Так вот, далее getActivation возвращает ссылку на контекст в саму функцию f, которая, в свою очередь, возвращает его в вызывающее f выражение. То есть мы в очередной раз можем проверить, что контекст вызова не перестает существовать после того, как функция отработала, если на него остались какие-то ссылки. Посмотрим, что получается на выходе:
******* a1 ******** 2: 7 1: 6 0: 5 __proto__: constructor: [type Function] length: 3 ******************* Снаружи this = _level0 ==== Контекст вызова f ==== ---- arguments ----- 1: 1,2,3 0: 1000 caller: null callee: [type Function] __proto__: constructor: [type Function] length: 2 -------------- getActivation: [type Function] o2: [object Object] y: Текстовая локальная переменная x: 5 arguments: 1000,1,2,3 this: _level0 =========================== :::::::: _level0 :::::::: f: [type Function] name: name __proto__: [object Object] constructor: [type Function] a1: 5,6,7 o1: [object Object] $appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/ $version: WIN 6,0,21,0 ::::::::::::::::::::::::::
Мы видим, что в контексте вызова функции хранятся все локальные переменные, а также ссылки на arguments и this. Также видно, что объект arguments отличается от обычного массива (для примера и проверки работы функции dumpObj выведены "внутренности" массива a1 ) только полями и . Интересен также тот факт, что у объекта "контекст вызова" отсутствуют поля constructor и __proto__. Это наводит на мысль о некоторых трюках, которые мы испробуем в следующем подпараграфе. Напоследок давайте посмотрим, какие из увиденных нами полей являются открытыми, а какие мы видим только благодаря применению недокументированной функции ASSetPropFlags. Закомментируем ее вызов в функции dumpObj, и тогда на выходе получим следующее:
******* a1 ******** 2: 7 1: 6 0: 5 ******************* Снаружи this = _level0 ==== Контекст вызова f ==== ---- arguments ----- 1: 1,2,3 0: 1000 -------------- getActivation: [type Function] o2: [object Object] y: Текстовая локальная переменная x: 5 =========================== :::::::: _level0 :::::::: f: [type Function] name: name a1: 5,6,7 o1: [object Object] $version: WIN 6,0,21,0 ::::::::::::::::::::::::::
Примечательно, что в объекте arguments мы не видим теперь ничего, кроме самих аргументов, а в контексте вызова видим только локальные переменные (но не видим arguments и this ).
Сейчас мы видели, что контекст вызова функции - это объект, не являющийся экземпляром какого-либо класса. Об этом говорит отсутствие ссылки __proto__. То есть у объекта нет прототипа и поля у него заведены только те, что нужны в данном конкретном случае. (Можно, конечно вспомнить про повторяющиеся, хоть и скрытые, поля arguments и this. Но факт остается фактом - эти поля тоже берутся не из прототипа, а заводятся каждый раз заново.) Однако мы уже знаем, что при __proto__. Возникает соблазн проделать то же самое и с контекстом вызова. В результате поля того объекта, на который будет указывать __proto__, будут восприниматься так же, как собственные поля контекста вызова. То есть ... как
локальные переменные! Это настолько неожиданный вывод, что хочется немедленно его проверить.
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
o1 = {k:10, l:20};
var f = function(){
var x = 5;
var y = "Текстовая локальная переменная";
var o2 = {k:15, l:25};
var getActivation = function(){
return this;
}
var act = getActivation();
act.__proto__ = o1; // Или var.__proto__ = o1;
trace("===================");
trace("k = " + k);
trace("l = " + l);
trace("===================");
k = 145;
var l = 1111;
trace("===================");
trace("k = " + k);
trace("l = " + l);
trace("===================");
return getActivation();
}
trace("==== Контекст вызова f ====");
dumpObj(f(1000, [1, 2, 3]));
trace("===========================");
trace("");
trace("******* o1 ********");
dumpObj(o1);
trace("*******************");
trace("");
trace(":::::::: _level0 ::::::::");
dumpObj(this);
trace("::::::::::::::::::::::::::");
На выходе получаем:
==== Контекст вызова f ====
===================
k = 10
l = 20
===================
===================
k = 10
l = 1111
===================
k: 10
l: 1111
__proto__: [object Object]
act: [object Object]
getActivation: [type Function]
o2: [object Object]
y: Текстовая локальная переменная
x: 5
arguments: 1000,1,2,3
this: _level0
===========================
******* o1 ********
k: 10
l: 20
__proto__: [object Object]
constructor: [type Function]
*******************
:::::::: _level0 ::::::::
name: name
__proto__: [object Object]
constructor: [type Function]
k: 145
f: [type Function]
o1: [object Object]
$appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/
$version: WIN 6,0,21,0
::::::::::::::::::::::::::
Мы видим удивительные (хотя и ожидавшиеся нами) вещи! Действительно, переменные k и l воспринимаются внутри функции (после того, как контексту указали __proto__ ) как локальные! Но более того, они еще и являются чем-то вроде константных локальных переменных! Наша попытка изменить переменную k привела к тому, что была заведена соответствующая переменная в _level0. (Поскольку содержимое объекта, на который указывает __proto__, не меняется, то должна быть заведена новая переменная; но поскольку ключевое слово var не было применено, то переменная заводится в объекте, из которого была вызвана функция f.) Поскольку приоритет в функции имеет локальная переменная, то значение k как бы и не изменилось. В случае же переменной l, когда мы использовали var, значение l в функции, конечно, было заменено. Но в объекте o1 оно, разумеется, осталось п
режним (так же как и значение k ). Конечно, польза такого необычного применения __proto__ далеко не очевидна. Однако сама возможность подобных фокусов вызывает теплые ностальгические чувства и воспоминания о Бэйсике и ассемблере конца 80-х годов прошлого века. Не будем вдаваться в подробности. У каждого своя ностальгия.
Вот попытка использовать все-таки странное содержимое этого параграфа. В прошлой лекции мы рассказывали о том, как эмулировать во Флэше приватные поля. Если вы пишете класс с нуля, - вы без всяких проблем сможете использовать этот метод. Но предположим, что вам достался уже готовый класс, а вы хотите сделать его наследника более защищенным. Сможете ли вы это сделать? В голову приходит идея пройтись по прототипу и перегененировать функции, которые мы там найдем, таким образом, чтобы потом вызывать исходные функции при помощи apply и передавать им в качестве объекта, с которым они работают, контекст вызова. (Попробуйте написать код, который делает это. А затем посмотрите, какой есть еще способ решить эту задачу - он описан в следующем подпараграфе.)
Перегенерация функций - это интересная идея. Но как быть, если вы хотите сделать приватными всего одно-два особенно важных поля, и обращаются к этим полям опять-таки один-два метода? Перегенерация всех методов класса будет в таком случае явно избыточной. С другой стороны, как обеспечить этим методам (в процессе перегенерации) доступ к остальному содержимому класса? Ведь вызывать мы будем эти методы при помощи apply, передавая им контекст вызова конструктора. Но на этот раз мы не собираемся в контекст конструктора копировать все нужные нам ссылки. Выход из этого положения как раз может подсказать найденный нами недавно фокус с заданием ссылки __proto__ для контекста вызова! Установим __proto__ контекста вызова ссылающимся на наш объект. Тогда все значения переменных из объекта будут доступны для рассматриваемой функции. Правда - и здесь нужно быть аккуратным - эта функция не сможет их изменить (точнее, измененные значения останутся в контексте вызова конструкт
ора, а не в самом объекте). Так что если менять значение публичного поля все-таки надо, следует или воспользоваться функцией- setter 'ом этого поля (если нужно, то создать его), или же сделать это поле приватным и перегенерировать еще и те функции, которые к нему обращаются. Первый способ, конечно, предпочтительнее.
Сейчас мы приведем пример использования предлагаемого метода. Воспользуемся уже знакомым нам замечательным лифтом. Предположим, мы добавили возможность внешнему контроллеру управлять этим лифтом. Контроллер сможет открывать и закрывать двери, а также останавливать лифт, если надо. Условимся, что изменение соответствующей переменной будет означать изменение состояния лифта. То есть, если мы установили переменную doorsAreOpen в true, то это означает, что двери открылись. Посмотрите на новый вариант кода лифта:
// ================ Базовый класс лифта - модифицирован =================
// Базовый конструктор - добавлено поле moving
_global.lift = function (minFloor, maxFloor){
// Проверяем, не перепутал ли пользователь максимальный и
// минимальный этажи
if (minFloor < maxFloor){
this.minFloor = Math.round(minFloor);
this.maxFloor = Math.round(maxFloor);
}
else{ // Если перепутал, корректируем
this.minFloor = Math.round(maxFloor);
this.maxFloor = Math.round(minFloor);
}
// Устанавливаем лифт на 1 этаж
// (но если лифт туда не должен ездить, корректируем
// начальный этаж в нужную сторону).
this.currentFloor = Math.min(Math.max(1, this.minFloor),
this.maxFloor);
this.doorsAreOpen = true;
// Новое поле
this.moving = false;
}
// Функция goto - без изменений
_global.lift.prototype.goto = function(where){
trace("-----------");
// Oкругляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where), this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();
// Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
// Уделяем повышенное внимание безопасности
_global.lift.prototype.setDoorsOpen = function(open){
// Если мы едем, двери трогать нельзя
if (this.moving) return;
if (this.doorsAreOpen != open){
trace("Осторожно!");
trace(open ? "Открываем двери." : "Закрываем двери.");
}
this.doorsAreOpen = open;
}
// Добавлена регистрация состояний движения/неподвижности,
// а также добавлено взаимодействие с внешним контроллером
_global.lift.prototype.go = function(){
// Осторожно, двери закрываются
this.setDoorsOpen(false);
this.moving = true;
// Поехали!
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
// Едем и выводим об этом сообщение
trace("Этаж " + i);
this.currentFloor = i;
// Проехали один этаж, сообщаем об этом контроллеру
externalController.work(this);
// А вдруг контроллер остановил лифт?
if (!this.moving) break;
}
// Приехали
this.moving = false;
this.setDoorsOpen(true);
}
Сразу видно, что с точки зрения безопасности движения у нас возникают проблемы. Контроллер может открыть двери в обход функции setDoorsOpen (которая теперь проверяет, движется ли лифт). Вот код контроллера со всевозможными вариантами потенциально опасных действий:
//================ Контроллер лифта ===========================
externalController = {};
externalController.work = function(liftToControl){
this.liftToControl = liftToControl;
if (liftToControl.currentFloor == 3) this.doEvilThing();
}
externalController.openLiftDoors1 = function(){
this.liftToControl.doorsAreOpen = true;
}
externalController.openLiftDoors2 = function(){
this.liftToControl.setDoorsOpen(true);
}
externalController.openLiftDoors3 = function(){
this.liftToControl.moving = false;
this.liftToControl.setDoorsOpen(true);
}
externalController.doEvilThing = externalController.openLiftDoors1;
Давайте теперь напишем класс safeLift, который будет наследником класса lift, избавленным от этих проблем.
//================== Безопасный лифт =================================
_global.safeLift = function(minFloor, maxFloor){
super(minFloor, maxFloor);
var doorsAreOpen = true;
var getActivationObject = function(){
return this;
}
var activationObject = getActivationObject();
activationObject.__proto__ = this;
// Или var __proto__ = this;
var setDoorsOpen = function(){
return this.__proto__.setDoorsOpen.apply(activationObject,
arguments);
}
// Переопределяем старую функцию
this.setDoorsOpen = setDoorsOpen;
}
_global.safeLift.prototype = new _global.lift(0, 1);
Тестировочный код будет у нас выглядеть следующим образом:
//=========================== Тестовая часть =============================
l1 = new lift(1, 5);
l2 = new safeLift(1, 5);
// Тестируем
trace("l1:");
l1.goto(10);
trace("l2:")
l2.goto(10);
externalController.doEvilThing = externalController.openLiftDoors2;
trace("l1:");
l1.goto(0);
trace("l2:")
l2.goto(0);
externalController.doEvilThing = externalController.openLiftDoors3;
trace("l1:");
l1.goto(10);
trace("l2:")
l2.goto(10);
Поместив в кадр _level0 все вышеприведенные кусочки кода и запустив эту программку, мы получим:
l1: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Осторожно! Открываем двери. ----------- l1: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Этаж 2 Этаж 1 Осторожно! Открываем двери. ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Этаж 2 Этаж 1 Осторожно! Открываем двери. ----------- l1: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Осторожно! Открываем двери. ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Осторожно! Открываем двери. -----------
Посмотрим, что у нас произошло. При первоначальном движении вверх у лифта номер 1 возникли проблемы. Контроллер открыл двери на 3-м этаже, при этом лифт не выдал никакого предупреждения и не остановился (о том, что двери открылись, можно судить по отсутствию сообщений об открывании дверей, когда лифт приехал на 5-й этаж). При движении вниз оба лифта отвергли попытку контроллера открыть двери с помощью setDoorsOpen, поскольку переменная moving была установлена в true. Наконец, при обратном движении наверх оба лифта остановились на 3-м этаже, поскольку контроллер установил moving в false. Итак, safeLift оказался полностью защищенным от некорректных действий контроллера.
Отметим, что строчка activationObject.__proto__ = this; в конструкторе безопасного лифта является обязательной. Попробуем закомментировать ее, и при движении лифта вниз (когда контроллер открывает двери при помощи setDoorsOpen ) мы получим:
l2: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Осторожно! Открываем двери. Этаж 2 Этаж 1 -----------
То есть в этом случае функция setDoorsOpen проигнорировала переменную moving, что неудивительно - она просто не смогла ее найти в контексте вызова конструктора, который мы передали ей в качестве this. И только запись activationObject.__proto__ = this; спасает положение.
Заметим, что вместо activationObject.__proto__ = this, мы могли также написать var __proto__ = this. Действительно, var как раз позволяет добавлять нам ссылки в контекст вызова, так что и ссылку __proto__ можно добавить эдаким образом. Хотя рассудок протестует против идеи составлять выражение из трех ключевых слов разного вида и оператора присваивания. Но, тем не менее, такая запись тоже работает.
Является ли приведенный здесь код образцом хорошего стиля? Вряд ли. Но в особых случаях (когда при
Мы уже познакомились с процессом создания объектов во Флэш МХ и теперь знаем, что любой объект легко поддается модификации. Но ведь прототипом объекта всегда тоже является какой-то объект! Значит и прототип можно модифицировать. К чему это приводит, мы сейчас увидим.
В предыдущей лекции прототип объекта первоначально был у нас пустым, и каждый метод в него мы добавляли "вручную". Предположим однако, что мы написали
function MyClass(){}
MyClass.prototype = new MyBaseClass();
и затем уже модифицируем имеющийся прототип. В результате многократного применения подобной операции возникает структура, подобная изображенной на рис.7.1.
(рис 7.1)
Вертикальные стрелки символизируют создание нового объекта при помощи вызова оператора new.
Нетрудно убедиться в том, что у нас получилось самое настоящее
Предположим, что мы написали obj.a, то есть запросили значение поля a у объекта obj. Где Флэш будет искать значение этого поля? Сначала, разумеется, он посмотрит среди полей, которые мы непосредственно завели в объекте obj. Если ничего не найдено, то, как мы знаем, Флэш принимается за поиски в прототипе, который он находит с помощью ссылки __proto__. Но поиски в прототипе идут по той же самой схеме, то есть если непосредственно в прототипе искомая ссылка не нашлась, то мы идем в прототип прототипа (относительно исходного объекта это __proto__.__proto__ ) и так далее вплоть до прототипа типа Object (который автоматически попадает в "корень" этой цепочки, даже если у самого
Можно ли как-нибудь узнать, добавлено ли некоторое поле непосредственно в исследуемый объект или же оно найдено где-то в __proto__? Оказывается, можно, хотя и приходится для этого использовать недокументированную функцию Object.hasOwnProperty. Вот код, который демонстрирует все, что нам нужно.
prot = {a: 5, b: 6};
constr = function(){}
constr.prototype = prot;
obj = new constr();
obj.a = 7;
trace("obj.a = " + obj.a);
trace("obj.b = " + obj.b);
trace("Has own property a ? " + obj.hasOwnProperty("a"));
trace("Has own property b ? " + obj.hasOwnProperty("b"));
obj.b = undefined;
delete obj.a;
trace("-------------");
trace("obj.a = " + obj.a);
trace("obj.b = " + obj.b);
trace("Has own property a ? " + obj.hasOwnProperty("a"));
trace("Has own property b ? " + obj.hasOwnProperty("b"));
Вот что получается в результате:
obj.a = 7 obj.b = 6 Has own property a ? true Has own property b ? false ------------- obj.a = 5 obj.b = Has own property a ? false Has own property b ? true
Итак, что же произошло? Мы создали объект prot, который использовали в качестве прототипа объекта типа constr. Собственно говоря, тут еще не было prot - это единичный объект, не описывающий тип. Если бы мы непременно хотели устроить Base, а затем объект этого класса использовать в качестве прототипа класса constr. Впрочем, мы пока что иллюстрируем не constr тот факт, что мы используем эту функцию в качестве конструктора, а то, что эта функция-объект задает класс, нам не очень важно. В конце концов, мы могли обойтись вовсе без конструктора, а написать obj.__proto__ =
prot. Можете убедиться, что этот вариант тоже работает; более подробно мы его разберем, когда будем говорить об
Так или иначе, мы получили объект со своими собственными полями и с полями, унаследованными из прототипа. Мы видим, что функция Object.hasOwnProperty работает так, как ожидалось. Заодно убеждаемся в том, что уничтожить поле и присвоить ему значение undefined - это разные вещи. В последнем случае значения из прототипа скрываются свежеприсвоенным значением undefined.
Итак, мы видим, что имея объект, можно обратиться как к его собственным полям, так и к полям прототипа, полям прототипа прототипа и т.д. Причем, такое обращение может осуществляться как автоматически (при поиске поля или функции), так и вручную при помощи цепочки ссылок вида __proto__.__proto__.__proto__ и т.д. Похоже, мы злоупотребляем здесь выражением "и так далее", но лишь по той причине, что это наилучший способ выражать мысли при разговоре о цепочке прототипов произвольной длины.
Так вот, возникает очевидное желание, во-первых, научиться выводить на печать список полей объектов с указанием того, из какого именно прототипа (или же из самого объекта) взято каждое поле. Во-вторых, поскольку в процессе выполнения нашего первого желания не обойтись без использования оператора 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);
В этом небольшом разделе мы поговорим о том, какие есть удобные способы вывести полную таблицу полей и методов объекта - таблицу, в которой все переменные и функции рассортированы по принадлежности к одному из прототипов в __proto__.
Для системных объектов вывести все их содержимое в правильном порядке довольно просто (поскольку содержимое прототипа всегда скрыто). Алгоритм здесь такой: при помощи for...in выводим поля, непосредственно принадлежащие объекту. Возможно, их надо будет сначала раскрыть при помощи ASSetPropFlags. Затем в качестве текущего объекта выбираем прототип, то есть содержимое ссылки __proto__. И повторяем вышеописанную операцию. Так действуем до тех пор, пока в __proto__ хоть что-нибудь есть (на деле мы всегда упираемся в Object ). C произвольным объектом такая операция не пройдет, поскольку в for...in сразу же обнаружатся поля из прототипов различных уровней вложенности. Можно, конечно, разыскать все эти прототипы заранее и скрыть все присутствующие в них поля и методы. Но если уж применять подобные "силовые приемы", то лучше воспользоваться временным "отцеплением цепочки", о котором мы сейчас расскажем.
Идея этого метода состоит в следующем: мы временно записываем в поле __proto__ значение null (сохранив куда-нибудь старое содержимое поля). В результате в нашем объекте останутся доступными только его собственные поля - мы как бы отключаем все __proto__ к прежнему состоянию, затем в качестве текущего объекта берем тот, на который ссылается __proto__, и повторяем все эти операции уже для него. В результате мы не только получим полный список полей, рассортированный по прототипам, в которых эти поля лежат. Мы еще и сможем увидеть в этом списке те поля, которые были переопределены где-то в __proto__ ближе к исследуемому объекту (тому, с которого мы начали всю процедуру). Таким образом, эта методика дает самую полную информацию о содержимом всех
// Печатаем имя и значение поля, но не смешиваем
// пустую строку и undefined
_global.printField = function(name, value){
if (value == undefined) value = "undefined";
trace(name + ": " + value)
}
// Печатаем все, что доступно функции for...in
// Чтобы увидеть скрытые поля, снимаем с них защиту
// Если не предпринять никаких мер, эта функция
// выведет как непосредственные поля и методы объекта,
// так и открытое содержимое его прототипов всех уровней.
_global.printFieldsByForIn = function(obj, str, tempProto){
trace("::::::::::: " + str + " ::::::::::::");
trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
// Снимаем "защиту" со скрытых полей.
ASSetPropFlags(obj, null, 0, 1);
for(var name in obj){
// Принимаем меры для того, чтобы наши действия
// с обнулением __proto__ не отражались на выводимой
// информации.
if (name == "__proto__") printField(name, tempProto);
else printField(name, obj[name]);
}
trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
// В этой рекурсивной функции мы, собственно, и реализуем
// вышеописанный фокус с "отцеплением цепочки" __proto__ .
_global.printAllFields = function(obj, name){
var tempProto = obj.__proto__;
obj.__proto__ = null;
printFieldsByForIn(obj, name, tempProto);
obj.__proto__ = tempProto;
// Проверка на null не нужна: null == undefined, хотя
// отличить их и можно при помощи оператора ===.
if (obj.__proto__ != undefined)
printAllFields(obj.__proto__, name + ".__proto__");
}
// А эта функция просто вызывает основную рабочую функцию и
// добавляет "элементы оформления" (в текстовом виде, разумеется).
_global.dumpObj = function(obj, name){
trace("================================================");
if (name == undefined) name = "<Dumped object>";
printAllFields(obj, name);
trace("================================================");
trace("");
}
// Теперь тестируем функцию dumpObj на разных объектах.
// Сначала на простейшем объекте с одним полем.
myObject = new Object();
myObject.a = 10;
dumpObj(myObject, "myObject");
// Затем тестируем на массиве.
someArray = [1, 2, 4, 7];
dumpObj(someArray, "someArray");
// Далее тестируем на примитивной сроке.
a = "Примитивная строка";
dumpObj(a, "Примитивная строка");
// И, наконец, на системном объекте _root, который
// представляет из себя корневой MovieClip.
dumpObj(_root, "_root");
Выводит этот код очень большое количество текста, но текст этот того стоит - ведь это внутренности разных системных объектов, что может быть интереснее? Вот что выводится в консоль:
========================================================== ::::::::::: myObject :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: a: 10 __constructor__: [type Function] constructor: [type Function] __proto__: [object Object] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: myObject.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: someArray :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 3: 7 2: 4 1: 2 0: 1 __proto__: constructor: [type Function] length: 4 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: someArray.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 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] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: someArray.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: Примитивная строка :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: Примитивная строка.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: substr: [type Function] split: [type Function] substring: [type Function] slice: [type Function] lastIndexOf: [type Function] indexOf: [type Function] concat: [type Function] charCodeAt: [type Function] charAt: [type Function] toLowerCase: [type Function] toUpperCase: [type Function] toString: [type Function] valueOf: [type Function] __proto__: [object Object] constructor: [type Function] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: Примитивная строка.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] ========================================================== ::::::::::: _root :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] a: Примитивная строка someArray: 1,2,4,7 myObject: [object Object] __proto__: [object Object] constructor: [type Function] $appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/ $version: WIN 6,0,21,0 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: _root.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: createTextField: [type Function] clear: [type Function] endFill: [type Function] lineStyle: [type Function] curveTo: [type Function] lineTo: [type Function] moveTo: [type Function] beginGradientFill: [type Function] beginFill: [type Function] createEmptyMovieClip: [type Function] stopDrag: [type Function] startDrag: [type Function] removeMovieClip: [type Function] duplicateMovieClip: [type Function] gotoAndStop: [type Function] gotoAndPlay: [type Function] prevFrame: [type Function] nextFrame: [type Function] stop: [type Function] play: [type Function] setMask: [type Function] getDepth: [type Function] attachVideo: [type Function] attachAudio: [type Function] getBytesLoaded: [type Function] getBytesTotal: [type Function] getBounds: [type Function] hitTest: [type Function] globalToLocal: [type Function] localToGlobal: [type Function] swapDepths: [type Function] attachMovie: [type Function] loadMovie: [type Function] loadVariables: [type Function] unloadMovie: [type Function] getURL: [type Function] meth: [type Function] tabIndex: undefined enabled: true useHandCursor: true __proto__: [object Object] constructor: [type Function] ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::: _root.__proto__.__proto__ :::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: __proto__: undefined toLocaleString: [type Function] isPropertyEnumerable: [type Function] isPrototypeOf: [type Function] hasOwnProperty: [type Function] toString: [type Function] valueOf: [type Function] addProperty: [type Function] unwatch: [type Function] watch: [type Function] constructor: [type Function] :::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Предоставив вам самостоятельно разглядывать разнообразные недокументированные функции, названия которых у нас сейчас распечатались (наряду со всем остальным), мы дадим все же несколько комментариев. Во-первых, разумеется, необязательно было снимать защиту со всех полей и методов. Если вы будете использовать подобную функцию для распечатки содержимого собственных классов, мы рекомендуем вам закомментировать соответствующую строчку.
Во-вторых, вы можете заметить, что наш фокус с обнулением __proto__ не сработал на клипе _root - в него все равно попали методы из класса Object. (Почему только из Object? Дело в том, что непосредственный прототип объекта _root - это , но снять защиту с его методов мы к моменту вызова dumpObj для _root еще не успели. А с Object защита уже была снята.) В остальных случаях такого не произошло. Отсюда вывод: все дело в том, что _root - слишком важный системный объект, чтобы позволять его поведению зависеть от того, что лежит у него в __proto__. (А, может, дело в том, как именно было удобнее всего реализовать корневой клип.) Во всяком случае, о том, что за объект является прототипом клипа _root, система Flash MX догадывается без обращения к полю _root.__proto__ и поэтому изменение его значения ничего не дает. О том, что это п
оведение не является общим для других клипов, можно узнать, вызвав функцию dumpObj для произвольного клипа. Для этого можно в конец вышеприведенного кода вставить такие строчки:
_root.createEmptyMovieClip("newClip", 1);
dumpObj(newClip, "mc");
Только что раскрытые функции Object и в секции, где выводятся собственные поля и методы клипа newClip, не обнаруживаются.
Вы уже знаете два способа обратиться из apply или call (при этом используется явное имя класса и ссылка prototype ). Можно также добраться до нужных функций прямо из объекта через this.__proto__.__proto__ (а потом опять использовать apply ). Этот способ хуже, поскольку при вызове функции, содержащей такой код, из классов, this.__proto__.__proto__ будет другим.
Но есть в системе Флэш МХ и механизм, который специально предназначен для решения рассматриваемой задачи. Этот механизм программистам, применяющим в своей работе язык Java, прекрасно известен. Основан он на использовании ключевого слова . С помощью этого ключевого слова можно вызвать конструктор . Список аргументов ничем не отличается от списка аргументов любой другой функции. При этом конструктор вызывается не как обычная функция (в которой this означает объект, в котором лежит ссылка на нее), а именно как конструктор ( this указывает на вновь созданный объект). Если же нужно обратиться к какому-либо полю или методу или (что, как правило, применяется при вызове базового варианта переопределяемой виртуальной функции).
Обратите внимание, что - это не просто ссылка, также как и this, поскольку имеет специальное значение в полиморфных классах. Ведь , использованный в методе некоторого класса, всегда будет означать ссылку на его непосредственного предка, даже когда вызов произошел из далекого имеет совсем другое значение). Вы можете представлять себе это так: компилятор Флэш, видя в коде ключевое слово , вставляет на его место нечто вроде ссылки непосредственно на
Последняя особенность "ссылки" вытекает из предыдущих: перед ней не надо писать this.
Мы теперь знаем, что обратиться к методу . Но в длинных не только неудобны - они еще и не работают, поскольку не является полем. Вот пример, доказывающий это.
superclass = function(){}
superclass.prototype = new Object();
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){super.say();}
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
subclass2 = function(){super.say();}
subclass2.prototype = new subclass1();
subclass2.prototype.say = function(text){trace(text +
"subclass2")}
subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text +
"subclass3")}
subclass3.prototype.say2 = function(text){super.say(text);}
subclass3.prototype.say3 = function(text){super.super.say(text);}
subclass3.prototype.say4 = function(text){super.super.super.say(text);}
a = new subclass3();
trace("---------------");
a.say("type = ");
a.say2("type = ");
a.say3("type = ");
a.say4("type = ");
Эта программа выводит следующее:
superclass subclass1 subclass2 --------------- type = subclass3 type = subclass2
Мы видим, что все цепочки типа не сработали. Что же делать? В С++ в таких случаях можно было явно указать имя Function по имени call и apply. В случае, когда мы переопределяем функцию apply, где аргументы не перечисляются друг за другом, а передаются целиком в объекте arguments.В примере, приведенном в начале параграфа, описание класса subclass3 можно сделать таким образом:
subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text +
"subclass3")}
subclass3.prototype.say2 = function(text){subclass2.
prototype.say.apply(this, arguments);}
subclass3.prototype.say3 = function(text){subclass1.
prototype.say.apply(this, arguments);}
subclass3.prototype.say4 = function(text){superclass.
prototype.say.apply(this, arguments);}
На сей раз вывод программы будет таким:
superclass subclass1 subclass2 --------------- type = subclass3 type = subclass2 type = subclass1 type = superclass
То есть теперь все работает как надо.
Чтобы завершить эту тему, скажем еще о том, что вызов конструкторов далеких предков (если они почему-то не были вызваны своими непосредственными потомками) вам тоже придется производить, пользуясь вышеописанным способом.
При анализе двух предыдущих примеров вы могли заметить, что на печать выводится несколько строк, которые мы "не заказывали". В частности, над горизонтальной чертой выводится не только строка subclass2, соответствующая рождению объекта типа subclass3 (в иллюстративных целях мы печатали в конструкторе имя и subclass1. Понятно, откуда они взялись: ведь прототипы мы создавали при помощи оператора new. Значит, при этом отработали конструкторы соответствующих классов. И, с одной стороны, такое поведение представляется логичным. Ведь конструктор предназначен для инициализации объекта; раз мы создаем какой-то объект, его надо инициализировать корректно, а, значит, следует вызвать конструктор. Особенно важным подобное поведение представляется в случае, когда в конструкторе происходит добавление в объект каких-либо новых методов или же делается запрос
системных ресурсов (в случае Флэш это может быть создание одного или нескольких клипов). С другой стороны, мы можем рассматривать прототип всего лишь как хранилище методов класса. В таком случае всякие
В некоторых случаях бывает необходимо проверить, является ли данный объект наследником некоторого класса. Хотя в грамотно спроектированной instanceof. Пример: if (arg instanceof
Оператор instanceof - это один из способов отличить примитивные типы от объектных (второй способ - попробовать завести у объекта данного типа поля). Рассмотрим такой код:
a = new Number(5); b = 5; a.x = 3; b.x = 4; trace(a instanceof Number); trace(b instanceof Number); trace(a.x); trace(b.x);
То есть мы производим одни и те же действия над объектами а и b, причем первый из них - объектного типа, а второй - примитивного. А вот и результат:
true false 3 undefined
Мы видим, что объект b к типу Number не принадлежит и попытка завести у него поля хотя и не приводит к ошибке, но и результата никакого не дает.
Наконец, чтобы отличить переменную, в которой записано примитивное значение 5 от Number(5), мы можем проверить, на что указывает поле __proto__ нашего объекта. В случае Number оно указывает на Number.prototype, в противном случае его значением будет undefined. Эти рассуждения наводят на мысль о том, что функцию, подобную instanceof, можно сделать самостоятельно. И действительно, в
function emulated_instanceof (theObject, theClass){
while ((theObject = theObject.__proto__) != null) {
if (theObject == theClass.prototype) {
return true;
}
}
return false;
}
Давайте разберем этот пример подробнее, поскольку на нем вполне можно проверить, правильно ли вы представляете себе суть прототипной модели и Object, некий произвольный объектный тип. Хотя, конечно, передать туда можно все, что угодно, но ожидает наша функция именно наследника Object. А вот второй аргумент имеет тип Function. Это - функция-конструктор класса, который мы предполагаем theObject (что и будем проверять). Теперь посмотрим на условие остановки цикла. На первый взгляд, всякий объект имеет свой прототип... Всякий - кроме самого первого, то есть Object.prototype! Действительно, оператор trace(Object.prototype.__proto__ == undefined) выдает true (как, впрочем, и операт
ор trace(Object.prototype.__proto__ == null). Таким образом, мы собираемся добраться до самого низа theObject = theObject.__proto__ в условии цикла всякий раз заменяет объект его прототипом. Затем следует проверка - не является ли этот прототип также прототипом некоторого класса? Если да, то все в порядке, это и значит, что наш объект этому классу принадлежит. Если же нет, то следует проверить не сам текущий объект, а уже его прототип, и так далее, пока цепочка не приведет нас к Object 'у. Таким образом, рассматриваемая функция действительно полностью идентична оператору instanceof.
Прототипная модель
Вот совсем простой пример изменения работы
_global.Base = function(a, b){
this.a = a;
this.b = b;
}
_global.Base.prototype.printAll = function(){
for (var name in this){
trace("this." + name + " = " + this[name]);
}
}
_global.Derived = function(a, b, c, d){
super(a, b);
this.c = c;
this.d = d;
}
_global.Derived.prototype = new Base();
b = new Base(10, 20);
d = new Derived(15, 25, 35, 45);
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
_global.Base.prototype.printAll = function(){
trace("Function is obsolete!");
}
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
На выходе получаем:
================ b =============== this.printAll = [type Function] this.b = 20 this.a = 10 ================================== ================ d =============== this.printAll = [type Function] this.d = 45 this.c = 35 this.b = 25 this.a = 15 ================================== ================ b =============== Function is obsolete! ================================== ================ d =============== Function is obsolete! ==================================
Функция действительно заменилась - как в
Чаще всего динамическое изменение Object.
В качестве простого примера мы сейчас внедрим в Object слегка модифицированную функцию printAll(), которая фигурировала у нас в предыдущем подпараграфе. Мы лишь добавим ей аргумент - строку с именем объекта, поля которого она выводит. Вот каким в результате будет код нашего примера:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}
a = [1, 3, 5, 7];
a.printAll("a");
На выходе получим следующее:
a.printAll = [type Function] a.3 = 7 a.2 = 5 a.1 = 3 a.0 = 1
Таким образом, создав совершенно произвольный массив, мы обнаружили в нем функцию, добавленную в класс Object. Согласитесь, что инструмент, который мы только что опробовали, весьма мощный, и открывающиеся перспективы поражают нетренированное воображение. Еще раз отметим, что добавить методы в
А теперь пару слов о "правилах хорошего тона". Мы видим, что при перечислении полей произвольного объекта обнаруживается свежедобавленная функция printAll. Ничего хорошего в этом нет; особенно такие вещи будут раздражать программистов, использующих ваш код, у которых вдруг ваши функции начнут появляться в каждом отладочном дампе полей произвольного объекта. Поэтому следует воспользоваться известной нам недокументированной функцией ASSetPropFlags и спрятать новый метод так же, как спрятаны системные. Правильный код будет вот таким:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}
// Прячем новую функцию от for...in
ASSetPropFlags(Object.prototype, "printAll", 1);
a = [1, 3, 5, 7];
a.printAll("a");
и на выходе мы, разумеется, получим
a.3 = 7 a.2 = 5 a.1 = 3 a.0 = 1
То есть теперь выводятся только необходимые нам данные, без всяких следов самой функции printAll.
Еще один Function. Мы с вами уже сталкивались с этим приемом, когда обсуждали способы создания статических и приватных свойств. Теперь мы рассмотрим еще один пример. Предположим, создавая свои функции, вы заводите у каждой функции-объекта поле name (с тем, чтобы использовать его в отладочных целях). Это удобно ровно до тех пор, пока вы не решите записать ссылку на функцию-объект в поле с другим именем. (Зачем такое может понадобиться? Например, вы хотите, чтобы два ваших класса из разных name у каждой из этих функций-объектов должно быть свое. А ведь мы собрались сделать две ссылки, ссылающиеся на один и тот же объект - понятно, что в таком случае сделать поля name разными будет невозможно. Выходит, объекты нам придется сделать разными. (Но, в конечном счете, должна вызываться одна и та же функция). Конечно, создавать новый объект-функцию при помощи конструкции = function всякий раз вручную вовсе несложно. Тем не менее, вам может показаться более удобным заранее создать в Function.prototype метод clone, который заодно и поле name установит в нужное значение. (Аналогичным образом приходится действовать во многих случаях, когда мы храним какую-либо информацию в полях объектов-функций). Итак, вот код, который реализуем намеченный нами алгоритм.
Function.prototype.clone = function(newName, owner){
// Сохраняем сылку на клонируемую функцию
// для использования внутри функции-клона.
var thisFunc = this;
var newFunc = function(){
// Добавляем аргументы, чтобы поддержать отладочные возможности.
return thisFunc.apply(owner,
arguments.concat(["cloned_func", arguments]));
}
for (var fieldName in this){
newFunc[fieldName] = this[fieldName];
}
newFunc.name = newName;
return newFunc;
}
// Прячем новую функцию от for...in
ASSetPropFlags(Function.prototype, "clone", 1);
// Отладочная функция для определения имени и аргументов
// функции, находящейся в стеке вызовов на 2 уровня выше.
// (Для того, чтобы узнать, что творится на 1 уровень выше
// писать специальную функцию не нужно).
_global.whereWeAre = function(caller_args){
var funcName, argsNum = caller_args.length;
var firstCallerArgs = caller_args;
trace("--------------------------");
// Ищем объект arguments той функции, которая вызвала
// отлаживаемую (внутрь отлаживаемой мы поместим вызов
// функции whereWeAre). Если использовались отладочные
// аргументы, то найти его просто.
if (firstCallerArgs[firstCallerArgs.length - 2] == "caller_args"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];
// Учтем возможность клонирования функций. Мы должны пройти
// по цепочке клонов до самой последней функции-клона,
// имя которой нам и необходимо узнать.
while(firstCallerArgs[firstCallerArgs.length - 2] == "cloned_func"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];
}
// Найдя нужную функцию, запоминаем ее имя,
// а также количество аргументов.
funcName = firstCallerArgs.callee.name + " ";
argsNum = firstCallerArgs.length;
}
else{
// Однако, возможно, что отлаживаемая функция была вызвана
// без отладочных аргументов. В таком случае доступно только
// имя вызывающей функции, но не ее аргументы.
funcName = firstCallerArgs.caller.name;
if (funcName != undefined){
trace("Мы находимся в функции " + funcName);
}
else{
trace("Имя вызывающей функции недоступно.");
}
trace("Агрументы вызывающей функции недоступны.");
trace("==============================================\n");
return;
}
var hasArgs = "с аргументами:";
// Отладочные аргументы не считаются
if (argsNum <= 0) hasArgs = "без аргументов."
trace("Мы находимся в функции " + funcName + hasArgs);
for(var i=0; i<argsNum; i++){
trace("arguments[" + i + "]=
" + firstCallerArgs[i]);
}
trace("==============================================\n");
}
// Теперь тестируем, что у нас получилось.
// Для пробы вызываем whereWeAre просто так.
whereWeAre();
// Эта функция будет играть роль отлаживаемой.
// В нее мы помещаем вызов whereWeAre.
function f(){
trace("Вызвана внутренняя функция f()");
whereWeAre(arguments);
}
// Эта функция будет играть роль внешней.
// Ее имя и аргументы мы будем стараться вывести с помощью
// whereWeAre. И ее же мы будем клонировать.
function g(a, b){
trace("Вызвана внешняя функция");
f(a+1, b+1, "caller_args", arguments);
}
g.name = "g()";
// Клонируем.
g1 = g.clone("g1()", this);
// Вызываем обычную и клонированную функции.
g(1, 2);
g1(3, 4);
// А в этой функции мы проверим, как whereWeAre справится
// с отсутствием отладочных аргументов.
strangeFunc = function(){
trace("Вызвана еще одна функция, в которой не учтены");
trace("наши новые отладочные возможности.");
f();
}
strangeFunc.name = "strangeFunc()";
strangeFunc("Some argument");
Вот что мы получаем, запустив этот код на выполнение:
-------------------------- Имя вызывающей функции недоступно. Агрументы вызывающей функции недоступны. ==================================================== Вызвана внешняя функция Вызвана внутренняя функция f() -------------------------- Мы находимся в функции g() с аргументами: arguments[0]= 1 arguments[1]= 2 ==================================================== Вызвана внешняя функция Вызвана внутренняя функция f() -------------------------- Мы находимся в функции g1() с аргументами: arguments[0]= 3 arguments[1]= 4 ==================================================== Вызвана еще одна функция, в которой не учтены наши новые отладочные возможности. Вызвана внутренняя функция f() -------------------------- Мы находимся в функции strangeFunc() Агрументы вызывающей функции недоступны. ====================================================
Видим, что for...in (иначе, как мы уже говорили, они будут появляться при переборе полей любого объекта этого или
Мы познакомились с тем интересным способом (называемым "прототипным
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Вот обычное наследование
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запустим это код и получим:
constructor of superclass constructor of superclass superclass --------------- type = subclass1
Мы еще раз убедились, что обычное prototype. То есть при создании самого (и этим фактом мы регулярно пользовались). Во-вторых, нам надо добиться, чтобы объект, на который указывает prototype, воспринимался как объект определенного класса ( subclass1.prototype.__proto__ = . Наконец, мы говорили, что выполнение оператора new не только создает новый объект и устанавливает ему свойство __proto__, но и инициализирует новому объекту еще два свойства по имени constructor и __constructor__. Свойство constructor нам сейчас не понадобится, а вот без __constructor__ не работает вызов базового конструктора через . Соответственно, именно на конструктор __constructor__. Итак, модифицируем вышеприведенный код следующим образом:
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Следующие две строки - это и есть альтернативное наследование
subclass1.prototype.__proto__ = superclass.prototype;
subclass1.prototype.__constructor__ = superclass;
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запускаем этот код и получаем:
constructor of superclass superclass --------------- type = subclass1
Этот способ cExtends, что является сокращением от custom extends), которая будет делать то, что мы написали выше. Эту функцию можно положить прямо в прототип класса Function, чтобы его можно было вызвать из любого объекта-функции. Вот как это делается:
Function.prototype.cExtends = function(base){
this.prototype.__proto__ = base.prototype;
this.prototype.__constructor__ = base;
}
superclass = function(){
trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){
super();
super.say();
}
// Сокращенная форма альтернативного наследования
subclass1.cExtends(superclass);
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
Запустив этот код, мы получим то же самое, что и в прошлый раз - функция cExtends замечательным образом сработала. Но нет предела совершенству - можно еще улучшить нашу замечательную функцию cExtends. Во-первых, неплохо бы сделать, чтобы поле __constructor__ было скрыто - так же, как и в том случае, когда его создает оператор new. Для этого надо в cExtends добавить строчку
ASSetPropFlags(this.prototype, "__constructor__", 1);
Для __proto__ ничего такого делать не надо - это свойство уже присутствует (и является скрытым) в prototype с самого начала. Во-вторых, вы, наверное, помните, что в прошлой лекции мы обсуждали, как сделать статические (принадлежащие целому классу) свойства. При этом свойства у нас хранились в полях функции-конструктора класса. Можно ли сделать эти статические свойства наследуемыми? Оказывается, да, и для этого функцию cExtends нужно модифицировать следующим образом:
Function.prototype.cExtends = function(base){
this.prototype.__proto__ = base.prototype;
this.prototype.__constructor__ = base;
ASSetPropFlags(this.prototype, "__constructor__", 1);
this.__proto__ = base;
}
В самом деле, после этой модификации функция-объект, для которой был вызван метод cExtends, становится как бы наследником функции-объекта, являющейся конструктором
Приведенный только что вариант функции довольно романтическую историю изобретения __proto__ - невозможность вызвать базовый конструктор через - затыкается с помощью недокументированной ссылки __constructor__. После этого подвига даже выдвигаласть идея предложить Эдвардсу пост магистра Йоды (невзирая на то, что Эдвардс наверняка не подходит, ибо настоящий Йода должен быть исключительно мал ростом и очень уродлив). Так или иначе, альянс одержал убедительную победу.
Однако, похоже, что империя нанесла ответный удар. Сайт FlashСoders закрыт (а ведь на него указывали едва ли не все ссылки Интернета, касающиеся недокументированных возможностей Флэш). Закрыты и остальные сайты подобного плана (например сайт, с которого можно было скачать
Еще остался список рассылки Flashcoders, подписаться на который можно здесь: , а вот здесь находится его архив переписки.
Так что не будем унывать - во-первых, мы-то с вами знаем все, что нужно, дабы устраивать всякие необычные фокусы во Флэше (без них настоящему программисту и жизнь не мила)! Во-вторых, документированных возможностей гораздо больше, чем недокументированных, так что потери не так уж велики. В-третьих, вы, наверное, в курсе, чем закончились "Звездные войны". Победа будет за нами!
Зачем нужны подобные изощрения? Мы отдаем себе отчет, что применять то, что описано в этом параграфе (а, возможно, и в предыдущем), вам, скорее всего, не придется. И при первом чтении параграф этот вполне можно пропустить. Тем не менее, упустить шанс показать, какие проблемы нас ждут при попытке реализовать
Давайте возьмем кусочек приведенного в этой лекции примера new Object() на new Number(5) (число 5 здесь, очевидно, выбрано случайно). Посмотрим на две вещи: работает ли Number. Пишем следующий код:
superclass = function(){}
superclass.prototype = new Number(5);
superclass.prototype.say = function(text){trace(text +
"superclass")}
subclass1 = function(){super.say();}
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text +
"subclass1")}
a = new subclass1();
trace("---------------");
a.say("type = ");
trace("\nНаследование работает. Теперь тестируем, ");
trace("сохранились ли у наследников свойства Number.\n");
x = new superclass();
trace("x + 4 = " + (x + 4));
trace("x = " + x);
trace("Теперь выводим просто х с помощью trace(x)");
trace(x);
trace("\nА теперь смотрим, как ведет себя x.__proto__");
trace("x.__proto__ = " + x.__proto__);
trace("x.__proto__ + 4 = " + (x.__proto__ + 4));
На выходе немедленно получаем:
superclass --------------- type = subclass1 Наследование работает. Теперь тестируем, сохранились ли у наследников свойства Number. x + 4 = 4 x = Теперь выводим просто х с помощью trace(x) [type Object] А теперь смотрим, как ведет себя x.__proto__ x.__proto__ = 5 x.__proto__ + 4 = 9
Видим, что Number у наследников никак не проявляются. (На всякий случай мы проверили, что у прототипа все в порядке с этими свойствами). Заметьте, что для Number при помощи new. Только так мы можем получить не read-only объект типа Number, в который можно добавить функции.
Для начала, конечно, неплохо бы сообразить, что может нам дать подобное Math. Так что действительно здесь уместно
Иерархия у нас будет такая: базовый объект, представляющий собой двуместный оператор (или выражение из двух частей, связанное этим оператором). От него, при помощи конкретизации вида оператора будут произведены наследники (в данном случае - выражения со сложением и с делением). И уже от них - конкретные классы-функции, в которых заданы первая и вторая части выражения.
Нужно заметить, что нас поджидают две сложности. Первая: оператор new не может создать новый объект типа Function. К счастью, вместо него можно использовать оператор function, создающий функции "на лету". Вторая сложность: до __proto__, даже правильно выстроенная, нам здесь не поможет: она обеспечивает работу this, но сама ссылка this внутри функции указывает не на объект этой функции, а на объект, к которому она принадлежит. Так что ссылку на производную функцию-объект в базовую нужно передавать "вручную". К счастью, этот механизм мы можем встроить в момент создания нового объекта в функции newFunc, которая в этой программе будет выполнять роль оператора new в пр
именении к функциям.
Вот код, который реализует изложенные выше идеи:
/* ==================================================== */
/* === Функция для создания новых объектов-функций ====*/
/* ==================================================== */
// Функция, аналогичная оператору new
// Передавать второй аргумент необязательно,
// это делается здесь только в отладочных целях
_global.newFunc = function(somefunc, name){
var f = function(){
// Мы должны передать исходный объект
// (ведь у нас нет здесь полиморфного this)
var newArgs = arguments;
// Проверяем, что мы в самом дальнем подклассе
if (arguments[arguments.length - 1] !=
"Derived class arg passed")
// И передаем в аргументах ссылку на себя
// и информацию об этом
newArgs = arguments.concat(
arguments.callee, "Derived class arg passed"
);
// При реальном использовании надо убрать эти
// отладочные вызовы
trace("------------");
trace("function: " + arguments.callee.name);
trace("arguments = " + arguments);
trace("newArgs = " + newArgs);
trace("------------");
// Вызываем исходную функцию для нашего объекта
return somefunc.apply(this, newArgs);
}
// Копируем все поля и подобъекты, которые есть у нашей
// функции (это тоже могут быть функции)
copyObj(f, somefunc);
// В отладочных целях присваиваем строковое поле с именем
if (name != undefined) f.name = name;
return f;
}
/* ==================================================== */
/* ========= Важная утилита - функция копирования ===== */
/* ===================================================== */
// Усовершенствованная функция рекурсивного копирования объектов
_global.copyObj = function(toObj, fromObj){
for (var fieldName in fromObj){
var type = typeof(fromObj[fieldName]);
// Ссылки на объекты требуют копирования этих объетов
if (type == "object" ){ // клипы игнорируем
if ((fromObj[fieldName]) instanceof Array){
// Если это массив, его надо скопировать с помощью slice
toObj[fieldName] = fromObj[fieldName].slice(
0, fromObj[fieldName].length
);
}
else{ // Иначе вызываем copyObj рекурсивно
toObj[fieldName] = {};
copyObj(toObj[fieldName], fromObj[fieldName]);
}
}
else if (type == "function"){
// Объекты-функции копируем отдельно
toObj[fieldName] = newFunc(fromObj[fieldName]);
}
else{ // Ссылки на примитивные объекты
toObj[fieldName] = fromObj[fieldName];
}
}
}
/* ==================================================== */
/* ========= Определяем базовую класс-функцию ========= */
/* ==================================================== */
// Исходная ("базовая") функция-объект
_global.twoExprFunc = function(x, funcThis, derPassed){
// Выводим отладочную информацию о том, куда мы попали
trace("function: " + arguments.callee.name);
trace("arguments = " + arguments);
trace("Original object: = " + funcThis.name);
// Исходный объект подкласса. Если сюда не передали
// информацию о производном классе-функции, для которого
// функция вызвана, значит она вызвана сама по себе.
if (funcThis == undefined) var funcThis = arguments.callee;
// Вместо this, который в функциях работает в расчете
// на обычные объекты, используем полученный в аргументах
// указатель funcThis - указатель на реальный подкласс,
// для которого внешний код произвел вызов функции.
return funcThis.resFunc(funcThis.firstExpr(x), funcThis.secondExpr(x));
}
// Устанавливаем отладочную строчку с именем вручную, поскольку
// эту функцию мы не создавали с помощью newFunc
twoExprFunc.name = "twoExprFunc";
// Методы базового объекта-функции
twoExprFunc.setResFunction = function(resFunc){
trace("********** settingResFunction: " + resFunc.name + " ***********");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace("");
this.resFunc = resFunc;
}
// Строчку с именем ставим вручную
twoExprFunc.setResFunction.name = "setResFunction";
twoExprFunc.setExpressionsFuncs = function(firstExpr, secondExpr){
trace("************* settingExpressions **************");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace("");
this.firstExpr = firstExpr;
this.secondExpr = secondExpr;
}
// Строчку с именем ставим вручную
twoExprFunc.setExpressionsFuncs.name = "setExpressionsFuncs";
/* ==================================================== */
/* ========= Определяем производные класс-функции ===== */
/* ===================================================== */
plusFunc = newFunc(twoExprFunc, "plusFunc");
var resFuncPlus = function(a, b){
return a + b;
}
// Строчку с именем ставим вручную
resFuncPlus.name = "resFuncPlus";
plusFunc.setResFunction(resFuncPlus);
divFunc = newFunc(twoExprFunc, "divideFunc");
var resFuncDiv = function(a, b){
return a / b;
}
// Строчку с именем ставим вручную
resFuncDiv.name = "resFuncDiv";
divFunc.setResFunction(resFuncDiv);
particularFunc = newFunc(plusFunc, "particularFunc");
particularFunc.setExpressionsFuncs(Math.round, Math.sin);
// В результате particularFunc вычисляет sin(x) + round(x)
anotherFunc = newFunc(divFunc, "anotherFunc");
anotherFunc.setExpressionsFuncs(Math.abs, function(x)
{return x*x*x});
// В результате anotherFunc вычисляет sign(x)/x^2, где sign(x)
// дает 1 для положительных чисел и -1 - для отрицательных.
// В случае x == 0 наша anotherFunc не определена (NaN).
/* ==================================================== */
/* =========== Проверка работоспособности ============= */
/* ==================================================== */
trace(
":::::::::::::::::::: particularFunc(Math.PI/2) = " +
particularFunc(Math.PI/2) + " ::::::::::::::::::::"
); // Аргумент здесь чуть больше полутора, синус его равен 1,
// так что в результате должно получиться 3.
trace("");
// Выводим пустую строку, чтобы отделить от вывода других функций
trace(
":::::::::::::::::::: anotherFunc(-2) = " +
anotherFunc(-2) + " ::::::::::::::::::::"
); // Должно получиться -1/4.
Запускаем этот код. Он выводит в консоль довольно много отладочной информации, попробуем в ней разобраться. Вот содержимое консоли после запуска:
------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncPlus *********** ------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncDiv *********** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: particularFunc arguments = 1.5707963267949 newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ ------------ function: plusFunc arguments = 1.5707963267949,[type Function],Derived class arg passed newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = 1.5707963267949,[type Function],Derived class arg passed Original object: = particularFunc ------------ function: resFuncPlus arguments = 2,1 newArgs = 2,1,[type Function],Derived class arg passed ------------ :::::::::::::::::::: particularFunc(Math.PI/2) = 3 :::::::::::::::::::: ------------ function: anotherFunc arguments = -2 newArgs = -2,[type Function],Derived class arg passed ------------ ------------ function: divideFunc arguments = -2,[type Function],Derived class arg passed newArgs = -2,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = -2,[type Function],Derived class arg passed Original object: = anotherFunc ------------ function: resFuncDiv arguments = 2,-8 newArgs = 2,-8,[type Function],Derived class arg passed ------------ :::::::::::::::::::: anotherFunc(-2) = -0.25 ::::::::::::::::::::
Разбирать содержимое консоли начнем с конца. Видим, что нужный результат для anotherFunc получен, и что были последовательно совершены вызовы основной функции, начиная от объекта-функции anotherFunc, через divideFunc и заканчивая twoExprFunc. Та в свою очередь вызвала определенный в divideFunc метод resFunc (а именно, конкретную функцию resFuncDiv ). Причем, поскольку этот метод вызывался на самом деле через самого последнего наследника, то есть через anotherFunc, то была вызвана копия, сделанная при помощи newFunc (отсюда и отладочная информация), а эта копия уже, в свою очередь, вызвала оригинальный метод.
Примерно то же самое произошло и при вызове particularFunc, который тоже дал правильное значение. Заметим, что вызовы setResFunction и setExpressionsFuncs также предваряются выводом большого количества отладочной информации, так как эти методы были изначально заведены еще в самом newFunc.
Естественно, когда мы сталкиваемся с таким большим числом скопированных функций, сразу возникает вопрос, который мы вынесли в заголовок следующего подпараграфа.
То есть не копировать поля, а просто выстроить __proto__? Ответ на этот вопрос такой: можно, но осторожно. При использовании newFunc вызов copyObj(f, somefunc); заменить на f.__proto__ = somefunc; после чего на выходе получим:
********** settingResFunction: resFuncPlus *********** ********** settingResFunction: resFuncDiv *********** ************* settingExpressions ************** ************* settingExpressions ************** ------------ function: particularFunc arguments = 1.5707963267949 newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ ------------ function: plusFunc arguments = 1.5707963267949,[type Function],Derived class arg passed newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = 1.5707963267949,[type Function],Derived class arg passed Original object: = particularFunc :::::::::::::::::::: particularFunc(Math.PI/2) = 3 :::::::::::::::::::: ------------ function: anotherFunc arguments = -2 newArgs = -2,[type Function],Derived class arg passed ------------ ------------ function: divideFunc arguments = -2,[type Function],Derived class arg passed newArgs = -2,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = -2,[type Function],Derived class arg passed Original object: = anotherFunc :::::::::::::::::::: anotherFunc(-2) = -0.25 ::::::::::::::::::::
То есть все сработало правильно; видно также, что методы объектов-функций (такие, как setResFunction и setExpressionsFuncs ) не копировались при помощи newFunc и в результате не выводят про своем вызове отладочную информацию.
Конечно, осталось еще довольно много работы. Нужно организовать хранение конструктора и его вызов при вызове newFunc (в нашем примере мы не пользовались конструкторами). Хорошо бы переписать наш пример с тем, чтобы функции-объекты (которые подвержены изменениям) создавать в конструкторе при помощи newFunc, тогда можно будет и разветвленные выражения с изменяемыми частями создавать при помощи rebel - . Однако все подобные вещи мы уже проделывали в предыдущих параграфах этой лекции. Так что наведение глянца предоставляется читателю в качестве упражнения. А мы переходим к дальнейшим "фокусам".
И этот параграф при первом чтении тоже вполне можно пропустить. Нельзя сказать, что он полностью оправдывает свой заголовок. Во-первых, мы долго размышляли над уместностью в названии буквы "р". Во-вторых, содержимое сего параграфа никак не сложнее, скажем, предыдущего; однако, в отличие от него, не несет в себе уж совсем никакой практической пользы. Все, что вы можете от него получить, - это острые программистские ощущения и (ложную) уверенность, что уж теперь-то вы понимаете все детали работы Флэш МХ. Но уж острые ощущения будут что надо, поверьте!
Мы много раз использовали словосочетание "контекст вызова функции" (по-английски его также называют activation object, то есть "объект активации"). Мы даже убедились, что это действительно единый объект, доступный во вложенных функциях через this. Давайте же исследуем его - как исследовали мы другие системные объекты! А заодно - и все остальное, что попадется по дороге. Для этого мы применим вот такой код:
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
o1 = {k:10, l:20};
a1 = [5, 6, 7];
trace("******* a1 ********");
dumpObj(a1);
trace("*******************");
trace("");
var f = function(){
trace("---- arguments -----");
dumpObj(arguments);
trace("--------------");
var x = 5;
var y = "Текстовая локальная переменная";
var o2 = {k:15, l:25};
var getActivation = function(){
return this;
}
return getActivation();
}
trace("Снаружи this = " + this);
trace("");
trace("==== Контекст вызова f ====");
dumpObj(f(1000, [1, 2, 3]));
trace("===========================");
trace("");
trace(":::::::: _level0 ::::::::");
dumpObj(this);
trace("::::::::::::::::::::::::::");
Посмотрим, как этот код справляется с нашей основной задачей: разобраться, что скрывается внутри контекста вызова функции. Контекст вызова f доступен при вызове getActivation через this. (Здесь важно, что ссылку getActivation мы пометили как var ).
Обратите внимание, что на функции, сгенерированные внутри кадра какого-то клипа (в данном случае, _level0 ), не распространяется это правило. То есть в них this также указывает на _level0, а не на какой-то "контекст вызова кадра". Чтобы это подчеркнуть, мы перед f нарочно поставили var.
Так вот, далее getActivation возвращает ссылку на контекст в саму функцию f, которая, в свою очередь, возвращает его в вызывающее f выражение. То есть мы в очередной раз можем проверить, что контекст вызова не перестает существовать после того, как функция отработала, если на него остались какие-то ссылки. Посмотрим, что получается на выходе:
******* a1 ******** 2: 7 1: 6 0: 5 __proto__: constructor: [type Function] length: 3 ******************* Снаружи this = _level0 ==== Контекст вызова f ==== ---- arguments ----- 1: 1,2,3 0: 1000 caller: null callee: [type Function] __proto__: constructor: [type Function] length: 2 -------------- getActivation: [type Function] o2: [object Object] y: Текстовая локальная переменная x: 5 arguments: 1000,1,2,3 this: _level0 =========================== :::::::: _level0 :::::::: f: [type Function] name: name __proto__: [object Object] constructor: [type Function] a1: 5,6,7 o1: [object Object] $appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/ $version: WIN 6,0,21,0 ::::::::::::::::::::::::::
Мы видим, что в контексте вызова функции хранятся все локальные переменные, а также ссылки на arguments и this. Также видно, что объект arguments отличается от обычного массива (для примера и проверки работы функции dumpObj выведены "внутренности" массива a1 ) только полями и . Интересен также тот факт, что у объекта "контекст вызова" отсутствуют поля constructor и __proto__. Это наводит на мысль о некоторых трюках, которые мы испробуем в следующем подпараграфе. Напоследок давайте посмотрим, какие из увиденных нами полей являются открытыми, а какие мы видим только благодаря применению недокументированной функции ASSetPropFlags. Закомментируем ее вызов в функции dumpObj, и тогда на выходе получим следующее:
******* a1 ******** 2: 7 1: 6 0: 5 ******************* Снаружи this = _level0 ==== Контекст вызова f ==== ---- arguments ----- 1: 1,2,3 0: 1000 -------------- getActivation: [type Function] o2: [object Object] y: Текстовая локальная переменная x: 5 =========================== :::::::: _level0 :::::::: f: [type Function] name: name a1: 5,6,7 o1: [object Object] $version: WIN 6,0,21,0 ::::::::::::::::::::::::::
Примечательно, что в объекте arguments мы не видим теперь ничего, кроме самих аргументов, а в контексте вызова видим только локальные переменные (но не видим arguments и this ).
Сейчас мы видели, что контекст вызова функции - это объект, не являющийся экземпляром какого-либо класса. Об этом говорит отсутствие ссылки __proto__. То есть у объекта нет прототипа и поля у него заведены только те, что нужны в данном конкретном случае. (Можно, конечно вспомнить про повторяющиеся, хоть и скрытые, поля arguments и this. Но факт остается фактом - эти поля тоже берутся не из прототипа, а заводятся каждый раз заново.) Однако мы уже знаем, что при __proto__. Возникает соблазн проделать то же самое и с контекстом вызова. В результате поля того объекта, на который будет указывать __proto__, будут восприниматься так же, как собственные поля контекста вызова. То есть ... как
локальные переменные! Это настолько неожиданный вывод, что хочется немедленно его проверить.
_global.dumpObj = function(obj){
// Снимаем "защиту" со скрытых полей
ASSetPropFlags(obj,null,0,1);
for(name in obj){
trace(name + ": " + obj[name]);
}
}
o1 = {k:10, l:20};
var f = function(){
var x = 5;
var y = "Текстовая локальная переменная";
var o2 = {k:15, l:25};
var getActivation = function(){
return this;
}
var act = getActivation();
act.__proto__ = o1; // Или var.__proto__ = o1;
trace("===================");
trace("k = " + k);
trace("l = " + l);
trace("===================");
k = 145;
var l = 1111;
trace("===================");
trace("k = " + k);
trace("l = " + l);
trace("===================");
return getActivation();
}
trace("==== Контекст вызова f ====");
dumpObj(f(1000, [1, 2, 3]));
trace("===========================");
trace("");
trace("******* o1 ********");
dumpObj(o1);
trace("*******************");
trace("");
trace(":::::::: _level0 ::::::::");
dumpObj(this);
trace("::::::::::::::::::::::::::");
На выходе получаем:
==== Контекст вызова f ====
===================
k = 10
l = 20
===================
===================
k = 10
l = 1111
===================
k: 10
l: 1111
__proto__: [object Object]
act: [object Object]
getActivation: [type Function]
o2: [object Object]
y: Текстовая локальная переменная
x: 5
arguments: 1000,1,2,3
this: _level0
===========================
******* o1 ********
k: 10
l: 20
__proto__: [object Object]
constructor: [type Function]
*******************
:::::::: _level0 ::::::::
name: name
__proto__: [object Object]
constructor: [type Function]
k: 145
f: [type Function]
o1: [object Object]
$appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/
$version: WIN 6,0,21,0
::::::::::::::::::::::::::
Мы видим удивительные (хотя и ожидавшиеся нами) вещи! Действительно, переменные k и l воспринимаются внутри функции (после того, как контексту указали __proto__ ) как локальные! Но более того, они еще и являются чем-то вроде константных локальных переменных! Наша попытка изменить переменную k привела к тому, что была заведена соответствующая переменная в _level0. (Поскольку содержимое объекта, на который указывает __proto__, не меняется, то должна быть заведена новая переменная; но поскольку ключевое слово var не было применено, то переменная заводится в объекте, из которого была вызвана функция f.) Поскольку приоритет в функции имеет локальная переменная, то значение k как бы и не изменилось. В случае же переменной l, когда мы использовали var, значение l в функции, конечно, было заменено. Но в объекте o1 оно, разумеется, осталось п
режним (так же как и значение k ). Конечно, польза такого необычного применения __proto__ далеко не очевидна. Однако сама возможность подобных фокусов вызывает теплые ностальгические чувства и воспоминания о Бэйсике и ассемблере конца 80-х годов прошлого века. Не будем вдаваться в подробности. У каждого своя ностальгия.
Вот попытка использовать все-таки странное содержимое этого параграфа. В прошлой лекции мы рассказывали о том, как эмулировать во Флэше приватные поля. Если вы пишете класс с нуля, - вы без всяких проблем сможете использовать этот метод. Но предположим, что вам достался уже готовый класс, а вы хотите сделать его наследника более защищенным. Сможете ли вы это сделать? В голову приходит идея пройтись по прототипу и перегененировать функции, которые мы там найдем, таким образом, чтобы потом вызывать исходные функции при помощи apply и передавать им в качестве объекта, с которым они работают, контекст вызова. (Попробуйте написать код, который делает это. А затем посмотрите, какой есть еще способ решить эту задачу - он описан в следующем подпараграфе.)
Перегенерация функций - это интересная идея. Но как быть, если вы хотите сделать приватными всего одно-два особенно важных поля, и обращаются к этим полям опять-таки один-два метода? Перегенерация всех методов класса будет в таком случае явно избыточной. С другой стороны, как обеспечить этим методам (в процессе перегенерации) доступ к остальному содержимому класса? Ведь вызывать мы будем эти методы при помощи apply, передавая им контекст вызова конструктора. Но на этот раз мы не собираемся в контекст конструктора копировать все нужные нам ссылки. Выход из этого положения как раз может подсказать найденный нами недавно фокус с заданием ссылки __proto__ для контекста вызова! Установим __proto__ контекста вызова ссылающимся на наш объект. Тогда все значения переменных из объекта будут доступны для рассматриваемой функции. Правда - и здесь нужно быть аккуратным - эта функция не сможет их изменить (точнее, измененные значения останутся в контексте вызова конструкт
ора, а не в самом объекте). Так что если менять значение публичного поля все-таки надо, следует или воспользоваться функцией- setter 'ом этого поля (если нужно, то создать его), или же сделать это поле приватным и перегенерировать еще и те функции, которые к нему обращаются. Первый способ, конечно, предпочтительнее.
Сейчас мы приведем пример использования предлагаемого метода. Воспользуемся уже знакомым нам замечательным лифтом. Предположим, мы добавили возможность внешнему контроллеру управлять этим лифтом. Контроллер сможет открывать и закрывать двери, а также останавливать лифт, если надо. Условимся, что изменение соответствующей переменной будет означать изменение состояния лифта. То есть, если мы установили переменную doorsAreOpen в true, то это означает, что двери открылись. Посмотрите на новый вариант кода лифта:
// ================ Базовый класс лифта - модифицирован =================
// Базовый конструктор - добавлено поле moving
_global.lift = function (minFloor, maxFloor){
// Проверяем, не перепутал ли пользователь максимальный и
// минимальный этажи
if (minFloor < maxFloor){
this.minFloor = Math.round(minFloor);
this.maxFloor = Math.round(maxFloor);
}
else{ // Если перепутал, корректируем
this.minFloor = Math.round(maxFloor);
this.maxFloor = Math.round(minFloor);
}
// Устанавливаем лифт на 1 этаж
// (но если лифт туда не должен ездить, корректируем
// начальный этаж в нужную сторону).
this.currentFloor = Math.min(Math.max(1, this.minFloor),
this.maxFloor);
this.doorsAreOpen = true;
// Новое поле
this.moving = false;
}
// Функция goto - без изменений
_global.lift.prototype.goto = function(where){
trace("-----------");
// Oкругляем и ограничиваем этаж назначения
this.floorToGo = Math.min(Math.max(Math.round(where), this.minFloor), this.maxFloor);
if (this.floorToGo != this.currentFloor) this.go();
// Поехали!
else trace("Ничего не делаем." );
trace("-----------");
}
// Уделяем повышенное внимание безопасности
_global.lift.prototype.setDoorsOpen = function(open){
// Если мы едем, двери трогать нельзя
if (this.moving) return;
if (this.doorsAreOpen != open){
trace("Осторожно!");
trace(open ? "Открываем двери." : "Закрываем двери.");
}
this.doorsAreOpen = open;
}
// Добавлена регистрация состояний движения/неподвижности,
// а также добавлено взаимодействие с внешним контроллером
_global.lift.prototype.go = function(){
// Осторожно, двери закрываются
this.setDoorsOpen(false);
this.moving = true;
// Поехали!
var distance = this.floorToGo - this.currentFloor;
var signOfDistance = (distance >= 0 ? 1 : -1);
for( // Это работает независимо от того, едем вверх или вниз
var i = this.currentFloor;
signOfDistance*i <= signOfDistance*this.floorToGo;
i += signOfDistance
){
// Едем и выводим об этом сообщение
trace("Этаж " + i);
this.currentFloor = i;
// Проехали один этаж, сообщаем об этом контроллеру
externalController.work(this);
// А вдруг контроллер остановил лифт?
if (!this.moving) break;
}
// Приехали
this.moving = false;
this.setDoorsOpen(true);
}
Сразу видно, что с точки зрения безопасности движения у нас возникают проблемы. Контроллер может открыть двери в обход функции setDoorsOpen (которая теперь проверяет, движется ли лифт). Вот код контроллера со всевозможными вариантами потенциально опасных действий:
//================ Контроллер лифта ===========================
externalController = {};
externalController.work = function(liftToControl){
this.liftToControl = liftToControl;
if (liftToControl.currentFloor == 3) this.doEvilThing();
}
externalController.openLiftDoors1 = function(){
this.liftToControl.doorsAreOpen = true;
}
externalController.openLiftDoors2 = function(){
this.liftToControl.setDoorsOpen(true);
}
externalController.openLiftDoors3 = function(){
this.liftToControl.moving = false;
this.liftToControl.setDoorsOpen(true);
}
externalController.doEvilThing = externalController.openLiftDoors1;
Давайте теперь напишем класс safeLift, который будет наследником класса lift, избавленным от этих проблем.
//================== Безопасный лифт =================================
_global.safeLift = function(minFloor, maxFloor){
super(minFloor, maxFloor);
var doorsAreOpen = true;
var getActivationObject = function(){
return this;
}
var activationObject = getActivationObject();
activationObject.__proto__ = this;
// Или var __proto__ = this;
var setDoorsOpen = function(){
return this.__proto__.setDoorsOpen.apply(activationObject,
arguments);
}
// Переопределяем старую функцию
this.setDoorsOpen = setDoorsOpen;
}
_global.safeLift.prototype = new _global.lift(0, 1);
Тестировочный код будет у нас выглядеть следующим образом:
//=========================== Тестовая часть =============================
l1 = new lift(1, 5);
l2 = new safeLift(1, 5);
// Тестируем
trace("l1:");
l1.goto(10);
trace("l2:")
l2.goto(10);
externalController.doEvilThing = externalController.openLiftDoors2;
trace("l1:");
l1.goto(0);
trace("l2:")
l2.goto(0);
externalController.doEvilThing = externalController.openLiftDoors3;
trace("l1:");
l1.goto(10);
trace("l2:")
l2.goto(10);
Поместив в кадр _level0 все вышеприведенные кусочки кода и запустив эту программку, мы получим:
l1: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Этаж 4 Этаж 5 Осторожно! Открываем двери. ----------- l1: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Этаж 2 Этаж 1 Осторожно! Открываем двери. ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Этаж 2 Этаж 1 Осторожно! Открываем двери. ----------- l1: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Осторожно! Открываем двери. ----------- l2: ----------- Осторожно! Закрываем двери. Этаж 1 Этаж 2 Этаж 3 Осторожно! Открываем двери. -----------
Посмотрим, что у нас произошло. При первоначальном движении вверх у лифта номер 1 возникли проблемы. Контроллер открыл двери на 3-м этаже, при этом лифт не выдал никакого предупреждения и не остановился (о том, что двери открылись, можно судить по отсутствию сообщений об открывании дверей, когда лифт приехал на 5-й этаж). При движении вниз оба лифта отвергли попытку контроллера открыть двери с помощью setDoorsOpen, поскольку переменная moving была установлена в true. Наконец, при обратном движении наверх оба лифта остановились на 3-м этаже, поскольку контроллер установил moving в false. Итак, safeLift оказался полностью защищенным от некорректных действий контроллера.
Отметим, что строчка activationObject.__proto__ = this; в конструкторе безопасного лифта является обязательной. Попробуем закомментировать ее, и при движении лифта вниз (когда контроллер открывает двери при помощи setDoorsOpen ) мы получим:
l2: ----------- Осторожно! Закрываем двери. Этаж 5 Этаж 4 Этаж 3 Осторожно! Открываем двери. Этаж 2 Этаж 1 -----------
То есть в этом случае функция setDoorsOpen проигнорировала переменную moving, что неудивительно - она просто не смогла ее найти в контексте вызова конструктора, который мы передали ей в качестве this. И только запись activationObject.__proto__ = this; спасает положение.
Заметим, что вместо activationObject.__proto__ = this, мы могли также написать var __proto__ = this. Действительно, var как раз позволяет добавлять нам ссылки в контекст вызова, так что и ссылку __proto__ можно добавить эдаким образом. Хотя рассудок протестует против идеи составлять выражение из трех ключевых слов разного вида и оператора присваивания. Но, тем не менее, такая запись тоже работает.
Является ли приведенный здесь код образцом хорошего стиля? Вряд ли. Но в особых случаях (когда при
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.