Você sabia?
JavaScript pode ter membros privados usando closures. More...

Membros privados no JavaScript

Posted: fevereiro 10th, 2010 | Author: diegoquinteiro | Filed under: Uncategorized | No Comments »


A maior conquista da orientação a objetos é o encapsulamento. Poder usar uma unidade de software sem precisar conhecer sua implementação é o que permite nosso limitado cérebro construir e manter aplicações complexas.

Um simples exemplo em Java:

public class Ponto {
    private double x;
    private double y;
    public Ponto (double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double distanciaDaOrigem () {
        return Math.sqrt(x * x + y * y);
    }
}

Quem usar a classe Ponto não precisa saber como a posição é guardada, ou como a distância da origem é calculada. A implementação está encapsulada. Encapsulamentos aumentam o nível de abstração do código e o torna mais simples.

Para podermos encapsular de forma eficiente, precisamos de membro privados, ou seja, variáveis que não possam ser alteradas ou lidas diretamente de fora da nossa unidade de software.

Nas linguagens tradicionais isso é feito pelo controle de acesso. Em Java usamos a palavra private para não permitir que uma variável seja acessada de fora de sua classe. Mas como fazer isso em JavaScript, se não há nem classes?

É na verdade bem simples. Variáveis declaradas dentro de uma função ficam restritas ao escopo desta função. Como visto no ultimo post sobre closures, as funções filhas tem acesso ao escopo da função pai. Agora é só juntar os pontos. Veja o exemplo:

var Ponto = function (x, y) {
    this.distanciaDaOrigem = function () {
        return Math.sqrt(x * x + y * y);
    }
}

var ponto = new Ponto(3, 4);
alert(ponto.distanciaDaOrigem()); // alerta "5"

alert(ponto.x); // alerta "undefined"
alert(ponto.y); // alerta "undefined"

Os membros x e y são privados. Elas são variáveis locais do construtor e fora dele elas não existiriam. Quando função filha distanciaDaOrigem é atribuida ao objeto this e esse objeto é retornado, cria-se um closure e essa função terá acesso permanente às duas variáveis locais do construtor.

O custo disso é que a cada execução do construtor as funções filha são criadas novamente, já que estão definidas no seu corpo. Uma instância do método pra cada instância do objeto. Normalmente não é um problema, mas fiquem atentos ao caso de objetos com centenas ou milhares de instâncias.


Escopo e closures no JavaScript

Posted: janeiro 30th, 2010 | Author: diegoquinteiro | Filed under: Uncategorized | Tags: , | 3 Comments »


Conheça o escopo das suas variáveis. Em JavaScript só funções definem escopos e só variáveis declaradas explicitamente usando var ficam restritas ao escopo, as demais são globais. Adicionalmente, todos os parâmetros de uma função são variáveis locais. Preste atenção no seguinte exemplo:

function exemplo (arg) {
	var variavelLocal = 0;
	if (arg > 0) {
		var mesmoEscopo = arg + 1;
	}
	variavelLocal = mesmoEscopo;
	alert(variavelLocal);
	variavelGlobal = arg;
}

exemplo(1); // Alerta "2"

alert(variavelLocal); // Alerta "undefined"

alert(mesmoEscopo); // Alerta "undefined"

alert(variavelGlobal); // Alerta "1"

Note que a variável mesmoEscopo pode ser atribuída à variavelLocal fora do bloco if em que foi definida. O bloco não define um escopo, apenas a função.

O JavaScript sempre define um objeto global, que nos navegadores é o objeto window. Todas as variáveis globais são na verdade propriedades desse objeto, de forma que:

exemplo(0);

window.exemplo(1); // funções também são variáveis

alert(variavelGlobal); // Alerta "1"

alert(window.variavelGlobal); // Alerta "1" também

alert(variavelGlobal === window.variavelGlobal); // Alerta "true"

Closures

Funções em JavaScript são objetos de primeira classe. Isso significa que você pode passar uma função por parâmetro, atribuir uma função a uma variável e até usar uma função como retorno de outra função. A declaração function nomeDaFuncao(arg1, arg2, …) { … corpo … } tem exatamente o mesmo efeito que var nomeDaFuncao = function (arg1, arg2, …) { … corpo … };.

As funções declaradas dentro de outra função sempre tem acesso às variáveis da função pai. Por exemplo:

var funcaoPai = function (valor) {
    var variavelDaFuncaoPai = valor;
    var funcaoFilha = function () {
        alert(variavelDaFuncaoPai);
    }
    funcaoFilha();
}

funcaoPai(1); // Alerta "1";

Algo curioso acontece quando guardamos uma referência pra função filha depois do fim da execução da função pai. Veja:

var funcaoPai = function (valor) {
    var variavelDaFuncaoPai = valor;
    var funcaoFilha = function () {
        alert(variavelDaFuncaoPai);
    }
    return funcaoFilha;
}

var filha1 = funcaoPai(1);
var filha2 = funcaoPai(2);

filha1(); // Alerta "1"!
filha2(); // Alerta "2"!

Fazendo isso criamos um fechamento ou closure.

As variáveis locais criadas na execução de funcaoPai não foram destruídas ao fim de sua execução. A referência pra função filha foi retornada e guardada, e ela sempre terá acesso à variavelDaFuncaoPai e a valor, ambas variáveis locais de funcaoPai.

Esse recurso é muito poderoso e deve ser explorado, como veremos nos próximos posts.


Construtores em JavaScript

Posted: janeiro 30th, 2010 | Author: diegoquinteiro | Filed under: javascript | Tags: , | 1 Comment »


Fato: JavaScript é uma linguagem orientada a objetos.

Sendo assim, você pode perguntar: onde estão as malditas classes?

A verdade é que não há classes. Objetos em JavaScript são maleáveis: propriedades e métodos são adicionados, modificados ou removidos de cada objeto individualmente em tempo de execução. É um modo diferente de programar orientado a objetos, mas ainda assim muito poderoso.

O operador new

Para dominar a orientação a objetos em JavaScript é preciso entender com clareza o funcionamento operador new. Esse operador serve para criar e inicializar um novo objeto usando uma função, chamada “função construtora”. Por exemplo:

function Pessoa(nome) {
	this.nome = nome;
}

var diego = new Pessoa("Diego");
alert(diego.nome); // Alerta "Diego"

var gabriel = new Pessoa("Gabriel");
alert(gabriel.nome); // Alerta "Gabriel"

Note que diego e gabriel não são objetos da classe Pessoa. Não existe uma classe Pessoa, apenas uma funçao Pessoa. Lembre-se: não há classes em JavaScript!

Quando o new é usado, um objeto vazio é passado para a função construtora na referência this. A função então pode adicionar membros a esse objeto usando atribuição, como em this.nome = nome;.

A não ser que a função retorne explicitamente um objeto não-nulo, o objeto this será retornado e ganhará uma propriedade constructor com uma referencia para a função construtora utilizada.

Parece muito com o processo de criar uma instância de uma classe, e é tentador chamar Pessoa de classe. Seria mesmo uma classe se não pudessemos alterar o objeto livremente depois de criado. Em JavaScript não é assim: um objeto criado por um construtor pode ser livremente alterado e transformado em um objeto totalmente diferente a qualquer momento.

Para efeito didático, o operador new do JavaScript pode ser definido como a seguinte função:

var operadorNew = function (construtor, args) {

	var objeto = {}, // cria um objeto vazio
	    retorno;

	// executa a função usando passando o objeto como this
	retorno = construtor.apply(objeto, args);

	if ((typeof(retorno) !== "object" &&
	    typeof(retorno) !== "function") ||
	    retorno === null) {

		/*
		 * se o retorno não for um objeto, ou for nulo,
		 * retorna 'this'
		 */
		retorno = objeto;
		retorno.constructor = construtor;
	}

	return retorno;
}

De forma que a chamada…

var objeto = new Construtor(arg1, arg2, ...);

… seja equivalente a

var objeto = operadorNew(Construtor, [arg1, arg2, ...]);

… para qualquer função Construtor implementada em JavaScript.

PS: Essa função não funciona para os construtores dos tipos primitivos (String, Array etc). Esses tipos são implementados no navegador e não em código JavaScript.

Analizando a função operadorNew que definimos, concluí-se que o operador new é apenas um atalho. Podemos sempre criar objetos a partir da notação literal e adicionar membros a ele a qualquer momento, sem usar um construtor para isso.

Considere o seguinte trecho:

var diego1 = new Pessoa("Diego");

var diego2 = {}
diego2.nome = "Diego";
diego2.constructor = Pessoa;

var diego3 = {constructor: Pessoa, nome: "Diego"};

var diego4 = operadorNew(Pessoa, ["Diego"]);

Os objetos diego1, diego2, diego3 e diego4 criados são todos iguais!

Portanto não se confunda: objetos e construtores sim, classes não.

Esse é o modo de ser do JavaScript ;D


Getting Real (Caindo na Real): É melhor remediar que prevenir!

Posted: julho 2nd, 2008 | Author: diegoquinteiro | Filed under: Sem Categoria, metodologia | Tags: , , | 5 Comments »

As mensagens que o livro Getting Real quer passar são das mais incomuns. Alguns dos conceitos defendidos pelo livro dos gurus da 37signals, criadores do Ruby on Rails, parecem contrariar tudo o que aprendemos:

  • Caia na real!
  • Faça menos que sua concorrência e seja ágil para mudar de rumo.
  • Jogue fora sua bola de cristal: não perca tempo hoje resolvendo problemas de amanhã – você já tem problemas suficientes hoje para se preocupar.
  • Seu projeto funcionando deve ser sua melhor e possivelmente única documentação. Não crie documentos que não viram realidade.
  • Reuniões devem ser evitadas. Quando inevitáveis, coloque o alarme do celular para 30 minutos: quando tocar, acabou a reunião.
  • Lembre-se: erros ocorrerão. Não se preocupe: é um sistema para web e não uma cirurgia de cérebro.

É uma abordagem um tanto chocante que pode deixar alguns contrariados, mas que certamente acrescentará muito a todos.

O livro está disponível gratuitamente na íntegra, traduzido para o português como “Caindo na Real”.

Leia, é grátis!


Mapeamento Objeto-Relacional (ORM)

Posted: julho 1st, 2008 | Author: diegoquinteiro | Filed under: PHP | Tags: , , | 1 Comment »
Com o lançamento do PHP5, as esperanças de utilizar as técnicas da programação orientada à objetos (POO) no desenvolvimento de sistemas web cresceu para os adeptos da linguagem . A versão anterior do PHP já possuia recursos de orientação à objetos, mas estes eram muito limitados e alguns problemas chegavam a desencorajar seu uso. O PHP5 veio com um novo motor, o Zend Engine 2, que corrigiu esses problemas e suporta a maioria dos recursos esperados de uma linguagem orientada à objetos, como interfaces, métodos abstratos, herança, polimorfismo, dentre outros.

Apesar das linguagens modernas possuirem recursos da POO, os sistemas de gerenciamento de bases de dados (SGBD) são quase todos baseadas no modelo relacional, onde temos tabelas ao invés de objetos. E na maioria dos casos o que queremos é justamente guardar nossos objetos na base dados. Guardar instâncias de objetos em registros de tabelas é o objetivo das técnicas de mapeamento objeto-relacional (em inglês object-relational mapping, ORM).

Existem muitas diferenças entre os modelos relacional e orientado à objeto e a mais incômoda delas para quem desenvolve sistemas web é que tabelas não suportam herança.

Considere o seguinte exemplo: temos três classes, sendo uma delas abstrata e as outras duas concretas e extendendo a primeira. Mais palpável ainda: considere uma classe abstrata Pessoa e as classes concretas Fornecedor e Cliente que são filhas da classe Pessoa.

Como guardar essas informações no banco de dados?

Uma tabela por classe contreta

A solução mais imediata seria criar 2 tabelas, da seguinte forma:

Essa solução mais simples ignora completamente a hierarquia de classes e faz uma tabela por classe concreta sem relacionamentos.

  • Prós:
    • Todos os dados de um objeto estão em uma só tabela, facilitando a manipulação.
    • Buscas serão executadas de forma rápida.
    • Não há campos nulos.
  • Contras
    • Dificulta alterações na classe pai, pois os campos estão duplicados (ou triplicados, quadruplicados…).
    • Pode ser um grande problema se houver alguma classe com referência para a classe pai (veja próximo exemplo).

Em alguns casos talvez essa seja uma boa solução. Não é o caso se houver uma referência para a classe pai, como por exemplo:

UML - Referência à classe pai

Nesse caso a nossa primeira solução falha, pois não existe uma forma de relacionar Mensagem com Pessoa sem explicitar se é um Fornecedor ou um Cliente. Poderíamos claro, usar a chave estrangeira de todas as subclasses, mas isso seria um pesadelo no caso de muitos relacionamentos e/ou muitas subclasses. Precisamos de uma solução melhor.

Uma tabela por árvore de classes

Podemos utilizar 1 só tabela para todas as subclasses de Pessoa, solucionando o problema anterior:

Nesta solução temos 1 tabela para cada árvore de classes. O campo tipo irá determinar se o registro é um Cliente ou Fornecedor. O campo endereço ficará nulo no caso dos clientes e o campo telefone ficará nulo no caso dos fornecedores.

