URL: https://www.progressiverobot.com/understanding-classes-in-javascript-pt/

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

classes illustration for: 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.

				
					
[environment second]

// Initializing a function with a function expression

const x = function() {}

				
			
				
					
[environment fourth]

// 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.

				
					
[environment second]

Object.getPrototypeOf(x);

				
			
				
					
[environment second]

[secondary_label Output]

ƒ () { [native code] }

				
			

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

				
					
[environment fourth]

Object.getPrototypeOf(y);

				
			
				
					
[environment fourth]

[secondary_label 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.

				
					
[environment second]

const x = function() {}



// Initialize a constructor from a function

const constructorFromFunction = new x();



console.log(constructorFromFunction);

				
			
				
					
[environment second]

[secondary_label Output]

x {}

constructor: ƒ ()

				
			

Isso também se aplica às classes.

				
					
[environment fourth]

const y = class {}



// Initialize a constructor from a class

const constructorFromClass = new y();



console.log(constructorFromClass);

				
			
				
					
[secondary_label Output]

[environment fourth]

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.

				
					
[label constructor.js]

[environment second]

// 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.

				
					
[label class.js]

[environment fourth]

// 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.

				
					
[label constructor.js]

[environment second]

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.

				
					
[label class.js]

[environment fourth]

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.

				
					
[environment fourth]

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.

				
					
[secondary_label Output]

[environment fourth]

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.

				
					
[label constructor.js]

[environment second]

// 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.

				
					
[environment second]

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

				
			

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

				
					
[secondary_label Output]

[environment second]

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.

				
					
[label class.js]

[environment fourth]

// 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.

				
					
[environment fourth]

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

				
			

Vamos imprimir hero2 para o console e visualizar o resultado.

				
					
[environment fourth]

[secondary_label 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.

				
					
[environment second]

[label 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;

}

				
			
				
					
[label class.js]

[environment fourth]

// 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.