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

Комментарии 33

Интересно сделано наследование — это то, чего в Backbone не хватает «из коробки».
Не только Backbone. Мне не встречалась ни одна библиотека, где было бы вменяемо сделано наследование. Собственно, именно это и было основной причиной для создания своего велосипеда. Результат мне нравится.
Это ваш проект? Круто.
Пост написан от первого лица и у него нет пометки «Перевод» :)
Не маловато ли для одной причины ;)? Просто в Backbone (extend из underscore) надо добавить всего-то пару строк и будет тоже самое о_О В конструктор базового класса ссылку на extend, а в самом методе сменить источник исходного объекта, ну и сделать добавление этого же самого extend-а в возвращаемые им констукторы…
я конечно понимаю, что «700 строк без комментариев» и «Код готов к использованию в производственной среде», но где же юнит-тесты?.. btw, язык и документация просто прекрасны, я аж зачитался.
Рискую быть закиданным помидорами, но до отдельных тестов для Sqimitive я уже не добрался. Хватило и тех, которые писал для связанных проектов. Впрочем, это всегда можно поправить.

Рад, что тексты понравились.
`Sqimitive.Sqimitive`, может стоило как то более логично обозвать, ну скажем `Sqimitive.Primitive`, раз это «The Actual Building Block»?
Штука обещает быть классной, обязательно где нибудь попробую.
Я действительно думал над этим, но в итоге решил оставить как есть, чтобы не было путаницы, какой класс наследовать. По задумке, когда вы используее Sqimitive, то наследуете в вашем приложении базовый класс, где собственно Sqimitive.Sqimitive используете единожды, а дальше вы упоминаете его только как App.MyBaseObject или App.Sqimitive (понятно, что ограничений тут никаких нет).
Грандиозная работа.

Смотрели фреймворк canjs.com? Значительная часть того, что делали вы там реализована, либо очень близка.
Я переписывал его значительную часть под свои нужды и решил многие из перечисленных вопросов довольно быстро.

Отказ от распределённой архитектуры (MVC) мне кажется несколько странным, лично мне, например, гораздо удобнее написать

var artist = App.model.Artist.getById({id: 25, cache: 1800});
  , tracks = App.model.Track.List.getByArtistId({id: 25, limit: 80});

$.when(artist, tracks).than(function(artist, tracks) {
    ...
}

Трюк в том, что при распределении работ в команде тебе достаточно знать интерфейс класса, и не задумываться как оно там внутри работает и выбирается.

По факту, большинство методов моделей описывалось всего одной строкой, типа:
'getById': '/artist/:id'

Либо чуть более сложной конструкцией, с конкретизацией как тянем данные:
return this.socket('artist/' + id)


Трюк с + — = мне очень понравился, крутая идея. Вопрос такой: что если мы наследуемся от метода, в котором уже реализован +, и в нашем тоже есть +. Они оба выполнятся или будет перекрытие? Если оба, то очень круто. Вот эту идею обязательно позаимствую и использую в следующем крупном js проекте.

Может быть созрею на написание аналогичной статьи, спасибо :-)

Если есть желание, могу через скайп показать код пары проектов.
Смотрели фреймворк canjs.com?

