пятница, 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>

Комментариев нет:

Отправить комментарий