image

Документация на русском
Github репозиторий

Всем привет. Сегодня, 28 сентября исполняется два года с первого коммита в репозиторий Матрешки. Так совпало, что к этому времени подоспел новый релиз со всякими вкусностями для любого JavaScript разработчика (даже для тех, кто не хочет использовать Матрешку в качестве фреймворка).

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

По традиции, напомню о том, что умеет этот фреймворк с помощью маленького кусочка кода.

Раньше можно было делать только так:

// this - экземпляр Матрешки
// связываем свойство "x" с элементом на стрнице
this.bindNode('x', 'input.my-node');
// если изменилось, выводим alert
this.on('change:x', function() {
    alert(this.x);
});
// меняем свойство, вызывается обработчик
// меняется и привязаннык к "x" элемент
this.x = 'Wow!';

Теперь можно еще и так:

var object = {};
// связываем свойство "x" с элементом на стрнице
MK.bindNode(object, 'x', 'input.my-node');
// если изменилось, выводим alert
MK.on(object, 'change:x', function() {
    alert(object.x);
});
// меняем свойство, вызывается обработчик
// меняется и привязаннык к "x" элемент
object.x = 'Wow!';

Из-за того, что последние версии Chrome и NodeJS стали, наконец, поддерживать большинство элементов синтаксиса ES6, все примеры ниже в этом посте будут написаны на ES6. Таким нехитрым способом я хочу поздравить всех, кто считает эти нововведения невероятно крутыми и привлечь внимание к ES.next тех, кто с ними еще не знаком.

Поддержка нативных объектов


Самым главным нововведением стала поддержка Матрешкой произвольных объектов. Да-да, для того, чтоб объявить байндинг или сделать еще что-нибудь крутое, не обязательно создавать экземпляр Матрешки.

let object = {};
//связываем свойство "x” с элементом  '.my-node'
MK.bindNode(object, 'x', '.my-node');
// "y” всегда будет суммой значений свойств x и z
MK.linkProps(object, 'y', 'x z', (x, z) => x + z);
//”z” - это всегда число, независимо то того, какой тип мы ему присвоим
MK.mediate(object, 'z', Number);
// ...

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

//было
this.bindNode('x', '.my-node');
//стало
MK.bindNode(object, 'x', '.my-node');

Кроме этого, коллекции, умеющие сами себя рендерить, теперь не требуют указания модели, которая раньше должна была наследоваться от класса Matreshka.

class MyArray extends MK.Array {
	itemRenderer() {
		return '<li>';
	}

	constructor() {
		super().bindNode('sandbox', '.some-node');
	}
}

let arr = new MyArr();
arr.push(someData);

Полный список новых статичных методов перечислен в разделе MatreshkaMagic.

Библиотека MatreshkaMagic


Благодаря поддержке нативных объектов появиласть возможность вынести все «магические» функции в отдельную, более компактную библиотеку, не включающую в себя классы Matreshka, Matreshka.Array, Matreshka.Object и функцию Class. Разработчику доступен объект MatreshkaMagic или более краткий вариант magic, содержащий все статичные методы класса Matreshka.

Библиотека находится в папке magic/ репозитория.

magic.bindNode(object, 'x', '.my-node');
magic.linkProps(object, 'y', 'x z', (x, z) => x + z);
// и т. д.

Подробнее о библиотеке в документации.

«Глубокое связывание»


Еще одна крутая фича, появившаяся благодаря поддержке нативных объектов — это так называемое «глубокое связывание». Имея объект произвольной вложенности, можно связать DOM ноду с любым свойством где-то в глубине этого объекта.

this.a = {b: {c: {d: 41}}}
this.bindNode('a.b.c.d', '.my-node');

Ядро Матрешки следит за всей веткой объектов и переустанавливает байндинг если один из объектов ветки переопределен

this.a.b = {c: {d: 42}};


«Глубокие ссылки»


Матрешка давно включает в себя метод linkProps, позволяющий задать зависимость одних свойств от других.

Можно задать зависимость от собственных свойств:

this.linkProps('a', 'b c d', (b, c, d) => b + c + d);
this.b = 1;
this.c = 2;
this.d = 3;
alert(this.a); // 6

Можно задать зависимость от свойств других объектов:

this.linkProps('a', [
	externalObject, 'b',
	externalObject2, 'c',
	this, 'd'
]);

externalObject.b = 1;
externalObject2.c = 2;
this.d = 3;
alert(this.a); // 6