Нет, первый раз слышу. Бегло просмотрел API и код — на первый взгляд кажется того же масштаба, что и Ember. Модули, много кода, разные компоненты. Особенно конструкции вроде таких меня сразу вгоняют в тоску:

	/**
	 * @add can.Component
	 */
	var Component = can.Component = can.Construct.extend(
		
		// ## Static
		/**
		 * @static
		 */
		
		{


Из 11 строк только одна несёт полезную нагрузку.

Sqimitive — это полная противоположность. Здесь ничего нет, всё собирается вами, используя тонко подогнанный и маленький набор универсальных инструментов… А дальше — как глина. Хотите коллекции, модели и виды? Не вопрос, просто наследуйте их (кстати, оба проекта Belstone как раз и используют разделение на M-C-V, потому что это удобно).

Просто я не вижу, зачем разделять их на уровне библиотеки, так как все свойства т.н. моделей (атрибуты), коллекций (фильтрация + вложенность) и представлений могут быть полезны для любого другого типа класса. Не хотите — не используйте вложенность в моделях, это ваше дело.

Больше того, имена методов я специально выбирал не совсем стандартные (можно было назвать add, а не nest) — именно для того, чтобы их можно было перекрыть и назвать в соответствии со стилем в вашем проекте. Нужен getById? Сделайте его, используя доступные методы. Аналогично со всем прочим.

Трюк в том, что при распределении работ в команде тебе достаточно знать интерфейс класса, и не задумываться как оно там внутри работает и выбирается.

Всё верно, поэтому Sqimitive в серьёзном проекте напрямую не будет использоваться — между ним и приложением будет небольшая (или большая) прослойка-переход в виде тех же M-C-V. Её ведь всё равно приходится делать на том же Backbone. Потому что всегда свой подход хоть в чём-то, но не совпадает с подходом авторов библиотеки.

Либо чуть более сложной конструкцией, с конкретизацией как тянем данные

Важно соблюдать баланс между «преднаписанным» API и временем, которое требуется на его изучение. Можно подумать, что если в Ember почти 50к строк кода, то вам ни строчки не придётся писать самому. Ан-нет, костыли будут точно так же, как и при использовании другой библиотеки. Ещё и грузится дольше будет. (Ну, я утрирую, конечно.)

Короче, из Sqimitive можно сделать основу для любого проекта, имея в её, гм, основе компактный код вместо многотысячных библиотек, которые так и так приходится доделывать. Кому-то такой подход нравится, кому-то нет.

Вопрос такой: что если мы наследуемся от метода, в котором уже реализован +, и в нашем тоже есть +. Они оба выполнятся или будет перекрытие?

Так вы наследуете не метод, вы перекрываете (с =) или расширяете (в других случаях) цепочку событий. Если у вас в базовом классе уже есть +event, а в потомке — ещё один +event, то в цепочке первым окажется метод (обработчик события) базового класса, после которого пойдёт метод потомка.

Представьте, что когда вызывается extend() вы блок events берётся и передаётся в NewClass.on({events}). Это и есть наследование в стиле Sqimitive.

Если есть желание, могу через скайп показать код пары проектов.

Напишите в личку подробности.
> Здесь ничего нет, всё собирается вами
На главной странице есть кнопка Costumize — бери что надо.

> Могут быть полезны для любого другого типа класса
Не вижу причин атрибутов с датакастами, конвенторами данных и т.п. где-нибудь кроме моделей :-)
Таких примеров много, на самом деле.
Там есть базовый класс — если нужно берешь только его, и получаешь библиотеку по объему в 1155 строк, часть из которых комментарии.

> Нужен getById? Сделайте его, используя доступные методы
Ну так метод и реализуется в модели. Причем реализуется одной строкой, если не специфичный. В результате получаешь ощутимо меньше кода.

> между ним и приложением будет небольшая (или большая) прослойка-переход в виде тех же M-C-V.
Я не совсем понял о прослойке какого рода ты говоришь. Накручивать абстракции вокруг базовых классов или бекэнд? Я бекэнд вообще не подразумевал.

> Потому что всегда свой подход хоть в чём-то, но не совпадает с подходом авторов библиотеки.
Конечно, для этого есть форки :-) Большинство библиотек позволяют их модифицировать не мешая основному коду программы, тем самым получаем свою сборку, которая легко обновляется до более новых версий.
Если ты имеешь ввиду «избыточность», то она, обычно, не особо критична. Бонусом является то, что код отлично оттестирован, в отличии от самописного.

> Ember почти 50к строк
Я сам не люблю избыточность. Очень не люблю. Мне нравится подход, когда ты точно знаешь как что работает, и что происходит.
Canjs в максимальной сборке состоит из 11.5 тысяч строк, из которых значительная часть это комментарии и куски которые вгоняют тебя в тоску :-)
Надо писать комментарии, которые в тоску не вгоняют, например:
/**
 * Get all issues for current department
 * @static @public
 * @param  {string} params.type all|my
 * @param  {string} params.skip from start
 * @return {object} $.Deferred -> models object
 */
