:::: MENU ::::

ECMAScript 6: Desmistificando as Classes

Desde seu anúncio, as classes no javascript tem gerado muita discórdia. Vários desenvolvedores ativos da comunidade tomaram posições divergentes sobre o assunto, um exemplo é o artigo Two Pillars of javascript escrito pelo Eric Elliot, onde ele fala sobre as vantagens da composição sobre a herança de classes e como as classes no javascript afetam esse comportamento. Um outro artigo que recomendo é o How to fix es6 class keyword, também do Eric Elliot.

Ambos artigos citados acima apontam o lado ruim das classes, no meu ponto de vista eles mostram pouco da real responsabilidade das classes no ES6. Classes não são monstros que querem transformar o javascript, o que elas querem na verdade é simplificar os casos de uso comuns do dia a dia. Eu, particularmente gosto bastante do artigo Does javascript need classes? do Nicholas Zakas, bem mais próximo da realidade.

Entendendo as Classes no ECMAScript 6

Antes do ES6 o javascript não possuía classes nem maneira alguma para definir uma herança. Para contornar essa situação foram criadas diversas bibliotecas de suporte como, _underscorejs e jQuery, que permitiam fazer composição de uma forma mais parecida com herança.

Definindo um comportamento similar a classes antes do ES6

O trecho de código a seguir é muito comum:

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

Person.prototype.sayName = function() {
  console.log(this.name);
};

let person = new Person("Waldemar");
person.sayName();   //"Waldemar"

console.log(person instanceof Person);  // true
console.log(person instanceof Object);  // true

Neste exemplo vemos que primeiramente foi definida a função construtura Person e depois foram atribuídos os métodos para o prototype dessa função. Em seguida uma nova instância de Person é criada e ela possui o método sayName.

Este é o comportamento utilizado por várias das bibliotecas; encapsular a lógica para que fique mais parecido com uma declaração de classe comum entre as linguagens. O papel das classes no ECMAScript 6 é facilitar esse tipo de comportamento.

Declarando uma classe no ECMAScript 6

Assim como temos a keyword “function”, agora também teremos a keywordclass”, ela será responsável por definir que o que virá a seguir será tratado pela engine do  javascript como uma classe.

O mesmo comportamento do exemplo anterior usando classe ficaria assim:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}

let person = new Person("Waldemar");
person.sayName();   //"Waldemar"
console.log(person instanceof Person);  // true
console.log(person instanceof Object); // true

console.log(typeof Person);                    // "function"
console.log(typeof Person.prototype.sayName);  // "function"

Class é apenas uma sintaxe, por baixo dos panos o comportamento ainda é similar ao primeiro exemplo. A declaração da classe Person cria uma função que será o construtor (constructor). Por isso o “typeof ” disse que tanto Person quanto o método “sayName” são funçõesEntendendo isso podemos misturar os comportamentos como no exemplo a seguir:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}

Person.prototype.sayWorks = () => {
  console.log("Works");
}

let person = new Person("Waldemar");
person.sayName();   //"Waldemar"
person.sayWorks(); //"Works"

Atribuir métodos diretamente ao prototype da classe como no primeiro exemplo vai funcionar normalmente

O que a sintaxe class traz de vantagens?

Quais seriam as vantagens de usar classes?

  • A declaração de “class” assim como “let” e “const” não fazem hoisting como “function” e “var”.
  • O escopo interno das classes roda sempre em strict mode.
  • Métodos dentro de classes não possuem construtor o que impossibilita a chamada com new.
  • Não é possível chamar o construtor de uma classe sem new.
  • Não é possível sobrescrever o nome da classe com um método interno.

Para adicionar essas características no primeiro exemplo (sem o uso da sintaxe class) seria necessário escrever isso:

let Person = (function() {
  "use strict";
  const Person = function(name) {
    if (typeof new.target === "undefined") {
      throw new Error("Constructor must be called with new.");
    }

    this.name = name;
  }

  Object.defineProperty(Person.prototype, "sayName", {
    value: function() {
      if (typeof new.target !== "undefined") {
        throw new Error("Method cannot be called with new.");
      }
      console.log(this.name);
    },
    enumerable: false,
    writable: true,
    configurable: true
  });

  return Person;
}());

Assim é possível notar que classes não são coisas de outro mundo, e que talvez o equivoco seja o nome dado a elas, talvez classes em javascript não sejam bem classes e sim apenas um tipo que simplifica a construção de objetos.

Classes como expressões anônimas

Assim como as funções, as classes também podem ser declaradas como expressões anônimas:

let person = new class {

  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }

}("Waldemar");

person.sayName(); //Waldemar

Propriedades de acesso

Getters e setters nem sempre são explicitos na maioria das linguagens mas o javascript resolveu implementar uma forma nativa para criar propriedades de acesso usando “get” e “set” como no exemplo:

class Person {

  constructor(name) {
    this.name = name;
  }

  get fullName() {
    return this.name + "something";
  }

  set fullName(value) {
    this.name = value;
  }
}

var descriptor = Object.getOwnPropertyDescriptor(Person.prototype,"fullName");
console.log("get" in descriptor);   // true
console.log("set" in descriptor);   // true
console.log(descriptor.enumerable); // false

No exemplo acima “fullName” se torna uma propriedade não enumerada do prototype ou seja, quando ela for acessada quem vai ser invocado sera o  “getter” e não a propriedade diretamente:

let person = new Person("Waldemar ");
console.log(person.fullName); //Waldemar something

Métodos estáticos em classes

Adicionar funções diretamente ao prototype de um objeto para simular um método estático é sempre foi uma prática comum desenvolvimento em javascript, por exemplo:

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

OldPerson.printAge = function(age) {
  console.log(age);
};

OldPerson.printAge(15);

Neste exemplo a função “OldPerson” funciona como o construtor e logo depois a função “printAge” é adicionada diretamente ao prototype ou seja ela não depende da instância.

Transformando esse mesmo código para usar classes o comportamento seria o seguinte:

class Person {
  constructor(name) {
    this.name = name;
  }

  static printAge(age) {
    console.log(age); //15
  }
}

Person.printAge(15);

O ES6 simplificou esse comportamento adicionando os métodos estáticos com a keyword “static”. Fora no construtor, os métodos estáticos podem ser usados em qualquer lugar.

Não vou abordar herança nesse post pois é um conteúdo relativamente grande e vou escrever um post somente para isso no futuro.

Grande abraço a todos!


  • Edily Cesar Medule

    Muito bom, valeu!

  • Marcos

    Não sabia ainda do get, set e static. Muito obrigado!

    • Waldemar Neto

      Que bom que te ajudou cara, logo farei a parte 2 falando de mais algumas coisas. Grande abraço

  • Excelente post, Waldemar. Me parece que o ranso com as classes do ES6 também vem do fato de muita gente ter esperado que o comitê desse uma guinada funcional na coisa. (o que eu ainda espero que aconteça nas próximas versões)

    • Waldemar Neto

      Valeu!
      Concordo contigo, vamos ver como vão ser as próximas versões, pois o ES7 ja está definido e não trouxe muitas coisas novas 🙁

  • Rodrigo Morais

    Muito bom post Waldemar.

    Não gosto muito da ideia de classes, me parece pouco natural para o JS mesmo como syntax sugar.
    Prefiro a ideia de imutabilidade e apenas um local onde os dados existam sem a mistura de funcionalidades e estrutura de dados juntos. Por outro lado esta ideia é muito nova ainda e com a popularização do JS e a importância que o mesmo recebeu as classes podem ajudar com código de maior qualidade para os desenvolvedores que não dominam FP.

    Estou ansioso pela continuação.

    Parabéns.

    • Waldemar Neto

      Valeu cara! Eu também acho essa sintaxe bem problemática pois antes todos os desenvolvedores que começavam a trabalhar com javascript tinham que entender a composição e esquecer o paradigma de herança e OO convencional. Agora muita gente vai pensar que classes são “classes” e vão tentar aplicar o mesmo tipo de design e arquitetura de outras linguagens que possuem classes e deixarão de aproveitar a grande diferença do js que é a composição.

  • Wow, parabéns pelo post!

    Eu já sabia do get e do set, mas o static ainda não. <3

    • Waldemar Neto

      Opa, que bom que te ajudou 🙂
      Logo sairá um post sobre as heranças… logo kkkk

  • Tiago Celestino

    Explicação resumida de muitas dúvidas que tinha. Parabéns pelo o post. 😉

  • Luciano Lima

    Ótimo post, bem esclarecedor principalmente sobre a parte de getter, setters e static methods.