// Tutorial //

Entendendo classes no JavaScript

Published on December 12, 2019
Default avatar
By Tania Rascia
Developer and author at DigitalOcean.
Português
Entendendo classes no JavaScript

Introdução

O JavaScript é uma linguagem baseada em protótipo, e cada objeto no JavaScript tem uma propriedade interna escondida chamada [[Prototype]], que pode ser usada para estender as propriedades e métodos de objetos. Você pode ler mais sobre protótipos no nosso tutorial Entendendo protótipos e herança no JavaScript.

Até recentemente, os desenvolvedores criativos usavam funções de construção para imitar um padrão de design orientado a objeto no JavaScript. A especificação de linguagem ECMAScript 2015, frequentemente chamada de ES6, introduziu classes na linguagem JavaScript. As classes no JavaScript não oferecem, de fato, funcionalidades adicionais e são muitas vezes descritas como provedoras de “açúcar sintático” em relação a protótipos e herança, sendo que estes oferecem uma sintaxe mais limpa e mais elegante. Uma vez que outras linguagens de programação usam classes, a sintaxe de classe no JavaScript torna a coisa mais simples para que desenvolvedores consigam transitar entre linguagens.

Classes são funções

Uma classe do JavaScript é um tipo de função. As classes são declaradas com a palavra-chave class. Vamos usar a sintaxe de expressão de função para inicializar uma função e a sintaxe de expressão de classe para inicializar uma classe.

// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}

Podemos acessar o [[Prototype]] de um objeto usando o método Object.getPrototypeOf(). Vamos usar isso para testar a função vazia que criamos.

Object.getPrototypeOf(x);
Output
ƒ () { [native code] }

Também podemos usar esse método na classe que acabamos de criar.

Object.getPrototypeOf(y);
Output
ƒ () { [native code] }

Ambos os código declarados com function e class retornam uma função [[Prototype]]. Com protótipos, qualquer função pode se tornar uma instância de construção usando a palavra-chave new.

const x = function() {}

// Initialize a constructor from a function
const constructorFromFunction = new x();

console.log(constructorFromFunction);
Output
x {} constructor: ƒ ()

Isso também se aplica às classes.

const y = class {}

// Initialize a constructor from a class
const constructorFromClass = new y();

console.log(constructorFromClass);
Output
y {} constructor: class

Estes exemplos de construtores de protótipo estão aparentemente vazios, mas podemos ver que sob a sintaxe, ambos os métodos estão alcançando o mesmo resultado final.

Definindo uma classe

No tutorial de protótipos e herança, criamos um exemplo baseado na criação de personagens em um jogo RPG baseado em texto. Vamos continuar com esse exemplo para atualizar a sintaxe de funções para classes.

Uma função de construção é inicializada com um número de parâmetros que seriam atribuídos como propriedades de this, referindo-se à função em si. A primeira letra do identificador seria maiúscula por convenção.

constructor.js
// Initializing a constructor function
function Hero(name, level) {
	this.name = name;
	this.level = level;
}

Quando traduzimos isso para a sintaxe classe mostrada abaixo, vemos que ela está estruturada de maneira similar.

class.js
// Initializing a class definition
class Hero {
	constructor(name, level) {
		this.name = name;
		this.level = level;
	}
}

Sabemos que uma função de construção é destinada a ser um projeto de objeto pela primeira letra do inicializador ser maiúscula (o que é opcional) e através da familiaridade com a sintaxe. A palavra-chave class comunica de maneira mais simples o objetivo da nossa função.

A única diferença na sintaxe de inicialização é usar a palavra-chave class ao invés de function, e atribuir as propriedades dentro de um método constructor().

Definindo métodos

Uma prática comum com funções de construção é atribuir métodos diretamente ao prototype ao invés da inicialização, como visto no método greet() abaixo.

constructor.js
function Hero(name, level) {
	this.name = name;
	this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
	return `${this.name} says hello.`;
}

Com classes, esta sintaxe é simplificada e o método pode ser adicionado diretamente à classe. Ao usar a forma abreviada da definição do método introduzida como sendo ES6, definir um método é um processo ainda mais conciso.

class.js
class Hero {
	constructor(name, level) {
		this.name = name;
		this.level = level;
	}

	// Adding a method to the constructor
	greet() {
		return `${this.name} says hello.`;
    }
}

Vamos ver essas propriedades e métodos em ação. Criaremos uma nova instância Hero usando a palavra-chave new e atribuiremos alguns valores.

const hero1 = new Hero('Varg', 1);

Se imprimirmos mais informações sobre nosso novo objeto com console.log(hero1), podemos ver mais detalhes sobre o que está acontecendo com a inicialização da classe.

Output
Hero {name: "Varg", level: 1} __proto__: ▶ constructor: class Hero ▶ greet: ƒ greet()

Podemos ver no resultado que as funções constructor() e greet() foram aplicadas ao __proto__, ou [[Prototype]] do hero1, e não diretamente como um método no objeto hero1. Embora isso seja claro ao criar funções de construção, não é óbvio ao criar classes. As classes permitem uma sintaxe mais simples e sucinta, mas sacrifica um pouco de clareza no processo.

Estendendo uma classe

Uma característica vantajosa de funções de construção e classes é que elas podem ser estendidas para novos projetos de objeto baseados no pai. Isso impede a repetição de código para objetos semelhantes, mas precisa de algumas características adicionais ou mais específicas.

Novas funções de construção podem ser criadas a partir do pai usando o método call(). No exemplo abaixo, vamos criar uma classe de personagens mais específica chamada Mage e atribuir as propriedades de Hero a ela usando o call(), assim como adicionar uma propriedade adicional.

constructor.js
// Creating a new constructor from the parent
function Mage(name, level, spell) {
	// Chain constructor with call
	Hero.call(this, name, level);

	this.spell = spell;
}

Neste ponto, podemos criar uma nova instância de Mage usando as mesmas propriedades que o Hero assim como uma nova que adicionamos.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

Ao enviar hero2 para o console, podemos ver que criamos um novo Mage baseado no construtor.

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: ▶ constructor: ƒ Mage(name, level, spell)

Com classes ES6, a palavra-chave super é usada no lugar de call para acessar as funções do pai. Vamos usar extends para nos referir à classe pai.

class.js
// Creating a new class from the parent
class Mage extends Hero {
	constructor(name, level, spell) {
		// Chain constructor with super
		super(name, level);

		// Add a new property
		this.spell = spell;
	}
}

Agora, podemos criar uma nova instância Mage da mesma maneira.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

Vamos imprimir hero2 para o console e visualizar o resultado.

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: Hero ▶ constructor: class Mage

O resultado é quase exatamente o mesmo, exceto que na construção de classes o [[Prototype]] está ligado ao pai, neste caso, Hero.

Abaixo está uma comparação lado a lado do processo inteiro de inicialização, adição de métodos e herança de uma função de construção e uma classe.

constructor.js
function Hero(name, level) {
	this.name = name;
	this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
	return `${this.name} says hello.`;
}

// Creating a new constructor from the parent
function Mage(name, level, spell) {
	// Chain constructor with call
	Hero.call(this, name, level);

	this.spell = spell;
}
class.js
// Initializing a class
class Hero {
	constructor(name, level) {
		this.name = name;
		this.level = level;
	}

	// Adding a method to the constructor
	greet() {
		return `${this.name} says hello.`;
    }
}

// Creating a new class from the parent
class Mage extends Hero {
	constructor(name, level, spell) {
		// Chain constructor with super
		super(name, level);

		// Add a new property
		this.spell = spell;
	}
}

Embora a sintaxe seja bastante diferente, o resultado fundamental é quase idêntico entre ambos os métodos. As classes dão-nos uma maneira mais concisa de criar plantas de objetos e as funções de construção descrevem com maior precisão o que está acontecendo nas entrelinhas.

Conclusão

Neste tutorial, aprendemos sobre as semelhanças e diferenças entre funções de construção e classes ES6 do JavaScript. Ambas classes e funções de construção imitam um modelo de herança orientado a objeto para JavaScript, que é uma linguagem de herança baseada em protótipo.

Compreender a herança prototípica é fundamental para ser um desenvolvedor eficaz do JavaScript. Estar familiarizado com classes é extremamente útil, já que as bibliotecas populares do JavaScript como a React fazem uso frequente da sintaxe class.


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!