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.
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);
Outputx {}
constructor: ƒ ()
Isso também se aplica às classes.
const y = class {}
// Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
Outputy {}
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.
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.
// 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.
// 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()
.
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.
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 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.
OutputHero {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.
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.
// 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.
OutputMage {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.
// 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.
OutputMage {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.
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;
}
// 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.
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
.
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