View on GitHub

Capacitacao-CEOS-2-Javascript

Prototype

Em Javascript, existe um mecanismo chamado de Prototype.

Esse mecanismo, nos permite buscar/delegar propriedades e métodos para outros objetos.

Pode ter ficado confuso, porém, vamos ver esse comportamento com um exemplo:

let meuObj = {};

let meuObjAprimorado = {
    a: 42,
    dizOi: function() { console.log('oi'); }
};

Object.setPrototypeOf(meuObj, meuObjAprimorado);

console.log(meuObj.a); // 42
meuObj.dizOi();

Executem esse código e vejam o que acontece.

Perceba que, embora o objeto meuObj claramente não possui a propriedade a e o método dizOi, o código executou sem problemas.

Isso aconteceu porque utilizamos o mecanismo de Prototype do Javascript. Como o objeto meuObj não possui a propriedade a, tivemos que procurar por essa propriedade em um outro objeto, que nesse caso era o objeto meuObjAprimorado. O mesmo acontece para o método dizOi.

Na linha Object.setPrototypeOf(meuObj, meuObjAprimorado);, estamos dizendo que o prototipo do objeto meuObj é o objeto meuObjAprimorado, em outras palavras, isso diz que: caso não exista uma determinada propriedade no objeto meuObj, podemos procurar essa propriedade também no objeto meuObjAprimorado.

Como o objeto meuObjAprimorado possui a propriedade a e o método dizOi, o código é executado sem problemas.

E é isso que o mecanismo de Prototype nos oferece: delegar/usar propriedades e métodos que foram definidos em outros objetos, nos poupando de ter que definir tuuuudo novamente, em cada objeto novo que for criado. Isso permite reutilização de código.

Você deve ter achado estranho também Object.setPrototypeOf.

Object é um objeto global de javascript, ele está sempre disponível no seu código, então podemos usar esse objeto a vontade. E setPrototypeOf é um método do objeto Object.

Existem dois métodos muito úteis quando estamos trabalhando com o mecanismo de prototype:

Voltando ao exemplo:

let meuObj = {};

let meuObjAprimorado = {
    a: 42,
    dizOi: function() { console.log('oi'); }
};

Object.setPrototypeOf(meuObj, meuObjAprimorado);

console.log(Object.getPrototypeOf(meuObj)); // mostra o objeto `meuObjAprimorado`

console.log(Object.getPrototypeOf(meuObj) === meuObjAprimorado); // true

No exemplo, Object.getPrototypeOf(meuObj) retorna o objeto meuObjAprimorado, que é o protótipo do objeto meuObj.

No nosso exemplo:

let meuObj = {};

let meuObjAprimorado = {
    a: 42,
    dizOi: function() { console.log('oi'); }
};

Object.setPrototypeOf(meuObj, meuObjAprimorado);

O objeto protótipo de meuObj será o objeto meuObjAprimorado.

Outro exemplo:

let fiat_uno = {
    nome: "Uninho",
};

let carro = {
    rodas: 4,
    ligarMotor: function() {
        console.log("VRUMMMMVNUMBNUNUMMMMMMMMMMm");
    },
    nome: "Carrão"
};

Object.setPrototypeOf(fiat_uno, carro);

fiat_uno.ligarMotor(); // VRUMMMMVNUMBNUNUMMMMMMMMMMm
console.log(fiat_uno.rodas); // 4

console.log(fiat_uno.nome); // Uninho

Perceba que neste exemplo, o protótipo do objeto fiat_uno é o objeto carro.

Algumas propriedades estão definidas no objeto fiat_uno, e outras no objeto carro. Perceba que a propriedade nome existe tanto no objeto carro quando no objeto fiat_uno, porém é impresso na tela “Uninho”.

Isso acontece porque quando tentamos acessar uma propriedade ou método em um objeto, a gente procura nesse primeiro objeto,e somente depois realiza a busca nos protótipos.

Portanto, quando tentamos acessar a propriedade nome do objeto fiat_uno, achamos de cara essa propriedade, com o valor "Uninho". Porém, no caso do método ligarMotor, não conseguimos achar esse método no objeto fiat_uno, então procuramos no protótipo de fiat_uno, que é o objeto carro,e então achamos a propriedade nome no objeto carro.

É importante compreender essa busca. O algoritmo seria algo do tipo:

- procura no objeto original, caso achar, use o valor armazenado nessa propriedade
- caso não ache, procura no protótipo do objeto inicial
- caso não ache no protótipo do objeto original, continue a busca no protótipo do protótipo do objeto original...
- vamos repetindo o passo acima, até achar a propriedade, ou encontrar null, encontrando null, nós paramos a busca.

Antes das versões mais novas do Javascript, usávamos o mecanismo de prototype para criar classes (nossos tipos novos,além dos tipos padrões do Javascript), fazendo algo do tipo:

function Animal(nome, idade) {
    this.nome = nome;
    this.idade = idade;
}

Animal.prototype.comer = function() {
    console.log(this.nome + " está comendo!");
}

Animal.prototype.fazerAniversario = function() {
    this.idade++;
    console.log(this.nome + " fez aniversário e agora tem " + this.idade + " anos!");
}

let animal = new Animal("Pato", 15);
animal.comer();
animal.fazerAniversario();

Você pode executar esse código clicando aqui

Existem algumas coisas diferentes nesse código. Vamos analisar e entender esse código acima.

Perceba a palavra new. new serve para criar um novo objeto que vai ser utilizado como this dentro da função que foi chamada, neste caso, a função Animal, então o this da função animal é um novo objeto vazio {}.

Animal é um tipo de função chamada de função construtora, porque ela serve para inicializar esse novo objeto vazio, o this. Isso remete muito a construtores em outras linguagens orientadas a objeto, como Java ou C++.

Animal.prototype é um objeto que será o protótipo desse objeto construído através do new, portanto, temos aqui um exemplo de código que depende do mecanismo de prototype do Javascript para funcionar. Antes do ES6, era esse o jeito mais usado de criar classes e escrever código orientado a objeto em JS.

Resumindo, os passos do código acima são os seguintes:

- new cria um novo objeto que será usado como this da função Animal
- Animal é uma função construtora, que serve para preencher esse objeto novo, que é acessado através do `this`
- Internamente, o javascript torna o objeto Animal.prototype o prototipo desse objeto criado com new
- esse objeto é retornado (implicitamente, perceba a ausencia de return)

Caso ainda esteja confuso, esse código acima é equivalente a:

// novo objeto
let animal = {
    nome: "Pato",
    idade: 15
};

let animalPrototype = {};

animalPrototype.comer = function() {
    console.log(this.nome + " está comendo!");
}

animalPrototype.fazerAniversario = function() {
    this.idade++;
    console.log(this.nome + " fez aniversário e agora tem " + this.idade + " anos!");
}

Object.setPrototypeOf(animal, animalPrototype);

animal.comer();
animal.fazerAniversario();

Executando esse código, você verá os mesmos resultados do código anterior.

Certo, antes do ES6 (lançado em 2015), era assim que simulavamos classes em Javascript.

Se você já estudou Orientação da Objetos, é importante notar que o mecanismo de prototipos do javascript é a forma de impĺementarmos herança em Javascript.

No próximo capitulo, iremos ver mais sobre como criar classes com a sintaxe atualizada de Javascript, que fornecem uma sintaxe mais prática para lidar com o mecanismo de protótipos do Javascript.