O que aprendi em javascript

Quando fico sem o que fazer, invento moda. Já tinha feitos aquelas brincadeiras todas com fractais e escrito o tutorial. E acabaram as coisas que eu consegui fazer (ou pelo menos que eu achava graça em fazer). Mas na última applet eu tinha “inovado”. Fiz um javascript para “chamar” a applet com os parâmetros que o usuário editasse (porque eu fiz isso ao invés de colocar botõezinhos na applet? sei-la, achei mais flexível desse jeito. A separação da interface (view, que nem no MVC, sabem?) e do modelo/controle ficava melhor. Era mais fácil dar manutenção só na interface, achei mais bacana.. não sei… sei que fiz :).

Nessas alturas eu empolguei. Resolvi portar minhas applets pra javascript, e fui atrás de técnicas para se desenhar em javascript. Daí vem a primeira dica:

Técnicas de desenho em javascript

Vou começar com a primeira técnica, mais tosca, mas mais portável. A teoria é simples: pra cada ponto na tela eu crio uma “div” de tamanho 1×1 que eu posiciono na coordenada correta e seleciono como cor de fundo a cor que eu quero “pintar” a tela. É super fácil, olhem o código abaixo:

// desenha um ponto da cor "color" na posição 
// (x,y) do elemento "where" (que pode ser 
// "document" ou uma div qualquer criada pelo
// usuário.
function plot(where, x, y, color) {
  node = document.createElement('div');
  var style = "position:absolute;" +
              "left:" + Math.round(x) + "px;" +
              "top:" + Math.round(y) +"px;" +
              "width:1px;height:1px;" +
              "background-color:" + color;
  node.setAttribute("style", style);
  where.appendChild(node);
}

Simples, fácil e prático. Mas com um probleminha: Performance! (ou desempenho, para os puristas do português). Essa solução é muito bacana pra se desenhar um outro quadradinho, quem sabe até uma casinha daquelas de jardim de infância.

Mas quando fui desenhar o Mandelbrot o bicho pegou! A coisa começava bem.. 2, 3 linhas vinham rapidamente. Aí chegava a 4ª linha e ficava devagar, a 5ª era quase impossível de esperar. A 6ª nunca chegou a acontecer! Cancelava sempre antes. Tentei diversas otimizações: criar as divs todas antes de mandar pra tela, temporizar a impressão para não travar o browser (vou falar disso em outra dica sobre multithread em javascript), e nada adiantava. A 6ª linha simplesmente nunca vinha!

Fui a caça de uma nova solução e descobri[1]: O Canvas! Era tudo que eu queria! Era um componente feito pra ser desenhado! E era rápido (mais rápido ainda se for no safari, mas eu descobri isso só depois). O código ficou assim:

// desenha um ponto da cor "color" na posição 
// (x,y) do elemento "where" (que precisa
// ser um objeto do tipo "canvas").
// por exemplo:
// var canvas = document.getElementById("canvas");
// plot(canvas, 10, 10, 'RED');
// vai imprimir um ponto vermelho
// na posição (10,10)
function plot(where, x, y, color) {
  ctx = where.getContext("2d");
  ctx.fillStyle = color;
  ctx.fillRect(x,y,1,1);
}

E como sempre, um novo problema: O diabo do IE não aceita esse tal canvas! ARGH! E novamente[2] achei soluções temporárias: explorercanvas. Uma lib do google que “imita” um canvas no IE. Rodou redondo, mas a performance no IE continua sofrível! Fazer o que? Quem usa esses browsers vagabundos tem mais de sofrer mesmo[3]!

Então parti pra próxima etapa em minha busca: Como não travar o browser enquanto o javascript esta lerdamente plotando um desenho no IE (e outros browsers). Pra isso acabei encontrando…

Multitarefa TOSCA em javascript

Pra quem não sabe, javascript não suporta threads, processos, ou qualquer outra forma de multitarefa (ou multithreading, pra quem prefere). Qualquer processamento mais longo TRAVA DE COM FORÇA o browser. Aí vem aquela mensagenzinha do Firefox (o IE não tem isso, shame on him again) perguntando se você quer interromper o script que está demorando demais! Era tosco demais um conjunto de mandelbrot que travasse o browser… Ainda mais com minhas intenções de incrementar ele com zoom e coisas do gênero! Então corri atrás de formas de interromeper o processamento e retomá-lo depois.

Comecei procurando: javascript sleep. Putz, quanto nego se contenta com busy waiting (espera ocupada, pros que abominam a língua patrícia)! A maioria das soluções envolvia busy waiting! As únicas que usavam um tal de window.setTimeout eram táo toscas que não conseguia ver como poderiam ser úteis (shame on me).

Continuei então a busca por javascript thread. Dessa vez achei de tudo! Tem um cara que implementou threads em javascript: Threading in JavaScript 1.7. Mas com um único problema: só funciona em javascript 1.7, que só roda em algumas versões específicas do Firefox.

Só que aí eu já tinha um ponto de partida! O segredo pra multithreading em javascript é simples: iterators!

Criando minhas funções sob a forma de iterators eu posso usar o window.setTimeout que eu tinha desprezado antes para rodar um próximo passo do iterator a cada intervalo de tempo. Bingo, eu tinha reinventado o “Thread.yield()” do java ou o “HandleEvents()” do VB (era do VB mesmo, ou era do Delphi? Lembro que era da época do windows 3.1 cachorro que não tinha threads). Meu código ficou assim:

// inicializa as variáveis a serem usadas.
// O desenho é calculado "offline" dentrode uma
// matriz bidimensional chamada plotBuff;
var plotBuff = mandelbrot();
var ploti = 0;
var plotj = 0;
var plotmaxi = 500;
var plotmaxj = 500;
 
// plota um único ponto da matriz na tela.
function plotNext() {
  ploti++;
  if (ploti >= plotmaxi) {
    ploti = 0;
    plotj++;
  }
  if (plotj >= plotmaxj) {
    return;
  }
  plot(where, ploti, plotj, plotBuff[ploti][plotj]);
}
// plota N elementos e agenda uma próxima execução para
// daqui a 10  milisegundos.
function plotNextN(n) {
  for (i = 0; i < n; i++) {
    plotNext();
  }
  window.setTimeout('plotNextN(' + n + ')', 10);
}

Tosco? MUITO! Mas é uma solução “razoável” para um ambiente que não suporta multitarefa.

Minha próxima dica não é relacionada com essa diretamente, mas a última dica, depois dela, é relacionada com ela e também com essa da multitarefa, então vou ter de colocar ela primeiro:

Orientação por objetos em javascript

Claro que eu não queria fazer um negócio tosco pra sempre, e no projeto seguinte (os fractais tipo IFS) eu resolvi encapsular todo o código. Não queria nada complicado não, só encapsulamento mesmo! Fui atrás de informações no labirinto de Faulkner[4]: object oriented javascript.

E claro, porque não imaginei isso antes? Nada de útil! Caramba, será que orientação por (a?) objetos virou sinônimo de herança? Sério, nada sobre como criar objetos, só sobre como implementar herança. Mas eu queria só o báaaaaaaaaaaasico! Herança não! Só encapsulamento mesmo! O básico, básico, básico!

