Entenda a nova especificação de classes do JavaScript Harmony ES6

Há tempos a criação de classes e objetos em JavaScript tem sido complexa e difícil de entender, isso por conta de não existir uma linha clara dividindo classes de funções e objetos e, com isso, existirem diversas maneiras de se criar estes bem como diversas formas de utilizar praticas de P.O.O. como a herança.

Por estes motivos, o ECMAScript6 (também conhecido como Harmony, ES.next ou ES6) prevê uma nova especificação chamada maximally_minimal_classes, que tem como objetivo uniformizar e simplificar a definição de classes do modo mais minimalista possível.

Atualmente, o modo mais usado para se definir algo parecido com uma classe em JavaScript é utilizando uma função construtora e adicionando funções ao seu protótipo. Confira abaixo como ficaria a representação de uma Pessoa deste modo:

var Pessoa = function(nome, email) {
     this.nome =  nome;
  
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }
};
 
Pessoa.prototype.fala = function(){
     console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
};

Note que a sintaxe não é exatamente intuitiva, deste modo você precisaria saber o que é um prototype e que, em JavaScript, uma classe é uma função no final das contas, o que tende a confundir bastante a cabeça de desenvolvedores acostumados com P.O.O.

Agora vamos ver como ficaria uma Pessoa utilizando a nova especificação:

class Pessoa{
    constructor(nome, email){
        this.nome =  nome;
  
         // verifica se o e-mail foi preenchido
         if (email) {
             this.email = email;    
         }
    }

    fala(){
        console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
    }
}
var leo = new Pessoa("Leonardo", "leonardo.wolter@caelum.com.br");
leo.fala(); // Olá, meu nome é Leonardo e meu email é leonardo.wolter@caelum.com.br 

Caso você já programe em uma linguagem orientada a objetos como o Java, essa sintaxe provavelmente será muito mais intuitiva que a anterior.

Um detalhe interessante dessa abordagem é que, apesar de a função fala ser declarada dentro da classe Pessoa, ela será adicionada no prototype da Pessoa, o que pode ser visivelmente mais performático do que adicioná-la diretamente na função.

Apesar de ser muito parecido com Java, essa especificação não adicionará modificadores de acesso, o que significa que todos os atributos ou métodos adicionados à classe serão públicos! Como a própria especificação diz: “private is simply achieved via private name objects“.

Além da definição padrão de classes citada acima, também estará disponível uma maneira de criar pseudo-propriedades simplesmente adicionando a palavra get ou set antes do nome da função, assim como você pode fazer em objetos literais. Exemplo:

class Pessoa {
    constructor(nome, email) {
         this.nome =  nome;
         this.comidas = [];
         // verifica se o e-mail foi preenchido
         if (email) {
             this.email = email;    
         }
    }

    fala() {
        console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
    }

    get primeiroNome() {
        return this.nome.split(" ")[0];
    }

    set gostaDe(comida) {
        this.comidas.push(comida);
    }
}

var leo = new Pessoa("Leonardo Wolter", "leonardo.wolter@caelum.com.br");
leo.gostaDe = "bolo";
console.log(leo.comidas); // ["bolo"]
console.log(leo.primeiroNome); // Leonardo

Agora conseguimos criar uma classe com uma sintaxe melhor, legal, mas e as heranças, como ficam?

Atualmente, o modo mais utilizado de se implementar herança é utilizando o Prototype-Chainning. Imagine que temos uma classe PessoaFisica que herda Pessoa. O código seria parecido com este:


var PessoaFisica = function(nome, email, cpf){
    Pessoa.call(this, nome, email);
    this.cpf = cpf;
};
 
PessoaFisica.prototype = new Pessoa();
PessoaFisica.prototype.constructor = PessoaFisica;
PessoaFisica.prototype.dizCpf = function(){
    console.log(this.cpf);
};

Resumindo você precisaria criar uma função construtora com o conteudo adequado, setar o prototype de PessoaFisica com uma instancia de Pessoa, consertar a propriedade construtor e, por ultimo mas nao menos importante, adicionar as funções específicas sua PessoaFisica.

Confira agora como implementaríamos a mesma herança utilizando a nova especificação:


class PessoaFisica extends Pessoa{

    constructor(nome, email, cpf){
        super(nome, email);
        this.cpf = cpf;
    }
  
