Matreshka.js — MK.Array

в 18:13, , рубрики: javascript, jquery, Matreshka, Matreshka.js, Веб-разработка, метки: ,

(Напомню, репозиторий находится здесь)

Приветствую всех. Предыдущую статью я закончил на том, что нам может потребоваться массив данных. Массивом в проекте Matreshka.js являются экземпляры класса MK.Array. Чем они отличается от обычных массивов? Почти ничем. Прототип MK.Array содержит все методы, которые есть у «традиционного» массива, конструктор принимает те же аргументы, что и оригинальный Array, а экземпляры имеют несколько интересных особенностей.

MK.Array — это массив на стероидах, который умеет:

  • Всё, что умеет Array
  • Генерировать события при модификации
  • Использовать цепочечный вызов методов там, где это возможно
  • Умеет то, что умеет Матрешка: привязывать элементы к свойствам и генерировать кастомные события

Код для привлечения внимания:

mkArray
	.on( 'push', function() {
		alert( 'push' );
	})
	.push( 1, 2, 3, 4 )
	.unshift( 0 )
	.shift()
	.pop()
	.push( 5 )
	.forEach( function() { ... } )
	.sort()
	.reverse()
;

Помните гифку из первой статьи серии?

Matreshka.js — MK.Array


При использовании MK.Array надо помнить 5 вещей:

0. Методы, пришедшие из Array в MK.Array не переписаны заново. MK.Array использует встроенные возможности массива, а это означает, что, во первых, код класса очень компактен, во-вторых мы избегаем багов неправильной реализации методов. Это так же значит, что множество новых методов не будет работать в Internet Explorer 8, пока вы не подключите, скажем, es5-shim. При вызове неподдерживаемого метода сгенерируется ошибка, сообщение которой предложит подключить эту библиотеку.

Список реализованных методов был составлен при изучении прототипа Array в последней сборке Chrome и документации на MDN.

1. Методы, которые модифицируют экземпляр, генерируют два события: "modify" и имя метода. Например, после выполнения метода .push генерируется не только "modify", но и "push".

var mkArray = new MK.Array;
mkArray.on( 'push', function() {
	alert( 'push' );
});
mkArray.on( 'modify', function() {
	alert( 'modify' );
});
mkArray.push( 42 );

Список модифицирующих методов:

2. Для избежания генерации соответствующих событий, используйте методы, начинающиеся с silent. Например, если вы хотите добавить элемент в массив, но хотите избежать генерации событий, используйте silentPush (обратите внимание, что имя оригинального метода пишется с большой буквы).

var mkArray = new MK.Array;
mkArray.silentPush( 42 );

Список модифицирующих методов, при запуске которых не генерируются события:

3. Метод .forEach, который в прототипе оригинального Array возвращает undefined, в MK.Array возвращает себя:

var mapped = mkArray
	.forEach( function() {
		...
	})
	.forEach( function() {
		...
	})
	.map( function() {
		...
	})
;

4. Методы оригинального Array, которые модифицируют массив, и выполняют вторичную функцию, возвращая длину массива (.push, .unshift) или удаленный элемент (.pop, .shift), в MK.Array возвращают себя. Это позволяет строить цепочки методов (то, что мне всегда не хватало). Методы, первичной задачей которых является возврат значения другого типа, работают традиционным образом. Это значит, что .indexOf возвращает число, .toString — строку…

mkArray
	.push( 1, 2, 3, 4 )
	.unshift( 0 )
	.shift()
	.pop()
	.push( 5 )
	.forEach( function() { ... } )
	.sort()
	.reverse()
;

Компенсировать такое поведение очень просто, например, для метода .push:

var array = new Array( 1, 2, 3, 4 ),
	mkArray = new MK.Array( 1, 2, 3, 4 ),
	arrayLength, mkArrayLength;

arrayLength = array.push( 6, 7 );

mkArray.push( 6, 7 );
mkArrayLength = mkArray.length;

5. Методы-итераторы (.forEach, .map, .filter, ...) принимают в качестве первого аргумента функцию, третьим аргументом которой является экземпляр родного массива. Особенность восьмого Осла не позволяет использовать Array.prototype[ method ].apply в контексте Матрешки, поэтому, запуск методов происходит таким образом:
1. Экземпляр MK.Array конвертируется в экземпляр Array.
2. Выполняется метод Array.prototype[ name ].apply( array, arguments );
3. Экземпляр MK.Array очищается и обновляется полученным массивом Array.

mkArray.forEach( function( item, index, array ) {
	console.log( array === mkArray ); // false
});