getAll: function(params) {
    if (params == undefined) {
        params = {};
    }
    return this.socket('issue/list/'
        + App.user.curDepartment + '/'
        + (params.type == undefined ? 'my' : params.type) + '/' 
        + (params.skip == undefined ? 0 : params.skip), {filter: params.filter});
}


>основе компактный код вместо многотысячных библиотек
Ну вот, например, сборка из живого проекта состоит из ~5к строк 3.5 тысячи строк — комментарии кода. Причем довольно качественные, которые круто спасают при дебаге. Т.е. на выходе имеет 1.5 тысячи строк для изучения. Уже не так страшно, да?
Промахнулся веткой. Это ответ на комментарий выше
Ctrl+Enter + таймаут редактирования. Мда.

На главной странице есть кнопка Costumize — бери что надо.

Не буду спорить, так как CanJS не знаю.

Не вижу причин атрибутов с датакастами, конвенторами данных и т.п. где-нибудь кроме моделей :-)

Просмотри ещё раз про state-based programming. Опции — они полезны не только в моделях, более того, они наиболее полезны именно в представлениях. Смотри на мир шире. Не обязательно для каждого чиха создавать свой API.

Ну так метод и реализуется в модели. Причем реализуется одной строкой, если не специфичный. В результате получаешь ощутимо меньше кода.

А если у модели нет ID? Специфичный ID? Как ты реализуешь общую версию этого метода? Придётся его перекрывать в потомках? А как они отреагируют на составной ID (из нескольких полей)?

Зачем вообще обобщённое понятие ID? Его можно избежать, добавив только там, где нужно. Кода на это потребуется минимумально.

Накручивать абстракции вокруг базовых классов или бекэнд?

Абстракции. Если в твоём проекте/тебе удобно использовать виды, модели и коллекции — наследуй от Sqimitive 3 класса, будут тебе обычные M-C-V. Так я делаю и не вижу в этом каких-либо проблем. Если же у тебя какой-то проект на 1000 строчек, который не будет развиваться — тебе, может, и одного хватит.

Бэкенд это другая тема.

Большинство библиотек позволяют их модифицировать не мешая основному коду программы, тем самым получаем свою сборку, которая легко обновляется до более новых версий.

Хорошая библиотека не должна модифицироваться, это на мой взгляд грубое нарушение принципов модульности. Естественно, если делается свой форк — это одно, но отдельный форк на каждый проект? По-моему перебор. Иначе бы не было такого понятия, как плагины (которые я видел в том же CanJS).

И про обновление ты явно хватил. Обновить Knockout с парой-тройкой плагинов? Ты знаешь его настолько хорошо, чтобы лазить в нутрях?

Бонусом является то, что код отлично оттестирован, в отличии от самописного.

Дело-то в том, что избыточный код не всегда даёт тебе всё, что нужно, и его тоже приходится доделывать. Либо искать, где там разработчики расставили свои капканы и почему твоя логика не согласуется с их. Я особенно сильно это прочувствовал, воюя с Backbone'овским sync.

Мне нравится подход, когда ты точно знаешь как что работает, и что происходит.

Это мне близко и я лишь говорил о том, что 1600 с комментариями и 11.5к — это всё же разные степени «когда ты точно знаешь как что работает». Я не уверен, что даже удалив все комментарии ты сможешь разобраться в таком объеме быстрее, чем в Sqimitive. Но, может быть, дополнительная функциональность для тебя более важна, и это нормально. Просто это не мой выбор, я вообще довольно аскетичен в выборе средств и инструментов.

.е. на выходе имеет 1.5 тысячи строк для изучения. Уже не так страшно, да?

Да, не так. Но, насколько я понял, CanJS это около 6к?
Просмотри ещё раз про state-based programming.

Если ничего не путаю, именно такой подход я везде и использую.
Только тут такой момент — любые данные укладываются в модель. Исключения очень редки. Как результат мы отслеживаем состояние модели
Пример из вьюшки:
/**
 * Change label and remove close buttons
 * @event
 * @param  {object} issue  changed issue model object
 * @param  {object} e      Event
 * @param  {string} newVal new status value
 */
'{issue} status.updated': function(issue, e, newVal) {
    this.element.find('#issueStatus')
        .attr('class', 'label label-success')
        .text(newVal)
            .parent()
            .effect("highlight", {}, 2000);
    this.element.find('.closeIssue').remove();
},


