Herança de Classes em JavaScript
Aprenda herança de classes em JavaScript com extends e super: cadeia de protótipos, substituição de métodos e propriedades, chamada de código pai, extensão de built-ins e herança de métodos estáticos.
Introdução à Herança de Classes em JavaScript
A herança de classes é um conceito fundamental da programação orientada a objetos que permite que uma classe herde propriedades e métodos de outra classe. Em JavaScript, a herança de classes é implementada usando a palavra-chave extends, fornecendo uma forma de criar uma classe derivada que herda de uma classe base.
Para mais informações sobre a sintaxe básica, consulte JavaScript: Classes e Sintaxe Básica.
Este capítulo aborda como criar classes derivadas, substituir métodos e propriedades, chamar o pai com super, estender classes nativas como Array, e herdar métodos estáticos — além da cadeia de protótipos que faz tudo isso funcionar internamente.
Como Funciona a Herança: A Cadeia de Protótipos
A palavra-chave class é açúcar sintático sobre a herança prototípica do JavaScript. Quando você escreve class Circle extends Shape, o motor configura dois vínculos:
Circle.prototype.__proto__ === Shape.prototype— para que os métodos de instância sejam resolvidos pela cadeia.Circle.__proto__ === Shape— para que os métodos estáticos também sejam herdados.
Quando você lê uma propriedade ou chama um método em um objeto, o motor verifica primeiro o próprio objeto. Se não for encontrado lá, ele percorre a cadeia de protótipos — para Circle.prototype, depois Shape.prototype, depois Object.prototype, e então null — parando no primeiro resultado encontrado. É exatamente por isso que uma instância de Circle pode chamar um método definido em Shape.
Object.getPrototypeOf(obj) retorna o próximo elo da cadeia (a forma padrão e preferida de inspecioná-la). O acessor legado obj.__proto__ aponta para o mesmo objeto. Compreender essa cadeia explica tudo o que se segue: a substituição funciona porque um protótipo mais próximo sobrepõe um mais distante, e super funciona porque pula explicitamente para o protótipo do pai.
Criando uma Classe Derivada
Para criar uma classe que herda de outra, usamos a palavra-chave extends:
Neste exemplo, Circle estende Shape, o que significa que herda as propriedades e métodos de Shape e, ao mesmo tempo, fornece métodos próprios. Note que Circle não define seu próprio construtor. Em JavaScript, classes derivadas sem um construtor explícito chamam automaticamente super() com os mesmos argumentos passados ao construtor da classe derivada.
Substituindo Métodos
Classes derivadas podem substituir métodos de suas classes base para fornecer comportamento específico à subclasse.
Aqui, Circle substitui o método print para refletir seu tipo específico.
Chamando Métodos do Pai com super
Você também pode chamar o método de uma classe pai a partir da classe derivada usando super.methodName(). Isso é útil quando você deseja estender o comportamento do pai em vez de substituí-lo completamente.
Aqui, super.print() executa a lógica do pai antes de adicionar a saída específica da subclasse.
Uma Substituição Realista: Calculando Área
A substituição se destaca quando cada subclasse precisa de um comportamento genuinamente diferente. Aqui, uma Shape base define a interface compartilhada, e cada subclasse substitui area() com seu próprio cálculo. Chamar area() em um Circle resolve para Circle.prototype.area, que sobrepõe a versão base.
Observe que describe() é definido apenas em Shape, mas chama this.area() e obtém a implementação da subclasse. Isso é a cadeia de protótipos em ação: this sempre se refere à instância real, então a busca pelo método começa em Circle ou Rectangle.
Substituindo Propriedades e Lendo super.prop
super não se limita a métodos — você pode ler uma propriedade definida no protótipo do pai com super.prop, o que é útil quando uma subclasse quer se basear no getter do pai em vez de substituí-lo.
Acessando o Construtor do Pai: a Palavra-chave super
Quando uma classe estende outra, a função construtora da classe derivada precisa chamar o construtor do pai usando super() antes de poder usar this. Veja como super é usado em construtores para garantir que a classe pai seja inicializada:
Por que super() Deve Ser Executado Antes de this
Em uma classe derivada, o objeto instância não é criado até que super() seja executado — essa é a responsabilidade do construtor pai. Até então, this está em um estado não inicializado, portanto tocá-lo (lendo, atribuindo ou até retornando o objeto implicitamente) lança um ReferenceError. Esta é uma regra da linguagem, não uma preferência de estilo.
Uma regra relacionada: se uma classe derivada define um construtor, ela deve chamar super() em algum lugar antes de finalizar, ou o mesmo ReferenceError é lançado. (Uma classe derivada sem construtor explícito não tem problema — o JavaScript insere constructor(...args) { super(...args); } automaticamente.) Uma vez que super() retorna, this está totalmente inicializado e pronto para uso.
Herdando Métodos Estáticos
Membros estáticos pertencem à própria classe, não a instâncias. Como extends também vincula Circle.__proto__ a Shape, classes derivadas herdam métodos estáticos e podem chamá-los diretamente. Para mais informações sobre como declará-los, consulte Propriedades e Métodos Estáticos em JavaScript.
Dentro de um método estático, this refere-se à classe na qual foi invocado, então Shape.create chamado como Circle.create constrói um Circle.
Herança em Múltiplos Níveis
As cadeias podem ter mais de dois níveis de profundidade. A busca por métodos simplesmente percorre mais acima na cadeia de protótipos, e super sempre se refere ao protótipo um nível acima da classe onde o método está definido.
Estendendo Classes Nativas
Você pode estender classes nativas como Array, Error ou Map para criar versões especializadas que mantêm todo o comportamento nativo enquanto adicionam o seu.
A armadilha do Symbol.species. Métodos como map, filter e slice retornam uma nova coleção. Por padrão, eles retornam uma instância da sua subclasse (MyArray), não um Array simples — o que geralmente está bem, mas pode surpreender código que espera um Array de verdade. Você pode reverter para arrays simples substituindo o getter estático Symbol.species.
Para combinar comportamentos de múltiplas fontes (JavaScript não possui herança múltipla), consulte Mixins em JavaScript.
Resumo
Principais pontos:
- Use
extendspara derivar uma classe; isso vincula tanto a cadeia de protótipos (métodos de instância) quanto a própria classe (métodos estáticos) ao pai. - A busca por métodos e propriedades percorre a cadeia de protótipos, parando no primeiro resultado — é por isso que uma definição mais próxima substitui uma mais distante. Inspecione a cadeia com
Object.getPrototypeOf(). - Em um construtor derivado, chame
super()antes dethis. Até quesuper()seja executado, a instância não está inicializada e qualquer uso dethislança umReferenceError. - Acesse o comportamento do pai explicitamente com
super.method()ousuper.prop, mesmo quando você o substituiu. - Você pode estender classes nativas como
Array— apenas fique atento ao comportamento doSymbol.speciesquando métodos retornam novas coleções.
Próximos passos:
- Herança Prototípica em JavaScript — o mecanismo no qual as classes são construídas.
- Propriedades e Métodos Estáticos em JavaScript — declarando e herdando membros estáticos.
- Mixins em JavaScript — reutilizando comportamento entre classes não relacionadas sem uma única cadeia de herança.