    dizCpf(){
        console.log(this.cpf);
    }
}

var leo = new PessoaFisica("Leonardo", "leonardo.wolter@caelum.com.br", "meucpf" );
leo.gostaDe = "bolo";
console.log(leo.comidas); // ["bolo"]
console.log(leo.idade); // 63
leo.dizCpf(); // "meucpf"
leo.fala(); // Olá, meu nome é Leonardo e meu email é leonardo.wolter@caelum.com.br 

Temos como resultado um código mais expressivo e enxuto, apenas utilizamos a palavra-chave extends para dizer que a PessoaFisica herda Pessoa e substituimos a estratégia de Constructor Stealing pelo código super(nome, email)

Legal, e quando eu vou poder usar isso?
Atualmente, apenas o traceur-compiler possui suporte a essa nova sintaxe, uma demonstração do código desse post funcionando pode ser vista aqui.

Caso esteja interessado nas outras funcionalidades que serão adicionadas ao Harmony, você pode conferir a tabela de compatibilidade com os browsers, compilers e node aqui.

17 Comentários

  1. Victor 04/06/2014 at 14:15 #

    Ótimo post! Deu uma boa esclarecida.
    Agora Leonardo, gostaria de saber sobre declaração de get e set antes do método. Faz parte do nome do método ou é uma especificação antes de declarar etc?

    get primeiroNome() {
    return this.nome.split(” “)[0];
    }

    set gostaDe(comida) {
    this.comidas.push(comida);
    }

  2. Leonardo Wolter 04/06/2014 at 14:34 #

    Olá, Victor, que bom que gostou!

    O get e o set são palavras-chave, não fazem parte do nome do método.
    Assim como você consegue fazer atualmente com objetos literais, quando você adiciona o get/set antes da propriedade, ela vira uma pseudo-propriedade.

    Básicamente é como se você rodasse uma função a cada vez que essa propriedade fosse acessada ou modificada.

    Você pode encontrar mais informações aqui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/get

    Abraço

  3. Flávio Almeida 04/06/2014 at 17:48 #

    Olá Leo! Excelente post! Eu já usava o Google traceur-compiler, inclusive automatizando o processo de compilação pelo Grunt. O mais bacana do seu post além de esclarecer as novidades é mostrar que já é possível usar estes recursos sem ter que esperar todos os browsers suportarem.

    Abraço

  4. Leonardo Wolter 04/06/2014 at 17:58 #

    Oi Flávio, muito obrigado!

    Você consegue postar o seu grunt aqui?

    Abraço!

  5. Raul 04/06/2014 at 18:04 #

    Leonardo, obrigado pela boa discussão levantada.

    Com sua experiência voltada ao mundo JS, qual sua opinião sobre adoção do mercado quanto a linguagem do Google, Dart?

    Apenas por curiosidade, segue exemplo de criação de classes:
    https://www.dartlang.org/codelabs/darrrt/#step-four

    E eu creio que em relação a Browsers suportados, ele já está mais avançado que o demorado ES6:
    https://www.dartlang.org/support/faq.html#browsers-and-compiling-to-javascript

    Na sua visão, ele pode ser amplamente usado no futuro, a ponto de no mínimo ser um grande concorrente do JS (ES6)?

    Aproveitando, qual sua opinião particular sobre o Coffescript, você gosta? Acredita que vale a pena usar?
    http://coffeescript.org/#classes

  6. Thiago Ponte 04/06/2014 at 18:28 #

    Muito boa explicação sobre o Harmony!
    Vai facilitar muito para quem está acostumado com o Java.

  7. Leonardo Wolter 04/06/2014 at 18:53 #

    Olá, Raul.

    Vamos lá, isso é uma discussão que com certeza vai gerar discórdia haha.
    (ja peço desculpas pelo texto grande)

    Sobre o Dart:

    Eu gosto bastante da ideia do Dart, ele parece resolver bastantes problemas que existem no Javascript hoje, porém ele tem um problema grave: ele não é Javascript.

    O que eu quero dizer com isso? Bom, o Javascript ja está em diversas plataformas: dispositivos, navegadores, node.js, temos interpretadores até mesmo de nossos desktops, dentro da JVM, etc. Apesar de eu estar falando do ES5, ele está lá, e essas plataformas ja estão começando, lentamente. a suportar o Harmony.

    E quanto ao Dart? Ele é uma linguagem nova que, atualmente(na minha ignorancia), não é suportada nativamente em produção por nenhuma dessas plataformas(apenas Dartium, eles querem suportar no Chrome, mas não temos nenhum prazo). Isso faz com que, para que você usar Dart atualmente, você precise compilá-lo para Javascript, sem exceção.
    Mas desse modo nós ja podemos usar o Harmony(ou grande parte dele), certo? O traceur está ai pra isso! A diferença é que, assim que o Harmony for suportado nativamente em todos os browsers, não teremos mais que nos preocupar com o js bizarro que o traceur gera, simplesmente o removeremos do projeto.

    De novo, não é que eu não goste do Dart como linguagem, ela tem features mega interessantes que eu adoraria usar, só penso que ela não vai tirar o lugar do Javascript, ainda mais agora que o Harmony está indo para o lado de simplificar(?) a linguagem assim como o Dart. Ou, pelo menos, não tão cedo.

    Sobre o Coffescript:

    Sinceramente, eu não tenho experiência o bastante com ele para dar minha opinião de se você deve usar ou não.

    A impressão que eu tenho é que a maioria dos desenvolvedores que não gostam dele usam um argumento do tipo: “por que eu usaria uma linguagem que será compilada pra javascript se eu preciso saber javascript e pensar no que será gerado pra usá-la?”.
    Claro que outros desenvolvedores gostam, mas isso é só o que algumas pessoas que eu tenho à minha volta pensam.

    Como eu disse antes, não tenho uma opinião formada sobre Coffescript, o que posso dizer é que a sintaxe não me agrada muito(assim como jade também não, assim como não gosto da do haml ou a do python), talvez eu só seja chato mesmo haha

    Abraço

  8. Leonardo Wolter 04/06/2014 at 20:27 #

    Muito obrigado, Thiago!

  9. Raul 04/06/2014 at 20:44 #

    Leonardo, wow… imaginava que a resposta seria superficial devido ao espaço, mas ainda bem que me enganei, acabou sendo a resposta completa e concisa que eu queria – e não achei grande não, pode deixar =)

    Faz completo sentido, para que usar o Dart compilando para Javascript, se pode usar o Harmony (+ o traceur que você citou), compilando para Javascript normal.

    O Coffeescript eu já usei bastante, dexei um grunt compilando para js e fazendo “live-reload” e gostei da experiência, justamente pela sintaxe e por digitar menos – pois ao contrário de você, meu caro, eu amo Python hahah… (ok brincadeira, sei que você não pretendeu iniciar uma guerrinha de linguagens, e nem eu ^^)

    Eu sou apenas um curioso e entusiasta de novas tecnologias, porém em geral estou preferindo optar pelo bom e velho Javascript padrão, assim como me parece que você também. Na realidade AINDA estou aprendendo Javascript [de verdade rs], porém estou gostando bastante, e aguardarei ansiosamente a evolução do Harmony.

  10. Leonardo Wolter 05/06/2014 at 14:17 #

    Legal, Raul, vou pensar em usar Coffespcript pra valer! Sempre bom aprender coisas novas!

    Abraço!

  11. Raul 05/06/2014 at 15:22 #

    Bacana! Espero que continue escrevendo ótimos artigos também.
    Abraços!

  12. Flávio Almeida 05/06/2014 at 18:46 #

    Oi Leo, segue o meu repositório com o script de grunt com o traceur-compiler configurado e outras features:

    https://github.com/flaviohenriquealmeida/grunt-features

  13. Leonardo Wolter 10/06/2014 at 11:45 #

    Muito obrigado pela contribuição Raul e Flávio!

  14. Joao 09/01/2015 at 11:01 #

    Lembra muito Java

  15. Felipe Rodrigues 19/03/2015 at 11:38 #

    Nossa muito bom mesmo cara! Não havia encontrado nenhum artigo em português tão objetivo e esclarecedor. Parabéns!

  16. Rafael 26/03/2015 at 23:41 #

    Leonardo

    Parabéns pelo post!

    Essa “adequação” do javascript a características que conhecemos do Java não te parece andar pra trás? Não vemos escrever mais código pra fazer o mesmo?

Deixe uma resposta