Теперь linkProps поддерживает указание пути к свойству:

this.linkProps('a', 'b.c.d e.f', (d, f) => d + f);
this.b = {c: {d: 40}};
this.e = {f: 2};
alert(this.a); // 42

Когда в цепочке пути к свойству что-то меняется, Матрешка перехватывает это изменение, разрывает связь со старой подцепочкой и создаёт зависимость от новой цепочки.

this.b.c = {d: 1};

По-прежнему, можно создавать зависимость от свойств других объектов, при этом, как упоминалось выше, в качестве исходного объекта могут выступать любой объект:

let targetObject = {},
	o1 = {b: {c: {d: 40}}},
	o2 = {e: {f: 2}};

MK.linkProps(targetObject, 'a', [
	o1, 'b.c.d',
	o2, 'e.f'
], (d, f) => d + f);

alert(targetObject.a); // 42


Прозрачный синтаксис делегированных событий


Напомню, что в предыдущих версиях можно было навешать событие не только на текущий объект (this), но и на объект произвольной вложенности. Но синтаксис заставлял желать лучшего. Приведу небольшой пример. Скажем, у экземпляра Матрешки есть или должно появиться некое свойство, которое, в свою очередь, так же является экземпляром Матрешки.

this.a = new Matreshka();

Вы могли до или после присваивания свойства создать обработчик любого события, которое относится к этому свойству. Для этого использовался синтаксис с «собачкой».

this.on('a@someevent', handler);
this.a.trigger('someevent');

Для массивов и объектов (которые в Матрешке являются коллекцями типа ключ-значение), можно было не указывать целевое свойство, так как событие слушается во всех элементах, входящих в коллекцию:

this.on('@someevent', handler);
this.push(new Matreshka());
this[0].trigger('someevent');

На первый взгляд, выглядит просто. Но что если наше дерево объектов немного сложнее? Например, свойство "a" содержит коллекцию:

this.a = new MK.Array();
this.a.push(new Matreshka());

Как отловить событие, внутри такой коллекции? Можно скомбинировать две собачки, которые говорят: «в объекте "a" словить событие "@someevent" -> в элементе массива словить событие "someevent"».

this.on('a@@someevent', handler);
this.a[0].trigger('someevent');

Это еще можно пережить (если выпить достаточное количество кофе). А что, если мы хотим пойти глубже? Тогда количество «собачек” увеличится и кофе уже не поможет…

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

this.on('a@someevent', handler);

Если мы хотим отловить событие у элемента коллекции, то вместо такого:

this.on('@someevent', handler);

Пишем так:

this.on('*@someevent', handler);

Звездочка значит “любое свойство, отвечающее за данные в MK.Object” или „любой элемент коллекции MK.Array”.

Идем глубже. Нам нужно причесать следующий пример, описанный выше:

this.on('a@@someevent', handler);

Теперь пишем так:

this.on('a.*@someevent', handler);

Синтаксис стал намного чище. Вам нужно просто указать путь к объекту перед @, а после неё указать имя события.

Подробная статья о событиях.

setClassFor


setClassFor — это еще одна невероятно крутая функция. Она указывает на то, экземпляром какого класса должно являться заданное свойство. При попытке перезаписать свойство, внутренний перехватчик, вместо присваивания, обновляет его новыми данными. Разберемся на примере.

// задаём свойству изначальные данные (не обязательно)
this.x = {a: 41};
// устанавливаем класс для свойства
this.setClassFor('x', MyClass);
// проверяем, является ли свойство экземпляром класса MyClass
console.log(this.x instanceof MyClass); // true
// проверяем свойство "a” экземпляра
console.log(this.x.a); // 41
// теперь самое интересное
// сохраняем значение "x” в одноименную переменную
var x = this.x;
// пытаемся перезаписать свойство
this.x = {a: 42};
// проверяем, обновились ли данные
console.log(x.a); // 42
// проверяем, перезаписаось ли свойство на самом деле
console.log(x === this.x); // true
// Wow! Экземпляр класса не изменился, а данные обновились!

Если у вас есть глубокая структура объектов и во вложенных объектах тоже запущен setClassFor, можно делать интересные вещи. Например, сохранять представление многоуровневых данных в локальном хранилище.

localStorage.x = JSON.stringify(this.x);

А потом восстанавливать их взмахом волшебной палочки:

this.x = JSON.parse(localStorage.x);

Либо, гонять туда-сюда на сервер.

Случаев, где такая логика может понадобиться невероятно много. В качестве еще одного примера, приведу код из документации (для краткости используются class properties из ECMAScript 7):

// app.js
class App extends MK {
	constructor(appData) {
		this.appData = appData;
		this.setClassFor('appData', AppData);
	}
}

// app-data.js
class AppData extends MK.Object {
	constructor(data) {
		super(data)
			.setClassFor({
				friends: Friends,
				settins: Settings
			});
	}
}

// friend.js
class Friend extends MK.Object {
	constructor(data) {
		super(data);
	}
}

// friends.js
class Friends extends MK.Array {
	Model = Friend;
	constructor(data) {
		super(...data);
	}
}

// settings.js
class Settings extends MK.Object {
	constructor(data) {
		super(data)
			.setClassFor('credentials', Credentials);
	}
}

// credentials.js
class Credentials extends MK.Object {
	constructor(data) {
		super(data);
	}
}

// app-init.js
var app = new App({
	settings: {
		name: 'Vasiliy Vasiliev',
		credentials: {
			email: 'vasia.vasia@gmail.com'
		}
	},
	friends: [{
		name: 'Yulia Zuyeva',
		id: 1
	}, {
		name: 'Konstantin Konstantinopolsky',
		id: 2
	}, {
		name: 'nagibator3000',
		id: 3
	}]
});

// данные можно сериализировать и передать на сервер
JSON.stringify(app.appData);


// потом просто присвоить новые данные свойству appData
// при этом, структура классов не изменится
app.appData = {
	settings: {
		name: 'Petr Petrov',
		credentials: {
			email: 'petr.petrov@gmail.com'
		}
	},
	friends: [{
		name: 'Yulechka Zuyeva',
		id: 1
	}, {
		name: 'Konstantin Konstantinopolsky',
		id: 2
	}]
};

Более подробно в документации к методу.

DOM шаблонизатор


Матрешка — это фреймворк, который исповедует идею о том, что логика должна содержаться в JS файлах, в противовес фреймворкам, реализующим паттерн MVVM, принуждающим описывать логику в HTML коде.

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

Поэтому, было решено улучшить и ускорить DOM шаблонизатор, отсутствующий до недавнего времени в официальном API. Что он делает? Он берет DOM узел, коллекцию DOM узлов, HTML код или песочницу текущего объекта, разбирает его, находя ангуляр-подобные конструкции типа {{KEY}} и создает привязки там, где эти конструкции найдены.

<a href="http://{{website.domain}}/{{category}}/{{page}}">Look at the {{info.title}}</a>

this.parseBindings();
this.website.domain = 'example.com';
this.category = 'foo';
this.page = 42;
this.info.title = 'cool stuff';

Подробнее метод описан в документации.

Метод не противоречит идеологии Матрешки, так как логики (циклов, условий, обработчиков) в шаблоне быть не может.

Кроме публикации API самого метода, шаблонизатор для коллекций теперь включен по умолчанию (не нужно больше писать useBindingsParser: true).

class MyArray extends MK.Array {
	itemRenderer = '<span>Hello, {{name}}</span>';
	...
}


Больше сахара для ECMAScript 2015



Из примера к setClassFor видно, что методы запускаются сразу после super(). Эта возможность стала реальной благодаря очень простому изменению: все три конструктора (Matreshka, Matreshka.Array, Matreshka.Object) возвращают this вместо undefined.

class MyObject extends MK.Object {
	constructor(data) {
		super(data)
			.bindNode('x', '.my-node');
	}
}

// создаст экземпляр MyObject со свойствами a и b,
// отвечающими за данные
myObject = new MyObject({a: 1, b: 2});

class MyCollection extends MK.Array {
	constructor(data) {
		super(...data)
			.bindNode('x', '.my-node');
	}
}

// создаст коллекцию, состоящую из 5 элементов
myCollection = new MyCollection([1,2,3,4,5]);


Поддержка объектов типа событие-обработчик в методах on, once, onDebounce


В предыдущих версиях разработчику был доступен единственный синтаксис объявления обработчиков события.

this.on('eventname1', handler1);
this.on('eventname2', handler2);

Теперь же можно объявить несколько обработчиков, вызвав соответствующий метод лишь однажды:

this.on({
	'eventname1': handler1,
	'eventname2': handler2
});

