Как стать автором
Обновить

Ext JS — Учимся правильно писать компоненты. Наследование и вложенные конфиги

Время на прочтение5 мин
Количество просмотров14K
Добрый день, уважаемые хабравчане. Продолжая тему о создании custom-компонентов, мы с Вами сегодня поговорим о наследовании свойств-объектов и о правильном использовании обработчиков событий.

Когда проект разрастается от простой «формочки с гридом и кнопкой» до сложного RIA-приложения, приходится выносить отдельные части интерфейса в компоненты. Начинающие ExtJS-девелоперы пытаются просто вынести весь конфиг той или иной панели в инструкцию extend:
  1. var MyPanel = Ext.extend(Ext.Panel, {
  2.     items: [{
  3.       xtype: 'grid'
  4. ...................................................
* This source code was highlighted with Source Code Highlighter.


и получают милейшую ошибку в консоли firebug'a (или же неудомевают, почему ничего не отображается в случае, если оный инструмент не установлен).

Почитав документацию на форуме ExtJS, они применяют рекомендуемое там решение:
  1. var MyPanel = Ext.extend(Ext.Panel, {
  2.   initComponent : function () {
  3.      Ext.applyIf(this, {
  4.       items: [
  5.     ..............................
  6.     });
  7.     MyPanel.superclass.initComponent.call(this);
  8.   }
  9. ....................................
* This source code was highlighted with Source Code Highlighter.


Теперь все работает и все рады. Но к сожалению, никто не задается вопросом, а почему же решение возможно только таким образом? А зря. Ведь потом, когда у ихних собственных компонентов начнут появляться доп св-ва, являющиеся объектами или массивами (например, параметры AJAX-запросов), непременно начнут возникать «веселые» баги. Рассмотрим следующий пример:

  1.     Ext.ux.BadForm = Ext.extend(Ext.Panel, {
  2.       border: false,
  3.       frame: true,
  4.       messages : {
  5.         hello: 'Hello, World!'
  6.       },
  7.       html: 'Bad Form',
  8.       buttons: [{
  9.         text: 'Say',
  10.         handler: function () { Ext.Msg.alert('Message', this.ownerCt.ownerCt.messages.hello); }
  11.       },{
  12.         text: 'Change Msg',
  13.         handler: function () { this.ownerCt.ownerCt.messages.hello = 'New Hello Message'; }
  14.       }]
  15.     });
* This source code was highlighted with Source Code Highlighter.


Если создать два экземпляра такой панели, вылезет очень интересная ошибка. По нажатию кнопки Say обе формы выдадут стандартное сообщение «Hello, World!». Но если поменять сообщение в любой из них, то оно поменяется и в другой. Как так может быть, если у нас — два разных экземпляра класса?

Все очень легко объяснимо. В JavaScript на самом деле нет такого четкого разграничения, как класс и объект. Все фреймворки обычно эмулируют привычную в обычном ООП схему наследования, предоставляя инструменты вроде Ext.extend()/Class.create()/new Class() (Ext JS/Prototype/MooTools). На самом же деле каждый объект конструируется на основе прототипа. Прототип же — один на всех.

Все что мы помещаем внутрь объектного литерала в методе Ext.extend() будет относится к
прототипу («классу») объекта, а вот содержимое методов будет выполнятся на уровне объекта («экземпляра класса»). При создании объекта содержимое прототипа переносится на объект. Но при этом фактически копируются только скалярные свойства (числа, строки). Свойства-объекты и методы передаются по ссылке. Любой созданный объект видит все свойства прототипа, если они у него не преопределены. Но все типы данный в JavaScript являются объектами и передаются по ссылке, лишь скаляры являются immutable (спасибо хабраюзеру mbezoyan за правильную трактовку свойств прототипа). Таким образом на уровне всех экземпляров «класса» BadPanel св-во messages будет указывать на одну и ту же область памяти. И изменение сообщения из одного экземпляра отразится на всех.

Для предотвращения таких ошибок свойства-объекты инициализируйте в методе initComponent, т.е. на уровне объекта, а не на уровне прототипа, как это сделано в «классе» GoodForm:

  1.     Ext.ux.GoodForm = Ext.extend(Ext.Panel, {
  2.       border: false,
  3.       frame: true,
  4.       initComponent : function () {
  5.         Ext.applyIf(this, {
  6.           messages : {
  7.             hello: 'Hello, World!'
  8.           },
  9.           buttons: [{
  10.             text: 'Say',
  11.             handler: function () { Ext.Msg.alert('Message', this.messages.hello); },
  12.             scope: this
  13.           },{
  14.             text: 'Change Msg',
  15.             handler: function () { this.messages.hello = 'New Hello Message'; },
  16.             scope: this
  17.           }]
  18.         });
  19.         Ext.ux.GoodForm.superclass.initComponent.call(this);
  20.       },      
  21.       html: 'Good Form'      
  22.     });    
* This source code was highlighted with Source Code Highlighter.


Так же хотим отметить, что возможность наследования не нарушется т.к. используется метод Ext.applyIf. Он установит только те свойства, которые не существуют. Таким образом, если при создании объекта GoodForm Вы передали через конфиг свой набор сообщений (messages), он не перезатрется.

По ссылке http://habrdemos.ibpro.com.ua/shared-resources-demo.html Вы можете просмотреть пример работы «хорошей» и «плохой» форм.

P.S. Как Вы заметили, инициализация кнопок в методе initComponent дала возможность упростить получение текста сообщения. Но об этом мы поговорим с Вами в следующей статье цикла, посвященной обработчикам событий.
Теги:
Хабы:
+6
Комментарии8

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн