ECMAScript 6: desmistificando as classes
Antes do ECMAScript 6, o JavaScript não possuía classes, nem maneira alguma para definir uma herança.
Desde seu anúncio, as classes no JavaScript têm 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, no qual ele fala sobre as vantagens da composição sobre a herança de classes e como as classes no JavaScript afetam esse comportamento. Outro artigo que recomendo é o How to fix es6 class keyword, também do Eric Elliot.
Ambos os 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
Nesse 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 keyword “class”, que será responsável por definir que o que virá a seguir e será tratada 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 o Person quanto o método “sayName” são funções. Entendendo 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 ao primeiro exemplo (sem o uso da sintaxe class), seria necessário escrever isto:
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 equívoco 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 explícitos 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 será 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);
Nesse 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 neste artigo, pois é um conteúdo relativamente grande e vou escrever um texto somente para isso no futuro.
Grande abraço a todos!








