Programação orientada a objetos
JavaScript é uma linguagem de programação baseada em protótipos (prototype-based language), onde propriedades e métodos podem ser compartilhadas entre objetos. Algumas características interessantes sobre objetos em JavaScript:
- Quase todos objetos JavaScript são instâncias de
Object
. - Um objeto típico herda as propriedades e métodos de
Object.prototype
. - Propriedades e métodos de
prototype
podem ser sobrescritas. - As alterações em
prototype
podem ser vistas por todos os objetos descendentes. - O construtor
Object
cria um objeto wrapper para um valor dado. - O construtor
Object
cria um objeto vazio para os valoresnull
eundefined
.
Um erro comum é acreditar que números inteiros (literais) não podem ser usados como objetos, pois o ponto .
faz com que o interpretador o interprete como um número de ponto flutuante.
123.toString(); // produz um erro de sintaxe
(123).toString(); // "123"
123.0.toString(); // "123"
123..toString(); // "123"
123 .toString(); // "123"
Veja mais em MDN Web Docs.
Implementação com prototype
Veremos um exemplo que faz uso do prototype
para compartilhar propriedades e métodos entre objetos descendentes, em outras palavras, herança. Para isso, criaremos três funções construtoras, a primeira, Coordenada2D, que será usada como base para as outras duas, Retangulo e Circulo.
function Coordenada2D(x, y) {
this.tipo = "Coordenada 2D";
this.x = x;
this.y = y;
}
function Retangulo(x, y, base, altura) {
Coordenada2D.call(this, x, y);
this.tipo = "Retângulo";
this.base = base;
this.altura = altura;
}
function Circulo(x, y, raio) {
Coordenada2D.call(this, x, y);
this.tipo = "Círculo";
this.raio = raio;
}
Na sequência, são criadas as relações de herança entre o prototype
de Coordenada2D e o prototype
de Retangulo e Circulo.
Retangulo.prototype = Object.create(Coordenada2D.prototype);
Circulo.prototype = Object.create(Coordenada2D.prototype);
Após a definição da relação de herança, definiremos os métodos mostrarPosicao
de Coordenada2D e os métodos mostrarAtributos
de Retangulo e de Circulo.
Coordenada2D.prototype.mostrarPosicao = function() {
return `${this.tipo} na posição (${this.x}, ${this.y})`;
};
Retangulo.prototype.mostrarAtributos = function() {
return `${this.mostrarPosicao()} com base ${this.base} e altura ${this.altura}`;
};
Circulo.prototype.mostrarAtributos = function() {
return `${this.mostrarPosicao()} com raio ${this.raio}`;
};
let r = new Retangulo(-10, 10, 200, 400);
r.mostrarPosicao(); // "Retângulo na posição (10, -10)"
r.mostrarAtributos(); // "Retângulo na posição (10, -10) com base 200 e altura 400"
let c = new Circulo(0, 0, 200);
c.mostrarPosicao(); // "Círculo na posição (0, 0)"
c.mostrarAtributos(); // "Círculo na posição (0, 0) com raio 200"
Inspecionando os objetos r
e c
, temos:
Retangulo {tipo: "Retângulo", x: 10, y: -10, base: 200, altura: 400}
altura: 400
base: 200
tipo: "Retângulo"
x: 10
y: -10
__proto__: Coordenada2D
mostrarAtributos: ƒ ()
__proto__:
mostrarPosicao: ƒ ()
constructor: ƒ Coordenada2D(x, y)
__proto__: Object
Circulo {tipo: "Círculo", x: 0, y: 0, raio: 200}
raio: 200
tipo: "Círculo"
x: 0
y: 0
__proto__: Coordenada2D
mostrarAtributos: ƒ ()
__proto__:
mostrarPosicao: ƒ ()
constructor: ƒ Coordenada2D(x, y)
__proto__: Object
Implementação com class
Entendemos classes como templates para criar objetos.
Em JavaScript, classes são funções construtoras com uma propriedade prototype
.
A partir da especificação ECMAScript 2015 (ES6), JavaScript passou a contar com implementação de objetos a partir de classes.
Essa sintaxe é descrita como um açúcar sintático, uma forma mais “simples” de implementar objetos com prototype
.
A seguir, replicamos o código apresentado previamente implementado com prototype
, agora com class
.
class Coordenada2D {
constructor(x, y) {
this.tipo = "Coordenada 2D";
this.x = x;
this.y = y;
}
mostrarPosicao() {
return `${this.tipo} na posição (${this.x}, ${this.y})`;
}
}
class Retangulo extends Coordenada2D {
constructor(x, y, base, altura) {
super(x, y);
this.tipo = "Retângulo";
this.base = base;
this.altura = altura;
}
mostrarAtributos() {
return `${this.mostrarPosicao()} com base ${this.base} e altura ${this.altura}`;
}
}
class Circulo extends Coordenada2D {
constructor(x, y, raio) {
super(x, y);
this.tipo = "Círculo";
this.raio = raio;
}
mostrarAtributos() {
return `${this.mostrarPosicao()} com raio ${this.raio}`;
}
}
let r = new Retangulo(-10, 10, 200, 400);
r.mostrarPosicao(); // "Retângulo na posição (10, -10)"
r.mostrarAtributos(); // "Retângulo na posição (10, -10) com base 200 e altura 400"
let c = new Circulo(0, 0, 200);
c.mostrarPosicao(); // "Círculo na posição (0, 0)"
c.mostrarAtributos(); // "Círculo na posição (0, 0) com raio 200"
Monkey patch (Bônus! ⭐)
Monkey patch é uma técnica usada para estender ou modificar o comportamento de componentes de um sistema de software em tempo de execução.
🤚 Cuidado: essa é uma técnica que deve ser usada com cautela.
Como exemplo, estenderemos o objeto Array
com um novo método que retorna somente os elementos que forem pares, e modificaremos o método pop
de forma que ele remova todos os elementos de um Array
.
// Adicionar o método somentePares() ao objeto Array
Array.prototype.somentePares = function() {
return this.filter(item => item % 2 === 0);
};
const numeros = [1, 1, 2, 3, 5, 8, 13, 21];
numeros.somentePares(); // [2, 8]
// Substituir o método pop do objeto Array
Array.prototype.pop = function() {
while (this.length > 0) this.shift();
};
numeros.pop(); // agora numeros é igual a []