JavaScript Orientado a Objetos – Parte 2

O JavaScript, apesar de ser usado há muito tempo quase que exclusivamente como linguagem de programação estruturada, também é uma linguagem de programação orientada a objetos poderosa. Nessa segunda parte da série, vamos nos aprofundar um pouco mais nessa técnica de programação.

A Propriedade prototype

No JavaScript, toda função construtora possui uma propriedade chamada Function.prototype. Essa propriedade nos permite adicionar e, em certos casos modificar, propriedades ou métodos de um construtor, após sua definição. Diferente de PHP, por exemplo, podemos adicionar propriedades durante a execução.

function Person() {}
        var me = new Person();
        alert(me.eyes); // undefined
        Person.prototype.eyes = 2;
        alert(me.eyes); // 2

Como se pode notar, na primeira tentativa não tínhamos a propriedade definida. Após isso, adicionada em tempo de execução, já temos a propriedade definida.

Também há certas exceções na utilidade do Function.prototype. A primeira delas é que essa propriedade não consegue sobrescrever qualquer coisa que já tenha sido definida no construtor. Não podemos definir uma propriedade no construtor e depois mudar seu valor através de Function.prototype.

function Person() { this.eyes = 2; }
        var me = new Person();
        alert(me.eyes); // 2
        Person.prototype.eyes = 1;
        alert(me.eyes); // 2, continuo tendo 2 olhos

Para resolver esse problema e continuar usando Function.prototype, podemos definir a propriedade fora do construtor usando a técnica, e sobrescrevê-la da mesma maneira.

function Person() {}
        Person.prototype.eyes = 2;
        var me = new Person();
        alert(me.eyes); // 2
        Person.prototype.eyes = 1;
        alert(me.eyes); // 1

Outro pequeno problema é quando definimos objetos ou arrays através dessa propriedade. Veja:

function Person() {}
        Person.prototype.uses = ["watch", "glasses"];
        var me = new Person();
        var you = new Person();
        me.uses.push("hat");
        alert(me.uses); // ["watch", "glasses", "hat"]
        alert(you.uses); // ["watch", "glasses", "hat"]

Com base no exemplo, pode-se ver que o array é compartilhado, assim como todos os seus itens. Moral da história: é preferível definir tudo no próprio construtor, ou então definir apenas métodos através de Function.prototype.

Encapsulamento

Formalmente, encapsulamento ainda não existe no JavaScript, mas podemos usar esse recurso disfarçado.

Tudo o que criamos dentro de nossos construtores está no escopo público, ou seja, pode ser acessado de dentro do construtor ou do restante do script. Uma maneira de simular variáveis privadas, que só podem ser acessadas de dentro do construtor e por seus métodos, é utilizando o escopo local. Lembra-se de quando falei sobre escopo de variáveis?

function Person() {
                this.public = "Variável Pública";
                var private = "Variável Privada";
                this.publicMethod = function() {
                        alert(this.public);
                        alert(private);
                }
                var privateMethod = function() {
                        alert(private);
                }
                this.callPrivate = function() {
                        alert(private);
                }
        }
        var me = new Person();
        alert(me.public); // "Variável Pública";
        alert(me.private); // undefined
        me.publicMethod();
        me.privateMethod(); // Retornará erro
        me.callPrivate();

O exemplo acima exemplifica bem de onde cada tipo de variável pode ser acessado. Já o uso de variáveis protegidas (protected) necessitam de mais gambiarras, mas isso fica pra outra vez.

Quanto ao uso de métodos privados só existe um pequeno problema: não é possível acessar propriedades ou métodos públicos diretamente. Para isso, é necessário modificar o escopo em que a função executada, através de Function.bind (função user-defined).

function Person() {
                this.public = "Variável Pública";
                var bindPublic = function() {
                        alert(this.public);
                }.bind(this);
                bindPublic();
        }
        var me = new Person();

Para testar, lembre-se de copiar a função Function.bind!

E a parte 2 se encerra aqui. Volto na parte 3 pra falar mais um pouco sobre programação orientada a objetos. Até lá!

19 de fevereiro, 2008

JavaScript Orientado a Objetos – Parte 1

O JavaScript, apesar de ser usado há muito tempo quase que exclusivamente como linguagem de programação estruturada, também é uma linguagem de programação orientada a objetos poderosa. Nessa série – remodelada – vou mostrar um pouco sobre o assunto.

Depois de muito tempo, resolvi editar essa série e repostá-la aqui. Aprendi um pouco mais sobre o assunto através da prática, corrigi alguns erros – muito criticados na época – e adicionei algumas informações que achei pertinentes. Como dá pra ver, o permalink continua o mesmo, e a versão antiga da série já não está mais no ar. Espero que as correções e adições possam esclarecer bem o assunto.

Alguns Conceitos

Antes de começarmos a colocar as mãos em código, vamos aprender um pouco de teoria. O JavaScript não é uma linguagem orientada a objetos completamente desenvolvida, e não implementa alguns conceitos nativamente, como herança e variáveis públicas e privadas, dependendo de “gambiarras” para sua implementação.