А если у модели нет ID?

При чем тут ID? Вероятно у нас недопонимание вышло.
В моделе описываются методы получения данных, в общем случае формат
НазваниеМетода: 'ссылка после /api/'
В более сложных случаях, как, например, получение данных из разных источниках, там будет несколько строк. DRY подход, в разных местах не будешь код дублировать.

Про это лучше расскажу по скайпу, а то запутаемся :-) Потом, как нибудь, напишу статью отдельную.

Если в твоём проекте/тебе удобно использовать виды, модели и коллекции

Честно говоря сайты, на которых отсутствует хотя бы один из этих компонентов, довольно редки :-) Я, пожалуй, с ходу даже ни одного не назову.
Но ок, сойдемся на том, что это дело вкуса/подхода.

Хорошая библиотека не должна модифицироваться

Это почему? Ты же пишешь код не в самой библиотеке, а в подключаемом компоненте, который может изменять некие механики ядра фреймворка. Это довольно распространённая практика, когда что-то ведет себя не совсем так, как надо.
Пример: я расширял модели таким образом, что бы они знали в каких коллекциях находятся, и пробрасывали в них свои события. Очень схоже с тем, что описывал в статье.

но отдельный форк на каждый проект

Вероятно, если ты модифицировал в библиотеке то, что тебе нужно, то тебе этого форка хватит :-) Либо ты модифицировал не то, такое бывает.
Ну и даже если не смотреть на это, то что плохого в форке на каждый проект, если они абсолютно независимы?

Ты знаешь его настолько хорошо, чтобы лазить в нутрях

CanJS (точнее, его предшественника, jquerymx) я знаю досконально. В свое время изучая исходники именно этого проекта я научился многой js магии.
Backbone — нет. Но при использовании какой-либо библиотеке в проекте я всегда прочитываю весь её код, и, примерно, пытаюсь разобраться как что работает.

11.5к — это всё же разные степени «когда ты точно знаешь как что работает»

Но, насколько я понял, CanJS это около 6к?

11.5к это все компоненты суммарно. Трюк в том, что компонент можно не изучать, если он тебе не нужен.
6к это в живом проекте, да. Но и там некотрые компоненты были подключены, но не использовались.
Как упоминал выше, базовая функциональность движка укладывается в 1155 строк с комментариями, что уже меньше твоего движка :-)

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

Хорошего дня!
Только тут такой момент — любые данные укладываются в модель. Исключения очень редки.

Это кому как. Мне лично кажется лишним создавать отдельную модель на каждый вид — особенно если там 1-2 флага, которые внутри этого вида только и используются.

Ты же пишешь код не в самой библиотеке, а в подключаемом компоненте

А, я понял, что ты изменяешь их собственный код. К компонентам у меня претензий нет. Но у меня был очень яркий опыт одного из первых проектов, где я использовал Backbone. Я расширял и расширял его (Backbone), пока в итоге проект не разросся до 3к строк + еще 4к строк «расширений». Это было неповоротливым и медленным. Конечно, ошибки проектирования/опыта имели значение, но всё равно не решающее. Это не единичный пример.

На мой взгляд, надстройки над библиотекаим наподобие Marionette страдают как раз этим — пытаясь заткнуть дыри изначально отсутствующего функционала они производят на свет намного больше кода, чем это нужно.

Пойми меня правильно — это всё лишь инструменты, и каждый выбирает инструмент, который ему удобен.

Конечно, у каждого свой подход. И я отдаю себе отчёт, что мой — довольно редкий :)

Что касается решений в других фреймворках — я их ищу и изучаю постоянно. Я не бегаю от новых идей.
модель на каждый вид — особенно если там 1-2 флага

Что за вид? Можно пример кода, пожалуйста?

Конечно, ошибки проектирования/опыта имели значение, но всё равно не решающее.

Вероятно, как раз, решающие. 10к строк JS'a не так много, что бы тормозить самим по себе. Важно что за ними скрывается.
Честно говоря код довольно запутанный.
Сходу непонятно что он делает вообще, так же, я не вижу разделение на методы класса и методы объекта.
eval'ы, while(true), смешение логики/представления/стилей — всё очень плохо :-(
Не переживай, просто это не твой подход. Но именно ради этого был создан Sqimitive, когда тебя ничего не сковывает…
На главной странице есть кнопка Costumize — бери что надо.
var model = new MyModel
$.getJSON('api/route', _.bind(model.assignResp, model))

