Sqimitive.js — Frontend Primitive или «Backbone без фантиков»

d89eed12756145b992a3d3c016abd61e.pngУже довольно давно большинство сайтов перестало быть набором HTML/PHP/CSS/JS-файлов, которые достаточно просто загрузить на сервер. Bower, Grunt, Component.js, AMD, Require.js, CoffeeScript, Clojure, Composer, npm, LESS и ещё 100500 инструментов — всё это сегодня применяется для сборки проектов, обновления компонентов, загрузки зависимостей, сжатия кода, компиляции из одного JavaScript в другой, подтасовки карт, прополки огорода и даже готовки яичницы.Многих людей это вдохновляет. Да что там — 95% моих знакомых в один голос твердят, как подключив всего пару-тройку библиотек с особой, уличной магией можно забабахать сайт на over-9000 зелёных австралийских долларов — и всего за один вечер, с перерывом на кофе и бублики.

А я — странный человек. Не люблю смешения языков, технологий, библиотек. Angular, Knockout, React — они все хороши, но каждая — по-своему сложна. А ведь есть и «гибриды», где сходится сразу несколько миров — как Ember и Knockout.Bootstrap. Вдобавок, многие построены на jQuery — впрочем, к ней даже у меня претензий нет; наверное, таким и должен был быть JavaScript.

Как бы то ни было, реальность беззастенчиво входит в контакт с мечтами и расставляет точки над «i». Мне так же приходится писать на «new & popular» —, а когда пишешь, душа томится и просится создать очередной велосипед…, а ей разве откажешь? Она ведь как дитя малое.

Велосипед был создан. Велосипед без фантиков. Такой же простой, как автомат Калашникова, и многогранный, как швейцарский нож, где вместо наследования — события, вместо моделей, коллекций и представлений — один класс, с неограниченной вложенностью и полной свободой действий, почти в два раза меньший Backbone.js, использующий Underscore.js и, необязательно, jQuery/Zepto.

Добро пожаловать в Sqimitive.

Как всё начиналосьЯ — фрилансер. «Фри» в данном случае обозначает прямо противоположное, поэтому по долгу службы я работаю над многими проектами, со многими технологиями и иногда подолгу. Последние два года было много работы именно с Backbone. В течении этого времени у меня накопился вагон и маленькая тележка наблюдений и замечаний касательно этой, в общем-то, хорошей библиотеки. В итоге они вылились в нечто новое. То, что я назвал «Sqimitive».Почти весь 2014 год мне повезло проработать в нью-йоркской фирме «Belstone Capital». Это превосходное место для серьёзной деятельности. Идеал души фрилансера (хотя эта же душа не даёт подолгу работать в одном, даже самом идеальном, месте). Sqimitive была создана именно там.

Сердечное спасибо коллегам из Belstone, которые на мою просьбу выложить часть внутреннего кода в открытый доступ ответили: «Go ahead».

Сейчас Sqimitive около 9 месяцев от роду. Она лежит в основе двух проектах этой компании, в сумме на 15–20 тысяч строк (это забавно, учитывая её собственный размер в 700 строк без комментариев). API последние месяцы не менялся, серьёзных ошибок замечено не было уже совсем давно. Код готов к использованию в производственной среде. Его можно найти на GitHub, полную документацию на 55 страниц — на squizzle.me и там же можно посмотреть пример простой To-Do App.

Ниже в этой статье я опишу 90% возможностей библиотеки, с уймой примеров, теории и лирики. А начнём мы с фундаментальных ям JavaScript и Backbone. Кстати, если вы знаете Backbone — Sqimitive вам покажется очень знакомым, тёплым и почти ламповым.

(Здесь и далее — моя субъективная точка зрения, которая может не совпадать с вашей, Хабра, президента или Космической коалиции. Читайте на свой страх и риск, не забывайте комментировать и помните о своей карме!)

Оглавление:

Наследование как фактор выживания Превращение методов в события Наследование на лету или прототипирование-2.0 О важных связях с общественностью Вложенность — наше всё UnderGoodness Что в опциях тебе моём? Нерадивые родители: деревья и списки Представляем виды О бренности жизни без сохранения данных assignResp и _respToOpt — карта ответа API И это что, всё? _shareProps, _mergeProps, masker () Наследование как фактор выживания Если бы JavaScript был Haskel, то у него бы не было этой проблемы (отчасти потому, что на нем бы никто не писал). Если бы JavaScript был Си, то у него были бы другие проблемы — может, много проблем, но точно не таких.Но JavaScript — это и не Haskel, и не Си. У него было тяжёлое детство с непостоянными родителями, которые навсегда повредили его это this. Так что теперь это непостоянство расхлёбывают программисты.

Функции в JavaScript — это обычные значения вроде чисел и строк (т.н. first class citizen). Функции можно присваивать, удалять, копировать, даже преобразовывать в строку. Плюс к этому функция имеет неявный параметр — this — который по задумке авторов языка должен указывать на объект, который вызвал срабатывание этой функции —, а никак не на объект, к которому эта функция была привязана.

Таким образом, объекты в JavaScript как бы есть, но получить контекст объекта — невозможно. Объекты в этом смысле — это те же ассоциативные массивы. Видимо, в этом суть концепции Java (ООП) + Script (ФП). Шутка.

(Прошу не принимать мой сарказм близко к сердцу людям с плохим чувством юмора. Я люблю и JavaScript, и Haskel, но я точно так же осведомлён об их… особенностях и стараюсь их чётко обозначить, чтобы найти им хорошее решение. А вообще, пора бы уже Ruby захватить мир.)

Классический пример (JSFiddle):

function Obj () { this.property = 'Hello!'

this.show = function () { alert (this.property) } }

var obj = new Obj setTimeout (obj.show, 100) // alert ('undefined') Причина — в том, что мы считали значение-функцию, которое записано в свойстве show объекта Obj, и передали её в setTimeout, которая просто её вызвала — в отрыве от Obj, в контексте window. Здесь obj для нас — всё равно, что безликий массив.Впрочем, проблема с непостоянным this худо-бедно, но решается — и даже товарищи из ECMAScript в конце концов сдались и спустя каких-то 16 лет (в 2011 вместе с 5.1) к Function был добавлен bind (), фиксирующий this в одном положении.

Другая особенность JavaScript — отсутствие в языке ссылки на базовый класс и вообще понятия «базового класса». JavaScript использует прототипное наследование, что в общем означает следующее: каждая функция (она же конструктор, который вы вызываете как new Func) имеет так называемый «прототип». При new Func происходит копирование полей этого прототипа в новый экземпляр объекта. «Наследование» в понятиях традиционного ООП — отсутствует, вместо этого прототип копируется в другой прототип, то есть все его поля копируются в другой объект: переменные и методы-функции — которые, как уже сказано, обычные значения, которыми можно манипулировать. Затем на новом прототипе делаются все изменения, которые предписывает «наследование» (перекрываются методы, добавляются поля и т.п.).

Фактически же мы получаем два независимых класса-прототипа.

Эта техника призвана бороться с некоторыми недостатками классического ООП — хрупким базовым классом, коллизиях при множественном наследовании и другими нюансами. Важную проблему в JavaScript решали, в целом, правильными методами, но плавающий this, функции-значения (невозможность определить своё имя в рамках объекта без прямого перебора всех полей) и отсутствие простых штатных связей между прототипами в сумме вызывают кровь и ярость.

В традиционном ООП наследование — это когда один объект копирует другой (возможно, с изменениями), но между ними сохраняется связь родитель-потомок. Теперь посмотрим на ООП в JavaScript (JSFiddle):

