Tableless

Busca Menu

Dominando o uso de prototype em JavaScript

Seja o primeiro a comentar por

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 😀

Publicado no dia