Front End

2 set, 2014

Usando Mixins em JavaScript

Publicidade

Mais uma vez vi uma dúvida, do mesmo usuário, no Stack Overflow em Português, com a seguinte pergunta:

O que são “mixins” [em JavaScript]?

Vejo em alguns códigos JavaScript e gostaria de saber o que é; exemplos reais de uso.

Por ter achado interessantíssima a pergunta e algo realmente relevante, decidi então elaborar uma resposta. Pois bem, aos interessados, ela segue abaixo.

Mixin em JavaScript é um uma classe pensada com foco no DRY. Ela dá a habilidade do desenvolvedor pegar atalhos para resolver problemas, quase como os famosos “helpers”, com a diferença de que um mixin pode ou não referenciar ou ser referenciado por um módulo1 sem a necessidade direta de extensão ou herança.

Um exemplo prático e útil

Botões (<button>) e âncoras (<a>) são conceitualmente componentes do DOM, o que significa que quando/se falarmos de “componentes”, estamos indo na síntese do negócio; indo na camada mais genérica dos elementos que a nossa linguagem favorita de marcação trabalha.

Num todo, componentes são materiais renderizáveis; são itens que quando marcados no seu HTML têm a capacidade de se invocar para a visualização do usuário e materialização da interface.

Portanto, exemplifiquei mixin nesse cenário, através de JavaScript. Usando botões e âncoras ainda, desenvolvi os seguintes objetos:

var Button = {  
    design: {
        colors: {
            background: 'blue',
            text: 'yellow'
        },
        borderRadius: 3,
        padding: 5,
    },
    shape: function () {
        return '<button style="background-color:' + this.design.colors.background + ';'
            + 'border-radius:' + this.design.borderRadius + 'px;'
            + 'padding:' + this.design.padding + 'px;'
            + 'color:' + this.design.colors.text + ';'
            + 'border: none;">'
            + this.getContent()
            + '</button>';
    }
};

e

var Anchor =  {  
    design: {
        color: 'green',
        underline: true
    },
    destination: 'google.com',
    shape: function () {
        return '<a style="color:' + this.design.color + ';'
        + 'text-decoration:' + (this.design.underline ? 'underline' : 'none') + ';" '
        + 'href="http://' + this.destination + '">'
        + this.getContent()
        + '</a>';
    }
};

Eles possuem características em comum, certo? São exemplos design e shape. Entretanto, se você prestar atenção, são diferenciados pela sua implementação; pela suas características particulares. Em outras palavras, cada um desses “componentes” possui a sua própria personalidade, como a marcação em si, que no caso do botão é <button> e no caso da âncora é <a>.

Relembrando…

Lembra que há pouco falei sobre “renderização”? Pois é, componentes como esses devem ter a capacidade de se manifestarem no DOM. Então, faríamos para ensinar os dois objetos (Button e Anchor) a se projetarem sem cair na repetição de criar métodos iguais em seus escopos? Mixins!

Veja este terceiro objeto que desenhei:

var Component = {  
    render: function (platform) {
        $(platform).html(this.shape());
    },
    append: function (platform) {
        $(platform).append(this.shape());
    },
    setContent: function (content) {
        this.content = content;
        return this;
    },
    getContent: function () {
        return this.content;
    }
};

Ele, por sua vez, possui recursos que podem tornar os nossos botões e âncoras úteis – e podemos usar livremente clonando o seu escopo entre os nossos dois objetos.

Para a clonagem, por sua vez, utilizei o método extend() do Underscore.js:

_.extend(Anchor, Component);  
_.extend(Button, Component);

O resultado ficou simples. Para renderizarmos os nossos componentes, faríamos então o seguinte:

Button  
    .setContent('Register')
    .append('body');

Anchor  
    .setContent('And here if you already have an account!')
    .append('body');

Se juntarmos tudo, o resultado será este:

var Component = {  
    render: function (platform) {
        $(platform).html(this.shape());
    },
    append: function (platform) {
        $(platform).append(this.shape());
    },
    setContent: function (content) {
        this.content = content;
        return this;
    },
    getContent: function () {
        return this.content;
    }
};

var Button = {  
    design: {
        colors: {
            background: 'blue',
            text: 'yellow'
        },
        borderRadius: 3,
        padding: 5,
    },
    shape: function () {
        return '<button style="background-color:' + this.design.colors.background + ';'
        + 'border-radius:' + this.design.borderRadius + 'px;'
        + 'padding:' + this.design.padding + 'px;'
        + 'color:' + this.design.colors.text + ';'
        + 'border: none;">'
        + this.getContent()
        + '</button>';
    }
};

var Anchor =  {  
    design: {
        color: 'green',
        underline: true
    },
    destination: 'google.com',
    shape: function () {
        return '<a style="color:' + this.design.color + ';'
        + 'text-decoration:' + (this.design.underline ? 'underline' : 'none') + ';" '
        + 'href="http://' + this.destination + '">'
        + this.getContent()
        + '</a>';
    }
};

_.extend(Anchor, Component);  
_.extend(Button, Component);

Button  
.setContent('Register')
.append('body');

Anchor  
.setContent('And here if you already have an account!')
.append('body');

Para brincar e praticar, aqui está o jsFiddle.