Как стать автором
Обновить

Javascript примеси для чайников

Время на прочтение3 мин
Количество просмотров31K
После того, как с классическим (от слова класс) наследованием в JS стало вроде-бы все понятно, я решил разобраться с реализацией другого способа повторного использвоания кода — примесями. Несмотря на довольно непривычное название, способ этот чертовски прост.


1. Примеси — это, собственно, что ?


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

Вот, например, прекрасная примесь:

var Mixin_Babbler =  
{
    say: function () 
    { 
        console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'");
    },
    argue: function() { console.log("You're totally wrong"); }
};

При попытке вызвать метод say просто так, нас ждет облом, потому что ни this.name, ни this.THOUGHTS в нашем объекте, почему-то, просто нет.
На самом деле все правильно, чтобы использовать примесь по назначению нам нужен другой объект, и метод, который копирует все свойства переданных ему объектов-примесей в прототип функции конструктора — обычно такой метод называют extend, или mix, или как-нибудь в этом духе:

function extend(object)
{
    var mixins = Array.prototype.slice.call(arguments, 1);
    for (var i = 0; i < mixins.length; ++i)
    {
        for (var prop in mixins[i])
        {
            if (typeof object.prototype[prop] === "undefined")
            {
                object.prototype[prop] = mixins[i][prop];
            }
        }
    }
}

2. Ну и как это использовать?


Легко и не напрягаясь — допустим, у нас есть парочка примесей:

var Mixin_Babbler =  
{
    say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'"); },
    argue: function() { console.log("You're totally wrong"); }
};

var Mixin_BeverageLover =
{
    drink: function () { console.log("* drinking " + this.FAVORITE_BEVERAGE + " *"); }
};

И, кому-то, возможно, уже знакомая, эволюционная цепочка:

function Man(name)
{
    this.name = name;
}
Man.prototype =
{
    constructor: Man,
     
    THOUGHTS: "I like soccer"
}
extend(Man, Mixin_Babbler);

function Gentleman(name)
{
    this.name = name;
}
Gentleman.prototype =
{
    constructor: Gentleman,

    THOUGHTS: "I like Earl Grey",
    FAVORITE_BEVERAGE: "Tea"
}
extend(Gentleman, Mixin_Babbler, Mixin_BeverageLover);

function Programmer(name)
{
    this.name = name;
}

Programmer.prototype =
{
    constructor: Programmer,

    THOUGHTS: "MVC, MVVM, MVP *___* like it!",
    FAVORITE_BEVERAGE: "Cofee",

    write_good_code: function () { console.log("*writing best code ever*"); this.drink(); }
}
extend(Programmer, Mixin_Babbler, Mixin_BeverageLover);

Каждый «класс» теперь не зависит от другого, а весь повторяющийся функционал реализован с помощью примесей.

Теперь стоит все проверить — вдруг заработает:

var man = new Man("Bob");
var gentleman = new Gentleman("Bill");
var programmer = new Programmer("Benjamin");

man.say();
man.argue();

gentleman.say();
gentleman.drink();

programmer.say();
programmer.write_good_code();

И консоль говорит что таки да, все как надо:

My name is Bob and i think:'I like soccer'
*You're totally wrong*
My name is Bill and i think:'I like Earl Grey'
*drinking Tea*
My name is Benjamin and i think:'MVC, MVVM, MVP like *__* it!'
*writing best code ever*
*drinking Cofee*

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

UPD. Как подсказал один очень хороший человек с ником lalaki, метод extend можно реализовать немножко по-другому:

function extend_2(object)
{
    var mixins = Array.prototype.slice.call(arguments, 1);
    for (var i = 0; i < mixins.length; ++i)
    {
        for (var prop in mixins[i])
        {
            if (typeof object.prototype[prop] === "undefined")
            {
                bindMethod = function (mixin, prop)
                {
                    return function () { mixin[prop].apply(this, arguments) }
                }

                object.prototype[prop] = bindMethod(mixins[i], prop);
            }
        }
    }
}


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

Весь код и примеры лежат себе спокойно вот тут
Теги:
Хабы:
Всего голосов 61: ↑55 и ↓6+49
Комментарии33

Публикации