Vários desenvolvedores web falam que protótipos representam uma forma de definirmos tipos de objetos, mas se você observar com cuidado, isto é uma característica de funções.
Perceba que todas as funções têm uma propriedade prototype que, inicialmente, referencia um objeto vazio.
Usando a palavra chave New para invocar a função construtor, temos agora um objeto recém instanciado como seu contexto.
Ex:
function Lutador(){} var lutador1 = new Lutador();
O básico – Incluindo métodos numa classe (função)
Temos nossa classe Lutador, que se encontra vazia e queremos anexar um método a ela. Logo utilizamos a seguinte estrutura:
function Lutador(){} Lutador.prototype.Socar = function(){ return true; } var lutador1 = new Lutador(); console.log(lutador1.Socar());
Um protótipo nos permite predefinir propriedades, incluindo métodoss. Você pode saber como acessar as propriedades de protótipos a partir da instância do objeto com lutador1.constructor.prototype.Socar.
Uma observação importante é que não importa a ordem onde o protótipo é declarado, pois “suas atualizações” são feitas dinamicamente, ex:
Isto:
function Lutador(){} Lutador.prototype.Socar = function(){ return true; } var lutador1 = new Lutador();
Tem o mesmo sentido de:
function Lutador(){} var lutador1 = new Lutador(); Lutador.prototype.Socar = function(){ return true; }
Podemos também instânciar um objeto deste modo:
function Lutador(){} var lutador1 = new Lutador(); var lutador2 = new lutador1.constructor(); console.log("Verificanco se esta afirmção é verdadeira: "+lutador1 !== lutador2);
Note que lutador1 não é o mesmo objeto de lutador2, mas são duas instâncias distintas.
Tudo que vimos até agora, foi o básico de que os protótipos oferecem, agora está na hora de avançar um pouco mais.
Herança e a cadeia de protótipos
Existem várias formas de como obter uma herança com protótipos, mas sem dúvida, a melhor forma é este modo:
function Lutador(){ this.attackPlayer = function(){ return true; } } function Habilidades(){ this.esquivaPlayer = function(){ console.log("esquivou"); } } //fazendo Lutador herdar de Habilidades Lutador.prototype = new Habilidades(); lutador1 = new Lutador(); //verificando console.log(lutador1 instanceof Lutador); console.log(lutador1 instanceof Habilidades);
Nota: “Existe também outra técnica semelhante a esta, e que eu desaconselho. É utilizar o objeto do protótipo de Habilidades diretamente como protótipo de Lutador_… Ex:_ Lutador.prototype = Habilidades.prototype; pois fazendo isto, qualquer alteração no protótipo de Lutador também modificará o protótipo de Habilidades, porque eles serão o mesmo objeto, e isso com certeza terá alguns efeitos colaterais indesejaveis
Incluindo novo método nos elementos HTML por meio do protótipo HTMLElement
Nos navegadores atuais e antigos (IE8+) temos uma funcionalidade bastante interessante que nos permite estender qualquer nó HTML de nossa escolha, vejamos então o próximo exemplo:
HTMLElement.prototype.remover = function() { this.parentNode.removeChild(this); }; document.querySelector("#a").remover();
Neste exemplo incluimos um novo método “remover”, em todos os elementos do DOM por meio do protótipo do contrutor HTMLElement. Logo depois removemos o elemento html que tem por id=”a”
Um exemplo muito bom de utilização deste recurso é a biblioteca Prototype, por ela conseguimos obter muitas funcionalidades nos elementos DOM, por exemplo injetar HTML e manipular CSS.
Extenção de Object e Array
Por meio de protótipos também podemos estender os tipos primitivos do javascript, como: Object, Array e Number. Vejamos como estender uma variável do tipo array por meio de protótipos:
Array.prototype.cataFruta = function(callback) { for(var i = 0; i < this.length; i++){ callback.call(this,this[i],i); } }; var frutas = ["laranja","uva","pinha","morango"]; frutas.cataFruta(function(element,index){ console.log("fruta: "+element+" sua posição é: "+index); });
Note que adicionamos um método “cataFruta()”, que serve como um forEach dentro do array, e este mesmo método pode receber um callback que retorna dois parâmetros, que é o elemento atual em execução e o seu índice. Também podemos estender Object:
Object.prototype.esconde = function(callback) { if(this.hasOwnProperty("style")){ this.style.opacity = 0; this.style.filter = "alpha(opacity=0)"; callback.call(this,this); } }; document.getElementById('escondido').esconde(function(element){ console.log("escondemos a div com o id: "+element.getAttribute("id")+"!!"); });
Note que usamos a mesma lógica para os dois, só mudou que ao invés de Array.prototype colocamos Object.prototype. Também podemos estender o tipo Number, algo que não recomendo pois ele é um protótipo nativo muito problemático.
“Devido à forma como números e propriedades de números são processados pelo engine JavaScript, alguns resultados podem ser bastante confusos…” (Segredos do Ninja Javascript::Novatec)
Subclasse do objeto Array
Como expliquei mais à cima, existe um modo de herdamos heranças em classes JavaScript, isso não é diferente com o objeto Array. Vejamos um exemplo de como podemos criar uma subclasse do tipo array:
function MinhaClasse(){} MinhaClasse.prototype = new Array(); var meu = new MinhaClasse(); meu.push(1,2,3); console.log("O tamanho da subclasse meu é: "+meu.length); Ou ao invés de criar, podemos apenas "simular" a criação de uma subclasse do tipo array, ex:
function MinhaClasse(){} MinhaClasse.prototype.length = 0; (function(){ var novos_metodos = ['push','shift','join','unshift','slice','pop','splice']; for (var i = 0; i < novos_metodos.length; i++)(function(metodo){ MinhaClasse.prototype[metodo] = function(){ return Array.prototype[metodo].apply(this,arguments); } })(novos_metodos[i]); })(); var meu = new MinhaClasse(); meu.push(1,2,3); console.log("O tamanho da subclasse meu é: "+meu.length);
Um erro grave de usuário
Tudo que vimos até agora é muito bom e ajuda bastante para nossas aplicações JavaScript, com esse conhecimento e um pouco de esforço da até mesmo para criar sua própria biblioteca JavaScript. Mas ainda eu não poderia encerrar o artigo sem antes lhes prevenir de um erro que alguns usuários com pouco conhecimento de JavaScript cometem…
Tudo que vimos não terá utilidade nenhuma se não invocarmos a função como construtor. Imagine que um usuário leigo de JS pegou seu código mas tenta invocar a função “como função” achando que é assim que tem que ser feito. ex:
function Pessoa(nome,sobrenome){ this.name = nome+" "+sobrenome; } var homem = Pessoa("Clóvis","Neto"); console.log(homem.name);
Note no console de seu navegador que um erro aconteceu ao tentarmos verificar o name de homem. Logo isto ocorrerá sempre que o usuário tentar invocar a “função como função”.
Então qual será a solução para este problema?
Simples podemos fazer uma verificação, se o objeto é uma instância da função… caso não seja, retornaremos a forma correta de como chamar a função e tudo estará resolvido. Ex:
function Pessoa(nome,sobrenome){ if(!(this instanceof arguments.callee)){ return new Pessoa(nome,sobrenome); } this.name = nome+" "+sobrenome; } var homem = Pessoa("Clóvis","Neto"); console.log(homem.name);
Rode agora nosso script e veja que tudo vai bem, e não aparece erro nenhum no console 🙂
Bem vou parar por aqui para o artigo não ficar muito grande, por hoje é só, um forte abraço de Clóvis Neto e até a próxima 😀