-
Notifications
You must be signed in to change notification settings - Fork 1
Class mutators
Мутатор, это объект, который может оказать влияние на структуру класса и каждого его экземпляра.
С каждым классом ассоциирован список мутаторов. При создании класса, он пропускается через все свои мутаторы, которые могут в этот момент изменить его структуру. При создании объекта, он также пропускается через все мутаторы своего класса.
С помощью мутаторов реализованы, например, статические поля и связь методов с контекстом.
var TestClass = go.Class(ParentClass, {
'__mutators': {
'one': {
'processClass': function (props) {
// ...
}
},
'two': {
'processInstance': function (instance) {
// ...
}
}
},
function method() {
// ...
}
});
Для определения мутаторов используется поле __mutators
.
В примере определены два мутатора one
и two
.
Мутаторы наследуются от внутреннего класса Mutator
, который расширяют.
В примере one
переопределил метод базового мутатора processClass()
.
Если одноимённые мутаторы уже были определены в предках (например, в ParentClass
), то они расширяются здесь.
Базовые методы, предназначенные для переопределения:
-
processClass(Object props)
- вызывается при создании класса. Получает список полей, переданных в функциюgo.Class()
и может изменить их. -
processInstance(Object instance)
- получает только что созданный экземпляр класса. Может изменить его, как хочет. Вызывается до того, как сработает конструкторinstance.__construct()
. -
getMethod(String name, Object instance)
- доступ к сохранённому методу. Подробнее ниже.
Доступные свойства:
-
name
- имя мутатора -
Class
- класс к которому привязан -
parent
- класс из которого наследован мутатор -
fields
- сохраняемые поля. Копируются из родительского мутатора. См. ниже.
Объект списка мутаторов хранится в свойстве класса __mutators
.
Сам массив мутаторов хранится в свойстве mutators
списка.
То есть, мутатор mymutator
для класса MyClass
доступен, как MyClass.__mutators.mutators.mymutator
.
У всех классов есть три стандартных мутатора (определённых в базовом классе go.Class.Root
).
Это sysvars
, static
и bind
.
На их примерах и рассмотрим постороение мутатора.
Кроме того, мутаторы используются в go.Ext.Options и go.Ext.Nodes.
Мутатор sysvars
обрабатывает такие системные поля, как __abstract и __final.
'sysvars': {
'vars' : {
'__abstract' : false,
'__final' : false,
'__classname' : "go.class"
},
'processClass': function (props) {
var C = this.Class,
vars = this.vars,
name;
for (name in vars) {
if (vars.hasOwnProperty(name)) {
if (props.hasOwnProperty(name)) {
C[name] = props[name];
delete props[name];
} else {
C[name] = vars[name];
}
}
}
delete props.eoc;
}
},
processClass()
вызывается в момент создания класса.
Переданные в качестве аргумента в go.Class()
поля класса пропускаются через мутаторы и могут быть изменены, прежде чем попадут в прототип нового класса.
Здесь такие поля, как __abstract
, __final
и __classname
переносятся в объект класса (this.Class
) и удаляются из его прототипа (props
).
Заодно удаляется eoc
.
Если нужные поля не определены, используется значение по умолчанию (не абстрактный и не финальный класс).
Кроме переопределения базовых методов можно определить какие угодно свои методы и свойства.
В примере, это vars
: список системных полей и их значения по умолчанию.
Мутатор static
отвечает за статические свойства.
'static': {
'processClass': function (props) {
var C = this.Class,
st = props.__static,
fields,
k;
fields = this.fields;
if (st) {
go.Lang.extend(fields, st);
delete props.__static;
}
for (k in fields) {
if (fields.hasOwnProperty(k)) {
C[k] = fields[k];
}
}
}
},
Мутатору требуется сделать статическими все поля, определённые в свойстве __static
.
То есть перенести их все в объект this.Class
и удалить само свойство props.__static
из прототипа.
По сравнению с sysvars
одно усложнение: статические свойства должны наследоваться, так что this.Class
должен быть наполнен также и статическими свойствами, определёнными в его родительском классе.
Здесь используется свойство fields
.
При создании мутатора, оно копируется из одноимённого мутатора родительского класса (если он там определён).
Именно копируется, то есть создаётся новый объект и туда переносятся все свойства, так что при изменении родительский fields
не будет затронут.
В приведённом коде все поля из __static
копируются в fields
.
А уже из него в класс.
При наследовании класса, мутатор возьмёт fields
предка (с его статическими свойствами), расширит его своими и уже полный список скопирует в this.Class
.
Мутатор bind
отвечает за связывание методов с контекстом.
Здесь уже не обойдёшься одним processClass()
, требуется ещё и processInstance()
.
'bind': {
'regexp': /^on[A-Z_]/,
'bindvar': "__bind",
'processClass': function (props) {
var names = this.getMethodsNames(props),
fields = this.fields,
i,
len,
name,
fn;
for (i = 0, len = names.length; i < len; i += 1) {
name = names[i];
fn = props[name];
if (typeof fn === "function") {
delete props[name];
fields[name] = fn;
}
}
},
'processInstance': function (instance) {
var bind = go.Lang.bind,
fields = this.fields,
original,
binded,
k;
for (k in fields) {
if (fields.hasOwnProperty(k)) {
original = fields[k];
binded = bind(original, instance);
binded.__original = original;
instance[k] = binded;
}
}
},
'getMethod': function (name, instance) {
if (this.fields.hasOwnProperty(name)) {
return go.Lang.bind(this.fields[name], instance);
}
return undefined;
},
'getMethodsNames': function (props) {
var names,
k,
reg = this.regexp;
if (props.hasOwnProperty(this.bindvar)) {
names = props[this.bindvar];
if (!names) {
names = [];
}
delete props[this.bindvars];
} else {
names = [];
for (k in props) {
if (props.hasOwnProperty(k)) {
if (typeof props[k] === "function") {
if (reg.test(k)) {
names.push(k);
}
}
}
}
}
return names;
}
}
В момент создания класса, processClass()
получает список всех методов, которые следует связать с контектом (через getMethodNames()
).
Он обходит их всех и сохраняет в fields
, удаляя из прототипа.
В момент создания каждого объекта для него вызывается processInstance()
и он заполняется сохранёнными в fields
методами (которые налету связываются с этим объектом).
За счёт копирования fileds
, как и в случае с мутатором static
, bind-методы наследуются.
Если мутатор сохраняет у себя и удаляет из прототипа какие-то методы (как мутатор bind
) он должен определить метод getMethod()
.
Он используется при доступе к родительской реализации метода.
ParentClass.__method(this, 'onClick', e); // Вызов родительской реализации onClick
Если метод onClick
не будет найден в прототипе ParentClass
(а он удалён оттуда bind'ом), то будут опрошены все мутаторы, на предмет, не сохранили ли они этот метод у себя.
Если мутатор сохранил метод, то должен вернуть его (хотя может и изменить).
Если у мутатора такого метода нет, должен вернуть undefined
.
Если мутатор был уже определён в родительских классах, то он расширяется:
var TestClass = go.Class({
'__mutators': {
'bind': {
'regexp': /^.+$/
}
}
});
Для мутатора bind
переопределено свойство regexp
.
Оно использовалось для указания того, какие методы биндить (те, что начниаются с "on").
Теперь будет биндится всё подряд.
При множественном наследовании мутаторы наследуются также и с дополнительных классов. Но только в том случае, если не были уже определены в основной ветви.
Чтобы удалить, мутатор нужно переопределить на null.
var TestClass = go.Class({
'__mutators': {
'static': null
}
});
Всё, теперь для TestClass
и его потомков статических методов не будет.