A base da programação orientada a objetos é, logicamente, objetos. Um objeto é uma abstração de algo real (uma mesa, um carro, a janela do navegador, um elemento HTML…). Esse objeto possui sua própria estrutura de dados, encapsulada em seu próprio escopo. Esses dados podem ser tipos primitivos (Strings, Arrays, Numbers, Booleans…), funções (as quais chamaremos de métodos, quando dentro dos objetos) e até outros objetos.

Para criarmos um objeto, precisamos da definição de sua estrutura de dados, criada através de uma matriz. Essa matriz é chamada de classe, ou construtor no JavaScript.

De começo creio que é o suficiente. Quando for necessário, explicarei outros conceitos, como encapsulamento, polimorfismo, herança…

Criando Construtores

Em nosso querido JavaScript, um construtor é apenas uma função comum, que é instanciada em uma variável. Essa instância se faz através da palavra-chave new, indicando a criação de um novo objeto.

function Person() {}
	var me = new Person();

A palavra-chave new, entretanto, só é necessária caso quando a função não retorna o objeto recém-criado. Para retorná-lo, usamos a palavra-chave this.

function Person() { return this; }
	var me = Person();

O this, apesar de estar no construtor, refere-se ao próprio objeto. Ele será bem usado mais a frente.

Definindo Propriedades

Propriedades, ou atributos, são os dados que ficam encapsulados no escopo do objeto. Como dito mais acima, essas propriedades podem ser tipos primitivos, funções ou outros objetos.

Seguindo a idéia de algo real, toda pessoa tem um nome. Vamos dar um nome à pessoa que “criamos”.

var me = new Person();
	me.name = "Julio Greff";

No código acima, definimos um nome apenas ao objeto me. Outros objetos criados a partir de Person não terão a mesma propriedade. Isso é útil em nosso caso, já que somente eu me chamo “Julio Greff”, e só você possui seu nome.

A mesma técnica não é tão útil quando precisamos definir uma propriedade para todos os objetos criados a partir de determinado construtor. Já pensou se tivéssemos, por exemplo, que definir o número de olhos para cada pessoa que criarmos? Para evitar retrabalho, definimos as propriedades comuns a todos objetos dentro do construtor.

function Person() {
		this.eyes = 2;
	}

No exemplo, usei this para me referir ao próprio objeto, ou seja, todo objeto criado terá a propriedade eyes igual a 2.

Mesmo definindo uma propriedade comum a todos objetos, podemos modificar alguma propriedade de um objeto em particular sem que os outros sejam afetados. Por exemplo, no caso de um cíclope, caolho ou alguma anomalia genética grave.

var me = new Person();
	var cyclop = new Person();
	cyclop.eyes = 1;
	alert(cyclop.eyes); // 1
	alert(me.eyes); // 2, inalterado

Como você deve ter notado – ou pelo menos devia – o acesso às propriedades é feito através do . (ponto), assim como é feita a sua definição.

Desde quando a humanidade é a humanidade, toda pessoa recebe um nome quando nasce. Também pode receber um apelido, uma roupa, ou qualquer outra coisa. Com os objetos acontece algo parecido. Na “concepção” de cada objeto, podemos passar parâmetros para a função construtora, assim como faríamos como outra função qualquer. Esses parâmetros podem, mais tarde, vir a se tornarem propriedades do objeto.

function Person(name) {
		this.name = name;
	}

Dessa maneira, quando criamos o objeto, já podemos dar um nome à pessoa, sem a necessidade de fazê-lo depois da criação do objeto.

Definindo Métodos

Assim como características (propriedades), pessoas também têm comportamento: andar, falar, comer… Objetos também têm comportamento, e esses comportamentos são chamados de métodos. Métodos nada mais são do que funções que são executadas no escopo de algum objeto.

Para criarmos métodos, procedemos da mesma forma com que criamos propriedades. Simples, rápido e indolor:

function Person() {
		this.speak = function(message) {
			alert(message);
		}
	}

Para executarmos métodos também procedemos da mesma forma com que acessamos propriedades, com a diferença de que estamos chamando uma função.

me.speak("Olá!"); // "Olá!"

Essa não é a única maneira de criar métodos, mas é a mais simples, mais organizada e mais eficiente. As outras formas usam propriedades apontando para uma função criada normalmente, dentro ou fora do objeto.

Para a parte 1 é o suficiente. Tenha uma boa digestão e logo volto com mais! Até lá!

18 de fevereiro, 2008

Mootools – Chain

Cookies!

Executar funções em determinada ordem, como filas de requisições, através de uma classe. Resumindo, é isso o que faz a classe Chain, da Mootools, e nesse post veremos como funciona e também como implementar essa funcionalidade em suas próprias classes.

Seqüenciando Funções

Para criar uma seqüência de funções, basta instanciar a classe Chain. Após isso, já podemos adicionar funções à seqüência para podermos chamá-la quando necessário.

Adicionamos funções à uma seqüência através do método Chain.chain, passando como parâmetro a função a ser adicionada à seqüência.

var myChain = new Chain();
myChain.chain(function() {
	alert("Primeira Função");
});
myChain.chain(function() {
	alert("Segunda Função");
});

O método Chain.callChain é quem chama a próxima função da seqüência. Quando o método é executado, a função é executada e então removida da seqüência.

myChain.callChain(); // "Primeira Função"
myChain.callChain(); // "Segunda Função"

Caso necessário, podemos limpar todas as funções de nossa seqüência através do método Chain.clearChain.

Implementando em Classes

Obviamente, Chain é muito mais útil quando usado dentro de uma função. Classes não deixam de ser uma função, e possuem muito mais poder. É nelas onde a utilização de Chain acontece na própria Mootools.

Após a implementação de Chain em alguma classe, podemos utilizar seus métodos.

var myClass = new Class({
	"initialize": function() {},
	"execute": function() {
		this.callChain();
		if(this.chains.length > 0) this.execute();
	}
});
myClass.implement(new Chain);
var myChain = new myClass();
myChain.chain(function() {
	alert("Primeira Função");
});
myChain.chain(function() {
	alert("Segunda Função");
});
myChain.execute();

Como no exemplo acima, também podemos utilizar a variável Chain.chains, que é o array que contém todas as funções na seqüência. Não é necessário se preocupar muito com ele, já que a classe faz todo o necessário. O utilizei acima somente para não criar um loop infinito.

Essa classe também já é implementada na utilização de efeitos, para encadear vários efeitos subseqüentes. Também muito útil para se criar uma fila de requisições Ajax, ou algo do gênero. Vou ficando por aqui, até!

10 de fevereiro, 2008

Firefox, Event Listeners e Return

Quem já não perdeu alguns fios de cabelo usando return false em um Event Listener no Firefox? Por mais que se tente, o evento continua com seu comportamento padrão. Nesse post vou mostrar a “grande” solução.

De acordo com a especificação do ECMAScript, Event Listeners (definidos através da famosa addEvent, para execvent, para executar funções no disparo de eventos) não possuem valores de retorno, ou seja, return false não faz efeito algum (pelo menos no Firefox, que segue esse padrão).

A solução é simples: a função não possui valor de retorno, mas recebe como parâmetro um objeto Event. Através dele, podemos usar Event.preventDefault para impedir que o comportamento padrão seja executado. Veja o código:

addEvent(myForm, "submit", function(e) {
		if(e && e.preventDefault) e.preventDefault();
		else return false; // JavaScript à moda IE
	})

Simples, não? Espero que possa ajudar, já que também tive esse pequeno problema. Até o próximo post!

07 de fevereiro, 2008

Cookies em JSON

Cookies!

Através da serialização de dados com JSON, podemos tornar muito mais fácil a utilização de cookies em JavaScript. Além de tornar mais fácil a leitura/gravação dos dados, podemos eliminar a necessidade de vários cookies para o mesmo site.

Com essa idéia na cabeça há tempos, e aproveitando que continuo bem posicionado no Google em relação a Cookies em JavaScript, iniciei minha implementação, que terminou no código que vou mostrar.

Antes de começar…

Antes de começar, será necessário que utilizemos um parser JSON. Você pode codificar sua própria solução, ou utilizar a biblioteca JSON do JSON.org. Eu optei pela segunda opção. Isso não quer dizer que é necessário você fazer o mesmo, podendo até utilizar outra biblioteca como base.

O Código

Download JSON Cookie

Você pode fazer o download do JSON Cookie em sua página de testes, ou clicando no botão abaixo. Você também encontra a opção de incluir ou não o parser JSON, caso você deseje utilizar uma solução externa.

Utilização

Esse novo código utiliza uma sintaxe bem parecida com o antigo JGCookie, modifiquei apenas os atributos data (logicamente, para poder tratar objetos) e expires, e adicionei path.

Para definir um novo cookie, utilizamos o método JsonCookie.set. É necessário definir o nome do cookie (primeiro parâmetro) e seu conteúdo (um objeto JSON, como segundo parâmetro).

var userData = { "user": "JulioGreff", "password": "abcdefghi" };
	JsonCookie.set("userData", userData); // Gravando o Cookie
	

Também é possível definir a data de expiração e caminho (path) do cookie:

JsonCookie.set("userData", userData, new Date(2008,11,30), "/admin");
	

Para ler o cookie, basta chamar o método JsonCookie.get com o nome do cookie.

alert(JsonCookie.get("userData").user); // "JulioGreff"
	

Corrigindo um bug do JGCookie, agora também é possível apagar os cookies, através do método JsonCookie.unset, com o nome do cookie.

JsonCookie.unset("userData");
	alert(JsonCookie.get("userData")); // false
	

Caso você tenha alguma dúvida, sugestão ou correção, comente!

27 de janeiro, 2008