Então fucei os códigos em javascript que eu tinha por aqui! Descobri o seguinte: Só não é mais tosco que em perl! Declarar um objeto em javascript é tosco assim:

  1. Cria-se uma “function” que é o construtor. Uma função comum, normal e tals…
  2. dentro dessa função você “xuxa” (isso é mineirismo pra “enfia”) propriedades em uma variável chamada “this”. Por exemplo: this.value = 1; this.method = function() { alert('Hello World"); };
  3. profit!

Ou seja,a declaração de uma classe mistura uma declaração de função, que é o construtor (e que tem de fazer toda a inicialização do objeto) com a declaração dos métodos dessa classe, que ficam como “inner functions” daquela função. Argh!!! TOSCO, TOSCO, TOSCO!!! Emfim, um exemplozinho pra vocês verem:

// Classe "Plotter" que desenha na tela de um canvas.
function Plotter(canvas, width, height, color) {
  this.width = width;
  this.height = height;
  this.canvas = canvas;
  this.context = canvas.getContext("2d");
  this.color = color;
 
  // essa tosqueira é um método, pode?
  this.plot = function(x, y) {
    this.context.fillStyle = this.color;
    this.context.fillRect(x,y,1,1);
  };
}

E foi nessas que eu esbarrei no último problema, que vai dar a última dica: a diacha da multitarefa tosca que eu fiz ali em cima não funciona com objetos!

Multitarefa dentro dos objetos

Aí que veio a boa! Minha antiga função plotNextN() parou de funcionar! Que diabos, ela estava assim:

function Fractal(canvas, width, height, color) {
 
  // ... bla bla bla ...
 
  this.plotNextN(n) = function(n) {
    for (i = 0; i < n; i++) {
      this.plotNext();
    }
    window.setTimeout('this.plotNextN(' + n + ')', 10);
  }
}

E que diabos, ela funcionava na primeira vez, mas não executava a segunda iteração. O setTimeout não funcionava nem a porrete! Claro né? Mais que óbvio. A primeira execução eu que chamava explicitamente o plotNextN(). O contexto do objeto tava certinho, tudo funcionava. A segunda “rodada” era chamada pelo setTimeout. Sem contexto do objeto, sem nada pra identificar o objeto,por sinal. Ele nem sabia quem diabos era “this”!

E de novo o labirinto de Faulkner me salvou! Achei uma solução (pior que perdi o link pra ela, nem posso dar os créditos ao coitado que me ajudou). É o seguinte: uma função não conhece o contexto do objeto onde ela está, mas ela conhece o contexto local (o contexto da função externa que define ela). Então basta copiar tudo que você quer (inclusive o “this”) para variáveis temporárias, dentro do contexto local, e criar uma função temporária que use elas pra chamar o objeto. Por fim, você usa o setTimeout com essa função temporária, e não com o método em si.

No final ficou assim:

function Fractal(canvas, width, height, color) {
 
  // ... bla bla bla ...
 
  this.plotNextN(n) = function(n) {
    for (i = 0; i < n; i++) {
      this.plotNext();
    }
    // cópia local das variáveis, incluindo o "this"
    var thisObj = this;
    var localN = n;
    // função temporária que chama essa função 
    // através das variáveis copiadas.
    var timeoutFunc = function () { thisObj.plotNextN(localN) };
    // setTimeout :)
    window.setTimeout(timeoutFunc, 10);
  }
}

Bom, a coisa já era tosca, tosco a mais, tosco a menos, se funciona eu to dentro!

Conclusão

Acho que a única conclusão razoável é: Javascript é um recurso muito, mas MUITO bacana mesmo! Mas fazer as coisas sofisticadas nele é uma tosqueira sem tamanho!

E daí? Eu sou tosco mas elegante mesmo!

Ah sim… tinha esquecido.. o resultado final está aqui: http://www.girino.org/frac/genericjsifs.html. E quem quiser baixar o fonte do javascript, está em: http://www.girino.org/frac/js/IFS.js

Notas

  1. ^ Thanks Rickbit.
  2. ^ Thanks Rickbit again!
  3. ^ não, não sou defensor ferrenho do Firefoz, mozilla, opera, Linux, open source, apple nem nada disso. Nem tenho muita coisa contra o IE, mas que deu uma raiva eu não conseguir fazer nada direito nele em javascript, isso deu!
  4. ^ By Muriloq

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.