- PVSM.RU - https://www.pvsm.ru -
Всем привет. Представляю очередное обновление фреймворка Matreshka.js до версии 0.2. Напомню: Матрешка — фреймворк общего назначения с окрытым исходным кодом, в идеологию которого положено доминирование данных над внешним видом: вы задаёте правила, как интерфейс должен синхронизированться с данными, затем работаете исключительно с данными, кроме случаев, когда событие интерфейса не касается данных (например, щелчек по кнопке или сабмит формы, сами по себе, не меняют данные, а запускают функцию, которая, в свою очередь, работает с данными).
<select class="my-select">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
Создаем экземпляр:
var mk = new Matreshka();
Связываем свойство x с элементом .my-select:
mk.bindElement( 'x', '.my-select' );
Меняем данные
mk.x = 2;
После того, как мы присвоим свойству x другое значение, остояние элемента изменися соответствующим образом.
Взгляните на живой пример [6]
Другой важной чертой матрешки являются события (в том числе и кастомные). Например, Матрешка умеет отлавливать изменение значения свойства:
mk.on( 'change:x', function( evt ) {
alert( 'x изменен на ' + evt.value );
});
Код выведет "x изменен на Привет":
mk.x = 'Привет';
Подробнее об этих и других фичах смотрите по ссылкам выше.
Ссылка на сайт Матрешки [7]. Ссылка на github репозиторий [8].
Матрешка теперь моддерживает спецификацию определения асинхронных модулей, Asynchronous Module Definition. Другими словами, Матрешка совместима с библиотеками, типа requirejs [9]. Это значит, что теперь можно писать тру-код, не гадящий в глобальное пространство имен. Поддерживается два типа подключения: запрос именованного модуля и запрос безымянного модуля.
Именованные модули:
requirejs.config({
paths: {
xclass: 'path/to/matreshka',
matreshka: 'path/to/matreshka'
}
});
require(['xclass', 'matreshka'], function(Class, MK) {
return Class({
'extends': MK
//...
});
});
Но это, скорее, побочный эффыект использования новой файловой структуры проекта. А рекомендованный способ — запрос безымянного модуля:
require(['path/to/matreshka'], function( MK ) {
return MK.Class({
'extends': MK
// ...
});
});
Как видете, Матрешка содержит свойство Class, которое дублирует функцию, создающую классы: нет нужды запрашивать дополнительный модуль.
1. Метод addDependence был переименован в addDependency по подсказке читателя buriy [11] (спасибо ему), старый метод помечен, как «устаревший».
2. Метод теперь поддерживает обещанную возможность добавления зависимости от свойств других классов. Синтаксис второго аргумента таков: [ инстанс, "ключ", инстанс, "ключ", инстанс, "ключ" ... ] — массив, с нечетными элементами — экземплярами классов, четными — ключами этих экземпляров, от которых и зависит искомое свойство. Взгляните на пример:
this.addDependency( 'a', [
anotherInstance1, 'b',
this, 'c',
anotherInstance2, 'd'
], function( b, c, d ) {
return b + c + d;
});
Здесь свойство "a" зависит от свойства "b" объекта anotherInstance1, от свойства "d" объекта anotherInstance2 и от собственного свойства "c". Старый синтаксис по-прежнему работает:
this.addDependency( 'a', 'b c', function( b, c ) {
return b + c;
});
3. Безопасные зависимости. Этот пункт никак не отражается на синтаксисе: начиная с этого релиза метод избегает бесконечного цикла при неправильном использовании addDependency. Представьте себе ситуацию, когда свойство "a" зависит от свойства "b", свойство "b" зависит от свойства "c", а свойство "c", в свою очередь, зависит от "a". Абстрактная иллюстрация к примеру:
this.addDependency( 'a', 'b', function( b ) {
return b * 2;
});
this.addDependency( 'b', 'c', function( c ) {
return c * 3;
});
this.addDependency( 'c', 'a', function( a ) {
return a / 5;
});
Каждая зависимость в этом коде вызывала следующую, результатом чего получаем повисшую страницу. Теперь же появилась защита от таких ошибок: код передаёт через всю цепочку зависимостей специальный флаг, и, когда фреймворк доходит до потенциально опасной зависимости, цепочка останавливается. addDependency в новом виде позволяет строить взаимные зависимости на основе сложных (или не очень) формул, не опасаясь ошибок в реализации этих формул. Пример вычисления периметра прямоугольника по длинам сторон, и вычисления длин сторон:
this.addDependency( 'p', 'a b', function( a, b ){
return (a + b) * 2;
});
this.addDependency( 'a', 'p b', function( p, b ){
return p/2 - b;
});
this.addDependency( 'b', 'p a', function( p, a ){
return p/2 - a;
});
Представьте себе следующую ситуацию (взята из моей практики). У вас есть форма с некими текстовыми полями: чекбосами и пр. Когда меняется значение одного из элементов формы, приложение должно отправить запрос на сервер, который, в свою очередь, возвращает данные для рендеринга трёх графиков. Рисование графиков — дело тяжелое для процессора и на слабом компьютере занимает полсекунды (Highcharts [13] он такой). Теперь представьте пользователя, которому скучно и он решил многократно кликнуть на чекбокс. Что произойдет? Отправится куча запросов, вернется куча ответов, которые так же многократно отрисуют график. Что обычно делают в таком случае? Отменяют запрос на сервер. Спрашивается: зачем было этот запрос посылать, если можно было дождаться, пока тот угомонится? :)
Для решения этой задачи я использовал простейшую функцию (возможно, велосипед), которая принимает другую функцию в качестве аргумента и возвращает её модификацию, которая может быть запущена только однажды за определенный промежуток времени. Без нее не обходится ни один проект, поэтому было решено включить её в код Матрешки. Пример:
var doSomethingHeavy = function( i ) {
console.log( 'Ok', i );
};
var procrastinateSomethingHeavy = MK.procrastinate( doSomethingHeavy );
for( var i = 0; i < 100; i++ ) {
procrastinateSomethingHeavy( i );
}
// >> Ok 100
Код функции (на случай, если вы захотите использовать её вне Матрешки):
var procrastinate =function ( f, d, thisArg ) {
var timeout;
if( typeof d !== 'number' ) {
thisArg = d;
d = 0;
}
return function() {
var args = arguments,
_this = this;
clearTimeout( timeout );
timeout = setTimeout( function() {
f.apply( thisArg || _this, args );
}, d || 0 );
};
};
Метод, кроме «прокрастинирующей» функции, принимает задержку, и контекст в качестве аргументов. Задержка отвечает за то, на сколько миллисикунд будет отложен реальный вызов функции при очередной попытке её вызова.
А вот пример случая, когда функция никогда не будет вызвана (для лучшего понимания).
var procrastinateSomethingHeavy = MK.procrastinate( function() {
console.log( 'Ok' );
}, 1000 );
setInterval( function() {
procrastinateSomethingHeavy();
}, 500 ); // интервал меньше задержки
initialize
Привязчик (binder [14]) — третий аргумент метода Matreshka#bindElement [15]. Если вы помните, это объект, состоящий из трех свойств: on (по какому DOM событию обновить свойство), getValue (как извлечь значение свойства из элемента), setValue (как установить значение свойства элементу). Подробнее вот здесь [16] (кстати, все статьи о Матрешке обновляются каждый релиз и являются актуальным материалом). Теперь появился еще одно опциональное свойство initialize.
initialize — функция, запускающаяся во время привязки, а точнее, до неё. Задача функци — подсластить код. Взгляните на пример из первой статьи [1]:
Во-первых, перед привязкой объявим слайдер:
<div class="slider"></div>$( ".slider" ).slider({ min: 0, max: 100 });Во-вторых объявляем экземпляр Матрешки:
var mk = new Matreshka();Дальше вызываем привязку:
mk.bindElement( 'x', '.slider', { on: 'slide', // событие, по которому из элемента извлекается значение getValue: function() { return $( this ).slider( 'option', 'value' ); // как вытащить значение из элемента (см. документацию jQuery ui.slider)? }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); // как установить значение для элемента (см. документацию jQuery ui.slider)? } });
Код несколько избыточен: мы дважды обращаемся к элементу с классом slider (сначала, применяя плагин, затем привязывая элемент). Теперь этого можно избежать:
var mk = new Matreshka();
mk.bindElement( 'x', '.slider', {
initialize: function() {
$( this ).slider({ min: 0, max: 100 });
},
on: 'slide',
getValue: function() {
return $( this ).slider( 'option', 'value' );
},
setValue: function( v ) {
$( this ).slider( 'option', 'value', v );
}
});
Этот новый метод, как не трудно догадаться, определяет сеттер для свойства.
this.defineSetter( 'x', function( value ) {
return alert( value );
});
При использовании метода нужно помнить, что он перетирает встроенный сеттер свойства (если он был) и события изменения свойства не будут работать.
this.x = 1;
this.on( 'change:x', function( evt ) { // обраьотчик, который не сработает из-за перетертого сеттера
alert( 'x is changed to ' + evt.value );
});
this.defineSetter( 'x', function() {
// ...
});
this.x = 2;
Сожалуй, самое важное в этом релизе — возможность добавить обработчик события на внутреннее содержимое экземпляра.
"ключ@имя_события"Теперь можно добавить обработчик для свойства внутри любого класса, унаследованного от Матрешки (в том числе, для MK.Object [18] и MK.Array [19]), при условии, если значением свойства является экземпляр Матрешки. Взгляните на пример:
var mk = new MK;
mk.on( 'x@yeah', function() {
alert( 'yeah' );
});
mk.x = new MK;
mk.x.trigger( 'yeah' );
Обратите внимание, что порядок определения свойства и навешивания обработчика не важен: вы можете сперва добавить обработчик события, а, затем, объявить свойство. Причем, если значение свойства меняется, то обработчик срабатывает только для нового значения, а для старого обработчик удаляется.
"@имя_события" для MK.Object
Такое имя события позволяет добавить обработчик для JSON ключа экземпляра MK.Object (что такое JSON ключ, или ключ, отвечающий за данные, смотрите в статье об MK.Object [3]).
var mkObject = new MK.Object;
mkObject.on( '@yeah', function() {
alert( 'yeah' );
});
mkObject.jset( 'x', new MK );
mkObject.x.trigger( 'yeah' );
Порядок объявления свойства и обработчика событий так же не важен.
"@имя_события" для MK.Array
По аналогии с MK.Object, такую же возможность имеет и MK.Array: обработчик навешивается на любой из элементов массива, при условии, что этот элемент унаследован от Матрешки.
var mkArray = new MK.Array;
mkArray.on( '@yeah', function() {
alert( 'yeah' );
});
mkArray.push( new MK );
mkArray[ 0 ].trigger( 'yeah' );
Эти три изменения не ограничтваются только лишь прослушкой события "yeah", можно с уверенностью слушать и другие события, например, "change:свойство"
this.on( 'x@change:y', function() { /* ... */ } );
this.on( '@change:y', function() { /* ... */ } );
Теоретически, эта фича позволяет строить причудливые имена событий, слушая другие события в глубине дерева данных. Скажем, у нас есть структура данных, которую можно изобразить в виде объекта:
{
a: [{
b: { c: { e: 1 } }
}, {
b: { d: { e: 2 } }
}]
}
Для того, чтоб докапаться до изменений свойства "e", можно добавить такой обработчик:
this.on( 'a@@b@@change:e', function() { /* ... */ } );
У Матрешки есть два метода, возвращающие привязанные элементы: Matreshka#bound [21], который возвращет первый привязанный элемент или null и Matreshka#boundAll [22], который возвращает коллекцию привязанных элементов. Здесь могут возникнуть проблемы у новичков, работающих с jQuery и не знакомых с VanillaJS [23] в понимании термина «коллекция» и привыкших к знау доллара. Поэтому, во фреймворк был добавлен метод $bound делающий совершенно то же самое, что и Matreshka#boundAll [22].
this.bindElement( 'a', '#x, #y' );
this.$bound( 'a' ).animate( /* ... */ ); // применяем любой jQuery метод
usejQuery и useBalalaika
Напомню, начиная с версии 0.1, Матрешка избавилась от жесткой зависимости jQuery, используя микро-библиотеку «Балалайка», если jQuery нет на странице. Седствием этогого изменения было создание двух методов, которые, не зависимо от наличия jQuery, заставляли Матрешку использовать одну з двух библиотек с помощью методов usejQuery (на случай, если jQuery была подключена после Матрешки) и useBalalaika (на случай, если jQuery был подключен раньше Матрешки, но вы всё равно хотите использовать встроенную библиотеку). Теперь плявился метод, который позволяет использовать вообще любую jQuery-подобную библиотеку (usejQuery и useBalalaika помечены, как устаревшие).
Примеры использования:
MK.useAs$( jQuery );
MK.useAs$( jQuery.noConflict() );
MK.useAs$( Zepto );
MK.useAs$( MK.$b ); // Балалайка
Следствием этого изменения стало то, что Матрешка, загружаясь, использует библиотеку знак-доллара, если такая есть и имеет определенные методы, вместо использования только лишь jQuery. Какие именно методы, можете узнать в исходном коде одного из файлов проекта [25].
Небольшое изменение, добавляющее ситаксический сахар в классы (см. статью о наседовании [2]). Часто, создавая класс, конструктору этого класса требуется, всего лишь, вызвать конструктор родителя в собственном контексте:
var MyClass = Class({
'extends': AnotherClass,
constructor: function() {
AnotherClass.call( this, arguments );
},
someNewMethod: function() { /* ... */ }
});
Теперь то же самое можно сделать более кратко:
var MyClass = Class({
'extends': AnotherClass,
constructor: AnotherClass.same(),
someNewMethod: function() { /* ... */ }
});
"click::x") до того, как элемент был привязанУ Матрешки есть возможность навешивать обработчики событий на приязанные элементы с помощью метода Matreshka#on [27]:
this.bindElement( 'x', '.my-element' );
this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});
Проблема в том, что нельзя было добавить обработчик DOM собтия до того, как элемент был привязан. Приходилось извращаться ожиданием события bind и добавлением обработчика по наступлению этого события:
this.on( 'bind:x', function() {
this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});
});
this.bindElement( 'x', '.my-element' );
Теперь порядок привязки/добавления DOM события не важен:
this.on( 'click::x', function() {
alert( '.my-element is clicked' );
});
this.bindElement( 'x', '.my-element' );
thisundefined в качестве аргумента"modify" для класса Matreshka.Array [19]"delete" при удалении свойства вместо "remove" потому что у Matreshka.Array есть событие с таким же именем, но вызываемое в другом случае[].forEach не существует, генерируется ошибка с предложением подсключить es5-shim [30]eventName + eventHandler + context может быть добавленна только раз на один экземплярClass (splice vs slice)
1. В следующей статье я ознакомлю вас с реализацией TodoMVC. Статья, уже готова, но требует редактирования. Реализация тоже готова, но для нее допиливается документация.
2. После этого планируется большая статья о MK.Array, заменяющая предыдущую. Там я расскажу подробне о методах, о том, как рендерятся элементы массива, о «модели» и о том, как передавать опции в методы массивов.
3. Версия 0.3 с кучей интересных изменений, которые уже тестируется. Как обычно, будет статья.
Затем, грядет большое ревью документации, в том числе, с причесыванием текстов и исправлению ошибок, касающихся английского языка. Текст главной страницы [7] и страницы «Почему Матрешка?» [35] уже исправлен.
Всем добра!
Автор: Finom
Источник [36]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/66645
Ссылки в тексте:
[1] Введение: http://habrahabr.ru/post/196146/
[2] Наследование: http://habrahabr.ru/post/200078/
[3] MK.Object: http://habrahabr.ru/post/196886/
[4] MK.Array: http://habrahabr.ru/post/198212/
[5] Matreshka.js v0.1: http://habrahabr.ru/post/217241/
[6] Взгляните на живой пример: http://jsbin.com/vidawequ/7/edit
[7] Ссылка на сайт Матрешки: http://finom.github.io/matreshka/
[8] Ссылка на github репозиторий: https://github.com/finom/matreshka/
[9] requirejs: http://requirejs.org/
[10] Matreshka#addDependency: http://finom.github.io/matreshka/docs/Matreshka.html#addDependency-1
[11] buriy: http://habrahabr.ru/users/buriy/
[12] Matreshka.procrastinate: http://finom.github.io/matreshka/docs/Matreshka.html#procrastinate
[13] Highcharts: http://www.highcharts.com/
[14] binder: http://finom.github.io/matreshka/docs/global.html#binder
[15] Matreshka#bindElement: http://finom.github.io/matreshka/docs/Matreshka.html#bindElement-1
[16] вот здесь: http://habrahabr.ru/post/196146/#how-matreshka-bindings-work
[17] Matreshka#defineSetter: http://finom.github.io/matreshka/docs/Matreshka.html#defineSetter-1
[18] MK.Object: http://finom.github.io/matreshka/docs/Matreshka.Object.html
[19] MK.Array: http://finom.github.io/matreshka/docs/Matreshka.Array.html
[20] Matreshka#$bound: http://finom.github.io/matreshka/docs/Matreshka.html#$bound
[21] Matreshka#bound: http://finom.github.io/matreshka/docs/Matreshka.html#bound
[22] Matreshka#boundAll: http://finom.github.io/matreshka/docs/Matreshka.html#boundAll
[23] VanillaJS: http://vanilla-js.com/
[24] Matreshka.useAs$: http://finom.github.io/matreshka/docs/Matreshka.html#useAs$
[25] исходном коде одного из файлов проекта: https://github.com/finom/matreshka/blob/master/src/dollar-lib.js
[26] xclass.same: http://finom.github.io/matreshka/docs/global.html#xclass
[27] Matreshka#on: http://finom.github.io/matreshka/docs/Matreshka.html#on
[28] Matreshka.Array#initializeSmartArray: http://finom.github.io/matreshka/docs/Matreshka.Array.html#initializeSmartArray
[29] Matreshka.Array#createFrom: http://finom.github.io/matreshka/docs/Matreshka.Array.html#createFrom
[30] es5-shim: https://github.com/es-shims/es5-shim
[31] Matreshka#once: http://finom.github.io/matreshka/docs/Matreshka.html#once
[32] Matreshka#off: http://finom.github.io/matreshka/docs/Matreshka.html#off
[33] Matreshka#trigger: http://finom.github.io/matreshka/docs/Matreshka.html#trigger
[34] MK#set: http://finom.github.io/matreshka/docs/Matreshka.html#set
[35] «Почему Матрешка?»: http://finom.github.io/matreshka/about/
[36] Источник: http://habrahabr.ru/post/231333/
Нажмите здесь для печати.