Проблема с созданием класса - Значения свойств передаются другому экземпляру
Что я сделал не так?
В консоли должно быть пусто, а вместо этого [1, 2, 3, 4] То есть в экземпляре s2 оказались члены массива из экземпляра s1... Как такое могло получиться??? Код в песочнице https://fiddle.sencha.com/#fiddle/qgo Ext.define("Subpath", { segments: [], add: function(segment) { this.segments.push(segment); return segment; } }); Ext.application({ name : 'Fiddle', launch : function() { var s1 = Ext.create("Subpath"); s1.add(1); s1.add(2); s1.add(3); s1.add(4); var s2 = Ext.create("Subpath"); console.log(s2.segments); } }); Вот тут http://docs.sencha.com/extjs/5.1/cor...s/classes.html явно видно, что можно задавать значения по умолчанию для свойств класса. |
В документации видно, что такие свойства задаются через конструктор:
Ext.define('My.sample.Person', { name: 'Unknown', constructor: function(name) { if (name) { this.name = name; } }, eat: function(foodType) { alert(this.name + " is eating: " + foodType); } }); var bob = Ext.create('My.sample.Person', 'Bob'); bob.eat("Bob"); // alert("Bob is eating: Salad"); А не через самодельный сеттер. Интуитивно непонятно, конечно. |
Строка 05 содержит условие if (name) {...},. То есть предполагается, что класс можно использовать так:
var noname = Ext.create('My.sample.Person'); В этом случае name будет содержать 'Unknown'. Отсюда я делаю вывод, что строка 02 содержит значение по умолчанию. Иначе бы они написали так: name: undefined, constructor: function(name) { if (name) { this.name = name; } else { this.name = 'Unknown'; } }, В этом случае видно, что значение по умолчанию задается только в конструкторе. |
Вы путаете конфигурационные свойства, которые задаются в блоке config: {} и могут наследоваться/объединяться, с обычными свойствами, которые никаким специальным образом не обрабатываются.
Класс Ext JS - это функция-конструктор; всё, что вы задаёте в блоке Ext.define(, {…}) попадает на прототип этого конструктора. Экземпляр этого класса, соответственно, это просто объект JavaScript, который через __proto__ показывает на соответствующий класс. Ну и как отсюда очевидно, если у вас в *экземпляре* класса (т.е. в объекте) нет своего свойства segments, то будет использоваться свойство segments с *прототипа*. Т.е. каждый экземпляр класса Subpath будет обращаться к одному и тому же массиву, наступая друг другу на ноги. Обычный JavaScript, ничего сложного. ;) Решение простое: в constructor присваивайте экземпляру все свойства, которые должны быть уникальными для каждого экземпляра. Ext.define('Subpath', { /** * @property {Number[]} segments Тут живут сегменты */ constructor: function() { this.segments = []; } }); Что касается примера г-на novikov, то это как раз и есть случай конфигурационного свойства. В таких случаях велосипед изобретать не надо, а лучше использовать блок config: Ext.define('My.sample.Person', { config: { name: 'Unknown' // значение по умолчанию } }); // Значение не передано, будет использовано по умолчанию var unknown = new My.sample.Person(); console.log(unknown.getName()); // Unknown // А тут мы передаём значение явно var foo = new My.sample.Person({ name: 'foo' }); console.log(foo.getName()); // foo Особо отмечу, что первый и второй случаи друг другу совершенно не противоречат; segments это *внутреннее* свойство объекта, а name это конфигурационное свойство. Использовать и те и другие в одном объекте это совершенно обычное дело, надо просто понимать разницу. |
Цитата:
На мой взгляд не совсем логично, что свойства надо создавать в конструкторе (точнее с точки зрения JS нормально, но с точки зрения обычного ООП не привычно, а ведь Ext JS пытается имитировать обычный ООП). И если забудешь создать, то будет ссылка на общее значение. Кстати, я до сих пор не сталкивался с этой проблемой, а ведь уже сколько подобных свойств я насоздавал и пока не напарывался... Придется всю толпу классов перерыть и всюду сделать инициализацию в конструкторе... неслабо однако... Кстати, а где об этом написано в документации? |
Кстати, в свете новых сведений о создании свойств не ясно как создавать приватные свойства.
Ext.define(..., { privates: {...} }); Ведь в блоке privates их объявлять нельзя получается. Только разве комментарий можно. А в конструкторе похоже можно создавать публичные свойства. Бардачок намечается чтоли? |
Цитата:
Цитата:
Цитата:
Цитата:
Цитата:
Практически единственный смысл блока privates - это дать вам, как пользователю, возможность узнать о том, что в вашем наследованном классе есть метод с таким же названием, как и приватный метод в родительском классе. В отличие от публичного API, приватные методы не гарантированы по прямой и обратной совместимости между версиями; т.е. мы оставляем себе право их менять как нам заблагорассудится. Мы слишком часто натыкались на ситуации, когда пользователи в своём коде называли методы так же, как и мы в своём. Результаты бывают феерически прекрасны, но очень трудоёмки к поимке. Из этой проблемы и растут ноги у блока privates. Это наш способ дать вам знать: вот этот метод - он приватный, не трогайте его. А если потрогаете и ему не понравится, то мы не виноваты. :) |
nohuhu,
я каждый раз когда ваши сообщения в разделе читаю, жалею что движок форума не разрешает одному человеку много плюсов ставить =) |
Цитата:
Спасибо! Теперь есть чем заняться. Буду все написанные классы исправлять, там много у меня подобных свойств. Цитата:
Но это не принципиально, просто заранее надо знать, что этот момент Ext не решает и надо самому не попасться. |
Цитата:
|
khusamov,
Там надо много людей плюсануть чтобы повторно можно было, человек 10 наверное, точно не считал |
Цитата:
Цитата:
Цитата:
Цитата:
Ext.define('Foo', { config: { blerg: { frobbe: 'throbbe' } } }); Ext.define('Bar', { extend: 'Foo', config: { blerg: { qux: 'xyzzy' } }); var beer = new Bar({ blerg: { foo: 42 } }); console.log(beer.getBlerg()); // { // foo: 42, // frobbe: 'throbbe', // qux: 'xyzzy' // } var gurgle = new Bar({ blerg: { frobbe: 'zingbong' } }); console.log(gurgle.getBlerg()); // { // frobbe: 'zingbong', // qux: 'xyzzy' // } Ну, я малость разошёлся с именами метапеременных, но надеюсь, что понятно всё же будет. ;) |
Цитата:
|
о как, конфиг теперь рекурсивно обрабатывается???
раньше такого не было. |
Ээээ, ну, не совсем рекурсивно… Там всё чуть сложнее. :) Эта система притащена в Ext JS 5.0 из Sencha Touch и (гхм) слегка допилена по месту. Делать с ней можно много весёлых штук для фана и профита, но лучше ограничиться документированным применением, примерно как я показал выше.
Ещё ньюанс: с массивами в конфигах надо быть осторожнее. Они, конечно, формально тоже объекты, но с массивами связка по прототипу не работает, и результат может быть непредсказуем. Это можно обойти, но я не уверен, что эти инструменты входят в публичное API. |
Цитата:
|
Если вы не помещаете массивы в свойства блока config, то никакой специальной магии к ним применяться не будет. Это будут простые свойства объекта, которые совершенно одинаковым образом работают на чтение - при этом неважно, где именно свойство находится, на экземпляре объекта или на прототипе. Сюрпризы могут быть, как вы уже выяснили, если пытаться в такие свойства записывать; но мы же о конфигах говорим, правильно? Конфиги по определению должны только читаться.
Если же вам надо помещать массивы в свойства блока config, то специальную магию надо будет слегка подправить напильничком, чтобы она работала как надо. Если не подправить, то из коробки может и не заработать. Со свойствами-объектами всё достаточно просто, их связывают по прототипной цепочке и они "просто работают". С массивами такого очевидного варианта нет. |
Цитата:
Я все этим методы активню пользуюсь, а теперь выясняется что нельзя? Они только для чтения??? |
Цитата:
|
Цитата:
Система конфигурации в Ext JS 5 имеет основной целью конфигурирование экземпляров объектов. Конфигурация каждого экземпляра может динамически меняться, при этом конфигурация других объектов того же класса изменяться не должна. Отсюда логически следует, что конфигурационные свойства классов должны быть доступны только для чтения, в отличие от объектов. В теории это всё звучит хорошо, но на практике у нас есть JavaScript и только одна схема наследования, прототипная. Это означает, что наследование конфигов можно сделать двумя способами: либо связывая конфиги-объекты по прототипной цепочке с соответствующими конфигами классов, либо тупо копируя все свойства для каждого нового экземпляра. Второй способ отпал по причине низкой производительности, поэтому остаётся первый. Если конфигурационные объекты по прототипу не связывать, то для каждого экземпляра объекта будут доступны только те свойства, которые содержатся в конфиге самого экземпляра - наследованные свойства доступны не будут. Именно так это работало в Ext JS 4: Ext.define('Bar', { blerg: { frobbe: 'throbbe' } }); var beer = new Bar({ blerg: { qux: 'dux' } }); console.log(beer.blerg); // { // qux: 'dux' // } Как видите, никакого наследования быть не может, т.к содержимое переменной beer это объект, прототипом которого является объект Bar. У каждого из этих объектов есть свойство blerg, и использоваться будет "верхнее" значение в прототипной цепочке. Блок config в Ext JS 5 работает по-другому и свойства-объекты связываются по прототипу, чтобы обеспечить наследование конфигов. Заметьте, что все эти пространные рассуждения никак не относятся к тривиальным значениям конфигов: строкам, числам, etc. С ними всё просто. Но объекты наследуются: Ext.define('Foo', { config: { blerg: { zilch: 42 } }, // Это нужно только в данном примере; // в обычном случае initConfig уже вызывается // родительским классом (Ext.Component, etc) constructor: function(config) { this.initConfig(config); } }); var foo = new Foo({ blerg: { bar: 'qux', zilch: 43 } }); console.log(foo.getBlerg()); // { // bar: 'qux', // zilch: 43 // } В этом примере объект в переменной foo имеет прототипом объект Foo, оба имеют конфиг blerg. Этот конфиг, в свою очередь, это тоже объект, прототипом которого является объект конфига blerg в объекте Foo. Свойство zilch имеется в обоих конфигах, но "верхнее" в цепочке принадлежит конфигу foo, поэтому оно и используется; свойство bar в конфиге Foo отсутствует и есть только в конфиге foo. Поскольку конфиги динамические, то их можно менять, но только для экземпляра объекта: foo.setBlerg({ zilch: 44 }); foo.getBlerg(); // { // zilch: 44 // } Если вы проинспектируете конфиг объекта Foo, то увидите, что он не изменился. Конфигурация происходит только на уровне экземпляра, но не класса. Что касается массивов, то с ними связывание по прототипу не работает, поэтому тоже будет использоваться "верхнее" значение в цепочке, так же как и с тривиальными типами данных: Ext.define('Baz', { config: { qux: [42] }, constructor: function(cfg) { this.initConfig(cfg); } }); var bazz = new Baz(); bazz.getQux(); // [42] var brazz = new Baz({ qux: [-1] }); brazz.getQux(); // [-1] Специальная магия нужна в случае, если вы хотите, чтобы массив был объединением элементов из наследованных конфигов с элементами из массива на экземпляре: Ext.define('MagicParent', { config: { qux: { $value: [42], merge: function(newValue, oldValue) { // Пристёгиваем новые элементы к старым return (oldValue || []).concat(newValue); } }, constructor: function(cfg) { this.initConfig(cfg); } }); Ext.define('MagicChild', { extend: 'MagicParent', config: { qux: [43] } }); var magic = new MagicChild({ qux: [44] }); magic.getQux(); // [42, 43, 44] // Значения, переданные в setter, считаются полными // и через цепочку связываний не проходят: magic.setQux([-1]); magic.getQux(); // [-1] Надеюсь, теперь будет понятнее. В целом система конфигов не безобразно сложна к пониманию, но имеет некоторые особенности, поэтому и остаётся большей частью приватной. Если разобраться, можно творить чудесатые чудеса. :) |
Часовой пояс GMT +3, время: 02:05. |