JavaScript orientado a objetos

Neste artigo você vai ver como criar objetos personalizados Javascript, que é uma linguagem orientada a objetos que trabalha com prototipação, em vez de classes, como é o padrão em outras linguagens de programação.

Lembra-se de quando falamos sobre os objetos nativos Javascript? A primeira frase daquele artigo descreve algo que é necessário para que você entenda como funcionam os objetos Javascript, tanto os nativos quanto os criados por você:

…Qualquer elemento se torna um objeto em Javascript, desde uma string primitiva (que é convertida em objeto quando necessário) até um array…

Tudo é um objeto para Javascript, e tais objetos têm métodos e propriedades que podem ser acessados através de um ponto (.) e o nome do que se deseja acessar.

objeto.valor = 'Qualquer valor'; // Propriedade
objeto.metodo(param, param); // Método

Um método é algo que faz alguma ação dentro do objeto (na verdade, é uma função do objeto); uma propriedade é uma opção que pode receber um valor qualquer, como se fosse uma variável dentro do objeto.

// Cria um objeto número
var numero = new Number(-10);

// Cria uma propriedade na instância do número
numero.nome = 'Dez negativo';

// Acessa a propriedade criada e o número
alert('Número "' + numero.nome + '" = ' + numero );
// Exibe: Número "Dez negativo" = -10

// Utiliza um método de Math para passar o número para positivo
var numerop = Math.abs(numero);

// Altera a propriedade na instância do número
numero.nome = 'Dez positivo';

// Acessa a propriedade alterada e o número
alert('Número "' + numero.nome + '" = ' + numerop );
// Exibe: Número "Dez positivo" = 10

No exemplo acima, utilizamos o objeto Number para criar um novo número; criamos uma nova propriedade nome na instância numero do objeto; por fim, utilizamos o método abs do objeto Math para converter o número para positivo. É apenas um exemplo para que você veja a diferença entre propriedades e métodos.

Você, provavelmente, deve achar que estou sendo repetitivo, com outro artigo sobre objetos em Javascript, porém, dessa vez não vamos verificar os objetos que são parte do coração do Javascript, mas, vamos aprender a criar nossos próprios objetos, com propriedades, métodos, protótipos e assim por diante.

Me acompanhe!

Prototipação (Prototype)

Em várias outras linguagens de programação – pelo menos as que conheço – para criar uma classe que estende outra, você define a classe de maneira que ela herde a funcionalidade do objeto de nível mais alto.

Em Javascript, por outro lado, você cria objetos utilizando Object, que fornece uma propriedade chamada prototype para estender a funcionalidade – métodos e propriedades – de um objeto para outro, até mesmo para objetos internos, como Number e cia.

// Adicionando um método no objeto Number
Number.prototype.alerta = function(num){
	alert('Número: ' + num);
}

// Primeira instância
var num = new Number();
num.alerta(25);

// Segunda instância
var num2 = new Number();
num2.alerta(5);

Perceba que não existe o método "alerta" no objeto interno "Number", porém, ao utilizar prototype, eu estendi a funcionalidade do objeto Number com este novo método. Ou seja, "alerta" vai estar disponível em todas as instâncias do objeto Number.

Isso é muito parecido com criar um novo método em uma instância do objeto, porém, não é a mesma coisa, veja abaixo:

// Primeira instância
var num = new Number();
// Adiciona o novo método apenas na instância 1
num.alerta = function(num){ alert(num); };
// Alerta: 1
num.alerta(1);

// Segunda instância
var num2 = new Number();
 // Erro: num2.alerta is not a function
num2.alerta(5);

No trecho acima, eu não utilizei prototipação (prototype) no objeto Number, ao invés disso, criei um novo método "alerta" na primeira instância do objeto, o que faz com que ele esteja disponível apenas na instância. O problema é que se eu chamar este método em outra instância do número, terei um erro: num2.alerta is not a function.

Isso significa que este método não existe em "num2".

Outro exemplo, seria criar um método que remova os espaços do início e do fim de uma string, veja abaixo:

String.prototype.trim = function(){
	// Atribui this para uma variável interna
	var val = this;
	// Remove os espaços do começo da string
	val = val.replace(/^[s|xA0]+/, '');
	// Remove os espaços do fim da string
	val = val.replace(/[s|xA0]+$/, '');
	
	// Retorna o valor
	return val;
}

