Basis.js
Роман Дворнов
Июнь, 2014
«под капотом»
basis.js – 

фреймворк для разработки
одностраничный веб-приложений
2
Ядро
3
Ядро
• Инициализация и настройка
• Вспомогательные функции
• Модульность
• Классы
4
Функции
5
Утилитарные функции
• Работа с объектами, строками, числами,
массивами и функциями
• Polyfill'ы для ES5 методов и исправления
некоторых методов для старых браузеров
• Обертка над консольными методами (basis.dev)
6
Специальные функции
• Функции по работе с путями (basis.path)
• Выполнение кода в следующем фрейме
(basis.setImmediate/clearImmediate, basis.nextTick)
• Асинхронная работа с document (basis.doc)
• basis.getter, basis.ready
7
Модульность
8
CommonJS
реализация близка к node.js
9
Пример
// подключаем другие модули	
var foo = basis.require('./path/to/module.js');	
!
// делаем что-то	
var instance = new foo.SomeClass({ ... });	
!
// экспортируем	
module.exports = instance;
10
Модульность
основана на ресурсах
11
Ресурс – 

это интерфейс к файлу
12
Ресурс
• Ленивый интерфейс (объявление не приводит к
загрузке содержимого)
• Это функция – ее вызов или ее метода .fetch()
возвращает содержимое ресурса, которое
кешируется
• Повторный вызов возвращает значение из кеша
13
Пример
// объявление, файл не будет загружен	
var someText = basis.resource('./path/to/file.txt');	
!
// файл будет загружен, его содержимое	
// будет закешировано и возвращено	
console.log(someText());	
!
// эквивалент, будет возвращено уже закешированое значение	
console.log(someText.fetch());
14
basis.require(..)
=
basis.resource(..).fetch()
15
Расширение файла
определяет тип результата
16
Разные типы
17
JSON.parse(content)
compileAndRun(content)
new CssResource(content)
content
.json
.js
.css
все остальное
Расширение Результат
Можно добавить свой тип
var Compiler = basis.require('./path/to/cs.js').CoffeeScript;	
var processJs = basis.resource.extensions['.js'];	
!
basis.resource.extensions['.coffee'] = 	
function(content, url){	
return processJs(Compiler.compile(content), url);	
};
18
* так же делается и в node.js
Относительные пути
19
JS-модули получают
• __filename* – путь к файлу
• __dirname* – путь к папке файла
• resource = basis.resource(__dirname + fn)
• require = basis.require(__dirname + fn)
20
* как и в node.js
Разрешение путей
• basis.resource и basis.require разрешают
пути относительно html файла
• resource и require – относительно файла
модуля
21
Относительные пути упрощают
реструктуризацию проекта
22
Пространства имен
23
В начале играли 

важную роль
24
Ресурсы уменьшили их значимость,
планомерно выводим из
фреймворка
25
docs.google.com/document/d/
1no1mEp3BsWa8DaXz675oKnLgapSUKcXBwYEo9LOj_DA/edit
Остаются только как сокращение
относительных путей
26
Namespace → path
27
basis.ui.popup
Namespace → path
27
basis.ui.popup
Корневой неймспейс
Namespace → path
27
basis.ui.popup
Корневой неймспейс
Им назначается абсолютный путь
Namespace → path
27
basis.ui.popup
Корневой неймспейс
Им назначается абсолютный путь
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлу
Им назначается абсолютный путь
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлу
Им назначается абсолютный путь Точки заменяются на слеши и 

добавляет расширение .js
/abs/path/to/
Namespace → path
27
basis.ui.popup
Корневой неймспейс Путь к файлу
Им назначается абсолютный путь Точки заменяются на слеши и 

добавляет расширение .js
/abs/path/to/basis/ui/popup.js
Классы
28
Придерживаемся
prototype inheritance
29
синтасический сахар 

лишь для создания
классов и экземпляров
30
Создание класса
var Foo = basis.Class(null, {	
property: 'example',	
init: function(value){ // конструктор	
this.property = value;	
},	
method: function(){	
// что-то делаем	
}	
});
31
Наследование
var Bar = Foo.subclass({	
init: function(value){	
Foo.prototype.init.call(this, value);	
// ...	
},	
method: function(){	
Foo.prototype.method.call(this);	
// ...	
}	
});
32
Наследование
var Bar = Foo.subclass({	
init: function(value){	
Foo.prototype.init.call(this, value);	
// ...	
},	
method: function(){	
Foo.prototype.method.call(this);	
// ...	
}	
});
32
Вызов 

переопределенных 

методов
Больше сахара
• Хелперы
• Авторасширение
• Расширяемые поля
33
basisjs.github.io/articles/ru-RU/basis.Class.html
Пользовательский
интерфейс
34
Компонентный подход
35
36
«Component-based software engineering
(CBSE) ... is a reuse-based approach
to defining, implementing and composing
loosely coupled independent components
into systems...»
en.wikipedia.org/wiki/Software_component
Компонентный подход
это про декомпозицию и
переиспользование
37
Обычный подход
38
list
item
item
item
item
Компонентный подход
39
list item
item
item
item
Обычный подход
40
window
form
field
field
panel
button button
Компонентный подход
41
window form field
field
panel
button button
basis.ui.Node

основная единица интерфейса
42
Стек функциональности
43
Функция Класс
События basis.event.Emitter
Данные basis.data.Object
DOM и патерны basis.dom.wrapper.Node
Шаблон basis.ui.Node
Наследование
События

basis.event.Emitter
44
Патерн Observer
45
Общение между
объектами осуществляется
через события
46
Почти все классы
наследники Emitter
47
Событие – специальный
метод
48
Событие – специальный метод
var Foo = basis.Class(null, {	
emit_myEvent: basis.event.create('myEvent'),	
method: function(){	
// выбрасываем событие	
this.emit_myEvent(foo, bar);	
}	
});
49
Объявляем
Используем
Работа со слушателями
var emitter = new basis.event.Emitter();	
var handler = {	
foo: function(){ .. },	
bar: function(){ .. }	
};	
!
// добавление слушателя	
emitter.addHandler(handler, context);	
// удаление слушателя	
emitter.removeHandler(handler, context);
50
Работа со слушателями
var emitter = new basis.event.Emitter();	
var handler = {	
foo: function(){ .. },	
bar: function(){ .. }	
};	
!
// добавление слушателя	
emitter.addHandler(handler, context);	
// удаление слушателя	
emitter.removeHandler(handler, context);
50
Обычно
переиспользуется
Обработчики хранятся
списком
51
Эффективно по памяти
52
Фреймворк 1 событие 2 события 3 события
Basis 240 240 240
Backbone 1 520 2 860 3 840
Ember 5 480 6 520 7 560
10 000 экземпляров, Кб
Эффективно по времени
53
Фреймворк 1 событие 2 события 3 события
Basis ~ 0 ~ 0 ~ 0
Backbone 20 29 38
Ember 49 68 89
10 000 экземпляров, ms
Данные
basis.data.Object
54
Интерфейсный узел

может быть моделью
55
Хранить данных как
ключ-значение
56
Работа c данными
var object = new basis.data.Object({	
data: {	
foo: 1,	
bar: 2	
}	
});	
!
// обновляем	
object.update({ bar: 3, baz: 4 });
57
Если будут изменения,
выбросит событие update с
дельтой и вернет дельту.
Здесь дельта
{ bar: 2, baz: undefined }
Но в основном ради
делегирования*
58
* об этом будет дальше
DOM
basis.dom.wrapper.Node
59
Интерфейс представляет
собой одно большое дерево
60
Для его организации
используется модель DOM
61
Взято из DOM
• свойства:

childNodes, firstChild, lastChild,
nextSibling, previousSibling, parentNode
• методы:

appendChild, insertBefore, removeChild,
replaceChild
62
Свои дополнения
• сателлиты*
• методы:

setChildNodes, clear
63
* basisjs.github.io/articles/ru-RU/basis.dom.wrapper_satellite.html
DOM дает
универсальность
64
Общие патерны
• сортировка
• группировка (любая вложенность)
• выделение (с созданием контекста)
• disabled/enabled (с созданием контекста)
• и т.д.
65
Привязка данных:

автоматическая синхронизация

набор → childNodes
66
Большая часть фич в
basis.js возможна именно
благодаря DOM
67
Шаблон
basis.ui.Node
68
На модуле basis.ui лежит
вся работа с шаблоном
69
На стороне JavaScript
70
var view = new basis.ui.Node({	
template: resource('./button.tmpl'),	
binding: {	
...	
},	
action: {	
...	
}	
});
На стороне JavaScript
70
var view = new basis.ui.Node({	
template: resource('./button.tmpl'),	
binding: {	
...	
},	
action: {	
...	
}	
});
Описание шаблона
На стороне JavaScript
70
var view = new basis.ui.Node({	
template: resource('./button.tmpl'),	
binding: {	
...	
},	
action: {	
...	
}	
});
Значения для шаблона
Описание шаблона
На стороне JavaScript
70
var view = new basis.ui.Node({	
template: resource('./button.tmpl'),	
binding: {	
...	
},	
action: {	
...	
}	
});
Значения для шаблона
Действия, которые можно

вызывать из шаблона
Описание шаблона
Binding
71
var Foo = basis.ui.Node.subclass({	
title: 'no title',	
binding: {	
title: function(leaf){	
return leaf.title;	
}	
},	
setTitle: function(newTitle){	
this.title = newTitle;	
this.updateBind('title'); // когда надо обновить	
}	
});
binding – дополняется при
наследовании
Обычно есть события
72
var Foo = basis.ui.Node.subclass({	
binding: {	
disabled: {	
events: ['disable', 'enable'],	
getter: function(node){	
return node.isDisabled();	
}	
}	
}	
});
Action
73
var Foo = basis.ui.Node.subclass({	
...	
action: {	
select: function(event){	
this.select();	
},	
...	
}	
});
action – дополняется при
наследовании
В шаблоне
74
<div class="foo {selected}" event-click="select">	
{caption}	
</div>
В шаблоне
74
<div class="foo {selected}" event-click="select">	
{caption}	
</div>
Значения из binding
В шаблоне
74
<div class="foo {selected}" event-click="select">	
{caption}	
</div>
Значения из binding
Выполнение действия из action по событию
Разделение логики и представления
75
Объект
Шаблон
(DOM fragment)
binding
action
DOM
операции
event
listeners
DocumentJavaScript
В коде
• Абстрагируемся от верстки
• Нет селекторов
• Нет указания CSS классов, имен тегов
• Нет HTML
• Нет стилей
76
В шаблонах
• Не известно как и откуда берутся значения
• Нет кода
• Нет циклов
• Нет ветвлений (if)*
• Нет вычислений*
77
* вероятно появится
В большинстве случаев
полное разделение 

логики и представления
78
Но в любом правиле
есть исключения ;)
79
Компоненты
80
В basis.js есть готовые
компоненты
81
… но больше как пример и 

для прототипирования
82
Создавать свои компоненты
достаточно просто
83
Делаем кнопку
84
Модуль с классом
module.exports = require('basis.ui').Node.subclass({	
template: resource('./button.tmpl'),	
binding: {	
caption: 'caption'	
},	
action: {	
click: function(){	
if (!this.isDisabled()) this.click();	
}	
}	
});
85
Шаблон и стиль
<b:style src="./button.css"/>	
<button class="my-button" event-click="click">	
{caption}	
</button>
86
.my-button {	
...	
}
Стиль (button.css)
Шаблон (button.tmpl)
Используем
var Button = require('./path/to/button.js');	
!
new Button({	
caption: 'Click me!',	
click: function(){	
alert('Hello world!');	
}	
});
87
Данные
88
Классы basis.js
89
НаборыОбъектыСкаляры
Token Object
Entity
Dataset
Merge
Автоматические
наборы
Агрегатные
функции
Value
Expression
Источники данных
Трансформеры
Vector
Особенности
• Все данные имеют состояние
• При изменении данных создается дельта
изменений
• Механизм делегирования
• Объекты взаимодействуют через изменение
данных и состояния
• ...
90
Делегирование
91
Как работает
92
Данные и
состояние
Данные и
состояние
basis.data.Objectbasis.data.Object
Как работает
92
Данные и
состояние
Данные и
состояние
basis.data.Objectbasis.data.Object
delegate
Как работает
92
Данные и
состояние
basis.data.Objectbasis.data.Object
delegate
93
С.delegate -> B
D.delegate -> B
B.delegate -> A
A
DC
B
Строим деревья
93
Данные и
состояние
С.delegate -> B
D.delegate -> B
B.delegate -> A
A
DC
B
Строим деревья
На практике
94
window
panel
button button
На практике
94
window
panel
button button
new Button({	
delegate: panel,	
handler: {	
stateChanged: function(){	
this.setDisabled(	
this.state == 'processing');	
}	
}	
});
На практике
94
window
panel
button button
new Button({	
delegate: panel,	
handler: {	
stateChanged: function(){	
this.setDisabled(	
this.state == 'processing');	
}	
}	
});
Сработает, не важно у кого менять
состояние: у кнопки, панели или окна – 

данные и состояние одни
dataSource → childNodes
95
Datasetbasis.ui.Node
Object
Object
Object
...
ItemschildNodes
dataSource → childNodes
95
Datasetbasis.ui.Node
dataSource
Object
Object
Object
...
ItemschildNodes
dataSource → childNodes
95
Datasetbasis.ui.Node
dataSource
Object
Object
Object
...
Node
Node
Node
...
delegate
delegate
delegate
ItemschildNodes
Разные задачи, разные решения
96
• Произвольные поля • Строгий набор полей
• Вычисляемые поля
• Индекс
• Нормализация значений
• Defaults
• Rollback
• ...
basis.entity.Entitybasis.data.Object
дешево и сердито дороже, но с плюшками
97
Подробнее в докладе
Не бойся, это всего лишь
данные... просто их много
tinyurl.com/client-side-big-data
Наборы данных
98
Dataset
(Collection)
Набор – это неупорядоченное
множество объектов
99
"Автоматические" наборы
• Merge – слияние множеств: объединение, разность и др
• Subtract – вычитание
• Filter – получение подмножества
• Split – разбиение на группы 1:1
• Cloud – разбиение на группы 1:М
• Slice – срез
• Extract – разворачивание
100
Наборы: пример
101
102
contacts
103
???
selected
contacts
104
new basis.data.dataset.Subtract({
minuend: contacts,
subtrahend: selectedContacts
});
105
new basis.data.dataset.Filter({
source: new basis.data.dataset.Subtract({
minuend: contacts,
subtrahend: selectedContacts
}),
rule: function(item){
return /ч/i.test(item.data.title);
}
});
Итог
• Описана некоторая логическая схема
связи данных и компонент
• Код работающий с contacts и
selectedContacts остался прежним
• О согласованности наборов и данных
заботится фреймворк
106
Шаблоны
107
DOM-based 

шаблонизатор
108
Известно какая будет 

DOM-структура и как будет
меняться
109
... еще до построения
нативного DOM, на этапе
парсинга
110
111
<div class="entry">	
{title}	
</div>
На этапе парсинга
Атрибут
Текстовый узел
Элемент
112
Описание шаблона =

DOM узлы + инструкции
Знания о DOM структуре
позволяют использовать
оптимизации
113
Например
• cloneNode(true) – быстрое создание 

DOM-фрагмента
• обработка событий через один глобальный
capture-обработчик на документе для
каждого уникального события
114
Шаблонизатор 

производит DOM-фрагменты
и обслуживает их
115
116
<div{el} class="example">	
{text}	
</div>
{	
el: <div>,	
text: #text,	
set: function(name, value){	
...	
}	
}
Описание Экземпляр
Интерфейс прилагается
Запись в DOM
117
<div{el} class="example">	
<h1>{text}</h1>	
<ul{content}/>	
</div>
el = fragment.firstChild	
text = el.firstChild.firstChild	
content = el.lastChild	
Описание Получение ссылок
Нет нужды в селекторах
(пути генерирует шаблонизатор)
Процесс
• построение 

DOM-фрагмента или
cloneNode(true)
• создание интерфейса
• DOM-операции
118
Создание Обновление
• DOM-операции
Работает быстро
119
Может проигрывать при
генерации больших
фрагментов неизменяемой
верстки
120
Но выигрывает на
генерации повторяющихся
фрагментов
121
TodoMVC
122
100 items 1000 items
AngularJS 125 ms 1491 ms
Backbone 53 ms 510 ms
Knockout 39 ms 489 ms
vanilla 23 ms 1882 ms
jQuery 20 ms 184 ms
Backbone + basis.js templates 18 ms 202 ms
basis.js 8 ms 95 ms
2.5x быстрее
Всегда выигрывают
на обновлении
123
Подробнее в докладе
Как построить DOM
124
tinyurl.com/build-dom
Конструкции
125
Подключение стилей
<b:style>
126
127
<b:style src="block.css"/>	
!
<div class="block block_{hidden}">	
{caption}	
</div>
Подключение стилей
.block	
{	
...	
}	
.block_hidden	
{	
...	
}
block.cssblock.tmpl
Включение других шаблонов
<b:include>
128
129
<b:style src="./example.css"/>	
<div class="wrapper">	
<b:include src="./button.tmpl">	
<b:after ref="caption"> {count}</b:after>	
</b:include>	
</div>
Включение шаблонов
Результат
example.tmpl
<b:style src="./button.css"/>	
<b:style src="./example.css"/>	
<div class="wrapper">	
<button class="button">	
{caption} {count}	
</button>	
</div>
<b:style src="./button.css"/>	
<button class="button">	
{caption}	
</button>
button.tmpl
Изоляция стилей
<b:isolate>
130
131
<b:style src="option.css"/>	
!
<div class="xo-bookings-change-status-popup-option
xo-bookings-change-status-popup-option_{disabled}
xo-bookings-change-status-popup-option_{hidden}">	
<span class="xo-bookings-change-status-popup-
option__caption xo-bookings-change-status-popup-
option__caption_{selected}">	
{title}	
</span>	
</div>
До
132
<b:style src="option.css"/>	
<b:isolate/>	
!
<div class="option option_{disabled} option_{hidden}">	
<span class="caption caption_{selected}">	
{title}	
</span>	
</div>
После
Храним шаблоны 

в отдельных файлах
133
Абстрагирование
+
внешние файлы
=
Live update
134
Live update –
обновление DOM-фрагментов 

без перезагрузки страницы
135
136
<div class="sidebar">	
...	
<ul class="list">	
<li>item 1</li>	
<li>item 2</li>	
</ul>	
...	
</div>
<div class="list-wrapper">	
<h2>Header</h2>	
<ul class="list">	
	
</ul>	
</div>
Замена DOM-фрагмента
Старый DOM Новый DOM
136
<div class="sidebar">	
...	
<ul class="list">	
<li>item 1</li>	
<li>item 2</li>	
</ul>	
...	
</div>
<div class="list-wrapper">	
<h2>Header</h2>	
<ul class="list">	
	
</ul>	
</div>
insertBefore
Замена DOM-фрагмента
Старый DOM Новый DOM
136
<div class="sidebar">	
...	
<ul class="list">	
<li>item 1</li>	
<li>item 2</li>	
</ul>	
...	
</div>
<div class="list-wrapper">	
<h2>Header</h2>	
<ul class="list">	
	
</ul>	
</div>
replaceChild
insertBefore
Замена DOM-фрагмента
Старый DOM Новый DOM
Live update
+
Логика
=
Адаптивные View
137
Live update
+
Наборы шаблонов
=
Темы
138
Тема = HTML + CSS
139
Без перезагрузки страницы!
140
Live update
экономит время и
разгоняет разработку
141
Экземпляры шаблонов
хранят мета-информацию*
142
* только в dev-режиме
Возможность
определять к какому
шаблону относится
DOM-узел
143
Это дает Heat map,
карту показывающую
где и как часто
меняется DOM
144
Анализ и выявление проблем
• какие классы используются в разметке,
но их нет в стилях
• какие селекторы никогда не сработают
• конфликты стилей
• и т.д.
145
Оптимальная сборка
• Минимизация классов
• Удаление не используемых разметки и
стилей
• и т.д.
146
Что есть еще?
147
Локализация
148
Роутер
149
Работа с сетью:

ajax, jsonp, soap, upload
150
И разного всякого:

crypt, dragdrop, resize,
computedStyle, UA, xml, etc.
151
Разработка
152
Инструменты
153
basisjs-tools
• Консольный инструмент для разработки
• Работает под управлением node.js
• Установка:

npm install -g basisjs-tools
154
В состав входят
• server – dev-сервер
• extract – строит граф файлов и
извлекает информацию о приложении
• build – сборка приложения
• create – кодогенерация
155
Сборка
• Не требует деклараций, списков файлов,
карты зависимостей и т.п.
• Рекурсивно парсит и анализирует файлы,
строя граф файлов приложения
• Использует AST для анализа
• Может применять различные оптимизации
156
Достаточно выполнить
basis build
157
Google Chrome Plugin
158
(расширение для Developer Tools)
Резюме
• Модульный фреймворк, решающий большую часть задач
• Большинство решений хорошо стыкуются между собой
• Полноценная экосистема (инструменты)
• Простое моделирование систем, меняющихся во времени
• Использование подходов "будущего": DOM-based
шаблонизатор, анализ проекта и автоматическая его
сборка, реактивное программирование
159
Ориентирован на большие
долгосрочные проекты
160
Постоянное добавление нового функционала,
удаление старого, переделка существующего
"Временные сложности" :)
• Только набирает известность и
популярность
• Документация все еще в процессе
написания
161
Вопросы?
162
Роман Дворнов
@rdvornov
rdvornov@gmail.com
basis.js
basisjs.com
github.com/basisjs

Basis.js – «под капотом»