Tutorial JavaScript Orientado à Objeto

Bem-vindo ao tutorial politicamente incorreto de JavaScript orientado à objeto.

Índice

A pressa passa e a merda fica Introdução Histórica (e lição de vida)

JavaScript foi criado e programado em 2 semanas pela Netscape para validar formulários dentro dos navegadores, sem a necessidade de recarregar a página. Aparentemente na época das conexões discadas medidas em BAUDS isso era muito importante (e com o 3G da tim tb ¬¬). Porém, este período curto para desenvolvimento da linguagem gerou um produto com diversas questões controversas. Seu nome foi puramente uma jogada de marketing, tentando se aproveitar da popularidade do Java, que na época era muito usado para fazer applets para páginas de internet. JavaScript teve várias versões após isso, sendo que a versão atual, padronizada como ES6, é bastante diferente da inicial.

Por enquanto, ela é a única linguagem de programação capaz de interagir com todos os navegadores de internet. Existe uma especificação em progresso que criará um formato binário capaz de se comunicar com JavaScript, e que poderá ser criado por outras linguagens. Mas ainda levará algum tempo para vermos isso acontecer. JavaScript também pode ser utilizado para programação server side, através do Node.js, mas não vou abordar isso por enquanto.

JavaScript já foi chamada de linguagem mais mal entendida do mundo. Grandes empresas como a Microsoft e o Google passaram bastante tempo desenvolvendo ferramentas para que não seja necessário escrever JavaScript. E o motivo de tudo isso... é que JavaScript é como espanhol.

Hay um hombre tarado con el...

Parece com português, mas significa uma coisa completamente diferente.

Há um homem careca com o casaco na mão correndo atrás do ônibus.

PS: Essas palavras se chamam falsos cognatos.

A sintaxe do JavaScript parece com C e derivados, mas significa coisas diferentes. Por exemplo, this faz coisas bem diferentes dos seus equivalentes em C#. É um falso cognato

Vou enfatizar justamente as diferenças ao longo desse tutorial. Mas antes cabe rapidamente rever um outro fato histórico, importante no desenvolvimento dessa linguagem.

A Netscape inicialmente não tinha intenção que a linguagem fosse padronizada ou aberta. Ela queria dominar o mercado, forçando as pessoas a fazerem páginas que só funcionavam no Netscape, e abriam zoadas no seu concorrente, o internet explorer. Esse episódio é conhecido como Browser Wars. O resultado disso foi uma grande divisão na linguagem, gerando incompatibilidades que temos que lidar até a versão 8 do Internet Explorer. Este foi o principal motivo do sucesso de bibliotecas com jQuery, que escondem estas incompatibilidades através de sua API.

Como os smartphones já nasceram com navegadores posteriores ao IE8, sites e aplicativos para estes dispositivos não precisam se preocupar com isso.

JavaScript un zero un ¡Hola Mundo!

Sei que vc é um programador fodástico e que não precisa que ninguém te ensine nada de programação. Então não vou mostrar nada além do estritamente necessário para explicar as diferenças de JavaScript em comparação com C e derivados. Ok?

A linguagem em si não é muito grande se você tirar toda velharia que ainda existe por causa de compatibilidade. E se você precisa suportar uma outra velharia chamada IE, suas incompatibilidades nesse caso são poucas. Mas, existe uma API exposta pelos navegadores, comumente utilizada em conjunto com JavaScript, chamada Document Object Model (DOM), que é bem maior e mais incompatível. Ele serve para que o JavaScript acesse os elementos de um arquivo html, xml ou svg. Mas vamos ver uma coisa de cada vez.

JavaScript pode existir dentro de um arquivo .js, .htm ou .svg

Arquivos .js contém apenas programação, ao passo que arquivos .htm e .svg misturam linguagens de marcação com JavaScript. Podemos usar o bloco de notas para criar e editar um arquivo .js.

O firefox tem uma ferramentinha bem útil pra testarmos pequenos blocos de JavaScript, sem precisar usar um arquivo .js externo. Ela se chama scratchpad.

Para o próximo exemplo, vamos usar um arquivo .htm bem simples para fazer nossos testes. Copie o trecho abaixo no bloco de notas de sua preferência, e salve como um arquivo .htm

index.htm - olá mundo

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Html mínimo aceitável</title> <meta charset='utf-8' /> <script src='tutorial.js'></script> </head> <body> </body> </html>

PS: Html também tem versões, que fazem com que a página pareça e se comporte de maneira diferente de versão para versão. A primeira linha do arquivo:

<!DOCTYPE html>

indica que estamos usando a versão 5 do html.

Depois cole o texto abaixo num arquivo novo, e salve com a extensão .js. Salve na mesma pasta que o arquivo .htm Note que os arquivos deste exemplo são salvos em utf-8.

tutorial.js - olá mundo

'use strict'; console.log('Utilizadores de alert terão seus dedos cortados fora.');

Abra o arquivo .htm no seu navegador. Se vc fez tudo certo, observe atentamente que não vai acontecer absolutamente porra nenhuma. Porém, se for IE 10-, vai gerar um erro! Começamos bem hein! Disse que não ia mostrar nada além do estritamente necessário para explicar as diferenças de JavaScript. E o propósito desse exemplo é explicar algumas coisas sobre o ambiente de execução da linguagem e das ferramentas de desenvolvimento dos navegadores.

Pra ver o resultado de nossa programação precisamos abrir as ferramentas de desenvolvimento, que todos os navegadores possuem (o Firefox possui duas, uma nativa e uma em forma de extensão). Para abrir faça:

Procure por uma aba chamada console. É para o console que as mensagens de debug vão. Recarregue a página e você deverá ver nossa mensagem instrutiva, inclusive no IE. As ferramentas de desenvolvimento também possibilitam inserir breakpoints no meio do código e avançar linha por linha, como outras IDEs.

Vamos agora explicar nosso exemplo inicial linha por linha.

'use strict';

A versão ES5 do JavaScript possui um modo, chamado modo estrito. Ao incluir 'use strict'; como a primeira linha do arquivo, você está habilitando este modo. Ele garante compatibilidade futura com novas versões da linguagem, além de reduzir as chances de introduzir erros na programação. Este modo pode ser habilitado para o arquivo inteiro, ou apenas para determinadas funções. Veremos esta segunda forma mais adiante.

console.log('Utilizadores de alert terão seus dedos cortados fora.');

console é um objeto, mas não pertence à linguagem. Ele faz parte da API do navegador, o DOM. Na prática isso significa que nem sempre ele está disponível para nós utilizarmos. Cada navegador expõe métodos diferentes deste objeto, e os interpreta de maneiras diversas. O método log existe em todos eles, e possui inúmeras vantagens sobre métodos anteriores de debug.

Note que não existe um método main. Uma página pode conter diversos blocos separados de JavaScript, portanto não existe o conceito do ponto único de entrada do seu código. Assim que o navegador identifica um trecho de código JavaScript, ele o interpreta e executa linha por linha (o que efetivamente acontece é um pouco mais sofisticado, mas vou deixar este assunto por aqui). Se o mesmo arquivo for inserido duas vezes, ele será executado duas vezes. Não existem include guards em JavaScript, como em C ou PHP.(#ifndef #define #endif, include_once...).

Em .net, existe algo similar em funcionalidade aos include guards. O método RegisterStartupScript, recebe uma id ao registrar um script. Se for registrado um novo script com a mesma id, o script anterior é descartado.

Como declarar uma variável Sério mesmo, como declarar uma variável!

Existem duas maneiras de declarar uma variável em JavaScript. A maneira mais antiga, que utiliza a palavra-chave var, e tem várias pegadinhas que veremos mais para frente. E a maneira mais nova, com a palavra-chave let, que se comporta de maneira mais similar à que estamos acostumados em outras linguagens. Existe também a possibilidade de declararmos constantes com const.

Declaramos uma constante com a palavra-chave const, seguida pelo nome da constante, e obrigatoriamente o sinal de igual e um valor a ser atribuído à constante.

declaração de constantes

'use strict'; const naoPodeMudar = 'o valor'; const vaiLancarErro; // é obrigatório atribuir o valor às constantes assim que as declaramos

Declaramos uma variável com a palavra-chave let ou com var, seguidas do nome da variável. Opcionalmente atribuímos um valor para ela. Se não atribuirmos, ela terá o valor padrão undefined.

declaração de variáveis

'use strict'; let naoInicializada; // possui valor undefined let dica = 'JavaScript não é "fortemente tipado"'; var naoInicializada2; // possui valor undefined var dica2 = 'Use "let" ao invés de "var"';

Não declaramos o tipo da variável, pois JavaScript não proíbe que o tipo dela mude (não é fortemente tipada). O tipo é inferido através do valor atualmente atribuído à variável. Note porém que se mantivermos o mesmo tipo da variável ao longo da execução de nosso código, o compilador consegue aplicar otimizações de performance. Se necessário, é possível verificar de maneira limitada o tipo da variável usando o operador typeof ou outros métodos como Array.isArray.

A lista completa dos tipos das variáveis pode ser vista no MDN, que é o portal de desenvolvimento da Mozilla. Vamos ver apenas os tipos que diferem de alguma maneira de seus primos em outras linguagens.

Note que podemos chamar métodos de instâncias através das literais como veremos em alguns exemplos.

Conversão de tipos e falsy values

Além de não proibir que o tipo das variáveis mude ao longo da execução do código, JavaScript converte tipos automaticamente em true e false quando necessário. Isso nos proporciona uma sintaxe reduzida para verificar estes valores e a existência de variáveis.

Os valores que são convertidos para false são chamados de falsy. são eles '' (string vazia), 0, null, undefined e NaN. Todos os demais são convertidos para true.

var objeto = {}; // a verificação reduzida if (objeto) { // } // equivale a verificação if (objeto === true) { // } // e ainda à verificação if (objeto !== false && objeto !== '' && objeto !== 0 && objeto !== null && objeto !== undefined && objeto !== NaN) { // }

Note que existem dois comparadores de igualdade em JavaScript. == e ===, assim como seus opostos != e !==. A primeira forma converte automaticamente os tipos, enquanto que a outra não.

Null e nullable

Como podemos possivelmente mudar livremente os tipos das varáveis que declaramos (embora não seja recomendado), não existe uma sintaxe especial para indicar que as variáveis podem ser nullable. Basta não atribuir valor nenhum, e nesse caso a variável terá o valor especial undefined. Ou atribuir o valor null a elas. Em algumas linguagens existe um operador específico para lidar com tipos nulos, chamado null coalescing operator (geralmente representado como ?? ou ainda uma forma abreviada do operador ?:, também chamado de operador Elvis). Em JavaScript, quem faz as vezes desse operador é o OR lógico ||.

TODO mostrar em outras linguagens quando pode ser nullable

tipos null com let

'use strict'; let k; // undefined let l = null; // null // || funciona como null coalescing operator // isso quer dizer que se k for undefined ou null, m recebe o valor 2 ao invés de k // o mesmo vale para n, que recebe o valor 2 se l for undefined ou null let m = k || 2; // 2 let n = l || 2; // 2 // existem outros valores que podem ser entendidos como null, como mencionei anteriormente no tópico falsy values let o = ''; let p = o || 2; // 2, pois '' é falsy

Namespaces

Essa funcionalidade, juntamente com var, costumava ser utilizada para simular namespaces, já que JavaScript não os possui nativamente. Note que atualmente a utilização de um padrão de módulos chamados AMD é mais recomendada por motivos de uso de memória. Existe também uma expecificação para criação de módulos usando as palavras import e export, que ainda não foi implementada em nenhum navegador.

tipos null com var

'use strict'; var a = 1; var b = null; // var permite que se declare a mesma variável mais de uma vez // então se a ou b já existirem, utilizamos o valor das variáveis existentes // se não, atribuímos o valor 2 var a = a || 2; // 1 var b = b || 2; // 2

Num mesmo arquivo a utilidade disso não parece muito grande, mas em dois arquivos, este padrão se mostrava mais atrativo.

sanfona.js - namespaces

'use strict' // namespace widgets var widgets = widgets || {}; // garantimos que widgets exista antes de adicionarmos propriedades a ele // namespace sanfona widgets.sanfona = {};

calendario.js - namespaces

'use strict' // namespace widgets var widgets = widgets || {}; // garantimos que widgets exista antes de adicionarmos propriedades a ele // namespace calendario widgets.calendario = {};
var console = console || {}; // cria um objeto somente se console não existir

Vimos anteriormente que utilizando funções anônimas autoexecutáveis isolamos um escopo. Porém, não conseguimos acessá-lo de fora. Para tanto, precisamos simular um namespace. Aproveitamos que JavaScript permite adicionar dinamicamente propriedades em objetos para fazer isso.

simulação de namespaces

var namespace = namespace || {}; namespace.subnamespace = namespace.subnamespace || {}; namespace.subnamespace.meuObjeto = { metodo: function() { // } }; (function() { // "import" var meuObjeto = namespace.subnamespace.meuObjeto; meuObjeto.metodo(); })();

O primeiro nivel do namescape ocupa o espaço global, que como já vimos, é compartilhado por todos os arquivos e blocos de script da página. Portanto, podemos acrescentar mais coisas posteriormente ao mesmo namespace em um arquivo diferente.

simulação de namespaces, outro arquivo

var namespace = namespace || {}; namespace.subnamespace = namespace.subnamespace || {}; namespace.subnamespace.meuOutroObjeto = { metodo: function() { // } }; (function() { // "import" var meuObjeto = namespace.subnamespace.meuObjeto; var meuOutroObjeto = namespace.subnamespace.meuOutroObjeto; meuObjeto.metodo(); meuOutroObjeto.metodo(); })();

Number

É o único tipo numérico existente em JavaScript. Sempre é um double (double-precision 64-bit binary format IEEE 754-2008) independente da notação que usamos. Pode também conter os valores especiais NaN, infinito positivo (Number.POSITIVE_INFINITY) e infinito negativo (Number.NEGATIVE_INFINITY).

formatos números

'use strict'; let i = 1; // é um double! let area = 4.527; // também é um double const negativo = -2; // sem notação especial para números negativos let abreviado = .7; // 0.7 // notação científica usa e ou E let c1 = 1e0; // 1 × 10^0 = 1 × 1 = 1 let c2 = 1E-1; // 1 × 10^-1 = 1 × 0.1 = 0.1 let c3 = -.7e-2; // -0.7 × 10^-2 = -0.7 × 0.01 = -0.007 // notação binária usa b ou B // também são double let binario = 0b01; let bipolarOtimista = 0B11010; let bipolarNegativo = -0b11010; // notação octal usa o (letra ó minúsculo) ou O (letra ó maiúsculo) // vc adivinhou, eles também são double let octal = 0o01234567; let entreMeusDedos = 0O12345670; // notação hexadecimal usa x ou X // isso mesmo, você é quase um expert // os números que utilizam essa notação também são double let hexadecimal = 0x0123456789ABCDEF; let cor = 0Xff0000; // NOTAÇÃO DECIMAL // =============== // JavaScript permite que os números // sejam precedidos por 0 sem // que isso influencie em seu valor int i1 = 10; int p1 = 01; let impossible1 = 1 / 0; /// Infinity let impossible2 = Math.sqrt(-1); // NaN let impossible3 = Number.parseInt('wrong format'); // NaN area.toFixed(1); // retorna '4.5' 1.9.toFixed(); // retorna '2'

Números não sofrem overflow nem underflow, mas não dão aviso se você tentar passar dos limites.

formatos números

'use strict'; let i = Number.MAX_VALUE; let j = Number.MAX_VALUE + 1; // não dá aviso if (i === j) { console.log('j foi limitado'); } i = -i j = -j - 1; // não dá aviso if (i === j) { console.log('j foi limitado'); }

String

É um objeto imutável. Pode usar como delimitador aspas simples ou duplas dependendo da necessidade. Para quebrá-la em mais de uma linha, escapamos a quebra de linha com uma barra invertida. Podemos escapar caracteres especiais assim como em outras linguagens.

tipos de strings

'use strict'; let simples = 'A primeira faz "tchan"'; let dupla = "A segunda faz 'tchun'"; const iara = "mãe d'água"; const modoDificil = 'O\'Reilly'; let html = '<em class="especial">ênfase</em>'; let calvinHarris = 'how deep\nis your love'; // 2 linhas let pareceMasNaoE = 'começa e \ termina na mesma linha?'; // 1 linha apenas // várias linhas let josePauloPaes = 'Meu amor é simples, Dora,\n\ Como a água e o pão.\n\ \n\ Como o céu refletido\n\ Nas pupilas de um cão.'; let emoji = '😍'; let es5 = '\uD83D\uDE0D'; // code units / surrogates let es6 = '\u{1F60D}'; // code point // string é imutável, então seus métodos retornam novas instâncias let beeGees = calvinHarris.replace('love', 'looove'); // pq um tutorial de programação não é completo sem uma homenagem aos bee gees ;) 'Gritando'.toUpperCase(); // retorna 'GRITANDO'

Existem strings um pouco mais poderosas à partir da versão ES6, chamadas Template Strings. Elas permitem ser quebradas em mais de uma linha. Seu demilitador é a crase.

template strings

'use strict'; let duplaTemplate = `A primeira faz "tchan". A segunda faz 'tchun'`; // várias linhas let leminskiTemplate = `Merda é veneno. No entanto, não há nada que seja mais bonito que uma bela cagada. Cagam ricos, cagam pobres, cagam reis e cagam fadas. Não há merda que se compare à bosta da pessoa amada.`;

A representação interna das strings é UTF-16. Nas versões ES6+, podemos iterar pelos code points das strings, isto é, ler caractere por caractere independente do número de bytes que eles usam. Nas versões ES5-, apenas code units são suportadas. O site 2ality tem um artigo bem completo sobre codificação de strings e outro atualizado para ES6.

code points

'use strict'; const multibyte = 'd😍b'; for (let caractere of multibyte) { console.log(caractere); console.log(caractere.length); } // d // 1 // 😍 // 2 // b // 1

Interpolação de variáveis

O real motivo das template strings existirem é para facilitar a utilização de variáveis junto com strings.

strings simples e template strings com variáveis

'use strict'; let nome = 'Pafúncio'; let respostaSimples = 'Oi ' + nome + '. Já te ligo ok?'; let respostaTemplate = `Oi ${nome}. Já te ligo ok?`; // ambos geram Oi Pafúncio. Já te ligo ok?

Existe ainda um uso mais avançado de templates que veremos em na parte sobre funções.

Array

Mistura de Array e ArrayList<Object>, isto é, muda de tamanho conforme a necessidade, e pode armazenar tipos misturados. Podemos inicializar com um tamanho inicial por motivos de performance. Acessamos os elementos dela com o operador []. Note que não é feito nenhum tipo de restrição para acessar índices inexistentes na Array!

Assim como ganhamos performance ao manter sempre o mesmo tipo para uma variável, ganhamos performance ao armazenar sempre o mesmo tipo de variáveis numa Array.

arrays

'use strict'; let noticias = []; // array vazia console.log(noticias.length); // mostra 0 console.log(noticias[56]); // mostra undefined e continua normalmente // array é mutável, então seus métodos modificam a array noticias.push('notícia extremamente curta :P'); // insere um elemento ao final da Array noticias[5] = 'e outra reduzida também'; // insere um elemento no índice 5 noticias.push('ao vivo'); // insere um elemento no índice 6, que é o final da Array let noticias2 = new Array(10); // array vazia com espaço pré-alocado para 10 elementos. console.log(noticias2.length); // mostra 10 console.log(noticias2[4]); // mostra undefined, pois não atribuímos um valor para este índice console.log(noticias2[20]); // mostra undefined, pois não atribuímos um valor para este índice noticias2.push('vai no índice 11'); // os 10 primeiros são undefined let bagunca = [38, 'texto']; // perfeitamente legal, embora seja lento

A Array possui alguns métodos nativos úteis, como forEach, filter, map e reduce. Mas devido ao modelo de paralelismo da linguagem, creio que a otimização desses métodos não seja tão grande assim.

Os exemplos abaixo são um pouco mais complicados, então talvez você queira ler a parte sobre funções e objetos e voltar aqui depois se não entender alguma coisa.

bonus métodos array

'use strict'; // retorna uma nova array sem os elementos das posições pares // se lembre que o índice começa em 0, e não em 1 [21, 72, 3, 34, 55, 60].filter(function(element, index) { if(index % 2 === 0) { return true; } return false; }); // cria uma array literal [1, 2] // executa o callback em cada elemento da array .map(function(currentValue) { return currentValue + 1; }) // [2, 3] // executa o callback em cada elemento da array // e vai acumulando o resultado .reduce(function(previousValue, currentValue) { return previousValue + currentValue; }); // o retorno de reduce é 5

RegExp

Além do objeto RegExp, JavaScript possui expressões regulares em forma de literal, delimitadas por barras, e opcionalmente com flags após a barra final.

formatos de expressões regulares

'use strict'; const regex = /a+/gi; // expressão na forma literal com flags g (não parar no primeiro match) e i (case insensitive) const regex2 = new RegExp('a+', 'gi'); // mesma expressão na forma de objeto regex.test('abobrinha'); // retorna true /why don't you/.test('me now\?'); // retorna false

Function

Em JavaScript, métodos e funções sempre são objetos! Isso significa que elas possuem propriedades e métodos como o call por exemplo.

Nunca declaramos o tipo de retorno de uma função. Uma vez que JavaScript não proíbe que os tipos das variáveis mude, é permitido que a função retorne qualquer coisa, misture os tipos de retorno, etc. O número de parâmetros também não precisa ser declarado, ou seja, todas as funções em JavaScript são variádicas. Esta funcionalidade costumava ser usada para simular sobrecarga de função, juntamente com o objeto arguments que toda a função recebe implicitamente. Porém esta é uma prática não mais recomendada. Para a solução atual recomendada, veja operador rest.

Note que embora a chamada de funções da maneira descrita acima seja permitida, o compilador ainda pode lançar exceções ao tentar utilizar variáveis que não foram devidamente passadas ao método.

chamadas válidas de métodos

'use strict'; function comum() { console.log(arguments); // objeto arguments iniciado automaticamente, contendo os parâmetros utilizados na chamada } function normal(texto) { console.log(arguments); // objeto arguments iniciado automaticamente, contendo os parâmetros utilizados na chamada } function retornoMisturado(numero) { if (numero > 0) { return 'texto'; } return 25; } // chamadas perfeitamente válidas comum(); // arguments será [] comum('lala'); // arguments será ['lala'] comum('lala', 'lolo'); // arguments será ['lala', 'lolo'] normal('lolo'); // texto será 'lolo' e arguments será ['lolo'] // chamada válida normal(); // o parâmetro texto terá o valor undefined e arguments será [] normal.call(this, 'dadaísmo'); // o parâmetro texto terá o valor 'dadaísmo' e arguments será ['dadaísmo'] retornoMisturado(-1); // retorna 25 retornoMisturado(1); // retorna 'texto'

Como mencionei, a possibilidade de chamar funções com parâmetros diferentes dos definidos costumava ser usada para simular sobrecarga de função. Se você precisa dar manutenção em códigos antigos, saiba que em Javascript, os métodos são diferenciados pelo seu nome exclusivamente, e sua sobrecarga antigamente era feita da seguinte maneira:

sobrecarga de função

'use strict'; function efetuarRequisicaoAjax(endereco) { const url = endereco; // SINTAXE ANTIGA!!! NÃO COPIE!!! if (arguments[1]) { if (typeof arguments[1] === 'number') { // segundo parâmetro passado para a função url += '?pagina=' + arguments[1]; } else if (typeof arguments[1] === 'string') { url += '?token=' + arguments[1]; } } // SINTAXE ANTIGA!!! NÃO COPIE!!! if (arguments[2]) { url += '&pagina=' + arguments[2]; // terceiro parâmetro passado para a função } console.log(url); // vamos ver um exemplo real de ajax lá no final ;) } // SINTAXE ANTIGA!!! NÃO COPIE!!! efetuarRequisicaoAjax('webservicedagrecia.ashx'); efetuarRequisicaoAjax('webservicedagrecia.ashx', 2); efetuarRequisicaoAjax('webserviceseguro.ashx', '987dcb987d6b96a9d5ab'); efetuarRequisicaoAjax('webserviceseguro.ashx', '987dcb987d6b96a9d5ab', 2);

Como mencionei lá no começo, funções podem ser selecionadas individualmente para utilizar o modo estrito. Isso é útil nos casos em que precisamos dar manutenção em códigos antigos, ou quando inserimos código dentro de tags <script />, ao invés de em arquivos externos.

funções estritas

function funcaoNormal() { console.log(varNaoDeclarada); // mostra undefined em navegadores antigos, nos novos lança exceção } function funcaoEstrita() { 'use strict'; console.log(varNaoDeclarada); // lança exceção }

JavaScript suporta funções anônimas, ou expressões lambda. Expressões lambda são utilizados frequentemente em JavaScript, por isso é importante compreender o que são.

Expressões lambda são declarações de funções anônimas diretamente no meio do código. Basicamente em qualquer lugar onde podemos passar uma referência para uma função, podemos criar uma expressão lambda. Seguem exemplos em outras linguagens de expressões lambdas.

código C

// lambda em C // pegadinha do malandro! C não tem isso.

código Swift

// lambda em Swift superior({() -> TipoDoRetorno in // corpo da função })

código Objective-C

// lambda em Objective-C (OS X v10.6+ e iOS 4+) - chamado de Block [superior usando:^() { // corpo da função }];

código C#

// lambda em C# 4 - idêntico a JavaScript superior(() => { // corpo da função });

código Java

// lambda em Java 8 superior(() -> { // corpo da função });

código C++

// lambda em C++11 superior([] () { // corpo da função });

código Rust

// lambda em Rust superior(|| { // corpo da função });

código PHP

// lambda em PHP - idêntico a JavaScript superior(function() { // corpo da função });

E as duas formas possíveis em JavaScript.

código JavaScript - funções de ordem superior

'use strict'; // função normal function normal() { // } // função de ordem superior function superior(funcao) { funcao(); // executa a função passada } // função normal sendo passado como parâmetro - note que usamos apenas o nome, sem executar com () superior(normal); // expressão lambda sendo passada como parâmetro superior(function() { // }); // expressão lambda com nova sintaxe de flecha (ES6) sendo passada como parâmetro // dependendo do que a expressão lambda fizer, alguns caracteres podem ser omitidos superior(() => { // });

Funções definidas com a flecha gorda, possuem novas regras específicas para a palavra-chave this utilizada dentro de seu corpo. Vale conferir as regras na MDN.

Além de expressões lambda, JavaScript suporta um modo de declaração chamado expressão de função. Mais adiante veremos outro uso para este tipo de declaração.

expressões de função

'use strict'; // declaração de função function normal() { // } // expressão de função - tratamos a função como um valor const surpresa = function() { // }; // expressão de função const outraSurpresa = normal; normal(); surpresa(); outraSurpresa(); // expressão de função anônima - note os () em volta dela // veremos mais adiante outros usos para essa forma (function() { // });

Operadores rest e spread

Agora que conhecemos funções, strings e arrays, podemos ver um exemplo mais completo dessas partes. Existem dois operadores em JavaScript que são representados por ..., chamados rest e spread. A função deles é respectivamente transformar uma sequência de elementos em uma Array, e expandir uma Array em uma sequência de elementos. Como ambos usam ..., o que diferencia um do outro é o local de uso.

Num local onde faça sentido expandir a Array, teremos o operador spread.

operador spread

'use strict'; let comeco = [1, 2, 3]; let completo = [...comeco, 4, 5]; // [1, 2, 3, 4, 5] function tresArgumentos(primeiro, segundo, terceiro) { // } // expandindo argumentos passados tresArgumentos(...comeco); // primeiro = 1, segundo = 2, terceiro = 3

E portanto, num local onde faça mais sentido juntar os elementos, teremos o operador rest.

operador rest

'use strict'; let a, b, outros; [a, b] = [1, 2]; // a = 1, b = 2 [a, b, ...outros] = [1, 2, 3, 4, 5]; // a = 1, b = 2, outros = [3, 4, 5] // agrupando argumentos que se espera receber // ou funções variádicas function variosArgumentos(primeiro, segundo, ...outros) { // } variosArgumentos(1, 2, 3, 4, 5); // primeiro = 1, segundo = 2, outros = [3, 4, 5]

Este operador é o recomendado atualmente para a programação de sobrecarga de métodos, ao invés de utilizar o objeto legado arguments. Suas vantagens incluem a maior facilidade de misturar parâmetros nomeados e não nomeados, percorrer os parâmetros não nomeados como uma Array (ao passo que arguments é um objeto com propriedades numeradas) e servir exclusivamente para gerenciar parâmetros, sendo que arguments possui propriedades depreciadas como arguments.caller e arguments.callee.

Em linguagens mais estritas, os métodos são diferenciados pela sua assinatura, que é composta pelo nome e pela quantidade e tipos de parâmetros. Portanto, podemos declarar funções e métodos com nomes diferentes desde que seus parâmetros também sejam diferentes.

sobrecarga de função em C# / Java

// Sobrecarga de método em C# / Java public class Overloaded { public void skip() { /**/ } public void skip(int amount) { /**/ } }

sobrecarga de função em C++

// Sobrecarga de método em C++ public class Overloaded { public: void skip() { /**/ } void skip(int amount) { /**/ } }

Em JavaScript tal diferenciação não existe. As funções são diferenciadas exclusivamente pelo seu nome. Portanto, a maneira mais próxima de obtermos o que queremos, é utilizando outro recurso, também presente em diversas outras linguagens. Podemos permitir uma quantidade variável de parâmetros nas funções, e depois escolhermos o que fazer baseados nos tipos dos argumentos.

Das linguagens mais utilizadas atualmente que suportam esse recurso, C e Objective-C possuem uma sintaxe própria com macros. C++ usa templates, que são ligeiramente mais verbosos. As demais são bastante parecidas como podemos ver a seguir.

funções variádicas em Swift

// funções variádicas em Swift // argumentos do mesmo tipo func imprimirSimples(titulo titulo:String, paginas:Int...) { for pagina in paginas { print("Imprimindo página \(pagina) num total de \(paginas.count)") } } imprimirSimples(titulo: "resumo", paginas: 1, 2, 5) // argumentos de todos os tipos func imprimir(titulo titulo:String, paginas:Any...) { for valor in paginas { switch valor { case let pagina as Int: print("Imprimindo página \(pagina)") case let faixa as Range<Int>: print("Imprimindo páginas \(faixa.first!) até \(faixa.last!)") default: print("Imprimindo outra coisa") } } } imprimir(titulo: "resumo", paginas: 1, 2...5, "capítulo 2")

funções variádicas em C#

// funções variádicas em C# // a propósito, o estilo padrão de { e } em C# é que eles fiquem em linhas separadas // mas ia ocupar muito espaço desnecessário nesse exemplo // argumentos do mesmo tipo void ImprimirSimples(string titulo, params int[] paginas) { foreach (int pagina in paginas) { System.Console.WriteLine(@"Imprimindo pagina {0} num total de {1}", pagina, paginas.Length); } } ImprimirSimples(@"resumo", 1, 2, 5); // argumentos de todos os tipos void Imprimir(string titulo, params object[] paginas) { foreach (object valor in paginas) { if (valor is int) { System.Console.WriteLine(@"Imprimindo página {0}", valor); } else if (valor is IEnumerable<int>) { System.Console.WriteLine( @"Imprimindo páginas {0} até {1}", (valor as IEnumerable<int>).First(), (valor as IEnumerable<int>).Last() ); } else { System.Console.WriteLine(@"Imprimindo outra coisa"); } } } Imprimir(@"resumo", 1, Enumerable.Range(2, 4), @"capítulo 2");

funções variádicas em Java

// métodos variádicos em Java // algumas coisas foram omitidas propositalmente como main // argumentos do mesmo tipo void imprimirSimples(String titulo, int... paginas) { for (int pagina : paginas) { System.out.println(String.format("Imprimindo página %1", pagina)); } } imprimirSimples("resumo", 1, 2, 5); // argumentos de todos os tipos void imprimir(String titulo, Object... paginas) { for (Object valor : paginas) { if (valor instanceof Integer) { System.out.println(String.format("Imprimindo página %d", valor)); } else if (valor instanceof Range<Integer>) { System.out.println(String.format( "Imprimindo páginas %d até %d", ((Range<Integer>)valor).getMinimum(), ((Range<Integer>)valor).getMaximum() )); } else { System.out.println("Imprimindo outra coisa"); } } } imprimir("resumo", 1, Range.between(2, 5), "capítulo 2");

funções variádicas em PHP 5.6+

// funções variádicas em PHP // argumentos do mesmo tipo function imprimirSimples(string $titulo, int ...$paginas) { foreach ($paginas as $pagina) { echo "Imprimindo página $pagina\n"; } } imprimirSimples("resumo", 1, 2, 5); // argumentos de todos os tipos function imprimir(string $titulo, ...$valores) { foreach ($valores as $valor) { if (is_int($valor)) { echo "Imprimindo página $valor\n"; } else if (is_array($valor)) { echo 'Imprimindo páginas' . $valor[0] . ' até ' . $valor[count($valor) - 1] . "\n"; } else { echo "Imprimindo outra coisa\n"; } } } imprimir("resumo", 1, range(2, 5), "capítulo 2");

funções variádicas em C++

// funções variádicas em C++ // argumentos do mesmo tipo #include <iostream> template<typename... Tipos> void imprimirSimples(std::string titulo, Tipos... paginas) { const unsigned short int quantidadeDeParametros {sizeof...(paginas)}; // não pode criar uma array com 0 elementos if (quantidadeDeParametros == 0) { return; } // expande os argumentos int paginasArray[quantidadeDeParametros] {paginas...}; for (auto pagina : paginasArray) { std::cout << "Imprimindo página " << pagina << std::endl; } } imprimirSimples(1, 2, 5); // ou com macros não seguras estilo C // Objective-C tem que fazer igual #include <iostream> #include <cstdarg> void imprimirSimplesMacro(const std::string titulo, ...) { // va_list argumentos recebe o que vir depois do parâmetro titulo va_list argumentos; va_start(argumentos, titulo); int pagina; while (true) { pagina = va_arg(argumentos, int); if (pagina == -1) { break; } std::cout << "Imprimindo " << pagina << std::endl; } va_end(argumentos); } // preciso sempre passar -1 no final como terminador // se não dá overflow e lê pedaço de memória além do que deveria imprimirSimplesMacro("resumo", 1, 2, 5, -1); // argumentos de todos os tipos #include <iostream> #include <boost/range/irange.hpp> /// imprimirHelper template genérico template<typename T> void imprimirHelper(T pagina) { // poderia usar a linha abaixo para descobrir o tipo de T // porém, melhor usar o template especializado // if (std::is_same<T, int>::value) { /* ... */ } // optimizable... std::cout << "Imprimindo outra coisa" << std::endl; } /// imprimirHelper template especializado para int template<> void imprimirHelper<int>(int pagina) { std::cout << "Imprimindo página " << pagina << std::endl; } /// imprimirHelper template especializado para boost::irange template<> void imprimirHelper<boost::irange>(boost::irange faixa) { std::cout << "Imprimindo página " << std::begin(faixa) << " até " << std::end(faixa) << std::endl; } // foward declaration imprimir template?? Não estou certo da função disso :/ void imprimir(std::string titulo) {} // implementação imprimir template template<typename Tipo, typename... Tipos> void imprimir(std::string titulo, Tipo pagina, Tipos... paginas) { // executa o helper especializado e chama recursivamente a si mesmo imprimirHelper(pagina); imprimir(titulo, paginas...); } imprimir<int, boost::irange, std::string>("resumo", 1, boost::irange(2, 5), "capítulo 2");

A forma em JavaScript é bastante parecida com a maioria. Note que não existe nenhum auxilio nativo para criar ranges, como nos outros exemplos. Optei por usar um objeto simples apenas para demonstrar a sintaxe.

funções variádicas em JavaScript com operador rest operator

'use strict'; // funções variádicas em JavaScript com operador rest // argumentos do mesmo tipo function imprimirSimples(titulo, ...paginas) { for (let pagina of paginas) { console.log(`Imprimindo página ${pagina}`); } } imprimirSimples("resumo", 1, 2, 5); // argumentos de todos os tipos function imprimir(titulo, ...valores) { for (let valor of valores) { if (Number.isInteger(valor)) { console.log(`Imprimindo página ${valor}`); } else if ((valor.begin || valor.begin === 0) && valor.end ) { console.log(`Imprimindo páginas ${valor.begin} até ${valor.end}`); } else { console.log('Imprimindo outra coisa'); } } } imprimir("resumo", 1, {begin: 2, end: 5}, "capítulo 2");

O operador rest também é útil quando utilizado em conjunto com template strings, permitindo recursos mais avançados na manipulação do template.

misturando funções, template strings e arrays

'use strict'; function template(strings, ...values) { // as strings são recebidas nornalmente através do primeiro argumento strings[0]; // a soma de strings[1]; // com strings[2]; // resulta em strings[3]; // . // os valores são agrupados numa array através do operador rest values[0]; // 1 values[1]; // 2 values[2]; // 3, que é a soma de valor1 e valor2 // é possível retornar qualquer coisa, string, função, objeto, etc. } const valor1 = 1; const valor2 = 2; template+ `a soma de ${valor1} com ${valor2} resulta em ${valor1 + valor2}.`;

Como declarar objetos Se vc já achou as variáveis confusas...

Foram feitas várias tentativas até chegarmos a sintaxe atual para declararmos objetos. Espero que não adicionem nenhuma nova sinceramente. Se vc precisar, incluo também as antigas como referência.

Object

Já estivemos utilizando o objeto console, que o navegador nos fornece. Além disso, os tipos de variáveis que vimos também são objetos, pois conseguimos chamar métodos deles como toFixed ou filter. Ou seja, estivemos escrevendo JavaScript orientado à objeto desde o começo! Mais um motivo pra não usar alert.

código

console.log('console é um objeto. log é um método');

JavaScript é bastante diferente de outras linguagens quanto à criação de objetos novos. Vamos nos ater ao básico por enquanto. Declaramos um objeto usando {}. Sim, um objeto literal. Não é necessário criarmos uma definição de objeto de antemão (como uma class ou struct), criamos o objeto diretamente.

Acessamos as propriedades e métodos usando ponto, como fazemos com console. Por conveniência, também existe a possibilidade de acessá-las usando a notação de Array. Esta notação é útil quando queremos passar o nome do campo dinamicamente.

Propriedades pode ter atributosr writable, enumerable e configurable, indicando que podem ser escritas, que são mostradas em laços for in e seus atributos podem ser modificados ou não, respectivamente.

Da maneira abaixo, todas as propriedades e métodos são públicos. Se você mal pode esperar pra saber como definir campos privados, fique à vontade para ir e voltar.

declaração de objetos com propriedades e métodos públicos

'use strict'; let objetoVazio = {}; const retangulo = { // define os métodos do objeto area() { // nas versões ES5- tinha que escrever area: function() { return this.largura * this.altura; }, // define as propriedades e valores padrão largura: 1, altura: 1 }; // o objeto já está construído // já podemos chamar seus métodos e especificar valores para suas propriedades // todas as propriedades são writable, configurable e enumerable retangulo.largura = 2; console.log(retangulo.altura); // mostra 1 console.log(retangulo['altura']); // notação de Array, mostra 1 let propriedade = 'largura'; console.log(retangulo[propriedade]); // notação de Array, mostra 2 console.log(retangulo.area()); // mostra 2 {}.hasOwnProperty('prototype'); // retorna true

Existe também uma sintaxe mais parecida com a de outras linguagens, utilizando a palavra-chave class, para facilitar a criação de objetos similares. Notem que é uma tentativa de mostrar um ambiente mais familiar a programadores de outras linguagens, uma vez que JavaScript não possui classes de verdade, seu modelo de herança é baseado em protótipos, onde um objeto herda diretamente de outro objeto. Explico mais detalhadamente os problemas que isso pode trazer na parte de OOP.

Note novamente que no exemplo abaixo os campos e métodos continuam sendo todos públicos.

declaração de objetos com getters e setters

'use strict'; class Retangulo { // construtor constructor(largura, altura) { // define as propriedades e seus valores iniciais // defineProperties nos dá controle sobre os atributos writable, configurable e enumerable // se não especificados, o padrão é false Object.defineProperties(this, { _largura: {value: largura, writable: true}, _altura: {value: altura, writable: true} }); Object.seal(this); // mais sobre isso na parte de OOP } // métodos // diferente da sintaxe de objeto, os métodos aqui não são enumerable por padrão area() { return this.largura * this.altura; } // getters e setters get largura() { return this._largura; } set largura(largura) { this._largura = largura; } get altura() { return this._altura; } set altura(largura) { this._altura = altura; } } // cria um objeto que herda de Retangulo const retangulo = new Retangulo(10, 20); console.log(retangulo.largura); // mostra 10 console.log(retangulo.area()); // mostra 200 console.log(retangulo['largura']); // mostra 10

Objetos antigos

Como mencionei, JavaScript teve outras sintaxes para criar objetos, que ainda são suportadas até hoje, porém não mais recomendadas. A alternativa vigente antes da sintaxe de classes utilizava o método Object.create. Esta maneira de criar objetos expunha o método de herança que JavaScript usa, baseado em protótipos.

declaração de objetos com getters e setters

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // cria um objeto que herda de Object para servir como base const retangulo = Object.create(Object.prototype, { // método qualquer usado para inicializar o objeto // a prática comum é chamà-lo de init // é quase como um construtor, mas esta sintaxe não necessita de construtores // o objeto já é usável antes de chamar init init: { value: function(largura, altura) { this.largura = largura; this.altura = altura; }, writable: false }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os métodos area: { value: function() { return this.largura * this.altura; }, writable: false }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os getters e setters largura: { get: function() { return this._largura; }, set: function(largura) { this._largura = largura; } }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! altura: { get: function() { return this._altura; }, set: function(altura) { this._altura = altura; } }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define as propriedades largura: { value: 0, writable: true }, altura: { value: 0, writable: true }, }); // NÃO COPIE!!! SINTAXE ANTIGA!!!! // cria uma instância de retangulo const meuRetangulo = Object.create(retangulo); meuRetangulo.init(10, 20); Object.seal(meuRetangulo); // cria outra instância de retangulo const outroRetangulo = Object.create(retangulo); outroRetangulo.init(15, 17); Object.seal(outroRetangulo); console.log(outroRetangulo.largura); // mostra 15 console.log(outroRetangulo.area()); // mostra 255 console.log(outroRetangulo['largura']); // mostra 17

Existe ainda outra sintaxe para criar objetos, que foi a primeira disponibilizada. Ela utiliza funções e a palavra-chave new. Assim como com class, foi uma tentativa de não causar estranheza aos programadores vindos de outras linguagens, simulando o comportamento que eles já estavam acostumados nas linguagens baseadas em classes. Novamente, isso escondia o verdadeiro funcionamento dos objetos em JavaScript. Douglas Crockford, que é um guru e um dos redatores da linguagem não recomenda a utilização desta sintaxe antiga. Vou mostrar apenas a título de curiosidade uma das variantes da sintaxe antiga.

Outra desvantagem deste método é ter que se lembrar de declarar os métodos no protótipo do objeto, sendo que as propriedades são declaradas diretamente dentro do construtor. As sintaxes mais novas são mais uniformes quanto à isso.

declaração de objetos com sintaxe antiga, não use!

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! const objetoVazio = new Object(); // define o objeto base function Retangulo(largura, altura) { // define as propriedades e valores padrão this.largura = largura; this.altura = altura; } // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os métodos do objeto Retangulo.prototype.area = function() { return this.largura * this.altura; }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // cria uma instância de retangulo const meuRetangulo = new Retangulo(10, 20); // cria outra instância de retangulo const outroRetangulo = new Retangulo(15, 17); console.log(outroRetangulo.largura); // mostra 15 console.log(outroRetangulo.area()); // mostra 255 console.log(outroRetangulo['largura']); // mostra 17

Os muitos laços for

Além do laço for clássico presente em outras linguagens, existem duas outras formas de iteração rápida pelas propriedades de um objeto. A mais recente delas, for of funciona de maneira similar ao foreach ou for : de outras linguagens. Ele irá iterar pelos índices de uma Array ou pelas propriedades expostas por um objeto que implemente [Symbol.iterator].Estas são as duas maneiras recomendadas de realizar este tipo de iteração.

tipos de laços for

'use strict'; const lala = [1, 2, 486]; // nova, também suporta iterators for (let propriedade of lala) { // 1, 2, 486 } // clássica manual for (let i = 0; i < lala.length; i++) { // 1, 2, 486 }

A outra forma, intermediária, possui comportamento mais complexo e inconsistente, e portanto não se tornou muito popular. Ela itera não só pelos índices da Array, mas também pelas propriedades dela e de seus protótipos. Para evitar este comportamento indesejado, é necessário incluir uma verificacão extra dentro do laço. Porém, a ordem de iteração não é garantida, e não há meios de reparar isso.

tipos de laços for

'use strict'; const lala = [1, 2, 486]; // NÃO COPIE!!! SINTAXE ANTIGA!!! // zoada for (let propriedade in lala) { if (lala.hasOwnProperty(propriedade)) { // qualquer ordem é válida segundo a especificação } }

As arrays contam ainda com mais uma opção, que é o método forEach, e que pode estar presente mesmo em navegadores que não suportam for of. A desvantagem dessa abordagem é que a iteração não pode ser interrompida pela instrução break.

tipos de laços for

'use strict'; const lala = [1, 2, 486]; lala.forEach((currentValue, index, array) => { // 1, 2, 486 // sem a possibilidade de usar break :( });

Ado, a ado, cada um no seu quadrado

Quando eu escrevi esse título originalmente, era começo de 2014 e essa música era menos trash, mas não muito :P

Escopo das variáveis

Lá no começo disse que haviam duas maneiras de declarar uma variável, a mais recente, usando let e a mais antiga usando var. Agora é o momento de vermos suas diferenças, as pegadinhas e boas práticas.

Dependendo da palavra-chave que você utiliza, a variável obedecerá regras de escopo diferentes. Não conheço nenhuma outra linguagem que tenha essa característica. O motivo disto ter sido feito desta maneira em JavaScript é que a introdução de let foi um puxadinho feito depois na linguagem, para não quebrar a compatibilidade com códigos já existentes que utilizavam var.

Primeiro, o comportamento de let.

escopo de variáveis let

'use strict'; let todosVe = '\o/'; let i = 0; for (i = 0; i < 5; i++) { // } console.log(i); // 5 for (let dentroDoFor = 0; dentroDoFor < 7; dentroDoFor++) { // } console.log(dentroDoFor); // ReferenceError, dentroDoFor não está definido if (i > 0) { let dentroDoIf = true; } console.log(dentroDoIf); // ReferenceError, dentroDoIf não está definido

Nada de muito extraordinário aqui certo? O comportamento é similar ao de outras linguagens. Mas let nem sempre esteve disponível nos navegadores, então precisamos ver a alternativa legada.

Para var, o único delimitador de escopo é a função. Variáveis fora de funções estão no espaço global, e variáveis declaradas em blocos script ou arquivos diferentes compartilham o mesmo espaço global. Isso foi feito assim porque o programador original da linguagem não tinha tempo de fazer um linker.

escopo de variáveis var

'use strict'; var todosVe = '\o/'; var i = 0; for (i = 0; i < 5; i++) { // } console.log(i); // 5 for (var dentroDoFor = 0; dentroDoFor < 7; dentroDoFor++) { // } console.log(dentroDoFor); // 7. Surpresa. dentroDoFor foi declarado no espaço global. if (i > 0) { var dentroDoIf = true; } console.log(dentroDoIf); // true. Mais surpresa.

Este é um dos motivos pelos quais declaramos todas as varíaveis no topo do arquivo ou no começo da função. De qualquer maneira, vamos ver como resolver parte desse problema quando abordarmos closures.

Variable Hoisting

Outro motivo para se declarar as variáveis no começo do escopo é uma característica do compilador JavaScript, chamada variable hoisting.

O que isso significa é que, apenas para variáveis declaradas com var,independente de onde você declarar sua variável, o compilador vai mover a declaração de forma transparente para o topo do escopo e deixá-la com valor undefined. A linha onde um valor é atribuído à variável não é afetada. Isso pode causar problemas se o código depender de uma verificação da existência da variável. Isso não ocorre para variáveis declaradas com let.

variable hoisting

'use strict'; // o que vc escreveu console.log(estranha); // mostra undefined e continua normalmente var estranha = 11; console.log(estranha); // mostra 11

Ou seja, o que o compilador vai fazer de modo transparente é:

variable hoisting explicado

'use strict'; // o que o compilador vê var estranha; // o compilador moveu a declaracão que usa var (let não é afetado), para o topo do arquivo console.log(estranha); estranha = 11; // e manteve a atribução do valor na mesma linha console.log(estranha);

Módulos simples

Se quisermos evitar poluir o espaço global, temos que usar uma função anônima (lambda) autoexecutável. Projetos de aplicativos windows 8 escritos em JavaScript já utilizam por padrão esse procedimento.

Vamos ver primeiramente um exemplo em que nada entra nem sai do escopo, pois é mais simples de entender.

função anônima autoexecutável definindo escopo

(() => { var segredo = '******'; })(); console.log(segredo); // lança exceção // ou (function(){ var segredo = '******'; })(); console.log(segredo); // undefined em navegadores mais antigos ou lança exceção se modo estrito ou em navegadores novos

Para memorizar isso eu geralmente escrevo assim:

Declaro a função.

() => { /**/ } // ou function(){ /**/ }

Transformo ela em expressão como vimos lá na parte sobre funções.

( () => { /**/ } ); // ou ( function(){ /**/ } );

Executo ela.

(() => { /**/ })(); // ou (function(){ /**/ })();

Closures

Resumidamente, uma closure é uma função mais seu contexto, isto é, as variáveis que ela consegue acessar (ou captura se você preferir) presentes no escopo em que ela foi criada. Essa definição não diz muito sobre as consequências dessa funcionalidade, então vamos desenvolver essa ideia em código em algumas etapas.

Colocando coisas dentro do escopo e type aliasing

Estamos prontos para complicar um pouquinho mais os exemplos de módulos simples acima, passando valores para dentro do escopo. É bem comum fazer isso em conjunto com jQuery. Note que esse padrão também permite que nós habilitemos o modo estrito num determinado escopo.

Mas para não no embolarmos na sintaxe necessária para isso, que quase lembra Lisp, vamos fazer antes um pequeno exercício mental antes sobre funções e nomes de parâmetros.

nomes de parâmetros

'use strict'; // a função "mostrar" não conhece os nomes das variáveis que são passadas para ela // tudo o que ela sabe é que ela pode acessá-los genericamente chamando-os de "numero" function mostrar(numero) { console.log(numero); } const um = 1; const dois = 2; const tres = 3; // ou seja, as variáveis "um", "dois" e "tres" são referenciadas temporariamente // como "numero" na execução da função, como consequência do funcionamento normal // da programação que escrevemos mostrar(um); mostrar(dois); mostrar(tres);

Este ocorrido da mudança de nomes possui um segundo uso menos frequente, que é o de podermos atribuir aliases (outros nomes) às nossas variáveis. De maneira genérica, temos:

nomes de parâmetros

'use strict'; function cabineTelefonica(superHomem) { // superHomem é o nome que damos ao parâmetro passado para esta função // se chamarmos esta função passando clarkKent como parâmetro // dentro dela podemos chamar clarkKent pelo nome de superHomem // referenciamos o mesmo objeto por dois nomes distintos } let clarkKent = '\o/'; cabineTelefonica(clarkKent);

Estamos acostumados a fazer isso frequentemente quando programamos com a sintaxe acima. Outra maneira seria usando a sintaxe de flecha.

nomes de parâmetros

'use strict'; const cabineTelefonica = (superHomem) => { // mesma coisa que o exemplo acima, porém usando a sintaxe de flecha }; let clarkKent = '\o/'; cabineTelefonica(clarkKent);

E finalmente, usando uma função anônima autoexecutável.

nomes de parâmetros

'use strict'; let clarkKent = '\o/'; ((superHomem) => { // mesma coisa que os exemplos anteriores // chamamos esta função passando clarkKent como parâmetro // e chamamos esse parâmetro de superHomem enquanto estivermos dentro do escopo da função })(clarkKent);

Vejamos então o exemplo completo envolvendo jQuery.

função anônima autoexecutável com parâmetros

// noConflict() determina que não vamos usar jQuery com $ globalmente // vamos referenciá-los apenas por jQuery para evitar colisões com outras // bibliotecas que eventualmente usem $ também jQuery.noConflict(); // criamos um escopo através de um lambda // indicamos que esse lambda vai receber um parâmetro, e que vamos chamá-lo de $ (($) => { 'use strict'; // habilita modo estrito só para este escopo // podemos usar jQuery como $ aqui dentro sem interferir com o espaço global que continua usando jQuery $('body').html('<p>padrão para plug-ins jQuery</p>'); })(jQuery); // executamos o lambda, passando jQuery como parâmetro console.log($); // ReferenceError, $ está definido apenas dentro do escopo definido pelo lambda

função anônima autoexecutável com parâmetros

// noConflict() determina que não vamos usar jQuery com $ globalmente // vamos referenciá-los apenas por jQuery para evitar colisões com outras // bibliotecas que eventualmente usem $ também jQuery.noConflict(); // criamos um escopo através de um lambda // indicamos que esse lambda vai receber um parâmetro, e que vamos chamá-lo de $ (($) => { 'use strict'; // habilita modo estrito só para este escopo // podemos usar jQuery como $ aqui dentro sem interferir com o espaço global que continua usando jQuery $('body').html('<p>padrão para plug-ins jQuery</p>'); })(jQuery); // executamos o lambda, passando jQuery como parâmetro console.log($); // ReferenceError, $ está definido apenas dentro do escopo definido pelo lambda

função anônima autoexecutável com parâmetros

// noConflict() determina que não vamos usar jQuery com $ globalmente // vamos referenciá-los apenas por jQuery para evitar colisões com outras // bibliotecas que eventualmente usem $ também jQuery.noConflict(); // criamos um escopo através de um lambda // indicamos que esse lambda vai receber um parâmetro, e que vamos chamá-lo de $ (($) => { 'use strict'; // habilita modo estrito só para este escopo // podemos usar jQuery como $ aqui dentro sem interferir com o espaço global que continua usando jQuery $('body').html('<p>padrão para plug-ins jQuery</p>'); })(jQuery); // executamos o lambda, passando jQuery como parâmetro console.log($); // ReferenceError, $ está definido apenas dentro do escopo definido pelo lambda

função anônima autoexecutável com parâmetros

// noConflict() determina que não vamos usar jQuery com $ globalmente // vamos referenciá-los apenas por jQuery para evitar colisões com outras // bibliotecas que eventualmente usem $ também jQuery.noConflict(); // criamos um escopo através de um lambda // indicamos que esse lambda vai receber um parâmetro, e que vamos chamá-lo de $ (($) => { 'use strict'; // habilita modo estrito só para este escopo // podemos usar jQuery como $ aqui dentro sem interferir com o espaço global que continua usando jQuery $('body').html('<p>padrão para plug-ins jQuery</p>'); })(jQuery); // executamos o lambda, passando jQuery como parâmetro console.log($); // ReferenceError, $ está definido apenas dentro do escopo definido pelo lambda

função anônima autoexecutável com parâmetros

// noConflict() determina que não vamos usar jQuery com $ globalmente // vamos referenciá-los apenas por jQuery para evitar colisões com outras // bibliotecas que eventualmente usem $ também jQuery.noConflict(); // criamos um escopo através de um lambda // indicamos que esse lambda vai receber um parâmetro, e que vamos chamá-lo de $ (($) => { 'use strict'; // habilita modo estrito só para este escopo // podemos usar jQuery como $ aqui dentro sem interferir com o espaço global que continua usando jQuery $('body').html('<p>padrão para plug-ins jQuery</p>'); })(jQuery); // executamos o lambda, passando jQuery como parâmetro console.log($); // ReferenceError, $ está definido apenas dentro do escopo definido pelo lambda

Resolvendo var e corrigindo closures

Quando passamos objetos para as funções que queremos executar funções, estes objetos são passados por referência. Se passarmos tipos simples como Boolean e Number, eles serão passados por valor. Isto é, teremos uma cópia do valor passado.

Este comportamento costuma ser usado para resolver problemas que o comportamento de var causa em laços e outras situações. Por exemplo, vamos revisitar este trecho problemático de código:

escopo de variáveis var

'use strict'; for (var dentroDoFor = 0; dentroDoFor < 7; dentroDoFor++) { // executa console.log daqui 100 milissegundos, depois que o laço terminou setTimeout(() => { console.log(dentroDoFor); }, 100); // sempre vai ser 7. dentroDoFor foi declarado no espaço global } console.log(dentroDoFor); // 7. Surpresa. dentroDoFor foi declarado no espaço global.

Neste caso, criamos inadvertidamente uma closure. O lambda que passamos para a função setTimeout também é uma função, e seu contexto de criação é tudo o que está no espaço global. Portanto ela captura a variável global dentroDoFor, e a encherga com seu valor atual 7 quando executada.

Caso estejamos num navegador em que seja possível usar let, a correção é bastante simples, pois o uso desta palavra-chave restringe o escopo de dentroDoFor para cada iteração do laço.

escopo de variáveis var

'use strict'; for (let dentroDoFor = 0; dentroDoFor < 7; dentroDoFor++) { // executa console.log daqui 100 milissegundos, depois que o laço terminou setTimeout(() => { console.log(dentroDoFor); }, 100); // 0, 1, 2... 6 como desejado } console.log(dentroDoFor); // 7. Surpresa. dentroDoFor foi declarado no espaço global.

Caso o navegador seja mais antigo e não seja possível o uso de let, ainda podemos corrigir o comportamento indesejado por meio de uma closure da seguinte maneira:

resolvendo problemas de escopo de var com closures

'use strict'; // dentroDoFor é passado por valor para o escopo que a função forBody cria // ou seja, o escopo recebe uma cópia no momento em que a função foi chamada dentro do laço // dentroDoFor local ofusca dentroDoFor global function forBody(dentroDoFor) { // executa console.log daqui 100 milissegundos, depois que o laço terminou setTimeout(() => { console.log(dentroDoFor); }, 100); // 0, 1, 2... 6 como desejado } var i = 0; for (i = 0; i < 8; i++) { forBody(i); } console.log(i); // ainda é 7, pois i foi declarado no espaço global. console.log(dentroDoFor); // ReferenceError, dentroDoFor só existe dentro da função forBody

O lambda continua sendo uma closure, mas agora encherga apenas a cópia local da variável dentroDoFor, pois a variável global foi obfuscada por utilizar o mesmo nome da local, que tem maior prioridade. A variável local nunca é incrementada, e o lambda então, se comporta como o esperado.

Não é necessário ofuscar a variável global utilizando o mesmo nome para a variável local. Poderíamos utilizar um nome distinto, desde que garantíssemos que sempre referenciar a variável local pelo nome distinto que escolhemos. Porém, usar o mesmo ajuda a evitar erros.

Tirando coisas de dentro do escopo

Agora que vimos o que são closures, vamos aprender mais uma coisa sobre elas. Se elas forem movidas para fora do seu contexto de criação, permanecem com a habilidade de acessar as variáveis e funções do escopo original.

closures

'use strict'; // cria um escopo function externa() { let informacao = 'wikileaks'; let interna = function() { console.log('vazou ' + informacao); } return interna; // expõe a função para fora do escopo } let dedoDuroInterno = externa(); // dedoDuroInterno é igual à função retornada do escopo, é um alias para ela dedoDuroInterno(); // expões os segredos dentro do escopo!

Essa sintaxe é ótima para ser utilizada com o padrão facade. Ou seja:

padrão de projeto facade

'use strict'; function facade() { // criar tudo o que não deve ser acessado diretamente de fora do escopo aqui // retornar o objeto ou função que tem permissão para acessar o escopo return function() { // }; } let acesso = facade();

E mais resumidamente, usando o módulo simples que vimos anteriormente:

padrão de projeto facade

'use strict'; let acesso = (function() { // criamos tudo o que não deve ser acessado diretamente de fora do escopo aqui // retornamos o objeto ou função que tem permissão para acessar o escopo // sim, funções anônimas autoexecutáveis podem retornar valores! return {}; })(); // ou let acesso = (() => { // mesma coisa que a alternativa acima ;) return {}; })();

Recapitulando, uma closure é uma função mais o contexto em que foi criada, isto é, as variáveis que ela consegue acessar (ou capturar se você preferir). No caso do problema com o laço for, criamos uma closure sem querer, e então precisamos adicionar uma função a mais para resolver o problema que a closure criou. No caso padrão facade criamos uma closure propositalmente e movemos ela para fora do seu contexto, mantendo o restande dele privado, podendo ser acessado apenas através da closure.

Os módulos do jQuery

O framework jQuery mistura vários dos conceitos apresentados aqui. Ainda não apresentei o conceito de eventos para entendermos todas as partes do jQuery, mas podemos identificar os já vistos no código abaixo.

uso simples de jQuery

'use strict'; jQuery(document).ready((evento) => { // escopo isolado, não polui o espaço global });

O primeiro dos conceitos utilizados é que jQuery é uma função. Por isso podemos chamá-la utilizando jQuery(). Esta função sempre retorna uma referência para ela mesma, ou seja, sempre retorna jQuery. Esta organização de código se chama interface fluida. O método recebe como parâmetro um seletor CSS ou objeto do DOM, neste caso o objeto document.

jQuery(document).ready((evento) => { // escopo isolado, não polui o espaço global });

A seguir temos o método ready, que é chamado quando o evento correspondente é disparado. Por enquanto basta saber que este método só será chamado quando a página estiver pronta para manipulação por JavaScript. Nem tudo que usamos em JavaScript precisa ser colocado dentro deste método. Apenas a parte que irá interagir imediatamente com o DOM. Por exemplo, podemos definir nossas bibliotecas anteriormente num outro local, e só chamá-las dentro de ready quando elas precisarem interagir com o DOM. O motivo disso é performance.

jQuery(document).ready((evento) => { // escopo isolado, não polui o espaço global });

Mas se jQuery() sempre retorna outra função jQuery, como acessamos o método ready? Lembre-se em JavaScript funções são objetos, e portanto podem ter propriedades e outros métodos, como o ready.

Por último, passamos ao método ready uma função anônima que contém o código que queremos executar. Esta sintaxe reduz a quantidade de variáveis e métodos no espaço global.

jQuery(document).ready((evento) => { // escopo isolado, não polui o espaço global });

Módulos avançados - Padrão AMD

Como JavaScript escrito sem cuidado pode poluir rapidamente o espaço global, devemos procurar maneiras de evitar a sobrescrita de variáveis. O framework jQuery pode não ser uma boa alternativa para uma quantidade maior de código, uma vez que sempre carrega todos seus plug-ins na memória, mesmo o que não estiver usando.

Vimos que funções podem ser usadas para criar escopos nomeados e anônimos, como nos exemplos de expressões de função e do laço e var, e que essas funções escopo podem receber e retornar valores. Essas são as bases para chegarmos ao que queremos.

Como os recursos da linguagem são poucos para uma organização mais efetiva do código, foi criado um padrão mais bem acabadinho, amplamente utilizado, inclusive em diversos frameworks, chamado AMD. Caso você não esteja usando um framework que já possua suporte (como Angular ou Ember), é possível baixar uma biblioteca exclusivamente para isso, chamada RequireJS. Esse padrão é utilizado através de uma função, chamada define, que pode ser utilizada da seguinte maneira:

Padrão AMD para definição de escopo

'use strict'; define('nomeDoModulo', () => { // escopo isolado, não polui o espaço global // retornar aqui o valor do módulo // isto é, a classe, objeto, função etc. // que poderá ser acessada pelo identificador 'nomeDoModulo' }); define('nomeOpcional', ['nomeDoModulo', 'dependencia2', 'etc'], (nomeDoModulo, dependencia2, etc) => { // escopo isolado, não polui o espaço global // podemos utilizar as dependências importadas });

Ou seja, se quisermos criar objetos que possam ser reaproveitados apenas em determinados escopos, fazemos da seguinte maneira:

Isolando dependências com AMD

'use strict'; // cria a classe num escopo isolado define('MinhaClasse', () => { const MinhaClasse = class MinhaClasse() { constructor() { /**/ } } // lembre-se de sempre retornar um valor do módulo, para que ele seja associado ao identificador return MinhaClasse; }); // cria a função num escopo isolado define('minhaFuncao', () => { const minhaFuncao = function() { /**/ }; return minhaFuncao; }); // utiliza a classe importada, optei por não nomear esse módulo define(['MinhaClasse'], (MinhaClasse) => { const meuObjeto = new MinhaClasse(); // Ok minhaFuncao(); // Erro, pois não importamos minhaFunção });

A primeira vista isso tudo pode assustar, então vamos ver passo a passo. O método define associa escopos com identificadores.

Isolando dependências com AMD

'use strict'; // cria a classe num escopo isolado define('MinhaClasse', () => { const MinhaClasse = class MinhaClasse() { constructor() { /**/ } } // lembre-se de sempre retornar um valor do módulo, para que ele seja associado ao identificador return MinhaClasse; }); // cria a função num escopo isolado define('minhaFuncao', () => { const minhaFuncao = function() { /**/ }; return minhaFuncao; }); // utiliza a classe importada, optei por não nomear esse módulo define(['MinhaClasse'], (MinhaClasse) => { const meuObjeto = new MinhaClasse(); // Ok minhaFuncao(); // Erro, pois não importamos minhaFunção });

Retornando valores dentro destes escopos, podemos associá-los ao identificador do escopo.

Isolando dependências com AMD

'use strict'; // cria a classe num escopo isolado define('MinhaClasse', () => { const MinhaClasse = class MinhaClasse() { constructor() { /**/ } } // lembre-se de sempre retornar um valor do módulo, para que ele seja associado ao identificador return MinhaClasse; }); // cria a função num escopo isolado define('minhaFuncao', () => { const minhaFuncao = function() { /**/ }; return minhaFuncao; }); // utiliza a classe importada, optei por não nomear esse módulo define(['MinhaClasse'], (MinhaClasse) => { const meuObjeto = new MinhaClasse(); // Ok minhaFuncao(); // Erro, pois não importamos minhaFunção });

E então podemos importar estes valores através dos identificadores.

Isolando dependências com AMD

'use strict'; // cria a classe num escopo isolado define('MinhaClasse', () => { const MinhaClasse = class MinhaClasse() { constructor() { /**/ } } // lembre-se de sempre retornar um valor do módulo, para que ele seja associado ao identificador return MinhaClasse; }); // cria a função num escopo isolado define('minhaFuncao', () => { const minhaFuncao = function() { /**/ }; return minhaFuncao; }); // utiliza a classe importada, optei por não nomear esse módulo define(['MinhaClasse'], (MinhaClasse) => { const meuObjeto = new MinhaClasse();// Ok minhaFuncao(); // Erro, pois não importamos minhaFunção });

O padrão AMD foi formulado para suportar minificação do código fonte, e portanto acaba sendo um pouco redundante.

DOM - El Poderoso Jefón

Se você leu o capítulo anterior (e deveria), pode ter visto que o IE10- lançou um erro em nosso exemplo simples, quando as ferramentas de desenvolvimento não estavam abertas. Isso ocorre porque nestes navegadores, o objeto console, pertencente ao DOM, não é acessível pelo JavaScript enquanto as ferramentas não estiverem abertas. Vou mostrar mais adiante como corrigir esta funcionalidade do IE10-.

Embora por caminhos tortos, isso nos ensina um conceito importante. O conceito de que JavaScript e o DOM são mundos diferentes, e nem sempre JavaScript consegue acessar tudo do DOM a toda a hora. Existem momentos específicos em que isso pode ser feito.

Vamos ver isso e outros conceitos com um segundo exemplo mais complexo.

seosoquenao.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Google está olhando</title> <meta charset='utf-8' /> <!-- script interrompe a leitura da página e executa imediatamente --> <script src='seosoquenao.js'></script> </head> <body> <h1 id='tagTitulo'>Acessibilidade e SEO</h1> <p id='tagParagrafo'> Sim pequeno gafanhoto, html tem mais de <strong>100 tags</strong> diferentes além de table e div. </p> </body> </html>

seosoquenao.js

'use strict'; // tenta pegar elementos da página Html pela id // e os tornar manipuláveis dentro do JavaScript let titulo = document.getElementById('tagTitulo'); let paragrafo = document.getElementById('tagParagrafo'); // titulo e paragrafo são null?! titulo.innerHTML = 'Black hat SEO'; //erro paragrafo.attributes.add('style', 'display: none'); // erro

Ao abrir este arquivo diretamente do seu computador, com o console aberto, talvez você note mais erros. Ao abrir este arquivo através de um servidor, mesmo que local, com certeza você notará mais erros.

Porém a programação acima está perfeitamente correta, e o arquivo .htm também. O problema que ocorre neste caso não existe em linguagens C e similares, e se dá quando combinamos os arquivos .htm e .js de uma determinada maneira que provoca uma interação inadequada entre o JavaScript e o DOM (Notou a elegância da frase? Ela é ótima para impressionar seu chefe quando vc tiver que explicar pq aquele site está funcionando perfeitamente no seu pc mas quando vc publicou pipocaram erros em todo o lugar).

Lembre-se que JavaScript não possui o conceito de único ponto de entrada (método main). Quando o navegador encontra uma tag <script> na página, ele pára de processar o restante do documento, baixa o arquivo js, roda seu conteúdo, e só depois continua o processamento normalmente. Em nosso exemplo, como a tag <script> está antes do conteúdo, o navegador carrega e roda a programação antes de tomar ciência do resto do documento. Nas primeiras linhas de nosso código, estamos pedindo para o navegador pegar determinados elementos na página, mas ele ainda não sabe que esses elementos existem. Isso talvez não ocorra sempre ao abrir o arquivo local devido diferenças que existem ao ler o arquivo diretamente do hd, porém sempre irá acontecer através de um servidor http.

E porque não tivemos esse problema no primeiro exemplo? Porque lá não estávamos acessando nenhum elemento da página. Nunca saímos do mundinho JavaScript para o mundo DOM.

Podemos corrigir nosso problema de três maneiras pelo menos.

  1. Mover nossa tag <script> para o final da página, após o conteúdo;
  2. Pedir para o navegador só executar nossa programação após o carregamento da página, na própria tag;
  3. Fazer a mesma coisa que a alternativa anterior, mas dentro do código JavaScript.

A terceira eu vou deixar pra mais adiante, pois precisaríamos saber o que são eventos. Vamos ver os 2 primeiros casos e explicar o que acontece.

Solução 1 - Movendo a tag script

seosoquenaoS1.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Google está olhando</title> <meta charset='utf-8' /> </head> <body> <h1 id='tagTitulo'>Acessibilidade e SEO</h1> <p id='tagParagrafo'> Sim pequeno gafanhoto, html tem mais de 100 tags diferentes além de table e div. </p> <!-- script executa depois que o corpo de body foi lido --> <script src='seosoquenao.js'></script> <!-- Segura a empolgação, a especificação não permite colocar mais nada depois de </body> e </html>. Mas isso nunca é necessário. --> </body> </html>

Essa solução talvez seja a mais simples. Quando o navegador rodar o JavaScript, já vai ter lido todos os elementos da página, e pode pegar qualquer um deles sem problema. A desvantagem deste método é que o navegador não poderá otimizar o download dos arquivos que a página irá usar (baixando em paralelo por exemplo), pois ele só vai saber que precisará deles no final do documento.

Solução 2 - Postergando a execução na tag

seosoquenaoS2.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Google está olhando</title> <meta charset='utf-8' /> <!-- defer faz o script executar depois que toda a página foi lida --> <script src='seosoquenao.js' defer></script> </head> <body> <h1 id='tagTitulo'>Acessibilidade e SEO</h1> <p id='tagParagrafo'> Sim pequeno gafanhoto, html tem mais de 100 tags diferentes além de table e div. </p> </body> </html>

Essa solução é bastante elegante, porém não é suportada nos IEs mais antigos. Além disso, ela só funciona para arquivos .js externos. Não é possível utilizá-la quando colocamos a programação entre as tags <script> e </script>. Através do atributo defer da tag, indicamos nosso desejo de executar a programação dentro dela só após o carregamento da página.

Se familiarizando com a Cosa Nostra

Utilizamos o método document.getElementById para trazer o elemento da página para dentro do nosso código. Esta solução não é a mais antiga para fazermos tal coisa, porém é a recomendada pela performance e compatibilidade com os navegadores.

Mas e se precisamos pegar elementos que não possuem id? A API disponibiliza outros métodos que retornam valores diferentes dependendo da sua necessidade, sendo que o último deles, document.querySelectorAll foi inspirado após o surgimento do jQuery.

cosanostra.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>1001 maneiras de falar com o DOM</title> <meta charset='utf-8' /> </head> <body> <header> <nav> <ul> <li><a href='pag1'>link 1</a></li> <li><a href='pag2'>link 2</a></li> <li><a href='pag3'>link 3</a></li> <li><a href='pag4'>link 4</a></li> </ul> </nav> </header> ... <footer> <div>Ir para o <a href='#'>topo</a></div> </footer> <script src='cosanostra.js'></script> </body> </html>

cosanostra.js

'use strict'; // retorna os links do menu e o ir para o topo let links = document.getElementsByTagName('a'); // filtra os links fora da lista let linksFiltrados = []; let nodeName; for (let link of links) { nodeName = link.parentNode.nodeName.toLowerCase(); if (nodeName === 'li') { linksFiltrados.push(link); } } // retorna os links do menu apenas. ñ funciona em IE8- let linksMenu = document.querySelectorAll('ul a'); // retorna os links do menu apenas. // versões do jQuery 1.10.x funcionam em todos os navegadores let linksMenuJ = jQuery('ul a');

Notem que cada chamada de método para trazer um elemento do DOM para dentro do JavaScript leva um certo tempo para realizar esta operação. Por motivos de performance, devemos sempre que possível guardar a referência para estes elementos e reutilizá-la, ao invés de chamar continuamente métodos que busquem no DOM. Vejam por exemplo este trecho da própria documentação do jQuery UI (http://jqueryui.com/datepicker/#date-formats):

interação pobre com o DOM

'use strict'; // busca o mesmo item 2x desnecessariamente // buscar coisas no DOM é uma operação despendiosa $(function() { $('#datepicker').datepicker(); $('#format').change(function() { $('#datepicker').datepicker('option', 'dateFormat', $(this).val()); }); });

Note que o mesmo elemento foi buscado duas vezes denecessariamente. Se quisermos deixar o código mais performático, podemos fazer a seguinte alteração:

interação correta com o DOM

'use strict'; $(function() { // salvamos a referência ao elemento na variável let meuDatePicker = $('#datepicker'); // e usamos quantas vezes quisermos sem sermos penalizados meuDatePicker.datepicker(); $('#format').change(function() { meuDatePicker.datepicker('option', 'dateFormat', $(this).val()); }); });

Google - O Grande Irmão

Acabamos de aprender como alterar html através de JavaScript. Igualmente importante é sabermos quando não devemos fazer isso. Via de regra, Google e outros bots não indexam conteúdo criado ou alterado por javascript. Porém, pode penalizar você se ele achar que você está tentando trapacear, mostrando um conteúdo muito diferente do que foi indexado. Para aplicativos web isso talvez não seja um problema, mas para páginas de internet com conteúdo com certeza é. Aqui vale o bom senso. Podemos fazer um ou outro dropdown, mas se abusarmos deste recursos, escondermos coisas sem um método de mostrá-las novamente, podemos ser punidos pelo Google com posições mais baixas nos resultados, e até mesmo banimento temporário.

O fato de bots não rodarem javascript em sua maioria também pode ser usado em nossa vantagem. É possível por exemplo proteger endereços de emails ou evitar envio automático de formulários alterando certas propriedades por JavaScript.

semcaptcha.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Google está olhando</title> <meta charset='utf-8' /> </head> <body> <form action='#'> <dl> <dt><label for='nome'>nome</label></dt> <dd><input id='nome' type='text' /></dd> </dl> <p><input type='submit' /></p> </form> <script src='semcaptcha.js'></script> </body> </html>

semcaptcha.js

'use strict'; let formulario = document.getElementsByTagName('form')[0]; formulario.action = 'cadastrar.cgi'; // não vai ser lido pela maioria dos bots

Desacoplando JavaScript das linguagens server-side

Se estruturarmos nossa programação de modo que as bibliotecas com os objetos estejam em arquivos externos, podemos apenas chamar os construtores de nossos objetos nas páginas com programação server-side, passando os parâmetros com valores dinâmicos.

serverside.php

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Html mínimo aceitável</title> <meta charset='utf-8' /> <script src='dropdown.js'></script> <script> (function() { 'use strict'; dropdown.init(<%= // valor calculado server side %>); })(); </script> </head> <body> </body> </html>

dropdown.js

'use strict'; let dropdown = { init: function(idConteiner) { this.idConteiner = idConteiner; this.conteiner = document.getElementById(this.idConteiner); this.rotulo = this.conteiner.querySelector('button'); this.lista = this.conteiner.querySelector('ul'); this.rotulo.addEventListener('click', this.clickHandler.bind(this)); }, clickHandler: function(evento) { this.visivel = !this.visivel; this.atualizar(); }, atualizar: function() { if (this.visible) { this.lista.style.display = 'block'; return; } this.lista.style.display = 'none'; }, idConteiner: null, conteiner: null rotulo: null, lista: null, visivel: false };

This doesn't makes sense - OOP Ou WTF is this??? :P

Já vimos acima como declarar objetos simples, utilizando literais de objeto {}. Estes objetos são dinâmicos, não possuindo uma noção de classe como em outras linguagens. Relembrando que de fato não existem classes em JavaScript, apenas recursos da linguagem para camuflar a implementação real. Esta característica, associada ao fato de que JavaScript não é fortemente tipado, permite que objetos tenham propriedades e métodos inseridos ou removidos dinamicamente, em tempo de execução.

Mesmo no modo estrito, podemos adicionar e remover propriedades. Note que isso não é muito bom para performance. Assim como acontece com os tipos de variáveis, se evitarmos mudar o objeto após sua criação, o compilador não consegue efetuar otimizações para tipos específicos.

Adicionalmente, JavaScript possui o operador delete para remover propriedades, que é lento. Preferencialmente atribuímos o valor null para a propriedade que desejamos remover. A versão ES5 adicionou um método chamado Object.seal, que previne modificação dinâmica do objeto.

adicão e remoção dinâmica de propriedades

'use strict'; let escravoDeJo = { jogar: function() { // } }; escravoDeJo.jogar = null; // tira a propriedade - use o valor null ao invés de delete escravoDeJo.som = 'zig zig za'; // bota a propriedade console.log(escravoDeJo); // note a mudança das propriedades Object.seal(escravoDeJo); // "corpo fechado" - previne mudanças no objeto // Lança exceção. Não é possível mudar um objeto selado escravoDeJo.fazer = function() { // }

Lembra que eu disse que JavaScript não possui classes de verdade, certo? Veja as consequências disso nesse pedaço de código.

adicão e remoção dinâmica de propriedades

'use strict'; class Estranha { seraQueTemMesmo() { console.log('tem!'); } } const estranho = new Estranha(); const diferente = new Estranha(); diferente.seraQueTemMesmo = null; // válido, mesmo com const. ver explicação abaixo estranho.seraQueTemMesmo(); // Ok diferente.seraQueTemMesmo(); // Erro! O objeto diferente não possui esse método

Ué? Objetos da mesma classe podem ser diferentes? E const não salvou o dia e botou um pouco de ordem nisso? Infelizmente a primeira resposta é sim, e a segunda é não. Ao contrário de outras linguagens, que não permitem que um objeto declarado com const mude de maneira nenhuma, JavaScript apenas proíbe que a constante referencia um objeto diferente do inicial.

Se não tomarmos cuidado, nunca podemos garantir que dois objetos de uma mesma classe sejam parecidos. Porém, nem tudo está perdido, uma vez que possuímos um método que é capaz de fazer o que você deseja, chamado Object.freeze.

adicão e remoção dinâmica de propriedades

'use strict'; class Estranha { constructor() { Object.defineProperty(this, propriedade, { value: 1, writable: true }); } seraQueTemMesmo() { console.log('tem!'); } } const estranho = new Estranha(); const diferente = new Estranha(); estranho = new Estranha(); // Erro! não é possível atribuir um novo valor à const Object.freeze(estranho); Object.freeze(diferente); diferente.seraQueTemMesmo = null; // Erro! Não é possível modificar um objeto congelado estranho.propriedade = 2; // Erro! Não é possível modificar um objeto congelado estranho.seraQueTemMesmo(); // Ok se chegasse aqui diferente.seraQueTemMesmo(); // Ok se chegasse aqui

Caso congelar um objeto seja uma medida muito extrema, existe um intermediário, que apenas proibe a alteração dos métodos, mas não das propriedades, chamado Object.seal.

adicão e remoção dinâmica de propriedades

'use strict'; class Estranha { constructor() { Object.defineProperty(this, propriedade, { value: 1, writable: true }); Object.seal(this); } seraQueTemMesmo() { console.log('tem!'); } } const estranho = new Estranha(); const diferente = new Estranha(); diferente.propriedade = 2; // Ok, seal permite modificar propriedades diferente.seraQueTemMesmo = null; // Erro, seal não deixa redefinir métodos estranho.seraQueTemMesmo(); // Ok se chegasse aqui diferente.seraQueTemMesmo(); // Ok se chegasse aqui

Para acomodar toda esta flexibilidade, JavaScript possui mais uma diferença, desta vez em relação à palavra-chave this. Se podemos inserir funções dinamicamente nos objetos, o que acontece quando uma destas funções utiliza this?

significado de this

'use strict'; const modeloCumprimentar = function() { console.log('Olá, menu nome é ' + this.nome); // WTF is this? } const diretorSuspense = { nome: 'Alfred'; }; const diretorAlternativo = { nome: 'Tim'; }; diretorSuspense.cumprimentar = modeloCumprimentar; diretorAlternativo.cumprimentar = modeloCumprimentar; diretorSuspense.cumprimentar(); // mostra Olá, menu nome é Alfred diretorAlternativo.cumprimentar(); // mostra Olá, menu nome é Tim

Resumindo, this em JavaScript muda de significado de acordo com o contexto. Mais precisamente, this aponta sempre para o objeto que está executando a função atualmente. Isso traz mais problemas do que vantagens geralmente. Isto permite que as funções funcionem um pouco como traits presentes em outras linguagens, que permitem compor um objeto sem usar herança diretamente.

Métodos que podem receber uma função como parâmetro, também aceitam métodos de objetos. Nesses casos, o significado de this sempre irá mudar. Métodos nativos como setTimeout e addEventListener possuem esse comportamento. Vejamos um outro exemplo disso:

mudanças indesejadas do contexto de execução, this

'use strict'; let frutas = [ { nome: 'banana', peso: 100 }, { nome: 'maçã', peso: 80 }, { nome: 'abacaxi', peso: 3500 } // um abacaxi enorme! ]; let ordenador = { ordenarPor: 'nome', ordenar: function(arrayOriginal) { arrayOriginal.sort(this.comparador); // sort muda o contexto de execução }, comparador: function(a, b) { // this aponta para o quê aqui?? Possivelmente para window. // note também o uso da notação [] para acessar campos dinamicamente if (typeof a[this.ordenarPor] === 'number' && typeof b[this.ordenarPor] === 'number' ) { return a[this.ordenarPor] - b[this.ordenarPor]; } if (a[this.ordenarPor] > b[this.ordenarPor]) { return 1; } else if (a[this.ordenarPor] < b[this.ordenarPor]) { return -1; } else if (a[this.ordenarPor] == b[this.ordenarPor]) { return 0; } } }; ordenador.ordenarPor = 'nome'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } ordenador.ordenarPor = 'peso'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } // PS: logar a array inteira de uma vez pode causar confusão // pois a ordem mostrada dos campos vai ser sempre o estado final da referência // por isso usamos for nos valores

A referência this parece apontar para o objeto ordenador, mas isso não é sempre verdade. Quando passamos o método para arrayOriginal.sort, nosso comparador vai executar num contexto diferente. Assim o this referenciado não é o que esperávamos. Tentamos usar a propriedade ordenarPor que não está mais acessível e isso causa uma exceção.

Deixando as coisas mais previsíveis

Não estamos desamparados para resolver os problemas de mudança de contexto (amém!). De fato, as funções em JavaScript possuem diversos métodos para controlar essa mudança. Vamos ver a seguir dois deles.

Lembrando que em JavaScript funções são objetos, em navegadores recentes as funções possuem um método chamado bind, que retorna uma cópia da função com o this fixo.

controlando contexto com bind

'use strict'; arrayOriginal.sort(this.comparador.bind(this)); // ES3, só IE9+ suporta

Para navegadores mais antigos temos outras duas alternativas. A concentualmente mais simples é utilizar jQuery.proxy, que funciona de maneira similar ao bind.

controlando contexto com jQuery.proxy

'use strict'; arrayOriginal.sort(jQuery.proxy(this.comparador, this)); // todos os navegadores

Note que jQuery.proxy permite passar parâmetros para a função na seguinte forma:

passando parâmetros com jQuery.proxy

'use strict'; jQuery.proxy(this.comparador, this, var1, var2, var3);

Internamente jQuery.proxy utiliza um outro método pertencente às funções, que também altera o valor de this, chamado apply. Na verdade, não é difícil fazermos uma versão simplificada de bind, ou jQuery.proxy com apply, evitando ser necessário uma biblioteca inteira apenas para essa funcionalidade.

A terceira alternativa é a mais manual e complexa. Possui porém a vantagem de cobrir todos os casos que o bind nativo cobre. Ela utiliza o conceito de closures.

Revisitando nosso abacaxi

Esta solução do nosso problema consiste em simplesmente não utilizar a palavra this. Apenas a palavra this muda de significado, se conseguirmos referenciar nosso objeto original por outro nome, como demonstramos com aliases e a cabineTelefonica, podemos acessá-lo sempre que precisarmos sem ter medo de uma crise de identidade. Para fazermos isso usamos uma closure. A primeira forma de utilizá-la, é fazendo-a conter a função completa de comparação.

resolvendo mudanças de contexto com closures

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. let frutas = [ { nome: 'banana', peso: 100 }, { nome: 'maçã', peso: 80 }, { nome: 'abacaxi', peso: 3500 } // um abacaxi enorme! ]; let ordenador = { ordenarPor: 'nome', // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. // init irá executar a closure, que salva a referência para this com um nome diferente // este nome não sofre do mesmo mal que this, e aponta sempre para a mesma referência init: function() { this.comparador = this.closureComparador(this); }, ordenar: function(arrayOriginal) { arrayOriginal.sort(this.comparador); }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. closureComparador: function (contexto) { return function(a, b) { // contexto sempre apontará para o objeto ordenador if (typeof a[contexto.ordenarPor] === 'number' && typeof b[contexto.ordenarPor] === 'number' ) { return a[contexto.ordenarPor] - b[contexto.ordenarPor]; } if (a[contexto.ordenarPor] > b[contexto.ordenarPor]) { return 1; } else if (a[contexto.ordenarPor] < b[contexto.ordenarPor]) { return -1; } else if (a[contexto.ordenarPor] == b[contexto.ordenarPor]) { return 0; } } }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. comparador: null }; ordenador.init(); ordenador.ordenarPor = 'nome'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. ordenador.ordenarPor = 'peso'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } // PS: logar a array inteira de uma vez pode causar confusão // pois a ordem mostrada dos campos vai ser sempre o estado final da referência // por isso usamos for nos valores

Note que precisamos ser bastante cuidadosos para sempre escrever contexto ao invés de this. Existe uma segunda forma dessa solução, que utiliza a closure para simplesmente passar o contexto correto ao método executado.

resolvendo mudanças de contexto com closures - solução alternativa

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. let frutas = [ { nome: 'banana', peso: 100 }, { nome: 'maçã', peso: 80 }, { nome: 'abacaxi', peso: 3500 } // um abacaxi enorme! ]; let ordenador = { ordenarPor: 'nome', // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. // init irá executar a closure, que salva a referência para this com um nome diferente // este nome não sofre do mesmo mal que this, e aponta sempre para a mesma referência init: function() { this.comparador = this.closureComparador(this); }, ordenar: function(arrayOriginal) { arrayOriginal.sort(this.comparador); }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. closureComparador: function (contexto) { return function(a, b) { // ao invés de escrever o corpo da função diretamente aqui, como no outro exemplo // simplesmente chamamos a função comparar definida no objeto // como comparar está sendo chamada indiretamente dentro da closure, através de "contexto" // o "this" dentro de "comparar" apontará para "contexto", que é o objeto ordenador, como queremos // ao contrário do problema original, onde "comparar" era chamada diretamente através de array.sort // e portanto seu "this" apontava para window return contexto.comparar(a, b); } }, comparador: null, // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. comparar: function(a, b) { // a closure se encarrega de executar este método com o contexto correto // por isso podemos utilizar this sem preocupações if (typeof a[this.ordenarPor] === 'number' && typeof b[this.ordenarPor] === 'number' ) { return a[this.ordenarPor] - b[this.ordenarPor]; } if (a[this.ordenarPor] > b[this.ordenarPor]) { return 1; } else if (a[this.ordenarPor] < b[this.ordenarPor]) { return -1; } else if (a[this.ordenarPor] == b[this.ordenarPor]) { return 0; } } // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. }; ordenador.init(); ordenador.ordenarPor = 'nome'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } // NÃO COPIE!!! SINTAXE ANTIGA!!!! USE BIND SE POSSÍVEL. ordenador.ordenarPor = 'peso'; ordenador.ordenar(frutas); for (let fruta of frutas) { console.log(fruta); } // PS: logar a array inteira de uma vez pode causar confusão // pois a ordem mostrada dos campos vai ser sempre o estado final da referência // por isso usamos for nos valores

Vale a pena reforçar que, a diferença entre o problema original e esta última solução, é que no código original, passávamos uma referência direta para o método do objeto, fazendo com que ele fosse chamado diretamente, ao passo que nesta última solução, passamos uma referência indireta ao método, fazendo com que ele seja chamado através do objeto ao qual pertence.

É tanta herança que vou ficar rico de conhecimento ;)

JavaScript suporta pelo menos 3 maneiras diferentes de criarmos uma hierarquia com heranças.

Herança com pouca classe

Já vimos que herança em JavaScript é baseada em protótipos. Um objeto herda diretamente de outro objeto. Embora possamos declarar uma herança como se estivéssemos usando classes, não é o que efetivamente acontece por baixo dos panos. Se nos atermos ao uso básico dos objetos, não quebraremos a ilusão de estarmos trabalhando com classes. Para saber efetivamente em que momentos esta ilusão é quebrada, continue lendo mais abaixo.

herança com polimorfismo

'use strict'; class ObjetoPai { constructor() { Object.defineProperty(this, 'propriedade', {value: []}); this.propriedade.push(valor); } metodo() { console.log(this.propriedade); } } // herança class ObjetoFilho extends ObjetoPai { constructor() { super(); Object.seal(this); } // sobrescrita do método metodo() { super.metodo(); } } // construtores objetoPai = new ObjetoPai(3); objetoFilho = new ObjetoFilho(3); objetoPai.propriedade.push('pai'); objetoFilho.propriedade.push('filho'); objetoPai.metodo(); objetoFilho.metodo();

Herança antiga com Object.create

Utilizamos o método Object.create. Já vimos isso rapidamente quando definimos um objeto com getters e setters utilizando descritores de propriedades. Vamos ver agora um exemplo com polimorfismo. Se precisarmos acessar o objeto base de dentro do objeto filho, chamamos o método Object.getPrototypeOf, que retorna um objeto acima da cadeia de herança.

Note que estamos tratando de objetos concretos, e não de definições de objetos como são as classes em outras linguagens. Isso significa que, quando acessarmos o objeto base, estaremos acessando não só as definições de métodos mas também os valores das propriedades dele. Devemos portanto tomar o cuidado de, ao chamar estes métodos base, indicar que queremos executá-los no contexto do objeto filho.

Outro cuidado que devemos tomar, é o de atribuir valores aos objetos num método separado, que o inicializa. Se atribuirmos valores diretamente na definição do objeto, e estes valores forem tipos de referência como arrays, eles serão compartilhados pelos subobjetos ao invés de cada um ter sua própria cópia.

herança com polimorfismo

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! var objetoPai = { init: function() { this.propriedade = []; // inicializar valores das propriedades só aqui! Muito importante! this.propriedade.push(valor); }, metodo: function() { console.log(this.propriedade); }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! propriedade: null }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // herança var objetoFilho = Object.create(objetoPai); // sobrescrita do método objetoFilho.metodo = function() { Object.getPrototypeOf(this).metodo.call(this); } // NÃO COPIE!!! SINTAXE ANTIGA!!!! objetoPai.init(3); objetoFilho.init(3); objetoPai.propriedade.push('pai'); objetoFilho.propriedade.push('filho'); objetoPai.metodo(); objetoFilho.metodo();

Se não precisarmos de compatibilidade com navegadores antigos, podemos usar descritores de propriedade, que nos dão mais controle sobre a definição do objeto.

herança com polimorfismo ES5

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! var objetoPai = Object.create(Object.prototype, { init: { value: function() { this.propriedade = []; // inicializar valores das propriedades só aqui! Muito importante! this.propriedade.push(valor); } }, metodo: { value: function() { console.log(this.propriedade); }, writable: true }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! propriedade: { value: null, writable: true } }); // NÃO COPIE!!! SINTAXE ANTIGA!!!! // herança var objetoFilho = Object.create(objetoPai, { // sobrescrita do método metodo: { value: function() { Object.getPrototypeOf(this).metodo.call(this); } } }); // NÃO COPIE!!! SINTAXE ANTIGA!!!! objetoPai.init(3); objetoFilho.init(3); objetoPai.propriedade.push('pai'); objetoFilho.propriedade.push('filho'); objetoPai.metodo(); objetoFilho.metodo();

Herança arcaica com function

Mostro também um exemplo de herança com a sintaxe antiga. Note que a sintaxe é mais confusa e propensa a erros, pois possui etapas manuais que devem ser lembradas toda a vez. Além disso, existem as questões comentadas anteriormente sobre misturar definições de coisas no construtor e no protótipo, e dar a ilusão que JavaScript possui classes, o que não é verdade.

herança com polimorfismo com sintaxe antiga, não use!

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! function ObjetoPai(valor) { // definir e inicializar valores aqui this.propriedade = []; this.propriedade.push(valor); } ObjetoPai.prototype.metodo = function() { console.log(this.propriedade); }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // herança function ObjetoFilho() { ObjetoPai.apply(this, Array.prototype.slice.call(arguments)); // "super()" } ObjetoFilho.prototype = new ObjetoPai(); // especificar manualmente qual o protótipo para herdar ObjetoFilho.prototype.constructor = ObjetoFilho; // sobrescrever manualmente o construtor // sobrescrita do método ObjetoFilho.prototype.metodo = function() { ObjetoPai.prototype.metodo.call(this); // nome do objeto base é hardcoded e não muda automaticamente de acordo com a herança }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! var objetoPai = new ObjetoPai('pai'); var objetoFilho = new ObjetoFilho('filho'); objetoPai.metodo(); objetoFilho.metodo();

Vamos ver um exemplo mais completo.

polimorfismo, exemplo completo

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define o objeto base var ampulheta = { init: function(tempo) { // define os valores padrão das propriedades this.tempoTotal = tempo; this.tempoAtual = this.tempoTotal; }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! contar: function() { if (this.tempoAtual > 0) { this.tempoAtual--; } }, girar: function() { this.tempoAtual = this.tempoTotal - this.tempoAtual; }, // NÃO COPIE!!! SINTAXE ANTIGA!!!! tempoTotal: null, tempoAtual: null }; // herança var ampulhetaAutomatica = Object.create(ampulheta); // "ampulhetaAutomatica extends ampulheta" // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os métodos do objeto derivado ampulhetaAutomatica.contar = function() { if (this.tempoAtual <= 0) { this.girar(); } else { // chamar o método da maneira abaixo o executaria no contexto do objeto pai, ampulheta // Object.getPrototypeOf(this).contar(); // por isso usando o método call da função para mudar o contexto novamente para ampulhetaAutomática Object.getPrototypeOf(this).contar.call(this); } } // NÃO COPIE!!! SINTAXE ANTIGA!!!! ampulheta.init(3); ampulhetaAutomatica.init(3); ampulheta.contar(); // 2 ampulheta.contar(); // 1 ampulheta.contar(); // 0 ampulheta.girar(); // 3 // NÃO COPIE!!! SINTAXE ANTIGA!!!! ampulhetaAutomatica.contar(); // 2 ampulhetaAutomatica.contar(); // 1 ampulhetaAutomatica.contar(); // 0 ampulhetaAutomatica.contar(); // 3

Mostrar properties

polimorfismo, exemplo completo com sintaxe antiga, não use!

'use strict'; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define o objeto base function Ampulheta(tempo) { // define as propriedades e valores padrão this.tempoTotal = tempo; this.tempoAtual = this.tempoTotal; } // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os métodos do objeto base Ampulheta.prototype.contar = function() { if (this.tempoAtual > 0) { this.tempoAtual--; } }; Ampulheta.prototype.girar = function() { this.tempoAtual = this.tempoTotal - this.tempoAtual; }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define o objeto derivado function AmpulhetaAutomatica() { Ampulheta.apply(this, Array.prototype.slice.call(arguments)); // "super()" } // herança AmpulhetaAutomatica.prototype = new Ampulheta(); // "AmpulhetaAutomatica extends Ampulheta" AmpulhetaAutomatica.prototype.constructor = AmpulhetaAutomatica; // "conserta" o construtor // NÃO COPIE!!! SINTAXE ANTIGA!!!! // define os métodos do objeto derivado AmpulhetaAutomatica.prototype.contar = function() { if (this.tempoAtual <= 0) { this.girar(); } else { // chamar o método da maneira abaixo o executaria no contexto do objeto pai, ampulheta // Ampulheta.prototype.contar(); // por isso usando o método call da função para mudar o contexto novamente para ampulhetaAutomatica Ampulheta.prototype.contar.call(this); } }; // NÃO COPIE!!! SINTAXE ANTIGA!!!! // simulação de sintaxe de outras linguagens var contador = new Ampulheta(3); var contadorAutomatico = new AmpulhetaAutomatica(3); contador.contar(); // 2 contador.contar(); // 1 contador.contar(); // 0 contador.girar(); // 3 contadorAutomatico.contar(); // 2 contadorAutomatico.contar(); // 1 contadorAutomatico.contar(); // 0 contadorAutomatico.contar(); // 3

Beware, here there be dragons

Vale lembrar também que as várias formas de declarar objetos e heranças descritas anteriormente podem coexistir ao mesmo tempo, sem necessariamente gerarem cadeias de herança 100% idênticas. Algumas utilizam funções objetos, outras objetos puros. Vou me ater à última maneira de herança, mais moderna e que parece ser a mais compacta, e mais parecida com a de outras linguagens, e portanto, mais propensa a erros de analogia.

Seja Javascript não possui classes verdadeiras, o que então é criado quando digitamos class? Uma função. E como toda função em JavaScript é um objeto dinâmico, é possível fazer, deliberadamente ou inadvertidamente toda sorte de coisas malucas.

hic sunt dracones

'use strict'; // NÃO COPIE!!! APENAS PARA DEMONSTRAÇÃO!!!! class Ha { constructor() { // se não retornarmos nada explicitamente // this é retornado implicitamente return 'outra coisa'; } } const ha = new Ha(); // string "outra coisa" ao invés de "instância" de Ha class Bla { static fazer() { /**/ } } console.log(Bla); // tipo function // adicionar propriedades estáticas dinamicamente à "classe" (função) Bla.propriedade = 2; Bla.propriedadeCompartilhada = { tipo: 'referência' }; // NÃO COPIE!!! APENAS PARA DEMONSTRAÇÃO!!!! // remover métodos estáticos dinamicamente Bla.fazer = null; // misturar maneiras diferentes de heranças let blo = Object.create(Bla); // NÃO COPIE!!! APENAS PARA DEMONSTRAÇÃO!!!! console.log(blo.propriedade); // 2. Era estática mas virou de instância console.log(blo.propriedadeCompartilhada); // { tipo: 'referência' }. Era estática mas virou de instância // referência é a mesma da propriedade estática blo.propriedadeCompartilhada.tipo = 'mudou'; console.log(Bla.propriedadeCompartilhada); // { tipo: 'mudou' } // Herança maldita ;)

Controle de visibilidade com Proxy Nativo

Todas as opções de declaração de objetos que vimos até agora possuem apenas propriedades e métodos públicos, sem a o possibilidade de um controle mais preciso de sua visibilidade. JavaScript não possui uma maneira de criar tais permissões de acesso através de palavras-chave como private, protected e public, e portanto precisamos recorrer a padrões de projeto para conseguir o que queremos. Nenhuma das soluções possíveis é capaz de satifazer completamente a falta dessa funcionalidade.

A partir da versão ES6 da linguagem, temos acesso à proxies nativos, que tornam a tarefa de controle de visibilidade um pouco menos difícil, permitindo uma melhor mistura entre visibilidade, herança e uso de memória do que é possível através de outras maneiras. Ainda assim, deixam muito a desejar, uma vez que não foram criados com isso em mente. Seu motivo de existência parece ser a de efeitos colaterais como logs e notificações.

Proxies nativos são capazes de interceptar chamadas de métodos, acesso à propriedades, e ações através dos operadores new, delete, in, e métodos de Object como Object.getPrototypeOf, Object.create, etc. Não vou abordar a lista completa de capacidades, apenas as pertinentes ao controle de visibilidade.

Estes proxies necessitam de um objeto que especifica o comportamento da intercepção para cada caso. Para simular campos protegidos e privados, temos que interceptar o acesso às propriedades e métodos, através dos métodos get e set da definição do Proxy. Por enquanto vou esconder alguns detalhes da implementação (mais especificamente _hasAccess) pra digerirmos tudo aos poucos.

padrão proxy

'use strict'; const _hasAccess = (alvo, propriedade) => { /* omitido por enquanto, vamos aos poucos ;) */ }; // define o comportamento do proxy const proxyHandler = { /** intercepta leitura dos valores de propriedades e métodos */ get(alvo, propriedade, receiver) { // se existe if (!(propriedade in alvo)) { throw new Error('Não existe ou é privado'); } // se é visível if (!_hasAccess(alvo, propriedade)) { throw new Error('Não existe ou é privado'); } return Reflect.get(alvo, propriedade, receiver);// Reflect.get encaminha a operação para o alvo }, /** intercepta escrita dos valores de propriedades */ set(alvo, propriedade, valor, receiver) { // se é visível if (!_hasAccess(alvo, propriedade)) { throw new Error('Não existe ou é privado'); } return Reflect.set(alvo, propriedade, valor, receiver); // Reflect.set encaminha a operação para o alvo } }; // define a classe class Cupcake { constructor() { this.cobertura = null; } } // cria uma instância const cupcake = new Cupcake(); const cupcakeProxy = new Proxy(cupcake, proxyHandler); cupcakeProxy.cobertura = 'doce de leite com cereja'; // vai ser interceptado por proxyHandler.set console.log(cupcakeProxy.cobertura); // vai ser interceptado por proxyHandler.get

O comportamento de get e set é determinado por algumas invariantes definidas na especificação ES6, na qual o JavaScript é baseado. Essas invariantes garantem que o objeto Proxy seja consistente com o objeto interceptado, podendo substituí-lo de maneira transparente. Infelizmente como efeito colateral, a flexibilidade do Proxy é reduzida. Por exemplo, devemos obrigatoriamente mostrar todas as propriedades não configuráveis. Se selarmos o objeto com Object.seal, todas as propriedades se tornarão não configuráveis como consequência, e portanto devemos mostrá-las. Também devemos retornar o mesmo valor que o objeto retornaria para propriedades somente leitura não configuráveis, e por aí vai.

Vamos passar para a implementação de _hasAccess.

padrão proxy

'use strict'; const _hasAccess = (alvo, propriedade) => { if (propriedade.toString().startsWith('_')) { return false; } return true; }

Nesse caso, optei por identificar o controle de visibilidade através de uma convenção de nomes das propriedades. Notem que esta não é a única alternativa para identificação do controle de visibilidade, embora talvez seja a mais simples. Sinta-se à vontade para bolar sua própria alternativa.

Se as propriedades começarem pelo caractere _ (underscore), o Proxy impedirá seu acesso. Porém, ele não funciona tão intuitivamente quanto poderia. Vamos ver alguns dos possíveis casos.

problemas de proxy

'use strict'; // define o comportamento do proxy const proxyHandler = { /* omitido pra ficar mais curto */ }; // define a classe class Cupcake { constructor() { Object.defineProperties(this, { _cobertura, {value: null, writable: true}, // privado, pois começa com _ _recheioCk, {value: null, writable: true} // privado, pois começa com _ }); } // getters e setters implícitos get cobertura() { return this._cobertura; } set cobertura(valor) { this._cobertura = valor; } get recheio() { return this._recheioCk; } set _recheio(valor) { this._recheioCk = valor; } // privado, pois começa com _ // métodos usarCobertura() { console.log(this._cobertura); } } // cria uma instância const cupcake = new Cupcake(); const cupcakeProxy = new Proxy(cupcake, proxyHandler); // getters e setters implícitos // seus acessos são interceptados pelo proxy // mas os acesso feitos dentro do corpo deles não é vigiado cupcakeProxy.cobertura = 'creme com suspiro'; console.log(cupcakeProxy.cobertura); // ok. this._cobertura é acessível dentro do getter implícito console.log(cupcakeProxy.recheio); cupcake._recheio = 'creme'; // erro, set _recheio é private como esperado // chamada de método normal // proxy age diferente nesse caso, interceptando os acessos dentro do corpo do método também cupcakeProxy.usarCobertura(); // erro! não consegue acessar this._cobertura de dentro do método!

Devido à essa inconsistência no comportamento do Proxy, temos que acessar algumas coisas através do objeto original. Infelizmente a especificação torna isso muito difícil propositalmente. Não existe maneira de saber quem está acessando as propriedades. Sendo assim, precisamos dar um jeito de manter o objeto original à mão.

encapsulamento com proxies

'use strict'; // define o comportamento do proxy const proxyHandler = { /* omitido pra ficar mais curto */ }; // define a classe class Cupcake { constructor() { Object.defineProperties(this, { // o construtor deve continuar usando this _cobertura, {value: null, writable: true}, // privado, pois começa com _ _recheioCk, {value: null, writable: true} // privado, pois começa com _ }); } // getters e setters implícitos get cobertura() { return self._cobertura; } set cobertura(valor) { self._cobertura = valor; } get recheio() { return self._recheioCk; } set _recheio(valor) { self._recheioCk = valor; } // privado, pois começa com _ // métodos usarCobertura() { console.log(self._cobertura); } } // cria uma instância const self = new Cupcake(); const cupcakeProxy = new Proxy(self, proxyHandler); // getters e setters implícitos // seus acessos são interceptados pelo proxy // mas os acesso feitos dentro do corpo deles não é vigiado cupcakeProxy.cobertura = 'creme com suspiro'; console.log(cupcakeProxy.cobertura); // ok, usa self internamente, que é a referência original sem proxy console.log(cupcakeProxy.recheio); cupcake._recheio = 'creme'; // erro, set _recheio é private como esperado // chamada de método normal // proxy age diferente nesse caso, interceptando os acessos dentro do corpo do método também cupcakeProxy.usarCobertura(); // ok, usa self internamente, que é a referência original sem proxy

Resta ainda facilitarmos o uso desse padrão. Fazendo alguns malabarismos com um Proxy a mais, permitimos que os objetos sejam instanciados de maneira transparente. O resultado final é bastante difícil de compreender para iniciantes em JavaScript, pois utiliza recursos idiomáticos da linguagem. Felizmente seu uso é bem mais simples.

encapsulamento com proxies e factory

'use strict'; // define o comportamento do proxy const proxyHandler = { /* omitido pra ficar mais curto */ }; const proxyFactory = (Classe, proxyHandler) => { // este proxy extra serve apenas para permitir que instanciemos // nosso objeto e o proxy juntos de maneira transparente, com o operador new const proxy = new Proxy(class {}, { /** intercepta a construção de novos objetos */ construct(alvo, parametros) { // salva o objeto original como self na closure do construtor const self = new Classe(); // faz o contrutor retornar o proxy do objeto ao invés do objeto original const proxy = new Proxy(self, proxyHandler); return proxy; } }); return proxy; }; // define a classe tendo o cuidado de usar self, com exceção do construtor // note que não é possível instanciá-la diretamente, pois self só existirá // quando passarmos ela pela proxyFactory class CupcakeDefinicao { constructor() { this._recheio = null; } usarRecheio() { console.log(self._recheio); } } // define classes filhas, seguindo os mesmos cuidados class CupcakeCobertoDefinicao extends CupcakeDefinicao { constructor() { super(); this._cobertura = null; } usarCobertura() { console.log(self._cobertura); } } // criar helpers para instanciar as classes com proxy // são estes helpers que expomos para uso geral const Cupcake = proxyFactory(CupcakeDefinicao, proxyHandler); const CupcakeCoberto = proxyFactory(CupcakeCobertoDefinicao, proxyHandler); // instancia e usa as classes de maneira fácil e transparente // pelo menos essa parte é simples const cupcakeDeChocolate = new Cupcake(); cupcakeDeChocolate.usarRecheio(); const cupcakeFlorestaNegra = new CupcakeCoberto(); cupcakeFlorestaNegra.usarRecheio(); cupcakeFlorestaNegra.usarCobertura();

Não sei quanto a você, mas até alguém aparecer com uma maneira mais simples de controlar a visibilidade com Proxy, eu vou continuar deixando tudo público...

Object.observe

Caso você escute por aí sobre Object.observe nativo, saiba que era uma adição na API que o Google estava fazendo lobbing a favor por causa de seuframework Angular, mas que foi embora mesmo antes de entrar oficialmente. Sua funcionalidade pode ser replicada usando proxies.

Controle de visibilidade com closures

A classe Proxy pode nos ajudar com o controle de visibilidade das propriedades. Para casos mais simples, ou para ambientes onde ela não está disponível, podemos usar closures para simular esta funcionalidade, como vimos em tirando coisas do escopo. Primeiro criamos um escopo usando AMD, ou uma função anônima autoexecutável, ou uma função construtora, e vazamos desse escopo a parte que queremos usar como interface de nosso objeto. Este objeto será usado de maneira transparente, sem ninguém precisar saber que sua funcionalidade está fragmentada internamente. O exemplo abaixo mostra o essencial deste procedimento.

encapsulamento com proxy e factory

'use strict'; // estilo AMD define('objeto', () => { const publico = { // acessar privado aqui dentro }; const privado = { // se necessário, pode acessar publico }; return publico; // expõe apenas a interface pública }); // ou estilo função anônima autoexecutável const objeto = (() => { const publico = { // acessar privado aqui dentro }; const privado = { // se necessário, pode acessar publico }; return publico; // expõe apenas a interface pública })();

Um exemplo mais completo.

encapsulamento com closures

'use strict'; const matador = (function() { const publico = { matarACobra() { privado.matar(); }, get pau() { return privado.pau; } }; const privado = { matar: function() { this.pau = 'quebrou'; }, pau: 'é feito de madeira!' }; return publico; })(); matador.matarACobra(); console.log(matador.pau); // mostra o pau ;)

Este padrão não é compatível com herança, pois a closure é compartilhada com todos os objetos do mesmo tipo e seus subobjetos.

encapsulamento com closures e factory

'use strict'; // NÃO COPIE!!! APENAS PARA DEMONSTRAÇÃO!!!! const matador = algumMetodoQueUsaClosurePraSimularPrivate(); // não vai fazer o quê você espera! const matador1 = Object.create(matador); const matador2 = Object.create(matador); matador1.matarACobra(); console.log(matador2.pau); // erro, pois matarACobra alterou a propriedade dos dois matadores

É possível criar cópias da closure usando um padrão factory. Embora consigamos centralizar a definição da parte private, perdemos o benefício de economizarmos memória mantendo referências dos métodos.

encapsulamento com closures e factory

'use strict'; const matadorFactory = () => { const publico = { matarACobra() { privado.metodoInterno(); }, get pau() { return privado.propriedadeInterna; } }; const privado = { metodoInterno: function() { console.log('método interno'); }, propriedadeInterna: 'é feito de madeira!' }; return publico; }; const matador1 = matadorFactory(); const matador2 = matadorFactory(); matador1.matarACobra(); console.log(matador2.pau); // ok

Async Yoda as coisas ordenando pode estar

Código assíncrono, ou async, é uma maneira de evitar esperas longas em chamadas de funções que podem demorar muito para retornarem. Sendo mais técnico, evita que determinadas funções bloqueiem a execução da thread em que estão rodando. Especialmente útil em JavaScript onde o conceito de threads (através de web workers) é relativamente novo, e a maioria do código ainda tende a ser executado na thread da interface gráfica. O exemplo mais comum disso talvez sejam funções que executam requisições http. Código assíncrono permite que o processador fique livre para executar outras instruções como computar as interações do usuário, enquanto espera o retorno da requisição por exemplo.

Muito importante para a compreensão do async em JavaScript é o conceito de event loop. Ele se trata de uma fila de execução de blocos de código. O que define a divisão entre um bloco e outro é o nível de empilhamento das funções executadas. Quando chamamos uma função dentro de outra, esta segunda função é empilhada em cima da primeira. Quando sua execução termina, ela é desempilhada, e o controle da execução volta à primeira função da pilha. Quando esta função por sua vez, termina a execução, todas as funções são desempilhadas e o bloco de código chega ao fim. O modelo de execução de JavaScript garante que um bloco de código não pode ser interrompido antes de chegar ao fim, eliminando erros de concorrência. O que as funções assíncronas fazem é executar determinadas linhas entre os blocos de código de maneiras variadas.

Outra característica das funções async é que elas tendem a mudar o contexto de execução para a window, e portanto cuidados adicionais devem ser tomados no uso de this.

Vamos ver algumas formas de código assíncrono.

Eventos

Já andamos flertando com eventos em diversos de nossos exemplos. Agora temos conhecimento suficiente para os vermos mais detalhadamente.

Eventos são a implementação nativa do padrão de projeto (design pattern) chamado Observer. É usada mais frequentemente na interface gráfica para responder às ações do usuário como cliques do mouse ou scroll da página, e outros eventos como o carregamento completo da página e requisições http. Eventos adicionam um callback na fila do event loop assim que são disparados, e seus observadores são notificados assim que todas as funções sendo atualmente executadas sejam desempilhadas.

Para indicar que estamos interessados num determinado evento, usamos o método addEventListener. Devemos desregistrar o evento quando não estamos mais interessados nele, através de removeEventListener, para evitar uso desnecessário de memória e tornar o coletor de lixo mais lento. IE8- utiliza um outro método não padronizado para registrar eventos, e nesses casos é necessário o uso de um fallback.

eventos

'use strict'; // evento de clique num botão, pode acontecer diversas vezes const botao = document.getElementById('botao'); botao.addEventListener('click', (evento) => { // botao guarda esse callback e o executa apenas quando é clicado // o callback de addEventListener recebe informações sobre o evento ocorrido // tais como qual a tecla pressionada, ou quem disparou o evento através // do objeto evento evento.target.value = 'clicou'; // muda o texto do botão clicado para clicou }); // evento de conclusão da requisição http, acontece apenas uma vez function requisicaoLoad(evento) { console.log(requisicao.responseText); // mostra a resposta da requisição evento.target.removeEventListener('load', requisicaoLoad); // remove o listener da requisicao e libera memória } const requisicao = new XMLHttpRequest(); requisicao.addEventListener('load', requisicaoLoad); // requisicao guarda esse callback e o executa apenas quando receber uma resposta requisicao.open('GET', 'lista.json'); requisicao.send();

setTimeout, setInterval e requestAnimationFrame

Estas funções são na verdade métodos do objeto window que faz parte do DOM. Assim como outros métodos assíncronos, elas adicionam um callback na fila do event loop. O que diferencia essas funções async é que podemos determinar um tempo de espera para a execução do callback. Ou seja, assim que o tempo determinado chegar, o ambiente de execução irá esperar que todas as funções sendo atualmente executadas sejam desempilhadas, e então executará o callback, ficando desbloqueado nesse meio tempo de espera.

async e tempos de espera

'use strict'; const time = 1000; // 1 segundo, ou 1000 milissegundos const intervalId = setInterval(() => { // vai executa aproximadamente a cada 1 segundo // cancela com clearInterval(intervalId); }, time); const timeoutId = setTimeout((evento) => { // vai executar uma única vez em aproximadamente 1 segundo // cancela com clearTimeout(timeoutId); }, time); setTimeout((evento) => { // vai executar quase que imediatamente // usado principalmente para prevenir bloqueios muito longos na thread // pois o código entra na fila do event loop ao invés de ser empilhado }, 0); requestAnimationFrame((timestamp) => { // parecido com setTimeout 0, mas // vai executar no próximo quadro });

setTimeout e requestAnimationFrame têm um outro uso secundário, que é evitar que o navegador coma alguns passos que ele julgar desnecessários.

animacao.css

/* Indica que a mudança de cor do elemento devem ser animada */ #elementoAnimado { transition: color 300ms; }

animacao.js

'use strict'; const div = document.getElementById('elementoAnimado'); const preto = '#000000'; const branco = '#ffffff'; div.style.color = preto; // o navegador irá ignorar esta linha por motivos de performance div.style.color = branco; // pois esta linha já anula a anterior muito rapidamente // infelizmente o comportamento acima impede que nossa animação seja executada // a maneira de impedir essa "otimização" é indicar que queremos mudar de cor // assim que possível, mas não imediatamente, utilizando uma das formas abaixo div.style.color = preto; setTimeout(() => { div.style.color = branco; }, 0); // ou div.style.color = preto; requestAnimationFrame((timestamp) => { div.style.color = branco; });

Quando desejamos criar animações por programação ou fazermos um laço principal, como em jogos, é mais apropriado utilizar a função requestAnimationFrame, que está disponível à partir do IE10.

requestAnimationFrame loop

'use strict'; // executa a função a cada quadro que o navegador mostrar // o mais comum é chamá-la numa taxa de 60Hz, ou 16,66ms // mais isso varia de acordo com o monitor, placa de vídeo, etc. gameLoop(timestamp) { // executar a lógica do quadro aqui requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop);

Promises nativas

Surgindo em diversas linguagens nos últimos tempos, JavaScript também tem uma implementação nativa de Promises, que já eram disponíveis através de bibliotecas como Q (usada no Angular) e RSVP (usada no Ember). Promises em JavaScript adicionam um callback ao event loop assim que são cumpridas.

sintaxe nativa de promises

'use strict'; function algumaCoisaAsync() { const promessa = new Promise((resolve, reject) => { // fazer alguma coisa demorada e se sucesso: resolve(algumValorOpcional); // ou se erro reject('motivo'); }); return promessa; } algumaCoisaAsync() .then((algumValorOpcional) => { // executar algo depois da operação async });

O propósito de fazer promessas, ao contrário do que dizem as más línguas, não é quebrá-las, mas sim tornar a execução do seu código assíncrono mais linear. Eventos previstos para disparar apenas uma única vez são bons candidatos para a conversão em promessas.

O que antigamente seria escrito como:

requisição xml http clássica com eventos e callbacks

'use strict'; function transferComplete(evento) { // fazer algo e então evento.target.removeEventListener('load', transferComplete); } function transferFailed(evento) { // fazer algo e então evento.target.removeEventListener('load', transferComplete); } let requisicao = new XMLHttpRequest(); requisicao.addEventListener('load', transferComplete); requisicao.addEventListener('error', transferFailed); requisicao.open('GET', 'lista.json'); requisicao.send();

Agora pode ser escrito como:

requisição xml http com promises

'use strict'; requisicao('GET', 'lista.json') .then( resposta => console.log(resposta), erro => console.log(erro) );

Desde que você escreva um wrapper bonitinho ao redor das chamadas para as requisições.

Concordo que o exemplo acima não é muito impressionante. A real força das promessas vem do amor S2. Opa, não não, quero dizer, a real força das promessas pode ser vista quando temos que encadear ou executar em "paralelo" várias chamadas assíncronas. Digo "paralelo" porque paralelismo em JavaScript é um caso à parte. Não vai achar que funciona igual que no C que o seu avô usou pra programar o Pong original, né? Vamos por enquanto só ver exemplos práticos ao que diz respeito às promessas.

Digamos que você queira o endereço e fuso horário de um local qualquer. Uma abordagem ingênua poderia gerar isso:

macarrão de requisições e callbacks

'use strict'; // passo 2 function requisitar(endereco, callback) { let requisicao = new XMLHttpRequest(); requisicao.addEventListener('load', callback); requisicao.open('GET', endereco); requisicao.send(); } // passo 3 function localizacaoLoaded(resposta) { requisitar('geocode', geocodeLoaded); requisitar('fusoHorario', fusoHorarioLoaded); } // passos 4 e 5 function geocodeLoaded(resposta) { resultados.endereco = resposta; resultados.semaforo++; pronto(); } function fusoHorarioLoaded(resposta) { resultados.fusoHorario = resposta; resultados.semaforo++; pronto(); } // passo 7 function pronto() { if (resultados.semaforo === 2) { finalmente(); } } // passo 8 (final) function finalmente() { // quer ao sugo, marinara ou alho e óleo? } // passo 6 var resultados = { semaforo = 0, endereco: null, fusoHorario = null }; // passo 1 requisitar('localizacao', localizacaoLoaded);

Claro que poderíamos organizar a lógica acima de diversas maneiras, evitando variáveis globais, etc. Porém, notem como promessas fornecem uma solução flexível para o problema em questão, e também reaproveitável para outros padrões de execução assíncronos.

requisições organizadas com promises

'use strict'; requisicao('localizacao') .then((resposta) => { const coordenadas = `?lat=${resposta.latitude}&lng=${resposta.longitude}`; // executa duas requisições em paralelo, após a // requisição original ter concluído return Promise.all([ requisicao('geocode' + coordenadas), requisicao('fusoHorario' + coordenadas) ]); }) .then((respostas) => { const endereco = respostas[0]; const fusoHorario = respostas[1]; // exercite sua criatividade aqui ;) });

Bem melhor não é? O procedimento necessário para encadear várias ações assíncronas é retornar uma nova promessa dentro do then da promessa anterior. No nosso exemplo, Promise.all faz exatamente isso, retorna uma nova promessa que será cumprida assim que todas as requisições completarem. Maios ou menos como uma barreira cíclica, só que com promessas ao invés de threads.

Não por acaso, um tempo atrás li uma conversa (no mesmo link acima do wrapper bonitinho) de que promessas poderiam ser usadas justamente para padronizar várias APIs assíncronas que foram sendo inclusas na linguagem ao longo dos tempos, tais como queries em IndexedDBs, requisições http, acesso à arquivos, etc. É pagar pra ver.

Deferreds

Promises requerem que você já saiba como vão resolvê-las, logo que elas são criadas. Se você não souber, pode implementar um objeto deferred, que permite postergar esta decisão.

deferreds

'use strict'; class Deferred { constructor() { Object.defineProperties(this, { _promise: {writable: true, value: null}, _resolve: {writable: true, value: null}, _reject: {writable: true, value: null} }); Object.seal(this); this._promise = new Promise(((resolve, reject) => { this._resolve = resolve; this._reject = reject; }).bind(this)); } resolve(value) { return this._resolve(value); } reject(reason) { return this._reject(reason); } then(callbackAsync) { return this._promise.then(callbackAsync); } }; // promises sabem como serão resolvidas no momento da criação obrigatoriamente const promise = new Promise((resolve, reject) => { // se nao definirmos aqui, a promessa nunca será cumprida setTimeout(() => { resolve(); }, 1000); }); // deferreds podem ter seu modo de resolução determinado depois de sua criação // criamos o objeto sem indicar mais nada const deferred = new Deferred(); // então podemos passá-lo para métodos // retorná-lo dentro de funções // armazenar em arrays // e só depois decidir quando chamar resolve ou reject neles setTimeout(() => { deferred.resolve(); }, 1000);

Generators

Tenho sentimentos confusos quanto à utilidade desta funcionalidade (que por si só já é confusa pra c@#$%☠), uma vez que o conceito de generators já existe há tempos em outras linguagens (C++, C#, Java e Python são exemplos) e não vejo pessoas dizendo como isso revolucionou o modo como elas programam. Mas sinto que não é o que está tentando se fazer parecer quanto a generators em JavaScript. Vai entender.

A implementação nativa deste conceito deveria facilitar a criação de generators e iterators. Mas sinceramente não funciona tão bem para todos os casos. De fato, o caso mais simples para o qual eles foram criados parece ser o menos beneficiado pela implementação nativa, e as alternativas escritas utilizando objetos, functors ou closures são bastante atraentes. O principal motivo de tanto frisson parece ser o uso não tão óbvio dos generators para mais um modelo de execução assíncrona (quantos nós já temos até agora? Já perdi a conta).

Mas chega de falar e vamos mostrar algum código. Gerar ou percorrer uma coleção de itens em partes, a fim de distribuir o tempo de processamento, parece ser o propósito mais comum. Para tanto, precisamos de um meio de guardar em que pedaço da coleção nós paramos. Vamos comparar as possíveis implementações.

Primeiro usando objetos simples para guardar o estado do processo todo.

guardando estado entre chamadas de funções com objetos

'use strict'; let colecao = { // propriedades itensPorPagina: 0, marcador: 0, pagina: null, valor: obterArrayMagicamente(), // métodos paginar: function(itensPorPagina) { if (this.marcador < this.valor.length) { this.pagina = this.valor.slice( this.marcador, this.marcador + itensPorPagina ); this.marcador += itensPorPagina; } return this.pagina; } }; function clickHandler() { colecao.paginar(); // colecao.pagina contém os resultados // mostrar a página } colecao.itensPorPagina = 10; botaoProximo.addEventListener('click', clickHandler);

Agora utilizando um estilo funcional, com closures:

guardando estado entre chamadas de funções com closures

'use strict'; function getPaginador(colecao, itensPorPagina) { // variáveis capturadas pela closure const total = colecao.length; let marcador = 0; let pagina = null; // função da closure return function paginar() { if (marcador < total) { pagina = colecao.slice( marcador, marcador + itensPorPagina ); marcador += itensPorPagina; } return pagina; } } const colecao = obterArrayMagicamente(); const paginar = getPaginador(colecao, 10); let pagina = null; function clickHandler() { pagina = paginar(); // mostrar a página } botaoProximo.addEventListener('click', clickHandler);

Poderíamos ainda criar uma implementação adicionando as propriedades de estado diretamente nas funções (uma vez que em JavaScript elas são objetos dinâmicos), mas acho que você já deve ter pego o espírito da coisa.

As maneira que as funções generator nativas expõem para guardar as informações de que precisaremos, é permitir sua interrupção e retomada quando nós quisermos, através de instruções específicas e de um iterator. Ela pode ser escrita da seguinte maneira:

guardando estado entre chamadas de funções com generators

'use strict'; // declaramos generators com "function*" function* paginar(colecao, itensPorPagina) { const total = colecao.length; let marcador = 0; while(marcador < total) { // "yield" pausa a execução da função // e permite resumi-la depois neste mesmo ponto // "yield" também pode retornar um valor, assim // como "return", mas sem terminar a função yield colecao.slice(marcador, marcador + itensPorPagina); marcador += itensPorPagina; } } let colecao = obterArrayMagicamente(); let paginador = paginar(colecao, 10); let pagina = null; function clickHandler() { // o método "next" executa a função de onde parou // até encontrar uma instrução "yield" pagina = paginador.next(); // além do valor retornado por "yield", "next" também indica // se a função terminou definitivamente com um "return" // e nesse caso done teria o valor true // ou se apenas foi interrompida com "yield", e nesse caso // done teria o valor false, e podemos chamar "next" novamente if (!pagina.done) { // pagina.value contém os resultados // mostrar a página } } botaoProximo.addEventListener('click', clickHandler);

Simplificando, uma função normal quando chamada, recebe alguns parâmetros e executa até o fim, retornando um valor. Uma função generator nativa pode ter chamadas parciais, retornando resultados parciais, antes de terminar sua execução completa retornando um valor. Fazemos isso através do método next do iterador que ela retorna e da palavra-chave yield.

Agora, porque foi utilizado o padrão de iteração:

iterador estilo JavaScript ES6

'use strict'; do { resultado = iterador.next(); if (resultado.value) { // verifica antes de usar resultado.value // pode usar resultado.value } } while (!resultado.done)

Ao invés dos mais conhecidos, e que me parecem ligeiramente mais intuitivos:

iterador estilo Java e C#

'use strict'; while (iterator.hasNext()) { resultado = iterator.next(); // use resultado sem medo, ele sempre será válido } while (iterator.MoveNext()) { resultado = iterator.Current; // use resultado sem medo, ele sempre será válido }

Eu acho que não vou descobrir tão cedo. Quero dizer, não é o iterador que terminou? Então não é ele que deveria dizer isso? Qual o motivo de colocar essa informação no resultado??

De qualquer maneira. As chamadas parciais à função generator também podem receber argumentos. Fazemos isso passando um parâmetro na chamada do next. Para manter os exemplos simples, nosso paginador até agora só foi capaz de avançar. Vou continuar com a demonstração dessa funcionalidade duvidosa, adicionando a possibilidade do paginador também voltar (tipo um revival do passado :P ), passando para next o argumento necessário.

paginador usando generators

'use strict'; const AVANCAR = 1; const VOLTAR = -1; function* paginar(colecao, itensPorPagina) { const total = colecao.length; let marcador = 0; let direcao = yield; // inicia "direcao" antes do loop while(marcador >= 0 && marcador < total) { direcao = yield colecao.slice( marcador, marcador + itensPorPagina ); marcador += itensPorPagina * direcao; } } let resultados = obterArrayMagicamente(); let paginador = paginar(resultados); // prepara o paginador para iniciar direcao paginador.next(); function clickAnteriorHandler() { mostrar(VOLTAR); } function clickProximoHandler() { mostrar(AVANCAR); } let result = null; let pagina = null; function mostrar(direcao) { result = paginador.next(direcao); if (!result.done) { pagina = result.value; // mostrar a página } } botaoAnterior.addEventListener('click', clickAnteriorHandler); botaoProximo.addEventListener('click', clickProximoHandler);

Se quisermos implementar nosso próprio iterador, que funcione com a sintaxe for of, nossa classe deve implementar um método especial chamado [Symbol.iterator]. Como esse método precisa ser um generator, usamos uma sintaxe especial para indicar isso, precedendo o nome do método com um *.

Paginador usando Symbol.iterator

'use strict'; class Paginador { constructor(colecao, itensPorPagina) { Object.defineProperties({ _colecao: {value: colecao}, _total: {value: colecao.length}, _itensPorPagina: {value: itensPorPagina, writable: true}, _marcador: {value: 0, writable: true} }); Object.seal(this); this.reset(); } // iterador for of *[Symbol.iterator]() { if(this._marcador < this._total) { yield colecao.slice( this._marcador, this._marcador + this._itensPorPagina ); this._marcador += this._itensPorPagina; } } reset() { this._marcador = 0; } } let colecao = obterArrayMagicamente(); let paginador = new Paginador(colecao, 10); for (let paginas of paginador) { console.log(paginas); }

Como nada disso me parece novo ou incrivelmente útil, vou logo passar para a outra funcionalidade que comentei lá em cima. A de que generators podem ser usados como mais uma alternativa de modelo de execução assíncrono, muito parecida com o async e await do C#. Essa ideia parece ser forte o suficiente a ponto de ter motivado uma nova proposta ao comitê técnico (TC39, que padroniza a especificação a qual o JavaScript se baseia), bem como a criação da biblioteca co.Uma vez que temos uma interface padronizada para identificar, pausar e resumir funções, podemos escrever uma função de ordem superior que controle a execução dos generators.

simulando async e await do C#

'use strict'; // função de controle + function* é quase como async // e yield como await controle(function*() { yield metodoAsync(); yield outroMetodo(); });

Tal função de controle não é tão simples de ser escrita, uma vez que uma implementação simplicista pode não prover toda a fluidez de execução que o modelo assíncrono disponibiliza, causar pilhas de execução muito grandes, etc. já que é necessário conhecimento moderado de como funciona o modelo de execução do JavaScript, o vulgo event loop. Se este é um modelo tão mais prático em JavaScript que do que outras alternativas eu não sei dizer. Certamente preciso escrever vários testes antes de decidir. Apenas gostaria que as pessoas escolhessem um modelo assíncrono e ficassem com ele, ao invés de ficarem adicionando mais e mais alternativas, deixando tudo labirintoso.

Web Workers (Threads)

Threads em JavaScript funcionam de maneira diferente das funcionalidades equivalentes em outras linguagens. Elas não compartilham memória. A maneira de se comunicarem é através de troca de mensagens, que possui limitações sobre o tipo de objeto que pode ser enviado. De fato, threads de outras linguagens se parecem mais com o que conseguimos simular com setTimeout e setInterval do que com objetos Worker. Worker threads não conseguem de maneira nenhuma acessar a interface gráfica.

JavaScript - main.js

'use strict'; // cria a worker thread de uso exclusivo // da main thread let webWorker = new Worker('worker.js'); // web workers não compartilham memória // apenas trocam mensagens entre si // e não podem acessar o DOM // envia mensagens para a worker thread webWorker.postMessage(['algum', 'valor']); webWorker.postMessage([10, 20]); // callback para recepção de mensagens // da worker thread webWorker.onmessage = (event) => { console.log(event.data); }; // mata a thread imediatamente se quiser // webWorker.terminate();

JavaScript - worker.js

'use strict'; // callback para recepção de mensagens // da main thread (ui thread) onmessage = (event) => { console.log(event.data[0]); console.log(event.data[1]); let result = event.data.join(' '); // envia mensagens para a main thread postMessage(result); // termina a thread se quiser // close(); };

JavaScript - sharedThread1.js

'use strict'; // cria uma worker thread compartilhada let webWorkerOp1 = new SharedWorker('worker.js'); // envia mensagens para a worker thread compartilhada webWorkerOp1.port.postMessage(['algum', 'valor']); webWorkerOp1.port.postMessage([10, 20]); // callback para recepção de mensagens // da worker thread webWorkerOp1.port.onmessage = (event) => { console.log(event.data); };

JavaScript - sharedThread2.js

'use strict'; // cria uma worker thread compartilhada let webWorkerOp2 = new SharedWorker('worker.js'); // envia mensagens para a worker thread compartilhada webWorkerOp2.port.postMessage(['misturar', 2]); webWorkerOp2.port.postMessage([10, 20]); // callback para recepção de mensagens // da worker thread webWorkerOp2.port.onmessage = (event) => { console.log(event.data); };

JavaScript - worker.js

'use strict'; onconnect = (event) => { let port = event.ports[0]; // callback para recepção de mensagens // da main thread (ui thread) port.onmessage = (event) => { console.log(event.data[0]); console.log(event.data[1]); let result = event.data.join(' '); // envia mensagens para a main thread port.postMessage(result); // termina a thread se quiser // close(); }; };

Nota final

Podemos verificar nosso código com ESLint, JSHint ou JSLint, que nos dão dicas para melhorá-lo.

Apêndice

SOLUÇÃO 3 - Postergar execução na programação

seosoquenaoS3.htm

<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml' lang='pt-BR' xml:lang='pt-br' > <head> <title>Google está olhando</title> <meta charset='utf-8' /> <script src='//code.jquery.com/jquery-1.10.2.min.js'></script> <script src='seosoquenaoS3.js'></script> </head> <body> <h1 id='tagTitulo'>Acessibilidade e SEO</h1> <p id='tagParagrafo'> Sim pequeno gafanhoto, html tem mais de 100 tags diferentes além de table e div. </p> </body> </html>

seosoquenaoS3b.js

'use strict'; function init() { // pega elementos da página pela id e os torna manipuláveis dentro do JavaScript var titulo = document.getElementById('tagTitulo'); var paragrafo = document.getElementById('tagParagrafo'); titulo.innerHTML = 'Black hat SEO'; paragrafo.attributes.add('style', 'display: none'); } // Espera o documento estar pronto para buscar na página jQuery(document).ready(function(evento) { init(); });

Como citei lá no comecinho, jQuery ganhou fama justamente por esconder as várias incompatibilidades entre os navegadores através de sua API. Este exemplo faz a mesma coisa que o exemplo anterior, porém depende de uma biblioteca externa. Se isso não for um problema pra vc, vai em frente. A sintaxe do jQuery é um pouco diferente do JavaScript convencional, e será abordada mais pra frente.

seosoquenaoS3.js

'use strict'; function init() { // pega elementos da página pela id e os torna manipuláveis dentro do JavaScript var titulo = document.getElementById('tagTitulo'); var paragrafo = document.getElementById('tagParagrafo'); titulo.innerHTML = 'Black hat SEO'; paragrafo.attributes.add('style', 'display: none'); } // verifica se o JavaScript rodou após o evento de carregamento completado da página if (document.readyState === 'interactive' || document.readyState === 'complete' || document.readyState === 'loaded' ) { init(); // se não, se registra para executar no evento } else { // registro do evento para navegadores legais if (document.addEventListener) { document.addEventListener('DOMContentLoaded', init); // registro de evento para IE8- } else if (window.attachEvent) { window.attachEvent('onload', init); } }

Esta solução é a mais compatível. Porém, precisamos adicionar uma verificação meio grande pra tudo funcionar de acordo com o que esperamos. Note que, dependendo do momento em que o JavaScript passa a existir na página, o evento indicando que o processamento do html terminou pode já ter sido disparado. Existe também a questão de compatibilidade com IEs antigos. Claro que podemos refatorar esse código e facilitar o uso deste evento. Mas aqui no ICI fazemos outra coisa. Usamos jQuery.

Ou usando closures.

dropdown.js

'use strict'; let dropdown = (function() { let publico = { init: function(idConteiner) { privado.idConteiner = idConteiner; privado.conteiner = document.getElementById(privado.idConteiner); privado.rotulo = privado.conteiner.querySelector('button'); privado.lista = privado.conteiner.querySelector('ul'); privado.rotulo.addEventListener('click', this.clickHandler.bind(this)); }, clickHandler: function(evento) { privado.visivel = !privado.visivel; privado.atualizar(); } }; let privado = { atualizar: function() { if (this.visible) { this.lista.style.display = 'block'; return; } this.lista.style.display = 'none'; }, idConteiner: null, conteiner: null rotulo: null, lista: null, visivel: false }; return publico; })();