Это особенность реализации Матрешки, которая может вызвать недоумение у опытных программистов. Не спешите писать гневный комментарий. Если Хабру будет интересно, я расскажу самое главное: как работает Матрешка.

Наследование

Матрешко-массив наследуется так же, как и любой класс, унаследованный от Матрешки:

var MyArray = Class({
	'extends': MK.Array,
	constructor: fucntion() {
		this.initMK();
	}
});

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

Применение на практике

MK.Array сам по себе является готовым к использованию классом, который добавит сахара в любое приложение. Есть только одно «но»: матрешко-массив не умеет привязывать данные к элементам массива. Привязать элемент к индексу с помощью .bindElement вы конечно сможете, но, очевидно, это имеет мало смысла: индексы постоянно меняются при модификации. Что делать, если у вас, скажем, есть список, который должен обновляться при изменении массива. В этом случае нужно отслеживать необходимые методы и менять DOM вручную. Например, если приложение предполагает добавление и удаление элементов, надо слушать события "push" и "splice", вопрос только в том, что нужно вашему приложению.

Гифка в начале поста — это демонстрация работы (пока) недокументированного плагина MK.DOMArray, который, реагируя на изменения данных, автоматически меняет DOM. Плагин был создан в качестве эксперимента, демонстрации возможностей Матрешки и для приближения ближе к идее о том, что DOM должен обновиться сам, когда данные меняются. Но эксперимент оказался настолько удачным, что, возможно, код плагина вольётся в код MK.Array. Изменения в DOM оптимальны: при добавлении объектов в массив, на страницу добавляется новые элементы, при удалении — удаляются, при сортировке — сортируются. Это я к тому, что таблица не перерисовывается заново, а реагирует последовательно.

Здесь матрешко-массив (а точнее его потомок: MK.DOMArray) в примере можно назвать аналогом Collection в Backbone, а его элементы — аналогом Model. Пример можно наблюдать здесь: jsbin.com/aZEseWE/16/ (именно с этого примера записывалась гифка). А вот здесь: jsbin.com/eQomoJe/9/ более аскетичный вариант. В нем нет переопределения встроенных методов. Я предпочитаю второй способ, несмотря на кажущуюся избыточность.

MK.DOMArray работает таким способом: при добавлении объектов (в начало, конец, середину… без разницы) он проверяет, есть ли у «себя» метод .renderer и, если он есть, вставляет возвращаемый элемент в дерево страницы и вызывает на объекте событие "render".

var Example = Class({
	'extends': MK.DOMArray,
	constructor: function() {
		this
			// инициализируем Матрешку
			.initMK()
			// Привязываем элементы
			.bindElement( this, 'table tbody' )
		;
	},
	renderer: function( object ) {
		// Возвращает строку с пустыми тегами. Значения ячеек обновятся автоматически, при привязке
		return '<tr><td class="a"></td><td class="b"></td><td class="c"></td></tr>';
	}
});

Задачей добавленного объекта является отлов события "render" и привязка элементов внутри элемента, привязанного к "__this__".

var ExampleObject = Class({
	'extends': MK.Object,
	constructor: function( o ) {
		this
			.initMK()
			.jset( o )
			.on( 'render', function( evt ) {
				this
					// аргумент обработчика события 'render' — объект, содержащий отрендереный элемент
					// почему MK.DOMArray самостоятельно не привязывает HTML элемент к элементу коллекции? 
					// потому что объект (элемент коллекции) сам должен решать, что делать,
					// так как один объект может содержаться в разных коллекциях
					.bindElement( this, evt.el )
					// привязываем необходимые элементы
					.bindElement({
						a: this.$( '.a' ),
						b: this.$( '.b' ),
						c: this.$( '.c' )
					}, MK.htmlp )
				;
			})
		;
	}
});
Идея

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

var table = MK.Table({
	table: '.my-table'
	rows: [{
		title: 'A',
		key: 'a'
	}, {
		title: 'B',
		key: 'b'
	}]
});

table.push({
	a: 'Fuck',
	b: 'Yeah'
});

В завершение цикла

Матрешка — это компактный, простой в изучении фреймворк, который будет и дальше развиваться. Независимо от уровня подготовки программиста, Матрешка проста в изучении и, скорее всего, является замечательным началом при переходе к структурированному коду новичками. Компактность и расширяемость даёт кучу идей для плагинов и дополнений. Со временем, будет убрана зависимость от jQuery, поддержка Internet Explorer 8, улучшена производительность и многое другое…

Благодарю за внимание, всем добра.

P. S. В README репозитория теперь есть roadmap.

Автор: Finom

Источник

Поделиться

* - обязательные к заполнению поля