// Instanciar o objeto string com um valor cheio de espaços
var frase = new String('            Ol!               ');

// Alerta a string passando pelo método trim()
alert( frase.trim() );

Agora o método trim() estará disponível para todas as strings do seu código Javascript, até as que você cria como string primitiva:

// Retorna *a*
var frase = '           a     ';
alert( '*' + frase.trim() + '*' );

Outro exemplo de prototipação, é quando você quer passar os métodos e propriedades de um objeto para outro, veja:

// Cria um objeto que tem apenas uma propriedade
var primeiro_nome = function(){
	this.nome = 'Luiz';
}

// Cria um segundo objeto mas usa a propriedade do primeiro
var sobrenome = function(){
	// Concatena a propriedade do primeiro objeto com um valor
	this.completo = this.nome + ' Miranda';
}
// Faz a prototipação
sobrenome.prototype = new primeiro_nome;

// Cria uma instância do segundo objeto
var mostra_nome = new sobrenome;

// Mostra "Luiz Miranda"
alert(mostra_nome.completo);

Neste caso, o segundo objeto (sobrenome) recebe as propriedades e métodos do primeiro objeto (primeiro_nome) por prototipação:

// Faz a prototipação
sobrenome.prototype = new primeiro_nome;

A palavra "this", neste caso, faz referência ao próprio objeto pai em que ela está sendo referenciada.

Como passamos as propriedades e métodos do "primeiro_objeto" por prototipação para o "sobrenome", agora a propriedade "nome" faz parte do seu contexto.

Resumindo: mesmo sem ter criado a propriedade "nome" em "sobrenome", se você utilizar um console para visualizar suas propriedades, terá o seguinte:

Object { 
	completo="Luiz Miranda", 
	nome="Luiz"
}

Isso é extremamente interessante para trabalhar com Javascript orientado a objetos.

Se você não sabe o que é, ou tem dúvidas sobre desenvolvimento com linguagens orientadas a objetos, é mais ou menos criar várias classes que fazem coisas diferentes dentro da sua aplicação. Como se elas fossem engrenagens de um motor maior e, se encaixadas, fazem um carro andar (só um exemplo).

Leia mais sobre isso no Wikipédia.

Objetos personalizados Javascript

Existem dois métodos para criar objetos em Javascript, o método literal e uma função construtora (um construtor), nessa sessão vamos ver ambos.

Objeto literal

Para criar um objeto literal em Javascript, simplesmente crie uma variável e coloque duas chaves:

var objeto = {};

Simples, não?

Quer adicionar propriedades no seu objeto literal?

var objeto = {
	propriedade1: 'Valor 1',
	propriedade2: 'Valor 2'
};

// Valor 1
alert(objeto.propriedade1);

Agora seu objeto tem duas propriedades.

Deseja um método?

var objeto = {
	propriedade1: 'Valor 1',
	propriedade2: 'Valor 2',
	metodo: function( nome ) {
		alert('Oi ' + nome);
	}
};

// Oi Luiz
objeto.metodo( "Luiz" );

Esse tipo de objeto é muito interessante quando você não precisa de novas instâncias do mesmo objeto, porque ele não tem um construtor. É recomendado que você utilize objetos literais para configurações do seu projeto, para criar coleções de objetos, ou ainda, para definir o namespace do seu script. Como algumas bibliotecas fazem, por exemplo:

// Cria o objeto
var Foo = {};

// Cria um construtor
Foo.metodo = function ( valor ) {
	this.valor = valor;
};

// Cria uma instância do construtor
var mostra = new Foo.metodo('Valor qualquer');

// Mostra o valor
alert(mostra.valor);

Funções construtoras

Construtores são simplesmente funções, nada mais, nada menos.

O interessante das funções construtoras é que qualquer código dentro delas é executado assim que você cria uma instância do objeto.

var Objeto1 = function(valor){
	alert(valor);
}

// Dois alertas
var instancia1 = new Objeto1('Valor 1');
var instancia2 = new Objeto1('Valor 2');

No trecho de código acima, temos duas instâncias do objeto "Objeto1" (instancia1 e instancia2). Assim que você executar o script, dois alertas serão exibidos na tela. Isso caracteriza um construtor em linguagens orientadas a objetos.