Эта новость не упоминалась бы в этом посте, если бы не одно но: используя ECMAScript 2015 можно сильно укоротить код в микрозадачах.

this.on({
	'eventname1': evt => this.x = 42,
	'eventname2': evt => doSomethingElse()
});

Против “старого” синтаксиса:

this.on({
	'eventname1': function(evt) {
		this.x = 42
	},
	'eventname2':  function(evt) {
		doSomethingElse();
	}
});


Переопределение itemRenderer


itemRenderer — это виртуальное свойство коллекции (Matreshka.Array), которое говорит о том, как отрисовывать элементы коллекции.

// для краткости, синтаксис ES7
class MyCollection extends MK.Array {
	itemRenderer = "<div>Hi there!</div>";
	constructor() {
		super()
			.bindNode('sandbox', '.array-sandbox')
			.push({a: 1}, {a: 2});
	}
}

Более подробно в документации к itemRenderer.

Начиная с новой версии, при переопределении itemRenderer, коллекция автоматически перерисовывается.

//каждый элемент коллекции - span
this.itemRenderer = '<span>I'm a span</spam>';

//каждый элемент коллекции - div
this.itemRenderer = '<div>I'm a div</div>';

Можно придумать несколько юз-кейсов: вы хотите одной кнопкой изменить дизайн коллекции либо же ваш шаблон находится на сервере (в примере ниже используется Fetch API).

fetch('templates/my-template.html')
	.then(resp => resp.text())
	.then(text => this.itemRenderer = text);

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

Новые байндеры


Напомню, байндер — это объект, который указывать на то, как связать свойство с элементом на странице. Байндер используется методом bindNode, реализующим одно или двух-сторонее связывание.

this.bindNode('x', '.my-node', binder);
this.x = 42; // элемент на странице тоже изменился

Подробное опсисание можно найти в документации к методу.

MK.binders.progress — связывает свойство с состоянием HTML5 элемента progress. Байндер не нужно вызывать вручную, так как он входит в коллекцию стандартных байндеров.

this.bindNode('x', '.my-progress');
this.x = 42; // меняет значение прогресса на 42

MK.binders.innerText — связывает свойство с текстовым значением любого элемента, у которого есть свойство textContent или innerText.

this.bindNode('x', '.my-node', MK.binders.innerText());
this.x = 'Some <i>Text</i>'; // задаст ноде содержимое "как есть”, в виде текста

MK.binders.style — связывает свойство объекта со свойством объекта style элемента.

this.bindNode('x', '.my-node', MK.binders.style('color'));
this.x = 'red'; // изменит цвет текста на красный

И самое интересное: MK.binders.file. Этот новый байндер не только отловит изменение пользователем содрежимого input[type=”file”], но и прочтет файл в том формате, который вам нужен:

this.bindNode('x', '.my-file', MK.binders.file('dataURL'));

// событие изменения генерируется, когда файл прочитан
this.on('change:x', function() {
	console.log(this.x.readerResult); // "data:image/png;base64,iVBO..."
});

Подробнее в документации к байндеру.

getValue для байндеров innerHTML, className, property и attribute


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

<div class="my-div">Some data</div>

// скажем, что this.x не определен.
this.bindNode('x', '.my-div', MK.binders.innerHTML());
alert(this.x); //"Some data"


onItemRender


У Matreshka.Array появился новый виртуальный метод onItemRender. Он вызывается тогда, когда один из элементов коллекции был отрисован. Метод делает код более плоским, позволяя избежать прослушивания события "render".

Событие "render" всегда было стандартным паттерном, позволяющим добавить необходимые привязки при рендеринге.

class MyCollection extends MK.Array {
	Model: MyModel;
	itemRenderer = '<li>';
	constructor() {
	super()
		.bindNode('sandbox', '.array-sandbox')
		.on('*@render', evt => {
			evt.self.bindNode(...);	
		});
	}
}

Теперь можно сделать так:

class MyCollection extends MK.Array {
	Model: MyModel;
	itemRenderer = '<li>';
	constructor() {
		super()
			.bindNode('sandbox', '.array-sandbox');
	}

	onItemRender(item, evt) {
		item.bindNode(...);
	}
}

У „моделей” появился похожий виртуальный метод: onRender.
Раньше было так:

class MyModel extends MK.Object {
	constructor() {
		super()
			.on('render', evt => {
				this.bindNode(...);	
			});
	}
}


Теперь можно писать так:

class MyModel extends MK.Object {
	constructor() {
		super()
	}

	onRender() {
		this.bindNode(...);
	}
}


Свойства nodes и $nodes


После объявления связывания данных и DOM ноды, разработчик мог получить доступ к связанным узлам с помощью методов bound и $bound. bound возвращает первый привязанный элемент, $bound — все элементы в виде коллекции jQuery или Balalaika.

this.bindNode('x', '.my-node');
var boundNode = this.bound('x');
var allBoundNodes = this.$bound('x');

Свойства nodes и $nodes позволяют делать то же самое, но практически бесплатно, с точки зрения производительности, так как эти свойства являются обычными объектами.

this.bindNode('x', '.my-node');
var boundNode = this.nodes.x;
var allBoundNodes = this.$nodes.x;


Еще немного новых методов


Matreshka.to конвертирует произвольный объект в экземпляры MK.Object и MK.Array.
MK.Array.of, работающий так же, как и Array.of, но возвращающий экземпляр MK.Array.
MK.Array.from, работающий так же, как и Array.from, но возвращающий экземпляр MK.Array.
MK.trim для браузеров, которые не поддерживают String.prototype.trim.
MK.toArray, конвертирующий array-like массив в нативный Array в два раза быстрее, чем это делает Array.prototype.slice.

Увеличение производительности


С помощью микрооптимизаций (например, использования цикла for..in вместо функции each) и более крупных изменений, получилось добиться отличных результатов. Например, в бенчмарке с небольшими коллекциями (10 элементов), Матрешка отставала от React на 10-20 процентов в Хроме и Файерфоксе (хотя и обгоняла в бенчмарках с большим коичеством элементов коллекции). Теперь в этом же тесте, Матрешка быстрее Реакта на 50 процентов в Хроме, и быстрее в 3 раза в Файерфоксе.

Вот список бенчмарков, чтобы убедиться самостоятельно: 10 элементов, 50 элементов, 100 элементов, 500 элементов, 1000 элементов.

Работа над ошибками: тесты


Матрешка, наконец, тестируется автоматически. На момент написания этой статьи, реализовано 148 тестов, проверяющих работоспособность методов до того, как они попадут в бранч develop. Особенно скурпулезно оттестированы делегированные события, которые обязаны работать при самых разных разных обстоятельствах и ничего не ломать при этом.

Поддержка браузерами


На сайте документации появилось предупреждение о том, что использование Матрешки в Internet Explorer 8 не рекомендуемо по причине наличия массы методов, которые невозможно реализовать для этой версии “осла». На деле, это лишь дисклеймер на случаи, когда разработчик пытается бездумно использовать такие методы. Следует помнить лишь одно: статичные методы, добавляющие «магию» в нативные объекты не работают в ИЕ8.

Такой код сработает в IE8, в случае, если this — экземпляр Матрешки.

this.bindNode('key', '.node');

И такой сработает:

var mk = new Matreshka();
mk.bindNode('key', '.node');

А этот код сработает только в IE9+ и в других браузерах (в том числе и в древних WebKit и Opera Mini):

var object = {};
MK.bindNode(object, 'key', '.node');

Если очень руки чешутся использовать статичные методы в восьмом осле, можете предварительно сконвертировать объект в экземпляр Матрешки:

var object = MK.to({});
MK.bindNode(object, 'key', '.node');

Таким образом семантичное версионирование соблюдено.

Другие изменения


— Синтаксис bindNode немного расширился.
— Ошибки об отсутствующих нодах, используя метод bindNode более информативны: теперь в тексте, кроме ключа, указывается селектор (если передан селектор).
— Исходный код разбит на мелкие составляющие.
— Из кода и из примеров на сайте убраны ненужные пробелы (f(x) вместо f( x )).
— Как уже писалось выше, Матрешка поддерживает Opera Mini и старые WebKit.

Об остальных изменениях и списке исправленных ошибок, можно узнать в соответствующем разделе на сайте.

И еще


Посмотреть на то, что сделано и планируется сделать, можно в Trello. Там же можно и проголовать, повышая приоритет карточек.

В чате Gitter довольно часто происходят обсуждения новых фич. Из-за этого, вопросы, которые задают пользователи, и ответы на них теряются где-то в темных подвалах интенета. Поэтому, было принято решение запустить, в качестве эксперимента, форум на базе Muut (на русском и на английском). Если возникает вопрос, не стесняйтесь его задавать там (даже если вы думаете, что вопрос глупый).

Для примера, вот один из отличных вопросов Rendol с равернутым ответом на него:
Приветствую!
Снова тот же вопрос, который у меня ранее уже был, но ответ я на него так и не нашел красивый.
Например переписка как в ВК:
User = {
  id: 5,
  name: 'Fedor',
  online: true
}

Этого пользователя мы размещаем в разных комнатах: room1, room2, room3.
Если User.online = false, то во всех 3х комнатах, должен измениться цвет например.
Т.о. 3 коллекции, которые содержат один объект и при этом этот объект выводится в 3х местах.
Примечание: не обязательно, что эти коллекции будут однотипными (не только комнаты), могут быть разного вида и представления.
Есть ли возможность привязать один объект к нескольким представлениям?

Ответ:
Да, коллекции, содержащие объект могут быть разными. Для этого у него должно быть установлено свойство bindRenderedAsSandbox: false, так как при вхождении в несколько коллекций, песочницей станет сразу несколько элементов (например, несколько разных строк из разных таблиц, а это сильно усложнит вам жизнь). Поэтому отключаем песочницу. При срабатывании события render можно определить, в какую именно коллекцию вставлена модель и, исходя из этого, объявить привязки.
Вот небольшой пример накатал: jsbin.com/cidise/12/edit. Объект user находится сразу в двух коллекциях (в таблице и списке ul), которые рендерятся по-разному. Можете написать в консоли tableUsers[0].name = 'xxx' и все узлы, привязанные к данному юзеру, изменятся. Получается, что не нужно создавать много отдельных объектов и синхронизировать их значения.
ЗЫ. При поддержке IE 8, для проверки того, является ли объект инстансом класса, нужно использовать метод .instanceOf
object.instanceOf( MyClass );
// вместо
object instanceof MyClass


Надеюсь, пост оказался полезным. Спасибо за внимание.
Что насчет попробовать Матрешку?

Проголосовало 85 человек. Воздержалось 43 человека.

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

Комментарии (27)


  1. seokirill
    28.09.2015 10:32

    Отлично, итак мне нравилась больше ангуляр. Теперь вообще. Какие типы файлов читает?


    1. Finom
      28.09.2015 10:34

      Если вопрос касается байндера file, то всех. Байндер сделан на базе FileReader.


  1. dolphin4ik
    28.09.2015 11:48

    Думаю не хватает только одного — таблички аля «can i use» для браузеров. Понятно что таких стариков как ie8 мы уже не помним, но вот с мобильными и всякими не топовыми типа яндекса и вивальди не охото рисковать.


    1. Finom
      28.09.2015 11:52
      -1

      dolphin4ik, Яндекс и Вивальди — это новые браузеры на неустаревших движках. Но идею я понял, добавил в список.


    1. Finom
      02.10.2015 14:38

      image

      Скоро будет на сайте с пояснениями, а список протестированных браузеров будет расширяться (например, Android 4.0 и 4.1). Вивальди и Яндекс сделаны, как и Хромиум, на движке Blink, не виду смысла их добавлять, как и дюжину других локальных форков.


    1. Finom
      02.10.2015 14:42

      Забыл сказать. Для того, чтоб убедиться самостоятельно, работает ли Матрешка в том или ином браузере, открой в нем эту страницу: coding.farm/matreshka/test/SpecRunner.html

      Если всё зеленое, значит, работает. Если красное — дайт мне знать :)


  1. nuit
    28.09.2015 12:18
    -4

    >Теперь в этом же тесте, Матрешка быстрее Реакта на 50 процентов в Хроме, и быстрее в 3 раза в Файерфоксе.

    Корявый бэнчмарк с добавлением элемента в конец списка, который является одним из worst case'ов для реакта. Который к тому же запускается на древней версии реакта 0.10.0 в девелопмент режиме (запуск в девелоп режиме в некоторых случаях на порядок медленее), и написан человеком, который явно ничего не писал на реакте.


    1. Finom
      28.09.2015 13:41
      +2

      Могу ли я попросить вас сделать, по-вашему, правильный тест и скинуть мне в личку или ответом на этот комментарий? Внизу каждого бенчмарка есть ссылка «edit these tests or add even more tests to this page».


      1. nuit
        28.09.2015 14:26
        -1

        Начнём с того что нужно забыть о том чтобы бэнчмаркать ui либы на jsperf'е.

        Вот один из моих бэнчмарков, который заточен под библиотеки использующие one way dataflow и поддерживают оптимизацию с использованием immutable структур данных: localvoid.github.io/uibench
        И глядя на цифры в этом бэнчмарке, я никогда не смогу сказать что одна библиотека быстрее другой в N раз, тк всё очень сильно зависит от тест кэйса.

        Если вас реально интересует производительность вашей либы, относительно конкурентов, могли бы сделать какой-нибудь нормальный бэнчмарк, а не эту поделку на jsperf'е, цель которой написать что ваша библиотека быстрее в N раз.

        Есть ещё вот такой бэнчмарк: mathieuancelin.github.io/js-repaint-perfs, но опять же нужно понимать что он бэнчмаркает, как работают остальные библиотеки и не делать глупых выводов что одна либа быстрее другой, основываясь на какой-то одной цифре.


        1. Finom
          28.09.2015 16:41
          +3

          Чем вам не угодил JSPerf? Почему мы можем тестировать, скажем, скорость сортировки массива, но не можем тестировать скорость библиотек, использующих DOM API, объекты которого являются, по сути, теми же JS объектами?

          По поводу вашего теста: я не понимаю, что вы тестируете. Хотя бы описание добавили.


          1. nuit
            28.09.2015 17:35
            -2

            >Почему мы можем тестировать, скажем, скорость сортировки массива, но не можем тестировать скорость библиотек, использующих DOM API, объекты которого являются, по сути, теми же JS объектами?

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

            >По поводу вашего теста: я не понимаю, что вы тестируете. Хотя бы описание добавили.

            Тестируется множество различных кэйсов, а не какой-то один вроде «добавление элемента в конец списка» как в вашем бэнчмарке. И тестируются библиотеки, которые используют один и тот же механизм для data binding'а, поддерживают те же самые оптимизации.

            Библиотеки, использующие разные формы для дата байндинга будут иметь преимущества на одних кэйсах и проигрывать на других, например виртуальный дом имеет преимущество когда нужно просто быстро отрендерить дерево компонент, тк не нужно будет вешать всякие вотчеры как в случае с kvo или dirty-checking'ом. kvo имеет преимущество, когда нужно делать мелкие точечные апдэйты (например добавление элемента в конец списка).

            Все эти бэнчмарки не имеют никакого смысла для общественности, которая не разбирается в том как работают различные библиотеки, их преимущества и недостатки.


            1. Finom
              29.09.2015 12:40

              Ну я, в любом случае, принимаю любую критику. Ваш коммент заставил повысить в приоритете эту фичу, позволяющую обогнать Vue в DBMON тесте (и Матрешка и Vue без track-by работают медленно, хотя первая — чуть быстрее).


              1. nuit
                29.09.2015 14:09

                в этом dbmon тесте есть проблема с тем что реализации, которые работают очень быстро, успевают сделать несколько дом апдэйтов до того как кадр начинает отрисовываться и из-за это fps на счётчике в правом нижнем углу будет показываться слишком завышеная цифра по сравнению с реальностью.

                Так же у дом операций есть такая особенность, что если неправильно бэнчмаркать, то библиотеки, которые будут успевать сделать как можно больше циклов с тестом, время которого замеряется до того как кадр начнёт перерисовываться, будут показывать значительно лучшие результаты, тк оверхэд на дом операции будет гораздо ниже. Этой проблемой страдает мой vdom-benchmark, но я его уже давно не трогаяю, тк другие разработчики активно его используют, бэнчмарки на jsperf'е страдают такой же проблемой.

                Вот насколько может отличаться скорость работы дом операций, если просто заставить после каждого теста делать перерисовку: jsfiddle.net/67jz79n2/2


                1. nuit
                  29.09.2015 14:31

                  в предыдущей версии был небольшой баг :) jsfiddle.net/67jz79n2/3


                1. Finom
                  29.09.2015 14:32

                  Если я не ошибаюсь, если рендеринг происходит после оповещения о рендеринге, то на следующей итерации скорость рендеринга предыдущей итерации будет учтена, так как DOM — штука синхронная.


                  1. nuit
                    29.09.2015 14:39

                    DOM операции синхронные, но например в хроме, на грязных элементах эти операции отрабатывают значительно быстрее до того как происходит reflow/paint итд. Поэтому если хочется увидеть цифры более близкие к реальности, то желательно заставлять браузер производить перерисовку перед каждой итерацией. Более быстрая библиотека от этого наврятли станет медленее, но зато результаты будут не в 5 раз быстрее, а где-нибудь в 2 раза, тк всё же дом операции являются узким местом.


        1. Finom
          30.09.2015 16:29

          1. nuit
            30.09.2015 18:37

            Если интересует, то вот нормально написаная версия на реакте: localvoid.github.io/react-dbmon


            1. Finom
              30.09.2015 19:55

              А почему не запостите это сюда: mathieuancelin.github.io/js-repaint-perfs?


              1. nuit
                30.09.2015 20:43

                Лучше заставить автора этого бэнчмарка принимать ссылки на внешние сайты с различными реализациями, вместо того чтобы пихать всё в один репозиторий. Неудобно же собирать проекты, которые зависят от всяких инструментов типа babel'а. И если что-то обновлять, то постоянно дожидаться пока он примет пулл риквест.


                1. Finom
                  30.09.2015 20:46
                  +1

                  Лучше заставить автора этого бэнчмарка принимать ссылки на внешние сайты с различными реализациями
                  Реализации самого бенчмарка тоже могут быть разными. Все бенчмарки в одном месте позволяют избежать жульничество.

                  И если что-то обновлять, то постоянно дожидаться пока он примет пулл риквест.
                  Ну можно и подождать. Мой риквест он почти сразу принял.


                  1. nuit
                    30.09.2015 21:29

                    >Реализации самого бенчмарка тоже могут быть разными.

                    Там уже начались появляться костыли вроде: github.com/mathieuancelin/js-repaint-perfs/blob/0cef56a71658e4be2291a2b963331f42220ea498/ENV.js#L100

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

                    В общем там итак полный бардак, не думаю что кто-то будет жульничать. Я например использую оригинальную версию этого бэнчмарка для отслеживания регрессий, не вижу смысла жульничать, всё равно легко проверить что происходит если у кого-то внезапно какой-то невероятно крутой результат. Да и гораздо сильнее можно жульничать в подобных бэнчмарках, если делать всякий изврат как например неявное кэширование всего в библиотеке Imba, утечки памяти гарантированы, зато побеждает кучу микробэнчмарков :)

                    И всё же, все реализации должны использовать те способы модификации данных, которые приняты в тестируемой библиотеке, а не то что в ENV.js.



  1. Finom
    01.10.2015 22:03

    Так же если посмотреть на реализацию эмбера, там они тупо сами генерируют и модифицируют данные тем способом, которым принято в эмбере, а не то что в файле ENV.js.
    Если мы работаем с кастомным API, то тест вполне себе верный.
    всё равно легко проверить что происходит если у кого-то внезапно какой-то невероятно крутой результат.
    Для того, чтоб кто-то принял всерьез тест, разработчик теста должен обладать авторитетом или популярностью. Если «Вася Пупкин» сделает тест и разместит его у себя на сайте, я даже заглядывать в код не буду.
    И всё же, все реализации должны использовать те способы модификации данных, которые приняты в тестируемой библиотеке, а не то что в ENV.js.
    По воводу тестов, согласен.


    1. nuit
      02.10.2015 05:39

      >разработчик теста должен обладать авторитетом или популярностью

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


      1. Finom
        02.10.2015 13:46

        Со сломанным тестом это действительно косяк (скорее мой, так как я не запуллил изменения перед пулл-риквестом, а человек каким-то хитрым способом поменял генератор данных), но как показывает практика:
        1. Никого не интересует, как сделан тест, кроме очень любопытных (их единицы).
        2. Под «авторитетом» я не имею в виду качество кода разработчика или внимательность к деталям. Я имею в виду упоминаемость. В данном случае, чувак собрал все фреймворки, а ссылкой на тесты пользуется море людей. Кто эти тесты на самом деле написал, никого не интересует.


      1. lega
        04.10.2015 22:54
        +1

        Лучше заставить автора этого бэнчмарка принимать ссылки на внешние сайты с различными реализациями
        Тогда через пол-года половина тестов развалится, реп позволяет держать это как единый проект, чем он и является.
        Тест (входные данные) могут меняться, какие-нибудь доработки, улучшения. Какие-то спорные моменты в issue.

        владелец репозитория просто не обладает достаточными знаниями обо всех библиотеках чтобы контролировать качество
        Сообщество может контролировать качество через issue и pull-request, влиять на 1 реп проще чем на 20 разных.

        Имхо, плюсов больше.