  • Prós:
    • Todas as informações de uma instância estão na mesma tabela, o que facilita a manipulação.
    • Permite referências à classe pai.
  • Contras:
    • Muitos campos nulos – pode ser muito deselegante para grandes árvores de classes.
    • Muitos registros na mesma tabela podem retardar as busca. Mesmo que indexemos o campo tipo teremos problemas, já que só é utilizado um índice por busca.
    • Registros muito grandes podem retardar as buscas.
    • Campo tipo extra.
    • Não permite referência específica às subclasses.

Note que essa solução falha se precisarmos ter uma referência para a subclasse. Se as mensagens, por exemplo, pudessem ser trocadas apenas entre clientes, não haveria como fazer essa restrição no banco de dados. E se nossa árvore de classes for grande a tabela se tornará um monstro com dezenas de campos nulos em cada registro. Precisamos ainda de uma solução melhor.

Uma tabela por classe

Podemos mapear cada classe para uma tabela, fazendo a representação da hereança por um relacionamento 1 para n:

Assim temos 1 tabela por classe, relacionando as classes de forma a mater a árvore e usando o campo tipo para indicar a subclasse. Na hora de buscar um registro, checa-se esse campo para saber qual é a classe e, logo, qual outra tabela consultar.

  • Prós:
    • Mantém as hierarquia das classes.
    • Não contém campos nulos.
    • Não quebra o encapsulamento de implementação da classe pai.
    • Permite fazer referências tanto a classe pai quanto às subclasses.
    • Registros pequenos, com poucos campos.
  • Contras:
    • Os atributos de um objeto estão espalhados em mais de uma tabela, tornando as buscas mais complexas.
    • As buscas podem ficar mais lentas pelo uso excessivo de joins.
    • Campo tipo extra.

Essa solução é mais completa, podendo ser utilizada em todas as situações. Além disso ela tem uma boa relação custo/benefício até para situações onde as outras soluções também são possíveis.

Conclusão

Recomendo utilizar a solução de uma tabela por classe sempre que possível. O motivo é simples: mesmo que as outras soluções sejam aplicáveis em algumas situações, essas situações podem mudar na próxima alteração, forçando a implementar uma tabela por classe . Além disso, a perda de desempenho pode ser bastante minimizada pela utilização correta de índices. Não otimize prematuramente sua aplicação!


Bordas arredondadas no CSS com uma forcinha do PHP

Posted: junho 29th, 2008 | Author: diegoquinteiro | Filed under: PHP, css | Tags: , , | 1 Comment »
Bordas arredondadas parecem nunca sair de moda. Para a infelicidade dos webdesigners, adicionar mais de uma imagem de fundo a um elemento e a propriedade “border-radius” são exclusividades da versão 3 do CSS, que ainda não é suportada pelo navegador da Microsoft e por isso seu uso está fora de cogitação.

Mas nem tudo está perdido: uma ótima técnica que utiliza CSS2, descrita no artigo “Even More Rounded Corners”, permite mostrar bordas arredondadas utilizando apenas uma imagem PNG com a caixa inteira e uma porção de declarações “background-position” para fatiar virtualmente a imagem. Como todas as soluções compatíveis com o IE, ela necessita de marcação extra no HTML para funcionar. Na verdade, ela é bem compacta, vejam:

<div class="dialog">
<div class="content">
<div class="t"></div>
<!-- Seu contéudo vai aqui --></div>
<div class="b">
<div></div>
</div>
</div>

Mesmo compacta ela ainda causa, em menor intensidade, os mesmos já conhecidos problemas das marcações extras:

  • Dificuldade de manutenção: é necessário reproduzir a estrutura cada vez que quisermos uma caixa arredondada.
  • Perda de legibilidade: seu conteúdo ficará escondido num mar de DIVs inúteis.
  • Quebra de encapsulamento: tags com propósito unicamente visual (DIVs vazias!) tornam o código menos semântico e intuitivo.

Não sendo possível diminuir mais a quantidade de porcaria marcação inútil servida para o usuário, podemos ao menos reduzir algumas linhas do nosso código fonte no servidor, utilizando o PHP.

A solução aqui apresentada permitirá escrever no fonte:

<div class="dialog">
<!-- Seu conteúdo vai aqui --></div>

E ter como resultado o código “sujo” da técnica do “Even More Rounded Corners” na saída. O truque é fazer com que o PHP injete as divs necessárias e sirva para o usuário a marcação extra sem sujar o fonte, melhorando legibilidade, facilitando a manutenção e evitando em certo nível a quebra de encapsulamento. Para executar a mágica vamos usar o controle de buffer de saída do PHP e a extensão DOM, para manipular o HTML.

Primeiro, vamos precisar adicionar o seguinte código no início da página, fazendo com que ele execute antes do script emitir qualquer saída:

<?
// Captura a saída em um buffer.
ob_start();
?>

Isso garantirá que toda a saída do PHP seja guardada em um buffer, ao invés de ser enviada diretamente ao user agent.

O próximo e último passo é pegar toda a saída e injetar a marcação extra. Isso deve ser feito, é claro, no fim do script, depois de qualquer saída ter sido gerada e, conseqüentemente, guardada no buffer:

<?
// Lê o buffer de saída para uma variável string.
$saida = ob_get_contents();

ob_end_clean();

// Defina aqui a classe a ser considerada
// para caixas arredondadas.
$classe = "dialog";

// Carrega o documento na classe DOM.
$dom = new DomDocument("1.0");

// Detecta a codificação da página.
if (
  mb_detect_encoding(
  $saida . 'a' , 'UTF-8, ISO-8859-1' )
  ==
  "UTF-8"
  )
{
  $saida = utf8_decode($saida);
}
$dom->loadHTML($saida);

// Seleciona todas as DIVs
$divs = $dom->getElementsByTagName("div");

foreach ($divs as $div) {

  // Filtra as DIVs da classe selecionada.
  if (strstr($div->getAttribute("class"), $classe)) {

    // Cria os nós adicionais
    $content_div = $dom->createElement("div");
    $content_div->setAttribute("class", "content");

    $t_div = $dom->createElement("div");
    $t_div->setAttribute("class", "t");

    $b_div = $dom->createElement("div");
    $b_div->setAttribute("class", "b");

    $extra_div = $dom->createElement("div");

    // Cria a árvore correta
    $b_div->appendChild($extra_div);
    $content_div->appendChild($t_div);

    // Importa o conteúdo do div a ser arredondado
    // para dentro de 'content'.
    while ($div->hasChildNodes()) {
      $content_div->appendChild(
      $div->removeChild($div->firstChild)
      );
    }

    // Adiciona tudo de volta à div.
    $div->appendChild($content_div);
    $div->appendChild($b_div);
  }
}

$resultado = $dom->saveHTML();

echo $resultado;

?>

É claro que você não irá copiar e colar esse texto em todas as suas páginas. Você pode usar dois includes (um no começo do documento e outro ao final), e ainda, se você usa algum sistema de templates, fazer esses includes apenas no arquivo do template.

Podemos adaptar essa técnica para várias outras situações onde necessitamos de marcação extra. Podemos ainda adaptá-la facilmente para rodar no lado do cliente, uma vez que as funções DOM do PHP e do javascript são praticamente idênticas. O único porém deste último método é que não podemos garantir que o user agent suporte e esteja com o javascript habilitado e é então sujeitos a falhas, motivo pelo qual escolhi o método server-side.

Agora ninguém mais tem desculpa para fazer cara feia quando tiver que implementar aquele design todo arredondado!


CSS Hacks ou comentários condicionais?

Posted: junho 29th, 2008 | Author: diegoquinteiro | Filed under: css | Tags: , | 2 Comments »
Meu xará Diego Eis escreveu um interessante artigo já há alguns meses em seu famoso blog do site Tableless, o qual recomendava o não uso de comentários condicionais e defendia o uso de CSS Hacks.

O argumento central do texto é que o uso de comentários condicionais duplicam o trabalho do desenvolvedor, pois é criado um arquivo CSS para os navegadores mais compatíveis com as especificações da W3C e outro(s) para os navegadores Internet Explorer. Dada a opção de usar dois arquivos, a tendência é criar uma folha de estilos muito diferente ou até mesmo independente para o navegador da Microsoft.

Mas ao dar uma passada de olho nas estatísticas de uso de navegadores deste mês notamos que cerca de 98% dos usuário utilizam Internet Explorer 6, Internet Explorer 7, Firefox, Opera ou Safari. Menos de 1% utilizam o Internet Explorer 5, conhecido pelo sua implementação problemática do CSS Box Model. Isto significa que se usarmos o Doctype Switch para deixar o IE6 em Strict Mode, atenderemos mais de 98% dos visitantes sem nos valermos do famigerado Box Model Hack! Sendo assim, não precisamos mais definir a altura/largura de cada elemento duas vezes, enxugando de forma decisiva o arquivo CSS exclusivo para os IEs.

Podemos em alguns casos eliminar completamente esse arquivo e ao mesmo tempo não usar nenhum hack, nivelando por baixo os recursos usados e tolerando algumas pequenas diferenças de renderização. Esse é melhor dos mundos, mas em layouts mais rígidos ou complexos e no uso de algumas técnicas mais sofisticadas isso infelizmente não é uma opção, e voltamos a ter que decidir entre hacks e comentários condicionais. Levando em conta o que já foi dito, faço uma avaliação dos prós e contras de cada solução:

Comentarios condicionais:

  • Prós:
    • Mantém o CSS original inalterado e válido.
    • Permite servir CSS customizado ou proprietário de forma segura e suportada pela Microsoft.
    • Pode-se criar um arquivo diferente para cada versão do IE.
    • Feitos para serem usados!
  • Contras:
    • Arquivo separado pode ser difícil de manter.
      • Não é trivial notar onde existe ou não uma correção para o IE ao ler o arquivo original.
      • Por vezes também não é imediata a compreensão do funcionamento da correção no arquivo customizado sem ler a definição no arquivo original.
    • Uma requisição HTTP a mais.
    • Adições no HTML que apesar de comentadas são grandes.
    • Horrível de implementar em definições do atributo style da tag.

CSS Hacks:

  • Prós:
    • Tudo no mesmo arquivo, é fácil ver onde as correções acontecem e ao que se aplicam.
    • Mais compacto, pode ser aplicado facilmente no atributo style.
    • Pode ser usado em definições do atributo style do javascript.
    • Em alguns casos são necessários para outros navegadores que não o Internet Explorer.
  • Contras:
    • Alterações no CSS original, possivelmente comprometendo a validação.
    • Método não suportado, pode criar problemas a cada atualização que a Microsoft liberar.
    • Hacks diferenciais (que afetam apenas uma versão) são deselegantes e deus sabe quanto deselegantes eles podem se tornar nas próximas versões do IE, ou mesmo se alguém será capaz de desenvolvê-los.
    • Baseados em bugs!

Atentando para cada um destes detalhes, faço minhas recomendações. Seguem passo a passo:

  1. Use o Doctype Switch para ativar o Strict Mode no IE6.
  2. Ignore o IE5 e anteriores.
  3. Procure diminuir ao máximo as diferenças entre as definições CSS dos navegadores. Tente eliminá-las completamente, utilizando um CSS Reset e simplificando o layout de forma que as pequenas diferenças de renderização não atrapalhem muito. Isso é fácil de fazer para o IE7, mas pode ser bastante difícil para o IE6, então não complique muito seu código para atingir esse objetivo e considere o próximo passo.
  4. Caso sobrem diferenças, separe-as em outro arquivo (use um nome sugestivo, ‘iefix.css’) utilizando os comentários condicionais. Não crie mais de um arquivo, trabalhe bastante no passo anterior para não precisar de um específico para o IE7.
  5. Utilize comentários no CSS original para indicar onde existem correções no arquivo ‘iefix.css’.
  6. Não utilize o atributo style. Se precisar alterar o estilo por javascript, utilize classes.
  7. Se notar que seu ‘iefix.css’ estiver crescendo muito, considere voltar ao passo 3. Se não houver mais onde enxugar e o tamanho do arquivo ainda for um problema, considere utilizar os Hacks, mas não utilize uma técnica híbrida: utilize CSS Hacks ou comentários condicionais – misturar os dois é a receita certa para dores de cabeça.

Espero ter ajudado (a colocar mais lenha na fogueira desta disussão)!


 
★Rooh.it
Ctrl+Shift+B to show Bookmarks Bar.

Drag to Bookmarks Bar

to get your own highlighter

Close