Outro ponto interessante de uma função construtora, é a palavra this. Dentro do contexto de um construtor, normalmente a palavra this faz referência ao próprio objeto.

// Cria o objeto Carros
var Carros = function(){
	// Cria variáveis privadas
	var carros = new Array(),
		i;
	
	// Cria métodos públicos
	this.incluiCarro = function(nomeCarro){
		// Inclui o nome do carro no final do array
		carros[ carros.length ] = nomeCarro;
	}
	
	// Método para mostrar os carros
	this.mostraCarros = function(){
		for( i = 0; i < carros.length; i++ ){
			alert(carros[i]);
		}
	}
};

// Cria uma instância do objeto Carros
var carros = new Carros();

// Inclui carros
carros.incluiCarro('Fusca');
carros.incluiCarro('Fiat 147');

// Fusca
// Fiat 147
carros.mostraCarros();

// Cria outra instância do objeto Carros
var novos_carros = new Carros();

// Inclui carros
novos_carros.incluiCarro('Gol');
novos_carros.incluiCarro('Uno');
novos_carros.incluiCarro('Polo');

// Gol
// Uno
// Polo
novos_carros.mostraCarros();

No código acima criamos um novo objeto "Carros", que tem duas variáveis privadas (um array e i) que não podem ser acessadas fora do contexto da função. Além disso, ele tem dois métodos públicos, incluiCarro e mostraCarros.

Para criar propriedades e métodos públicos (acessíveis pelo ponto) dentro do objeto construtor, basta utilizar a palavra this.

Por exemplo, criar o método ou propriedade "this.marcaCarro" dentro objeto "Carros", faz com que ela fique disponível através da instância do objeto no formato "instância.marcaCarro", ou no caso de método, "instância.marcaCarro()".

Aqui também a prototipação entra em ação. Eu poderia facilmente criar um outro objeto que fizesse outra coisa e utilizar os métodos e propriedades do "objeto pai" (digamos) com prototype.

Veja uma modificação do exemplo anterior:

// Cria o objeto Carros
var Carros = function(){
	// Cria variáveis privadas
	var carros = new Array(),
		i;
	
	// Cria métodos públicos
	this.incluiCarro = function(nomeCarro){
		// Inclui o nome do carro no final do array
		carros[ carros.length ] = nomeCarro;
	}
	
	// Método para mostrar os carros
	this.mostraCarros = function(){
		for( i = 0; i < carros.length; i++ ){
			alert(carros[i]);
		}
	}
};

// Cria outro objeto
var outrosCarros = function(){}
// Obtém as propriedades e métodos por prototipação
outrosCarros.prototype = new Carros;

// Cria uma instância do objeto outrosCarros
var carros = new outrosCarros();

// Inclui carros
carros.incluiCarro('Fusca');
carros.incluiCarro('Fiat 147');

// Fusca
// Fiat 147
carros.mostraCarros();

Além de prototipação, você também pode utilizar os métodos call e apply para passar métodos e propriedades de um objeto para outro, veja:

// Cria o objeto Carros
var Carros = function(){
	// Cria variáveis privadas
	var carros = new Array(),
		i;
	
	// Cria métodos públicos
	this.incluiCarro = function(nomeCarro){
		// Inclui o nome do carro no final do array
		carros[ carros.length ] = nomeCarro;
	}
	
	// Método para mostrar os carros
	this.mostraCarros = function(){
		for( i = 0; i < carros.length; i++ ){
			alert(carros[i]);
		}
	}
};

// Outro objeto
var outrosCarros = function(){
	// Obtém os métodos e propriedades
	Carros.call(this);
}

// Cria uma instância do objeto Carros
var carros = new outrosCarros();

// Inclui carros
carros.incluiCarro('Fusca');
carros.incluiCarro('Fiat 147');

// Fusca
// Fiat 147
carros.mostraCarros();

Não vou entrar muito em detalhes aqui para que o artigo não fique tão extenso, mas seguem os links para exemplos: call e apply.

Outras aulas

Caso tenha perdido a aula anterior, segue o link:

Próxima aula:

Caso queira visualizar todas as aulas dessa sessão em ordem cronológica invertida: