Vamos entender como podemos trabalhar com orientação a objetos no Javascript. Não se preocupe se não entender os primeiros paragrafos entramos primeiro em uma parte conceitual, vamos praticar mais a frente.
O primeiro passo é entender que o Javascript é uma linguagem orientada a prótotipos, e uma das unicas que trabalha com orientação a objetos usando prototipo.
✔️ Herança: é implementada através do processo de clonagem de objetos existentes que servem como protótipos.
✔️ Encapsulamento: é implementada através de variaveis locais dentro do escopo das funções (objetos).
✔️ Polimorfismo: é implementando re-definindo os comportamentos do objeto base.
Para alguns esses recursos não são o sulficiente para ser uma linguagem orientada a objetos por falta de:
❌ Classe abstra ou Abstração (até é possivel atravez de algumas implementações porem não de forma nativa)
❌ Sobrecarga (Overload)
Contudo esses recursos não são os pilares da Orientação a objetos, os pilares são:
E mesmo com isso quando utilizamos typescript temos todos os recursos nescessarios e mais algumas vatagens que uma linguagem orientada a protótipo tem como:
- Flexibilidade: Podemos alterar um objeto sem a necessidade de criar uma nova classe
Na classica classe e objeto são coisas diferenetes. Quando definimos uma subclasse, criamos uma nova classe que herda propriedades e comportamentos de sua classe base. E só possivel manipular os dados da subclasse após instanciar ela e neste momento ela se torna um objeto.
Já na orientada a protótipo não temos essa distinção temos apenas objetos, e objetos tem seus prototipos (prototypes) que podem ser usados para gerar outros objetos. Todos objetos instanciado tem referencia a seu objeto prototypo de origem através do proto. Cada objeto pode definir suas propriedades.
IMAGEM HERE
Quando criamos um objeto literal o javascript automaticamente "instancia um novo objeto" apartir da "classe" Object
(que na verdade não é uma classe e sim um construtor), na verdade ele criar um novo objeto copiando todo o prototype do Object
como vemos a alguns encontros atrás.
const pessoa = {};
console.log(pessoa instanceof Object); // true
Podemos criar uma função que defina o comportamento daquele objeto.
function createPessoa(nome) {
const pessoa = {};
pessoa.nome = nome;
pessoa.apresentacao = () => alert(`Olá, eu sou ${pessoa.nome}`);
return pessoa;
}
const pessoa1 = createPessoa('Marcos');
console.log(pessoa);
// {nome: 'Marcos', apresentacao: ƒ}
// apresentacao: () => alert(`Olá, eu sou ${pessoa.nome}`)
// nome: "Marcos"
// [[Prototype]]: Object
Porem não é muito legivel, e se torna quase impossivel saber qual a instancia dessa classe se não indentificando por suas propriedades e comportamentos.
Outro caminho mais interessante é definir um construtor como os existentes Object, Date, Array que são nativos do Javascript. Para isso basta usar uma função:
function Pessoa(nome) {
this.nome = nome;
this.apresentacao = () => this.nome;
}
const pessoa2 = new Pessoa('Marcos');
console.log(pessoa);
// {nome: 'Marcos', apresentacao: ƒ}
// apresentacao: () => alert(`Olá, eu sou ${pessoa.nome}`)
// nome: "Marcos"
// [[Prototype]]: Object
Assim se quiser verificar sua instancia podemos usar o instanceof:
console.log( pessoa1 instanceof Pessoa ); // false
console.log( pessoa1 instanceof Object ); // true
console.log( pessoa2 instanceof Object ); // true
console.log( pessoa2 instanceof Pessoa ); // true
console.log( Pessoa.prototype instanceof Object ); // true
console.log( pessoa1.constructor.name ); // Object
console.log( pessoa2.constructor.name ); // Pessoa
Porem como disse antes para criar uma nova instancia o Javascript copia o prototipo de um objeto para o outro desta forma se verificar de quem pessoa2
herdou suas propriedades de Object
,
por este motivo ele tem as propriedades padrões do Object
.
console.log(pessoa2.hasOwnProperty); // ƒ hasOwnProperty() { [native code] }
Outra forma de fazer isso pode ser usando o prototype do objeto:
function Pessoa(nome) {
this.nome = nome;
}
Pessoa.prototype.apresentacao = function () { return this.nome; };
Para herdar as propriedades de outro objeto podemos usar o call assim ele passa o escopo do objeto que esta sendo criado para a chamada do objeto pai que ele herdara as propriedades desta forma:
function Pessoa(nome) {
this.nome = nome;
}
function Medico(nome, crm) {
Pessoa.call(this, ...arguments);
this.crm = crm;
}
const medico1 = new Medico('Antonio', 'xxx');
console.log( medico1 );
// Medico {nome: 'Marcos', crm: 'xxx'}
// [[Prototype]]: Object
console.log( medico1 instanceof Pessoa ); // false
console.log( medico1 instanceof Medico ); // true
console.log( medico1 instanceof Object ); // true
Se prestar atenção ele ainda mantem o Object como sua herança para mudar isso basta definir seu protótipo com o objecto que ele herdara suas propriedades assim:
Medico.prototype = Object.create(Pessoa.prototype);
Object.defineProperty(Medico.prototype, 'constructor', { value: Medico, enumerable: false, writable: true });
const medico2 = new Medico('Antonio', 'xxx');
console.log( medico2 );
// Medico {nome: 'Marcos', crm: 'xxx'}
// [[Prototype]]: Pessoa
console.log( medico1 instanceof Pessoa ); // true
console.log( medico1 instanceof Medico ); // true
console.log( medico1 instanceof Object ); // true
Com a ECMAScript 2015 ou es6 acima podemos utilizar classes para ter o mesmo resultado que antes era preciso usar toda aquela sintax verbosa:
class Pessoa {
constructor(nome) {
this.nome = nome;
}
}
Inclusive aplicar herança de forma mais curta:
class Medico extends Pessoa {
constructor(nome, crm) {
super(nome);
this.crm = crm;
}
}
Definir propriedades getter e setter:
class Carro {
get documento() {
return this._documento;
}
set documento(documento) {
this._documento = documento;
}
}
Mesmo ainda não tendo uma sintax para acessar variaveis privadas a comunidade criou uma convenção para usar _
o underline para dizer que não podemos usar mecher no valor pois é privado.
Porem isso não impede de acessar aquele valor. Tem uma proposta de implementação de propriedades privadas
em andamento você pode conferir os status disso aqui. Ficaria próximo disso:
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42;
}
}
O Objetivo deste estudo é aprender os conceitos fundamentais ao trabalhar com dados estruturados, usando objetos.