function NumberIterator(number){
var i = 0;
return {
next: function(){
return i < number
? {done: false, value: i++}
: {done: true};
}
}
}
var iter = NumberIterator(3);
iter.next(); // => {done: false, value: 0}
iter.next(); // => {done: false, value: 1}
iter.next(); // => {done: false, value: 2}
iter.next(); // => {done: true}
четверг, 20 ноября 2014 г.
JavaScript Range function
Первый вариант
if(!Array.indexOf){
Array.prototype.indexOf = function (obj) {
for (var i=0; i<this.length; i++) {
if (this[i] == obj) {
return i;
}
}
}
}
function range(begin, end, step){
var rangeArray = []
, rangeString = 'abcdefghijklmnopqrstuvwxyz';
if (Object.prototype.toString.call(begin) == '[object Number]') {
rangeArray[0] = begin;
step = step || 1;
while (begin + step <= end) {
rangeArray[rangeArray.length] = begin += step;
}
} else {
if (begin === begin.toUpperCase()){
end = end.toUpperCase();
rangeString = rangeString.toUpperCase();
}
rangeString = rangeString.substring(rangeString.indexOf(begin), rangeString.indexOf(end) + 1);
rangeArray = rangeString.split('');
}
return rangeArray;
}
console.log(range(0, 10));
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(-100, 100, 20));
// [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]
console.log(range('A', 'F'));
// ['A', 'B', 'C',' D', 'E', 'F')
console.log(range('m', 'r'));
// ['m', 'n', 'o', 'p', 'q', 'r']
Второй вариант
function range (start, end, step) {
var range = []
, typeofStart = typeof start
, typeofEnd = typeof end;
if (step === 0) {
throw TypeError("Step cannot be zero.");
}
if (typeofStart == "undefined" || typeofEnd == "undefined") {
throw TypeError("Must pass start and end arguments.");
} else if (typeofStart != typeofEnd) {
throw TypeError("Start and end arguments must be of same type.");
}
typeof step == "undefined" && (step = 1);
if (end < start) {
step = -step;
}
if (typeofStart == "number") {
while (step > 0 ? end >= start : end <= start) {
range.push(start);
start += step;
}
} else if (typeofStart == "string") {
if (start.length != 1 || end.length != 1) {
throw TypeError("Only strings with one character are supported.");
}
start = start.charCodeAt(0);
end = end.charCodeAt(0);
while (step > 0 ? end >= start : end <= start) {
range.push(String.fromCharCode(start));
start += step;
}
} else {
throw TypeError("Only string and number types are supported");
}
return range;
}
console.log(range("a", "z", 1));
console.log(range("Z", "A", 1));
console.log(range("A", "Z", 3));
console.log(range(0, 25, 1));
console.log(range(0, 25, 5));
console.log(range(20, 5, 5));
if(!Array.indexOf){
Array.prototype.indexOf = function (obj) {
for (var i=0; i<this.length; i++) {
if (this[i] == obj) {
return i;
}
}
}
}
function range(begin, end, step){
var rangeArray = []
, rangeString = 'abcdefghijklmnopqrstuvwxyz';
if (Object.prototype.toString.call(begin) == '[object Number]') {
rangeArray[0] = begin;
step = step || 1;
while (begin + step <= end) {
rangeArray[rangeArray.length] = begin += step;
}
} else {
if (begin === begin.toUpperCase()){
end = end.toUpperCase();
rangeString = rangeString.toUpperCase();
}
rangeString = rangeString.substring(rangeString.indexOf(begin), rangeString.indexOf(end) + 1);
rangeArray = rangeString.split('');
}
return rangeArray;
}
console.log(range(0, 10));
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(-100, 100, 20));
// [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]
console.log(range('A', 'F'));
// ['A', 'B', 'C',' D', 'E', 'F')
console.log(range('m', 'r'));
// ['m', 'n', 'o', 'p', 'q', 'r']
function range (start, end, step) {
var range = []
, typeofStart = typeof start
, typeofEnd = typeof end;
if (step === 0) {
throw TypeError("Step cannot be zero.");
}
if (typeofStart == "undefined" || typeofEnd == "undefined") {
throw TypeError("Must pass start and end arguments.");
} else if (typeofStart != typeofEnd) {
throw TypeError("Start and end arguments must be of same type.");
}
typeof step == "undefined" && (step = 1);
if (end < start) {
step = -step;
}
if (typeofStart == "number") {
while (step > 0 ? end >= start : end <= start) {
range.push(start);
start += step;
}
} else if (typeofStart == "string") {
if (start.length != 1 || end.length != 1) {
throw TypeError("Only strings with one character are supported.");
}
start = start.charCodeAt(0);
end = end.charCodeAt(0);
while (step > 0 ? end >= start : end <= start) {
range.push(String.fromCharCode(start));
start += step;
}
} else {
throw TypeError("Only string and number types are supported");
}
return range;
}
console.log(range("a", "z", 1));
console.log(range("Z", "A", 1));
console.log(range("A", "Z", 3));
console.log(range(0, 25, 1));
console.log(range(0, 25, 5));
console.log(range(20, 5, 5));
пятница, 14 ноября 2014 г.
Самый полный способ имитации традиционных классов в JavaScript включая паттерн синглтон и примеси
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head><title>JavaScript Class</title></head>
<body>
<script type="text/javascript">
var extend = this.extend || function (childClass, parentClass) {
// Копируем в дочерний класс статичные свойства и методы из родительского класса
for (var key in parentClass) {
if (parentClass.hasOwnProperty(key)) {
childClass[key] = parentClass[key];
}
}
// Ставим ссылку конструктора на дочерний класс
function F () {
this.constructor = childClass;
}
F.prototype = parentClass.prototype; // Копируем свойства и методы прототипа из родителя во временную функцию
childClass.prototype = new F(); // Присваиваем дочернему прототипу объект, созданный из родительского класса
childClass.__super__ = parentClass.prototype; // Для непосредственного вызова родительских методов, если потребуется, хотя это редко будет нужно (взято из coffeescript). Можно использовать в дочернем методе, вызывая дополнительно метод родительского класса внутри него так ChildClass.__super__.move.call(this, 5);
};
var mixin = this.mixin || function (childClass) {
var mixins = Array.prototype.slice.call(arguments, 1) // Классы с набором методов mixin
, i
, len = mixins.length
, key;
if (len) {
// Для каждого элемента из массива mixins копируем статичные свойства и методы в дочерний класс
for (i = 0; i < len; i++) {
for (key in mixins[i]) {
// Копируем в дочерний класс статичные свойства и методы из примеси
if (mixins[i].hasOwnProperty(key)) {
childClass[key] = mixins[i][key];
}
}
}
// Ставим ссылку конструктора на дочерний класс
function F () {
this.constructor = childClass;
}
F.prototype = childClass.prototype; // Копируем свойства и методы прототипа из примеси во временную функцию
for (i = 0; i < len; i++) {
// Копируем в прототип дочернего класса свойства и методы из прототипа примеси
for (key in mixins[i].prototype) {
F.prototype[key] = mixins[i].prototype[key];
}
}
childClass.prototype = new F(); // Присваиваем дочернему прототипу объект, созданный из примеси
}
};
</script>
<script type="text/javascript">
// Родительский класс
var ParentClass = (function(){
// Constructor
function ParentClass (initVar, initMethod) {
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
// Private
var privateVar = 1;
function privateFunction () {}
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ParentClass)) {
return new ParentClass(initVar, initMethod);
}
}
ParentClass.prototype.constructor = ParentClass;
// Public
ParentClass.prototype.publicVar = '5) ' + (new Date()).getTime();
ParentClass.prototype.publicMethod = function () {console.log('6) ' + (new Date()).getTime());};
// Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = '7) ' + (new Date()).getTime();
function privateMethod () {console.log('8) ' + (new Date()).getTime());}
ParentClass.prototype.getPrivateVar = function () {return privateVar;};
ParentClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ParentClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Static
ParentClass.staticVar = '9) ' + (new Date()).getTime();
ParentClass.staticMethod = function () {console.log('10) ' + (new Date()).getTime());};
return ParentClass;
})();
// Примесь
var Mixin = (function(){
function Mixin () {}
Mixin.prototype.mixinMethod = function () {console.log('11) ' + (new Date()).getTime() + ' | ' + this.privilegeVar);};
Mixin.mixinStaticMethod = function () {console.log('12) ' + (new Date()).getTime());};
return Mixin;
})();
mixin(ParentClass, Mixin);
// Простое наследование родительского класса дочерним
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ChildClass )) {
return new ChildClass (initVar, initMethod);
}
}
return ChildClass;
})(ParentClass);
// Наследование родительского класса дочерним с перезаписью свойств и методов
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// parentClass.call(null, publicMethod); // Вызвать конкретный метод родительского класса, если необходимо
// Override Init
this.initVar = initVar;
this.initMethod = initMethod;
// Override Privilege
this.privilegeVar = 'Child 1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('Child 2) ' + (new Date()).getTime());};
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ChildClass )) {
return new ChildClass (initVar, initMethod);
}
}
// Override Public
ChildClass.prototype.publicVar = 'Child 5) ' + (new Date()).getTime();
ChildClass.prototype.publicMethod = function () {console.log('Child 6) ' + (new Date()).getTime());};
// Override Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = 'Child 7) ' + (new Date()).getTime();
function privateMethod () {console.log('Child 8) ' + (new Date()).getTime());}
ChildClass.prototype.getPrivateVar = function () {return privateVar;};
ChildClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ChildClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Override Static
ChildClass.staticVar = 'Child 9) ' + (new Date()).getTime();
ChildClass.staticMethod = function () {console.log('Child 10) ' + (new Date()).getTime());};
return ChildClass;
})(ParentClass);
// Тестирование наследования и примешивания примесей
console.log('====================================');
console.log('Создание объекта из родительского класса');
console.log('====================================');
var parentObject = new ParentClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent privilegeMethod: ' + parentObject.privilegeMethod);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent initMethod: ' + parentObject.initMethod);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent publicMethod: ' + parentObject.publicMethod);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent privateMethod: ' + parentObject.getPrivateMethod());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent staticMethod: ' + ParentClass.staticMethod);
console.log('Parent constructor: ' + parentObject.constructor.name);
console.log('Parent mixinMethod: ' + parentObject.mixinMethod);
console.log('Parent mixinStaticMethod: ' + ParentClass.mixinStaticMethod);
parentObject.mixinMethod();
console.log('====================================');
console.log('Создание объекта из дочернего класса');
console.log('====================================');
var childObject = new ChildClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
console.log('Child privilegeVar: ' + childObject.privilegeVar);
console.log('Child privilegeMethod: ' + childObject.privilegeMethod);
console.log('Child initVar: ' + childObject.initVar);
console.log('Child initMethod: ' + childObject.initMethod);
console.log('Child publicVar: ' + childObject.publicVar);
console.log('Child publicMethod: ' + childObject.publicMethod);
console.log('Child privateVar: ' + childObject.getPrivateVar());
console.log('Child privateMethod: ' + childObject.getPrivateMethod());
console.log('Child staticVar: ' + ChildClass.staticVar);
console.log('Child staticMethod: ' + ChildClass.staticMethod);
console.log('Child constructor: ' + childObject.constructor.name);
console.log('Child mixinMethod: ' + childObject.mixinMethod);
console.log('Child mixinStaticMethod: ' + ChildClass.mixinStaticMethod);
childObject.mixinMethod();
console.log('====================================');
console.log('Создание значений объекта дочернего класса');
console.log('====================================');
childObject.privilegeVar = 1;
childObject.initVar = 2;
childObject.publicVar = 3;
childObject.setPrivateVar(4);
ChildClass.staticVar = 5;
childObject.mixinMethod = function () {console.log('Child new mixinMethod');};
ChildClass.mixinStaticMethod = function () {console.log('Child new mixinStaticMethod');};
console.log('====================================');
console.log('Проверка значений объекта дочернего класса');
console.log('====================================');
console.log('Child privilegeVar: ' + childObject.privilegeVar);
console.log('Child initVar: ' + childObject.initVar);
console.log('Child publicVar: ' + childObject.publicVar);
console.log('Child privateVar: ' + childObject.getPrivateVar());
console.log('Child staticVar: ' + ChildClass.staticVar);
console.log('Child mixinMethod: ' + childObject.mixinMethod);
console.log('Child mixinStaticMethod: ' + ChildClass.mixinStaticMethod);
console.log('====================================');
console.log('Проверка не изменились ли значения объекта родительского класса');
console.log('====================================');
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent mixinMethod: ' + parentObject.mixinMethod);
console.log('Parent mixinStaticMethod: ' + ParentClass.mixinStaticMethod);
// Синглтон
var ParentClass = (function(){
// Constructor
function ParentClass (initVar, initMethod) {
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
}
ParentClass.prototype.constructor = ParentClass;
// Public
ParentClass.prototype.publicVar = '5) ' + (new Date()).getTime();
ParentClass.prototype.publicMethod = function () {console.log('6) ' + (new Date()).getTime());};
// Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = '7) ' + (new Date()).getTime();
function privateMethod () {console.log('8) ' + (new Date()).getTime());}
ParentClass.prototype.getPrivateVar = function () {return privateVar;};
ParentClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ParentClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Static передаются через синглтон
ParentClass.staticVar = '9) ' + (new Date()).getTime();
ParentClass.staticMethod = function () {console.log('10) ' + (new Date()).getTime());};
// Singleton
var instance;
function SingletonParentClass (initVar, initMethod) {
if (!instance) {
instance = new ParentClass(initVar, initMethod);
}
return instance;
}
SingletonParentClass.prototype = ParentClass.prototype;
for (var key in ParentClass) {
if (ParentClass.hasOwnProperty(key)) {
SingletonParentClass[key] = ParentClass[key];
}
}
return SingletonParentClass;
})();
mixin(ParentClass, Mixin);
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
}
// Singleton
/* Вариант 1 */
/*
var instance;
function SingletonChildClass (initVar, initMethod) {
if (!instance) {
instance = new ChildClass(initVar, initMethod);
}
return instance;
}
*/
// Но лучше даже так, чтобы можно было наследовать от Singleton без проблем с замыканием переменной instance, хранящей внутри себя созданный объект
/* Вариант 2 */
/*
function SingletonChildClass (initVar, initMethod) {
if (!SingletonChildClass.instance) {
SingletonChildClass.instance = new ChildClass(initVar, initMethod);
}
return SingletonChildClass.instance;
}
SingletonChildClass.instance = null;
SingletonChildClass.prototype = ChildClass.prototype;
for (var key in ChildClass) {
if (ChildClass.hasOwnProperty(key)) {
SingletonChildClass[key] = ChildClass[key];
}
}
*/
// return SingletonChildClass;
/* Вариант 3 */
return singleton(ChildClass);
})(ParentClass);
/* Вариант 3 */
function singleton (ParentClass) {
function SingletonClass () {
if (!SingletonClass.instance) {
SingletonClass.instance = ParentClass.apply(this, arguments);
}
return SingletonClass.instance;
}
SingletonClass.instance = null;
SingletonClass.prototype = ParentClass.prototype;
for (var key in ParentClass) {
if (ParentClass.hasOwnProperty(key)) {
SingletonClass[key] = ParentClass[key];
}
}
return SingletonClass;
}
// Тестирование синглтона
console.log('====================================');
console.log('Проверка значений синглтона');
console.log('====================================');
var parentObject = new ParentClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
var singletonObject = new ParentClass('Singleton) ' + (new Date()).getTime(), function () {console.log('Singleton) ' + (new Date()).getTime());});
console.log('Singleton: ' + (parentObject === singletonObject));
console.log('====================================');
console.log('Проверка значений объекта родительского класса');
console.log('====================================');
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent privilegeMethod: ' + parentObject.privilegeMethod);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent initMethod: ' + parentObject.initMethod);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent publicMethod: ' + parentObject.publicMethod);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent privateMethod: ' + parentObject.getPrivateMethod());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent staticMethod: ' + ParentClass.staticMethod);
console.log('Parent constructor: ' + parentObject.constructor.name);
console.log('====================================');
console.log('Проверка значений объекта синглтона');
console.log('====================================');
console.log('Singleton privilegeVar: ' + singletonObject.privilegeVar);
console.log('Singleton privilegeMethod: ' + singletonObject.privilegeMethod);
console.log('Singleton initVar: ' + singletonObject.initVar);
console.log('Singleton initMethod: ' + singletonObject.initMethod);
console.log('Singleton publicVar: ' + singletonObject.publicVar);
console.log('Singleton publicMethod: ' + singletonObject.publicMethod);
console.log('Singleton privateVar: ' + singletonObject.getPrivateVar());
console.log('Singleton privateMethod: ' + singletonObject.getPrivateMethod());
console.log('Singleton staticVar: ' + ParentClass.staticVar);
console.log('Singleton staticMethod: ' + ParentClass.staticMethod);
console.log('Singleton constructor: ' + singletonObject.constructor.name);
// Класс, позволяющий выполнить рендеринг, вставку на страницу, добавление событий и удаление объекта после его использования
var RenderHTMLClass = (function(){
// Constructor
function RenderHTMLClass () {
this.html; // HTML-код объекта с привязанным к нему событиям
this.render(); // Генерирование HTML-кода объекта с привязыванием к нему событий
}
// Public
RenderHTMLClass.prototype.render = function () {
this.html = document.createElement('h1');
this.html.innerHTML = 'Привет';
this.html.addEventListener('click', this._event, false);
};
// Private
RenderHTMLClass.prototype._event = function () {
alert(1);
};
// Clear - Очистка: удаление HTML-кода объекта из DOM и удаление привязанных к нему событий
RenderHTMLClass.prototype.clearBeforeDeleteReference = function () {
this.html.removeEventListener('click', this._event);
document.body.removeChild(this.html);
}
// Delete - Удаление ссылки на объект JavaScript-кода
RenderHTMLClass.prototype.deleteReference = function (referenceName) {
eval(
referenceName + '= null;' // Устанваливая referenceName равным null мы удаляем ссылку на объект, а после можем удалить и сам объект
+ ' delete ' + referenceName + ';'
);
};
return RenderHTMLClass;
})();
console.log('====================================');
console.log('Проверка значений рендеринга объекта');
console.log('====================================');
var renderHTMLObject = new RenderHTMLClass(); // Создание объекта
document.body.appendChild(renderHTMLObject.html); // Вставка объекта на страницу
console.log(renderHTMLObject.html);
renderHTMLObject.clearBeforeDeleteReference(); // Удаление объекта из кода страницы и очистка, привязанных к нему событий
renderHTMLObject.deleteReference('renderHTMLObject'); // Удаление ссылки на объект из JavaScript
console.log(renderHTMLObject.html);
</script>
</body>
</html>
<html>
<meta charset="utf-8" />
<head><title>JavaScript Class</title></head>
<body>
<script type="text/javascript">
var extend = this.extend || function (childClass, parentClass) {
// Копируем в дочерний класс статичные свойства и методы из родительского класса
for (var key in parentClass) {
if (parentClass.hasOwnProperty(key)) {
childClass[key] = parentClass[key];
}
}
// Ставим ссылку конструктора на дочерний класс
function F () {
this.constructor = childClass;
}
F.prototype = parentClass.prototype; // Копируем свойства и методы прототипа из родителя во временную функцию
childClass.prototype = new F(); // Присваиваем дочернему прототипу объект, созданный из родительского класса
childClass.__super__ = parentClass.prototype; // Для непосредственного вызова родительских методов, если потребуется, хотя это редко будет нужно (взято из coffeescript). Можно использовать в дочернем методе, вызывая дополнительно метод родительского класса внутри него так ChildClass.__super__.move.call(this, 5);
};
var mixin = this.mixin || function (childClass) {
var mixins = Array.prototype.slice.call(arguments, 1) // Классы с набором методов mixin
, i
, len = mixins.length
, key;
if (len) {
// Для каждого элемента из массива mixins копируем статичные свойства и методы в дочерний класс
for (i = 0; i < len; i++) {
for (key in mixins[i]) {
// Копируем в дочерний класс статичные свойства и методы из примеси
if (mixins[i].hasOwnProperty(key)) {
childClass[key] = mixins[i][key];
}
}
}
// Ставим ссылку конструктора на дочерний класс
function F () {
this.constructor = childClass;
}
F.prototype = childClass.prototype; // Копируем свойства и методы прототипа из примеси во временную функцию
for (i = 0; i < len; i++) {
// Копируем в прототип дочернего класса свойства и методы из прототипа примеси
for (key in mixins[i].prototype) {
F.prototype[key] = mixins[i].prototype[key];
}
}
childClass.prototype = new F(); // Присваиваем дочернему прототипу объект, созданный из примеси
}
};
</script>
<script type="text/javascript">
// Родительский класс
var ParentClass = (function(){
// Constructor
function ParentClass (initVar, initMethod) {
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
// Private
var privateVar = 1;
function privateFunction () {}
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ParentClass)) {
return new ParentClass(initVar, initMethod);
}
}
ParentClass.prototype.constructor = ParentClass;
// Public
ParentClass.prototype.publicVar = '5) ' + (new Date()).getTime();
ParentClass.prototype.publicMethod = function () {console.log('6) ' + (new Date()).getTime());};
// Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = '7) ' + (new Date()).getTime();
function privateMethod () {console.log('8) ' + (new Date()).getTime());}
ParentClass.prototype.getPrivateVar = function () {return privateVar;};
ParentClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ParentClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Static
ParentClass.staticVar = '9) ' + (new Date()).getTime();
ParentClass.staticMethod = function () {console.log('10) ' + (new Date()).getTime());};
return ParentClass;
})();
// Примесь
var Mixin = (function(){
function Mixin () {}
Mixin.prototype.mixinMethod = function () {console.log('11) ' + (new Date()).getTime() + ' | ' + this.privilegeVar);};
Mixin.mixinStaticMethod = function () {console.log('12) ' + (new Date()).getTime());};
return Mixin;
})();
mixin(ParentClass, Mixin);
// Простое наследование родительского класса дочерним
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ChildClass )) {
return new ChildClass (initVar, initMethod);
}
}
return ChildClass;
})(ParentClass);
// Наследование родительского класса дочерним с перезаписью свойств и методов
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// parentClass.call(null, publicMethod); // Вызвать конкретный метод родительского класса, если необходимо
// Override Init
this.initVar = initVar;
this.initMethod = initMethod;
// Override Privilege
this.privilegeVar = 'Child 1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('Child 2) ' + (new Date()).getTime());};
// Events
var self = this;
document.addEventListener(click, funciton(){
alert(self.privilegeVar);
}, false);
// Если конструктор вдруг был вызван без слова new
if (!(this instanceof ChildClass )) {
return new ChildClass (initVar, initMethod);
}
}
// Override Public
ChildClass.prototype.publicVar = 'Child 5) ' + (new Date()).getTime();
ChildClass.prototype.publicMethod = function () {console.log('Child 6) ' + (new Date()).getTime());};
// Override Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = 'Child 7) ' + (new Date()).getTime();
function privateMethod () {console.log('Child 8) ' + (new Date()).getTime());}
ChildClass.prototype.getPrivateVar = function () {return privateVar;};
ChildClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ChildClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Override Static
ChildClass.staticVar = 'Child 9) ' + (new Date()).getTime();
ChildClass.staticMethod = function () {console.log('Child 10) ' + (new Date()).getTime());};
return ChildClass;
})(ParentClass);
// Тестирование наследования и примешивания примесей
console.log('====================================');
console.log('Создание объекта из родительского класса');
console.log('====================================');
var parentObject = new ParentClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent privilegeMethod: ' + parentObject.privilegeMethod);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent initMethod: ' + parentObject.initMethod);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent publicMethod: ' + parentObject.publicMethod);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent privateMethod: ' + parentObject.getPrivateMethod());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent staticMethod: ' + ParentClass.staticMethod);
console.log('Parent constructor: ' + parentObject.constructor.name);
console.log('Parent mixinMethod: ' + parentObject.mixinMethod);
console.log('Parent mixinStaticMethod: ' + ParentClass.mixinStaticMethod);
parentObject.mixinMethod();
console.log('====================================');
console.log('Создание объекта из дочернего класса');
console.log('====================================');
var childObject = new ChildClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
console.log('Child privilegeVar: ' + childObject.privilegeVar);
console.log('Child privilegeMethod: ' + childObject.privilegeMethod);
console.log('Child initVar: ' + childObject.initVar);
console.log('Child initMethod: ' + childObject.initMethod);
console.log('Child publicVar: ' + childObject.publicVar);
console.log('Child publicMethod: ' + childObject.publicMethod);
console.log('Child privateVar: ' + childObject.getPrivateVar());
console.log('Child privateMethod: ' + childObject.getPrivateMethod());
console.log('Child staticVar: ' + ChildClass.staticVar);
console.log('Child staticMethod: ' + ChildClass.staticMethod);
console.log('Child constructor: ' + childObject.constructor.name);
console.log('Child mixinMethod: ' + childObject.mixinMethod);
console.log('Child mixinStaticMethod: ' + ChildClass.mixinStaticMethod);
childObject.mixinMethod();
console.log('====================================');
console.log('Создание значений объекта дочернего класса');
console.log('====================================');
childObject.privilegeVar = 1;
childObject.initVar = 2;
childObject.publicVar = 3;
childObject.setPrivateVar(4);
ChildClass.staticVar = 5;
childObject.mixinMethod = function () {console.log('Child new mixinMethod');};
ChildClass.mixinStaticMethod = function () {console.log('Child new mixinStaticMethod');};
console.log('====================================');
console.log('Проверка значений объекта дочернего класса');
console.log('====================================');
console.log('Child privilegeVar: ' + childObject.privilegeVar);
console.log('Child initVar: ' + childObject.initVar);
console.log('Child publicVar: ' + childObject.publicVar);
console.log('Child privateVar: ' + childObject.getPrivateVar());
console.log('Child staticVar: ' + ChildClass.staticVar);
console.log('Child mixinMethod: ' + childObject.mixinMethod);
console.log('Child mixinStaticMethod: ' + ChildClass.mixinStaticMethod);
console.log('====================================');
console.log('Проверка не изменились ли значения объекта родительского класса');
console.log('====================================');
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent mixinMethod: ' + parentObject.mixinMethod);
console.log('Parent mixinStaticMethod: ' + ParentClass.mixinStaticMethod);
// Синглтон
var ParentClass = (function(){
// Constructor
function ParentClass (initVar, initMethod) {
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
}
ParentClass.prototype.constructor = ParentClass;
// Public
ParentClass.prototype.publicVar = '5) ' + (new Date()).getTime();
ParentClass.prototype.publicMethod = function () {console.log('6) ' + (new Date()).getTime());};
// Private использовать не рекомендуется, так как их изменение в классе потомке влияет на родительский класс
var privateVar = '7) ' + (new Date()).getTime();
function privateMethod () {console.log('8) ' + (new Date()).getTime());}
ParentClass.prototype.getPrivateVar = function () {return privateVar;};
ParentClass.prototype.setPrivateVar = function (value) {privateVar = value;};
ParentClass.prototype.getPrivateMethod = function () {return privateMethod;};
// Static передаются через синглтон
ParentClass.staticVar = '9) ' + (new Date()).getTime();
ParentClass.staticMethod = function () {console.log('10) ' + (new Date()).getTime());};
// Singleton
var instance;
function SingletonParentClass (initVar, initMethod) {
if (!instance) {
instance = new ParentClass(initVar, initMethod);
}
return instance;
}
SingletonParentClass.prototype = ParentClass.prototype;
for (var key in ParentClass) {
if (ParentClass.hasOwnProperty(key)) {
SingletonParentClass[key] = ParentClass[key];
}
}
return SingletonParentClass;
})();
mixin(ParentClass, Mixin);
var ChildClass = (function(parentClass){
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass (initVar, initMethod) {
parentClass.apply(this, arguments); // Обязательно для наследования классом потомком родительских свойств и методв из Init и Privilege
// Init public
this.initVar = initVar;
this.initMethod = initMethod;
// Privilege public
this.privilegeVar = '1) ' + (new Date()).getTime();
this.privilegeMethod = function () {console.log('2) ' + (new Date()).getTime());};
}
// Singleton
/* Вариант 1 */
/*
var instance;
function SingletonChildClass (initVar, initMethod) {
if (!instance) {
instance = new ChildClass(initVar, initMethod);
}
return instance;
}
*/
// Но лучше даже так, чтобы можно было наследовать от Singleton без проблем с замыканием переменной instance, хранящей внутри себя созданный объект
/* Вариант 2 */
/*
function SingletonChildClass (initVar, initMethod) {
if (!SingletonChildClass.instance) {
SingletonChildClass.instance = new ChildClass(initVar, initMethod);
}
return SingletonChildClass.instance;
}
SingletonChildClass.instance = null;
SingletonChildClass.prototype = ChildClass.prototype;
for (var key in ChildClass) {
if (ChildClass.hasOwnProperty(key)) {
SingletonChildClass[key] = ChildClass[key];
}
}
*/
// return SingletonChildClass;
/* Вариант 3 */
return singleton(ChildClass);
})(ParentClass);
/* Вариант 3 */
function singleton (ParentClass) {
function SingletonClass () {
if (!SingletonClass.instance) {
SingletonClass.instance = ParentClass.apply(this, arguments);
}
return SingletonClass.instance;
}
SingletonClass.instance = null;
SingletonClass.prototype = ParentClass.prototype;
for (var key in ParentClass) {
if (ParentClass.hasOwnProperty(key)) {
SingletonClass[key] = ParentClass[key];
}
}
return SingletonClass;
}
// Тестирование синглтона
console.log('====================================');
console.log('Проверка значений синглтона');
console.log('====================================');
var parentObject = new ParentClass('3) ' + (new Date()).getTime(), function () {console.log('4) ' + (new Date()).getTime());});
var singletonObject = new ParentClass('Singleton) ' + (new Date()).getTime(), function () {console.log('Singleton) ' + (new Date()).getTime());});
console.log('Singleton: ' + (parentObject === singletonObject));
console.log('====================================');
console.log('Проверка значений объекта родительского класса');
console.log('====================================');
console.log('Parent privilegeVar: ' + parentObject.privilegeVar);
console.log('Parent privilegeMethod: ' + parentObject.privilegeMethod);
console.log('Parent initVar: ' + parentObject.initVar);
console.log('Parent initMethod: ' + parentObject.initMethod);
console.log('Parent publicVar: ' + parentObject.publicVar);
console.log('Parent publicMethod: ' + parentObject.publicMethod);
console.log('Parent privateVar: ' + parentObject.getPrivateVar());
console.log('Parent privateMethod: ' + parentObject.getPrivateMethod());
console.log('Parent staticVar: ' + ParentClass.staticVar);
console.log('Parent staticMethod: ' + ParentClass.staticMethod);
console.log('Parent constructor: ' + parentObject.constructor.name);
console.log('====================================');
console.log('Проверка значений объекта синглтона');
console.log('====================================');
console.log('Singleton privilegeVar: ' + singletonObject.privilegeVar);
console.log('Singleton privilegeMethod: ' + singletonObject.privilegeMethod);
console.log('Singleton initVar: ' + singletonObject.initVar);
console.log('Singleton initMethod: ' + singletonObject.initMethod);
console.log('Singleton publicVar: ' + singletonObject.publicVar);
console.log('Singleton publicMethod: ' + singletonObject.publicMethod);
console.log('Singleton privateVar: ' + singletonObject.getPrivateVar());
console.log('Singleton privateMethod: ' + singletonObject.getPrivateMethod());
console.log('Singleton staticVar: ' + ParentClass.staticVar);
console.log('Singleton staticMethod: ' + ParentClass.staticMethod);
console.log('Singleton constructor: ' + singletonObject.constructor.name);
// Класс, позволяющий выполнить рендеринг, вставку на страницу, добавление событий и удаление объекта после его использования
var RenderHTMLClass = (function(){
// Constructor
function RenderHTMLClass () {
this.html; // HTML-код объекта с привязанным к нему событиям
this.render(); // Генерирование HTML-кода объекта с привязыванием к нему событий
}
// Public
RenderHTMLClass.prototype.render = function () {
this.html = document.createElement('h1');
this.html.innerHTML = 'Привет';
this.html.addEventListener('click', this._event, false);
};
// Private
RenderHTMLClass.prototype._event = function () {
alert(1);
};
// Clear - Очистка: удаление HTML-кода объекта из DOM и удаление привязанных к нему событий
RenderHTMLClass.prototype.clearBeforeDeleteReference = function () {
this.html.removeEventListener('click', this._event);
document.body.removeChild(this.html);
}
// Delete - Удаление ссылки на объект JavaScript-кода
RenderHTMLClass.prototype.deleteReference = function (referenceName) {
eval(
referenceName + '= null;' // Устанваливая referenceName равным null мы удаляем ссылку на объект, а после можем удалить и сам объект
+ ' delete ' + referenceName + ';'
);
};
return RenderHTMLClass;
})();
console.log('====================================');
console.log('Проверка значений рендеринга объекта');
console.log('====================================');
var renderHTMLObject = new RenderHTMLClass(); // Создание объекта
document.body.appendChild(renderHTMLObject.html); // Вставка объекта на страницу
console.log(renderHTMLObject.html);
renderHTMLObject.clearBeforeDeleteReference(); // Удаление объекта из кода страницы и очистка, привязанных к нему событий
renderHTMLObject.deleteReference('renderHTMLObject'); // Удаление ссылки на объект из JavaScript
console.log(renderHTMLObject.html);
</script>
</body>
</html>
среда, 12 ноября 2014 г.
Обучение работы с Node JS
Хороший курс от Microsoft - Building Apps with Node.js Jump Start
www.microsoftvirtualacademy.com/training-courses/building-apps-with-node-js-jump-start
www.microsoftvirtualacademy.com/training-courses/building-apps-with-node-js-jump-start
Подробное описание архитектур: MVC, MVP, MVVM
Scaling Isomorphic Javascript Code
Take a minute and think about how often you've heard the phrase "Model-View-Controller" (or MVC). Do you really know what it means? At a high-level it is about a separation of concerns between the major areas of functionality in presentation-centric applications built on retained graphics systems (i.e not-raster graphics, such as games). Dig a little deeper and it becomes obvious that it is just a bucket term for a lot of different things. In the past, most development communities built-out an MVC solution that worked well for their most popular use-case and moved on. Great examples of this are the Ruby and Python communities with the MVC-based architecture Rails and Django both embody.
This approach has been acceptable for other languages such as Java, Ruby, and Python it is simply not good enough for Node.js for one reason: Javascript is now an isomorphic language. By isomorphic we mean that any given line of code (with notable exceptions) can execute both on the client and the server. On the surface this seemingly innocuous property creates a number of challenges that are not solved by current MVC-based patterns. This article will explore some of these existing patterns, how both their implementation and concerns vary across languages and environments, and how they are not good enough for a truly isomorphic Javascript codebase. In conclusion, we will explore a new pattern: Resource-View-Presenter.
At a glance
Design patterns are the bread and butter of application development. They encapsulate and outline the concerns of the application and the environment of the in which it exists. Between the browser and the server these concerns can vary widely:
- Is the view ephemeral (e.g. on the server) or long-lived (e.g. in the browser)?
- Is the view reusable across different use-cases or scenarios?
- Should the view be annotated with application-specific tags or markup?
- Where should the bulk of the business logic reside? (in the model? in the controller?)
- How is the application state persisted or accessed?
Lets explore some of the existing patterns and how they answer these questions:
- Model-View-Controller
- Model2
- Model-View Presenter and Model-View-ViewModel
- Modern Javascript Implementations
- Real-time Implications
- tl;dr? Introducing Resource-View-Presenter
- Conclusion
MVC
Figure 1: Model-View-Controller
The traditional Model-View-Controller pattern assumes a long-lived view and a swappable Controller. For example, in a given view you may have different Controller logic depending on who is logged in. At a high-level no decision is made about how the view is rendered (i.e. what templating engine is used).
Given the assumption of a long-lived view and that user interaction is by definition coming into the view, traditional MVC is a very useful pattern for front-end development. As we will explore later, a slightly modified version of this pattern is in fact whatBackbone.js uses.
Model2
Figure 2: Model2 Model-View-Controller
Don't be scared if you've never heard of Model2 before; it is a design pattern that dates back to 1999 when it was coined in an article by Govind Seshadri: Understanding JavaServer Pages Model 2 architecture. It can be argued that Model2 does not necessitate a fully-realized MVC pattern, but most modern implementations (such as Ruby on Rails) do formalize it in that way.
Figure 3: Rails Model2 Model-View-Controller
The common wisdom is that in Model2-like frameworks such as Ruby on Rails one should have "fat models and thin controllers". This is not the case for every application, but in practice it is generally what this author has seen. In the traditional MVC Controllers tend to be heavier (i.e. more business logic) due to their need to listen and react to input from the view so this decision seems to add up.
Given the stateless nature of HTTP the Model2 View is truly ephemeral: no state maintained in the view itself between requests.In most server-side frameworks any application state is stored via Session Cookies. This makes the decision of a one-way communication between the Controller and View very logical, but also unsuitable for any front-end development.
MVP and MVVM
Both the Model-View-Presenter and Model-View-ViewModel patterns are similar to the traditional MVC pattern with a few key differences:
- View does not have a direct reference to the Model
- Presenter (or ViewModel) has a reference to the view and updates it based on changes to the model
The MVP pattern is discussed at length by Martin Fowler (here, and here) and is generally discussed in the context of two implementations:
- Passive View: Design the view to be as naive as possible with the all but the absolute necessary presentation and business logic contained in the Presenter.
- Supervising Controller: Used in systems in which the declarative view can be expanded to encompass simple logic. In this incantation the Presenter should only take over when the declarative logic in the view cannot meet the system requirements.
Figure 4: Model-View-Presenter
Figure 5: Model-View-ViewModel
MVP and MVVM are almost indistinguishable with one key exception: MVVM assumes that changes in the ViewModel will be reflected in the view by a robust data-binding engine. Niraj Bhatt outlined the difference eloquently in his MVC vs. MVP vs. MVVM article: "For instance if View had a property IsChecked and Presenter was setting it in classic MVP, in MVVM ViewModel will have that IsChecked Property which View will sync up with."
The positive end of the MVP and MVVM is that the Presenter (or ViewModel) is easier to unit-test because the state of the View is by definition contained in methods invoked by (MVP) or properties set on (MVVM) by the Presenter or ViewModel respectively.
For front-end development either of these patterns are perfectly acceptable choices. A routing layer can pass control to the appropriate Presenter (or ViewModel) which in-turn can update and respond to a persistent view in the browser. With some massaging either could also be re-worked for use on the server for one reason: there is no connection between the model and the view. This allows for an ephemeral view which is rendered by a given Presenter (or ViewModel). As we will see later on, this modified pattern can be truly isomorphic.
Modern Javascript Implementations
The design patterns presented above have many modern implementations today:
These frameworks are generally used for creating Single-Page Applications (although more traditional AJAX is also possible). User Interaction within a single-page application comes in two distinct flavors:
- OnHashChanged or pushState events: Occur when the URL in the browser changes. For example: navigating tohttp://myapp.local/#/some-page
- DOM events: Occur when a user makes a specific interaction with the current DOM. For example: clicking on an anchor tag.
Lets consider some of the patterns and architectures used. If you're interested in further reading check out Peter Michaux's article on MVC Architecture for Javascript Applications.
Backbone
Backbone.js is one of the most popular client-side development frameworks available today. At it's core it is an implementation of the traditional Model-View-Controller pattern. When we examine it in more detail, however, we can see that there are some deviations from the traditional MVC pattern explored earlier.
Figure 6: Backbone Model-View-Controller
In the diagram above we have separated the control-flow represented by OnHashChanged and DOM events to illustrate the separate entry points offered by Backbone. By illustrating this nuance it is clear that Backbone has one important difference from traditional MVC: the View manipulates the Model. When we examine the Backbone Todo Sample, we can see this is clearly the accepted common wisdom:
window.AppView = Backbone.View.extend({
// ....
//
// When a TodoView is instantiated it is passed an
// instance of the Todo model.
//
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
}
// ....
});
window.TodoView = Backbone.View.extend({
// ....
//
// Here the TodoView updates the state of the Todo model.
// This breaks from the traditional MVC in which the view
// only listens for changes on the Model.
//
toggleDone: function() {
this.model.toggle();
}
// ....
});
The decision to break from the traditional MVC pattern gives large Backbone applications all similar feel: thin controllers and models combined with heavy views. These business logic-heavy views are essentially Presenters when looked at objectively. In a large Backbone codebase you should expect a large number of views composited together via a DOM framework like jQuery or zepto.
There is nothing wrong with breaking from the traditional MVC pattern; in the context of front-end development having a reference to the Model from the View removes a lot of bookkeeping logic from the application. It does, however, make it a pattern that is not isomorphic.
Batman
Batman.js is a new Javascript framework that was discussed at JSConf 2011. Although the entities within Batman are Model, View, and Controller. The presence of a strong data binding engine and pure HTML views suggests that it is actually an implementation of the Model-View-ViewModel.
Figure 7: Batman Model-View-ViewModel
Having not worked with Batman much it is difficult to say with confidence what a large Batman codebase looks like. That said: the emphasis on the binding engine and thin views suggest that business logic will end up spread between the Controllers and Models within a given application.
As with Backbone, Batman breaks from the traditional Model-View-ViewModel pattern in that the Model communicates directly with the View and the ViewModel (i.e. Controller) does not manipulate the View directly. In addition, because of the references between the Model and the View it is not easily reusable as a server-side pattern. It is, however, more easily massaged into a server-side pattern if a Composite or Adapter pattern is implemented in the Model layer to render a static view and respond to real-time requests.
Real-time implications
Among all of the developer buzz one topic continues to be at the forefront: realtime web applications. Nodejitsu is an official sponsor of the upcoming Keeping it Realtime conference in Portland; check it out! So how do some of the patterns we've examined stack up for realtime support (such as WebSockets)?
- Model-View-Controller (Yes): Model(s) can listen for real-time events and update the view appropriately.
- Model2 (No): The Ephemeral View concept is baked into the Model2 pattern. That is: Controller(s) do not listen for events from the Model.
- Model-View-Presenter (Yes): Model(s) can listen for real-time events, which will propagate to the Presenter(s) who will update the view appropriately.
- Model-View-ViewModel (Yes): Model(s) can listen for real-time events, which will propagate to the ViewModel(s) who will update the view appropriately.
The acceptability of MVC, MVP, and MVVM patterns make Backbone.js or Batman.js workable realtime frameworks for front-end developments. On the server-side this fact no longer holds true because as we have shown: traditional MVC, MVP, and MVVM patterns will not work for static views due to the tight coupling between the View and the Model.
Enter the Resource-View-Presenter pattern.
Introducing Resource-View-Presenter
As we have shown: the MVC, MVP, and MVVM patterns will not work on both the client and the server. At the crux of Resource-View-Presenter is the realization that no pattern will work flawlessly both on the client and the server without some modification. As was suggested when examining the MVP and MVVM patterns, it is possible to make these patterns truly isomorphic due to their decoupled Model and View layers.
The main decisions which Resource-View-Presenter are:
- Decouple the Model and View
- Recognize and plan for the differences between the Client and Server.
- Expect a thin View, and a heavy Presenter and Resource.
- Prefer business logic in the Resource over the Presenter.
- Allow for both ephemeral (i.e. static server-side views) and persistent Views (i.e. the DOM).
- Prefer a Presenter over a ViewModel to preserve the purity of the Markup (i.e. HTML).
- Assume a persistent Presenter and Model.
Although these decision may seem as though they are random, each has a specific purpose:
- By decoupling the Model and the View we allow for both ephemeral and persistent Views
- A thin View is consistent with modern, logic-less templating engines such as weld and mustache.
- Having a Presenter instead of a ViewModel is consistent with designer-friendly templating such as weld.
- Assuming a persistent Presenter and Model both on the Client and Server enables encapsulation of real-time functionality in Presenters on both sides.
Lets dig deeper. On the Client-side Resource-View-Presenter resembles a traditional MVP pattern. The choice to rename the Model to Resource is heavily influenced by our assumption to prefer business logic in the Resource over the Presenter. This makes the Resource in RVP more similar to the heavy Models found in Model2, not traditional MVP the nomenclature denotes that. When implementing RVP the litmus test for what logic belongs in the Presenter is two fold: any Presentation-specific logic which is too heavy for a "thin" View or any business logic which utilizes global application state.
As with Backbone and Batman, Client-side RVP implementations should support both OnHashChange / pushState routing and DOM events.
Figure 8: Client-side Resource-View-Presenter
On the Server-side Resource-View-Presenter is almost identical with one notable exception: the View is ephemeral and does not pass calls or have a reference to the Presenter. In fact, when using RVP for JSON-based web-services the view is practically non-existent; it is just a call to
JSON.stringify(). Figure 9: Server-side Resource-View-Presenter
At first glance, Server-side RVP may seem similar to Model2 MVC, but the persistent-nature of both the Presenter and Model to support real-time events makes this similarity is only skin deep. RVP does this by listening for events and changes on the model which may be backed by a real-time data source such as Redis PubSub or CouchDB changes.
The real-time support is of particular note because it enables the application developer to focus on the business logic, not the underlying network transport. This may seem trivial, but examine the pattern (or lack there-of) offered by Express and Socket.io(the most popular node.js Framework and Realtime IO libraries respectively) and it becomes clear that it is not.
Figure 10: Express and Socket.io
This is not meant to be an attack on Express or Socket.io; they both are explicit in what they offer and they both do it exceptionally well. A higher-level design pattern is simply not a concern.
Conclusion
Writing large applications is hard. Encapsulating and reusing components of those applications across the Client and the Server is even harder. After considering this analysis it is hopefully clear how utilizing the Resource-View-Presenter pattern within your own applications will help to make this easier.
Подписаться на:
Сообщения (Atom)