function Base () { // Пустой конструктор. }

Base.prototype.property = 'Hello!'

Base.prototype.show = function () { alert (this.property) }

function Child () { // Пустой конструктор. }

// Копируем прототип базового «класса». for (var prop in Base.prototype) { Child.prototype[prop] = Base.prototype[prop] }

// Так мы можем не указывать базовый класс явно при перекрытых вызовах (см. ниже). Child.__super__ = Base.prototype

Child.prototype.show = function () { // Вызвать унаследованный код? Child.__super__.show.call (this) } Как видно, здесь функция show, которая привязана к Child, не знает ни своего имени (она может быть привязана под разными именами, много раз, к разным прототипам), ни имени базового класса, ни даже this, если мы сделаем что-то вроде setTimeout ((new Child).show, 100).Нам остаётся только вшить (hardcode) эти значения в код самой функции. Понятно, что это — плохой путь:

Меняется имя класса — нужно изменить все ссылки на него Меняется имя функции — нужно также изменить все ссылки Копируется функция — нужно менять имя (как часто это забывается) Копируется класс — ну, вы поняли Это не говоря о том, что писать Foo.__super__.bar.apply (this, arguments) — как минимум утомительно и неэстетично. А отладка забытых непереименованных ссылок может сравниться разве что с изучением чёрной магии…Тем не менее, именно такой принцип используется в Backbone (в Angular, Knockout, React вы фактически пишите на «полу-ООП», где явно указываете предков при вызове, что не многим лучше). Хорошее решение — у Ember, с его автоматическим this._super:

Child.reopen ({ show: function (msg) { this._super (msg + '123') }, }) Но Ember — это 48 000 строк чистого JavaScript. Неужели нельзя проще?…Можно. Sqimitive решает эту проблему так (JSFiddle):

var Base = Sqimitive.Sqimitive.extend ({ property: 'Hello',

show: function (right) { alert (this.property + right) }, })

var Child = Base.extend ({ property: 'Bye',

events: { '=show': function (sup, right) { sup (this, [' World' + right]) }, }, })

;(new Base).show ('123') // alert ('Hello123')

;(new Child).show ('123') // alert ('Bye World123') Кроме того, вы можете использовать стандартный вариант с __super__ — оставлен для любителей стрелять себе по ногам и для поддержки legacy-кода (JSFiddle): var Base = Sqimitive.Sqimitive.extend ({ // Как выше. })

var Child = Base.extend ({ property: 'Bye',

show: function (right) { Child.__super__.show.call (this, ' World' + right) }, }) Превращение методов в события Блок events в Sqimitive определяет новые обработчики, которые вызываются для событий объектов этого класса. Когда имя события совпадает с именем уже существующего метода — этот метод (в примере — show) заменяется на firer ('show') — функцию, которая при вызове инициирует одноимённое событие. Заменённый метод (например, унаследованный) ставится в начало цепочки обработки, а заменяемый — после него. Таким образом, сохраняется логика выполнения. Нет нужды изменять что-либо при изменении структуры базового или наследующего класса.Если же метода не было — новый метод становится единственным обработчиком. Если же под данным именем значится не метод, то это свойство перезаписано не будет и событие можно будет вызвать только явно, через fire ().

Таким образом, любой метод класса — возможная точка привязки события, а сами события можно возбуждать как явно через fire ('event'), так и вызывая метод на самом объекте — если это событие, то оно будет инициировано благодаря firer (), а если нет — функция будет вызвана напрямую (фактически это единственный обработчик события). Трансформация метода в событие делается прозрачно и на лету.

При этом стоит отметить, что для пуристов, которые борются за наносекунды — всё чисто. Если вам важна производительность конкретного метода или класса — просто определите его как обычно, без события (см. пример выше с __super__) — тогда он будет вызываться напрямую, минуя fire (). Причём сделать это можно и в базовом классе, и в потомках, и уже имея перекрытые методы-события. Нужно только следить, чтобы в последствии никто не создал из этого метода событие, иначе наносекунды потекут не в ту сторону.

Как показал мой опыт, полная замена метода, как в примере выше — штука довольно редкая. В Sqimitive есть ещё три типа добавления обработчика, которые различаются префиксом (знак равно выше — один из них):

Без префикса — самый часто используемый тип. Добавляет обработчик после существующих и игнорирует результат вызова. Минус (-) — добавляет обработчик перед существующими и игнорирует результат. Плюс (+) — как без префикса, только передаёт текущий результат в первом параметре и ожидает получить новый результат от функции (если она вернёт undefined — сохраняется прежний; именно это и происходит, если функция вернулась без return). Равно (=) — уже показанный вариант, когда обработчик родителя перекрывается целиком и у новой функции есть выбор — вызывать его или нет, с какими аргументами, в каком контексте и что делать с результатом. Оригинальная функция передаётся в виде первого параметра, для краткости вызываемая как sup (context, argArray). Во всех случаях параметры события передаются каждому обработчику.Первый тип покрывает 50% причины для перекрытия методов, второй и третий — ещё 40%. var Child = Base.extend ({ events: { show: function (msg) { // Действие совершилось — нужно обновить что-либо, почистить кэш, // разослать оповещания или что-то ещё. this.render () },

'-show': function (msg) { // Сделать что-то до того, как произойдёт действие — выполнить проверку // и выбросить исключение, сохранить старое значение и прочее. if (msg.length < 3) { throw 'Сообщение для show() должно иметь хотя бы 3 символа.' } },

'+show': function (res) { // Проверить результат, возможно сохранить или изменить его и вернуть новый. return '(' + res + ')' },

'=show': function (sup, msg) { // Новая логика, которая требует целиком новой функции. В дикой природе // встречается редко. return '(' + sup (this, [msg + ' foo!']) + ')' }, }, }) Кроме того, обработчики могут быть строками — если нужно просто вызвать метод с этим именем с оригинальными параметрами. Это сильно сокращает код и делает его понятнее: var Child = Base.extend ({ events: { // Вызывает render () с аргументами, переданными show. Результат отбрасывает. show: 'render', }, }) Наследование на лету или прототипирование-2.0 Итак, у нас есть наследование через события… А обязательно ли его проводить во время объявлении класса через extend? Как понятно из названия — нет. События — они динамические, их кашей не корми, дай только возбудиться. Да, главное, побольше обработчиков!

var Base = Sqimitive.Sqimitive.extend ({ property: 'Hello',

show: function (right) { alert (this.property + right) }, })

var base = new Base base.on ('=show', function (sup) { sup (this, [' — I alert']) }) Результат (JSFiddle) аналогичен тому, как если бы мы унаследовали от Base новый класс и перекрыли там метод. Здесь же мы сделали это на «живом» объекте, единственном в своём роде. В добавок — как сделали, точно так же можем и убрать (JSFIddle): base.off ('show') Но будьте осторожны: это уберёт все обработчики события show, кроме «припаянных» — fused (наследованные, к примеру, одни из таких). Если мы хотим убрать именно наш — используем его идентификатор (JSFiddle): var handlerID = base.on ('=show', function (sup) { sup (this, [' — I alert']) })

base.off (handlerID) А что произойдёт с методом, который мы перекрыли — Base.show? Как видно в JSFiddle, он восстановится, как только его =show-обработчик будет снят. Всё, как у людей.Естественно, другие префиксы можно использовать точно так же, как они используются в блоке events.

Кроме on и off в нашем распоряжении есть и once — полностью аналогичен on, но отменяет обработчик после того, как он был вызван ровно один раз.

О важных связях с общественностью До поры до времени объектов мало, приложение простое, памяти много и вообще полный мир и идиллия. Но так бывает не всегда.Для приложений средней руки классов становятся десятки и сотни, а объектов за тысячи. Они постоянно заменяют друг друга и борются за место под солнцем в тесной песочнице DOM. В такой ситуации оставлять их все висеть в фоне — не гуманно. И в то же время не понятно, как управлять их связями — когда именно объект создаётся и «подключается» к матрице, а когда — удаляется, и как отключить его обработчики при переходе из бренного мира к праотцам?

В Backbone появились методы listenTo и stopListening (изначально их не было), которые позволяют запоминать связанные объекты и избавляться от связей с ними. Однако сам Backbone не содержит логики вкладывания этих объектов. Модели в коллекциях не считаем — основная проблема именно в постоянной циркуляции представлений (или видов, View).

В Sqimitive есть и аналог listenTo, и вложенность объектов. О последней подробно поговорим дальше в статье, а пока простой пример:

var Bindable = Sqimitive.Sqimitive.extend ({ // opt (option) в терминах Sqimitive аналогичен attribute в Backbone: он точно так же // возбуждает событие при изменении значения и имеет пару-тройку других особенностей. _opt: { // Этот флаг будет нам говорить, были ли инициализированы обработчики или нет. wasBound: false, },

events: { // postInit вызывается после того, как объект был создан. Можно заменить // на owned — после того, как объект был вложен в другой. postInit: 'bindAll', // unnest вызывается для удаления объекта из списка родителя. '-unnest': 'unbindAll', },

bindAll: function () { // ifSet возвращает true, если новое значение опции было отличным от старого. this.ifSet ('wasBound', true) && this.bind (this) // sink вызывает указанный метод на всех вложенных объектах, рекурсивно. return this.sink ('bindALl') },

unbindAll: function () { if (this._parent && this.ifSet ('wasBound', false)) { this.unbind (this) } return this.sink ('unbindAll') },

// Здесь наследованные классы уже указывают свою логику — регистрируют // обработчики, связываются с другими объектами и прочее. Гарантированно // вызывается один раз, если не был вызван unbind. bind: function () { },

// Отменяет действия bind — удаляет обработчики. Вызывается только один раз, // если не был вызван bind. unbind: function (self) { // autoOff без параметров — аналог stopListening. Удаляет обработчики с // объектов, которые были зарегистрированы через autoOff ('event') — см. ниже. this.autoOff () }, }) Теперь мы можем наследовать Bindable, наполнив его своей логикой. В большинстве случаев выглядит это так: var MyObject = Bindable.extend ({ _opt: { someObject: null, // некий объект Sqimitive, который мы «слушаем». },

events: { bind: function () { this.autoOff (this.get ('someObject'), { event1: …, event2: …, }) }, }, })

new MyObject ({someObject: new X}) Здесь MyObject создаётся с опцией (параметром) someObject, к которому затем добавляются обработчики двух событий: event1 и event2. Делается это через autoOff, который аналогичен on, но добавляет данный объект в список зависимостей и затем, когда вызывается unbind, autoOff () без параметров удаляет все обработчики своего объекта (MyObject) со всех объектов, для которых он ранее был вызван (someObject).Заметьте, что это не стандартное поведение Sqimitive, это уже наш собственный код, который можно заложить в базовый для вашего приложения класс Sqimitive.

Третий параметр к autoOff — необязательный контекст, который изначально устанавливается в зависимый объект (а не тот, к которому добавляется обработчик). В связке с именами методов-обработчиков вместо замыканий это даёт довольно компактный синтаксис:

this.autoOff (someObject, { // Вызвать render () на this при событии change в someObject. change: 'render', nest: 'render', })

// Аналогично следующему: someObject.on ('change', function () { this.render.apply (this, arguments) }, this) someObject.on ('nest', function () { this.nest.apply (this, arguments) }, this) У этих методов есть и другие особенности — подробности см. в документации.Вложенность — наше всё В Backbone, на мой взгляд, очень мало внимания (читай — никакого) уделено вкладыванию объектов друг в друга. А ведь это крайне важная их особенность. Проекты наподобие Marionette.js пытаются компенсировать этот недостаток, но это как раз тот случай, когда библиотека зиждется на библиотеке, всё это как-то собирается и даже работает, но потребляет столько космической энергии, что лучше бы все сидели по домам. А в случае ошибки — не понятно, кого ругать — авторов Backbone за отсутствие штатных средств, авторов Marionette за их логику, себя — за несовместимое с ними мировоззрение, или JavaScript — просто потому, что он «не такой, как все».Кроме того, Marionette — это ещё 4 000 строк кода в добавок к существующим зависимостям. А ведь каждая строчка — потенциальная ошибка, каждый метод — новая статья в документации (Marionette, впрочем, таковой просто не имеет).

В Sqimitive концепция родитель-потомок заложена на втором уровне. Сама библиотека разбита на две части в виде двух классов: Sqimitive.Core и Sqimitive.Sqimitive. Core — событийное ядро, всё то, что я уже описал выше. Sqimitive — его наследник, добавляющий опции и вложенность.

Именно Sqimitive даёт тот конечный функционал, который нужен в приложениях. Core можно наследовать, если вы хотите внедрить в свой класс только событийный (и наследующий) механизм.

В библиотеке Sqimitive нет разделения на модель, коллекцию и представление (M-C-V). Единый класс обладает как атрибутами (присущи моделям в Backbone) — их зовут «опциями», так как они передаются в конструктор, а также может содержать вложенные объекты определённого класса, над которыми можно проводить фильтрацию (доступен весь набор методов Underscore.js), автоматически перенаправлять их события родителю и вообще трактовать как некую совокупность, над которой можно работать как с чем-то единым, безликим, а не с каждым объектом в отдельности.

Для индивидуальной работы как раз подходит _opt, где каждый элемент — нечто особенное, и каждое движение (доступ, замена) можно отследить, перекрыв ifSet и get, добавив normalize_OPT, реагируя на change_OPT и change — об этом будет ниже.

В противоположность этой дзенской простоте Marionette, Ember и другие — сложны. В Ember есть разные типы свойств (computer, observers, bindings), в Marionette — разные типы представлений (раскладки, регионы, представления элементов и коллекций, составные). Конечно, это всё полезно — для определённого уровня приложений и команд. Но для многих других это всё равно что стрельба из пушки по воробьям. Дыма и шума много, публика довольна, но само действо не эффективно и трудозатратно. К тому же нужно для начала изучить, как летают пушки воробьи и ядра.

Интересно и то, что даже наличие таких готовых средств не гарантирует, что вам не будет проще написать какую-то их часть заново конкретно под вашу задачу.

Ниже — пример объявления вкладываемых классов в Sqimitive:

var MyItem = Sqimitive.Sqimitive.extend ({ // Какой-то набор атрибутов данной модели. _opt: { complete: false, something: 'bar', }, })

var MyCollection = Sqimitive.Sqimitive.extend ({ _childClass: MyItem, _childEvents: ['change', 'foobar'],

_opt: { // Сделаем так, чтобы коллекция не допускала объекты с ! complete. allowIncomplete: false, },

events: { // Опция изменилась — перепроверим всё, что вложено. change_allowIncomplete: function (newValue) { newValue || this.each (this._checkComplete, this) },

// Вложенный объект изменился — перепроверим его. '.change': '_checkComplete',

// Добавили новый объект — проверим, что с ним. '+nest': '_checkComplete', },

_checkComplete: function (sqim) { if (! this.get ('allowIncomplete') && ! sqim.get ('complete')) { throw 'This collection only allows complete items!' } }, }) Мы объявили два класса: MyItem, который имеет опцию (атрибут) complete, и MyCollection, который: Содержит экземпляры MyItem, на что указывает свойство _childClass Автоматически возбуждает события .change и .foobar (с лидирующей точкой), если change и foobar (без точки) возникли в одном из объектов, которые он содержит Имеет опцию allowIncomplete, которую использует для проверки всех вложенных объектов (их complete должно не быть false, если allowIncomplete не установлен) При изменении allowIncomplete в false автоматически происходит проверка всех вложенных объектов При изменении вложенного объекта (благодаря событию .change) происходит проверка этого объекта При добавлении (nest) нового объекта также происходит его проверка Вот пример использования, когда коллекция изначально не допускает не-complete объекты (JSFiddle): var col = new MyCollection

var item1 = new MyItem col.nest (item1) // exception

var item2 = new MyItem ({complete: true}) col.nest (item2) // okay

item2.set ('complete', false) // exception А вот — когда флаг allowIncomplete меняется на ходу (JSFiddle): var col = new MyCollection ({allowIncomplete: true})

var item1 = new MyItem col.nest (item1) // okay

col.set ('allowIncomplete', false) // exception Внимание: в случае не прохождения проверки исключение будет выброшено, но объект останется частью коллекции. В реальной жизни изменение либо нужно блокировать (слушая .-change и -nest), либо удалять неугодный объект (при change_allowIncomplete).UnderGoodness Sqimitive внутренне использует Underscore.js — библиотеку с функциями общего назначения, во многом перекрывающую функционал новых версий ECMAScript. Особенно много удобных функций имеется для работы с наборами данных — массивами и объектами.Большую часть этих функций (около 40) можно использовать и на объекте Sqimitive для работы с его вложенными объектами.

Ниже — пример использования наиболее полезных методов на примере MyCollection, описанного выше. Полный список с описаниями приведён в документации.

var col = new MyCollection col.nest (new MyItem ({complete: true})) col.nest (new MyItem ({something: 'item2'})) col.nest (new MyItem ({complete: true, something: 'item3'}))

var completeCounts = col.countBy (function (sqim) { return sqim.get ('complete') ? 'done' : 'undone' }) // completeCounts = {done: 2, undone: 1}

var isEveryComplete = col.every (function (sqim) { return sqim.get ('complete') }) // isEveryComplete = false, так как не все элементы имеют complete == true.

var allComplete = col.filter (Sqimitive.Sqimitive.picker ('get', 'complete')) // Итератор, сгенерированный picker () — идентичен тому, что выше. // allComplete = [MyItem item1, MyItem item3] — два объекта с complete == true.

var firstComplete = col.find (Sqimitive.Sqimitive.picker ('get', 'complete')) // firstComplete = MyItem item1 (её complete == true). Либо undefined,

var doneUndone = col.partition (Sqimitive.Sqimitive.picker ('get', 'complete')) // doneUndone = [[item1, item3], [item2]] — фильтрует объекты, помещая // прошедшие условия в первый массив, а не прошедшие — во второй.

var firstChild = col.first () var lastChild = col.last () var parentKeys = col.keys () var three = col.length var item2 = col.at (1) var item2_3 = col.slice (1, 1)

var somethings = col.invoke ('get', 'something') // somethings = ['bar', 'item2', 'item3'] — вызывает метод с параметрами // и возвращает массив результатов, по результату для каждого объекта в col.

var sorted = col.sortBy (Sqimitive.Sqimitive.picker ('get', 'something')) // sorted = [item1, item2, item3] — массив вложенных объектов, отсортированных // по значению, которое вернул итератор.

var serialized = col.invoke ('get') // Аналог Backbone.Collection.toJSON (), который делает shallow copy.

col.invoke ('render') // Вызывает render () на всех вложенных объектах. Часто используется.

var cids = col.map (function (sqim) { return sqim._cid }) // cids = ['p11', 'p12', 'p13'] — почти как invoke (), только использует // результат вызова замыкания. _cid — уникальный идентификатор объекта.

col.each (function (sqim, key) { alert (key + ': ' + sqim._cid) }, col) // Вызывает итератор 3 раза в контексте col (this). Что в опциях тебе моём? Опции или атрибуты — необычайно полезная вещь для любого типа класса, а не только моделей, как это сделано в Backbone. Это основа для state-based programming, когда ваш код реагирует на изменения сразу, а не проверяет их в местах, где от их состояния зависит какой-то результат (тем более обычно их много и из всевозможных вызовов _updateSize и _checkInput получаются отличные макароны).Самый простой пример — изменение параметров представления. Сейчас очень модно говорить о шаблонах, самообновляющихся при изменении свойств модели — этим славятся Angular, Knockout и, конечно, React. В Sqimitive можно делать нечто подобное, только здесь нет зависимостей от шаблонизатора (вы можете вообще весь HTML писать вручную), моделей (все данные могут быть в самом представлении или разбросаны по разным объектам), события нужно расставлять самому, а изменять при их срабатывании можно всё что угодно.

var MyView = Sqimitive.Sqimitive.extend ({ _opt: { name: 'Иван', surname: 'Петрович', age: 900, },

events: { change: function (opt, value) { this.$('.' + opt).text (value) },

render: function () { this.el.empty () .append ($('

').text (this.get ('name'))) .append ($('

').text (this.get ('surname'))) .append ($('

').text (this.get ('age'))) }, }, }) Это очень простой пример (JSFiddle) и у него есть очевидные недостатки: Данные хранятся в самом объекте-представлении. Для простейших приложений (или простых классов в сложных приложениях) это — оптимально, но всё же желательно держать их в отдельном объекте, которым можно обмениваться, события которого можно слушать, который можно добавлять в коллекции и прочее. HTML задан прямо в коде класса. Пуристы не оценят, да и вообще это не очень удобно — к тому же страдает подсветка синтаксиса. Мне нравится использовать Handlebars, но он объёмный и для простых случаев вполне подойдёт встроенный в Underscore шаблонизатор template (). Вариант с change — короткий, но опасный, так как мы не проверяем opt и она вполне может отличаться от name, surname и age, которые мы хотим обновлять var MyModel = Sqimitive.Sqimitive.extend ({ _opt: { name: 'Иван', surname: 'Петрович', age: 900, }, })

var MyView = Sqimitive.Sqimitive.extend ({ _opt: { model: null, },

// Естественно, код шаблонов лучше всего выносить прямо в код самой страницы // как