Выглядит интересно.
А если для одной и той же модели данные приходят из разных мест в разных форматах, как это лавировать?
Ну например, как-то так:
var model = new MyModel
// вызов 1:
$.getJSON('api/route', _.bind(model.assignResp_ajax, model))
// вызов 2 (формат чуть отличается):
pusher_channel.bind('my-event', function(data) {
model.assignResp_pusher(data);
});
Вот, кстати, очень важный плюс моделей полноценных — не надо знать «что откуда приходит» — интерфейс един, а на фоне он может несколько запросов делать, обращаться в localStorage за кешем и т.п.
С подходом выше придется лапши много писать каждый раз.
Zav, давай всё же будем объективны. Если у «полноценных» модели набор fetch/parse одни на один класс (как в Backbone), то чем они лучше Sqimitive/assignResp? В последнем случае ты можешь его настроить по-разному в рамках одной модели или коллекции — правила для трансформации, источник данных, что-то ещё.
1) Обычно модели имеют более 1 метода получения данных
2) Управление кешированием и т.п.
3) Даже если там всего 1 метод, это значительно лучше чем дублировать код. DRY. Сделал раз и пользуйся.
Видимо, мне не удалось полностью донести, в чем суть Sqimitive. Она не запрещает иметь много или мало методов. Это основа для твоего мини-фреймворка, который специфичен для данного приложения. Нужно несколько источников и кэширование? Напиши методы, используй ООП и все будет DRY и KISS.

У тебя один опыт, у меня другой. Мнений столько же, сколько программистов. По моем опыту модели имеют один источник (иначе бы это были разные модели). У тебя не так? Сделай по-своему. Много кода это не займет, зато будет работать именно так, как ты хочешь.
1) Т.е. это фреймворк для создания фреймворка? Несколько странно
2) Модель может брать часть данных из loalStorage, а недостающие догружать, например.
А если для одной и той же модели данные приходят из разных мест в разных форматах, как это лавировать?

Сейчас штатно в assignResp нельзя передать набор других правил (они берутся из _respToOpt), но я думаю добавить во второй аргумент (options) параметр, который будет использоваться вместо _respToOpt самой модели.

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

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

Мне кажется вам ещё рано писать свои JavaScript фреймворки :-)
Не очень круто так писать, без объяснения что не так, и комментариев как правильно. Статьи и комментарии читают не только матёрые разработчики, но и начинающие.

Поясню за вас:
вместо этого прототип копируется в другой прототип, то есть все его поля копируются в другой объек

Конечно же, ничего никуда не копируется, а наследуемый класс просто ссылается на прототип класса-родителя.
Скрипт, в первую очередь, смотрит о существовании метода в локальном скоупе, и, при его отсутствии, выполняет метод класса-родителя в текущем контексте (т.е. в себе).

Несмотря на подобную ошибку я считаю твой комментарий некорректным, т.к. результат работы ProgerXP'a вполне на уровне.
В примере я пытался показать, как технически происходит наследование (по крайней мере, как это выглядит снаружи). Я не хотел вдаваться в детали цепочек prototype, какая разница между prototype и __proto__, что такое hasOwnProperty() и прочего. Сказав «два независимых класса-прототипа» я, действительно, схитрил, но это не меняет того, что наследование с точки зрения привычного ООП в JavaScript нет. Это и было целью примера, и на мой взгляд она никуда не делась.
Технически это полнейшая чушь. И снаружи тоже это выглядит как наследование, а вовсе не клонирование. И совсем не надо запугивать читателя кучей терминов, чтобы честно сказать, что потомок ссылается на родителя, а не «хитрить», говоря, что в языке ссылки нет, а поля просто копируются. Единственное чем яваскрипт «непривычен» — это корявый синтаксис и поддержка множественных конструкторов из-за чего инстанцирование происходит не по имени класса (которым фактически является тот самый прототип), а по имени конструктора.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории