Você está na página 1de 303

Table of Contents

Introduction 1.1

Valores, Tipos e Operadores 1.2


Estrutura do Programa 1.3

Funes 1.4

Estrutura de Dados: Objeto e Array 1.5

Funes de Ordem Superior 1.6

A Vida Secreta dos Objetos 1.7


Prtica: Vida Eletrnica 1.8

Erros e Manipulao de Erros 1.9

Expresses Regulares 1.10


Mdulos 1.11

Prtica: A Linguagem de Programao 1.12

JavaScript e o Navegador 1.13

O Document Object Model 1.14

Manipulando Eventos 1.15

Projeto: Plataforma de Jogo 1.16

Desenhando no Canvas 1.17

HTTP 1.18

Formulrios e Campos de Formulrios 1.19

Projeto: Um Programa de Pintura 1.20


Node.js 1.21

Projeto: Website de Compartilhamento de Habilidades 1.22

1
JavaScript Eloquente - 2 edio
Uma moderna introduo ao JavaScript, programao e maravilhas digitais.

Mantenedor: Eric Douglas

Contedo do Livro
Introduo

1. Valores, Tipos e Operadores - (Parte 1: Linguagem)


2. Estrutura do Programa
3. Funes
4. Estrutura de Dados: Objeto e Array
5. Funes de Ordem Superior
6. A Vida Secreta dos Objetos
7. Prtica: Vida Eletrnica
8. Erros e Manipulao de Erros
9. Expresses Regulares
10. Mdulos

2
11. Prtica: A Linguagem de Programao
12. JavaScript e o Navegador - (Parte 2: Navegador)
13. O Document Ob ject Model
14. Manipulando Eventos
15. Projeto: Plataforma de Jogo
16. Desenhando no Canvas
17. HTTP
18. Formulrios e Campos de Formulrios
19. Projeto: Um Programa de Pintura
20. Node.js - (Parte 3: Node.js)
21. Projeto: Website de Compartilhamento de Habilidades

Status Geral do Projeto


As informaes sobre o status e log de cada captulo esto organizadas nessa issue.

Atualmente, estamos melhorando o que j est traduzido, focando na qualidade e preciso da traduo e
entendimento do texto como um todo, alm de tentar aplicar a gramtica mais correta possvel. Vrios
contribuidores ajudaram em diferentes partes do livro e, por isso, existem diversas oportunidades de melhorias.

Como Contribuir?
Se voc tiver interesse em ajudar, criamos um guia para ajud-lo e, se tiver qualquer dvida, basta abrir uma
issue.

Informaes Importantes
Autor: Marijn Haverbeke
Verso original deste livro.

Licenciado sob a licena Creative Commons attribution-noncommercial.

Todo cdigo neste livro tambm pode ser considerado sob a licena MIT.

3
Valores, Tipos e Operadores
Abaixo da parte superficial da mquina, o programa se movimenta. Sem esforo, ele se expande e se
contrai. Com grande harmonia, os eltrons se espalham e se reagrupam. As formas no monitor so como
ondulaes na gua. A essncia permanece invisvel por baixo.

Master Yuan-Ma, The Book of Programming

Dentro do mundo do computador, h somente dados. Voc pode ler, modificar e criar novos dados, entretanto,
qualquer coisa que no seja um dado simplesmente no existe. Todos os dados so armazenados em longas
sequncias de bits e so, fundamentalmente, parecidos.

Bits podem ser qualquer tipo de coisa representada por dois valores, normalmente descritos como zeros e uns.
Dentro do computador, eles representam formas tais como uma carga eltrica alta ou baixa, um sinal forte ou
fraco ou at um ponto na superfcie de um CD que possui brilho ou no. Qualquer pedao de informao pode ser
reduzido a uma sequncia de zeros e uns e, ento, representados por bits.

Como um exemplo, pense sobre a maneira que o nmero 13 pode ser armazenado em bits. A forma usual de se
fazer esta analogia a forma de escrevermos nmeros decimais, mas ao invs de 10 dgitos, temos apenas 2. E,
ao invs de o valor de um dgito aumentar dez vezes sobre o dgito aps ele, o valor aumenta por um fator de 2.
Estes so os bits que compem o nmero treze, com o valor dos dgitos mostrados abaixo deles:

0 0 0 0 1 1 0 1
128 64 32 16 8 4 2 1

Assim, este o nmero binrio 00001101, ou 8 + 4 + 1, que equivale a 13.

Valores
Imagine um mar de bits, um oceano deles. Um computador moderno possui mais de trinta bilhes de bits em
seu armazenamento voltil de dados. J em armazenamento de dados no volteis, sendo eles o disco rgido ou
algo equivalente, tende a ter uma ordem de magnitude ainda maior.

Para que seja possvel trabalhar com tais quantidades de bits sem ficar perdido, voc pode separ-los em partes
que representam pedaos de informao. No ambiente JavaScript, essas partes so chamadas de valores.
Embora todos os valores sejam compostos por bits, cada valor exerce um papel diferente e todo valor possui um
tipo que determina o seu papel. Existem seis tipos bsicos de valores no JavaScript: nmeros, strings,
Booleanos, objetos, funes e valores indefinidos.

4
Para criar um valor, voc deve simplesmente invocar o seu nome. Isso bastante conveniente, pois voc no
precisa de nenhum material extra para constru-los e muito menos ter que pagar algo por eles. Voc s chama por
ele e pronto, voc o tem. claro que eles no so criados do nada. Todo valor precisa estar armazenado em
algum lugar e, se voc quiser usar uma quantidade enorme deles ao mesmo tempo, voc pode acabar ficando
sem bits. Felizmente, esse um problema apenas se voc precisa deles simultaneamente. A medida que voc
no utiliza um valor, ele ser dissipado, fazendo com que seus bits sejam reciclados, disponibilizando-os para
serem usados na construo de outros novos valores.

Esse captulo introduz os elementos que representam os tomos dos programas JavaScript, que so os simples
tipos de valores e os operadores que podem atuar sobre eles.

Nmeros
Valores do tipo nmero so, sem muitas surpresas, valores numricos. Em um programa JavaScript, eles so
escritos assim:

13

Coloque isso em um programa e isso far com que padres de bits referentes ao nmero 13 sejam criados e
passe a existir dentro da memria do computador.

O JavaScript utiliza um nmero fixo de bits, mais precisamente 64 deles, para armazenar um nico valor numrico.
Existem apenas algumas maneiras possveis que voc pode combinar esses 64 bits, ou seja, a quantidade de
nmeros diferentes que podem ser representados limitada. Para um valor N de dgitos decimais, a quantidade
de nmeros que pode ser representada 10. De forma similar, dado 64 dgitos binrios, voc pode representar
2 nmero diferentes, que aproximadamente 18 quintilhes (o nmero 18 com 18 zeros aps ele). Isso muito.

A memria do computador costumava ser bem menor e, por isso, as pessoas usavam grupos de 8 ou 16 bits
para representar os nmeros. Por isso, era muito fcil extrapolar essa capacidade de armazenamento to
pequena usando nmeros que no cabiam nesse espao. Hoje em dia, at os computadores pessoais possuem
memria suficiente, possibilitando usar grupos de 64 bits, sendo apenas necessrio se preocupar em exceder o
espao quando estiver lidando com nmeros extremamente grandes.

Entretanto, nem todos os nmeros inteiros menores do que 18 quintilhes cabem em um nmero no JavaScript.
Os bits tambm armazenam nmeros negativos, sendo que um desses bits indica o sinal do nmero. Um grande
problema que nmeros fracionrios tambm precisam ser representados. Para fazer isso, alguns bits so
usados para armazenar a posio do ponto decimal. Na realidade, o maior nmero inteiro que pode ser
armazenado est na regio de 9 quatrilhes (15 zeros), que ainda assim extremamente grande.

Nmeros fracionrios so escritos usando um ponto.

9.81

Para nmeros muito grandes ou pequenos, voc pode usar a notao cientfica adicionando um e (de
expoente) seguido do valor do expoente:

2.998e8

Isso 2.998 x 10 = 299800000.

Clculos usando nmeros inteiros menores que os 9 quadrilhes mencionados, sero com certeza precisos.
Infelizmente, clculos com nmero fracionrios normalmente no so precisos. Da mesma forma que (pi) no
pode ser expresso de forma precisa por uma quantidade finita de dgitos decimais, muitos nmeros perdem sua

5
preciso quando existem apenas 64 bits disponveis para armazen-los. Isso vergonhoso, porm causa
problemas apenas em algumas situaes especficas. O mais importante estar ciente dessa limitao e tratar
nmeros fracionrios como aproximaes e no como valores precisos.

Aritmtica
A principal coisa para se fazer com nmeros so clculos aritmticos. As operaes como adio ou
multiplicao recebem dois valores numricos e produzem um novo nmero a partir deles. Elas so
representadas dessa forma no JavaScript:

100 + 4 * 11

Os smbolos + e * so chamados de operadores. O primeiro referente adio e o segundo multiplicao.


Colocar um operador entre dois valores ir aplic-lo a esses valores e produzir um novo valor.

O significado do exemplo anterior adicione 4 e 100 e, em seguida, multiplique esse resultado por 11 ou a
multiplicao realizada antes da adio? Como voc deve ter pensado, a multiplicao acontece primeiro.
Entretanto, como na matemtica, voc pode mudar esse comportamento envolvendo a adio com parnteses.

(100 + 4) * 11

Para subtrao existe o operador - e para a diviso usamos o operador / .

Quando os operadores aparecem juntos sem parnteses, a ordem que eles sero aplicados determinada pela
precedncia deles. O exemplo mostra que a multiplicao ocorre antes da adio. O operador / possui a
mesma precedncia que * e, de forma similar, os operadores + e - possuem a mesma precedncia entre si.
Quando vrios operadores de mesma precedncia aparecem prximos uns aos outros, como por exemplo 1 - 2

+ 1 , eles so aplicados da esquerda para a direita: (1 - 2) + 1 .

Essas regras de precedncia no so algo que voc deve se preocupar. Quando estiver em dvida, apenas
adicione os parnteses.

Existe mais um operador aritmtico que voc talvez no reconhea imediatamente. O smbolo % usado para
representar a operao de resto. X % Y o resto da diviso de X por Y . Por exemplo, 314 % 100 produz 14 e
144 % 12 produz 0 . A precedncia do operador resto a mesma da multiplicao e diviso. Voc ouvir com
frequncia esse operador ser chamado de modulo mas, tecnicamente falando, resto o termo mais preciso.

Nmeros Especiais
Existem trs valores especiais no JavaScript que so considerados nmeros, mas no se comportam como
nmeros normais.

Os dois primeiros so Infinity e -Infinity , que so usados para representar os infinitos positivo e negativo. O
clculo Infinity - 1 continua sendo Infinity , assim como qualquer outra variao dessa conta. Entretanto, no
confie muito em clculos baseados no valor infinito, pois esse valor no matematicamente slido, e
rapidamente nos levar ao prximo nmero especial: NaN .

NaN a abreviao de not a numb er (no um nmero), mesmo sabendo que ele um valor do tipo nmero.
Voc receber esse valor como resultado quando, por exemplo, tentar calcular 0 / 0 (zero dividido por zero),
Infinity - Infinity ou, ento, realizar quaisquer outras operaes numricas que no resultem em um nmero
preciso e significativo.

6
Strings
O prximo tipo bsico de dado a string. Strings so usadas para representar texto, e so escritas delimitando o
seu contedo entre aspas.

"Patch my boat with chewing gum"


'Monkeys wave goodbye'

Ambas as aspas simples e duplas podem ser usadas para representar strings, contanto que as aspas abertas
sejam iguais no incio e no fim.

Quase tudo pode ser colocado entre aspas, e o JavaScript criar um valor do tipo string com o que quer que seja.
Entretanto, alguns caracteres so mais difceis. Voc pode imaginar como deve ser complicado colocar aspas
dentro de aspas. Alm disso, os caracteres newlines (quebra de linhas, usados quando voc aperta Enter),
tambm no podem ser colocados entre aspas. As strings devem permanecer em uma nica linha.

Para que seja possvel incluir tais caracteres em uma string, a seguinte notao utilizada: toda vez que um
caractere de barra invertida ( \ ) for encontrado dentro de um texto entre aspas, ele indicar que o caractere
seguinte possui um significado especial. Isso chamado de escapar o caractere. Uma aspa que se encontra
logo aps uma barra invertida no representar o fim da string e, ao invs disso, ser considerada como parte do
texto dela. Quando um caractere n aparecer aps uma barra invertida, ele ser interpretado como uma quebra de
linha e, de forma similar, um t significar um caractere de tabulao. Considere a seguinte string:

"This is the first line\nAnd this is the second"

O texto na verdade ser:

This is the first line


And this is the second

Existe, obviamente, situaes nas quais voc vai querer que a barra invertida em uma string seja apenas uma
barra invertida e no um cdigo especial. Nesse caso, se duas barras invertidas estiverem seguidas uma da
outra, elas se anulam e apenas uma ser deixada no valor da string resultante. Essa a forma na qual a string A

newline character is written like \n. pode ser representada:

"A newline character is written like \"\\n\"."

Strings no podem ser divididas, multiplicadas nem subtradas, entretanto, o operador + pode ser usado nelas.
Ele no efetua a adio, mas concatena, ou seja, junta duas strings em uma nica string. O prximo exemplo
produzir a string "concatenate" :

"con" + "cat" + "e" + "nate"

Existem outras maneiras de manipular as strings, as quais sero discutidas quando chegarmos aos mtodos no
Captulo 4.

Operadores Unrios
Nem todos os operadores so smbolos, sendo que alguns so escritos como palavras. Um exemplo o
operador typeof , que produz um valor do tipo string contendo o nome do tipo do valor que voc est verificando.

7
console.log(typeof 4.5)
// number
console.log(typeof "x")
// string

Ns vamos usar console.log nos cdigos de exemplo para indicar que desejamos ver o resultado da avaliao de
algo. Quando voc executar tais cdigos, o valor produzido ser mostrado na tela, entretanto, a forma como ele
ser apresentado vai depender do ambiente JavaScript que voc usar para rodar os cdigos.

Todos os operadores que vimos operavam em dois valores, mas typeof espera um nico valor. Operadores que
usam dois valores so chamados de operadores b inrios, enquanto que aqueles que recebem apenas um, so
chamados de operadores unrios. O operador - pode ser usado tanto como binrio quanto como unrio.

console.log(- (10 - 2))


// -8

Valores Booleanos
Voc frequentemente precisar de um valor para distinguir entre duas possibilidades, como por exemplo sim e
no, ou ligado e desligado. Para isso, o JavaScript possui o tipo Booleano, que tem apenas dois valores:
verdadeiro e falso (que so escritos como true e false respectivamente).

Comparaes
Essa uma maneira de produzir valores Booleanos:

console.log(3 > 2)
// true
console.log(3 < 2)
// false

Os sinais > e < so tradicionalmente smbolos para representar maior que e menor que
respectivamente. Eles so operadores binrios, e o resultado da aplicao deles um valor Booleano que indica
se eles so verdadeiros nesse caso.

Strings podem ser comparadas da mesma forma.

console.log("Aardvark" < "Zoroaster")


// true

A forma na qual as strings so ordenadas mais ou menos alfabtica. Letras maisculas sero sempre
menores que as minsculas, portanto, Z < a verdadeiro. Alm disso, caracteres no alfabticos (!, -, e
assim por diante) tambm so includos nessa ordenao. A comparao de fato, baseada no padro Unicode,
que atribui um nmero para todos os caracteres que voc possa precisar, incluindo caracteres do Grego, rabe,
Japons, Tmil e por a vai. Possuir tais nmeros til para armazenar as strings dentro do computador, pois faz
com que seja possvel represent-las como uma sequncia de nmeros. Quando comparamos strings, o
JavaScript inicia da esquerda para a direita, comparando os cdigos numricos dos caracteres um por um.

Outros operadores parecidos so >= (maior que ou igual a), <= (menor que ou igual a), == (igual a) e != (no
igual a).

console.log("Itchy" != "Scratchy")
// true

8
Existe apenas um valor no JavaScript que no igual a ele mesmo, que o valor NaN . Ele significa not a
numb er, que em portugus seria traduzido como no um nmero.

console.log(NaN == NaN)
// false

NaN supostamente usado para indicar o resultado de alguma operao que no tenha sentido e, por isso, ele
no ser igual ao resultado de quaisquer outras operaes sem sentido.

Operadores Lgicos
Existem tambm operadores que podem ser aplicados aos valores Booleanos. O JavaScript d suporte a trs
operadores lgicos: and, or e not, que podem ser traduzidos para o portugus como e, ou e no. Eles podem ser
usados para "pensar" de forma lgica sobre Booleanos.

O operador && representa o valor lgico and ou, em portugus, e. Ele um operador binrio, e seu resultado
apenas verdadeiro se ambos os valores dados ele forem verdadeiros.

console.log(true && false)


// false
console.log(true && true)
// true

O operador || indica o valor lgico or ou, em portugus, ou. Ele produz um valor verdadeiro se qualquer um dos
valores dados ele for verdadeiro.

console.log(false || true)
// true
console.log(false || false)
// false

Not, em portugus no, escrito usando um ponto de exclamao ( ! ). Ele um operador unrio que inverte o
valor que dado ele. Por exemplo, !true produz false e !false produz true .

Quando misturamos esses operadores Booleanos com operadores aritmticos e outros tipos de operadores,
nem sempre bvio quando devemos usar ou no os parnteses. Na prtica, voc normalmente no ter
problemas sabendo que, dos operadores que vimos at agora, || possui a menor precedncia, depois vem o
operador && , em seguida vm os operadores de comparao ( > , == , etc) e, por ltimo, quaisquer outros
operadores. Essa ordem foi escolhida de tal forma que, em expresses tpicas como o exemplo a seguir, poucos
parnteses so realmente necessrios:

1 + 1 == 2 && 10 * 10 > 50

O ltimo operador lgico que iremos discutir no unrio nem binrio, mas ternrio, operando em trs valores.
Ele escrito usando um ponto de interrogao e dois pontos, como mostrado abaixo:

console.log(true ? 1 : 2);
// 1
console.log(false ? 1 : 2);
// 2

9
Esse operador chamado de operador condicional (algumas vezes chamado apenas de operador ternrio, j
que o nico operador desse tipo na linguagem). O valor presente esquerda do ponto de interrogao
seleciona qual dos outros dois valores ser retornado. Quando ele for verdadeiro, o valor do meio escolhido e,
quando for falso, o valor direita retornado.

Valores Indefinidos
Existem dois valores especiais, null e undefined , que so usados para indicar a ausncia de um valor com
significado. Eles so valores por si ss, mas no carregam nenhum tipo de informao.

Muitas operaes na linguagem que no produzem um valor com significado (voc ver alguns mais para frente)
retornaro undefined simplesmente porque eles precisam retornar algum valor.

A diferena de significado entre undefined e null um acidente que foi criado no design do JavaScript, e no faz
muita diferena na maioria das vezes. Nos casos em que voc deve realmente se preocupar com esses valores,
recomendo trat-los como valores idnticos (vamos falar mais sobre isso em breve).

Converso Automtica de Tipo


Na introduo, mencionei que o JavaScript tentar fazer o seu melhor para aceitar quase todos os programas que
voc fornecer, inclusive aqueles que fazem coisas bem estranhas. Isso pode ser demonstrado com as seguintes
expresses:

console.log(8 * null)
// 0
console.log("5" - 1)
// 4
console.log("5" + 1)
// 51
console.log("five" * 2)
// NaN
console.log(false == 0)
// true

Quando um operador aplicado a um tipo de valor errado, o JavaScript converter, de forma silenciosa, esse
valor para o tipo que ele desejar, usando uma srie de regras que muitas vezes no o que voc deseja ou
espera. Esse comportamento chamado de coero de tipo (ou converso de tipo). Portanto, na primeira
expresso, null se torna 0 e, na segunda, a string "5" se torna o nmero 5 . J na terceira expresso, o
operador + tenta efetuar uma concatenao de string antes de tentar executar a adio numrica e, por isso, o
nmero 1 convertido para a string "1" .

Quando algo que no pode ser mapeado como um nmero de forma bvia (tais como "five" ou undefined )
convertido para um nmero, o valor NaN produzido. Quaisquer outras operaes aritmticas realizadas com
NaN continuam produzindo NaN , portanto, quando voc perceber que est recebendo esse valor em algum lugar
inesperado, procure por converses acidentais de tipo.

Quando comparamos valores do mesmo tipo usando o operador == , o resultado fcil de se prever: voc
receber verdadeiro quando ambos os valores forem o mesmo, exceto no caso de NaN . Por outro lado, quando
os tipos forem diferentes, o JavaScript usa um conjunto de regras complicadas e confusas para determinar o que
fazer, sendo que, na maioria dos casos, ele tenta apenas converter um dos valores para o mesmo tipo do outro
valor. Entretanto, quando null ou undefined aparece em algum dos lados do operador, ser produzido verdadeiro
apenas se ambos os lados forem null ou undefined .

10
console.log(null == undefined);
// true
console.log(null == 0);
// false

O ltimo exemplo um comportamento que normalmente bastante til. Quando quiser testar se um valor possui
um valor real ao invs de null ou undefined , voc pode simplesmente compar-lo a null com o operador ==

(ou != ).

Mas e se voc quiser testar se algo se refere ao valor preciso false ? As regras de converso de strings e
nmeros para valores booleanos afirmam que 0 , NaN e empty strings contam como false , enquanto todos os
outros valores contam como true . Por causa disso, expresses como 0 == false e "" == false retornam true .
Para casos assim, onde voc no quer qualquer converso automtica de tipos acontecendo, existem dois tipos
extras de operadores: === e !== . O primeiro teste se o valor precisamente igual ao outro, e o segundo testa se
ele no precisamente igual. Ento "" === false falso como esperado.

Usar os operadores de comparao de trs caracteres defensivamente, para prevenir inesperadas converses de
tipo que o faro tropear, algo que eu recomendo. Mas quando voc tem certeza de que os tipos de ambos os
lados sero iguais, ou que eles vo ser ambos null / undefined , no h problemas em usar os operadores
curtos.

O Curto-Circuito de && e ||
Os operadores lgicos && e || tem uma maneira peculiar de lidar com valores de tipos diferentes. Eles vo
converter o valor sua esquerda para o tipo booleano a fim de decidir o que fazer, mas ento, dependendo do
operador e do resultado da converso, eles ou retornam o valor esquerda original, ou o valor direita.

O operador || vai retornar o valor sua esquerda quando ele puder ser convertido em true , ou valor sua
direita caso contrrio. Ele faz a coisa certa para valores booleanos, e vai fazer algo anlogo para valores de outros
tipos. Isso muito til, pois permite que o operador seja usado para voltar um determinado valor predefinido.

console.log(null || "user")
// user
console.log("Karl" || "user")
// Karl

O operador && trabalha similarmente, mas ao contrrio. Quando o valor sua esquerda algo que se torne
false , ele retorna o valor, e caso contrrio ele retorna o valor sua direita.

Outro importante propriedade destes 2 operadores que a expresso a sua direita avaliada somente quando
necessrio. No caso de true || X , no importa o que X - pode ser uma expresso que faa algo terrvel - o
resultado vai ser verdadeiro, e X nunca avaliado. O mesmo acontece para false && X , que falso, e vai ignorar
X .

Resumo
Ns vimos 4 tipos de valores do JavaScript neste captulo. Nmeros, strings, booleanos e valores indefinidos.

Alguns valores so criados digitando seu nome ( true , null ) ou valores (13, "abc" ). Eles podem ser
combinados e transformados com operadores. Ns vimos operadores binrios para aritmtica ( + , - , * , / ,e
% ), um para concatenao de string ( + ), comparao ( == , != , === , !== , < , > , <= , >= ) e lgica ( && , || ),

11
como tambm vrios operadores unrios ( - para negativar um nmero, ! para negar uma lgica, e typeof

para encontrar o tipo do valor).

Isto lhe d informao suficiente para usar o JavaScript como uma calculadora de bolso, mas no muito mais. O
prximo captulo vai comear a amarrar essas operaes bsicas conjuntamente dentro de programas bsicos.

12
Estrutura do Programa
O meu corao vermelho brilha nitidamente sob minha pele e ele tm que administrar 10cc de JavaScript
para fazer com que eu volte (Eu respondi bem a toxinas no sangue). Cara, esse negcio vai chutar os
pssegos de direita para fora!

_why, Why's (Poignant) Guide to Ruby

Este o ponto onde ns comeamos a fazer coisas que podem realmente ser chamadas de programao. Ns
vamos expandir nosso domnio da linguagem JavaScript para alm dos substantivos e fragmentos de sentenas
que ns vimos anteriormente, para o ponto onde poderemos realmente expressar algo mais significativo.

Expresses e Afirmaes
No Captulo 1 ns criamos alguns valores e ento aplicamos operadores para obter novos valores. Criar valores
desta forma uma parte essencial de todo programa JavaScript, mas isso somente uma parte. Um fragmento
de cdigo que produz um valor chamado de expresso. Todo valor que escrito literalmente (como 22 ou
"psychoanalysis" ) uma expresso. Uma expresso entre parnteses tambm uma expresso, e tambm um
operador binrio aplicado a duas expresses, ou um unrio aplicado a uma.

Isso mostra parte da beleza da interface baseada na linguagem. Expresses podem ser encadeadas de forma
semelhante s subfrases usadas na linguagem humana - uma subfrase pode conter sua prpria subfrase, e
assim por diante. Isto nos permite combinar expresses para expressar computaes complexas arbitrariamente.

Se uma expresso corresponde a um fragmento de sentena, uma afirmao, no JavaScript, corresponde a uma
frase completa em linguagem humana. Um programa simplesmente uma lista de afirmaes.

O tipo mais simples de afirmao uma expresso com um ponto e vrgula depois dela. Este o programa:

1;
!false;

um programa intil, entretanto. Uma expresso pode ser apenas para produzir um valor, que pode ento ser
usado para fechar a expresso. Uma declarao vale por si s, e s equivale a alguma coisa se ela afeta em
algo. Ela pode mostrar algo na tela - que conta como mudar algo - ou pode mudar internamente o estado da
mquina de uma forma que vai afetar outras declaraes que iro vir. Estas mudanas so chamadas efeitos
colaterais. As afirmaes nos exemplos anteriores somente produzem o valor 1 e true e ento imediatamente
os jogam fora novamente. No deixam nenhuma impresso no mundo. Quando executamos o programa, nada
acontece.

Ponto e vrgula
Em alguns casos, o JavaScript permite que voc omita o ponto e vrgula no fim de uma declarao. Em outros
casos ele deve estar l ou coisas estranhas iro acontecer. As regras para quando ele pode ser seguramente
omitido so um pouco complexas e propensas a erro. Neste livro todas as declaraes que precisam de ponto e
vrgula vo sempre terminar com um. Eu recomendo a voc fazer o mesmo em seus programas, ao menos at
voc aprender mais sobre as sutilezas envolvidas em retirar o ponto e vrgula.

Variveis

13
Como um programa mantm um estado interno? Como ele se lembra das coisas? Ns vimos como produzir
novos valores com valores antigos, mas isso no altera os valores antigos, e o valor novo deve ser imediatamente
usado ou vai ser dissipado. Para pegar e guardar valores, o JavaScript fornece uma coisa chamada varivel.

var caught = 5 * 5;

E isso nos d um segundo tipo de declarao. A palavra especial (palavra-chave) var indica que esta sentena
vai definir uma varivel. Ela seguida pelo nome da varivel e, se ns quisermos d-la imediatamente um valor,
por um operador = e uma expresso.

A declarao anterior criou uma varivel chamada caught e a usou para armazenar o valor que foi produzido pela
multiplicao 5 por 5.

Depois de uma varivel ter sido definida, seu nome pode ser usado como uma expresso. O valor da expresso
o valor atual mantido pela varivel. Aqui temos um exemplo:

var ten = 10;


console.log(ten * ten);
// 100

Nomes de variveis podem ser quase qualquer palavra, menos as reservadas para palavras-chave (como var ).
No pode haver espaos includos. Dgitos podem tambm ser parte dos nomes de variveis - catch22 um
nome vlido, por exemplo - mas um nome no pode iniciar com um dgito. O nome de uma varivel no pode
incluir pontuao, exceto pelos caracteres $ e _ .

Quando uma varivel aponta para um valor, isso no significa que estar ligada ao valor para sempre. O operador
= pode ser usado a qualquer hora em variveis existentes para desconect-las de seu valor atual e ento
apont-las para um novo:

var mood = "light";


console.log(mood);
// light
mood = "dark";
console.log(mood);
// dark

Voc deve imaginar variveis como tentculos, ao invs de caixas. Elas no contm valores; elas os agarram -
duas variveis podem referenciar o mesmo valor. Somente os valores que o programa mantm tem o poder de
ser acessado por ele. Quando voc precisa se lembrar de algo, voc aumenta o tentculo para segurar ou
recoloca um de seus tentculos existentes para fazer isso.

Quando voc define uma varivel sem fornecer um valor a ela, o tentculo fica conceitualmente no ar - ele no tem
nada para segurar. Quando voc pergunta por um valor em um lugar vazio, voc recebe o valor undefined .

14
Um exemplo. Para lembrar da quantidade de dlares que Luigi ainda lhe deve, voc cria uma varivel. E ento
quando ele lhe paga 35 dlares, voc d a essa varivel um novo valor.

var luigisDebt = 140;


luigisDebt = luigisDebt - 35;
console.log(luigisDebt);
// 105

Palavras-chave e Palavras Reservadas


Palavras que tem um significado especial, como var , no podem ser usadas como nomes de variveis. Estas
so chamadas keywords (palavras-chave). Existe tambm algumas palavras que so reservadas para uso em
futuras verses do JavaScript. Estas tambm no so oficialmente autorizadas a serem utilizadas como nomes
de variveis, embora alguns ambientes JavaScript as permitam. A lista completa de palavras-chave e palavras
reservadas um pouco longa:

break case catch continue debugger default delete do else false finally for function if implements

in instanceof interface let new null package private protected public return static switch throw true

try typeof var void while with yield this

No se preocupe em memoriz-las, mas lembre-se que este pode ser o problema quando algo no funcionar
como o esperado.

O Ambiente
A coleo de variveis e seus valores que existem em um determinado tempo chamado de environment

(ambiente). Quando um programa inicia, o ambiente no est vazio. Ele ir conter no mnimo o nmero de
variveis que fazem parte do padro da linguagem. E na maioria das vezes haver um conjunto adicional de
variveis que fornecem maneiras de interagir com o sistema envolvido. Por exemplo, em um navegador, existem
variveis que apontam para funcionalidades que permitem a voc inspecionar e influenciar no atual carregamento
do website, e ler a entrada do mouse e teclado da pessoa que est usando o navegador.

Funes

15
Muitos dos valores fornecidos no ambiente padro so do tipo function (funo). Uma funo um pedao de
programa envolvido por um valor. Este valor pode ser aplicado a fim de executar o programa envolvido. Por
exemplo, no ambiente do navegador, a varivel alert detm uma funo que mostra uma pequena caixa de
dilogo com uma mensagem. usada da seguinte forma:

alert("Good morning!");

Executar uma funo denominado invocar, chamar ou aplicar uma funo. Voc pode chamar uma funo
colocando os parnteses depois da expresso que produz um valor de funo. Normalmente voc ir usar o
nome da varivel que contm uma funo diretamente. Os valores entre os parnteses so passados ao
programa dentro da funo. No exemplo, a funo alert usou a string que foi passada como o texto a ser
mostrado na caixa de dilogo. Os valores passados para funes so chamados de arguments (argumentos). A
funo alert precisa somente de um deles, mas outras funes podem precisar de diferentes quantidades ou
tipos de argumentos.

A Funo console.log

A funo alert pode ser til como sada do dispositivo quando experimentada, mas clicar sempre em todas
estas pequenas janelas vai lhe irritar. Nos exemplos passados, ns usamos console.log para sada de valores. A
maioria dos sistemas JavaScript (incluindo todos os navegadores modernos e o Node.js), fornecem uma funo
console.log que escreve seus argumentos como texto na sada do dispositivo. Nos navegadores, a sada fica no
console JavaScript. Esta parte da interface do browser fica oculta por padro, mas muitos browsers abrem
quando voc pressiona F12 , ou no Mac, quando voc pressiona Command + option + I . Se isso no funcionar,
busque no menu algum item pelo nome de web console ou developer tools.

Quando rodarmos os exemplos ou seu prprio cdigo nas pginas deste livro, o console.log vai mostrar embaixo
o exemplo, ao invs de ser no console JavaScript.

var x = 30;
console.log("o valor de x ", x);
// o valor de x 30

Embora eu tenha afirmado que nomes de variveis no podem conter pontos, console.log claramente contm um
ponto. Eu no tinha mentido para voc. Esta no uma simples varivel, mas na verdade uma expresso que
retorna o campo log do valor contido na varivel console . Ns vamos entender o que isso significa no captulo 4.

Retornando Valores
Mostrar uma caixa de dilogo ou escrever texto na tela um efeito colateral. Muitas funes so teis por causa
dos efeitos que elas produzem. tambm possvel para uma funo produzir um valor, no caso de no ser
necessrio um efeito colateral. Por exemplo, temos a funo Math.max , que pega dois nmeros e retorna o maior
entre eles:

console.log(Math.max(2, 4));

16
Quando uma funo produz um valor, dito que ela retorna ( return ) ele. Em JavaScript, tudo que produz um valor
uma expresso, o que significa que chamadas de funo podem ser usadas dentro de expresses maiores. No
exemplo abaixo, uma chamada para a funo Math.min , que o oposto de Math.max , usada como uma das
entradas para o operador de soma:

console.log(Math.min(2, 4) + 100);

O prximo captulo explica como ns podemos escrever nossas prprias funes.

prompt e confirm

O ambiente fornecido pelos navegadores contm algumas outras funes para mostrar janelas. Voc pode
perguntar a um usurio uma questo Ok/Cancel usando confirm . Isto retorna um valor booleano: true se o
usurio clica em OK e false se o usurio clica em Cancel.

confirm("Shall we, then?");

prompt pode ser usado para criar uma questo "aberta". O primeiro argumento a questo; o segundo o texto
que o usurio inicia. Uma linha do texto pode ser escrita dentro da janela de dilogo, e a funo vai retornar isso
como uma string.

prompt("Tell me everything you know.", "...");

Estas duas funes no so muito usadas na programao moderna para web, principalmente porque voc no
tem controle sobre o modo que a janela vai aparecer, mas elas so teis para experimentos.

Fluxo de Controle
Quando seu programa contm mais que uma declarao, as declaraes so executadas, previsivelmente, de
cima para baixo. Como um exemplo bsico, este programa tem duas declaraes. A primeira pergunta ao usurio
por um nmero, e a segunda, que executada posteriormente, mostra o quadrado deste nmero:

var theNumber = Number(prompt("Pick a number", ""));


alert("Your number is the square root of " + theNumber * theNumber);

A funo Number converte o valor para um nmero. Ns precisamos dessa converso pois o resultado de prompt

um valor do tipo string , e ns queremos um nmero. Existem funes similares chamadas String e Boolean

que convertem valores para estes tipos.

17
Aqui podemos ver uma representao bem trivial do fluxo de controle em linha reta:

Execuo Condicional
Executar declaraes em ordem linear no a nica opo que temos. Uma alternativa a execuo condicional,
onde escolhemos entre duas rotas diferentes baseado em um valor booleano, como ilustra a seguir:

A execuo condicional escrita, em JavaScript, com a palavra-chave if . No caso mais simples, ns queremos
que algum cdigo seja executado se, e somente se, uma certa condio existir. No programa anterior, por
exemplo, podemos mostrar o quadrado do dado fornecido como entrada apenas se ele for realmente um nmero.

var theNumber = Number(prompt("Pick a number", ""));


if (!isNaN(theNumber))
alert("Your number is the square root of " +
theNumber * theNumber);

Com essa modificao, se voc fornecer "queijo" como argumento de entrada, nenhuma sada ser retornada.

A palavra-chave if executa ou no uma declarao baseada no resultado de uma expresso Booleana. Tal
expresso escrita entre parnteses logo aps a palavra-chave e seguida por uma declarao a ser executada.

A funo isNaN uma funo padro do JavaScript que retorna true apenas se o argumento fornecido for NaN .A
funo Number retorna NaN quando voc fornece a ela uma string que no representa um nmero vlido. Por isso,
a condio se traduz a "a no ser que theNumber no seja um nmero, faa isso".

Voc frequentemente no ter cdigo que executa apenas quando uma condio for verdadeira, mas tambm
cdigo que lida com o outro caso. Esse caminho alternativo representado pela segunda seta no diagrama. A
palavra-chave else pode ser usada, juntamente com if , para criar dois caminhos distintos de execuo.

var theNumber = Number(prompt("Pick a number", ""));


if (!isNaN(theNumber))
alert("Your number is the square root of " +
theNumber * theNumber);
else
alert("Hey. Why didn't you give me a number?");

18
Se tivermos mais que dois caminhos a escolher, mltiplos pares de if / else podem ser "encadeados". Aqui
temos um exemplo:

var num = Number(prompt("Pick a number", "0"));

if (num < 10)


alert("Small");
else if (num < 100)
alert("Medium");
else
alert("Large");

O programa ir primeiramente verificar se num menor que 10. Se for, ele escolhe esse caminho, mostra "Small"
e termina sua execuo. Se no for, ele escolhe o caminho else , que contm o segundo if . Se a segunda
condio (< 100) for verdadeira, o nmero est entre 10 e 100, e "Medium" ser mostrado. Caso contrrio, o
segundo e ltimo else ser escolhido.

O esquema de setas para este programa parece com algo assim:

Loops While e Do
Considere um programa que imprime todos os nmeros pares de 0 a 12. Uma forma de escrever isso :

console.log(0);
console.log(2);
console.log(4);
console.log(6);
console.log(8);
console.log(10);
console.log(12);

Isso funciona, mas a ideia de escrever um programa fazer com que algo seja menos trabalhoso, e no o
contrrio. Se precisarmos de todos os nmeros pares menores do que 1.000, essa abordagem seria invivel. O
que precisamos de uma maneira de repetir cdigo. Essa forma de fluxo de controle chamada de lao de
repetio (loop).

19
O fluxo de controle do loop nos permite voltar a um mesmo ponto no programa onde estvamos anteriormente e
repeti-lo no estado atual do programa. Se combinarmos isso a uma varivel contadora, conseguimos fazer algo
assim:

var number = 0;
while (number <= 12) {
console.log(number);
number = number + 2;
}
// 0
// 2
// etcetera

Uma declarao que inicia com a palavra-chave while cria um loop. A palavra while acompanhada por uma
expresso entre parnteses e seguida por uma declarao, similar ao if . O loop continua executando a
declarao enquanto a expresso produzir um valor que, aps convertido para o tipo Booleano, seja true .

Nesse loop, queremos imprimir o nmero atual e somar dois em nossa varivel. Sempre que precisarmos
executar mltiplas declaraes dentro de um loop, ns as envolvemos com chaves ( { e } ). As chaves, para
declaraes, so similares aos parnteses para as expresses, agrupando e fazendo com que sejam tratadas
como uma nica declarao. Uma sequncia de declaraes envolvidas por chaves chamada de b loco.

Muitos programadores JavaScript envolvem cada if e loop com chaves. Eles fazem isso tanto para manter a
consistncia quanto para evitar que seja necessrio adicionar ou remover chaves quando houver alteraes
posteriores no nmero de declaraes. Nesse livro, para sermos mais breves, iremos escrever sem chaves a
maioria das declaraes compostas por uma nica linha. Fique a vontade para escolher o estilo que preferir.

A varivel number demonstra uma maneira na qual variveis podem verificar o progresso de um programa. Toda
vez que o loop se repete, number incrementado por 2 . No incio de cada repetio, ele comparado com o
nmero 12 para decidir se o programa terminou de executar todo o trabalho esperado.

Como um exemplo de algo que seja til, podemos escrever um programa que calcula e mostra o valor de 2 (2
elevado dcima potncia). Ns usamos duas variveis: uma para armazenar o resultado e outra para contar
quantas vezes multiplicamos esse resultado por 2. O loop testa se a segunda varivel j atingiu o valor 10 e ento
atualiza ambas as variveis.

var result = 1;
var counter = 0;
while (counter < 10) {
result = result * 2;
counter = counter + 1;
}
console.log(result);
// 1024

20
O contador pode tambm iniciar com 1 e checar o valor com <= 10 , mas por razes que iremos ver no Captulo
4, uma boa ideia se acostumar a usar a contagem iniciando com zero.

O loop do uma estrutura de controle similar ao while . A nica diferena entre eles que o do sempre executa
suas declaraes ao menos uma vez e inicia o teste para verificar se deve parar ou no apenas aps a primeira
execuo. Para demonstrar isso, o teste aparece aps o corpo do loop:

do {
var name = prompt("Who are you?");
} while (!name);
console.log(name);

Esse programa ir forar voc a informar um nome. Ele continuar pedindo at que seja fornecido um valor que
no seja uma string vazia. Aplicar o operador ! faz com que o valor seja convertido para o tipo Booleano antes de
neg-lo, e todas as strings exceto "" convertem para true .

Indentando Cdigo
Voc deve ter reparado nos espaos que coloco em algumas declaraes. No JavaScript, eles no so
necessrios e o computador ir aceitar o programa sem eles. De fato, at as quebras de linhas so opcionais. Se
voc quiser, pode escrever um programa inteiro em uma nica linha. O papel da indentao dentro dos blocos
fazer com que a estrutura do cdigo se destaque. Em cdigos complexos, onde temos blocos dentro de blocos,
pode se tornar extremamente difcil distinguir onde um bloco comea e o outro termina. Com a indentao
adequada, o formato visual do programa corresponde ao formato dos blocos contidos nele. Gosto de usar dois
espaos para cada bloco, mas essa preferncia pode variar algumas pessoas usam quatro espaos e outras
usam caracteres "tab".

Loops For
Vrios loops seguem o padro visto nos exemplos anteriores do while . Primeiramente uma varivel "contadora"
criada para monitorar o progresso do loop. Em seguida, temos o loop while que contm uma expresso de
teste que normalmente checa se o contador alcanou algum limite. O contador atualizado no final do corpo do
loop, permitindo acompanhar o progresso.

Por esse padro ser muito comum, o JavaScript e linguagens similares fornecem uma forma um pouco mais
curta e compreensiva chamada de loop for .

for (var number = 0; number <= 12; number = number + 2)


console.log(number);
// 0
// 2
// etcetera

Esse programa equivalente ao exemplo anterior que imprime nmeros pares. A nica diferena que todas as
declaraes relacionadas ao "estado" do loop esto agora agrupadas.

Os parnteses aps a palavra-chave for devem conter dois pontos e vrgulas. A parte anterior ao primeiro ponto
e vrgula inicializa o loop, normalmente definindo uma varivel. A segunda parte a expresso que verifica se o
loop deve continuar ou no. A parte final atualiza o estado do loop aps cada iterao. Na maioria dos casos,
essa construo menor e mais clara que a do while .

Aqui est o cdigo que calcula 2 usando for ao invs de while :

21
var result = 1;
for (var counter = 0; counter < 10; counter = counter + 1)
result = result * 2;
console.log(result);
// 1024

Repare que mesmo no abrindo o bloco com { , a declarao no loop continua indentada com dois espaos
para deixar claro que ela "pertence" linha anterior a ela.

Quebrando a execuo de um Loop


Ter uma condio que produza um resultado false no a nica maneira que um loop pode parar. Existe uma
declarao especial chamada break que tem o efeito de parar a execuo e sair do loop em questo.

Esse programa ilustra o uso da declarao break . Ele encontra o primeiro nmero que , ao mesmo tempo,
maior ou igual a 20 e divisvel por 7.

for (var current = 20; ; current++) {


if (current % 7 == 0)
break;
}
console.log(current);
// 21

Usar o operador resto ( % ) uma maneira fcil de testar se um nmero divisvel por outro. Se for, o resto da
diviso entre eles zero.

A construo do for nesse exemplo no contm a parte que checa pelo fim do loop. Isso significa que o loop no
vai parar de executar at que a declarao break contida nele seja executada.

Se voc no incluir a declarao break ou acidentalmente escrever uma condio que sempre produza um
resultado true , seu programa ficar preso em um loop infinito. Um programa preso em um loop infinito nunca vai
terminar sua execuo, o que normalmente uma coisa ruim.

Se voc criar um loop infinito em algum dos exemplos destas pginas, voc normalmente ser perguntado se
deseja interromper a execuo do script aps alguns segundos. Se isso no funcionar, voc dever fechar a aba
que est trabalhando, ou em alguns casos, fechar o navegador para recuper-lo.

A palavra-chave continue similar ao break , de modo que tambm influencia o progresso de um loop. Quando
continue encontrado no corpo de um loop, o controle de execuo pula para fora do corpo e continua
executando a prxima iterao do loop.

Atualizando variveis sucintamente


Um programa, especialmente quando em loop, muitas vezes precisa de atualizar uma varivel para armazenar
um valor baseado no valor anterior dessa varivel.

counter = counter + 1;

O JavaScript fornece um atalho para isso:

counter += 1;

22
Atalhos similares funcionam para outros operadores, como result *= 2 para dobrar o result ou counter -= 1

para diminuir um.

Isto nos permite encurtar nosso exemplo de contagem um pouco mais:

for (var number = 0; number <= 12; number += 2)


console.log(number);

Para counter += 1 e counter -= 1 , existem equivalentes mais curtos: counter++ e counter--

Resolvendo um valor com switch

comum que o cdigo fique assim:

if (variable == "value1") action1();


else if (variable == "value2") action2();
else if (variable == "value3") action3();
else defaultAction();

H um construtor chamado switch que se destina a resolver o envio de valores de uma forma mais direta.
Infelizmente, a sintaxe JavaScript usada para isso (que foi herdada na mesma linha de linguagens de
programao, C e Java) um pouco estranha - frequentemente uma cadeia de declaraes if continua
parecendo melhor. Aqui est um exemplo:

switch (prompt("What is the weather like?")) {


case "rainy":
console.log("Remember to bring an umbrella.");
break;
case "sunny":
console.log("Dress lightly.");
case "cloudy":
console.log("Go outside.");
break;
default:
console.log("Unknown weather type!");
break;
}

Dentro do bloco aberto pelo switch , voc pode colocar qualquer nmero de rtulo no case . O programa vai pular
para o rtulo correspondente ao valor que switch fornece, ou para default se nenhum valor for encontrado. Ento
ele comea a executar as declaraes, e continua a passar pelos rtulos, at encontrar uma declarao break .
Em alguns casos, como no exemplo case "sunny" , pode ser usado para compartilhar algum cdigo entre os
cases (ele recomenda "ir l fora" para ambos os tempos sunny e cloudy ). Mas tenha cuidado: fcil esquecer de
um break , o que far com que o programa execute cdigo que voc no gostaria de executar.

Capitalizao
Nomes de variveis no podem conter espaos, no entanto muito til usar mltiplas palavras para descrever
claramente o qu a varivel representa. Estas so praticamente suas escolhas para escrever nomes de variveis
com vrias palavras:

23
fuzzylittleturtle
fuzzy_little_turtle
FuzzyLittleTurtle
fuzzyLittleTurtle

O primeiro estilo difcil de ler. Pessoalmente, eu gosto de usar sublinhados, embora esse estilo seja um pouco
doloroso de escrever. O padro das funes em JavaScript, e o da maioria dos programadores JavaScript,
seguir o ltimo estilo - eles capitalizam toda palavra exceto a primeira. No difcil se acostumar com coisas
pequenas assim, e o cdigo com estilos de nomenclaturas mistas pode se tornar desagradvel para leitura,
ento vamos seguir esta conveno.

Em alguns casos, como a funo Number , a primeira letra da varivel capitalizada tambm. Isso feito para
marcar a funo como um construtor. O que um construtor ser esclarecido no captulo 6. Por enquanto, o
importante no ser incomodado por esta aparente falta de consistncia.

Comentrios
Frequentemente, o cdigo puro no transmite todas as informaes necessrias que voc gostaria que tivessem
para leitores humanos, ou ele se transmite de uma forma to enigmtica que as pessoas realmente no
conseguem entend-lo. Em outras ocasies, voc est apenas se sentindo potico ou quer anotar alguns
pensamentos como parte de seu programa. Os comentrios so para isto.

O comentrio um pedao de texto que parte de um programa mas completamente ignorado pelo
computador. No JavaScript temos duas maneiras de escrever os comentrios. Para escrever em uma nica linha
de comentrio, voc pode usar dois caracteres barra ( // ) e ento o comentrio aps.

var accountBalance = calculateBalance(account);


// It's a green hollow where a river sings
accountBalance.adjust();
// Madly catching white tatters in the grass.
var report = new Report();
// Where the sun on the proud mountain rings:
addToReport(accountBalance, report);
// It's a little valley, foaming like light in a glass.

Um // comentrio vai at o final da linha. Uma seo de texto entre /* e */ ser ignorado, independentemente
se ele contm quebras de linha. Isto geralmente til para adicionar blocos de informao sobre um arquivo ou
um pedao do programa.

/*
I first found this number scrawled on the back of one of
my notebooks a few years ago. Since then, it has
occasionally dropped by, showing up in phone numbers and
the serial numbers of products that I bought. It
obviously likes me, so I've decided to keep it.
*/

var theNumber = 11213;

Resumo
Voc agora sabe que um programa construdo de declaraes, que as vezes contm mais declaraes.
Declaraes tendem a conter expresses, que podem ser feitas de pequenas expresses.

24
Colocar declaraes uma aps a outra nos d um programa que executado de cima para baixo. Voc pode
causar transtornos no fluxo de controle usando declaraes condicionais ( f , else e switch ) e loops ( while ,
do e for ).

As variveis podem ser usadas para arquivar pedaos de dados sob um nome, e so teis para rastrear o estado
de um programa. O ambiente um conjunto de variveis que so definidas. O sistema JavaScript sempre coloca
um nmero padro de variveis teis dentro do seu ambiente.

Funes so valores especiais que encapsulam um pedao do programa. Voc pode invoc-las escrevendo
function Name (argument1, argument2) {} . Essa chamada de funo uma expresso, que pode produzir um valor.

Exerccios
Se voc est inseguro sobre como testar suas solues para os exerccios, consulte a introduo.

Cada exerccio comea com a descrio de um problema. Leia e tente resolv-lo. Se voc tiver dificuldades,
considere a leitura das dicas abaixo do exerccio. As solues completas para os exerccios no esto inclusas
neste livro, mas voc pode procurar elas onlines em eloquentjavascript.net/code. Se voc quer aprender algo, eu
recomendo que veja as solues somente aps ter resolvido o exerccio, ou pelo menos, depois que tentou por
um perodo longo e duro o suficiente para dar uma pequena dor de cabea.

Tringulo com Loop


Escreva um programa que faa sete chamadas a console.log() para retornar o seguinte tringulo:

#
##
###
####
#####
######
#######

Uma maneira interessante para saber o comprimento de uma string escrevendo .length aps ela.

var abc = "abc";


console.log(abc.length);
// 3

A maioria dos exerccios contm um pedao de cdigo que pode ser utilizada para alterar e resolver o exerccio.
Lembre-se que voc pode clicar em um bloco de cdigo para edit-lo.

// Your code here.

Dicas:

Voc pode comear com um programa que simplesmente imprime os nmeros de 1 a 7, na qual voc pode
derivar algumas modificaes no exemplo de impresso de nmeros dado no incio do captulo aqui, onde o loop
foi introduzido.

Agora, considere a equivalncia entre nmeros e cadeias em um hash de caracteres. Voc pode ir de 1 para 2
adicionando 1 ( + = 1 ). Voc pode ir de "#" para "##", adicionando um caractere ( + = "#" ). Assim, a soluo pode
acompanhar de perto o nmero, de impresso do programa.

FizzBuzz
25
Escreva um programa que imprima usando console.log() todos os nmeros de 1 a 100 com duas excees. Para
nmeros divisveis por 3, imprima Fizz ao invs do nmero, e para nmeros divisveis por 5 (e no 3), imprima
Buzz .

Quando o programa estiver funcionando, modifique-o para imprimir FizzBuzz para nmeros que so divisveis
ambos por 3 e 5 (e continue imprimindo Fizz e Buzz para nmeros divisveis por apenas um deles).

(Isto na verdade uma pergunta de entrevista usada para eliminar uma porcentagem significativa de candidatos
programadores. Ento se voc resolv-la, voc est autorizado de se sentir bem consigo mesmo).

Dica:

Interar sobre os nmeros trabalho claro de um loop, e selecionar o que imprimir uma questo de execuo
condicional. Lembre-se do truque de usar o operador restante ( % ) para verificar se um nmero divisvel por
outro nmero (ter zero de resto).

Na primeira verso, existem trs resultados possveis para cada nmero, ento voc ir criar uma cadeia de
if/else if/else .

Na segunda verso o programa tem uma soluo simples e uma inteligente. A maneira mais simples adicionar
um outro "ramo" para um teste preciso da condio dada. Para o mtodo inteligente construir uma sequncia de
caracteres contendo palavra ou palavras para a sada, que imprima a palavra ou o nmero, caso no haja palavra,
fazendo o uso do operador elegante || .

Tabuleiro de Xadrez
Escreva um programa que cria uma string que representa uma grade 8x8, usando novas linhas para separar os
caracteres. A cada posio da grade existe um espao ou um caractere "#". Esses caracteres formam um
tabuleiro de xadrez.

Passando esta string para o console.log deve mostrar algo como isto:

# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #

Quando voc tiver o programa que gere este padro, defina a varivel size = 8 e altere programa para que ele
funcione para qualquer size , a sada da grade de largura e altura.

// Your code here.

Dica:

A sequncia pode ser construda iniciando vazia ("") e repetidamente adicionando caracateres. O caracter para
uma nova linha escrito assim \n .

Utilize console.log para visualizar a sada do seu programa.

Para trabalhar com duas dimenses, voc ir precisar de um loop dentro de outro loop. Coloque entre chaves os
"corpos" dos loops para se tornar mais fcil de visualizar quando inicia e quando termina. Tente recuar
adequadamente esses "corpos". A ordem dos loops deve seguir a ordem que usamos para construir a string
(linha por linha, esquerda para direita, cima para baixo). Ento o loop mais externo manipula as linhas e o loop
interno manipula os caracteres por linha.

26
Voc vai precisar de duas variveis para acompanhar seu progresso. Para saber se coloca um espao ou um "#"
em uma determinada posio, voc pode testar se a soma dos dois contadores ainda divisvel por ( % 2 ).

Encerrando uma linha com um caracter de nova linha acontece aps a linha de cima ser construda, faa isso
aps o loop interno, mas dentro do loop externo.

27
Funes
As pessoas pensam que Cincia da Computao a arte de gnios. Na realidade o oposto, so vrias
pessoas fazendo coisas que dependem uma das outras, como um muro de pequenas pedras. Donald
Knuth

Voc j viu valores de funes como alert , e como invoc-las. Funes so essenciais na programao
JavaScript. O conceito de encapsular uma parte do programa em um valor tem vrios usos. uma ferramenta
usada para estruturar aplicaes de larga escala, reduzir repetio de cdigo, associar nomes a subprogramas e
isolar esses subprogramas uns dos outros.

A aplicao mais bvia das funes quando queremos definir novos vocabulrios. Criar novas palavras no
nosso dia a dia geralmente no uma boa ideia, porm em programao indispensvel.

Um adulto tpico tem por volta de 20.000 palavras em seu vocabulrio. Apenas algumas linguagens de
programao possuem 20.000 conceitos embutidos, sendo que o vocabulrio que se tem disponvel tende a ser
bem definido e, por isso, menos flexvel do que a linguagem usada por humanos. Por isso, normalmente temos
que adicionar conceitos do nosso prprio vocabulrio para evitar repetio.

Definindo Uma Funo


Uma definio de funo nada mais do que uma definio normal de uma varivel, na qual o valor recebido pela
varivel uma funo. Por exemplo, o cdigo a seguir define uma varivel square que se refere a uma funo que
retorna o quadrado do nmero dado:

var square = function(x) {


return x * x;
};

console.log(square(12));
// 144

Uma funo criada por meio de uma expresso que se inicia com a palavra-chave function . Funes podem
receber uma srie de parmetros (nesse caso, somente x ) e um "corpo", contendo as declaraes que sero
executadas quando a funo for invocada. O "corpo" da funo deve estar sempre envolvido por chaves, mesmo
quando for formado por apenas uma simples declarao (como no exemplo anterior).

Uma funo pode receber mltiplos parmetros ou nenhum parmetro. No exemplo a seguir, makeNoise no
recebe nenhum parmetro, enquanto power recebe dois:

28
var makeNoise = function() {
console.log("Pling!");
};

makeNoise();
// Pling!

var power = function(base, exponent) {


var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
};

console.log(power(2, 10));
// 1024

Algumas funes produzem um valor, como as funes power e square acima, e outras no, como no exemplo de
makeNoise , que produz apenas um efeito colateral. A declarao return usada para determinar o valor de
retorno da funo. Quando o controle de execuo interpreta essa declarao, ele sai imediatamente do contexto
da funo atual e disponibiliza o valor retornado para o cdigo que invocou a funo. A palavra-chave return sem
uma expresso aps, ir fazer com que o retorno da funo seja undefined .

Parmetros e Escopos
Os parmetros de uma funo comportam-se como variveis regulares. Seu valor inicial informado por quem
invocou a funo e no pelo cdigo da funo em si.

Uma propriedade importante das funes que variveis definidas dentro do "corpo" delas, incluindo seus
parmetros, so locais prpria funo. Isso significa, por exemplo, que a varivel result no exemplo power ser
criada novamente toda vez que a funo for invocada, sendo que as diferentes execues no interferem umas
nas outras.

Essa caracterstica de localidade das variveis se aplica somente aos parmetros e s variveis que forem
declaradas usando a palavra-chave var dentro do "corpo" de uma funo. Variveis declaradas fora do contexto
de alguma funo so chamadas de glob ais (no locais), pois elas so visveis em qualquer parte da aplicao.
possvel acessar variveis glob ais dentro de qualquer funo, contanto que voc no tenha declarado uma
varivel local com o mesmo nome.

O cdigo a seguir demonstra esse conceito. Ele define e executa duas funes em que ambas atribuem um valor
varivel x . A primeira funo f1 declara a varivel como local e ento muda apenas seu valor. J a segunda
funo f2 no declara x localmente, portanto sua referncia a x est associada varivel global x definida
no topo do exemplo:

29
var x = "outside";

var f1 = function() {
var x = "inside f1";
};
f1();
console.log(x);
// outside

var f2 = function() {
x = "inside f2";
};
f2();
console.log(x);
// inside f2

Esse comportamento ajuda a prevenir interferncias acidentais entre funes. Se todas as variveis fossem
compartilhadas por toda a aplicao, seria muito trabalhoso garantir que o mesmo nome no fosse utilizado em
duas situaes com propsitos diferentes. Alm disso, se fosse o caso de reutilizar uma varivel com o mesmo
nome, talvez voc pudesse se deparar com efeitos estranhos de cdigos que alteram o valor da sua varivel.
Assumindo que variveis locais existem apenas dentro do contexto da funo, a linguagem torna possvel ler e
entender funes como pequenos universos, sem termos que nos preocupar com o cdigo da aplicao inteira
de uma s vez.

Escopo Aninhado
O JavaScript no se distingue apenas pela diferenciao entre variveis locais e glob ais. Funes tambm podem
ser criadas dentro de outras funes, criando vrios nveis de localidades.

Por exemplo, a funo landscape possui duas funes, flat e mountain , declaradas dentro do seu corpo:

var landscape = function() {


var result = "";
var flat = function(size) {
for (var count = 0; count < size; count++)
result += "_";
};
var mountain = function(size) {
result += "/";
for (var count = 0; count < size; count++)
result += "'";
result += "\\";
};

flat(3);
mountain(4);
flat(6);
mountain(1);
flat(1);
return result;
};

console.log(landscape());
// ___/''''\______/'\_

As funes flat e mountain podem ver a varivel result porque elas esto dentro do mesmo escopo da funo
que as definiu. Entretanto, elas no conseguem ver a varivel count uma da outra (somente a sua prpria), pois
elas esto definidas em escopos diferentes. O ambiente externo funo landscape no consegue ver as
variveis definidas dentro de landscape .

30
Em resumo, cada escopo local pode tambm ver todos os escopos locais que o contm. O conjunto de variveis
visveis dentro de uma funo determinado pelo local onde aquela funo est escrita na aplicao. Todas as
variveis que estejam em blocos ao redor de definies de funes, so visveis aos corpos dessas funes e
tambm queles que esto no mesmo nvel. Essa abordagem em relao visibilidade de variveis chamada
de escopo lxico.

Pessoas com experincia em outras linguagens de programao podem talvez esperar que qualquer bloco de
cdigo entre chaves produza um novo ambiente local. Entretanto, no JavaScript, as funes so as nicas coisas
que podem criar novos escopos. Tambm permitido a utilizao de blocos livres:

var something = 1;
{
var something = 2;
// Do stuff with variable something...
}
// Outside of the block again...

Entretanto, a varivel something dentro do bloco faz referncia mesma varivel fora do bloco. Na realidade,
embora blocos como esse sejam permitidos, eles so teis somente para agrupar o corpo de uma declarao
condicional if ou um lao de repetio.

Se voc acha isso estranho, no se preocupe, pois no est sozinho. A prxima verso do JavaScript vai introduzir
a palavra-chave let , que funcionar como var , mas criar uma varivel que local ao b loco que a contm e no
funo que a contm.

Funes Como Valores


As variveis de funo, normalmente, atuam apenas como nomes para um pedao especfico de um programa.
Tais variveis so definidas uma vez e nunca se alteram. Isso faz com que seja fcil confundir a funo com seu
prprio nome.

Entretanto, so duas coisas distintas. Um valor de funo pode fazer todas as coisas que outros valores podem
fazer - voc pode us-lo em expresses arbitrrias e no apenas invoc-la. possvel armazenar um valor de
funo em um novo local, pass-lo como argumento para outra funo e assim por diante. No muito diferente,
uma varivel que faz referncia a uma funo continua sendo apenas uma varivel regular e pode ser atribuda a
um novo valor, como mostra o exemplo abaixo:

var launchMissiles = function(value) {


missileSystem.launch("now");
};

if (safeMode)
launchMissiles = function(value) {/* do nothing */};

No captulo 5, ns vamos discutir as coisas maravilhosas que podem ser feitas quando passamos valores de
funo para outras funes.

Notao Por Declarao


Existe uma maneira mais simples de expressar var square = function . A palavra-chave function tambm pode
ser usada no incio da declarao, como demonstrado abaixo:

31
function square(x) {
return x * x;
}

Isso uma declarao de funo. Ela define a varivel square e faz com que ela referencie a funo em questo.
At agora tudo bem, porm existe uma pequena diferena nessa maneira de definir uma funo.

console.log("The future says:", future());

function future() {
return "We STILL have no flying cars.";
}

O exemplo acima funciona, mesmo sabendo que a funo foi definida aps o cdigo que a executa. Isso ocorre
porque as declaraes de funes no fazem parte do fluxo normal de controle, que executado de cima para
baixo. Elas so conceitualmente movidas para o topo do escopo que as contm e podem ser usadas por
qualquer cdigo no mesmo escopo. Isso pode ser til em algumas situaes, porque nos permite ter a liberdade
de ordenar o cdigo de uma maneira que seja mais expressiva, sem nos preocuparmos muito com o fato de ter
que definir todas as funes antes de us-las.

O que acontece quando definimos uma declarao de funo dentro de um bloco condicional ( if ) ou um lao de
repetio? Bom, no faa isso. Diferentes plataformas JavaScript usadas em diferentes navegadores tm
tradicionalmente feito coisas diferentes nessas situaes, e a ltima verso basicamente probe essa prtica. Se
voc deseja que seu programa se comportem de forma consistente, use somente essa forma de definio de
funo no bloco externo de uma outra funo ou programa.

function example() {
function a() {} // Okay
if (something) {
function b() {} // Danger!
}
}

A Pilha de Chamadas
Ser muito til observamos como o fluxo de controle flui por meio das execues das funes. Aqui, temos um
simples programa fazendo algumas chamadas de funes:

function greet(who) {
console.log("Hello " + who);
}
greet("Harry");
console.log("Bye");

A execuo desse programa funciona da seguinte forma: a chamada funo greet faz com que o controle pule
para o incio dessa funo (linha 2). Em seguida, invocado console.log (uma funo embutida no navegador),
que assume o controle, faz seu trabalho e ento retorna o controle para a linha 2 novamente. O controle chega ao
fim da funo greet e retorna para o local onde a funo foi invocada originalmente (linha 4). Por fim, o controle
executa uma nova chamada a console.log .

Podemos representar o fluxo de controle, esquematicamente, assim:

32
top
greet
console.log
greet
top
console.log
top

Devido ao fato de que a funo deve retornar ao local onde foi chamada aps finalizar a sua execuo, o
computador precisa se lembrar do contexto no qual a funo foi invocada originalmente. Em um dos casos,
console.log retorna o controle para a funo greet . No outro caso, ela retorna para o final do programa.

O local onde o computador armazena esse contexto chamado de call stack (pilha de chamadas). Toda vez que
uma funo invocada, o contexto atual colocado no topo dessa "pilha" de contextos. Quando a funo finaliza
sua execuo, o contexto no topo da pilha removido e utilizado para continuar o fluxo de execuo.

O armazenamento dessa pilha de contextos necessita de espao na memria do computador. Quando a pilha
comear a ficar muito grande, o computador reclamar com uma mensagem do tipo out of stack space (sem
espao na pilha) ou too much recursion (muitas recurses). O cdigo a seguir demonstra esse problema fazendo
uma pergunta muito difcil para o computador, que resultar em um ciclo infinito de chamadas entre duas funes.
Se o computador tivesse uma pilha de tamanho infinito, isso poderia ser possvel, no entanto, eventualmente
chegaremos ao limite de espao e explodiremos a "pilha".

function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + " came first.");
// ??

Argumentos Opcionais
O cdigo abaixo permitido e executa sem problemas:

alert("Hello", "Good Evening", "How do you do?");

A funo alert , oficialmente, aceita somente um argumento. No entanto, quando voc a chama assim, ela no
reclama. Ela simplesmente ignora os outros argumentos e lhe mostra o seu "Hello".

O JavaScript extremamente tolerante com a quantidade de argumentos que voc passa para uma funo. Se
voc passar mais argumentos que o necessrio, os extras sero ignorados. Se voc passar menos argumentos,
os parmetros faltantes simplesmente recebero o valor undefined .

A desvantagem disso que, possivelmente - e provavelmente - voc passar um nmero errado de argumentos,
de forma acidental, para as funes e nada ir alert-lo sobre isso.

A vantagem que esse comportamento pode ser usado em funes que aceitam argumentos opcionais. Por
exemplo, a verso seguinte de power pode ser chamada com um ou dois argumentos. No caso de ser invocada
com apenas um argumento, ela assumir o valor 2 para o expoente e a funo se comportar com um expoente
ao quadrado.

33
function power(base, exponent) {
if (exponent == undefined)
exponent = 2;
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
}

console.log(power(4));
// 16
console.log(power(4, 3));
// 64

No prximo captulo, veremos uma maneira de acessar a lista que contm todos os argumentos que foram
passados para uma funo. Isso til, pois torna possvel uma funo aceitar qualquer nmero de argumentos.
Por exemplo, console.log tira proveito disso, imprimindo todos os valores que foram passados.

console.log("R", 2, "D", 2);


// R 2 D 2

Closure
A habilidade de tratar funes como valores, combinada com o fato de que variveis locais so recriadas toda vez
que uma funo invocada; isso traz tona uma questo interessante.

O que acontece com as variveis locais quando a funo que as criou no est mais ativa?

O cdigo a seguir mostra um exemplo disso. Ele define uma funo wrapValue que cria uma varivel local e
retorna uma funo que acessa e retorna essa varivel.

function wrapValue(n) {
var localVariable = n;
return function() { return localVariable; };
}

var wrap1 = wrapValue(1);


var wrap2 = wrapValue(2);
console.log(wrap1());
// 1
console.log(wrap2());
// 2

Isso permitido e funciona como voc espera: a varivel ainda pode ser acessada. Vrias instncias da varivel
podem coexistir, o que uma boa demonstrao do conceito de que variveis locais so realmente recriadas para
cada nova chamada, sendo que as chamadas no interferem nas variveis locais umas das outras.

A funcionalidade capaz de referenciar uma instncia especfica de uma varivel local aps a execuo de uma
funo chamada de closure. Uma funo que closes over (fecha sobre) variveis locais chamada de closure.

Esse comportamento faz com que voc no tenha que se preocupar com o tempo de vida das variveis, como
tambm permite usos criativos de valores de funo.

Com uma pequena mudana, podemos transformar o exemplo anterior, possibilitando a criao de funes que
se multiplicam por uma quantidade arbitrria.

34
function multiplier(factor) {
return function(number) {
return number * factor;
};
}

var twice = multiplier(2);


console.log(twice(5));
// 10

A varivel explcita localVariable do exemplo na funo wrapValue no necessria, pois o parmetro em si j


uma varivel local.

Pensar em programas que funcionam dessa forma requer um pouco de prtica. Um bom modelo mental
pensar que a palavra-chave function "congela" o cdigo que est em seu corpo e o envolve em um pacote (o
valor da funo). Quando voc ler return function(...) {...} , pense como se estivesse retornando um
manipulador que possibilita executar instrues computacionais que foram "congeladas" para um uso posterior.

No exemplo, multiplier retorna um pedao de cdigo "congelado" que fica armazenado na varivel twice .A
ltima linha do exemplo chama o valor armazenado nessa varivel, fazendo com que o cdigo "congelado" ( return

number * factor; ) seja executado. Ele continua tendo acesso varivel factor que foi criada na chamada de
multiplier e, alm disso, tem acesso ao argumento que foi passado a ele (o valor 5) por meio do parmetro
number .

Recurso
perfeitamente aceitvel uma funo invocar a si mesma, contanto que se tenha cuidado para no sobrecarregar
a pilha de chamadas. Uma funo que invoca a si mesma denominada recursiva. A recursividade permite que
as funes sejam escritas em um estilo diferente. Veja neste exemplo uma implementao alternativa de power :

function power(base, exponent) {


if (exponent == 0)
return 1;
else
return base * power(base, exponent - 1);
}

console.log(power(2, 3));
// 8

Essa a maneira mais prxima da forma como os matemticos definem a exponenciao, descrevendo o
conceito de uma forma mais elegante do que a variao que usa um lao de repetio. A funo chama a si
mesma vrias vezes com diferentes argumentos para alcanar a multiplicao repetida.

Entretanto, h um grave problema: em implementaes tpicas no JavaScript, a verso recursiva


aproximadamente dez vezes mais lenta do que a variao que utiliza um lao de repetio. Percorrer um lao de
repetio simples mais rpido do que invocar uma funo mltiplas vezes.

O dilema velocidade versus elegncia bastante interessante. Voc pode interpret-lo como uma forma de
transio gradual entre acessibilidade para humanos e mquina. Praticamente todos os programas podem se
tornar mais rpidos quando se tornam maiores e mais complexos, cabendo ao desenvolvedor decidir qual o
balano ideal entre ambos.

No caso da verso anterior da implementao de power , a verso menos elegante (usando lao de repetio)
bem simples e fcil de ser lida, no fazendo sentido substitu-la pela verso recursiva. Porm, frequentemente
lidamos com aplicaes mais complexas e sacrificar um pouco a eficincia para tornar o cdigo mais legvel e

35
simples acaba se tornando uma escolha atrativa.

A regra bsica que tem sido repetida por muitos programadores e com a qual eu concordo plenamente, no se
preocupar com eficincia at que voc saiba, com certeza, que o programa est muito lento. Quando isso
acontecer, encontre quais partes esto consumindo maior tempo de execuo e comece a trocar elegncia por
eficincia nessas partes.

evidente que essa regra no significa que se deva ignorar a performance completamente. Em muitos casos,
como na funo power , no h muitos benefcios em usar a abordagem mais elegante. Em outros casos, um
programador experiente pode identificar facilmente, que uma abordagem mais simples nunca ser rpida o
suficiente.

A razo pela qual estou enfatizando isso que, surpreendentemente, muitos programadores iniciantes focam
excessivamente em eficincia at nos menores detalhes. Isso acaba gerando programas maiores, mais
complexos e muitas vezes menos corretos, que demoram mais tempo para serem escritos e, normalmente,
executam apenas um pouco mais rapidamente do que as variaes mais simples e diretas.

Porm, muitas vezes a recurso no uma alternativa menos eficiente do que um lao de repetio. muito mais
simples resolver alguns problemas com recurso do que com laos de repetio. A maioria desses problemas
envolve explorao ou processamento de vrias ramificaes, as quais podem se dividir em novas ramificaes e
assim por diante.

Considere este quebra-cabea: iniciando com o nmero 1 e repetidamente adicionando 5 ou multiplicando por 3,
uma infinita quantidade de novos nmeros pode ser produzida. Como voc implementaria uma funo que, dado
um nmero, tenta achar a sequncia de adies e multiplicaes que produzem esse nmero? Por exemplo, o
nmero 13 pode ser produzido multiplicando-se por 3 e adicionando-se 5 duas vezes. J o nmero 15 no pode
ser produzido de nenhuma forma.

Aqui est uma soluo recursiva:

function findSolution(target) {
function find(start, history) {
if (start == target)
return history;
else if (start > target)
return null;
else
return find(start + 5, ( + history + + 5)) ||
find(start * 3, ( + history + * 3));
}
return find(1, 1);
}

console.log(findSolution(24));
// (((1 * 3) + 5) * 3)

Note que esse programa no necessariamente encontra a menor sequncia de operaes. Ele termina sua
execuo quando encontra a primeira soluo possvel.

Eu no espero que voc entenda como isso funciona imediatamente, mas vamos analisar o exemplo, pois um
timo exerccio para entender o pensamento recursivo.

A funo interna find responsvel pela recurso. Ela recebe dois argumentos (o nmero atual e uma string que
registra como chegamos a esse nmero) e retorna uma string que mostra como chegar no nmero esperado ou
null .

Para fazer isso, a funo executa uma entre trs aes possveis. Se o nmero atual o nmero esperado, o
histrico atual reflete uma possvel sequncia para alcanar o nmero esperado, ento ele simplesmente
retornado. Se o nmero atual maior que o nmero esperado, no faz sentido continuar explorando o histrico, j

36
que adicionar ou multiplicar o nmero atual gerar um nmero ainda maior. Por fim, se ns tivermos um nmero
menor do que o nmero esperado, a funo tentar percorrer todos os caminhos possveis que iniciam do
nmero atual, chamando ela mesma duas vezes, uma para cada prximo passo que seja permitido. Se a
primeira chamada retornar algo que no seja null , ela retornada. Caso contrrio, a segunda chamada
retornada, independentemente se ela produzir string ou null .

Para entender melhor como essa funo produz o resultado que estamos esperando, vamos analisar todas as
chamadas a find que so feitas quando procuramos a soluo para o nmero 13.

find(1, 1)
find(6, (1 + 5))
find(11, ((1 + 5) + 5))
find(16, (((1 + 5) + 5) + 5))
too big
find(33, (((1 + 5) + 5) * 3))
too big
find(18, ((1 + 5) * 3))
too big
find(3, (1 * 3))
find(8, ((1 * 3) + 5))
find(13, (((1 * 3) + 5) + 5))
found!

A indentao reflete a profundidade da pilha de chamadas. A primeira chamada do find invoca a si mesma duas
vezes, explorando as solues que comeam com (1 + 5) e (1 * 3) . A primeira chamada tenta achar a soluo
que comea com (1 + 5) e, usando recurso, percorre todas as possveis solues que produzam um nmero
menor ou igual ao nmero esperado. Como ele no encontra uma soluo para o nmero esperado, o valor null

retornado at retornar para a chamada inicial. Nesse momento, o operador || faz com que a pilha de
chamadas inicie o processo de explorao pelo outro caminho (1 * 3) . Essa busca tem resultados satisfatrios,
porque aps duas chamadas recursivas acaba encontrando o nmero 13. Essa chamada recursiva mais interna
retorna uma string e cada operador || nas chamadas intermedirias passa essa string adiante, retornando
no final a soluo esperada.

Funes Crescentes
Existem duas razes naturais para as funes serem introduzidas nos programas.

A primeira delas quando voc percebe que est escrevendo o mesmo cdigo vrias vezes. Ns queremos evitar
isso, pois quanto mais cdigo, maiores so as chances de erros e mais linhas de cdigo h para as pessoas
lerem e entenderem o programa. Por isso, ns extramos a funcionalidade repetida, encontramos um bom nome
para ela e colocamos dentro de uma funo.

A segunda razo quando voc precisa de uma funcionalidade que ainda no foi escrita e que merece ser
encapsulada em uma funo prpria. Voc comea dando um nome funo e, em seguida, escreve o seu corpo.
s vezes, voc pode at comear escrevendo o cdigo que usa a funo antes mesmo de defini-la.

A dificuldade de encontrar um bom nome para uma funo um bom indicativo de quo claro o conceito que
voc est tentando encapsular. Vamos analisar um exemplo.

Ns queremos escrever um programa que imprima dois nmeros, sendo eles o nmero de vacas e galinhas em
uma fazenda com as palavras Cows (vacas) e Chickens (galinhas) depois deles e algarismos zeros antes de
ambos os nmeros para que sejam sempre nmeros de trs dgitos.

007 Cows
011 Chickens

37
Bom, claramente, isso uma funo que exige dois argumentos. Vamos codar.

function printFarmInventory(cows, chickens) {


var cowString = String(cows);
while (cowString.length < 3)
cowString = 0 + cowString;
console.log(cowString + Cows);
var chickenString = String(chickens);
while (chickenString.length < 3)
chickenString = 0 + chickenString;
console.log(chickenString + Chickens);
}
printFarmInventory(7, 11);

Adicionar .length aps o valor de uma string nos fornecer o tamanho (quantidade de caracteres) daquela
string . Por isso, o lao de repetio while continua adicionando zeros no incio da string que representa o
nmero at que a mesma tenha trs caracteres.

Misso cumprida! Porm, no momento em que iramos enviar o cdigo ao fazendeiro (juntamente com uma
grande cobrana, claro), ele nos ligou dizendo que comeou a criar porcos, e perguntou, se poderamos
estender a funcionalidade do software para tambm imprimir os porcos?

claro que podemos. Antes de entrar no processo de copiar e colar essas mesmas quatro linhas outra vez,
vamos parar e reconsiderar. Deve existir uma forma melhor. Aqui est a primeira tentativa:

function printZeroPaddedWithLabel(number, label) {


var numberString = String(number);
while (numberString.length < 3)
numberString = 0 + numberString;
console.log(numberString + + label);
}

function printFarmInventory(cows, chickens, pigs) {


printZeroPaddedWithLabel(cows, Cows);
printZeroPaddedWithLabel(chickens, Chickens);
printZeroPaddedWithLabel(pigs, Pigs);
}

printFarmInventory(7, 11, 3);

Funcionou! Mas o nome printZeroPaddedWithLabel um pouco estranho. Ele uma combinao de trs coisas -
imprimir, adicionar zeros e adicionar a label correta - em uma nica funo.

Ao invs de tentarmos abstrair a parte repetida do nosso programa como um todo, vamos tentar selecionar
apenas um conceito.

function zeroPad(number, width) {


var string = String(number);
while (string.length < width)
string = 0 + string;
return string;
}

function printFarmInventory(cows, chickens, pigs) {


console.log(zeroPad(cows, 3) + Cows);
console.log(zeroPad(chickens, 3) + Chickens);
console.log(zeroPad(pigs, 3) + Pigs);
}

printFarmInventory(7, 16, 3);

38
Ter uma funo com um bom nome descritivo como zeroPad torna fcil para qualquer um ler e entender o cdigo.
Alm disso, ele pode ser til em outras situaes, alm desse programa especfico. Voc pode us-lo, por
exemplo, para imprimir nmeros corretamente alinhados em uma tabela.

O quo inteligente e verstil as nossas funes deveriam ser? Ns poderamos escrever funes extremamente
simples, que apenas adicionam algarismos para que o nmero tenha trs caracteres, at funes complicadas,
para formatao de nmeros fracionrios, nmeros negativos, alinhamento de casas decimais, formatao com
diferentes caracteres e por a vai.

Um princpio til no adicionar funcionalidades, a menos que voc tenha certeza absoluta de que ir precisar
delas. Pode ser tentador escrever solues genricas para cada funcionalidade com que voc se deparar.
Resista a essa vontade. Voc no vai ganhar nenhum valor real com isso e vai acabar escrevendo muitas linhas
de cdigo que nunca sero usadas.

Funes e Efeitos Colaterais


Funes podem ser divididas naquelas que so invocadas para produzir um efeito colateral e naquelas que so
invocadas para gerar um valor de retorno (embora tambm seja possvel termos funes que produzam efeitos
colaterais e que retornem um valor).

A primeira funo auxiliar no exemplo da fazenda, printZeroPaddedWithLabel , invocada para produzir um efeito
colateral: imprimir uma linha. A segunda verso, zeroPad , chamada para produzir um valor de retorno. No
coincidncia que a segunda verso til em mais situaes do que a primeira. Funes que criam valores so
mais fceis de serem combinadas de diferentes maneiras do que funes que produzem efeitos colaterais
diretamente.

Uma funo "pura" um tipo especfico de funo que produz valores e que no gera efeitos colaterais, como
tambm no depende de efeitos colaterais de outros cdigos por exemplo, ela no utiliza variveis globais que
podem ser alteradas por outros cdigos. Uma funo pura tem a caracterstica de, ser sempre chamada com os
mesmos argumentos, produzir o mesmo valor (e no far nada alm disso). Isso acaba fazendo com que seja
fcil de entendermos como ela funciona. Uma chamada para tal funo pode ser mentalmente substituda pelo
seu resultado, sem alterar o significado do cdigo. Quando voc no tem certeza se uma funo pura est
funcionando corretamente, voc pode test-la simplesmente invocando-a. Sabendo que ela funciona nesse
contexto, funcionar em qualquer outro contexto. Funes que no so "puras" podem retornar valores diferentes
baseados em vrios tipos de fatores e produzem efeitos colaterais que podem fazer com que seja difcil de testar
e pensar sobre elas.

Mesmo assim, no h necessidade de se sentir mal ao escrever funes que no so "puras" ou comear uma
"guerra santa" para eliminar cdigos impuros. Efeitos colaterais so teis em algumas situaes. No existe uma
verso "pura" de console.log , por exemplo, e console.log certamente til. Algumas operaes so tambm mais
fceis de se expressar de forma mais eficiente quando usamos efeitos colaterais, portanto a velocidade de
computao pode ser uma boa razo para se evitar a "pureza".

Resumo
Este captulo ensinou a voc como escrever suas prprias funes. A palavra-chave function , quando usada
como uma expresso, pode criar um valor de funo. Quando usada como uma declarao, pode ser usada para
declarar uma varivel e dar a ela uma funo como valor.

39
// Create a function value f
var f = function(a) {
console.log(a + 2);
};

// Declare g to be a function
function g(a, b) {
return a * b * 3.5;
}

Um aspecto chave para entender funes, entender como os escopos locais funcionam. Parmetros e variveis
declaradas dentro de uma funo so locais quela funo, recriados toda vez que a funo invocada, e no so
acessveis do contexto externo funo. Funes declaradas dentro de outras tm acesso ao escopo local das
funes mais externas que as envolvem.

Separar as tarefas que a sua aplicao executa em diferentes funes, bastante til. Voc no ter que repetir o
cdigo e as funes fazem um programa mais legvel, agrupando o cdigo em pedaos conceituais, da mesma
forma que os captulos e as sees ajudam a organizar um texto.

Exerccios
Mnimo
O captulo anterior introduziu a funo Math.min que retorna o seu menor argumento. Ns podemos reproduzir
essa funcionalidade agora. Escreva uma funo min que recebe dois argumentos e retorna o menor deles.

// Your code here.

console.log(min(0, 10));
// 0
console.log(min(0, -10));
// -10

Dica: Se estiver tendo problemas para colocar as chaves e os parnteses nos seus lugares corretos, para ter
uma definio de uma funo vlida, comece copiando um dos exemplos desse captulo e modificando-o. Uma
funo pode conter vrias declaraes de retorno ( return ).

Recurso
Ns vimos que o % (operador resto) pode ser usado para testar se um nmero par ou mpar, usando % 2 para
verificar se ele divisvel por dois. Abaixo, est uma outra maneira de definir se um nmero inteiro positivo par
ou mpar:

Zero par.
Um mpar.
Para todo outro nmero N, sua paridade a mesma de N - 2.

Defina uma funo recursiva isEven que satisfaa as condies descritas acima. A funo deve aceitar um
nmero como parmetro e retornar um valor Booleano.

Teste-a com os valores 50 e 75. Observe como ela se comporta com o valor -1. Por qu? Voc consegue pensar
em uma maneira de arrumar isso?

40
// Your code here.

console.log(isEven(50));
// true
console.log(isEven(75));
// false
console.log(isEven(-1));
// ??

Dica: Sua funo ser semelhante funo interna find do exemplo recursivo findSolution neste captulo, com
uma cadeia de declaraes if / else if / else que testam qual dos trs casos se aplica. O else final,
correspondente ao terceiro caso, responsvel por fazer a chamada recursiva. Cada uma das ramificaes
dever conter uma declarao de retorno ou retornar um valor especfico.

Quando o argumento recebido for um nmero negativo, a funo ser chamada recursivamente vrias vezes,
passando para si mesma um nmero cada vez mais negativo, afastando-se cada vez mais de retornar um
resultado. Ela, eventualmente, consumir todo o espao em memria da pilha de chamadas e abortar.

Contando feijes
Voc pode acessar o N-simo caractere, ou letra, de uma string escrevendo "string".charAt(N) , similar a como
voc acessa seu tamanho com "s".length . O valor retornado ser uma string contendo somente um caractere
(por exemplo, "b" ). O primeiro caractere est na posio zero, o que faz com que o ltimo seja encontrado na
posio string.length -1 . Em outras palavras, uma string com dois caracteres possui tamanho ( length ) dois, e
suas respectivas posies so 0 e 1 .

Escreva uma funo countBs que receba uma string como nico argumento e retorna o nmero que indica
quantos caracteres "B", em maisculo, esto presentes na string .

Em seguida, escreva uma funo chamada countChar que se comporta de forma parecida com countBs , exceto
que ela recebe um segundo argumento que indica o caractere que ser contado (ao invs de contar somente o
caractere "B" em maisculo). Reescreva countBs para fazer essa nova funcionalidade.

// Your code here.

console.log(countBs(BBC));
// 2
console.log(countChar(kakkerlak, k));
// 4

Dica: Um lao de repetio em sua funo far com que todos os caracteres na string sejam verificados se
usarmos um ndice de zero at uma unidade abaixo que seu tamanho ( < string.length ). Se o caractere na
posio atual for o mesmo que a funo est procurando, ele incrementar uma unidade na varivel de contagem
( counter ). Quando o lao chegar ao seu fim, a varivel counter dever ser retornada.

Certifique-se de usar e criar variveis locais funo, utilizando a palavra-chave var .

41
Estrutura de dados: Objetos e Array
Em duas ocasies me perguntaram: "Ora, Sr. Babbage, se voc colocar nmeros errados em uma
mquina, repostas certas iro sair?" [...] Certamente eu no sou capaz de compreender o tipo de confuso
de ideias que poderia provocar tal questionamento.

Charles Babbage, Passages from the Life of a Philosopher (1864)

Nmeros, Booleanos e strings so os tijolos usados para construir as estruturas de dados. Entretanto, voc no
consegue fazer uma casa com um nico tijolo. Objetos nos permitem agrupar valores (incluindo outros objetos) e,
consequentemente, construir estruturas mais complexas.

Os programas que construmos at agora foram seriamente limitados devido ao fato de que estiveram operando
apenas com tipos de dados simples. Esse captulo ir adicionar uma compreenso bsica sobre estrutura de
dados para o seu kit de ferramentas. Ao final, voc saber o suficiente para comear a escrever programas teis.

O captulo ir trabalhar com um exemplo de programao mais ou menos realista, introduzindo conceitos a
medida em que eles se aplicam ao problema em questo. O cdigo de exemplo ser, muitas vezes, construdo
em cima de funes e variveis que foram apresentadas no incio do texto.

O Esquilo-homem
De vez em quando, geralmente entre oito e dez da noite, Jacques se transforma em um pequeno roedor peludo
com uma cauda espessa.

Por um lado, Jacques fica muito contente por no ter licantropia clssica. Transformar-se em um esquilo tende a
causar menos problemas do que se transformar em um lobo. Ao invs de ter que se preocupar em comer
acidentalmente o vizinho (isso seria bem estranho), ele tem que se preocupar em no ser comido pelo gato do
vizinho. Aps duas ocasies em que ele acordou nu, desorientado e em cima de um galho fino na copa de uma
rvore, ele resolveu trancar as portas e as janelas do seu quarto durante a noite e colocar algumas nozes no cho
para manter-se ocupado.

Isso resolve os problemas do gato e da rvore. Mesmo assim, Jacques ainda sofre com sua condio. As
ocorrncias irregulares das transformaes o faz suspeitar de que talvez possa ter alguma coisa que as ativam.
Por um tempo, ele acreditava que isso s acontecia nos dias em que ele havia tocado em rvores. Por isso, ele
parou de tocar de vez nas rvores e at parou de ficar perto delas, mas o problema persistiu.

42
Mudando para uma abordagem mais cientfica, Jacques pretende comear a manter um registro dirio de tudo o
que ele faz e se ele se transformou. Com essas informaes, ele espera ser capaz de diminuir e limitar as
condies que ativam as transformaes.

A primeira coisa que ele dever fazer criar uma estrutura de dados para armazenar essas informaes.

Conjuntos de dados
Para trabalhar com um pedao de dados digitais, primeiramente precisamos encontrar uma maneira de
represent-los na memria da nossa mquina. Vamos dizer que, como um exemplo simples, queremos
representar a coleo de nmeros: 2, 3, 5, 7 e 11.

Poderamos ser criativos com strings (elas podem ter qualquer tamanho, assim podemos armazenar muitos
dados nelas) e usar "2 3 5 7 11" como nossa representao. Entretanto, isso estranho, pois voc teria que, de
alguma forma, extrair os dgitos e convert-los em nmeros para poder acess-los.

Felizmente, o JavaScript fornece um tipo de dado especfico para armazenar uma sequncias de valores. Ele
chamado de array e escrito como uma lista de valores separados por vrgulas e entre colchetes.

var listOfNumbers = [2, 3, 5, 7, 11];


console.log(listOfNumbers[1]);
// 3
console.log(listOfNumbers[1 - 1]);
// 2

A notao para acessar elementos contidos em um array tambm usa colchetes. Um par de colchetes
imediatamente aps uma expresso, contendo outra expresso entre esses colchetes, ir procurar o elemento
contido na expresso esquerda que est na posio dada pelo ndice determinado pela expresso entre
colchetes.

Indexao de Arrays

O primeiro ndice de um array o nmero zero e no o nmero um. Portanto, o primeiro elemento pode ser
acessado usando listOfNumbers[0] . Se voc no tem experincia com programao, essa conveno pode levar
um tempo para se acostumar. Mesmo assim, a contagem baseada em zero uma tradio de longa data no
mundo da tecnologia e, desde que seja seguida consistentemente (que o caso no JavaScript), ela funciona
bem.

Propriedades
Ns vimos, em exemplos anteriores, algumas expresses de aparncia suspeita, como myString.length (para
acessar o tamanho de uma string) e Math.max (funo que retorna o valor mximo). Essas so expresses que
acessam uma propriedade em algum valor. No primeiro caso, acessamos a propriedade length do valor contido
em myString . No segundo, acessamos a propriedade chamada max no objeto Math (que um conjunto de
valores e funes relacionados matemtica).

Praticamente todos os valores no JavaScript possuem propriedades. As nicas excees so null e undefined .
Se voc tentar acessar a propriedade em algum deles, voc receber um erro.

null.length;
// TypeError: Cannot read property 'length' of null

43
As duas formas mais comuns de acessar propriedades no JavaScript so usando ponto e colchetes. Ambos
value.x e value[x] acessam uma propriedade em value , mas no necessariamente a mesma propriedade. A
diferena est em como o x interpretado. Quando usamos o ponto, a parte aps o ponto deve ser um nome de
varivel vlido e referente ao nome da propriedade em questo. Quando usamos colchetes, a expresso entre os
colchetes avaliada para obter o nome da propriedade. Enquanto que value.x acessa a propriedade chamada
"x", value[x] tenta avaliar a expresso x e, ento, usa o seu resultado como o nome da propriedade.

Portanto, se voc sabe que a propriedade que voc est interessado se chama "length", voc usa value.length .
Se voc deseja extrair a propriedade cujo nome o valor que est armazenado na varivel i , voc usa value[i] .
Devido ao fato de que nomes de propriedades podem ser qualquer string, se voc quiser acessar as
propriedades "2" ou "John Doe", voc deve usar os colchetes: value[2] ou value["John Doe"] , pois mesmo
sabendo exatamente o nome da propriedade, "2" e "John Doe" no so nomes vlidos de variveis, sendo
impossvel acess-los usando a notao com o ponto.

Mtodos
Ambos os objetos string e array possuem, alm da propriedade length , um nmero de propriedades que se
referem valores de funo.

var doh = "Doh";


console.log(typeof doh.toUpperCase);
// function
console.log(doh.toUpperCase());
// DOH

Toda string possui uma propriedade toUpperCase . Quando chamada, ela retornar uma cpia da string com todas
as letras convertidas para maisculas. Existe tambm a propriedade toLowerCase , que voc j pode imaginar o
que faz.

Curiosamente, mesmo que a chamada para toUpperCase no passe nenhum argumento, de alguma forma a
funo tem acesso string "Doh" , que o valor em que a propriedade foi chamada. Como isso funciona
exatamente descrito no Captulo 6.

As propriedades que contm funes so geralmente chamadas de mtodos do valor a que pertencem. Como
por exemplo, " toUpperCase um mtodo de uma string".

O exemplo a seguir demonstra alguns mtodos que os objetos do tipo array possuem:

var mack = [];


mack.push("Mack");
mack.push("the", "Knife");
console.log(mack);
// ["Mack", "the", "Knife"]
console.log(mack.join(" "));
// Mack the Knife
console.log(mack.pop());
// Knife
console.log(mack);
// ["Mack", "the"]

O mtodo push pode ser usado para adicionar valores ao final de um array. O mtodo pop faz o contrrio, remove
o valor que est no final do array e o retorna. Um array de strings pode ser combinado em uma nica string com o
mtodo join . O argumento passado para join determina o texto que ser inserido entre cada elemento do
array.

44
Objetos
Voltando ao esquilo-homem. Um conjunto de registros dirios pode ser representado como um array. Entretanto,
as entradas no so compostas apenas por um nmero ou uma string, pois precisam armazenar a lista de
atividades e um valor booleano que indica se Jacques se transformou em um esquilo. Ns deveramos,
idealmente, agrupar esses valores em um nico valor e, em seguida, coloc-los em um array com os registros.

Valores do tipo ob jeto so colees arbitrrias de propriedades, sendo que podemos adicionar ou remover essas
propriedades da forma que desejarmos. Uma maneira de criar um objeto usando a notao com chaves.

var day1 = {
squirrel: false,
events: ["work", "touched tree", "pizza", "running",
"television"]
};
console.log(day1.squirrel);
// false
console.log(day1.wolf);
// undefined
day1.wolf = false;
console.log(day1.wolf);
// false

Dentro das chaves, podemos informar uma lista de propriedades separadas por vrgulas. Cada propriedade
escrita com um nome seguido de dois pontos e uma expresso que fornece o valor da propriedade. Espaos e
quebras de linha no fazem diferena. Quando um objeto se estende por vrias linhas, indent-lo, como mostrado
no exemplo anterior, melhora a legibilidade. Propriedades cujos nomes no so variveis ou nmeros vlidos
precisam estar entre aspas.

var descriptions = {
work: "Went to work",
"touched tree": "Touched a tree"
};

Isso significa que as chaves possuem dois significados no JavaScript. Quando usadas no incio de uma
declarao, elas definem o comeo de um bloco de declaraes. Em qualquer outro caso, elas descrevem um
objeto. Felizmente, praticamente intil iniciar uma declarao com as chaves de um objeto e, em programas
normais, no existe ambiguidade entre os dois casos de uso.

Tentar acessar uma propriedade que no existe ir produzir um valor undefined , o que acontece quando tentamos
ler pela primeira vez a propriedade wolf no exemplo anterior.

possvel atribuir um valor a uma propriedade usando o operador = . Isso ir substituir o valor de uma
propriedade, caso ela exista, ou criar uma nova propriedade no objeto se no existir.

Retornando brevemente ao nosso modelo de tentculos para associaes de variveis, as associaes de


propriedades funcionam de forma similar. Elas receb em valores, mas outras variveis e propriedades podem
tambm estar associadas aos mesmos valores. Voc pode pensar em objetos como polvos com um nmero
qualquer de tentculos, e cada tentculo com um nome escrito nele.

45
O operador delete corta um tentculo de nosso polvo. Ele um operador unrio que, quando aplicado a uma
propriedade, ir remover tal propriedade do objeto. Isso no algo comum de se fazer, mas possvel.

var anObject = {left: 1, right: 2};


console.log(anObject.left);
// 1
delete anObject.left;
console.log(anObject.left);
// undefined
console.log("left" in anObject);
// false
console.log("right" in anObject);
// true

O operador binrio in , quando aplicado a uma string ou a um objeto, retorna um valor booleano que indica se
aquele objeto possui aquela propriedade. A diferena entre alterar uma propriedade para undefined e remov-la
de fato, que no primeiro caso, o objeto continua com a propriedade (ela simplesmente no tem um valor muito
interessante), enquanto que no segundo caso, a propriedade no estar mais presente no objeto e o operador
in retornar false .

Os arrays so, ento, apenas um tipo especializado de objeto para armazenar sequncias de coisas. Se voc
executar typeof [1, 2] , ir produzir "object" . Voc pode interpret-los como polvos com longos tentculos de
tamanhos semelhantes, ordenados em linha e rotulados com nmeros.

46
Portanto, podemos representar o dirio de Jacques como um array de objetos.

var journal = [
{events: ["work", "touched tree", "pizza",
"running", "television"],
squirrel: false},
{events: ["work", "ice cream", "cauliflower",
"lasagna", "touched tree", "brushed teeth"],
squirrel: false},
{events: ["weekend", "cycling", "break",
"peanuts", "beer"],
squirrel: true},
/* and so on... */
];

Mutabilidade
Ns iremos chegar na programao de fato muito em breve. Mas h, primeiramente, uma ltima parte terica que
precisamos entender.

Ns vimos que os valores de objetos podem ser modificados. Os tipos de valores discutidos nos captulos
anteriores, tais como nmeros, strings e booleanos, so imutveis. impossvel mudar o valor j existente
desses tipos. Voc pode, a partir deles, combin-los e criar novos valores, mas quando voc analisar um valor
especfico de string, ele ser sempre o mesmo, sendo que o seu texto no pode ser alterado. Por exemplo, se
voc tiver referncia a uma string que contm "cat" , impossvel que outro cdigo altere os caracteres dessa
string para "rat" .

Por outro lado, no caso de objetos, o contedo de um valor pode ser modificado quando alteramos suas
propriedades.

Quando temos dois nmeros, 120 e 120, podemos consider-los exatamente os mesmos nmeros, mesmo se
eles no fazem referncia aos mesmos bits fsicos. Entretanto, no caso de objetos h uma diferena entre ter
duas referncias para o mesmo objeto e ter dois objetos diferentes que possuem as mesmas propriedades.
Considere o cdigo a seguir:

var object1 = {value: 10};


var object2 = object1;
var object3 = {value: 10};

console.log(object1 == object2);
// true
console.log(object1 == object3);
// false

object1.value = 15;
console.log(object2.value);
// 15
console.log(object3.value);
// 10

As variveis object1 e object2 esto associadas ao mesmo ob jeto e, por isso, alterar object1 tambm altera o
valor de object2 . A varivel object3 aponta para um objeto diferente, o qual inicialmente contm as mesmas
propriedades de object1 e sua existncia totalmente separada.

Quando comparamos objetos, o operador == do JavaScript ir retornar true apenas se ambos os objetos
possuem exatamente o mesmo valor. Comparar objetos diferentes ir retornar false mesmo se eles tiverem
contedos idnticos. No existe uma operao nativa no JavaScript de "deep" comparison (comparao

47
"profunda"), onde se verifica o contedo de um objeto, mas possvel escrev-la voc mesmo (que ser um dos
exerccios ao final desse captulo).

O log da licantropia
Jacques inicia seu interpretador de JavaScript e configura o ambiente que ele precisa para manter o seu dirio.

var journal = [];

function addEntry(events, didITurnIntoASquirrel) {


journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
}

E ento, todas as noites s dez ou as vezes na manh seguinte aps descer do topo de sua estante de livros, ele
faz o registro do dia.

addEntry(["work", "touched tree", "pizza", "running",


"television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna",
"touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts",
"beer"], true);

Uma vez que ele tem dados suficientes, ele pretende calcular a correlao entre sua transformao em esquilo e
cada um dos eventos do dia e espera aprender algo til a partir dessas correlaes.

A correlao uma medida de dependncia entre variveis ("variveis" no sentido estatstico e no no sentido do
JavaScript). Ela geralmente expressa em um coeficiente que varia de -1 a 1. Zero correlao significa que as
variveis no so relacionadas, enquanto que a correlao de um indica que as variveis so perfeitamente
relacionadas (se voc conhece uma, voc tambm conhece a outra). A correlao negativa de um tambm indica
que as variveis so perfeitamente relacionadas, mas so opostas (quando uma verdadeira, a outra falsa).

Para variveis binrias (booleanos), o coeficiente phi () fornece uma boa forma de medir a correlao e
relativamente fcil de ser calculado. Para calcular , precisamos de uma tabela n que contm o nmero de vezes
que as diversas combinaes das duas variveis foram observadas. Por exemplo, podemos considerar o evento
de "comer pizza" e coloc-lo nessa tabela da seguinte maneira:

48
pode ser calculado usando a seguinte frmula, onde n se refere tabela:

= (n11n00 - n10n01) / n1n0n1n0


[TODO: Adicionar formatao correta da frmula aps converter em asciidoc]

A notao n01 indica o nmero de ocorrncias nas quais a primeira varivel (transformar-se em esquilo) falsa
(0) e a segunda varivel (pizza) verdadeira (1). Nesse exemplo, n01 igual a 9.

O valor n1 se refere soma de todas as medidas nas quais a primeira varivel verdadeira, que no caso do
exemplo da tabela 5. Da mesma forma, n0 se refere soma de todas as medidas nas quais a segunda
varivel falsa.

Portanto, para a tabela de pizza, a parte de cima da linha (o dividendo) seria 1x76 - 4x9 = 40, e a parte de baixo (o
divisor) seria a raiz quadrada de 5x85x10x80, ou 340000. Esse clculo resulta em 0.069, o que um valor
bem pequeno. Comer pizza parece no ter influncia nas transformaes.

Calculando a correlao
No JavaScript, podemos representar uma tabela dois por dois usando um array com quatro elementos ( [76, 9, 4,

1] ). Podemos tambm usar outras formas de representaes, como por exemplo um array contendo dois arrays
com dois elementos cada ( [[76, 9], [4, 1]] ), ou at mesmo um objeto com propriedades nomeadas de "11" e
"01" . Entretanto, a maneira mais simples e que faz com que seja mais fcil acessar os dados utilizando um
array com quatro elementos. Ns iremos interpretar os ndices do array como elementos binrios de dois bits,
onde o dgito a esquerda (mais significativo) se refere varivel do esquilo, e o dgito a direita (menos
significativo) se refere varivel do evento. Por exemplo, o nmero binrio 10 se refere ao caso no qual Jacques
se tornou um esquilo, mas o evento no ocorreu (por exemplo "pizza"). Isso aconteceu quatro vezes, e j que o
nmero binrio 10 equivalente ao nmero 2 na notao decimal, iremos armazenar esse valor no ndice 2 do
array.

Essa a funo que calcula o coeficiente de um array desse tipo:

49
function phi(table) {
return (table[3] * table[0] - table[2] * table[1]) /
Math.sqrt((table[2] + table[3]) *
(table[0] + table[1]) *
(table[1] + table[3]) *
(table[0] + table[2]));
}

console.log(phi([76, 9, 4, 1]));
// 0.068599434

Essa simplesmente uma traduo direta da frmula de para o JavaScript. Math.sqrt a funo que calcula a
raiz quadrada, fornecida pelo objeto Math que padro do JavaScript. Temos que somar dois campos da tabela
para encontrar valores como n1, pois a soma das linhas ou colunas no so armazenadas diretamente em
nossa estrutura de dados.

Jacques manteve seu dirio por trs meses. O conjunto de dados resultante est disponvel no ambiente de
cdigo desse captulo, armazenado na varivel JOURNAL e em um arquivo que pode ser baixado.

Para extrair uma tabela dois por dois de um evento especfico desse dirio, devemos usar um loop para percorrer
todas as entradas e ir adicionando quantas vezes o evento ocorreu em relao s transformaes de esquilo.

function hasEvent(event, entry) {


return entry.events.indexOf(event) != -1;
}

function tableFor(event, journal) {


var table = [0, 0, 0, 0];
for (var i = 0; i < journal.length; i++) {
var entry = journal[i], index = 0;
if (hasEvent(event, entry)) index += 1;
if (entry.squirrel) index += 2;
table[index] += 1;
}
return table;
}

console.log(tableFor("pizza", JOURNAL));
// [76, 9, 4, 1]

A funo hasEvent testa se uma entrada contm ou no o evento em questo. Os arrays possuem um mtodo
indexOf que procura pelo valor informado no array (nesse exemplo o nome do evento), e retorna o ndice onde ele
foi encontrado ou -1 se no for. Portanto, se a chamada de indexOf no retornar -1, sabemos que o evento foi
encontrado.

O corpo do loop presente na funo tableFor , descobre qual caixa da tabela cada entrada do dirio pertence,
verificando se essa entrada contm o evento especfico e se o evento ocorreu juntamente com um incidente de
transformao em esquilo. O loop adiciona uma unidade no nmero contido no array que corresponde a essa
caixa na tabela.

Agora temos as ferramentas necessrias para calcular correlaes individuais. O nico passo que falta
encontrar a correlao para cada tipo de evento que foi armazenado e verificar se algo se sobressai. Como
podemos armazenar essas correlaes assim que as calculamos?

Objetos como mapas

50
Uma maneira possvel armazenar todas as correlaes em um array, usando objetos com as propriedades
name (nome) e value (valor). Porm, isso faz com que o acesso s correlaes de um evento seja bastante
trabalhoso, pois voc teria que percorrer por todo o array para achar o objeto com o name certo. Poderamos
encapsular esse processo de busca em uma funo e, mesmo assim, iramos escrever mais cdigo e o
computador iria trabalhar mais do que o necessrio.

Uma maneira melhor seria usar as propriedades do objeto nomeadas de acordo com o tipo do evento. Podemos
usar a notao de colchetes para acessar e ler as propriedades e, alm disso, usar o operador in para testar se
tal propriedade existe.

var map = {};


function storePhi(event, phi) {
map[event] = phi;
}

storePhi("pizza", 0.069);
storePhi("touched tree", -0.081);
console.log("pizza" in map);
// true
console.log(map["touched tree"]);
// -0.081

Um map uma maneira de associar valores de um domnio (nesse caso nomes de eventos) com seus valores
correspondentes em outro domnio (nesse caso coeficientes ).

Existem alguns problemas que podem ser gerados usando objetos dessa forma, os quais sero discutidos no
captulo 6. Por enquanto, no iremos nos preocupar com eles.

E se quisssemos encontrar todos os eventos nos quais armazenamos um coeficiente? Diferentemente de um


array, as propriedades no formam uma sequncia previsvel, impossibilitando o uso de um loop for normal.
Entretanto, o JavaScript fornece uma construo de loop especfica para percorrer as propriedades de um objeto.
Esse loop parecido com o loop for e se distingue pelo fato de utilizar a palavra in .

for (var event in map)


console.log("The correlation for '" + event +
"' is " + map[event]);
// The correlation for 'pizza' is 0.069
// The correlation for 'touched tree' is -0.081

A anlise final
Para achar todos os tipos de eventos que esto presentes no conjunto de dados, ns simplesmente
processamos cada entrada e percorremos por todos os eventos presentes usando um loop. Mantemos um objeto
chamado phis que contm os coeficientes das correlaes de todos os tipos de eventos que foram vistos at
agora. A partir do momento em que encontramos um tipo que no est presente no objeto phis , calculamos o
valor de sua correlao e ento adicionamos ao objeto.

51
function gatherCorrelations(journal) {
var phis = {};
for (var entry = 0; entry < journal.length; entry++) {
var events = journal[entry].events;
for (var i = 0; i < events.length; i++) {
var event = events[i];
if (!(event in phis))
phis[event] = phi(tableFor(event, journal));
}
}
return phis;
}

var correlations = gatherCorrelations(JOURNAL);


console.log(correlations.pizza);
// 0.068599434

Vamos ver o resultado.

for (var event in correlations)


console.log(event + ": " + correlations[event]);
// carrot: 0.0140970969
// exercise: 0.0685994341
// weekend: 0.1371988681
// bread: -0.0757554019
// pudding: -0.0648203724
// and so on...

A grande maioria das correlaes tendem a zero. Comer cenouras, po ou pudim aparentemente no ativam a
transformao em esquilo. Entretanto, elas parecem acontecer com mais frequncia aos finais de semana.
Vamos filtrar os resultados para mostrar apenas as correlaes que so maiores do que 0.1 ou menores do que
-0.1.

for (var event in correlations) {


var correlation = correlations[event];
if (correlation > 0.1 || correlation < -0.1)
console.log(event + ": " + correlation);
}
// weekend: 0.1371988681
// brushed teeth: -0.3805211953
// candy: 0.1296407447
// work: -0.1371988681
// spaghetti: 0.2425356250
// reading: 0.1106828054
// peanuts: 0.5902679812

A-ha! Existem dois fatores nos quais a correlao claramente mais forte que a das outras. Comer amendoins
tem um forte efeito positivo na chance de se transformar em um esquilo, enquanto que escovar os dentes tem um
significante efeito negativo.

Interessante. Vamos tentar uma coisa.

for (var i = 0; i < JOURNAL.length; i++) {


var entry = JOURNAL[i];
if (hasEvent("peanuts", entry) &&
!hasEvent("brushed teeth", entry))
entry.events.push("peanut teeth");
}
console.log(phi(tableFor("peanut teeth", JOURNAL)));
// 1

52
Est bem evidente! O fenmeno ocorre precisamente quando Jacques come amendoins e no escova os dentes.
Se ele no fosse preguioso em relao higiene bucal, ele no sequer teria reparado nesse problema que o
aflige.

Sabendo disso, Jacques simplesmente para de comer amendoins e descobre que isso coloca um fim em suas
transformaes.

Tudo ficou bem com Jacques por um tempo. Entretanto, alguns anos depois, ele perdeu seu emprego e
eventualmente foi forado a trabalhar em um circo, onde suas performances como O Incrvel Homem-Esquilo se
baseavam em encher sua boca com pasta de amendoim antes de cada apresentao. Em um dia de sua pobre
existncia, Jacques no conseguiu se transformar de volta em sua forma humana e fugiu do circo, desapareceu
pela floresta e nunca mais foi visto.

Estudo aprofundado de Arrays


Antes de finalizar esse captulo, gostaria de introduzir alguns outros conceitos relacionados a objetos.
Comearemos com alguns mtodos normalmente teis dos arrays.

Vimos no incio do captulo os mtodos push e pop , que adicionam e removem elementos no final de um array.
Os mtodos correspondentes para adicionar e remover itens no incio de um array so chamados unshift e
shift .

var todoList = [];


function rememberTo(task) {
todoList.push(task);
}
function whatIsNext() {
return todoList.shift();
}
function urgentlyRememberTo(task) {
todoList.unshift(task);
}

O programa anterior gerencia uma lista de tarefas. Voc pode adicionar tarefas no final da lista chamando
rememberTo("eat") e, quando estiver preparado para realizar alguma tarefa, voc chama whatIsNext() para acessar
e remover o primeiro item da lista. A funo urgentlyRememberTo tambm adiciona uma tarefa, porm, ao invs de
adicionar ao final da lista, a adiciona no incio.

O mtodo indexOf tem um irmo chamado lastIndexOf , que comea a pesquisa de um dado elemento pelo final
do array ao invs de comear pelo incio.

console.log([1, 2, 3, 2, 1].indexOf(2));
// 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// 3

Ambos indexOf e lastIndexOf recebem um segundo argumento opcional que indica onde iniciar a pesquisa.

Outro mtodo fundamental o slice , que recebe um ndice de incio e outro de parada, retornando um array que
contm apenas os elementos presentes entre esses ndices. O ndice de incio inclusivo e o de parada
exclusivo.

console.log([0, 1, 2, 3, 4].slice(2, 4));


// [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// [2, 3, 4]

53
Quando o ndice de parada no informado, o slice ir pegar todos os elementos aps o ndice de incio. Strings
tambm possuem o mtodo slice com um comportamento similar.

O mtodo concat pode ser usado para unir arrays, parecido com o que o operador + faz com as strings. O
exemplo a seguir mostra ambos concat e slice em ao. Ele recebe um array e um ndice como argumento,
retornando um novo array que uma cpia do array original, exceto pelo fato de que o elemento no ndice
informado foi removido.

function remove(array, index) {


return array.slice(0, index)
.concat(array.slice(index + 1));
}
console.log(remove(["a", "b", "c", "d", "e"], 2));
// ["a", "b", "d", "e"]

Strings e suas propriedades


Podemos ler propriedades como length e toUpperCase de strings. Porm, caso tente adicionar uma nova
propriedade, ela no ser adicionada.

var myString = "Fido";


myString.myProperty = "value";
console.log(myString.myProperty);
// undefined

Valores do tipo string, numb er e Boolean no so objetos e, mesmo pelo fato da linguagem no reclamar quando
tentamos adicionar novas propriedades neles, elas no so armazenadas. Esses valores so imutveis e no
podem ser alterados.

Mesmo assim, esses tipos possuem propriedades nativas. Toda string possui uma srie de mtodos.
Provavelmente, alguns dos mais teis so slice e indexOf , que so parecidos com os mtodos de array que
possuem o mesmo nome.

console.log("coconuts".slice(4, 7));
// nut
console.log("coconut".indexOf("u"));
// 5

Uma diferena que o indexOf das strings pode receber uma string contendo mais de um caractere, enquanto
que o mtodo correspondente no array procura apenas por um nico elemento.

console.log("one two three".indexOf("ee"));


// 11

O mtodo trim remove todos os espaos vazios (espaos, linhas, tabs e caracteres similares) do comeo e do
final de uma string.

console.log(" okay \n ".trim());


// okay

J vimos a propriedade length das strings. Para acessar caracteres individuais de uma string, podemos usar o
mtodo charAt ou simplesmente ler suas propriedades numricas, da mesma forma que voc faria em um array.

54
var string = "abc";
console.log(string.length);
// 3
console.log(string.charAt(0));
// a
console.log(string[1]);
// b

O Objeto Arguments
Sempre que uma funo invocada, uma varivel especial chamada arguments adicionada ao ambiente no qual
o corpo da funo executa. Essa varivel se refere a um objeto que contm todos os argumentos passados
funo. Lembre-se de que no JavaScript voc pode passar mais (ou menos) argumentos para uma funo,
independentemente do nmero de parmetros que foi declarado.

function noArguments() {}
noArguments(1, 2, 3); // This is okay
function threeArguments(a, b, c) {}
threeArguments(); // And so is this

O objeto arguments possui a propriedade length que nos informa o nmero de argumentos que realmente foi
passado funo. Alm disso, contm uma propriedade para cada argumento, chamadas 0, 1, 2, etc.

Se isso soa muito parecido como um array para voc, voc est certo. Esse objeto muito parecido com um
array. Porm, ele no possui nenhum dos mtodos de array (como slice ou indexOf ), fazendo com que seja um
pouco mais difcil de se usar do que um array de verdade.

function argumentCounter() {
console.log("You gave me", arguments.length, "arguments.");
}
argumentCounter("Straw man", "Tautology", "Ad hominem");
// You gave me 3 arguments.

Algumas funes podem receber qualquer nmero de argumentos, como no caso de console.log . Essas funes
normalmente percorrem por todos os valores em seu objeto arguments e podem ser usadas para criar interfaces
extremamente agradveis. Por exemplo, lembre-se de como criamos as entradas no dirio do Jacques.

addEntry(["work", "touched tree", "pizza", "running",


"television"], false);

Devido ao fato de que essa funo ir ser executada muitas vezes, poderamos criar uma alternativa mais
simples.

function addEntry(squirrel) {
var entry = {events: [], squirrel: squirrel};
for (var i = 1; i < arguments.length; i++)
entry.events.push(arguments[i]);
journal.push(entry);
}
addEntry(true, "work", "touched tree", "pizza",
"running", "television");

Essa verso l o primeiro argumento ( squirrel ) da forma normal e depois percorre o resto dos argumentos (o
loop pula o primeiro argumento, iniciando no ndice 1) juntando-os em um array.

55
O Objeto Math
Como vimos anteriormente, Math uma caixa de ferramentas com funes relacionadas a nmeros, tais como
Math.max (mximo), Math.min (mnimo) e Math.sqrt (raiz quadrada).

O objeto Math usado como um container para agrupar uma srie de funcionalidades relacionadas. Existe
apenas um nico objeto Math e, na maioria das vezes, ele no til quando usado como um valor. Mais
precisamente, ele fornece um namespace (espao nominal) para que todas essas funes e valores no
precisem ser declaradas como variveis globais.

Possuir muitas variveis globais "polui" o namespace. Quanto mais nomes so usados, mais provveis so as
chances de acidentalmente sobrescrever o valor de uma varivel. Por exemplo, provvel que voc queira chamar
algo de max em um de seus programas. Sabendo que no JavaScript a funo nativa max est contida de forma
segura dentro do objeto Math , no precisamos nos preocupar em sobrescrev-la.

Muitas linguagens iro parar voc ou, ao menos, avis-lo quando tentar definir uma varivel com um nome que j
est sendo usado. Como o JavaScript no faz isso, tenha cuidado.

De volta ao objeto Math , caso precise realizar clculos trigonomtricos, Math pode ajud-lo. Ele contm cos

(cosseno), sin (seno) e tan (tangente), tanto quanto suas funes inversas aos , asin e atan

respectivamente. O nmero (pi), ou pelo menos a aproximao que possvel ser representada atravs de um
nmero no JavaScript, est disponvel como Math.PI . (Existe uma tradio antiga na programao de escrever os
nomes de valores constantes em caixa alta).

function randomPointOnCircle(radius) {
var angle = Math.random() * 2 * Math.PI;
return {x: radius * Math.cos(angle),
y: radius * Math.sin(angle)};
}
console.log(randomPointOnCircle(2));
// {x: 0.3667, y: 1.966}

Se senos e cossenos no so muito familiares para voc, no se preocupe. Quando eles forem usados no
Captulo 13 desse livro, eu lhe explicarei.

O exemplo anterior usa Math.random . Essa uma funo que retorna um nmero "pseudo-aleatrio" entre zero
(includo) e um (excludo) toda vez que voc a chama.

console.log(Math.random());
// 0.36993729369714856
console.log(Math.random());
// 0.727367032552138
console.log(Math.random());
// 0.40180766698904335

Embora os computadores sejam deterministas (sempre reagem da mesma maneira quando so usados os
mesmos dados de entrada), possvel fazer com que eles produzam nmeros que paream ser aleatrios. Para
fazer isso, a mquina mantm um nmero (ou uma quantidade deles) armazenado em seu estado interno.
Assim, toda vez que um nmero aleatrio requisitado, ela executa alguns clculos complicados e deterministas
usando esse estado interno e, ento, retorna parte do resultado desses clculos. A mquina tambm utiliza
esses resultados para mudar o seu estado interno, fazendo com que o prximo nmero "aleatrio" produzido seja
diferente.

Se ao invs de um nmero fracionrio, quisermos um nmero aleatrio inteiro, podemos usar Math.floor (que
arredonda o nmero para o menor valor inteiro mais prximo) no resultado de Math.random .

56
console.log(Math.floor(Math.random() * 10));
// 2

Multiplicar o nmero aleatrio por dez resulta em um nmero que seja maior ou igual a zero e menor do que dez.
Devido ao fato de que Math.floor arredonda o valor para baixo, essa expresso ir produzir, com chances iguais,
qualquer nmero de zero a nove.

Tambm existem as funes Math.ceil (para arredondar o valor para o maior nmero inteiro mais prximo) e
Math.round (para arredondar o valor para o nmero inteiro mais prximo).

O objeto global
O escopo global, que o espao no qual as variveis globais residem, tambm pode ser abordado como um
objeto no JavaScript. Cada varivel global est presente como uma propriedade desse objeto. Nos navegadores,
o objeto do escopo global armazenado na varivel window .

var myVar = 10;


console.log("myVar" in window);
// true
console.log(window.myVar);
// 10

Resumo
Objetos e arrays (que so tipos especficos de objetos) fornecem maneiras de agrupar uma conjunto de valores
em um nico valor. Conceitualmente, ao invs de tentar carregar e manter todas as coisas individualmente em
nossos braos, eles nos permitem colocar e carregar todas as coisas relacionadas dentro de uma bolsa.

Com exceo de null e undefined , a maioria dos valores no JavaScript possuem propriedades e so acessados
usando value.propName ou value["propName"] . Objetos tendem a usar nomes para suas propriedades e
armazenam mais o menos uma quantidade fixa delas. Por outro lado, os Arrays normalmente contm
quantidades variveis de valores conceitualmente iguais e usam nmeros (iniciando do zero) como os nomes de
suas propriedades.

Existem algumas propriedades com nomes especficos nos arrays, como length e uma srie de mtodos.
Mtodos so funes que so armazenadas em propriedades e, normalmente, atuam no valor nas quais elas
so propriedade.

Objetos podem tambm ser usados como mapas, associando valores com seus nomes. O operador in pode
ser usado para verificar se um objeto contm a propriedade com o nome informado. A mesma palavra-chave pode
ser usada em um loop for ( for (var name in object) ) para percorrer todas as propriedades do objeto.

Exerccios
A soma de um intervalo
A introduo desse livro mencionou a seguinte maneira como uma boa alternativa para somar um intervalo de
nmeros:

console.log(sum(range(1, 10)));

57
Escreva uma funo chamada range que recebe dois argumentos, start (incio) e end (fim), e retorna um array
contendo todos os nmeros a partir do valor start at o valor end (incluindo-o).

Em seguida, escreva a funo sum que recebe um array de nmeros como argumento e retorna a soma desses
nmeros. Execute o programa anterior e veja se o resultado retornado de fato 55.

Como exerccio bnus, modifique a sua funo range para aceitar um terceiro argumento opcional que indica o
tamanho do "incremento" usado para construir o array. Se nenhum valor for atribudo ao tamanho do incremento, o
array de elementos ser percorrido em incrementos de um, correspondendo ao comportamento original. A
chamada funo range(1, 10, 2) deve retornar [1, 3, 5, 7, 9] . Certifique-se de que funcione tambm com
valores negativos, fazendo com que range(5, 2, -1) produza [5, 4, 3, 2] .

// Your code here.

console.log(range(1, 10));
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(5, 2, -1));
// [5, 4, 3, 2]
console.log(sum(range(1, 10)));
// 55

Dicas

A maneira mais fcil de construir um array primeiramente inicializar uma varivel usando [] (um novo array
vazio) e, em seguida, chamar vrias vezes o seu mtodo push para adicionar os valores. No se esquea de
retornar o array no final da funo.

Devido ao fato de que o limite final inclusivo, ao invs de usar um simples operador < , voc dever usar o
operador <= para checar o final do seu loop.

Para verificar se o argumento opcional de incremento foi fornecido, voc pode verificar o arguments.length ou
comparar o valor do argumento com undefined . Caso no tenha sido informado, apenas configure o seu valor
padro (1) no incio da funo.

Fazer com que range entenda incrementos negativos provavelmente mais fcil de ser feito escrevendo dois
loops distintos, um para contar valores crescentes e outro para valores decrescentes. Isso se d pelo fato de que,
quando estamos contando valores decrescentes, o operador que compara e verifica se o loop terminou precisa
ser >= ao invs de <= .

Pode ser til usar um valor de incremento diferente do valor padro (por exemplo -1) quando o valor final do
intervalo for menor do que o valor de incio. Dessa forma, ao invs de ficar preso em um loop infinito, range(5, 2)

retorna algo relevante.

Invertendo um array
Os arrays possuem o mtodo reverse , que modifica o array invertendo a ordem em que os elementos aparecem.
Para esse exerccio, escreva duas funes: reverseArray e reverseArrayInPlace . A primeira ( reverseArray ) recebe
um array como argumento e produz um novo array que tem os mesmos elementos com ordem inversa. A
segunda ( reverseArrayInPlace ) funciona da mesma forma que o mtodo reverse , s que nesse caso, invertendo
os elementos do prprio array que foi fornecido como argumento. Ambas as funes no devem usar o mtodo
padro reverse .

Levando em considerao as notas sobre efeitos colaterais e funes puras do captulo anterior, qual verso voc
espera que seja til em mais situaes? Qual delas mais eficiente?

58
// Your code here.

console.log(reverseArray(["A", "B", "C"]));


// ["C", "B", "A"];
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// [5, 4, 3, 2, 1]

Dicas

Existem duas maneiras bvias de implementar reverseArray . A primeira simplesmente iterar o array fornecido
do incio ao fim e usar o mtodo unshift para inserir cada elemento no incio do novo array. A segunda iterar o
array fornecido comeando pelo fim e terminando no incio, usando o mtodo push . Iterar um array de trs para
frente faz com que seja necessrio usar uma notao for um pouco estranha ( var i = array.length - 1; i >= 0; i-

- ).

Inverter o array em questo ( reverseArrayInPlace ) mais difcil. Voc deve ter cuidado para no sobrescrever
elementos que voc precisar posteriormente. Usar reverseArray ou at mesmo copiar o array inteiro
( array.slice(0) uma boa forma de se copiar um array) funciona, mas considerado trapaa.

O truque inverter o primeiro e o ltimo elemento, depois o segundo e o penltimo e assim por diante. Voc pode
fazer isso percorrendo at a metade do valor de length do array (use Math.floor para arredondar o valor para
baixo, pois voc no precisa lidar com o elemento do meio de um array com tamanho mpar) e substituir o
elemento na posio i com o elemento na posio array.length - 1 - i . Voc pode usar uma varivel local para
armazenar temporariamente um dos elementos, sobrescrever o seu valor com o valor do elemento espelhado
(elemento que deseja substituir) e, por fim, colocar o valor da varivel local no lugar onde o elemento espelhado
estava originalmente.

A lista
Objetos tratados como agrupamentos genricos de valores, podem ser usados para construir diversos tipos de
estrutura de dados. Uma estrutura de dados comum a lista (no se confunda com o array). A lista um conjunto
de objetos, sendo que o primeiro objeto contm uma referncia para o segundo, o segundo para o terceiro, e
assim por diante.

var list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};

O resultado uma cadeia de objetos conectados, como mostrado abaixo:

Uma das vantagens das listas que elas podem compartilhar partes de sua estrutura. Por exemplo, se eu
criasse dois novos valores {value: 0, rest: list} e {value: -1, rest: list} (sendo que list uma referncia
varivel definida anteriormente), ambas sero listas independentes que compartilham a mesma estrutura que foi

59
usada para criar os trs ltimos elementos. Alm disso, a lista original ainda uma lista vlida com trs
elementos.

Escreva a funo arrayToList que constri uma estrutura de dados similar estrutura anterior quando fornecido
[1, 2, 3] como argumento e, escreva tambm, a funo listToArray que produz um array a partir de uma lista.
Alm disso, implemente uma funo auxiliar prepend que receber um elemento e uma lista e ser responsvel
por criar uma nova lista com esse novo elemento adicionado ao incio da lista original e, por fim, crie a funo nth

que recebe uma lista e um nmero como argumentos e retorna o elemento que est na posio informada pelo
nmero ou undefined caso no exista elemento em tal posio.

Caso no tenha feito ainda, implemente a verso recursiva da funo nth .

// Your code here.

console.log(arrayToList([10, 20]));
// {value: 10, rest: {value: 20, rest: null}}
console.log(listToArray(arrayToList([10, 20, 30])));
// [10, 20, 30]
console.log(prepend(10, prepend(20, null)));
// {value: 10, rest: {value: 20, rest: null}}
console.log(nth(arrayToList([10, 20, 30]), 1));
// 20

Dicas

Construir uma lista mais fcil de ser feito de trs para frente. Portanto, arrayToList poderia percorrer o array de
trs para frente (veja o exerccio anterior) e, para cada elemento, adicionar um objeto lista. Voc pode usar uma
varivel local para armazenar a parte da lista que foi criada e usar um padro parecido com list = {value: X, rest:

list} para adicionar um elemento.

Para percorrer uma lista (no caso de listToArray e nth ), o seguinte loop for pode ser usado:

for (var node = list; node; node = node.rest) {}

Voc consegue ver como isso funcionaria? A cada iterao do loop, node aponta para a prxima sublista e, por
isso, o corpo da funo pode acessar a propriedade value para pegar o elemento atual. Ao final de cada iterao,
node atualizado apontando para a prxima sublista. Quando seu valor null , ns chegamos ao final da lista e
o loop finalizado.

A verso recursiva de nth ir, similarmente, olhar para uma parte ainda menor do tail (final) da lista e, ao mesmo
tempo, fazer a contagem regressiva do ndice at que chegue ao zero, significando que o ponto no qual ela pode
retornar a propriedade value do n que est sendo verificado. Para acessar o elemento na posio zero de uma
lista, voc pode simplesmente acessar a propriedade value do seu n head (inicial). Para acessar o elemento N

+ 1 , voc pega o n-simo elemento da lista que est contido na propriedade rest da lista em questo.

Comparao "profunda"
O operador == compara objetos pelas suas identidades. Entretanto, algumas vezes, voc pode preferir comparar
os valores das suas propriedades de fato.

Escreva a funo deepEqual que recebe dois valores e retorna true apenas se os valores forem iguais ou se
forem objetos que possuem propriedades e valores iguais quando comparados usando uma chamada recursiva
de deepEqual .

Para saber se a comparao entre duas coisas deve ser feita pela identidade (use o operador === para isso) ou
pela verificao de suas propriedades, voc pode usar o operador typeof . Se ele produzir "object" para ambos
os valores, voc dever fazer uma comparao "profunda". Entretanto, voc deve levar em considerao uma

60
exceo: devido a um acidente histrico, typeof null tambm produz "object" .

// Your code here.

var obj = {here: {is: "an"}, object: 2};


console.log(deepEqual(obj, obj));
// true
console.log(deepEqual(obj, {here: 1, object: 2}));
// false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// true

Dicas

O teste para saber se est lidando com um objeto real dever ser parecido com typeof x == "object" && x != null .
Tome cuidado para comparar as propriedades apenas quando ambos argumentos forem objetos. Em todos os
outros casos, voc pode simplesmente retornar imediatamente o resultado da aplicao do operador === .

Use um loop for/in para percorrer todas as propriedades. Voc precisa verificar se ambos os objetos possuem
o mesmo conjunto de propriedades e se essas propriedades tm valores idnticos. O primeiro teste pode ser
feito contando a quantidade de propriedades em cada objeto e retornar false se forem diferentes. Caso seja o
mesmo, ento, percorra todas as propriedades de um objeto e, para cada uma delas, verifique se o outro objeto
tambm a possui. Os valores das propriedades so comparados usando uma chamada recursiva para
deepEqual .

Para retornar o valor correto da funo, mais fcil retornar imediatamente false quando qualquer diferena for
encontrada e retornar true apenas ao final da funo.

61
Funes de ordem superior
"Tzu-li e Tzu-ssu estavam se gabando do tamanho dos seus ltimos programas. 'Duzentas mil linhas sem
contar os comentrios!', disse Tzu-li. Tzu-ssu respondeu: 'Pssh, o meu j tem quase um milho de linhas'.
Mestre Yuan-Ma disse: 'Meu melhor programa tem quinhentas linhas'. Ouvindo isso, Tzu-li e Tzu-ssu ficaram
esclarecidos."

Master Yuan-Ma, The Book of Programming

"Existem duas maneiras de construir o design de um software: uma maneira deix-lo to simples de tal
forma em que obviamente no h deficincias, e a outra torn-lo to complicado que no haver
deficincias bvias."

C.A.R. Hoare, 1980 ACM Turing Award Lecture

Um programa grande um programa custoso, e no necessariamente devido ao tempo que leva para construir.
Tamanho quase sempre envolve uma complexidade e complexidade confunde os programadores.
Programadores confusos tendem a criar erros (bugs) no programa. Um programa grande tem a possibilidade de
esconder bugs que so difceis de serem encontrados.

Vamos rapidamente abordar dois exemplos que foram citados na introduo. O primeiro contm um total de 6
linhas.

var total = 0, count = 1;


while (count <= 10) {
total += count;
count += 1;
}
console.log(total);

O segundo necessita de duas funes externas e escrito em apenas uma linha.

console.log(sum(range(1, 10)));

Qual mais propenso a erros?

Se medirmos o tamanho das definies de sum e range , o segundo programa tambm ser grande - at maior
do que o primeiro. Mesmo assim, eu diria que ele o mais provvel a estar correto.

A razo dele possivelmente ser o mais correto, que a soluo expressa em um vocabulrio que corresponde
ao problema que est sendo resolvido. Somar um intervalo de nmeros no se trata de laos de repetio e
contadores. Trata-se de intervalos e somas.

As definies desse vocabulrio (as funes sum e range ) ainda assim tero que lidar com laos de repetio,
contadores e outros detalhes secundrios. No entanto, devido ao fato de representarem conceitos mais simples,
elas acabam sendo mais fceis de se entender.

Abstrao
No contexto da programao esse tipo de vocabulrio geralmente expressado pelo termo abstraes.
Abstraes escondem detalhes e nos d a habilidade de falar sobre problemas em alto nvel (mais abstrato).

Isto uma analogia que compara duas receitas de sopa de ervilha:

62
"Coloque 1 copo de ervilha por pessoa num recipiente. Adicione gua at as ervilhas ficarem cobertas.
Deixa as ervilhas na gua por no mnimo 12 horas. Tire as ervilhas da gua e coloque-as numa panela.
Adicione 4 copos de gua por pessoa. Cubra a panela e deixe-as cozinhando por duas horas. Pegue meia
cebola por pessoa, corte em pedaos, adicione s ervilhas. Pegue um talo de aipo por pessoa, corte em
pedaos e adicione s ervilhas. Pegue uma cenoura por pessoa, corte em pedaos! Adicione s ervilhas.
Cozinhe por 10 minutos".

E a segunda receita:

"Para uma pessoa: 1 copo de ervilha, meia cebola, um talo de aipo e uma cenoura." Embeba as ervilhas por
12 horas, ferva por 2 horas em 4 copos de gua (por pessoa). Pique e adicione os vegetais. Deixe cozinhar
por mais 10 minutos".

A segunda bem menor e mais fcil de interpretar. Mas ela necessita de um conhecimento maior sobre algumas
palavras relacionadas cozinhar como: embeber, ferva, pique e vegetais.

Quando programamos no podemos contar com todas as palavras do dicionrio para expressar o que
precisamos. Assim cairemos no primeiro padro de receita - onde damos cada comando que o computador tem
que realizar, um por um, ocultando os conceitos de alto nveis que se expressam.

Perceber quando um conceito implora para ser abstrado em uma nova palavra um costume que tem de virar
algo natural quando programamos.

Abstraindo Array transversal


Funes, como vimos anteriormente, so boas maneiras para se criar abstraes. Mas algumas vezes elas ficam
aqum.

No captulo anterior, esse tipo de loop apareceu vrias vezes:

var array = [1, 2, 3];


for (var i = 0; i < array.length; i++) {
var current = array[i];
console.log(current);
}

O que ele diz : "Para cada elemento do array, registre no console ". Mas utiliza um jeito redundante que envolve
uma varivel contadora, uma checagem do tamanho do array e a declarao de uma varivel extra para pegar o
elemento atual. Deixando de lado a monstruosidade do cdigo, ele tambm nos d espao para possveis erros:
Podemos reusar a varivel i , digitar errado length como lenght , confundir as variveis i e current e por a
vai.

Ento vamos tentar abstrair isso em uma nova funo. Consegue pensar em alguma forma?

trivial escrever uma funo que passa sobre um array e chama console.log para cada elemento:

function logEach(array) {
for (var i = 0; i < array.length; i++)
console.log(array[i]);
}

Mas e se quisermos fazer algo diferente do que apenas registrar os elementos? Uma vez que "fazer alguma
coisa" pode ser representado com uma funo e as funes so apenas valores, podemos passar nossas aes
como um valor para a funo.

63
function forEach(array, action) {
for (var i = 0; i < array.length; i++)
action(array[i]);
}

forEach(["Wampeter", "Foma", "Granfalloon"], console.log);


// Wampeter
// Foma
// Granfalloon

Normalmente voc no ir passar uma funo predefinida para o forEach , mas ela ser criada localmente dentro
da funo.

var numbers = [1, 2, 3, 4, 5], sum = 0;


forEach(numbers, function(number) {
sum += number;
});
console.log(sum);
// 15

Isso parece muito com um loop clssico, com o seu corpo escrito como um bloco logo abaixo. No entanto o
corpo est dentro do valor da funo, bem como esta dentro dos parnteses da chamada de forEach . por isso
que precisamos fechar com chave e parntese.

Nesse padro, podemos simplificar o nome da varivel ( number ) pelo elemento atual, ao invs de simplesmente
ter que busc-lo fora do array manualmente.

De fato, no precisamos definir um mtodo forEach . Ele esta disponvel como um mtodo padro em arrays .
Quando um array fornecido para o mtodo agir sobre ele, o forEach espera apenas um argumento obrigatrio:
a funo a ser executada para cada elemento.

Para ilustrar o quo til isso , vamos lembrar da funo que vimos no captulo anterior, onde continha dois
arrays transversais.

function gatherCorrelations(journal) {
var phis = {};
for (var entry = 0; entry < journal.length; entry++) {
var events = journal[entry].events;
for (var i = 0; i < events.length; i++) {
var event = events[i];
if (!(event in phis))
phis[event] = phi(tableFor(event, journal));
}
}
return phis;
}

Trabalhando com forEach faz parecer levemente menor e bem menos confuso.

function gatherCorrelations(journal) {
var phis = {};
journal.forEach(function(entry) {
entry.events.forEach(function(event) {
if (!(event in phis))
phis[event] = phi(tableFor(event, journal));
});
});
return phis;
}

64
Funes de ordem superior
Funes que operam em outras funes, seja ela apenas devolvendo argumentos, so chamadas de funes de
ordem superior. Se voc concorda com o fato de que as funes so valores normais, no h nada de notvel
sobre o fato de sua existncia. O termo vem da matemtica onde a distino entre funes e outros valores
levado mais a srio.

Funes de ordem superior nos permitem abstrair as aes. Elas podem serem de diversas formas. Por
exemplo, voc pode ter funes que criam novas funes.

function greaterThan(n) {
return function(m) { return m > n; };
}
var greaterThan10 = greaterThan(10);
console.log(greaterThan10(11));
// true

E voc pode ter funes que alteram outras funes.

function noisy(f) {
return function(arg) {
console.log("calling with", arg);
var val = f(arg);
console.log("called with", arg, "- got", val);
return val;
};
}
noisy(Boolean)(0);
// calling with 0
// called with 0 - got false

Voc pode at escrever funes que fornecem novos tipos de fluxos de controles.

function unless(test, then) {


if (!test) then();
}
function repeat(times, body) {
for (var i = 0; i < times; i++) body(i);
}

repeat(3, function(n) {
unless(n % 2, function() {
console.log(n, "is even");
});
});
// 0 is even
// 2 is even

As regras de escopo lxico que discutimos no captulo 3 trabalham a nosso favor quando usamos funes dessa
maneira. No exemplo acima, a varivel n um parmetro da funo externa. Mas como as funes internas
esto dentro do ambiente externo, podemos usar a varivel n . Os "corpos" de tais funes internas podem
acessar as variveis que esto em torno delas. Eles podem desempenhar um papel similar aos blocos {}

usados em loops e expresses condicionais. Uma diferena importante que variveis declaradas dentro das
funes internas no podem ser acessadas fora da funo. Isso geralmente algo bom.

Passando argumentos

65
A funo noisy declarada abaixo, envolve seu argumento em outra funo, isso gera uma grave deficincia.

function noisy(f) {
return function(arg) {
console.log("calling with", arg);
var val = f(arg);
console.log("called with", arg, "- got", val);
return val;
};
}

Se f receber mais de um parmetro, ele recebe apenas o primeiro. Poderamos acrescentar vrios argumentos
para a funo interna ( arg1 , arg2 , e assim por diante) e passar elas para f , mas mesmo assim isso no
deixaria explcito quantos seriam suficientes. Essa soluo limita algumas informaes de f como por exemplo
arguments.length . Sempre passaremos a mesma quantidade de argumentos, mas nunca saberemos a
quantidade exata de argumentos que foi passada.

Para esse tipo de situao, funes em JavaScript possuem um mtodo chamado apply . Voc passa um array

(ou um array como objeto ) como argumento, e ele ir chamar a funo com estes argumentos.

function transparentWrapping(f) {
return function() {
return f.apply(null, arguments);
};
}

Essa funo intil, mas nos mostra o padro que estamos interessados, a funo passa todos os argumentos
dados para f e retorna, apenas estes argumentos, para f . Ela faz isso passando seus prprios argumentos
para o objeto apply . O primeiro argumento do apply , estamos passando null , isto pode ser usado para simular
uma chamada de mtodo. Iremos voltar a ver isto novamente no prximo captulo.

JSON
Funes de ordem superior que aplicam uma funo para os elementos de um array so bastante usadas em
JavaScript. O mtodo forEach uma funo mais primitiva. Existe outras variantes disponveis como mtodos em
arrays . Para acostumarmos com eles vamos brincar com um outro conjunto de dados.

H alguns anos, algum juntou um monte de arquivos e montou um livro sobre a histria do nome da minha
famlia (Haverbeke que significa Oatbrook). Eu abri na esperana de encontrar cavaleiros, piratas, e alquimistas...
mas o livro acaba por ser principalmente de agricultores de Flamengos. Para minha diverso extrai uma
informao sobre os meus antepassados e coloquei em um formato legvel por um computador.

O arquivo que eu criei se parece mais ou menos assim:

[
{"name": "Emma de Milliano", "sex": "f",
"born": 1876, "died": 1956,
"father": "Petrus de Milliano",
"mother": "Sophia van Damme"},
{"name": "Carolus Haverbeke", "sex": "m",
"born": 1832, "died": 1905,
"father": "Carel Haverbeke",
"mother": "Maria van Brussel"},
and so on
]

66
Este formato chamado de JSON (pronuncia-se "Jason") que significa JavaScript Ob ject Notation. JSON
amplamente utilizado como armazenamento de dados e formato de comunicao na Web .

JSON se escreve semelhantemente como arrays e objetos em JavaScript, mas com algumas restries. Todos
os nomes das propriedades devem ficar entre aspas duplas e apenas expresses de dados simples so
permitidos, no permitido chamadas de funes, variveis ou qualquer coisa que envolva clculo real.
Comentrios no so permitidos em JSON .

JavaScript nos fornece duas funes JSON.stringify e JSON.parse , que convertem dados para este formato. O
primeiro recebe um valor em JavaScript e retorna uma string codificada em JSON . A segunda obtm uma string e
converte-a para um valor que ele codifica.

var string = JSON.stringify({name: "X", born: 1980});


console.log(string);
// {"name":"X","born":1980}
console.log(JSON.parse(string).born);
// 1980

O varivel ANCESTRY_FILE est disponvel na sandbox deste captulo para download no site, onde est o contedo
do meu arquivo JSON como uma string . Vamos decodific-lo e ver quantas pessoas contm.

var ancestry = JSON.parse(ANCESTRY_FILE);


console.log(ancestry.length);
// 39

Filtrando um array
Para encontrar as pessoas no conjunto de dados dos ancestrais que eram jovens em 1924, a seguinte funo
pode ser til. Ele filtra os elementos em uma matriz que no passa pelo teste.

function filter(array, test) {


var passed = [];
for (var i = 0; i < array.length; i++) {
if (test(array[i]))
passed.push(array[i]);
}
return passed;
}

console.log(filter(ancestry, function(person) {
return person.born > 1900 && person.born < 1925;
}));
// [{name: "Philibert Haverbeke", }, ]

Este utiliza um argumento chamado de test , com um valor de funo, para preencher uma lacuna na
computao. A funo test chamada para cada elemento, e o seu valor de retorno determina se um elemento
includo no array retornado.

Trs pessoas no arquivo estavam vivas e jovens em 1924: meu av, minha av e minha tia-av.

Observe como a funo filter , em vez de excluir os elementos do array , constri um novo com apenas os
elementos que passaram no teste. Esta funo primitiva. No modifica o array que foi dado.

Assim como forEach , filter um mtodo padro de arrays . O exemplo define uma funo s para mostrar o
que ela faz internamente. A partir de agora vamos us-lo assim:

67
console.log(ancestry.filter(function(person) {
return person.father == "Carel Haverbeke";
}));
// [{name: "Carolus Haverbeke", }]

Transformando com map


Digamos que temos um array de objetos que representam pessoas, produzido atravs do array de ancestrais
de alguma forma. Mas queremos um array de nomes o que mais fcil para ler.

O mtodo map transforma um array aplicando uma funo para todos os elementos e constri um novo array a
partir dos valores retornados. O novo array ter o mesmo tamanho do array enviado, mas seu contedo
mapeado para um novo formato atravs da funo.

function map(array, transform) {


var mapped = [];
for (var i = 0; i < array.length; i++)
mapped.push(transform(array[i]));
return mapped;
}

var overNinety = ancestry.filter(function(person) {


return person.died - person.born > 90;
});
console.log(map(overNinety, function(person) {
return person.name;
}));
// ["Clara Aernoudts", "Emile Haverbeke",
// "Maria Haverbeke"]

Curiosamente, as pessoas que viveram pelo menos 90 anos de idade so as mesmas trs que vimos antes, as
pessoas que eram jovens em 1920, passam a ser a gerao mais recente no meu conjunto de dados. Eu acho
que a medicina j percorreu um longo caminho.

Assim como forEach e filter , map tambm um mtodo padro de arrays .

Resumindo com reduce


Outro padro na computao em arrays calcular todos elementos e transform-los em apenas um. No nosso
exemplo atual, a soma do nosso intervalo de nmeros, um exemplo disso. Outro exemplo seria encontrar uma
pessoa com um ano de vida no conjunto de dados.

Uma operao de ordem superior que representa este padro chamada de reduce (diminui o tamanho do
array ). Voc pode pensar nisso como dobrar a matriz, um elemento por vez. Quando somado os nmeros, voc
inicia com o nmero zero e, para cada elemento, combina-o com a soma atual adicionando os dois.

Os parmetros para a funo reduce so, alm do array , uma funo para combinao e um valor inicial. Esta
funo menos simples do que o filter e map por isso observe com muita ateno.

68
function reduce(array, combine, start) {
var current = start;
for (var i = 0; i < array.length; i++)
current = combine(current, array[i]);
return current;
}

console.log(reduce([1, 2, 3, 4], function(a, b) {


return a + b;
}, 0));
// 10

O array padro do mtodo reduce que corresponde a esta funo tem uma maior comodidade. Se o seu array

contm apenas um elemento, voc no precisa enviar um valor inicial. O mtodo ir pegar o primeiro elemento do
array como valor inicial, comeando a reduo a partir do segundo.

Para usar o reduce e encontrar o meu mais antigo ancestral, podemos escrever algo parecido com isto:

console.log(ancestry.reduce(function(min, cur) {
if (cur.born < min.born) return cur;
else return min;
}));
// {name: "Pauwels van Haverbeke", born: 1535, }

Componibilidade
Considere como escreveramos o exemplo anterior (encontrar a pessoa mais velha) sem funes de ordem
superior. O cdigo no ficaria to ruim.

var min = ancestry[0];


for (var i = 1; i < ancestry.length; i++) {
var cur = ancestry[i];
if (cur.born < min.born)
min = cur;
}
console.log(min);
// {name: "Pauwels van Haverbeke", born: 1535, ...}

Existem mais variveis, e o programa est com duas linhas a mais, mesmo assim continuou bem fcil de
entender.

Funes de ordem superior so teis quando voc precisa compor funes. Como exemplo, vamos escrever um
cdigo que encontra a idade mdia para homens e mulheres no conjunto de dados.

function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
function age(p) { return p.died - p.born; }
function male(p) { return p.sex == "m"; }
function female(p) { return p.sex == "f"; }

console.log(average(ancestry.filter(male).map(age)));
// 61.67
console.log(average(ancestry.filter(female).map(age)));
// 54.56

( um pouco bobo termos que definir plus como uma funo, mas os operadores em JavaScript, diferentemente
das funes, no so valores, ento no podemos passar nenhum argumento.)

69
Ao invs de juntar toda a lgica em um loop gigante, ele est bem composto nos conceitos que interessamos
como - determinando sexo, calculando a idade e a mdia dos nmeros. Podemos aplic-las uma de cada vez
para obtermos o resultado que estamos procurando.

Escrever um cdigo limpo fabuloso. Infelizmente essa clareza tem um custo.

O Custo
No mundo dos cdigos elegantes e lindos arco-ris, vive um monstro que estraga os prazeres chamado de
ineficincia.

Um programa que processa um array mais elegante expresso em uma sequncia separada onde cada passo
pode fazer algo com o array e produzir um novo array . Mas a construo de todos esses arrays intermedirios
um pouco custoso.

Passar uma funo para forEach e deixar que o mtodo cuide da iterao para os ns conveniente e fcil de ler.
Mas chamadas de funes em JavaScript so custosas comparadas com os simples blocos de repetio.

E assim existem vrias tcnicas que ajudam a melhorar a clareza de um programa. Abstraes adiciona uma
camada a mais entre as coisas cruas que o computador faz e o conceito que estamos trabalhando, sendo assim
a mquina realiza mais trabalho. Esta no uma lei de ferro, existem linguagens de programao que tem um
suporte melhor para a construo de abstrao sem adio de ineficincias, at mesmo em JavaScript, um
programador experiente pode encontrar maneiras de escrever um cdigo abstrato e rpido. Mas um problema
que muito comum.

Existem vrias tcnicas que ajudam a esclarecer o cdigo. Elas adicionam camadas entre as coisas cruas que o
computador est fazendo com os conceitos que estamos trabalhando e faz com que a mquina trabalhe mais
rpido. Isso no uma lei inescapvel -- existem linguagens de programao que possuem um melhor suporte
para construir aplicaes sem adicionar ineficincias e, ainda em JavaScript, um programador experiente pode
encontrar jeitos de escrever cdigos relativamente abstratos que ainda so rpidos, porm um problema
frequente.

Felizmente muitos computadores so extremamente rpidos. Se voc estiver processando uma modesta coleo
de dados ou fazendo alguma coisa que tem de acontecer apenas em uma escala de tempo humano (digamos,
toda vez que o usurio clica em um boto), ento no importa se voc escreveu aquela soluo maravilhosa que
leva meio milissegundo ou uma super soluo otimizada que leva um dcimo de um milissegundo.

til saber quanto tempo mais ou menos leva um trecho de cdigo para executar. Se vocs tm um loop dentro
de um loop (diretamente, ou atravs de um loop externo chamando uma funo que executa um loop interno),
o cdigo dentro do loop interno acaba rodando NxM vezes, onde N o nmero de vezes que o loop de fora se
repete e M o nmero de vezes que o loop interno se repete dentro de cada interao do loop externo. Se esse
loop interno tiver outro loop que realize P voltas, seu bloco rodar M x N x P vezes e assim por diante. Isto
pode adicionar muitas operaes. Quando um programa lento o problema muitas das vezes pode estar
atribuda a apenas uma pequena parte do cdigo que fica dentro de um loop interno.

O pai do pai do pai do pai


Meu av, Philibert Haverbeke est includo nos dados do arquivo. Comeando com ele, eu posso traar minha
linhagem para descobrir qual a pessoa mais velha no conjunto de dados, Pauwels van Haverbeke, meu
ancestral direto. E se ele for, gostaria de saber o quanto de DNA, teoricamente, que partilho com ele.

Para ser capaz de fazer uma busca pelo nome de um pai para um objeto real que representa uma pessoa,
primeiramente precisamos construirmos um objeto que associa os nomes com as pessoas.

70
var byName = {};
ancestry.forEach(function(person) {
byName[person.name] = person;
});

console.log(byName["Philibert Haverbeke"]);
// {name: "Philibert Haverbeke", }

Agora o problema no totalmente simples como conseguir as propriedades do pai e ir contando quantos levam
at chegar a Pauwels. Existem vrios casos na rvore genealgica onde pessoas se casaram com seus primos
de segundo grau (pequenos vilarejos tm essas coisas). Isso faz com que as ramificaes da famlia se
reencontrem em certos lugares, o que significa que eu compartilho mais de 1/2G do meu genes com essa
pessoa, onde usaremos G como nmero de geraes entre Pauwels e mim. Esta frmula vem a partir da ideia
que de cada gerao divide o conjunto de genes em dois.

Uma maneira razovel de pensar sobre este problema olhar para ele como sendo um anlogo de reduce , que
condensa um array em um nico valor, por valores que combinam vrias vezes da esquerda para a direita. Neste
caso ns tambm queremos condensar a nossa estrutura de dados para um nico valor mas de uma forma que
segue as linhas da famlia. O formato dos dados a de uma rvore genealgica em vez de uma lista plana.

A maneira que ns queremos reduzir esta forma calculando um valor para uma determinada pessoa,
combinando com os valores de seus ancestrais. Isso pode ser feito de uma forma recursiva: se estamos
interessados em uma pessoa A, temos que calcular os valores para os pais de As, que por sua vez obriga-nos a
calcular o valor para os avs de As e assim por diante. A princpio isso iria exigir-nos a olhar para um nmero
infinito de pessoas, j que o nosso conjunto de dados finito, temos que parar em algum lugar. Vamos permitir
um valor padro para nossa funo de reduo, que ser utilizado para pessoas que no esto em nossos
dados. No nosso caso, esse valor simplesmente zero, pressupondo de que as pessoas que no esto na lista
no compartilham do mesmo DNA do ancestral que estamos olhando.

Dado uma pessoa, a funo combina os valores a partir de dois pais de uma determinada pessoa, e o valor
padro, reduceAncestors condensa o valor a partir de uma rvore genealgica.

function reduceAncestors(person, f, defaultValue) {


function valueFor(person) {
if (person == null)
return defaultValue;
else
return f(person, valueFor(byName[person.mother]),
valueFor(byName[person.father]));
}
return valueFor(person);
}

A funo interna ( valueFor ) lida com apenas uma pessoa. Atravs da magica da recursividade ela pode chamar a
si mesma para lidar com o pai e com a me. Os resultados junto com o objeto da pessoa em si, so passados
para f na qual devolve o valor real para essa pessoa.

Podemos ento usar isso para calcular a quantidade de DNA que meu av compartilhou com Pauwels van
Haverbeke e depois dividir por quatro.

71
function sharedDNA(person, fromMother, fromFather) {
if (person.name == "Pauwels van Haverbeke")
return 1;
else
return (fromMother + fromFather) / 2;
}
var ph = byName["Philibert Haverbeke"];
console.log(reduceAncestors(ph, sharedDNA, 0) / 4);
// 0.00049

A pessoa com o nome Pauwels van Haverbeke obviamente compartilhada 100 por cento de seu DNA com
Pauwels van Haverbeke (no existem pessoas que compartilham o mesmo nome no conjunto de dados), ento a
funo retorna 1 para ele. Todas as outras pessoas compartilham a mdia do montante que os seus pais
compartilham.

Assim estatisticamente falando, eu compartilho cerca de 0,05 por cento do DNA de uma pessoa do sculo 16.
Deve-se notar que este s uma aproximao estatstica e, no uma quantidade exata. Este um nmero
bastante pequeno mas dada a quantidade de material gentico que carregamos (cerca de 3 bilhes de pares de
bases), provavelmente ainda h algum aspecto na mquina biolgica que se originou de Pauwels.

Ns tambm podemos calcular esse nmero sem depender de reduceAncestors . Mas separando a abordagem
geral (condensao de uma rvore genealgica) a partir do caso especfico (computao do DNA compartilhado)
podemos melhorar a clareza do cdigo permitindo reutilizar a parte abstrata do programa para outros casos. Por
exemplo, o cdigo a seguir encontra a porcentagem de antepassados conhecidos para uma determinada pessoa
que viveu mais de 70 anos (por linhagem, para que as pessoas possam ser contadas vrias vezes).

function countAncestors(person, test) {


function combine(current, fromMother, fromFather) {
var thisOneCounts = current != person && test(current);
return fromMother + fromFather + (thisOneCounts ? 1 : 0);
}
return reduceAncestors(person, combine, 0);
}
function longLivingPercentage(person) {
var all = countAncestors(person, function(person) {
return true;
});
var longLiving = countAncestors(person, function(person) {
return (person.died - person.born) >= 70;
});
return longLiving / all;
}
console.log(longLivingPercentage(byName["Emile Haverbeke"]));
// 0.129

Tais nmeros no so levados muito a srio, uma vez que o nosso conjunto de dados contm uma coleo
bastante arbitrria de pessoas. Mas o cdigo ilustra o fato de que reduceAncestors d-nos uma pea til para
trabalhar com o vocabulrio da estrutura de dados de uma rvore genealgica.

Binding
O mtodo bind , est presente em todas as funes, ele cria uma nova funo que chama a funo original mas
com alguns argumentos j fixados.

O cdigo a seguir mostra um exemplo de uso do bind . Ele define uma funo isInSet , que nos diz se uma
pessoa est em um determinado conjunto de string . Ao chamar filter a fim de selecionar os objetos pessoa
cujos nomes esto em um conjunto especfico. Ns podemos escrever uma expresso de funo que faz a

72
chamada para isInSet enviando nosso conjunto como primeiro argumento ou parcialmente aplicar a funo
isInSet .

var theSet = ["Carel Haverbeke", "Maria van Brussel",


"Donald Duck"];
function isInSet(set, person) {
return set.indexOf(person.name) > -1;
}

console.log(ancestry.filter(function(person) {
return isInSet(theSet, person);
}));
// [{name: "Maria van Brussel", },
// {name: "Carel Haverbeke", }]
console.log(ancestry.filter(isInSet.bind(null, theSet)));
// same result

A chamada usando bind retorna uma funo que chama isInSet com theset sendo o primeiro argumento,
seguido por todos os demais argumentos indicados pela funo vinculada.

O primeiro argumento onde o exemplo passa null , utilizado para as chamadas de mtodo, semelhante ao
primeiro argumento do apply . Eu vou descrever isso com mais detalhes no prximo captulo.

Sumrio
A possibilidade de passar funes como argumento para outras funes no apenas um artifcio mas sim um
aspecto muito til em JavaScript. Ela nos permite escrever clculos com intervalos como funes, e chamar estas
funes para preencher estes intervalos, fornecendo os valores para funo que descrevem os clculos que
faltam.

Arrays fornece uma grande quantidade de funes de ordem superior - forEach faz algo com cada elemento de
um array , filter para construir um novo array com valores filtrados, map para construir um novo array onde
cada elemento colocado atravs de uma funo e reduce para combinar todos os elementos de um array em
um valor nico.

Funes tm o mtodo apply que pode ser usado para chamar um array especificando seus argumentos. Elas
tambm possuem um mtodo bind que usado para criar uma verso parcial da funo que foi aplicada.

Exerccios
Juntando
Use o mtodo reduce juntamente com o mtodo concat para juntar um array de arrays em um nico array

que tem todos os elementos de entrada do array .

var arrays = [[1, 2, 3], [4, 5], [6]];


// Your code here.
// [1, 2, 3, 4, 5, 6]

Diferena de idade entre me e filho


Usando os dados de exemplo definidos neste captulo, calcule a diferena de idade mdia entre mes e filhos (a
idade da me quando a criana nasce). Voc pode usar a funo average definida anteriormente neste captulo.

73
Note que nem todas as mes mencionadas no conjunto de dados esto presentes no array . O objeto byName

facilita a busca por um objeto pessoa atravs do nome. Esse mtodo pode ser til aqui.

function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}

var byName = {};


ancestry.forEach(function(person) {
byName[person.name] = person;
});

// Your code here.

// 31.2

Dica:

Como nem todos os elementos do array de ascendncia produz dados teis (no podemos calcular a diferena
de idade, a menos que saibamos a data de nascimento da me) teremos que aplicar de alguma maneira um filtro
antes de chamarmos o average . Voc pode fazer isso no primeiro passo, basta definir uma funo hasKnownMother

para a primeira filtragem. Alternativamente voc pode comear a chamar o map e na funo de mapeamento
retornar a diferena de idade ou nulo se me for desconhecida. Em seguida voc pode chamar o filter para
remover os elementos nulos antes de passar o array para o mtodo average .

O Histrico da expectativa de vida


Quando olhamos para todas as pessoas no nosso conjunto de dados que viveram mais de 90 anos, s a ltima
gerao dos dados que retornou. Vamos observar mais de perto esse fenmeno.

Calcule o resultado da idade mdia das pessoas no conjunto de dados definidos por sculo. Uma pessoa
atribuda a um sculo pegando o ano da sua morte, dividindo por 100 e arredondando para cima com
Math.ceil(person.died / 100) .

function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}

// Your code here.

// 16: 43.5
// 17: 51.2
// 18: 52.8
// 19: 54.8
// 20: 84.7
// 21: 94

Para ganhar um ponto extra escreva uma funo groupBy que abstrai a operao de agrupamento. Ele deve
aceitar um array como argumento e uma funo que calcula cada elemento do grupo de array e retorna um
objeto que mapeia os nomes dos grupos de arrays e os membros do grupo.

Dica:

A essncia desse exemplo encontra-se no agrupamento dos elementos em um conjunto por alguns aspectos - a
divises do array de ancestrais em pequenos arrays com os ancestrais de cada sculo.

74
Durante o processo de agrupamento, mantenha um objeto que associa os nomes dos sculos (nmeros) com
os arrays de objetos de pessoas ou idades. J que no sabemos quais agrupamentos iremos encontrar,
teremos que cri-los em tempo real. Depois de calcular o sculo para cada pessoa, vamos testar para saber se o
sculo j existe. Se no existir adicione um array para ele. Em seguida adicione a pessoa (ou idade) no array

de acordo com o sculo apropriado.

Finalmente um loop for/in pode ser usado para escrever a mdia de idades para cada sculo individualmente.

Todos e alguns
Arrays tambm vm com os mtodos padres every (todos) e some (alguns). Ambos recebem uma funo
predicada que quando chamada com um array como argumento retorna true ou false . Assim como o
operador && retorna apenas true como valor quando as expresses de ambos os lados forem true ; every

retorna true quando a funo predicada retorna true para cada elemento do array . Sendo assim, a funo
predicada some retorna quando algum elemento do array tiver um valor como true . Ele no processa mais
elementos do que o necessrio - por exemplo, se o predicado some encontrar o que precisa no primeiro elemento
do array ele no percorrer os outros elementos.

Escreva duas funes, que se comporte como esses mtodos, every e some , exceto se eles receberem um
array como seu primeiro argumento ao invs de um mtodo.

// Your code here.

console.log(every([NaN, NaN, NaN], isNaN));


// true
console.log(every([NaN, NaN, 4], isNaN));
// false
console.log(some([NaN, 3, 4], isNaN));
// true
console.log(some([2, 3, 4], isNaN));
// false

Dica:

As funes podem seguir um padro semelhante definio de forEach que foi mostrado no incio do captulo, a
nica exceo que eles devem retornar imediatamente (com o valor direita) quando a funo predicada retorna
true ou false . No se esquea de colocar uma outra instruo de return aps o loop ; para que a funo
retorne um valor correto quando atingir o final do array .

75
A vida secreta dos objetos
"O problema com as linguagens orientadas a objeto que elas tm tudo implcito no ambiente que elas
carregam consigo. Voc queria banana, mas o que voc teve foi um gorila segurando a banana e toda a
floresta." `Joe Armstrong, entrevistado em Coders at Work``

Quando um programador diz "objeto", isso um termo carregado. Na minha profisso, objetos so a maneira de
viver, o sujeito das guerras santas, e um jargo apaixonante que ainda no perdeu o seu poder.

Para um estrangeiro, isso provavelmente um pouco confuso. Vamos comear com uma rpida histria dos
objetos como constrtutores da programao.

Histria
Essa histria, como a maioria das histrias de programao, comea com um problema de complexidade. A
teoria de que a complexidade pode ser administrada separando-a em pequenos compartimentos isolados um
do outro. Esses compartimentos acabaram ganhando o nome de ob jetos.

Um objeto um escudo duro que esconde a complexidade grudenta dentro dele e nos apresenta pequenos
conectores (como mtodos) que apresentam uma interface para utilizarmos o objeto. A ideia que a interface seja
relativamente simples e toda as coisas complexas que vo dentro do objeto possam ser ignoradas enquanto se
trabalha com ele.

76
Como exemplo, voc pode imaginar um objeto que disponibiliza uma interface para uma determinada rea na sua
tela. Ele disponibiliza uma maneira de desenhar formas ou textos nessa rea, mas esconde todos os detalhes de
como essas formas so convertidos para os pixels que compem a tela. Voc teria um conjunto de
mtodos- desenharCirculo , por exemplo- e essas sero as nicas coisas que voc precisa saber pra usar tal
objeto.

Essas ideias foram trabalhadas inicialmente por volta dos anos 70 e 80 e, nos anos 90, foram trazidas a tona por
uma enorme onda hype-a revoluo da programao orientada a objetos. De repente, existia uma enorme tribo de
pessoas declarando que objetos eram a maneira correta de programar-e que qualquer coisa que no envolvesse
objetos era uma loucura ultrapassada.

Esse tipo de fanatismo produz um monte de bobagem impraticvel, e desde ento uma espcie de contra-
revoluo vem acontecendo. Em alguns crculos de desenvolvedores, os objetos tm uma pssima reputao
hoje em dia.

Eu prefiro olhar para esse problema de um ngulo prtico, e no ideolgico. Existem vrios conceitos teis, dentre
eles um dos mais importantes o encapsulamento (distinguir complexidade interna e interface externa), que a
cultura orientada a objetos tem popularizado. Vamos ver esses conceitos, pois eles valem a pena.

Esse captulo descreve uma pegada mais excntrica do JavaScript com foco nos objetos e na forma como eles se
relacionam com algumas tcnicas clssicas de orientao a objetos.

77
Mtodos
Mtodos so propriedades simples que comportam valores de funes. Isso um mtodo simples:

var coelho = {};


coelho.diz = function(linha) {
console.log("O coelho diz '" + linha + "'");
};

coelho.diz("Estou vivo.");
// O coelho diz 'Estou vivo.'

Normalmente um mtodo precisa fazer alguma coisa com o objeto pelo qual ele foi chamado. Quando uma
funo chamada como um mtodo-visualizada como uma propriedade e imediatamente chamada, como em
objeto.metodo() -a varivel especial this no seu contedo vai apontar para o objeto pelo qual foi chamada.

function speak(line) {
console.log("The " + this.type + " rabbit says '" +
line + "'");
}
var whiteRabbit = {type: "white", speak: speak};
var fatRabbit = {type: "fat", speak: speak};

whiteRabbit.speak("Oh my ears and whiskers, " +


"how late it's getting!");
// The white rabbit says 'Oh my ears and whiskers, how
// late it's getting!'
fatRabbit.speak("I could sure use a carrot right now.");
// The fat rabbit says 'I could sure use a carrot
// right now.'

O cdigo acima usa a palavra chava this para dar a sada do tipo de coelho que est falando. Lembrando que
ambos os mtodos apply e bind podem usar o primeiro argumento para simular chamadas de mtodos. Esse
primeiro argumento, na verdade, usado para passar um valor ao this .

Existe um mtodo parecido ao apply chamado call . Ele tambm chama a funo na qual ele um mtodo e
aceita argumentos normalmente, ao invs de um array. Assim como apply e bind ,o call pode ser passado
com um valor especfico no this .

speak.apply(fatRabbit, ["Burp!"]);
// The fat rabbit says 'Burp!'
speak.call({type: "old"}, "Oh my.");
// The old rabbit says 'Oh my.'

Prototypes
Observe com ateno.

var empty = {};


console.log(empty.toString);
// function toString(){}
console.log(empty.toString());
// [object Object]

Eu acabei de sacar uma propriedade de um objeto vazio. Mgica!

78
S que no. Eu venho ocultando algumas informaes sobre como os objetos funcionam no JavaScript. Alm de
sua lista de propriedades, quase todos os objetos tambm possuem um prottipo, ou prototype. Um prototype
outro objeto que usado como fonte de fallb ack para as propriedades. Quando um objeto recebe uma chamada
em uma propriedade que ele no possui, seu prototype designado para aquela propriedade ser buscado, e
ento o prototype daquele prototype e assim por diante.

Ento quem o prototype de um objeto vazio? o ancestral de todos os prototypes, a entidade por trs de quase
todos os objetos, Object.prototype .

console.log(Object.getPrototypeOf({}) ==
Object.prototype);
// true
console.log(Object.getPrototypeOf(Object.prototype));
// null

A funo Object.getPrototypeOf retorna o prototype de um objeto como o esperado.

As relaes dos objetos JavaScript formam uma estrutura em forma de rvore, e na raiz dessa estrutura se
encontra o Object.prototype . Ele fornece alguns mtodos que esto presentes em todos os objetos, como o
toString, que converte um objeto para uma representao em string.

Muitos objetos no possuem o Object.prototype diretamente em seu prototype. Ao invs disso eles tm outro
objeto que fornece suas propriedades padro. Funes derivam do Function.prototype , e arrays derivam do
Array.prototype .

console.log(Object.getPrototypeOf(isNaN) ==
Function.prototype);
// true
console.log(Object.getPrototypeOf([]) ==
Array.prototype);
// true

Por diversas vezes, o prototype de um objeto tambm ter um prototype, dessa forma ele ainda fornecer
indiretamente mtodos como toString .

A funo Object.getPrototypeOf obviamente retornaro o prototype de um objeto. Voc pode usar Object.create para
criar um objeto com um prototype especfico.

var protoCoelho = {
fala: function(linha) {
console.log("O coelho " + this.tipo + " fala '" +
linha + "'");
}
};
var coelhoAssassino = Object.create(protoCoelho);
coelhoAssassino.tipo = "assassino";
coelhoAssassino.fala("SKREEEE!");
// O coelho assassino fala 'SKREEEE!'

Construtores
A maneira mais conveniente de criar objetos que herdam algum prototype compartilhado usar um construtor. No
JavaScript, chamar uma funo precedida pela palavra-chave new vai fazer com que ela seja tratada como um
construtor. O construtor ter sua varivel this atrelada a um objeto novo, e a menos que ele explicite o retorno do
valor de outro objeto, esse novo objeto ser retornado a partir da chamada.

Um objeto criado com new chamado de instncia do construtor.

79
Aqui est um construtor simples para coelhos. uma conveo iniciar o nome de um construtor com letra
maiscula para que seja fcil destingu-los das outras funes.

function Coelho(tipo) {
this.tipo = tipo;
}

var coelhoAssassino = new Coelho("assassino");


var coelhoPreto = new Coelho("preto");
console.log(coelhoPreto.tipo);
// preto

Construtores (todas as funes, na verdade) pegam automaticamente uma propriedade chamada prototype , que
por padro possui um objeto vazio que deriva do Object.prototype . Toda instncia criada com esse construtor ter
esse objeto assim como seu prototype. Ento, para adicionar um mtodo fala aos coelhos criados com o
construtor Coelho , ns podemos simplesmente fazer isso:

Coelho.prototype.fala = function(linha) {
console.log("O coelho " + this.tipo + " fala '" +
linha + "'");
};
coelhoPreto.fala("Doom...");
// O coelho preto fala 'Doom...'

importante notar a dinstino entre a maneira que um prototype associado a um construtor (por sua
propriedade prototype) e a maneira que objetos tm um prototype (que pode ser obtido com
Object.getPrototypeOf ). O prototype propriamente dito de um construtor Function.prototype , visto que os
construtores so funes. Sua propriedade prototype ser o prototype de instncias criadas atravs dele mas
no ser seu prprio prototype.

Definindo uma tabela


Eu vou trabalhar sobre um exemplo ou pouco mais envolvido na tentativa de dar a voc uma melhor ideia de
polimorfismo, assim como de programao orientada a objetos em geral. O projeto este: ns vamos escrever
um programa que, dado um array de arrays de clulas de uma tabela, cria uma string que contm uma tabela
bem formatada - significando que colunas so retas e linhas esto alinhadas. Algo dessa forma:

name height country


------------ ------ -------------
Kilimanjaro 5895 Tanzania
Everest 8848 Nepal
Mount Fuji 3776 Japan
Mont Blanc 4808 Italy/France
Vaalserberg 323 Netherlands
Denali 6168 United States
Popocatepetl 5465 Mexico

A forma que nosso sistema de construir tabelas vai funcionar que a funo construtora vai perguntar para cada
clula quanto de altura e largura ela vai querer ter e ento usar essa informao para determinar a largura das
colunas e a altura das linhas. A funo construtora vai ento pedir para as clulas se desenharem no tamanho
correto e montar o resultado dentro de uma string.

O programa de layout vai comunicar com os objetos clulas atravs de uma interface bem definida. Dessa forma,
os tipos de clulas que o programa suporta no est definida antecipadamente. Ns podemos adicionar novas
clulas de estilo depois por exemplo, clulas sublinhadas para cabealho e se eles suportarem nossa
interface, isso vai simplesmente, funcionar, sem exigir alteraes no layout do programa.

80
Esta a interface:

minHeight() retorna um nmero indicando a altura mnima que esta clula necessita (em linhas).
minWidth() retorna um nmero indicando a largura mnima da clula (em caracteres).
draw(width, height) retorna um array de tamanho height , que contm uma srie de strings que contm width

caracteres de tamanho. Isso representa o contedo da clula.

Irei fazer forte uso de mtodos de ordem superior de array neste exemplo uma vez que isso apropriado para
essa abordagem.

A primeira parte do programa calcula matrizes de largura e altura mnima para uma grade de clulas.

function rowHeights(rows) {
return rows.map(function(row) {
return row.reduce(function(max, cell) {
return Math.max(max, cell.minHeight());
}, 0);
});
}

function colWidths(rows) {
return rows[0].map(function(_, i) {
return rows.reduce(function(max, row) {
return Math.max(max, row[i].minWidth());
}, 0);
});
}

Usar um nome de varivel que se inicia com um underscore ( _ ) ou consistir inteiramente em um simples
underscore uma forma de indicar (para leitores humanos) que este argumento no ser usado.

A funo rowHeights no deve ser to difcil de ser seguida. Ela usa reduce para computar a altura mxima de um
array de clulas e envolve isso em um map para fazer isso em todas as linhas no array rows .

As coisas so um pouco mais difceis na funo colWidths porque o array externo um array de linhas, no de
colunas. No mencionei at agora que map (assim como forEach , filter e mtodos de array similares) passam
um segundo argumento funo fornecida: o ndice do elemento atual. Mapeando os elementos da primeira linha
e somente usando o segundo argumento da funo de mapeamento, colWidths constri um array com um
elemento para cada ndice da coluna. A chamada reduce roda sobre o array rows exterior para cada ndice e
pega a largura da clula mais larga nesse ndice.

Aqui est o cdigo para desenhar a tabela:

81
function drawTable(rows) {
var heights = rowHeights(rows);
var widths = colWidths(rows);

function drawLine(blocks, lineNo) {


return blocks.map(function(block) {
return block[lineNo];
}).join(" ");
}

function drawRow(row, rowNum) {


var blocks = row.map(function(cell, colNum) {
return cell.draw(widths[colNum], heights[rowNum]);
});
return blocks[0].map(function(_, lineNo) {
return drawLine(blocks, lineNo);
}).join("\n");
}

return rows.map(drawRow).join("\n");
}

A funo drawTable usa a funo interna drawRow para desenhar todas as linhas e ento as junta com caracteres
newline (nova linha).

A funo drawRow primeiro converte os objetos clula na linha em b locos, que so arrays representando o
contedo das clulas, divididos por linha. Uma clula simples contendo apenas o nmero 3776 deve ser
representada por um array com um nico elemento como ["3776"] , onde uma clula sublinhada deve conter
duas linhas e ser representada pelo array ["name", "----"] .

Os blocos para uma linha, que devem todos ter a mesma largura, devem aparecer prximos um ao outro na sada
final. A segunda chamada a map em drawRow constri essa sada linha por linha mapeando sobre as linhas do
bloco mais esquerda e, para cada uma delas, coletando uma linha que expande a tabela para sua largura
mxima. Essas linhas so ento juntadas com caracteres newline para fornecer a linha completa e ser o valor
retornado de drawRow .

A funo drawLine extrai linhas que devem aparecer prximas uma a outra a partir de um array de blocos e as
junta com um caracter espao para criar o espao de um caracter entre as colunas da tabela.

Agora vamos escrever o construtor para clulas que contenham texto, implementando a interface para as clulas
da tabela. O construtor divide a linha em um array de linhas usando o mtodo string split , que corta uma string
em cada ocorrncia do seu argumento e retorna um array com as partes. O mtodo minWidth encontra a linha
com maior largura nesse array.

82
function repeat(string, times) {
var result = "";
for (var i = 0; i < times; i++)
result += string;
return result;
}

function TextCell(text) {
this.text = text.split("\n");
}
TextCell.prototype.minWidth = function() {
return this.text.reduce(function(width, line) {
return Math.max(width, line.length);
}, 0);
};
TextCell.prototype.minHeight = function() {
return this.text.length;
};
TextCell.prototype.draw = function(width, height) {
var result = [];
for (var i = 0; i < height; i++) {
var line = this.text[i] || "";
result.push(line + repeat(" ", width - line.length));
}
return result;
};

O cdigo usa uma funo auxiliar chamada repeat , que constri uma linha na qual o valor um argumento
string repetido times nmero de vezes. O mtodo draw usa isso e adiciona "preenchimento" para as linhas
assim todas vo ter o tamanho requerido.

Vamos testar tudo que construmos e criar um tabuleiro de damas 5 x 5.

var rows = [];


for (var i = 0; i < 5; i++) {
var row = [];
for (var j = 0; j < 5; j++) {
if ((j + i) % 2 == 0)
row.push(new TextCell("##"));
else
row.push(new TextCell(" "));
}
rows.push(row);
}
console.log(drawTable(rows));
// ## ## ##
// ## ##
// ## ## ##
// ## ##
// ## ## ##

Funciona! Mas apesar de todas as clulas terem o mesmo tamanho, o cdigo do layout da tabela no faz nada
realmente interessante.

Os dados fonte para a tabela de montanhas que estamos tentando construir esto disponveis na varivel
MOUNTAINS na sandb ox e tambm neste arquivo em nosso website.

Vamos querer destacar a linha do topo, que contm o nome das colunas, sublinhando as clulas com uma srie
de caracteres trao. Sem problemas ns simplesmente escrevemos um tipo de clula que manipula o
sublinhado.

83
function UnderlinedCell(inner) {
this.inner = inner;
};
UnderlinedCell.prototype.minWidth = function() {
return this.inner.minWidth();
};
UnderlinedCell.prototype.minHeight = function() {
return this.inner.minHeight() + 1;
};
UnderlinedCell.prototype.draw = function(width, height) {
return this.inner.draw(width, height - 1)
.concat([repeat("-", width)]);
};

Uma clula sublinhada contm outra clula. Ela reporta seu tamanho mnimo sendo o mesmo que da sua clula
interna (chamando os mtodos minWidth e minHeight desta clula) mas adicionando um largura para contar o
espao usado pelo sublinhado.

Desenhar essa clula bem simples - ns pegamos o contedo da clula interna e concatenamos uma simples
linha preenchida com traos a ela.

Tendo um mecanismo de sublinhamento, ns podemos agora escrever uma funo que constri uma grade de
clulas a partir do conjunto de dados.

function dataTable(data) {
var keys = Object.keys(data[0]);
var headers = keys.map(function(name) {
return new UnderlinedCell(new TextCell(name));
});
var body = data.map(function(row) {
return keys.map(function(name) {
return new TextCell(String(row[name]));
});
});
return [headers].concat(body);
}

console.log(drawTable(dataTable(MOUNTAINS)));
// name height country
// ------------ ------ -------------
// Kilimanjaro 5895 Tanzania
// etcetera

A funo padro Object.keys retorna um array com nomes de propriedades de um objeto. A linha do topo da tabela
deve conter clulas sublinhadas que do os nomes das colunas. Abaixo disso, os valores de todos os objetos no
conjunto de dados aparecem como clulas normais - ns os extramos mapeando sobre o array keys de modo
que tenhamos certeza que a ordem das clulas a mesma em todas as linhas.

A tabela resultante se assemelha ao exemplo mostrado anteriormente, exceto que ela no alinha os nmeros
direita na coluna height . Vamos chegar nessa parte em um instante.

Getters and Setters


Quando especificamos uma interface, possvel incluir propriedades que no so mtodos. Poderamos ter
definido minHeight e minWidth para simplesmente conter nmeros. Mas isso teria exigido de ns comput-los no
construtor, o que adicionaria cdigo que no estritamente relevante para construo do objeto. Isso pode causar
problemas se, por exemplo, a clula interior de uma clula exterior mudou, onde nesse ponto o tamanho da clula
sublinhada tambm deve mudar.

84
Isso tem levado algumas pessoas a adotarem um princpio de nunca inclurem propriedades nonmethod em
interfaces. Ao invs de acessarem diretamente o valor da propriedade, eles usam mtodos getSomething e
setSomething para ler e escrever propriedades. Esta abordagem tem a parte negativa de que voc ir acabar
escrevendo - e lendo - muitos mtodos adicionais.

Felizmente, o JavaScript fornece uma tcnica que fornece o melhor de ambos os mundos. Ns podemos
especificar propriedades que, do lado de fora, parecem propriedades normais mas secretamente tem mtodos
associados a elas.

var pile = {
elements: ["eggshell", "orange peel", "worm"],
get height() {
return this.elements.length;
},
set height(value) {
console.log("Ignoring attempt to set height to", value);
}
};

console.log(pile.height);
// 3
pile.height = 100;
// Ignoring attempt to set height to 100

Em um objeto literal, a notao get ou set para propriedades permite que voc especifique uma funo a ser
executada quando a propriedade for lida ou escrita. Voc pode tambm adicionar tal propriedade em um objeto
existente, por exemplo um prottipo, usando a funo Object.defineProperty (que ns previamente usamos para
criar propriedades no enumerveis).

Object.defineProperty(TextCell.prototype, "heightProp", {
get: function() { return this.text.length; }
});

var cell = new TextCell("no\nway");


console.log(cell.heightProp);
// 2
cell.heightProp = 100;
console.log(cell.heightProp);
// 2

Voc pode usar a propriedade similar set , no objeto passado defineProperty , para especificar um mtodo
setter. Quando um getter definido mas um setter no, escrever nessa propriedade algo simplesmente
ignorado.

Herana
Ns no estamos prontos com nosso exerccio de layout de tabela. Ela deve ajudar na leitura de nmeros
alinhados direita em colunas. Ns devemos criar outra tipo de clula como TextCell , mas ao invs de dar
espao nas linhas do lado direito, vamos espa-las do lado esquerdo que ir alinhas direita.

Podemos simplesmente construir um novo construtor com todos os trs mtodos em seu prottipo. Mas
prottipos podem ter seus prprios prottipos, e isso nos permite fazer algo inteligente.

85
function RTextCell(text) {
TextCell.call(this, text);
}
RTextCell.prototype = Object.create(TextCell.prototype);
RTextCell.prototype.draw = function(width, height) {
var result = [];
for (var i = 0; i < height; i++) {
var line = this.text[i] || "";
result.push(repeat(" ", width - line.length) + line);
}
return result;
};

Ns reusamos o construtor e os mtodos minHeight e minWidth de TextCell . Um RTextCell agora basicamente


equivalente a TextCell , exceto que seu mtodo draw contm uma funo diferente.

Este padro chamado herana. Isso nos permite construir tipos de dados levemente diferentes a partir de tipos
de dados existentes com relativamente pouco esforo. Tipicamente, o novo construtor vai chamar o antigo
construtor (usando o mtodo call para ser capaz de dar a ele o novo objeto assim como o seu valor this ). Uma
vez que esse construtor tenha sido chamado, ns podemos assumir que todos os campos que o tipo do antigo
objeto supostamente contm foram adicionados. Ns organizamos para que o prottipo do construtor derive do
antigo prottipo, ento as instncias deste tipo tambm vo acesso s propriedades deste prottipo. Finalmente,
ns podemos sobrescrever algumas das propriedades adicionando-as ao nosso novo prottipo.

Agora, se ns ajustarmos sutilmente a funo dataTable para usar RTextCell para as clulas cujo valor um
nmero, vamos obter a tabela que estvamos buscando.

function dataTable(data) {
var keys = Object.keys(data[0]);
var headers = keys.map(function(name) {
return new UnderlinedCell(new TextCell(name));
});
var body = data.map(function(row) {
return keys.map(function(name) {
var value = row[name];
// This was changed:
if (typeof value == "number")
return new RTextCell(String(value));
else
return new TextCell(String(value));
});
});
return [headers].concat(body);
}

console.log(drawTable(dataTable(MOUNTAINS)));
// beautifully aligned table

Herana uma parte fundamental da orientao a objetos tradicional, ao lado de encapsulamento e


polimorfismo. Mas enquanto os dois ltimos sejam agora geralmente considerados como ideias brilhantes,
herana algo controverso.

A principal razo para isso que este tpico geralmente confundido com polimorfismo, vendido como uma
ferramenta mais poderosa do que realmente , e subsequentemente usado em excesso de diversas horrveis
formas. Onde encapsulamento e polimorfismo podem ser usados para separar pedaos de cdigo de cada um,
reduzindo o emaranhamento de todo o programa, herana fundamentalmente vincula os tipos, criando mais
emaranhados.

86
Voc pode ter polimorfismo sem herana, como ns vimos. Eu no vou dizer para voc evitar herana
completamente. Eu a uso regularmente em meus programas. Mas voc deve v-la como um leve truque
desonesto que vai ajud-lo a definir novos tipos com menos cdigo, no como um grande princpio de
organizao de cdigo. Uma forma mais apropriada de extender tipos atravs da composio, como
UnderlinedCell constri em outra clula simplesmente armazenando-a em uma propriedade e um mtodo
posterior a chama nos seus prprios mtodos.

O operador instanceof

Ocasionalmente til saber se um objeto foi derivado de um construtor em especfico. Para isso, o JavaScript
fornece um operador binrio chamado instaceof .

console.log(new RTextCell("A") instanceof RTextCell);


// true
console.log(new RTextCell("A") instanceof TextCell);
// true
console.log(new TextCell("A") instanceof RTextCell);
// false
console.log([1] instanceof Array);
// true

O operador vai olhar atravs dos tipos herdados. Um RTextCell uma instncia de TextCell porque
RTextCell.prototype deriva de TextCell.prototype . O operador pode ser aplicado a construtores padro como
Array . Praticamente todos os objetos so uma instncia de Object .

Resumo
Ento objetos so mais complicados do que inicialmente eu os retratei. Eles tem prottipos, que so outros
objetos, e vo agir como se tivessem propriedades que eles no tem caso seu prottipo tenha essa propriedade.
Objetos simples tem Object.prototype como seus prottipos.

Construtores, que so funes cujos nomes usualmente iniciam com uma letra maiscula, podem ser usador
com o operador new para criar objetos. O prottipo do novo objeto ser o objeto encontrado na propriedade
prototype da funo construtora. Voc pode fazer bom uso disso adicionando propriedades que todos os valores
de um tipo compartilham em seus prottipos. O operador instanceof pode, dado um objeto e um construtor, dizer
se o objeto uma instncia deste construtor.

Algo til a se fazer com objetos especificar uma interface para eles e dizer para todos quer iro supostamente
conversar com seu objeto a fazer isso somente por essa interface. O resto dos detalhes que constroem seu
objeto esto agora encapsulados, escondidos atrs da interface.

Uma vez que voc esteja conversando em termos de interfaces, quem diz que apenas um tipo de objeto pode
implementar essa interface? Ter diferentes objetos expondo a mesma interface chamado de polimorfismo. Isso
muito til.

Quando implementando vrios tipos que diferem apenas em alguns detalhes, pode ser til simplesmente criar o
prottipo do seu novo tipo derivando do prottipo do seu antigo tipo e ter seu novo construtor chamando o antigo.
Isso lhe d um tipo similar de objeto ao antigo mas que permite que voc adicione ou sobrescreva propriedades
quando necessrio.

Exerccios

87
Um tipo de vetor
Escreva um construtor Vector que represente um vetor em duas dimenses do espao. Ele recebe os
parmetros x e y (nmeros), que deve salvar em propriedades de mesmo nome.

D ao prottipo de Vector dois mtodos, plus e minus , que pegam outro vetor como parmetro e retornam um
novo vetor que tem a soma ou diferena dos valores x e y dos dois vetores (o vetor que est em this eo
passado no parmetro).

Adicione uma propriedade getter length ao prottipo que calcula o tamanho do vetor - isto , a distncia do ponto
( x, y ) at a origem (0,0).

// Your code here.

console.log(new Vector(1, 2).plus(new Vector(2, 3)));


// Vector{x: 3, y: 5}
console.log(new Vector(1, 2).minus(new Vector(2, 3)));
// Vector{x: -1, y: -1}
console.log(new Vector(3, 4).length);
// 5

Dicas

Sua soluo pode seguir o padro do construtor Rabbit deste captulo de forma bem semelhante.

Adicionar uma propriedade getter ao construtor pode ser feita com a funo Object.defineProperty . Para calcular a
distncia do ponto (0, 0) at (x, y) voc pode usar o teorema de Pitgoras, que diz que o quadrado da
distncia que estamos procurando igual ao quadrado da coordenada x mais o quadrado da coordenada y.
Assim, (x2 + y2) o nmero que voc quer, e Math.sqrt o caminho para voc calcular a raiz quadrada no
JavaScript.

Outra clula
Implemente uma clula do tipo StretchCell(inner, width, height) que se adeque a interface da clula da tabela
descrita anteriormente neste captulo. Ela deve envolver outra clula (como UnderlinedCell faz) e assegurar que a
clula resultante tem pelo menos a largura ( width ) e altura ( height ) especificada, mesmo se a clula interior for
naturalmente menor.

// Your code here.

var sc = new StretchCell(new TextCell("abc"), 1, 2);


console.log(sc.minWidth());
// 3
console.log(sc.minHeight());
// 2
console.log(sc.draw(3, 2));
// ["abc", " "]

Dicas

Voc vai ter que armazenar os 3 argumentos construtores na instncia do objeto. Os mtodos minWidth e
minHeight devem chamar atravs dos mtodos correspondentes na clula interna ( inner ), mas assegure-se que
nenhum nmero menor que o tamanho dado retornado (possivelmente usando Math.max ).

No se esquea de adicionar um mtodo draw que simplesmente encaminha a chamada para a clula interior.

Interface sequencial

88
Projete uma interface que abstraia interaes sobre uma coleo de valores. Um objeto que fornece esta interface
representa uma sequncia, e a interface deve de alguma forma tornar possvel para o cdigo que usa este objeto
iterar sobre uma sequncia, olhando para o valor dos elementos de que ela composta e tendo alguma forma de
saber quando o fim da sequncia foi atingido.

Quando voc tiver especificado sua interface, tente escrever uma funo logFive que pega um objeto sequencial
e chama console.log para seus primeiros 5 elementos - ou menos, se a sequncia tiver menos do que cinco
elementos.

Ento implemente um tipo de objeto ArraySeq que envolve um array e permite interao sobre o array usando a
interface que voc desenvolveu. Implemente outro tipo de objeto RangeSeq que itera sobre um intervalo de inteiros
(recebendo os argumentos from e to em seu construtor).

// Your code here.

logFive(new ArraySeq([1, 2]));


// 1
// 2
logFive(new RangeSeq(100, 1000));
// 100
// 101
// 102
// 103
// 104

Dicas

Uma forma de resolver isso fornecendo objetos sequenciais state, que significa que suas propriedades so
alteradas no seu processo de uso. Voc pode armazenar um contador que indica quo longe o objeto
sequenciais avanaram.

Sua interface vai precisar expor ao menos uma forma de pegar o prximo elemento e encontrar se a iterao j
chegou no fim da sequencia. tentador fazer isso em um mtodo, next , que retorna null ou undefined quando
a sequncia chegar ao fim. Mas agora voc tem um problema quando a sequncia realmente tiver null . Ento
um mtodo separado (ou uma propriedade getter) para descobrir se o fim foi alcanado provavelmente
prefervel.

Outra soluo evitar mudar o estado do objeto. Voc pode expor um mtodo para pegar o elemento atual (sem o
auxlio de nenhum contador) e outro para pegar uma nova sequncia que representa os elementos restantes
depois do atual (ou um valor especial se o fim da sequncia tiver sido atingido). Isso bem elegante - um valor
sequencial vai "permanecer ele mesmo" mesmo depois de ter sido usado e pode ser compartilhado com outro
cdigo sem a preocupao sobre o que pode acontecer com ele. Isso , infelizmente, algo um pouco ineficiente
numa linguagem como JavaScript porque envolve criar vrios objetos durante a iterao.

89
Projeto - Vida eletrnica
[...] A questo da mquinas poder pensar [...] to relevante quanto a questo dos submarinos poderem
nadar.

Edsger Dijkstra, The Threats to Computing Science

Nos captulo "Projeto" irei apresentar uma nova teoria por um breve momento e trabalhar atravs de um programa
com voc. A teoria indispensvel quando estamos aprendendo a programar mas deve ser acompanhada da
leitura para entender os programas no triviais.

Nosso projeto neste captulo construir um ecossistema virtual, um mundo pequeno povoado com criaturas que
se movem e luta pela sobrevivncia.

Definio
Para tornar esta tarefa gerencivel, vamos simplificar radicalmente o conceito de um mundo. Ou seja, um mundo
ser uma grid bidimensional onde cada entidade ocupa um quadrado da grid . Em cada turno os bichos todos
tm a chance de tomar alguma ao.

Utilizaremos o tempo e o espao com um tamanho fixo como unidades. Os quadrados sero os espaos e as
voltas o tempo. claro que as aproximaes sero imprecisas. Mas nossa simulao pretende ser divertida para
que possamos livremente cortar as sobras.

Podemos definir um mundo com uma matriz de Strings que estabelece uma grid do mundo usando um
personagem por metro quadrado.

var plan = ["############################",


"# # # o ##",
"# #",
"# ##### #",
"## # # ## #",
"### ## # #",
"# ### # #",
"# #### #",
"# ## o #",
"# o # o ### #",
"# # #",
"############################"];

Os caracteres "#" representam as paredes e rochas, e os personagens "O" representam os bichos. Os


espaos como voc j deve ter pensado o espao vazio.

Um plano de matriz pode ser usada para criar um objeto de mundo. Tal objeto mantm o controle do tamanho e
do contedo do mundo. Ele tem um mtodo toString , que converte o mundo de volta para uma sequncia de
impresso (similar ao plano que foi baseado) para que possamos ver o que est acontecendo l dentro. O objeto
do mundo tambm tem um mtodo por sua vez que permite que todos os bichos podem darem uma volta e
atualizar o mundo para terem suas aes.

Representando o espao

90
A grid modela o mundo com uma largura e altura fixa. Os quadrados so identificados pelas suas coordenadas
x e y. Ns usamos um tipo simples, Vector(como visto nos exerccios do captulo anterior) para representar esses
pares de coordenadas.

function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};

Em seguida, temos um tipo de objeto que o modelo da grid .A grid uma parte do mundo, e tornamos ela um
objeto separado(que ser uma propriedade de um objeto do mundo) para manter o objeto bem simples. O
mundo deve preocupar-se com as coisas relacionadas com o mundo e a grid deve preocupar-se com as coisas
relacionadas da grid .

Para armazenar um valor a grid temos vrias opes. Podemos usar um array de arrays tendo duas
propriedades de acessos para chegar a um quadrado especfico como este:

var grid = [["top left", "top middle", "top right"],


["bottom left", "bottom middle", "bottom right"]];
console.log(grid[1][2]);
// bottom right

Ou podemos usar uma nica matriz com largura x altura e decidir que o elemento (x, y) encontrado na posio
x + (y * largura) na matriz.

var grid = ["top left", "top middle", "top right",


"bottom left", "bottom middle", "bottom right"];
console.log(grid[2 + (1 * 3)]);
// bottom right

Uma vez que o acesso real a essa matriz esta envolto em mtodos de tipo do objeto da grid , no importa o
cdigo que adotamos para abordagem. Eu escolhi a segunda representao pois torna muito mais fcil para criar
a matriz. Ao chamar o construtor de Array com um nico argumento, ele cria uma nova matriz vazia com o
comprimento que foi passado de parmetro.

Esse cdigo define o objeto grid , com alguns mtodos bsicos:

function Grid(width, height) {


this.space = new Array(width * height);
this.width = width;
this.height = height;
}
Grid.prototype.isInside = function(vector) {
return vector.x >= 0 && vector.x < this.width &&
vector.y >= 0 && vector.y < this.height;
};
Grid.prototype.get = function(vector) {
return this.space[vector.x + this.width * vector.y];
};
Grid.prototype.set = function(vector, value) {
this.space[vector.x + this.width * vector.y] = value;
};

Aqui esta um exemplo trivial do teste:

91
var grid = new Grid(5, 5);
console.log(grid.get(new Vector(1, 1)));
// undefined
grid.set(new Vector(1, 1), "X");
console.log(grid.get(new Vector(1, 1)));
// X

A interface da programao dos bichos


Antes de comearmos nosso construtor global, devemos especificar quais os objetos bichos que estaro vivendo
em nosso mundo. Eu mencionei que o mundo vai especificar os bichos e as aes que eles tero. Isso funciona
da seguinte forma: cada bicho um objeto e tem um mtodo de ao que quando chamado retorna uma ao.
Uma ao um objeto com uma propriedade de tipo, que d nome a ao que o bicho ter, por exemplo "move" .
A ao pode tambm conter informao extra de alguma direo que o bicho possa se mover.

Bichos so terrivelmente mopes e podem ver apenas os quadrados em torno da grid . Mas essa viso limitada
pode ser til ao decidir que ao tomar. Quando o mtodo act chamado o objeto de verificao permite que o
bicho inspecione seus arredores. Ns vamos nomear oito quadrados vizinhos para ser as coordenadas: "n"

para norte, "ne" para nordeste e assim por diante. Aqui est o objeto, vamos utilizar para mapear os nomes das
direes para coordenar os offsets :

var directions = {
"n": new Vector( 0, -1),
"ne": new Vector( 1, -1),
"e": new Vector( 1, 0),
"se": new Vector( 1, 1),
"s": new Vector( 0, 1),
"sw": new Vector(-1, 1),
"w": new Vector(-1, 0),
"nw": new Vector(-1, -1)
};

O objeto de exibio tem um mtodo que observa em qual direo o bicho esta indo e retorna um personagem
por exemplo, um "#" quando h uma parede na direo ou um "" (espao) quando no h nada. O objeto tambm
fornece os mtodos find e findAll . Ambos tomam um mapa de carter como um argumento. O primeiro retorna
a direo em que o personagem pode ser encontrado ao lado do bicho ou retorna nulo se no existir nenhum
sentido. O segundo retorna um array contendo todas as direes possveis para o personagem. Por exemplo,
uma criatura sentada esquerda(oeste) de um muro vai ter ["ne", "e", "se"] ao chamar findAll passando o
caractere "#" como argumento.

Aqui um bicho simples e estpido que segue apenas seu nariz at que ela atinja um obstculo e depois salta
para fora em uma direo aleatria:

92
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}

var directionNames = "n ne e se s sw w nw".split(" ");

function BouncingCritter() {
this.direction = randomElement(directionNames);
};

BouncingCritter.prototype.act = function(view) {
if (view.look(this.direction) != " ")
this.direction = view.find(" ") || "s";
return {type: "move", direction: this.direction};
};

A funo auxiliar randomElement simplesmente pega um elemento aleatrio de uma matriz usando Math.random

para obter um ndice aleatrio. Vamos usar isso de novo mais tarde porque a aleatoriedade pode ser til em
simulaes.

Para escolher uma direo aleatria o construtor de BouncingCritter chama randomElement em uma matriz de
nomes de direo. Ns tambm poderamos termos usado Object.keys para obter essa matriz de direes que
definimos anteriormente, mas no garantido a ordem em que as propriedades sero listadas. Na maioria das
situaes os motores modernos de JavaScript retornam as propriedades na ordem em que foram definidos, mas
eles no so obrigados a terem tais comportamentos.

O || "s" no mtodo de ao serve para impedir que this.direction obtenha um valor nulo para o bicho que
est preso em um espao vazio em torno dele(por exemplo, quando um canto esta lotado de outros bichos).

O objeto do mundo
Agora podemos comear a fazer o objeto mundo. O construtor tem um plano(a matriz de Strings que representa a
grid do mundo como descrito anteriormente) e uma legenda como argumentos. A legenda um objeto que nos
diz o que cada personagem no mapa significa. Ela contm um construtor para cada personagem, exceto para o
caractere de espao que sempre se refere como null sendo este o valor que vamos usar para representar o
espao vazio.

function elementFromChar(legend, ch) {


if (ch == " ")
return null;
var element = new legend[ch]();
element.originChar = ch;
return element;
}

function World(map, legend) {


var grid = new Grid(map[0].length, map.length);
this.grid = grid;
this.legend = legend;

map.forEach(function(line, y) {
for (var x = 0; x < line.length; x++)
grid.set(new Vector(x, y),
elementFromChar(legend, line[x]));
});
}

93
Em elementFromChar primeiro criamos uma instncia do tipo correto, observando o construtor do carter aplicando
um novo para ele. Em seguida adicionado uma propriedade originChar tornando mais fcil de descobrir em
qual personagem o elemento foi originalmente criado.

Precisamos da propriedade originChar quando implementarmos o mtodo toString no mundo. Este mtodo
constri uma sequncia de mapeamento de estado atual do mundo atravs da realizao de um ciclo de duas
dimenses sobre os quadrados na grid .

function charFromElement(element) {
if (element == null)
return " ";
else
return element.originChar;
}

World.prototype.toString = function() {
var output = "";
for (var y = 0; y < this.grid.height; y++) {
for (var x = 0; x < this.grid.width; x++) {
var element = this.grid.get(new Vector(x, y));
output += charFromElement(element);
}
output += "\n";
}
return output;
};

A parede um objeto simples que usado apenas para ocupar espao e no tem nenhum mtodo de ao.

function Wall() {}

Vamos criar um objeto Mundo com base no plano passado no incio do captulo, em seguida iremos chamar
toString sobre ele.

var world = new World(plan, {"#": Wall,


"o": BouncingCritter});
console.log(world.toString());
// ############################
// # # # o ##
// # #
// # ##### #
// ## # # ## #
// ### ## # #
// # ### # #
// # #### #
// # ## o #
// # o # o ### #
// # # #
// ############################

This e seu escopo


O construtor do mundo contm uma chamada de forEach . Uma coisa interessante que podemos notar que
dentro da funo do forEach no estamos mais no escopo da funo do construtor. Cada chamada de funo
recebe o seu prprio escopo de modo que o escopo presente na funo interna no se refere ao objeto externo
recm-construdo. Na verdade quando a funo no chamada como um mtodo isso refere ao objeto global.

Isso significa que no podemos escrever this.grid para acessar nada de fora de dentro do loop . Podemos criar
uma varivel local na funo exterior da grid , onde a funo interna tera acesso a ela.

94
Isso um erro de design no JavaScript. Felizmente a prxima verso da linguagem ir fornecer uma soluo para
este problema. Enquanto isso existem solues alternativas. Um padro comum dizer var auto = this uma
varivel local que guarda sua referencia.

Outra soluo usar o mtodo de bind que nos permite oferecer uma chamada explcita para o objeto.

var test = {
prop: 10,
addPropTo: function(array) {
return array.map(function(elt) {
return this.prop + elt;
}.bind(this));
}
};
console.log(test.addPropTo([5]));
// [15]

A funo mapeia um array e retorna o valor do prop que esta dentro do objeto test somado ao resultado do
valor de um elemento do array .

A maioria dos mtodos que mapeiam matrizes tais como forEach e map , tm um segundo argumento opcional
que pode ser usado para fornecer um escopo para dentro do bloco de interao (segundo argumento do
interador). Assim, voc poder expressar o exemplo anterior de uma forma um pouco mais simples.

var test = {
prop: 10,
addPropTo: function(array) {
return array.map(function(elt) {
return this.prop + elt;
}, this); // no bind
}
};
console.log(test.addPropTo([5]));
// [15]

Isso funciona apenas para as funes de interaes que suportam tal parmetro de contexto. Quando algum
mtodo no suporta receber um contexto voc ir precisar usar as outras abordagens.

Em nossas prprias funes de interaes podemos apoiar tal parmetro de contexto enviando um segundo
argumento no bloco. Por exemplo, aqui no mtodo forEach para o nosso tipo de grid , chamaremos uma
determinada funo para cada elemento da grid que no seja nulo ou indefinido:

Grid.prototype.forEach = function(f, context) {


for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var value = this.space[x + y * this.width];
if (value != null)
f.call(context, value, new Vector(x, y));
}
}
};

Dando vida
O prximo passo escrever um mtodo para o objeto mundo que d aos bichos a chance de movimento. Ele vai
passar por cima da grid usando o mtodo forEach que acabamos de definir a procura de objetos com um
mtodo act . Quando ele encontra um ele chama o mtodo para obter uma ao e realiza a ao quando ela for
vlida. Por enquanto apenas as aes "move" sero compreendidas.

95
Existe um problema com esta abordagem. Voc consegue identificar? Se deixarmos as criaturas se mover
livremente, eles podem se mover para um quadrado que no existe, e ns vamos permitir que eles se mova
novamente quando estiver dentro do quadrado vazio. Assim temos que ficar mantendo uma variedade de criaturas
que j sumiram ao invs de apenas ignorarmos.

World.prototype.turn = function() {
var acted = [];
this.grid.forEach(function(critter, vector) {
if (critter.act && acted.indexOf(critter) == -1) {
acted.push(critter);
this.letAct(critter, vector);
}
}, this);
};

Ns usamos o contexto como segundo parmetro no mtodo forEach para ser a referncia da grid para
conseguirmos acessar corretamente as funes internas. O mtodo letAct contm a lgica real que permite que
os bichos se movam.

World.prototype.letAct = function(critter, vector) {


var action = critter.act(new View(this, vector));
if (action && action.type == "move") {
var dest = this.checkDestination(action, vector);
if (dest && this.grid.get(dest) == null) {
this.grid.set(vector, null);
this.grid.set(dest, critter);
}
}
};

World.prototype.checkDestination = function(action, vector) {


if (directions.hasOwnProperty(action.direction)) {
var dest = vector.plus(directions[action.direction]);
if (this.grid.isInside(dest))
return dest;
}
};

Em primeiro lugar, ns simplesmente pedimos para o bicho se mover, passando um objeto de exibio que tem
informaes sobre o mundo e a posio atual do bicho naquele mundo(vamos definir a tela em algum momento).
O mtodo retorna alguma tipo de ao.

Se o tipo de ao no um "move" ele ser ignorado. Se "move" ele ter uma propriedade de direo que se
refere a um sentido vlido caso o quadrado na direo referida estiver vazio( null ). Iremos definir o bicho para o
quadrado de destino e ao se mover novamente vamos definir null para este quadrado visitado e armazenar o
bicho na prximo quadrado .

Perceba que letAct no ignora coisas que no fazem sentidos como, se a propriedade direo vlida ou se a
propriedade do tipo faz sentido. Este tipo de programao defensiva faz sentido em algumas situaes. A principal
razo para faz-la validar alguma fonte proveniente que no seja de controle(como alguma entrada de valores
definidas por usurios), mas tambm pode ser til para isolar outros subsistemas. Neste caso a inteno que
os bichos podem serem programados de forma no cuidadosa, eles no tm de verificar se suas aes de
destinado faz sentido. Eles podem simplesmente solicitar uma ao e o mundo que vai permitir a ao.

Estes dois mtodos no fazem a parte da interface externa de um objeto do mundo. Eles so um detalhe interno.
Algumas lnguas fornece maneiras de declarar explicitamente certos mtodos e propriedades privadas e sinalizar
um erro quando voc tenta us-los de fora do objeto. JavaScript no faz isso ento voc vai ter que confiar em
alguma outra forma de comunicao para descrever o que faz ou no parte da interface de um objeto. s vezes ele

96
pode ajudar a usar um esquema de nomes para distinguir entre as propriedades externas e internas, por
exemplo, prefixando todas as propriedades internas com um caractere sublinhado( _ ). Isso far com que os
usos acidentais de propriedades que no fazem parte da interface de um objeto fique mais fcil de detectar.

A nica parte que falta para a tela se parece com isso:

function View(world, vector) {


this.world = world;
this.vector = vector;
}
View.prototype.look = function(dir) {
var target = this.vector.plus(directions[dir]);
if (this.world.grid.isInside(target))
return charFromElement(this.world.grid.get(target));
else
return "#";
};
View.prototype.findAll = function(ch) {
var found = [];
for (var dir in directions)
if (this.look(dir) == ch)
found.push(dir);
return found;
};
View.prototype.find = function(ch) {
var found = this.findAll(ch);
if (found.length == 0) return null;
return randomElement(found);
};

O mtodo observa e descobre se as coordenadas que estamos visitando est dentro da grid e se o
personagem correspondente ao elemento. Para coordenadas fora da grid podemos simplesmente fingir que h
uma paredes no modo que podemos definir um mundo que no murado mas os bichos no podero caminhar
fora das bordas.

O movimento
Ns instanciamos o objeto mundo antes. Agora que ns adicionamos todos os mtodos necessrios, devemos
fazer os movimentos dos elementos no mundo.

for (var i = 0; i < 5; i++) {


world.turn();
console.log(world.toString());
}
// five turns of moving critters

Imprimir vrias cpias do mundo uma forma bastante desagradvel para movimentar um mundo. por isso que
o sandbox oferece uma funo animateWorld que executa uma animao, movendo o mundo com trs voltas por
segundo at que voc aperte o boto de stop .

animateWorld(world);
// life!

A implementao do animateWorld parece algo misterioso agora, mas depois que voc ler os captulos deste livro
que discutem a integrao JavaScript em navegadores web, ele no sera to mgico.

Mais formas de vida


97
O destaque dramtico do nosso mundo quando duas criaturas saltam para fora. Voc consegue pensar em
outra forma interessante de comportamento?

O bicho que se move ao longo das paredes. Conceitualmente o bicho mantm a sua mo esquerda(pata,
tentculo ou o que for) para a parede e segue junto a ela. Este jeito acaba sendo no muito trivial de implementar.

Precisamos ser capazes de calcular as direes com a bssola. As direes so modelados por um conjunto de
String , precisamos definir nossa prpria operao( dirPlus ) para calcular as direes relativas. Ento
dirPlus("n", 1) significa 45 graus no sentido horrio para norte quando retornar "ne". Da mesma forma
dirPlus("s", -2) significa 90 graus para a esquerda ao sul retornando leste.

function dirPlus(dir, n) {
var index = directionNames.indexOf(dir);
return directionNames[(index + n + 8) % 8];
}

function WallFollower() {
this.dir = "s";
}

WallFollower.prototype.act = function(view) {
var start = this.dir;
if (view.look(dirPlus(this.dir, -3)) != " ")
start = this.dir = dirPlus(this.dir, -2);
while (view.look(this.dir) != " ") {
this.dir = dirPlus(this.dir, 1);
if (this.dir == start) break;
}
return {type: "move", direction: this.dir};
};

O mtodo act s "varre" os arredores do bicho a partir do seu lado esquerdo no sentido horrio at encontrar
um quadrado vazio. Em seguida ele se move na direo do quadrado vazia.

O que complica que um bicho pode acabar no meio de um espao vazio, quer como a sua posio de partida ou
como um resultado de caminhar em torno de um outro bicho. Se aplicarmos a abordagem que acabei de
descrever no espao vazio o bicho vai apenas virar esquerda a cada passo correndo em crculos.

Portanto, h uma verificao extra(instruo if ) no inicio da digitalizao para a esquerda para analisar se o
bicho acaba de passar algum tipo de obstculo, no caso, se o espao atrs e esquerda do bicho no estiver
vazio. Caso contrrio, o bicho comea a digitalizar diretamente frente de modo que ele vai andar em linha reta
ate um espao vazio.

E finalmente h um teste comparando this.dir para comear aps cada passagem do lao para se certificar de
que o circuito no vai correr para sempre quando o bicho est no muro ou quando o mundo esta lotados de outros
bichos no podendo achar quadrados vazios.

Este pequeno mundo demonstra as criaturas na parede:

animateWorld(new World(
["############",
"# # #",
"# ~ ~ #",
"# ## #",
"# ## o####",
"# #",
"############"],
{"#": Wall,
"~": WallFollower,
"o": BouncingCritter}
));

98
Uma simulao mais realista
Para tornar a vida em nosso mundo mais interessante vamos adicionar os conceitos de alimentao e
reproduo. Cada coisa viva no mundo ganha uma nova propriedade, a energia, a qual reduzida por realizar
aes e aumenta comendo alguma coisas. Quando o bicho tem energia suficiente ele pode se reproduzir,
gerando um novo bicho da mesma espcie. Para manter as coisas simples; os bichos em nosso mundo se
reproduzem assexuadamente ou seja por si so.

Se bichos s se movimentar e comer uns aos outros o mundo em breve ira se sucumbir na lei da entropia
crescente, ficando sem energia e tornando um deserto sem vida. Para evitar que isso acontea(muito
rapidamente pelo menos) adicionaremos plantas para o mundo. As plantas no se movem. Eles s usam a
fotossntese para crescer(ou seja aumentar a sua energia) e se reproduzir.

Para fazer este trabalho vamos precisar de um mundo com um mtodo diferente de letAct . Poderamos
simplesmente substituir o prottipo global do mtodo mas eu gostei muito da nossa simulao e gostaria que os
novos bichos mantivesse o mesmo jeito do velho mundo.

Uma soluo usar herana. Criamos um novo construtor, LifelikeWorld , cujo seu prottipo baseado no
prottipo global, mas que substitui o mtodo letAct . O novo mtodo letAct delega o trabalho do que realmente
deve executar uma ao para vrias funes armazenados no objeto actionTypes .

function LifelikeWorld(map, legend) {


World.call(this, map, legend);
}
LifelikeWorld.prototype = Object.create(World.prototype);

var actionTypes = Object.create(null);

LifelikeWorld.prototype.letAct = function(critter, vector) {


var action = critter.act(new View(this, vector));
var handled = action &&
action.type in actionTypes &&
actionTypes[action.type].call(this, critter,
vector, action);
if (!handled) {
critter.energy -= 0.2;
if (critter.energy <= 0)
this.grid.set(vector, null);
}
};

O novo mtodo letAct verifica primeiro se uma ao foi devolvido, ento se a funo manipuladora para este tipo
de ao existir, o resultado deste manipulador sera true , indicando que ele tratou com sucesso a ao. Observe
que usamos uma chamada para dar o acesso ao manipulador do mundo, atravs de sua chamada. Observe que
para dar o acesso ao manipulador no mundo, precisamos fazer uma chamada.

Se a ao no funcionou por algum motivo a ao padro que a criatura simplesmente espere. Perde um quinto
de sua energia e se o seu nvel de energia chega a zero ou abaixo a criatura morre e removido da grid .

Manipuladores de aes
A ao mais simples que uma criatura pode executar "crescer" e sera usado pelas plantas. Quando um objeto
de ao como {type: "grow"} devolvido o seguinte mtodo de manipulao ser chamado:

99
actionTypes.grow = function(critter) {
critter.energy += 0.5;
return true;
};

Crescer com sucesso acrescenta meio ponto no nvel total da reserva de energia.

Analise o mtodo para se mover

actionTypes.move = function(critter, vector, action) {


var dest = this.checkDestination(action, vector);
if (dest == null ||
critter.energy <= 1 ||
this.grid.get(dest) != null)
return false;
critter.energy -= 1;
this.grid.set(vector, null);
this.grid.set(dest, critter);
return true;
};

Esta ao verifica primeiro se o destino vlido usando o mtodo checkDestination . Se no vlido, se o destino
no est vazio ou se o bicho no tem energia necessria; o movimento retorna false para indicar que nenhuma
ao foi feita. Caso contrrio ele move o bicho e subtrai sua energia.

Alm de movimentar, os bichos pode comer.

actionTypes.eat = function(critter, vector, action) {


var dest = this.checkDestination(action, vector);
var atDest = dest != null && this.grid.get(dest);
if (!atDest || atDest.energy == null)
return false;
critter.energy += atDest.energy;
this.grid.set(dest, null);
return true;
};

Comer um outro bicho tambm envolve o fornecimento de um quadrado de destino vlido. Desta vez o destino no
pode estar vazio e deve conter algo com energia, por exemplo um bicho(mas no pode ser a parede pois elas no
so comestveis). Sendo assim a energia a partir da comida transferido para o comedor e a vtima retirada da
grid .

E finalmente ns permitiremos que os nossos bichos se reproduzem.

actionTypes.reproduce = function(critter, vector, action) {


var baby = elementFromChar(this.legend,
critter.originChar);
var dest = this.checkDestination(action, vector);
if (dest == null ||
critter.energy <= 2 * baby.energy ||
this.grid.get(dest) != null)
return false;
critter.energy -= 2 * baby.energy;
this.grid.set(dest, baby);
return true;
};

Reproduzir custa duas vezes mais o nvel de energia de um bicho recm-nascido. Ento primeiro criamos o beb
(hipoteticamente) usando elementFromChar no prprio carter origem do bicho. Uma vez que temos um beb
podemos encontrar o seu nvel de energia e testar se o pai tem energia suficiente para traz-lo com sucesso no

100
mundo. Tambm exigido um destino vlido(vazio).

Se tudo estiver bem o beb colocado sobre a grid (que j no hipoteticamente), e a energia subtrada do
pai.

Populando o novo mundo


Agora temos um quadro para simular essas criaturas mais realistas. Poderamos colocar os bichos do velho
mundo para o novo, mas eles s iriam morrer, uma vez que no temos uma propriedade de energia. Ento vamos
fazer novos elementos. Primeiro vamos escrever uma planta que uma forma de vida bastante simples.

function Plant() {
this.energy = 3 + Math.random() * 4;
}
Plant.prototype.act = function(context) {
if (this.energy > 15) {
var space = context.find(" ");
if (space)
return {type: "reproduce", direction: space};
}
if (this.energy < 20)
return {type: "grow"};
};

As plantas comeam com um nvel de energia randomizados entre 3 e 7, isso para que eles no se reproduzam
todos no mesmo tempo. Quando a planta atinge nvel 15 de energia e no h espao vazio nas proximidades ela
no se reproduz. Se uma planta no pode se reproduzir ele simplesmente cresce at atingir o nvel 20 de energia.

Vamos agora definir um comedor de plantas.

function PlantEater() {
this.energy = 20;
}
PlantEater.prototype.act = function(context) {
var space = context.find(" ");
if (this.energy > 60 && space)
return {type: "reproduce", direction: space};
var plant = context.find("*");
if (plant)
return {type: "eat", direction: plant};
if (space)
return {type: "move", direction: space};
};

Vamos usar o caractere * para representar as plantas, quando algum bichos encontrar eles podem consumir
como alimento.

Dando a vida
Agora faremos elementos suficientes para experimentar o nosso novo mundo. Imagine o seguinte mapa sendo
um vale gramado com um rebanho de herbvoros em que h algumas pedras e vida vegetal exuberante em todos
os lugares.

101
var valley = new LifelikeWorld(
["############################",
"##### ######",
"## *** **##",
"# *##** ** O *##",
"# *** O ##** *#",
"# O ##*** #",
"# ##** #",
"# O #* #",
"#* #** O #",
"#*** ##** O **#",
"##**** ###*** *###",
"############################"],
{"#": Wall,
"O": PlantEater,
"*": Plant}
);

Vamos ver o que acontece ao executar.

animateWorld(valley);

Na maioria das vezes as plantas se multiplicam e expandem muito rapidamente, mas em seguida a abundncia
de alimento provoca uma exploso populacional dos herbvoros que saem para acabar com quase todas as
plantas resultando em uma fome em massa dos bichos. s vezes o ecossistema se recupera e comea outro
ciclo. Em outros momentos uma das espcies desaparece completamente. Se os herbvoros todo o espao ir
ser preenchido por plantas. Se as plantas os bichos restantes morrem de fome e o vale se torna uma terra
desolada. Olha que crueldade da natureza.

Exerccios
Estupidez artificial
Tendo os habitantes do nosso mundo se extinguindo aps alguns minutos uma espcie de deprimente. Para
lidar com isso poderamos tentar criar uma forma mais inteligente para o comedor de plantas.

H vrios problemas bvios com os nossos herbvoros. Primeiro eles so terrivelmente ganancioso enchendo-se
com todas as plantas que veem at que tenham dizimado a vida vegetal local. Em segundo lugar o seu
movimento randomizado(lembre-se que o mtodo view.find retorna uma direo aleatria quando mltiplas
direes combinar) faz com que eles fique em torno de si e acabe morrendo de fome se no no acontecer de
haver plantas nas proximidades. E finalmente eles se reproduzem muito rpido o que faz com que os ciclos entre
abundncia e fome se tornem bastante intensos.

Escrever um novo tipo de bicho que tenta abordar um ou mais desses pontos e substitu-lo para o tipo PlantEater

no velho no mundo do vale. Veja como que as tarifas esto. Ajuste um pouco mais se necessrio.

102
// Your code here
function SmartPlantEater() {}

animateWorld(new LifelikeWorld(
["############################",
"##### ######",
"## *** **##",
"# *##** ** O *##",
"# *** O ##** *#",
"# O ##*** #",
"# ##** #",
"# O #* #",
"#* #** O #",
"#*** ##** O **#",
"##**** ###*** *###",
"############################"],
{"#": Wall,
"O": SmartPlantEater,
"*": Plant}
));

Dicas:

O problema avidez podem ser atacados de diversas maneiras. Os bichos pode parar de comer quando atingem
um certo nvel de energia. Ou eles poderiam comer apenas a cada N voltas(mantendo um contador de voltas
desde a sua ltima refeio em uma propriedade no objeto da criatura). Ou para certificar-se de que as plantas
nunca seja extinta totalmente, os animais poderiam se recusar a comer uma planta a menos que tenha pelo
menos uma outra planta prxima(usando o mtodo findAll no view ). Uma combinao desta ou alguma
estratgia completamente diferente pode funcionar.

Podemos recuperar uma das estratgias do movimento dos bichos em nosso velho mundo para fazer os bichos
se moverem de forma mais eficaz. Tanto o comportamento de saltar e o de seguir pela parede mostrou uma gama
muito maior de movimento do que a de completamente aleatria.

Fazendo as criaturas mais lentas na reproduo pode ser trivial. Basta aumentar o nvel de energia mnima em
que se reproduzem. claro que ao fazer isto o ecossistema ficara mais estvel tornando-se tambm mais chato.
Se voc tem um rodada cheia de bichos imveis mastigando um mar de plantas e nunca se reproduzindo torna o
ecossistema muito estvel. E ningum quer ver isso.

Predators
Qualquer ecossistema srio tem uma cadeia alimentar mais do que um nico link. Faa outro bicho que sobrevive
comendo o bicho herbvoro. Voc vai notar que a estabilidade ainda mais difcil de conseguir, agora que h
ciclos em vrios nveis. Tente encontrar uma estratgia para tornar o ecossistema funcional sem problemas
durante pelo menos um curto perodo.

Uma coisa que vai ajudar fazer um mundo maior. Desta forma o crescimento da populao local ou de bustos
so menos propensos a acabar com uma espcie inteiramente e no h espao para a populao relativamente
grande de presa necessria para sustentar uma populao pequena de predadores.

103
// Your code here
function Tiger() {}

animateWorld(new LifelikeWorld(
["####################################################",
"# #### **** ###",
"# * @ ## ######## OO ##",
"# * ## O O **** *#",
"# ##* ########## *#",
"# ##*** * **** **#",
"#* ** # * *** ######### **#",
"#* ** # * # * **#",
"# ## # O # *** ######",
"#* @ # # * O # #",
"#* # ###### ** #",
"### **** *** ** #",
"# O @ O #",
"# * ## ## ## ## ### * #",
"# ** # * ##### O #",
"## ** O O # # *** *** ### ** #",
"### # ***** ****#",
"####################################################"],
{"#": Wall,
"@": Tiger,
"O": SmartPlantEater, // from previous exercise
"*": Plant}
));

Dicas:

Muitos dos mesmos truques que trabalhamos no exerccio anterior tambm se aplicam aqui. Fazer os predadores
grandes(lotes de energia) se reproduzirem lentamente recomendado. Isso vai torn-los menos vulnerveis aos
perodos de fome quando os herbvoros estiverem escassos.

Alm de manter-se vivo, manter seu estoque de alimentos vivo o objetivo principal de um predador. Encontrar
uma forma de fazer predadores caarem de forma mais agressiva quando h um grande nmero de herbvoros e
caarem mais lentamente quando a presa rara. Os comedores de plantas se movimentam, o simples truque de
comer um s quando os outros esto nas proximidades improvvel que funcione, raramente pode acontecer
que seu predador morra de fome. Mas voc poderia manter o controle de observaes nas voltas anteriores; de
alguma forma precisamos manter a estrutura de dados nos objetos dos predadores e teremos que basear o seu
comportamento no que ele tem visto recentemente.

104
Bugs e manipulao de erros
Debugar duas vezes mais difcil do que escrever cdigo. Portanto, se voc escrever cdigo da maneira
mais inteligente possvel, por definio, voc no inteligente o suficiente para debug-lo. Brian
Kernighan and P.J. Plauger, The Elements of Programming Style

Yuan-Ma havia escrito um pequeno programa onde utilizou muitas variveis globais e atalhos que faziam a
qualidade do seu cdigo inferior. Lendo o programa, um estudante perguntou: Voc nos avisou para no
usar essas tcnicas e mesmo assim as encontro no seu programa. Como pode isso?. O mestre
respondeu: No h necessidade de se buscar uma mangueira de gua quando a casa no est em
chamas. Master Yuan-Ma, The Book of Programming

Programas so pensamentos cristalizados. Algumas vezes, esses pensamentos so confusos e erros podem
ser inseridos quando convertemos pensamentos em cdigo, resultando em um programa com falhas.

Falhas em um programa so normalmente chamadas de b ugs, e podem ser causadas por erros inseridos pelo
programador ou problemas em outros sistemas que a aplicao interage. Alguns b ugs so imediatamente
aparentes, enquanto outros so sutis e podem ficar escondidos em um sistema por anos.

Muitas vezes os problemas aparecem quando um programa executa de uma forma que o programador no
considerou originalmente. As vezes, tais situaes so inevitveis. Quando o usurio insere um dado invlido,
isso faz com que a aplicao fique em uma situao difcil. Tais situaes devem ser antecipadas e tratadas de
alguma maneira.

Erros do programador
O nosso objetivo simples quando se trata de erros do programador. Devemos encontr-los e corrigi-los. Tais
erros podem variar entre erros simples que faz o computador reclamar assim que ele tenta executar o programa
ou erros sutis causado por uma compreenso errada da lgica do programa levando a resultados incorretos,
podendo ser constante ou em apenas algumas condies especficas. Esse ltimo tipo de erros pode levar
semanas para ter um diagnostico correto.

O nvel de ajuda que as linguagens oferece para encontrar os erros variam bastante. Isso no nenhuma
surpresa pois o JavaScript est no "quase no ajuda em nada" no final dessa escala. Algumas linguagens
exigem os tipos de todas as suas variveis e expresses antes mesmo de executar; isso da a possibilidade do
programa nos dizer imediatamente quando um tipo usado de forma incorreta. JavaScript considera os tipos
somente na execuo do programa e mesmo assim ele permite que voc faa algumas coisas visivelmente
absurdas sem dar nenhum tipo de aviso como por exemplo: x = true "macaco" * .

H algumas coisas que o JavaScript no se queixam. Mas escrever um programa que sintaticamente incorreto
faz com que ele nem execute e dispare um erro imediatamente. Existem outras coisas como, chamar algo que
no uma funo ou procurar uma propriedade em um valor indefinido, isso causa um erro a ser relatado
somente quando o programa entrar em execuo e encontrar essa ao que no tem sentido.

Mas muitas das vezes um clculo absurdo pode simplesmente produzir um NaN (no um nmero) ou um valor
indefinido. O programa ir continua alegremente convencido de que est fazendo algo correto. O erro vai se
manifestar somente mais tarde, depois que o valor falso passou por vrias funes. No que isso venha
desencadear um erro em tudo, mas isso pode silenciosamente causar uma srie de sadas erradas. Encontrar a
fonte de tais problemas so considerados difceis.

O processo de encontrar erros (bugs) nos programas chamado de depurao.

105
Modo estrito
JavaScript pode ser feito de uma forma mais rigorosa, permitindo que o modo seja estrito. Para obter esse modo
basta inserir uma string "use strict" na parte superior de um arquivo ou no corpo de uma funo. Veja o exemplo:

function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}

canYouSpotTheProblem();
// ReferenceError: counter is not defined

Normalmente, quando voc esquece de colocar var na frente de sua varivel como acontece no exemplo, o
JavaScript cria uma varivel global para utiliza-la, no entanto no modo estrito um erro relatado. Isto muito til.
Porm deve-se notar que isso no funciona quando a varivel em questo j existe como uma varivel global,
isso apenas para atribuio ou criao.

Outra mudana no modo estrito que esta ligao tem o valor undefined para funes que no so chamadas
como mtodos. Ao fazer tal chamada fora do modo estrito a referencia do objeto do escopo global. Ento se
voc acidentalmente chamar um mtodo ou um construtor incorretamente no modo estrito o JavaScript produzir
um erro assim que ele tentar ler algo com isso ao invs de seguir trabalhando normalmente com a criao e
leitura de variveis globais no objeto global.

Por exemplo, considere o seguinte cdigo que chama um construtor sem a nova palavra-chave, na qual seu
objeto no vai se referir a um objeto recm-construdo:

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


var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// Ferdinand

Assim, a falsa chamada para Person foi bem sucedida, mas retornou um valor indefinido e criou uma varivel
global. No modo estrito, o resultado diferente.

"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// TypeError: Cannot set property 'name' of undefined

Somos imediatamente informados de que algo est errado. Isso til.

Existe mais coisas no modo estrito. Ele no permite dar a uma funo vrios parmetros com o mesmo nome e
remove totalmente certas caractersticas problemticas da linguagem.

Em suma, colocando um "use strict" no topo do seu programa no ir causar frustraes mas vai ajudar a
detectar problemas.

Testando
A linguagem no vai nos ajudar muito a encontrar erros, ns vamos ter que encontr-los da maneira mais difcil:
executando o programa e analisando se o comportamento est correto.

106
Fazer sempre testes manualmente uma maneira insana de conduzir-se. Felizmente possvel muitas das
vezes escrever um segundo programa que automatiza o teste do seu programa atual.

Como por exemplo, vamos construir um objeto Vector :

function Vector(x, y) {
this.x = x;
this.y = y;
}

Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};

Vamos escrever um programa para verificar se a nossa implementao do objeto Vector funciona como o
esperado. Ento cada vez que mudarmos a implementao o programa de teste executado, de modo que
fiquemos razoavelmente confiantes de que ns no quebramos nada. Quando adicionarmos uma funcionalidade
extra (por exemplo, um novo mtodo) no objeto Vector , tambm devemos adicionar testes para o novo recurso.

function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);

if (p1.x !== 10) return "fail: x property";


if (p1.y !== 20) return "fail: y property";
if (p2.x !== -10) return "fail: negative x property";
if (p3.x !== 0) return "fail: x from plus";
if (p3.y !== 25) return "fail: y from plus";
return "everything ok";
}
console.log(testVector());
// everything ok

Escrevendo testes como este tende a parecer um pouco repetitivo e um cdigo estranho. Felizmente existem
opes de software que ajudam a construir e executar colees de testes (suites de teste), fornecendo uma
linguagem (na forma de funes e mtodos) adequada para expressar os testes e emitir informaes
informativas de quando um teste falhou. Isto chamados de estruturas de teste.

Depurao
Voc consegue perceber que h algo errado com o seu programa quando ele esta se comportando mal ou
produzindo erros; o prximo passo descobrir qual o problema.

s vezes bvio. A mensagem de erro vai apontar para a linha especfica; e se voc olhar para a descrio do erro
e para linha de cdigo muitas vezes voc ir entender o problema.

Mas nem sempre assim. s vezes a linha que desencadeou o problema simplesmente o primeiro lugar onde
um valor falso foi produzido e que em outros lugares foi usado de uma forma incorreta ou as vezes no h
nenhuma mensagem de erro, apenas um resultado invlido. Se voc tentou resolver os exerccios nos captulos
anteriores voc provavelmente j experimentou tais situaes.

O exemplo seguinte tenta converter um nmero inteiro para uma cadeia em qualquer base (decimal, binrio, e
assim por diante), para se livrar do ltimo dgito escolhemos o ltimo dgito repetidamente e em seguida
dividimos. Mas a sada produzida sugere que ele tem um bug.

107
function numberToString(n, base) {
var result = "", sign = "";
if (n < 0) {
sign = "-";
n = -n;
}
do {
result = String(n % base) + result;
n /= base;
} while (n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
// 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3

Mesmo se voc j viu o problema e fingiu por um momento que voc no viu. Sabemos que o nosso programa
no est funcionando corretamente e queremos descobrir o porqu.

Esta a hora onde voc deve resistir tentao de comear a fazer mudanas aleatrias no cdigo. Em vez disso
pense, analise o que est acontecendo e chegue a uma teoria de por que isso pode estar acontecendo. Ento
faa observaes adicionais para testar esta teoria ou se voc ainda no tem uma teoria, faa observaes
adicionais que podem ajud-lo.

Colocar algumas chamadas console.log estratgicas no programa uma boa maneira de obter informaes
adicionais sobre o que o programa est fazendo. Neste caso queremos tomar os n valores de 13, 1 at 0.
Vamos descrever o seu valor no incio do loop.

13
1.3
0.13
0.013

1.5e-323

Certo. Dividindo 13 por 10 no produz um nmero inteiro. Em vez de n / = base o que ns realmente queremos
n = Math.floor (n / base) de modo que o nmero est devidamente deslocando-se para a direita.

Uma alternativa para o uso do console.log usar os recursos de depurao do seu browser. Navegadores
modernos vm com a capacidade de definir um ponto de interrupo em uma linha especfica de seu cdigo. Isso
far com que a execuo do programa faz uma pausa a cada vez que a linha com o ponto de interrupo
atingido. Isso permite que voc inspecione os valores das variveis nesse ponto. Eu no vou entrar em detalhes
aqui pois depuradores diferem de navegador para navegador, mas vale a pena olhar as ferramentas de
desenvolvimento do seu navegador e pesquisar na web para obter mais informaes. Outra maneira de definir
um ponto de interrupo incluir uma declarao no depurador (que consiste em simplesmente em uma palavra-
chave) em seu programa. Se as ferramentas de desenvolvedor do seu navegador esto ativos, o programa far
uma pausa sempre que ele atingir esta declarao e voc ser capaz de inspecionar o seu estado.

Propagao de erros
Infelizmente nem todos os problemas podem ser evitados pelo programador. Se o seu programa se comunica
com o mundo externo de qualquer forma h uma chance da entrada de outros sistemas estarem invlidos ou a
comunicao estar quebrada ou inacessvel.

Programas simples ou programas que so executados somente sob a sua superviso pode se dar ao luxo de
simplesmente desistir quando esse problema ocorre. Voc vai olhar para o problema e tentar novamente.
Aplicaes "reais" por outro lado espera que nunca falhe. s vezes a maneira correta tirar a m entrada

108
rapidamente para que o programe continue funcionando. Em outros casos melhor informar ao usurio o que
deu de errado para depois desistir. Mas em qualquer situao o programa tem de fazer algo rapidamente em
resposta ao problema.

Digamos que voc tenha uma funo promptInteger que pede para o usurio um nmero inteiro e retorna-o. O que
ele deve retornar se a entradas do usurio for incorreta?

Uma opo faz-lo retornar um valor especial. Escolhas comuns so valores nulos e indefinido.

function promptNumber(question) {
var result = Number(prompt(question, ""));
if (isNaN(result)) return null;
else return result;
}

console.log(promptNumber("How many trees do you see?"));

Isto uma boa estratgia. Agora qualquer cdigo que chamar a funo promptNumber deve verificar se um nmero
real foi lido, e na falha deve de alguma forma recuperar preenchendo um valor padro ou retornando um valor
especial para o seu chamador indicando que ele no conseguiu fazer o que foi solicitado.

Em muitas situaes, principalmente quando os erros so comuns e o chamador deve explicitamente t-las em
conta, retornaremos um valor especial, uma forma perfeita para indicar um erro. Mas essa maneira no entanto
tem suas desvantagens. Em primeiro lugar, como a funo pode retornar todos os tipos possveis de valores?
Para tal funo difcil encontrar um valor especial que pode ser distinguido a partir de um resultado vlido.

O segundo problema com o retorno de valores especiais, isso pode levar a um cdigo muito confuso. Se um
pedao de cdigo chama a funo promptNumber 10 vezes, teremos que verificar 10 vezes se nulo foi devolvido. E
se a sua resposta ao encontrar nulo simplesmente retornar nulo, o chamador por sua vez tem que verificar
assim por diante.

Excees
Quando uma funo no pode prosseguir normalmente, o que gostaramos de fazermos simplesmente parar o
que esta sendo feito e saltar imediatamente de volta para o lugar onde devemos lidar com o problema. Isto o
que faz o tratamento de exceo.

As excees so um mecanismo que torna possvel parar o cdigo que executado com problema disparando
(ou lanar) uma exceo que nada mais que um simples valor. Levantando uma exceo lembra um pouco um
retorno super carregado a partir de uma funo: ele salta para fora no apenas da funo atual mas tambm fora
de todo o caminho de seus interlocutores para a primeira chamada que iniciou a execuo atual. Isto chamado
de desenrolamento do stack . Voc pode se lembrar das chamadas de funo do stack que foi mencionado no
Captulo 3. Uma exceo exibida no stack indicando todos os contextos de chamadas que ele encontrou.

Se as excees tivessem um stack de uma forma ampliada no seria muito til. Eles apenas fornecem uma
nova maneira de explodir o seu programa. Seu poder reside no fato de que voc pode definir "obstculos" ao
longo do seu stack para capturar a exceo. Depois voc pode fazer alguma coisa com ele no ponto em que a
exceo foi pega para que o programa continua em execuo.

Aqui est um exemplo:

109
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}

function look() {
if (promptDirection("Which way?") == "L")
return "a house";
else
return "two angry bears";
}

try {
console.log("You see", look());
} catch (error) {
console.log("Something went wrong: " + error);
}

A palavra-chave throw usada para gerar uma exceo. Para tratar uma excesso basta envolver um pedao de
cdigo em um bloco try , seguido pela palavra-chave catch . Quando o cdigo no bloco try causa uma exceo
a ser lanada o bloco catch chamado. O nome da varivel (entre parnteses) aps captura ser vinculado ao
valor de exceo. Aps o termino do bloco catch ou do bloco try o controle prossegue sob toda a instruo
try/catch .

Neste caso usaremos o construtor de erro para lanar o nosso valor de exceo. Este um construtor JavaScript
normal que cria um objeto com uma propriedade de mensagem. Em ambientes de JavaScript modernos
instncias deste construtor tambm coletam informaes para o stack e sobre chamadas que existia quando a
exceo foi criado, o chamado stack de rastreamento. Esta informao armazenada na propriedade do stack e
pode ser til ao tentar depurar um problema: ela nos diz a funo precisa de onde ocorreu o problema e que
outras funes que levou at a chamada onde ocorreu a falha.

Note que se olharmos para funo promptDirection podemos ignoramos completamente a possibilidade de que
ela pode conter erros. Esta a grande vantagem do tratamento de erros - manipulao de erro no cdigo
necessrio apenas no ponto em que o erro ocorre e no ponto onde ele tratado. Essas funes no meio pode
perder tudo sobre ela.

Bem, estamos quase l.

Limpeza aps excees


Considere a seguinte situao: a funo withContext quer ter certeza de que durante a sua execuo, o contexto
de nvel superior da varivel tem um valor de contexto especfico. Depois que terminar ele restaura esta varivel
para o seu valor antigo.

var context = null;

function withContext(newContext, body) {


var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}

Como que o body gera uma exceo? Nesse caso, a chamada para withContext ser exibido no stack pela
exceo, e o contexto nunca ser definido de volta para o seu valor antigo.

110
O try tem mais uma declarao. Eles podem ser seguidos por um finally com ou sem o bloco catch . O bloco
finally significa "no importa o que acontea execute este cdigo depois de tentar executar o cdigo do bloco
try". Se uma funo tem de limpar alguma coisa, o cdigo de limpeza geralmente deve ser colocado em um bloco
finally .

function withContext(newContext, body) {


var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}

Note-se que no temos mais o resultado do context para armazenar (o que queremos voltar) em uma varivel.
Mesmo se sair diretamente do bloco try o ltimo bloco ser executado. Ento podemos fazer isso de um jeito mais
seguro:

try {
withContext(5, function() {
if (context < 10)
throw new Error("Not enough context!");
});
} catch (e) {
console.log("Ignoring: " + e);
}
// Ignoring: Error: Not enough context!

console.log(context);
// null

Mesmo que a chamada da funo withContext explodiu, withContext limpou corretamente a varivel context .

Captura seletiva
Quando uma exceo percorre todo o caminho at o final do stack sem ser pego, ele tratado pelo environment .
Significa que isto diferente entre os ambientes. Nos navegadores uma descrio do erro normalmente escrita
no console do JavaScript (alcanvel atravs de "Ferramentas" do navegador no menu de "developer").

Erros passam muitas vezes como algo normal, isto acontece para erros do programador ou problemas que o
browser no consegue manipular o erro. Uma exceo sem tratamento uma forma razovel para indicar a um
programa que ele esta quebrado e o console JavaScript em navegadores modernos ter que fornecer-lhe
algumas informaes no stack sobre quais foram as chamadas de funes quando o problema ocorreu.

Para problemas que se espera que acontea durante o uso rotineiro chegando como uma exceo e que no
seja tratada isso pode no ser uma resposta muito simptica.

Usos incorretos da linguagem como, a referncia a uma varivel inexistente, propriedade que tem null ou chamar
algo que no uma funo tambm ir resultar em lanamentos de excees. Essas excees podem ser
capturados como outra qualquer.

Quando um pedao de cdigo inserido no bloco catch , todos ns sabemos que algo em nosso corpo try

pode ou vai causar uma exceo. Mas ns no sabemos o que ou qual exceo que sera lanada.

111
O JavaScript (tem uma omisso gritante) no fornece suporte direto para a captura seletiva excees: ou voc
manipula todos ou voc trata de algum em especfico. Isto torna muito fcil supor que a exceo que voc recebe
o que voc estava pensando quando escreveu o bloco catch .

Mas talvez no seja nenhuma das opes citadas. Alguma outra hiptese pode ser violada ou voc pode ter
introduzido um erro em algum lugar que est causando uma exceo. Aqui est um exemplo que tentei manter a
chamada a funo promptDirection at que ele receba uma resposta vlida:

for (;;) {
try {
var dir = promtDirection("Where?"); // typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}

O for (;;) a construo de um loop infinito de forma intencionalmente que no para sozinho. Ns quebramos o
circuito de fora somente quando uma direo vlida fornecida. Mas a mal escrita do promptDirection resultar
em um erro de "varivel indefinida". O bloco catch ignora completamente o seu valor de exceo, supondo que
ele sabe qual o problema ele trata equivocadamente o erro de varivel como uma indicao de m entrada. Isso
no s causa um loop infinito mas tambm exibi uma mensagem de erro incorretamente sobre a varivel que
estamos usando.

Como regra geral no capturamos excees a menos que tenha a finalidade de monitora-las em algum lugar, por
exemplo atravs de softwares externos conectados nossa aplicao que indica quando nossa aplicao est
cada. E assim mesmo podemos pensar cuidadosamente sobre como voc pode estar escondendo alguma
informao.

E se quisermos pegar um tipo especfico de exceo? Podemos fazer isso atravs da verificao no bloco catch
para saber se a exceo que temos a que queremos, dai ento so lanar a exceo novamente. Mas como
que ns reconhecemos uma exceo?

Naturalmente ns poderamos fazer uma comparao de mensagens de erros. Mas isso uma forma instvel de
escrever cdigo pois estaramos utilizando informaes que so destinadas ao consumo humano (a mensagem)
para tomar uma deciso programtica. Assim que algum muda (ou traduz) a mensagem o cdigo ir parar de
funcionar.

Em vez disso, vamos definir um novo tipo de erro e usar instanceof para identific-lo.

function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}

InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";

O prototype feito para derivar-se de Error.prototype para que instanceof Error retorne true para objetos de
InputError . Nome a propriedade tambm dada para tipos de erro padro ( Error , SyntaxError , ReferenceError e
assim por diante) para que tambm se tenha uma propriedade.

A atribuio da propriedade no stack tenta deixar o rastreamento do objeto pelo stacktrace um pouco mais til,
em plataformas que suportam a criao de um objeto de erro regular pode usar a propriedade de stack do objeto
para si prprio.

Agora promptDirection pode lanar um erro.

112
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}

E o loop pode ser tratado com mais cuidado.

for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Not a valid direction. Try again.");
else
throw e;
}
}

Isso vai pegar apenas os casos de InputError e atravs disso deixa algumas excees independentes. Se voc
introduzir um erro de digitao ou um erro de varivel indefinida a aplicao nos avisar.

Asseres
As asseres so ferramentas que auxiliam na verificao da sanidade bsica de erros do programador.
Considere essa funo auxiliar que afirma:

function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);

function assert(test, message) {


if (!test)
throw new AssertionFailed(message);
}

function lastElement(array) {
assert(array.length > 0, "empty array in lastElement");
return array[array.length - 1];
}

Isso fornece uma maneira compacta de fazer cumprir as expectativas solicitadas para quebrar um programa se a
condio descrita no for vlida. Por exemplo se a funo lastElement que busca o ltimo elemento de uma matriz
voltar indefinida para matrizes vazias caso a declarao for omitida. Buscar o ltimo elemento de uma matriz vazia
no faz muito sentido por isso quase certeza de que um erro de programao pode acontecer.

As afirmaes so maneiras de certificar-se de que erros pode causar falhas e qual o ponto deste erro ao invs
de valores sem sentido produzidos silenciosamente que pode acarretar problemas em uma parte do programa a
qual no se tem nenhuma relao de onde ocorreu realmente.

Resumo

113
Erros e m entrada acontecem. Erros de programas precisam ser encontrados e corrigidos. Eles podem tornar-
se mais fcil de perceber quando se tem uma suites de testes automatizadas e asseres adicionadas em seu
programa.

Problemas causados por fatores fora do controle do programa devem geralmente serem tratados normalmente.
s vezes quando o problema pode ser tratado localmente, valores de retorno especiais um caminho sensato
para monitor-los. Caso contrrio as excees so preferveis.

Lanar uma exceo faz com que stack de chamadas se desencadeie o bloco try/catch at a parte inferior do
stack . O valor da exceo ser capturado pelo bloco catch onde podemos verificar se ele realmente do tipo de
exceo esperada e em seguida fazer algo com ela. Para lidar com o fluxo de controle imprevisvel causado pelas
excees, o bloco finally pode ser utilizado para garantir que um pedao de cdigo seja sempre executado.

Exerccios
Tente outra vez...
Digamos que voc tenha uma funo primitiveMultiply que em 50 por cento dos casos multiplica dois nmeros e
em outros 50 por cento levanta uma exceo do tipo MultiplicatorUnitFailure . Escreva uma funo que envolva
esta funo MultiplicatorUnitFailure e simplesmente tente at que uma chamada seja bem-sucedido retornando
o resultado.

Certifique-se de lidar com apenas as excees que voc est tentando manipular.

function MultiplicatorUnitFailure() {}

function primitiveMultiply(a, b) {
if (Math.random() < 0.5)
return a * b;
else
throw new MultiplicatorUnitFailure();
}

function reliableMultiply(a, b) {
// Coloque seu cdigo aqui.
}

console.log(reliableMultiply(8, 8));
// 64

Dica

A chamada de primitiveMultiply obviamente deve acontecer em um bloco try . O bloco catch fica responsvel
para relanar a exceo quando no uma instncia de MultiplicatorUnitFailure e garantir que a chamada
repetida quando ele uma instncia de MultiplicatorUnitFailure .

Para refazer o processo, voc pode usar um loop que quebra somente quando a chamada for bem sucedida;
veja os exemplos de recurso nos captulos anteriores e faa o uso; espero que voc no tenha uma grande
sries de erros na funo primitiveMultiply pois isso pode extrapolar o stack e entrar em loop infinito.

A caixa trancada
Considere o seguinte objeto:

114
var box = {
locked: true,
unlock: function() { this.locked = false; },

lock: function() { this.locked = true; },

_content: [],

get content() {
if (this.locked) throw new Error("Locked!");
return this._content;
}
};

Isto uma caixa com um cadeado. Dentro dela tem um array mas voc pode obt-lo apenas quando a caixa for
desbloqueada. No permitido acessar a propriedade _content diretamente.

Escreva uma funo chamada withBoxUnlocked que assume o valor da funo que passada por argumento para
abrir esta caixa. Execute a funo e em seguida garanta que a caixa est bloqueada antes de voltar novamente;
no importa se o argumento da funo retornou normalmente ou emitiu uma exceo.

function withBoxUnlocked(body) {
// Your code here.
}

withBoxUnlocked(function() {
box.content.push("gold piece");
});

try {
withBoxUnlocked(function() {
throw new Error("Pirates on the horizon! Abort!");
});
} catch (e) {
console.log("Error raised:", e);
}

console.log(box.locked);
// true

Para ganhar pontos extras, certifique-se de que chamou withBoxUnlocked quando a caixa j estava desbloqueada,
pois a caixa deve sempre permanecer desbloqueada.

Dica:

Voc provavelmente deve ter adivinhado que este exerccio solicita o uso do bloco finally . Sua funo deve ser
destravar a caixa e em seguida chamar a funo que vem de argumento dentro da funo withBoxUnlocked . E no
finally ele deve travar a caixa novamente.

Para certificar-se de que ns no bloqueamos a caixa quando ela j estava bloqueada verifique no incio da
funo se a mesma verificao vlida para quando a caixa esta desbloqueada e para quando quisermos
bloquear ela novamente.

115
Captulo 9

Expresses Regulares
"Algumas pessoas, quando confrontadas com um problema, pensam "Eu sei, terei que usar expresses
regulares." Agora elas tm dois problemas.

Jamie Zawinski

"Yuan-Ma disse, 'Quando voc serra contra o sentido da madeira, muita fora ser necessria. Quando voc
programa contra o sentido do problema, muito cdigo ser necessrio'

Mestre Yuan-Ma, The Book of Programming

A maneira como tcnicas e convenes de programao sobrevivem e se disseminam, ocorrem de um modo


catico, evolucionrio. No comum que a mais agradvel e brilhante vena, mas sim aquelas que combinam
bem com o trabalho e o nicho, por exemplo, sendo integradas com outra tecnologia de sucesso.

Neste captulo, discutiremos uma dessas tecnologias, expresses regulares. Expresses regulares so um
modo de descrever padres em um conjunto de caracteres. Eles formam uma pequena linguagem parte, que
includa no JavaScript (assim como em vrias outras linguagens de programao e ferramentas).

Expresses regulares so ao mesmo tempo, extremamente teis e estranhas. Conhec-las apropriadamente


facilitar muito vrios tipos de processamento de textos. Mas a sintaxe utilizada para descrev-las ridiculamente
enigmtica. Alm disso, a interface do JavaScript para elas um tanto quanto desajeitada.

Notao
Uma expresso regular um objeto. Ele pode ser construdo com o construtor RegExp ou escrito como um valor
literal, encapsulando o padro com o caractere barra ('/').

var expReg1 = new RegExp("abc");


var expReg2 = /abc/;

Este objeto representa um padro, que no caso uma letra "a" seguida de uma letra "b" e depois um "c".

Ao usar o construtor RegExp, o padro escrito como um texto normal, de modo que as regras normais se
aplicam para barras invertidas. Na segunda notao, usamos barras para delimitar o padro. Alguns outros
caracteres, como sinais de interrogao (?) e sinais de soma (+), so usados como marcadores especiais em
expresses regulares, e precisam ser precedidos por uma barra invertida, para representarem o caractere
original e no o comando de expresso regular.

var umMaisum = /1 \+ 1/;

Saber exatamente quais caracteres devem ser escapados com uma barra invertida em uma expresso regular
exige que voc saiba todos os caracteres especiais e seus significados na sintaxe de expresses regulares. Por
enquanto, pode no parecer fcil saber todos, ento, se tiver dvidas, escape todos os caracteres que no sejam
letras e nmeros ou um espao em branco.

Testando por correspondncias

116
Expresses regulares possuem vrios mtodos. O mais simples test, onde dado um determinado texto, ele
retorna um booleano que informa se o padro fornecido na expresso foi encontrado nesse texto.

console.log( /abc/.test("abcde") );
// true
console.log( /abc/.test("12345") );
// false

Uma expresso regular que contenha apenas caracteres simples, representa essa mesma sequncia de
caracteres. Se "abc" existe em qualquer lugar (no apenas no incio) do texto testado, o resultado ser verdadeiro.

Encontrando um conjunto de caracteres


Saber quando uma _string_contm "abc" pode muito bem ser feito usando a funo indexOf. A diferena das
expresses regulares que elas permitem padres mais complexos de busca.

Digamos que queremos achar qualquer nmero. Em uma expresso regular, colocar um conjunto de caracteres
entre colchetes ("[]") faz com que a expresso ache qualquer dos caracteres dentro dos colchetes.

A expresso abaixo, acha todas as strings que contem um dgito numrico.

console.log( /[0123456789]/.test("ano 1992") );


// true
console.log( /[0-9]/.test("ano 1992") );
// true

Dentro de colchetes, um hfen ("-") entre dois caracteres pode ser usado para indicar um conjunto entre dois
caracteres. Uma vez que os cdigos de caracteres Unicode de "0" a "9" contm todos os dgitos (cdigos 48 a 57),
[0-9] encontrar qualquer dgito.

Existem alguns grupos de caracteres de uso comum, que j possuem atalhos includos na sintaxe de expresses
regulares. Dgitos so um dos conjuntos que voc pode escrever usando um atalho, barra invertida seguida de
um "d" minsculo (\d), com o mesmo significado que [0-9].

- \d caracteres numricos
- \w caracteres alfanumricos ("letras")
- \s espaos em branco (espao, tabs, quebras de linha e similares)
- \D caracteres que no so dgitos
- \W caracteres no alfanumricos
- \S caracteres que no representam espaos
- . (ponto) todos os caracteres, exceto espaos

Para cada um dos atalhos de conjuntos de caracteres, existe uma variao em letra maiscula que significa o
exato oposto.

Ento voc pode registrar um formato de data e hora como "30/01/2003 15:20" com a seguinte expresso:

var dataHora = /\d\d\/\d\d\/\d\d\d\d \d\d:\d\d/;


console.log( dataHora.test("30/01/2003 15:20") );
// true
console.log( dataHora.test("30/jan/2003 15:20") );
// false

Parece confuso, certo? Muitas barras invertidas, sujando a expresso, que dificultam compreender qual o padro
procurado. Mas assim mesmo o trabalho com expresses regulares.

117
Estes marcadores de categoria tambm podem ser usados dentro de colchetes, ento [\d.] significa qualquer
dgito ou ponto.

Para "inverter" um conjunto de caracteres, buscar tudo menos o que voc escreveu no padro, um cento
circunflexo ("^") colocado no incio do colchete de abertura.

var naoBinario = /[^01]/;


console.log( naoBinario.test("01101") );
// false
console.log( naoBinario.test("01201") );
// true

Partes repetidas em um padro


J aprendemos a encontrar um dgito, mas o que realmente queremos encontrar um nmero, uma sequncia
de um ou mais dgitos.

Quando se coloca um sinal de mais ("+") depois de algo em uma expresso regular, indicamos que pode existir
mais de um. Ento /\d+/ encontra um ou mais dgitos.

console.log( /'\d+'/.test("'123'") );
// true
console.log( /'\d+'/.test("''") );
// false
console.log( /'\d*'/.test("'123'") );
// true
console.log( /'\d*'/.test("''") );
// true

O asterisco ("*") tem um significado similar, mas tambm permite no encontrar o padro. Ento, algo com um
asterisco depois no impede um padro de ser achado, apenas retornando zero resultados.

Uma interrogao ("?") define uma parte do padro de busca como "opcional", o que significa que ele pode
ocorrer zero ou mais vezes. Neste exemplo, permitido que ocorra o caractere "u", mas o padro tambm
encontrado quando ele est ausente.

var neighbor = /neighbou?r/;


console.log(neighbor.test("neighbour"));
// true
console.log(neighbor.test("neighbor"));
// true

Para permitir que um padro ocorra um nmero definido de vezes, chaves ("{}") so usadas. Colocando {4} depois
de um elemento do padro, mostra que ele deve ocorrer 4 vezes, exatamente. Da mesma maneira, {2,4}
utilizado para definir que ele deve aparecer no mnimo 2 vezes e no mximo 4.

Segue outra verso do padro mostrado acima, de data e hora. Ele permite, dias com um dgito, ms e hora como
nmeros e mais legvel:

var dataHora = /\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}/;


console.log( dataHora.test("30/1/2003 8:45") );
// true

Tambm possvel deixar em aberto o nmero mnimo ou mximo de ocorrncias, omitindo o nmero
correspondente. Ento {,5} significa que deve ocorrer de 0 at 5 vezes e {5,} significa que deve ocorrer cinco ou
mais vezes.

118
Agrupando subexpresses
Para usar um operador como "*" ou "+" em mais de um caractere de de uma vez, necessrio o uso de
parnteses. Um pedao de uma expresso regular que delimitado por parnteses conta como uma nica
unidade, assim como os operadores aplicados a esse pedao delimitado.

var cartoonCrying = /boo+(hoo+)+/i;


console.log( cartoonCrying.test("Boohoooohoohooo") );
// true

O terceiro "+" se aplica a todo grupo (hoo+), encontrando uma ou mais sequncias como essa.

O "i" no final da expresso do exemplo acima faz com que a expresso regular seja case-insensitive, permitindo-a
encontrar a letra maiscula "B" na _string_dada, mesmo que a descrio do padro tenha sido feita em letras
minsculas.

Resultados e grupos
O mtodo test a maneira mais simples de encontrar correspondncias de uma expresso regular. Ela apenas
informa se foi encontrado algo e mais nada. Expresses regulares tambm possuem o mtodo exec (executar),
que ir retornar null quando nenhum resultado for encontrado, e um objeto com informaes se encontrar.

var match = /\d+/.exec("one two 100");


console.log(match);
// ["100"]
console.log(match.index);
// 8

Valores _string_possuem um mtodo que se comporta de maneira semelhante.

console.log("one two 100".match(/\d+/));


// ["100", index: 8, input: "one two 100"]

Um objeto retornado pelo mtodo exec ou match possui um index de propriedades que informa aonde na
_string_o resultado encontrado se inicia. Alm disso, o objeto se parece (e de fato ) um array de strings, onde o
primeiro elemento a _string_que foi achada, no exemplo acima, a sequncia de dgitos numricos.

Quando uma expresso regular contm expresses agrupadas entre parnteses, o texto que corresponde a
esses grupos tambm aparece no array. O primeiro elemento sempre todo o resultado, seguido pelo resultado
do primeiro grupo entre parnteses, depois o segundo grupo e assim em diante.

var textoCitado = /'([^']*)'/;


console.log( textoCitado.exec("'ela disse adeus'") );
// ["'ela disse adeus'", "ela disse adeus", index: 0, input: "'ela disse adeus'"]

Quando um grupo no termina sendo achado (se por exemplo, possui um sinal de interrogao depois dele), seu
valor no array de resultado ser undefined. Do mesmo modo, quando um grupo achado vrias vezes, apenas o
ltimo resultado encontrado estar no array.

console.log(/bad(ly)?/.exec("bad"));
// ["bad", undefined]
console.log(/(\d)+/.exec("123"));
// ["123", "3"]

119
Grupos podem ser muito teis para extrair partes de uma string. Por exemplo, podemos no querer apenas
verificar quando uma _string_contm uma data, mas tambm extra-la, e construir um objeto que a representa. Se
adicionarmos parnteses em volta do padro de dgitos, poderemos selecionar a data no resultado da funo
exec.

Mas antes, um pequeno desvio.

O tipo data
O JavaScript possui um objeto padro para representar datas, ou melhor, pontos no tempo. Ele chamado Date.
Se voc simplesmente criar uma data usando new, ter a data e hora atual.

console.log( new Date() );


// Fri Feb 21 2014 09:39:31 GMT-0300 (BRT)

Tambm possvel criar um objeto para uma hora especfica

console.log( new Date(2014, 6, 29) );


// Tue Jul 29 2014 00:00:00 GMT-0300 (BRT)
console.log( new Date(1981, 6, 29, 18, 30, 50) );
// Wed Jul 29 1981 18:30:50 GMT-0300 (BRT)

O JavaScript utiliza uma conveno onde a numerao dos meses se inicia em zero (ento Dezembro 11), mas
os dias iniciam-se em um. bem confuso, ento, tenha cuidado.

Os ltimos quatro argumentos (horas, minutos, segundos e milissegundos) so opcionais, e assumem o valor
de zero se no forem fornecidos.

Internamente, objetos do tipo data so armazenados como o nmero de milissegundos desde o incio de 1970.
Usar o mtodo getTime em uma data retorna esse nmero, e ele bem grande, como deve imaginar.

console.log( new Date(2014, 2, 21).getTime() );


// 1395370800000
console.log( new Date( 1395370800000 ) );
// Fri Mar 21 2014 00:00:00 GMT-0300 (BRT)

Quando fornecemos apenas um argumento ao construtor do Date, ele tratado como se fosse um nmero de
milissegundos.

Objetos Date possuem mtodos como getFullYear (getYear retorna apenas os inteis dois ltimos dgitos do
ano), getMonth, getDate, getHours, getMinutes e getSeconds para extrair os componentes da data.

Ento agora, ao colocar parnteses em volta das partes que nos interessam, podemos facilmente extrair uma
data de uma string.

function buscaData(string) {
var dateTime = /(\d{1,2})\/(\d{1,2})\/(\d{4})/;
var match = dateTime.exec(string);
return new Date( Number(match[3]), Number(match[2] ), Number(match[1]) );
}
console.log( buscaData("21/1/2014") );
// Fri Feb 21 2014 00:00:00 GMT-0300 (BRT)

Limites de palavra e string

120
A funo b uscaData acima ir extrair facilmente a data de um texto como "100/1/30000", um resultado pode
acontecer em qualquer lugar da string fornecida, ento, nesse caso, vai encontrar no segundo caractere e terminar
no ltimo

Se quisermos nos assegurar que a busca seja em todo o texto, podemos adicionar os marcadores "^" e "$". O
primeiro acha o incio da string fornecida e o segundo o final dela. Ento /^\d+$/ encontra apenas em uma string
feita de um ou mais dgitos, /^!/ encontra qualquer string que comea com sinal de exclamao e /x^/ no acha
nada (o incio de uma string no pode ser depois de um caractere).

Se, por outro lado, queremos ter certeza que a data inicia e termina no limite da palavra, usamos o marcador \b.
Um limite de palavra um ponto onde existe um caractere de um lado e um caractere que no seja de palavra de
outro.

console.log( /cat/.test("concatenate") );
// true
console.log( /\bcat\b/.test("concatenate") );
// false

Note que esses marcadores de limite no cobrem nenhum caractere real, eles apenas asseguram que o padro
de busca ir achar algo na posio desejada, informada nos marcadores.

Alternativas
Agora, queremos saber se um pedao do texto contm no apenas um nmero, mas um nmero seguido por
uma das palavras "porco", "vaca", "galinha" ou seus plurais tambm.

Podemos escrever trs expresses regulares, e testar cada uma, mas existe uma maneira mais simples. O
caractere pipe ("|") indica uma opo entre o padro esquerda ou a direita. Ento podemos fazer:

var contagemAnimal = /\b\d+ (porco|vaca|galinha)s?\b/;


console.log( contagemAnimal.test("15 porcos") );
// true
console.log( contagemAnimal.test("15 porcosgalinhas") );
// false

Parnteses podem ser usados para limitar a que parte do padro que o pipe ("|") se aplica, e voc pode colocar
vrios desses operadores lado a lado para expressar uma escolha entre mais de dois padres.

O mecanismo de procura

Uma string corresponde expresso se um caminho do incio (esquerda) at o final (direita) do diagrama puder
ser encontrado, com uma posio inicial e final correspondente, de modo que cada vez que passar em uma caixa,
verificamos que a posio atual na sequncia corresponde ao elemento descrito nela, e, para os elementos que

121
correspondem caracteres reais (menos os limites de palavra), continue no fluxo das caixas.

Ento se encontrarmos "the 3 pigs" existe uma correspondncia entre as posies 4 (o dgito "3") e 10 (o final da
string).

Na posio 4, existe um limite de palavra, ento passamos a primeira caixa


Ainda na posio 4, encontramos um dgito, ento ainda podemos passar a primeira caixa.
Na posio 5, poderamos voltar para antes da segunda caixa (dgitos), ou avanar atravs da caixa que
contm um nico caractere de espao. H um espao aqui, no um dgito, por isso escolhemos o segundo
caminho.
Estamos agora na posio 6 (o incio de "porcos") e na diviso entre trs caminhos do diagrama. Ns no
temos "vaca" ou "galinha" aqui, mas ns temos "porco", por isso tomamos esse caminho.
Na posio 9, depois da diviso em trs caminhos, poderamos tambm ignorar o "s" e ir direto para o limite
da palavra, ou achar o "s" primeiro. Existe um "s", no um limite de palavra, ento passamos a caixa de "s".
Estamos na posio 10 (final da string) e s podemos achar um limite de palavra. O fim de uma string conta
como um limite de palavra, de modo que passamos a ltima caixa e achamos com sucesso a busca.

O modo como o mecanismo de expresses regulares do JavaScript trata uma busca em uma string simples.
Comea no incio da string e tenta achar um resultado nela. Nesse casso, existe um limite de palavra aqui, ento
passamos pela primeira caixa, mas no existe um dgito, ento ele falha na segunda caixa. Continua no segundo
caractere da string e tenta novamente. E assim continua, at encontrar um resultado ou alcanar o fim da string e
concluir que no encontrou nenhum resultado

Retrocedendo
A expresso regular /\b([01]+b|\d+|[\da-f]h)\b/ encontra um nmero binrio seguido por um "b", um nmero decimal,
sem um caractere de sufixo, ou um nmero hexadecimal (de base 16, com as letras "a" a "f" para os algarismos
de 10 a 15), seguido por um "h". Este o diagrama equivalente:

http://eloquentJavaScript.net/2nd_edition/preview/img/re_number.svg

Ao buscar esta expresso, muitas vezes o ramo superior ser percorrido, mesmo que a entrada no contenha
realmente um nmero binrio. Quando busca a string "103", apenas no "3" que torna-se claro que estamos no
local errado. A expresso buscada no apenas no ramo que se est executando.

o que acontece se a expresso retroage. Quando entra em um ramo, ela guarda em que ponto aconteceu
(nesse caso, no incio da string, na primeira caixa do diagrama), ento ela retrocede e tenta outro ramo do
diagrama se o atual no encontra nenhum resultado. Ento para a string "103", aps encontrar o caractere "3", ela
tentar o segundo ramo, teste de nmero decimal. E este, encontra um resultado.

Quando mais de um ramo encontra um resultado, o primeiro (na ordem em que foi escrito na expresso regular)
ser considerado.

Retroceder acontece tambm, de maneiras diferentes, quando buscamos por operadores repetidos. Se
buscarmos /^.x/ em "ab cxe", a parte "." tentar achar toda a string. Depois, tentar achar apenas o que for seguido
de um "x", e no existe um "x" no final da string. Ento ela tentar achar desconsiderando um caractere, e outro, e
outro. Quando acha o "x", sinaliza um resultado com sucesso, da posio 0 at 4.

possvel escrever expresses regulares que fazem muitos retrocessos. O Problema ocorre quando um padro
encontra um pedao da string de entrada de muitas maneiras. Por exemplo, se confundimos e escrevemos nossa
expresso regular para achar binrios e nmeros assim /([01]+)+b/.

http://eloquentJavaScript.net/2nd_edition/preview/img/re_slow.svg

122
Ela tentar achar sries de zeros sem um "b" aps elas, depois ir percorrer o circuito interno at passar por
todos os dgitos. Quando perceber que no existe nenhum "b", retorna uma posio e passa pelo caminho de fora
mais uma vez, e de novo, retrocedendo at o circuito interno mais uma vez. Continuar tentando todas as rotas
possveis atravs destes dois loops, em todos os caracteres. Para strings mais longas o resultado demorar
praticamente para sempre.

O mtodo replace
Strings possuem o mtodo replace, que pode ser usado para substituir partes da string com outra string

console.log("papa".replace("p", "m"));
// mapa

O primeiro argumento tambm pode ser uma expresso regular, que na primeira ocorrncia de correspondncia
ser substituda.

console.log("Borobudur".replace(/[ou]/, "a"));
// Barobudur
console.log("Borobudur".replace(/[ou]/g, "a"));
// Barabadar

Quando a opo "g" ("global") adicionada expresso, todas as ocorrncias sero substitudas, no s a
primeira.

Seria melhor se essa opo fosse feita atravs de outro argumento, em vez de usar a opo prpria de uma
expresso regular. (Este um exemplo de falha na sintaxe do JavaScript)

A verdadeira utilidade do uso de expresses regulares com o mtodo replace a opo de fazer referncias aos
grupos achados atravs da expresso. Por exemplo, se temos uma string longa com nomes de pessoas, uma
por linha, no formato "Sobrenome, Nome" e queremos trocar essa ordem e remover a vrgula, para obter o formato
"Nome Sobrenome", podemos usar o seguinte cdigo:

console.log("Hopper, Grace\nMcCarthy, John\nRitchie, Dennis".replace(/([\w ]+), ([\w ]+)/g, "$2 $1"));


// Grace Hopper
// John McCarthy
// Dennis Ritchie

O "$1" e "$2" na string de substituio referem-se as partes entre parnteses no padro. "$1" ser substitudo pelo
texto achado no primeiro grupo entre parnteses e "$2" pelo segundo, e assim em diante, at "$9".

Tambm possvel passar uma funo, em vez de uma string no segundo argumento do mtodo replace. Para
cada substituio, a funo ser chamada com os grupos achados (assim como o padro) como argumentos, e
o valor retornado pela funo ser inserido na nova string.

Segue um exemplo simples:

var s = "the cia and fbi";


console.log(s.replace(/\b(fbi|cia)\b/g, function(str) {
return str.toUpperCase();
}));
// the CIA and FBI

E outro exemplo:

123
var stock = "1 lemon, 2 cabbages, and 101 eggs";
function minusOne(match, amount, unit) {
amount = Number(amount) - 1;
if (amount == 1) // only one left, remove the 's'
unit = unit.slice(0, unit.length - 1);
else if (amount == 0)
amount = "no";
return amount + " " + unit;
}
console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
// no lemon, 1 cabbage, and 100 eggs

Ele pega a string, acha todas as ocorrncias de um nmero seguido por uma palavra alfanumrica e retorna uma
nova string onde cada achado diminudo em um.

O grupo (\d+) finaliza o argumento da funo e o (\w+) limita a unidade. A funo converte o valor em um nmero,
desde que achado, \d+ faz ajustes caso exista apenas um ou zero esquerda.

Quantificador / Greed
simples usar o mtodo replace para escrever uma funo que remove todos os comentrios de um pedao de
cdigo JavaScript. Veja uma primeira tentativa

function stripComments(code) {
return code.replace(/\/\/.*|\/\*[\w\W]*\*\//g, "");
}
console.log(stripComments("1 + /* 2 */3"));
// 1 + 3
console.log(stripComments("x = 10;// ten!"));
// x = 10;
console.log(stripComments("1 /* a */+/* b */ 1"));
// 1 1

A parte [\w\W] uma maneira (feia) de encontrar qualquer caractere. Lembre-se que um ponto no encontra um
caractere de quebra de linha / linha nova. Comentrios podem conter mais de uma linha, ento no podemos
usar um ponto aqui. Achar algo que seja ou no um caractere de palavra, ir encontrar todos os caracteres
possveis.

Mas o resultado do ltimo exemplo parece errado. Porque?

A parte "." da expresso, como foi escrita na seo "Retrocedendo", acima, encontrar primeiro tudo que puder e
depois, se falhar, volta atrs e tenta mais uma vez a partir da. Nesse caso, primeiro procuramos no resto da string
e depois continuamos a partir da. Encontrar uma ocorrncia de "/" depois volta quatro caracteres e acha um
resultado. Isto no era o que desejvamos, queramos um comentrio de uma linha, para no ir at o final do
cdigo e encontrar o final do ltimo comentrio.

Existem duas variaes de operadores de repetio em expresses regulares ('+', '*', e '{}'). Por padro, eles
quantificam, significa que eles encontram o que podem e retrocedem a partir da. Se voc colocar uma
interrogao depois deles, eles se tornam non_greedy, e comeam encontrando o menor grupo possvel e o
resto que no contenha o grupo menor.

E exatamente o que queremos nesse caso. Com o asterisco encontramos os grupos menores que tenham "*/"
no fechamento, encontramos um bloco de comentrios e nada mais.

124
function stripComments(code) {
return code.replace(/\/\/.*|\/\*[\w\W]*?\*\//g, "");
}
console.log(stripComments("1 /* a */+/* b */ 1"));
// 1 + 1

Criando objetos RegExp dinamicamente


Existem casos onde voc pode no saber o padro exato que voc precisa quando escreve seu cdigo. Digamos
que voc queira buscar o nome de um usurio em um pedao de texto e coloc-lo entre caracteres "_" para
destac-lo. O nome ser fornecido apenas quando o programa estiver sendo executado, ento no podemos usar
a notao de barras para criar nosso padro.

Mas podemos construir uma string e usar o construtor RegExp para isso. Por exemplo:

var name = "harry";


var text = "Harry is a suspicious character.";
var regexp = new RegExp("\\b(" + name + ")\\b", "gi");
console.log(text.replace(regexp, "_$1_"));
// _Harry_ is a suspicious character.

Ao criar os marcos de limite "\b, usamos duas barras invertidas, porque estamos escrevendo-os em uma string
normal, no uma expresso regular com barras. As opes (global e case-insensitive) para a expresso regular
podem ser inseridas como segundo argumento para o construtor RegExp.

Mas e se o nome for "dea+hl[]rd" porque o usurio um adolescente nerd? Isso ir gerar uma falsa expresso
regular, por conter caracteres comando, que ir gerar um resultado estranho

Para contornar isso, adicionamos contrabarras antes de qualquer caractere que no confiamos. Adicionar
contrabarras antes de qualquer caractere alfabtico uma m idia, porque coisas como "\b" ou "\n" possuem
significado para uma expresso regular. Mas escapar tudo que no for alfanumrico ou espao seguro.

var name = "dea+hl[]rd";


var text = "This dea+hl[]rd guy is quite annoying.";
var escaped = name.replace(/[^\w\s]/g, "\\$&");
var regexp = new RegExp("\\b(" + escaped + ")\\b", "gi");
console.log(text.replace(regexp, "_$1_"));
// This _dea+hl[]rd_ guy is quite annoying.

O marcador "$&" na string de substituio age como se fosse "$1", mas ser substitudo em dodos os resultados
ao invs do grupo encontrado.

O mtodo search
O mtodo indexOf em strings no pode ser invocado com uma expresso regular. Mas existe um outro mtodo,
search, que espera como argumento uma expresso regular, e como o indexOf, retorna o ndice do primeiro
resultado encontrado ou -1 se no encontra.

console.log(" word".search(/\S/));
// 2
console.log(" ".search(/\S/));
// -1

125
Infelizmente, no existe um modo de indicar onde a busca deve comear, com um ndice (como o segundo
argumento de indexOf), o que seria muito til.

A propriedade lastIndex
O mtodo exec tambm no possui um modo conveniente de iniciar a busca a partir de uma determinada
posio. Mas ele fornece um mtodo no muito prtico.

Expresses regulares possuem propriedades (como source que contm a string que originou a expresso). Uma
dessas propriedades, lastIndex, controla, em algumas circunstncias, onde a busca comear.

Essas circunstncias so que a expresso regular precisa ter a opo "global" (g) habilitada e precisa ser no
mtodo exec. Novamente, deveria ser da mesma maneira que permitir um argumento extra para o mtodo exec,
mas coeso no uma caracterstica que define a sintaxe de expresses regulares em JavaScript

var pattern = /y/g;


pattern.lastIndex = 3;
var match = pattern.exec("xyzzy");
console.log(match.index);
// 4
console.log(pattern.lastIndex);
// 5

A propriedade lastIndex atualizada ao ser executada aps encontrar algo. Quando no encontra nada, lastIndex
definida como zero, que tambm o valor quando uma nova expresso construda.

Quando usada uma expresso regular global para mltiplas chamadas ao mtodo exec, esta mudana da
propriedade lastIndex pode causar problemas, sua expresso pode iniciar por acidente em um ndice deixado na
ultima vez que foi executada.

Outro efeito interessante da opo global que ela muda a maneira como o mtodo match funciona em uma
string. Quando chamada com uma expresso global, em vez de retornar um array semelhante ao retornado pelo
exec, match encontrar todos os resultados do padro na string e retornar um array contendo todas as strings
encontradas.

console.log("Banana".match(/an/g));
// ["an", "an"]

Ento tenha cuidado com expresses regulares globais. Os casos em que so necessrias - chamadas para
substituir e lugares onde voc deseja usar explicitamente lastIndex - normalmente so os nicos lugares onde
voc deseja utiliz-las.

Um padro comum buscar todas as ocorrncias de um padro em uma string, com acesso a todos os grupos
encontrados e ao ndice onde foram encontrados, usando lastIndex e exec.

var input = "A text with 3 numbers in it... 42 and 88.";


var re = /\b(\d+)\b/g;
var match;
while (match = re.exec(input))
console.log("Found", match[1], "at", match.index);
// Found 3 at 12
// Found 42 at 31
// Found 88 at 38

Usa-se o fato que o valor de uma expresso de definio ('=') o valor assinalado. Ento usando-se match =

re.exec(input) como a condio no bloco while , podemos buscar no incio de cada iterao.

126
Analisando um arquivo .ini
Agora vamos ver um problema real que pede por uma expresso regular. Imagine que estamos escrevendo um
programa que coleta informao automaticamente da internet dos nossos inimigos. (No vamos escrever um
programa aqui, apenas a parte que l o arquivo de configurao, desculpe desapont-los). Este arquivo tem a
seguinte aparncia:

searchengine=http://www.google.com/search?q=$1
spitefulness=9.7

; comments are preceded by a semicolon...


; these are sections, concerning individual enemies
[larry]
fullname=Larry Doe
type=kindergarten bully
website=http://www.geocities.com/CapeCanaveral/11451

[gargamel]
fullname=Gargamel
type=evil sorcerer
outputdir=/home/marijn/enemies/gargamel

As regras exatas desse formato (que um formato muito usado, chamado arquivo .ini) so as seguintes:

Linhas em branco e linhas iniciadas com ponto e vrgula so ignoradas.


Linhas entre colchetes "[ ]" iniciam uma nova seo.
Linhas contendo um identificador alfanumrico seguido por um caractere = adicionam uma configurao
seo atual.
Qualquer outra coisa invlida.

Nossa tarefa converter uma string como essa em um array de objetos, cada uma com um nome e um array de
pares nome/valor. Precisaremos de um objeto para cada seo e outro para as configuraes de seo.

J que o formato precisa ser processado linha a linha, dividir em linhas separadas um bom comeo. Usamos o
mtodo split antes para isso, string.split("\n"). Entretanto alguns sistemas operacionais no usam apenas um
caractere de nova linha para separar linhas, mas um caractere de retorno seguido por um de nova linha ("\r\n").

Desse modo o mtodo split ,em uma expresso regular com /\r?\n/ permite separar os dois modos, com "\n"e
"\r\n" enre linhas.

127
function parseINI(texto) {
var categorias = [];
function novaCategoria(nome) {
var categ = {nome: nome, fields: []};
categorias.push(categ);
return categ;
}
var categoriaAtual = novaCategoria("TOP");

texto.split(/\r?\n/).forEach(function(linha) {
var encontrados;
if (/^\s*(;.*)?$/.test(linha))
return;
else if (encontrados = linha.encontrados(/^\[(.*)\]$/))
categoriaAtual = novaCategoria(encontrados[1]);
else if (encontrados = linha.encontrados(/^(\w+)=(.*)$/))
categoriaAtual.fields.push({nome: encontrados[1],
value: encontrados[2]});
else
throw new Error("Linha '" + linha + "' is invalid.");
});

return categorias;
}

O cdigo percorre cada linha no arquivo. Ele mantm um objeto "categoria atual", e quando encontra um diretiva
normal, adiciona ela ao objeto. Quando encontra uma linha que inicia uma nova categoria, ela troca a categoria
atual pela nova, para adicionar as diretivas seguintes. Finalmente, retorna um array contendo todas as categorias
que encontrou.

Observe o uso recorrente de e $ para certificar-se que a expresso busca em toda a linha, no apenas em parte
dela. Esquecer isso um erro comum, que resulta um cdigo que funciona mas retorna resultados estranhos
para algumas entradas.

A expresso /^\s(;.)?$/ pode ser usada para testar linhas que podem ser ignoradas. Entende como funciona? A
parte entre parnteses ir encontrar comentrios e o ? depois certificar que tambm encontrar linhas apenas
com espaos em branco.

O padro if (encontrados = texto.match(...)) parecido com o truque que foi usado como definio do while antes.
Geralmente no temos certeza se a expresso encontrar algo. Mas voc s deseja fazer algo com o resultado se
ele no for nulo, ento voc precisa testar ele antes. Para no quebrar a agradvel sequencia de ifs podemos
definir o resultado a uma varivel para o teste, e fazer a busca e testes em uma nica linha.

Caracteres internacionais
Devido a uma implementao inicial simplista e o fato que esta abordagem simplista mais tarde foi gravada em
pedra como comportamento padro, expresses regulares do JavaScript so um pouco estpidas sobre
caracteres que no parecem na lngua inglesa. Por exemplo, "caracteres palavra", nesse contexto, atualmente
significam apenas os 26 caracteres do alfabeto latino. Coisas como "" ou "", que definitivamente so caracteres
de palavras, no encontraro resultados com \w (e sero encontradas com o marcador de letras maisculas \W).

Devido a um estranho acidente histrico, \s (espao em branco) diferente, e ir encontrar todos os caracteres
que o padro Unicode considera como espao em branco, como espaos sem quebra ou o separador de vogais
do alfabeto Mongol.

Algumas implementaes de expresses regulares em outras linguagens de programao possuem uma


sintaxe para buscar conjuntos especficos de caracteres Unicode, como todas as maisculas, todos de
pontuao, caracteres de controle ou semelhantes. Existem planos para adicionar esse suporte ao JavaScript,

128
mas infelizmente parece que isso no acontecer to cedo.

Uma ou mais ocorrncias do padro


Expresses regulares so objetos que representam padres em strings. Eles usam sua prpria sintaxe para
expressar esses padres.

/abc/ Sequncia de caracteres


/[abc]/ Qualquer caractere do conjunto
/[^abc]/ Qualquer caractere que no seja do conjunto
/[0-9]/ Qualquer caractere no intervalo de caracteres
/x+/ Uma ou mais ocorrncias do padro
/x+?/ Uma ou mais ocorrncias do padro, no obrigatrio
/x*/ Zero ou mais ocorrncias
/x?/ Zero ou uma ocorrncia
/x{2,4}/ Entre duas e quatro ocorrncias
/(abc)+/ Agrupamento
/a|b|c/ Padres alternativos
/\d/ Caracteres dgitos
/\w/ Caracteres alfanumricos ("caracteres palavra")
/\s/ caracteres espao em branco
/./ Todos caracteres exceto quebras de linha
/\b/ Limite de palavra
/^/ Incio da entrada
/$/ Final da Entrada

Uma expresso regular possui um mtodo test para testar quando um padro encontrado em uma string, um
mtodo exec que quando encontra um resultado retorna um array com todos os grupos encontrados e uma
propriedade index que indica onde o resultado inicia.

Strings possuem um mtodo match para test-las contra uma expresso regular e um mtodo search para
buscar por um resultado. O mtodo replace pode substituir resultados encontrados por um padro. Como
alternativa, uma funo pode ser passada para montar o texto que ser substitudo de acordo com que foi achado.

Expresses regulares podem ter opes configuradas (flags), que so escritas aps o fechamento da barra. A
opo "i" faz a busca sem se importar se maiscula ou minscula, a opo "g" faz a busca global, que, entre
outras coisas, faz o mtodo replace substituir todas as ocorrncias, em vez de s a primeira.

O construtor RegExp pode ser usado para criar uma expresso regular dinmica a partir de uma string.

Expresses regulares so uma ferramenta precisa mas com um manuseio estranho. Elas simplificaro muito
algumas tarefas simples, mas rapidamente se tornaro inviveis quando aplicadas a tarefas mais complexas.
Saber quando us-las til. Parte do conhecimento de saber quando us-las o conhecimento de saber como
us-las e quando desistir do seu uso e procurar uma abordagem mais simples.

Exerccios
quase inevitvel que, no decorrer do trabalho, voc ir ficar confuso e frustado por algum comportamento
estranho de uma expresso regular. O que ajuda s vezes colocar a sua expresso em uma ferramenta online
como debuggex.com, para ver se a visualizao corresponde sua inteno inicial, e rapidamente ver como ela
responde vrias strings diferentes.

Regexp golf

129
"Golf de Cdigo" um termo usado para o jogo de tentar escrever um programa com o menor nmero de
caracteres possvel. Parecido, o regexp golf a prtica de escrever pequenas expresses regulares para achar
um determinado padro (e apenas esse padro).

Escreva uma expresso regular que testa quando qualquer das sub -strings dadas ocorre em um texto. A
expresso regular dever achar apenas strings contendo uma das sub -strings dadas. No se preocupe com
limites de palavras a no ser que seja explicitamente pedido. Quando a sua expresso funcionar, veja se
consegue faz-la menor.

"car" e "cat"
"pop" e "prop"
"ferret", "ferry", e "ferrari"
Qualquer palavra terminando em "ious"
Um espao em branco seguido por um ponto, vrgula, dois-pontos, ou ponto-e-vrgula
Uma palavra com mais de seis letras
Uma palavra sem a letra "e"

Consulte a tabela no captulo Sumrio para achar algo rapidamente. Teste cada soluo encontrada com alguns
testes com strings.

// Fill in the regular expressions

verify(/.../,
["my car", "bad cats"],
["camper", "high art"]);

verify(/.../,
["pop culture", "mad props"],
["plop"]);

verify(/.../,
["ferret", "ferry", "ferrari"],
["ferrum", "transfer A"]);

verify(/.../,
["how delicious", "spacious room"],
["ruinous", "consciousness"]);

verify(/.../,
["bad punctuation ."],
["escape the dot"]);

verify(/.../,
["hottentottententen"],
["no", "hotten totten tenten"]);

verify(/.../,
["red platypus", "wobbling nest"],
["earth bed", "learning ape"]);

function verify(regexp, yes, no) {


// Ignore unfinished tests
if (regexp.source == "...") return;
yes.forEach(function(s) {
if (!regexp.test(s))
console.log("Failure to match '" + s + "'");
});
no.forEach(function(s) {
if (regexp.test(s))
console.log("Unexpected match for '" + s + "'");
});
}

130
Estilo de aspas
Imagine que voc escreveu um texto e usou aspas simples por toda parte. Agora voc deseja substituir todas que
realmente possuem algum texto com aspas duplas, mas no as usadas em contraes de texto com _aren't).

Pense em um padro que faa distino entre esses dois usos de aspas e faa uma chamada que substitua
apenas nos lugares apropriados.

var text = "'I'm the cook,' he said, 'it's my job.'";


// Altere esta chamada
console.log(text.replace(/A/, "B"));
// "I'm the cook," he said, "it's my job."

Dicas

A soluo mais bvia substituir apenas as aspas que no esto cercadas de caracteres de palavra. A primeira
expresso vem mente /\W'\W/, mas preciso cuidado para lidar com o incio da string corretamente. Isso pode
ser feito usando os marcadores "" e "$", como em /(\W|^)'(\W|$)/.

Novamente nmeros
Sries de dgitos podem ser usados pela agradvel expresso regular /\d+/.

Escreva uma expresso que encontre (apenas) nmeros no estilo JavaScript. Isso significa que precisa suportar
um sinal de menor ou maior, opcional, na frente do nmero, um ponto decimal e a notao exponencial 5e-3 ou
1E10, novamente com o sinal opcional na frente dele.

// Preencha esta expresso regular


var number = /^...$/;

// Tests:
["1", "-1", "+15", "1.55", ".5", "5.", "1.3e2", "1E-4",
"1e+12"].forEach(function(s) {
if (!number.test(s))
console.log("Falhou em achar '" + s + "'");
});
["1a", "+-1", "1.2.3", "1+1", "1e4.5", ".5.", "1f5",
"."].forEach(function(s) {
if (number.test(s))
console.log("Aceitou erroneamente '" + s + "'");
});

Dicas

Primeiro, no esquea da barra invertida em frente ao ponto.

Achar o sinal opcional na frente do nmero, como na frente do exponencial, pode ser feito com [+-]? ou (+|-|) (mais,
menos ou nada).

A parte mais complicada deste exerccio provavelmente a dificuldade de achar "5." e ".5" sem achar tambm o ".".
Para isso, achamos que a melhor soluo usar o operador "|" para separar os dois casos, um ou mais dgitos
opcionalmente seguidos por um ponto e zero ou mais dgitos, ou um ponto seguido por um ou mais dgitos.

Finalmente, fazer o "e" case-insensitive, ou adicional a opo "i" expresso regular ou usar "[eE] ".

131
Captulo 10

Mdulos
Um programador iniciante escreve seus programas como uma formiga constri seu formigueiro, um
pedao de cada vez, sem pensar na estrutura maior. Seus programas iro parecer como areia solta. Eles
podem durar um tempo, mas se crescem demais, desmoronam.

Percebendo esse problema, o programador comear a gastar muito tempo pensando sobre a estrutura.
Seus programas sero rigidamente estruturados, como esculturas em pedra. Eles so slidos, mas
quando precisam mudar, devem ser quebrados.

O programador experiente sabe quando aplicar uma estrutura e quando deixar as coisas mais simples.
Seus programas so como argila, slidos mas ainda maleveis.

Master Yuan-Ma, The Book of Programming

Todo programa possui uma forma. Em menor escala essa forma determinada pela diviso em funes e os
blocos dentro destas funes. Programadores tm muita liberdade na forma que do aos seus programas.
determinado mais pelo bom (ou mau) gosto, do que pela funcionalidade planejada.

Quando olhamos um programa grande em seu todo, funes individuais comeam a se misturar e seria bom
possuir uma unidade maior de organizao.

Mdulos dividem programas em blocos de cdigo, que por algum critrio pertencem a uma mesma unidade. Este
captulo explora alguns dos benefcios que estes agrupamentos fornecem e mostra algumas tcnicas para
construo de mdulos em JavaScript.

Organizao
Existem algumas razes porque autores dividem seus livros em captulos e sees. Elas facilitam para o leitor
entender como o livro foi feito ou achar uma parte especfica em que est interessado. Elas tambm ajudam o
autor, dando um foco claro para cada seo.

Os benefcios de dividir um programa em vrios arquivos ou mdulos so semelhantes, ajudam as pessoas que
no esto familiarizadas com o cdigo a achar o que elas buscam, e ajudam o programador a colocar coisas
semelhantes juntas.

Alguns programas so organizados seguindo o modelo de um texto tradicional, com uma ordem bem definida
que encoraja o leitor a percorrer o programa, e muito falatrio (comentrios) fornecendo uma descrio coerente
do cdigo. Isso faz o programa muito menos intimidador (ler cdigo desconhecido geralmente intimidador). Mas
existe um lado ruim que a maior quantidade de trabalho a fazer e dificulta um pouco as alteraes, porque os
comentrios tendem a ser mais interligados do que o cdigo em si.

Como regra geral, organizao tem um custo, e nos estgios iniciais do projeto, quando no sabemos com
certeza aonde vamos e que tipo de mdulos o programa precisar. Eu defendo uma estrutura minimalista, com
pouca estrutura. Apenas coloque tudo em um simples arquivo at que o cdigo esteja estabilizado. Dessa
maneira, voc no estar se sobrecarregando pensando em organizao enquanto tem pouca informao, no
perder tempo fazendo e desfazendo coisas, e no ir acidentalmente travar-se em uma estrutura que no serve
realmente para seu programa.

Namespaces

132
A maioria das linguagens modernas de programao tm um nvel de escopo entre "global" (todos podem ver) e
"local" (s esta funo pode ver isto). JavaScript no. Assim, por padro, tudo o que precisa ser visvel fora do
pequeno escopo da funo atual visvel em todos os lugares.

Poluio de Namespace, o problema de um monte de cdigo no relacionado ter que compartilhar um nico
conjunto de nomes de variveis globais, foi mencionado no captulo 4, onde o objeto Math foi dado como um
exemplo de um objeto que age como uma espcie de mdulo por um agrupamento srie de funcionalidades
relacionadas com a matemtica.

Embora JavaScript no possua a criao de mdulos nativamente, objetos podem ser usados para criar sub-
namespaces publicamente acessveis, e funes podem ser usadas para criar um namespace privado dentro de
um mdulo. Vou demonstrar algumas tcnicas que nos permitiro construir mdulos namespace isolados bem
convenientes.

Reuso
Em um projeto "flat" (plano), no claro quais partes do cdigo so necessrias para se usar uma funo em
particular. Se, no meu programa para espionar inimigos (spying on enemies), eu escrever uma funo para ler os
arquivos de configurao, e agora eu uso essa funo novamente em outro projeto, eu devo ir e copiar as partes
do programa antigo que so relevantes para a funcionalidade que eu preciso, e col-las no meu novo programa.
Ento, se eu encontrar um erro nesse cdigo, eu vou consertar isso neste programa que eu estava trabalhando
no momento, e esquecer de tambm consertar no outro programa.

Uma vez que voc tenha muitos pedaos de cdigo compartilhados e duplicados, voc vai se encontrar perdendo
uma grande quantidade de tempo e energia organiz-los e mant-los atualizados.

Quando partes de funcionalidades que so independentes so colocadas em arquivos e mdulos separados,


elas podem ser rastreadas mais facilmente, atualizadas quando uma nova verso for criada, ou at mesmo
compartilhadas, tendo vrias partes do cdigo que desejam us-las carregando o mesmo arquivo.

Essa idea fica ainda mais poderosa quando as relaes entre os mdulos - onde outros mdulos cada mdulo
depende - so explicitamente especificados. Voc pode ento automatizar o processo de instalao e atualizao
de mdulos externos.

E, levando isso ainda mais longe, imagine um servio online que rastreia e distribui centenas de milhares destes
mdulos, permitindo a voc buscar pela funcionalidade que deseja, e, uma vez que voc a encontre, configure-a
no seu projeto para ser baixada automaticamente.

Este servio existe. chamado NPM (npmjs.org). NPM consiste em um banco de dados online de mdulos, e
uma ferramenta para download e atualizao dos mdulos que seu programa depende. Ele cresceu com o
Node.js. o ambiente JavaScript b rowser-less (que no depende do navegador), discutido no captulo 20, mas
tambm pode ser usado quando programando para o navegador.

Desacoplamento
Outro importante papel dos mdulos os de isolar partes de cdigo um do outro, da mesma forma que as
interfaces dos objetos no captulo 6 fazem. Um mdulo bem desenvolvido fornece uma interface para uso de
cdigos externos, e mesmo que o mdulo continue sendo trabalhado (bugs consertados, funcionalidades
adicionadas) a interface existente permanece estvel, assim outro mdulos podem usar uma nova e melhorada
verso sem qualquer alterao neles mesmos.

Note que uma interface estvel no significa que novos elementos no so adicionados. Isso apenas significa
que elementos existentes no sero removidos ou seus significados no sero alterados.

133
Construir a interface de um mdulo que permite que este cresa sem quebras na antiga interface significa
encontrar um balano entre expor a menor quantidade de conceitos internos ao mundo exterior quanto possvel, e
ainda assim criar uma "linguagem" exposta pela interface que seja poderosa e flexvel o suficiente para ser
aplicada em uma vasta variedade de situaes.

Para interfaces que expes um nico e focado conceito, como um arquivo leitor de configurao, isso natural.
Para as outras interfaces, como um componente editor de texto, onde cdigo externo precisa acessar vrios
conceitos diferentes, isso requer cuidado no projeto.

Funes como namespaces


Funes so o nico construtor em JavaScript que criam um novo escopo. Ento se ns desejamos que nossos
mdulos tenham um escopo prprio, teremos que coloc-los em funes de alguma forma.

Considere este mdulo trivial que associa nomes com o nmero dos dias da semana retornado pelo mtodo
getDay de um objeto date.

var names = ["Sunday", "Monday", "Tuesday", "Wednesday",


"Thursday", "Friday", "Saturday"];
function dayName(number) {
return names[number];
}

console.log(dayName(1));
// Monday

A funo dayName parte desta interface, mas a varivel names no. Ns preferimos no deix-la no escopo
global.

Podemos fazer isso:

var dayName = function() {


var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return function(number) {
return names[number];
};
}();

console.log(dayName(3));
// Wednesday

Agora names uma varivel local dentro de uma funo (annima). Esta funo criada e chamada
imediatamente, e seu valor retornado (a funo dayName ) armazenada em uma varivel. Podemos ter pginas e
mais pginas de cdigo nessa funo, criando centenas de variveis locais. Elas sero todas internas ao
mdulo, visveis ao prprio mdulo, mas no visvel a cdigos externos.

Um padro similar usado para isolar inteiramente cdigo do mundo exterior. O mdulo abaixo tem algum efeito,
mas no fornece qualquer valor para outros mdulos usarem.

(function() {
function square(x) { return x * x; }
var hundred = 100;

console.log(square(hundred));
})();
// 10000

134
Este cdigo simplesmente imprime o quadrado de cem (no mundo real, este poderia ser um mdulo que
adiciona um mtodo a algum prototype, ou configura algum widget em uma pgina da web). Ele encapsula seu
cdigo em uma funo para, novamente, prevenir que as variveis que ele usa internamente estejam no escopo
global.

Por que a funo namespace est encapsulada em uma par de parnteses? Isso tem relao com um truque da
sintaxe JavaScript. Se uma expresso comea com a palavra-chave function , ela uma expresso de funo.
Entretanto, se uma declarao inicia com esta palavra-chave, ser uma declarao de funo, que requer um
nome e no pode ser chamada imediatamente. Mesmo que uma declarao comece com uma expresso, a
segunda regra tem precedncia, e se os parnteses extras foram esquecidos no exemplo acima, isso ir produzir
um erro de sintaxe. Voc pode imagin-los como um truco para forar a linguagem a entender que ns queremos
escrever uma expresso.

Objetos como namespaces


Agora imagine que o mdulo dia-da-semana (day-of-the-week) precise fornecer no uma, mas duas funes,
porque ns adicionamos uma funo dayNumber que vai de um nome para um nmero. Ns podemos mais
simplesmente retornar a funo, mas devemos encapsular as duas funes em um objeto.

var weekDay = function() {


var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
}();

console.log(weekDay.name(weekDay.number("Sunday")));
// Sunday

Para mdulos maiores, juntar todos os mdulos exportados em um objeto no fim da funo se torna algo
incmodo, e geralmente requer que faamos algo repetido. Isso pode ser melhorado declarando um objeto,
usualmente nomeado exports , e adicionando propriedades a este objeto sempre que ns definirmos algo que
precise ser exportado. Este objeto pode ento ser retornado, ou aceito como um parmetro armazenado em
algum lugar pelo cdigo exterior ao mdulo.

(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];

exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(window.weekDay = {});

console.log(weekDay.name(weekDay.number("Saturday")));
// Saturday

Removendo do escopo global

135
O padro acima usado normalmente em mdulos JavaScript criados para o navegador. Eles requerem um
simples e conhecido nome global, e encapsular seu cdigo em uma funo para ter seu namespace privado
prprio.

Ainda existe um problema quando mltiplos mdulos reivindicam o mesmo nome, ou quando voc quer, por
qualquer motivo, carregar duas verses do mesmo mdulo de forma conjunta.

Com um pequeno encanamento, ns podemos criar um sistema que permite que aos mdulos requererem
diretamente por interfaces de objetos de outros mdulos que eles precisem de acessar, sem precisarmos usar o
escopo global. Isso resolve os problemas mencionados acima e tem um benefcio adicional de ser explcito sobre
suas dependncias, tornando difcil usar acidentalmente algum mdulo sem declarar que voc precisa dele.

Nosso objetivo uma funo 'require' que, quando dado o nome de um mdulo, vai carregar esse arquivo (do
disco ou da web, dependendo da plataforma que estivermos rodando), e retornar o valor apropriado da interface.

Para isso ns precisamos de pelo menos duas coisas. Primeiramente, ns vamos imaginar que temos uma
funo readFile (que no est presente por padro no JavaScript), que retorna o contedo do arquivo com um
nome fornecido. Existem formas de acessar a web com JavaScript no navegador, e acessar o disco rgido com
outras plataformas JavaScript, mas elas so mais envolvidas. Por agora, ns apenas pretendemos desta simples
funo.

Em segundo lugar, ns precisamos de ser capazes, quando tivermos uma string contendo o cdigo (lida do
arquivo), de realmente executar o cdigo como um programa JavaScript.

Avaliando dados como cdigo


Existem vrias formas de se pegar dados (uma string de cdigo) e rod-los no contexto do programa atual.

A mais bvia maneira o operador padro especial eval , que vai executar a string de cdigo no escopo atual.
Isso usualmente uma ideia muito ruim, porque quebra algumas propriedades que escopos normalmente tem
(ser isolado do mundo externo a mais notvel).

function evalAndReturnX(code) {
eval(code);
return x;
}

console.log(evalAndReturnX("var x = 2"));
// 2

A melhor forma de converter dados dentro do programa usar uma funo construtora. Ela recebe como
argumentos uma lista de nomes de argumentos separados por vrgula, e ento uma string contendo o corpo da
funo.

var plusOne = new Function("n", "return n + 1;");


console.log(plusOne(4));
// 5

Isso precisamente o que precisamos - podemos encapsular o cdigo para um mdulo em uma funo, com
este escopo de funo se tornando nosso escopo de mdulo.

Require

136
Se a nova funo construtora, usada pelo nosso mdulo de carregamento, encapsula o cdigo em uma funo de
qualquer forma, ns podemos omitir a funo namespace encapsuladora atual dos arquivos. Ns tambm vamos
fazer exports um argumento funo mdulo, ento o mdulo no precisar de declarar isso. Isso remove um
monte de barulho suprfluo do nosso mdulo de exemplo:

var names = ["Sunday", "Monday", "Tuesday", "Wednesday",


"Thursday", "Friday", "Saturday"];

exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};

Essa uma implementao mnima de require :

function require(name) {
var code = new Function("exports", readFile(name));
var exports = {};
code(exports);
return exports;
}

console.log(require("weekDay").name(1));
// Monday

Quando usando este sistema, um mdulo tipicamente comea com pequenas declaraes de variveis que
carregam os mdulos que ele precisa.

var weekDay = require("weekDay");


var today = require("today");

console.log(weekDay.name(today.dayNumber()));

A implementao de require acima tem diversos problemas. Primeiro, ela vai carregar e rodar um mdulo todas
as vezes que este for "require-d" (requisitado), ento se diversos mdulos tm a mesma dependncia, ou uma
chamada require colocada dentro de uma funo que vai ser chamada mltiplas vezes, tempo e energia sero
desperdiados.

Isso pode ser resolvido armazenando os mdulos que j tenham sido carregados em um objeto, e simplesmente
retornando o valor existente se eles forem carregados novamente.

O segundo problema que no possvel para um mdulo expor diretamente um valor simples. Por exemplo, um
mdulo pode querer exportar apenas o construtor do tipo do objeto que ele define. Por agora, isso no pode ser
feito, porque require sempre vai usar o objeto exports que ele cria como o valor exportado.

A soluo tradicional para isso fornecer outra varivel, module , que um objeto que tem a propriedade exports .
Essa propriedade inicialmente aponta para o objeto vazio criado por require, mas pode ser sobrescrita com outro
valor para exportar algo a mais.

137
function require(name) {
if (name in require.cache)
return require.cache[name];

var code = new Function("exports, module", readFile(name));


var exports = {}, mod = {exports: exports};
code(exports, mod);

require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);

Agora temos um sistema de mdulo que usa uma simples varivel global ( require ) para permitir que mdulos
encontrem e usem um ao outro sem ter que ir para o escopo global.

Este estilo de sistema de mdulos chamado "Mdulos CommonJS", aps o pseudo-padro que o implementou
pela primeira vez. Ele tambm feito dentro do Node.js. Implementaes reais fazem bem mais do que o
exemplo que eu mostrei. Mais importante, eles tem uma forma muito mais inteligente de ir de um nome de
mdulo para uma parte de cdigo real, permitindo ambos caminhos relativos e nomes de mdulos registrados
"globalmente".

Carregando mdulos lentamente


Embora seja possvel usar a tcnica acima para carregar JavaScript no navegador, isso um pouco complicado. A
razo para isso que ler um arquivo (mdulo) na web muito mais lento que ler este mesmo arquivo do seu
disco rgido. JavaScript no navegador obrigado a se comportar de tal forma que, enquanto um script esteja
rodando, nada mais pode acontecer no site que ele est rodando. Isso significa que se todas as chamadas
require carregarem algo em algum servidor web distante, a pgina vai ficar congelada por um doloroso longo
perodo durante sua inicializao.

Existem maneiras de se trabalhar isso, por exemplo, rodando outro programa (como o Browserify) em seu
programa antes, que ir concatenar todas as dependncias olhando todas as chamadas require , e colocando-
as em juntas em um grande arquivo.

Outra soluo encapsular seu mdulo em uma funo, carregar os mdulos que ela depende em segundo
plano, e apenas rodas essa funo quando todas suas dependncias forem carregadas. Isso o que o sistema
de mdulos AMD ("Asynchronous Module Definition") faz.

Nosso programa trivial com dependncias, em AMD, se parece com isso:

define(["weekDay", "today"], function(weekDay, today) {


console.log(weekDay.name(today.dayNumber()));
});

A funo define o conceito central nessa abordagem. Ela primeiro recebe um array com nomes de mdulos, e
ento uma funo que recebe um argumento para cada dependncia. Ela vai carregar as dependncias (se elas
ainda no tiverem sido carregadas) em segundo plano, permitindo que a pgina continue a trabalhar em quanto
est esperando. Uma vez que todas as dependncias estejam carregadas, ela vai carregar a funo que foi
passada, com as interfaces das dependncias como argumentos.

Os mdulos que so carregados dessa forma devem conter uma chamada a define . O valor usado para sua
interface qualquer valor retornado pela funo que o segundo argumento passado nessa chamada. Aqui est
o mdulo weekDay de novo.

138
define([], function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
});

Para mostrar uma simples implementao de define , vamos supor que tambm temos uma funo
backgroundReadFile , que pega o nome do arquivo e uma funo, e vai chamar a funo com o contedo do arquivo
assim que este for carregado.

function define(depNames, moduleFunction) {


var deps = [], myMod = define.currentModule;

depNames.forEach(function(name) {
if (name in define.cache) {
var depMod = define.cache[name];
} else {
var depMod = {exports: null,
loaded: false,
onLoad: []};
define.cache[name] = depMod;
backgroundReadFile(name, function(code) {
define.currentModule = depMod;
new Function("", code)();
});
}
deps.push(depMod);
if (!depMod.loaded)
depMod.onLoad.push(runIfDepsLoaded);
});

function runIfDepsLoaded() {
if (!deps.every(function(m) { return m.loaded; }))
return;

var args = deps.map(function(m) { return m.exports; });


var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.every(function(f) { f(); });
}
}
runIfDepsLoaded();
}
define.cache = Object.create(null);

Isso muito mais difcil de seguir que a funo require . Sua execuo no segue um caminho simples e
previsvel. Ao invs disso, mltiplas operaes so definidas para acontecerem em algum tempo no
especificado no futuro (quando o mdulo for carregado), que obscurece a forma que o cdigo executado.

O maior problema que este cdigo lida coletar os valores das interfaces das dependncias do mdulo. Para
rastrear os mdulos, e seus estados, um objeto criado para cada mdulo que carregado por define . Este
objeto armazena o valor exportado pelo mdulo, um booleano indicando se o mdulo j foi completamente
carregado e um array de funes para ser chamado quando o mdulo tiver sido carregado.

Um cache usado para prevenir o carregamento de mdulos mltiplas vezes, assim como fizemos para o
require . Quando define chamada, ns primeiro construmos um array de mdulos de objetos que
representam as dependncias deste mdulo. Se o nome da dependncia corresponde com o nome de um

139
mdulo cacheado, ns usamos o objeto existente. Caso contrrio, ns criamos um novo objeto (com o valor de
loaded igual a false ) e armazenamos isso em cache. Ns tambm comeamos a carregar o mdulo, usando a
funo backgroundReadFile . Uma vez que o arquivo tenha sido carregado, seu contedo rodado usando o
construtor Function .

assumido que este arquivo tambm contenha uma (nica) chamada a define . A propriedade
define.currentModule usada para informar a esta chamada sobre o mdulo objeto que est sendo carregado
atualmente, dessa forma podemos atualiz-lo umas vez e terminar o carregamento.

Isso manipulado na funo runIfDepsLoaded , que chamada uma vez imediatamente (no caso de no ser
necessrio carregar nenhuma dependncia) e uma vez para cada dependncia que termina seu carregamento.
Quando todas as dependncias esto l, ns chamamos moduleFunction , passando para ela os valores
exportados apropriados. Se existe um mdulo objeto, o valor retornado da funo armazenado, o objeto
marcado como carregado (loaded), e as funes em seu array onLoad so chamadas. Isso vai notificar qualquer
mdulo que esteja esperando que suas dependncias sejam carregadas completamente.

Uma implementao real do AMD , novamente, bem mais inteligente em relao a resoluo dos nomes e suas
URLs, e genericamente mais robusta. O projeto RequireJS (http://requirejs.org) fornece uma implementao
popular deste estilo que carregamento de mdulos.

Projeto de interfaces
Projetar interfaces para mdulos e tipos de objeto um dos aspectos sutis da programao. Qualquer pedao
no trivial de funcionalidade pode ser modelada de formas diferentes. Encontrar um caminho que funciona bem
requer perspiccia e previdncia.

A melhor forma de aprender o valor de um bom projeto de interface usar vrias interfaces, algumas boas,
algumas horrveis. Experincia vai ensinar a voc o que funciona e o que no funciona. Nunca assuma que uma
interface dolorosa de se usar "da forma que ela deve ser". Conserte-a, ou encapsule-a em uma nova interface
de forma que funcione melhor para voc.

Previsilibidade
Se programadores podem prever a forma que a interface vai funcionar, eles (ou voc) no vo ser desviados
frequentemente pela necessidade de checar como trabalhar com esta interface. Portanto, tente seguir convenes
(por exemplo, quando se trata da capitalizao de nomes). Quando existe outro mdulo ou parte do ambiente
padro JavaScript que faz algo similar ao que voc est implementando, uma boa ideia fazer sua interface se
assemelhar a interface existente. Dessa forma, as pessoas que conhecem a interface existente vo se sentir em
casa.

Outra rea que previsibilidade importante no comportamento do seu cdigo. Pode ser tentador "empilhar
inteligncia" com a justificativa que isso torna a interface fcil de ser utilizada. Por exemplo, aceitando todos os
diferentes tipos e combinaes de argumentos, e fazendo "a coisa certa" para todos eles, ou fornecendo dezenas
de diferentes funes especializadas por "convenincia" que fornecem pequenas alteraes do sabor da
funcionalidade do seu mdulo. Isso pode tornar o cdigo construdo em cima da sua interface um pouco menor,
mas isso vai tambm tornar o cdigo muito mais difcil para as pessoas manterem um modelo mental do
comportamento do mdulo em suas cabeas.

"Componibilidade"
Em suas interfaces, tente usar as estruturas de dados mais simples que funcionem e crie funes que faam
algo simples e claro - sempre que possvel, crie funes puras (veja captulo 3).

140
Por exemplo, no comum para mdulos fornecerem suas prprias colees de objetos similares a arrays, com
sua prpria interface para contar e extrair elementos. Tais objetos no tero os mtodos map e forEach ,e
qualquer funo existente que espere um array real no ser capaz de trabalhar com estas colees. Este um
exemplo de componibilidade (composab ility) ruim - o mdulo no pode ser facilmente composto com outro
cdigo.

Outro exemplo seria um mdulo verificao ortogrfica de texto, que podemos necessitar se quisermos escrever
um editor de texto. O verificador pode ser construdo para funcionar diretamente em qualquer tipo complexo de
estrutura de dados que o editor usa, e chamar funes internas diretamente no editor para que o usurio possa
escolher entre as sugestes de ortografia. Se formos por esse caminho, o mdulo no poder ser usado com
outros programas. De outra forma, se ns definirmos a interface do verificador ortogrfico para que possamos
passar simples strings e retornar a possvel localizao do erro, juntamente com um array de correes
sugeridas, ns teremos uma interface que pode ser composta com outros sistemas, porque strings e arrays
estaro sempre disponveis.

Interfaces em camadas
Quando projetando uma interface para uma complexa parte de funcionalidade - digo, enviar email - voc
geralmente se depara com um dilema. Em uma mo, voc no quer sobrecarregar o usurio da sua interface
com detalhes. Ele no deve estudar sua interface por 20 minutos antes de ser capaz de enviar um email. Na outra
mo, voc no quer esconder todos os detalhes - quando pessoas precisam fazer coisas complicadas com seu
mdulo, eles tambm devem ser capazes.

Normalmente a soluo oferecer duas interfaces: uma de "baixo nvel" detalhada para situaes complexas e
uma de "alto nvel" simples para uso rotineiro. A segunda pode ser construda de forma simples utilizando as
ferramentas fornecidas pela primeira camada. No mdulo de email, a interface de alto nvel pode simplesmente
ser uma funo que recebe uma mensagem, um endereo de remetente, um endereo de destinatrio e envia o
email. A interface de baixo nvel deve permitir um controle completo sobre os cabealhos do email, anexos, envio
de email HTML, e por ai vai.

Resumo
Mdulos fornecem estrutura para programas grandes, separando o cdigo em diferentes arquivos e namespaces.
Dando a estes mdulos interfaces bem definidas os tornam fceis de se utilizar, reusando-os em contextos
diferentes, e continuando os usando mesmo quando evoluem.

Mesmo que a linguagem JavaScript no auxilie muito quando se trata de mdulos, as flexveis funes e objetos
que ela fornece fazem que seja possvel definir teis sistemas de mdulo. Escopo de funo pode ser utilizado
como namespace interno para o mdulo, e objetos podem ser usados para armazenar blocos de valores
exportados.

Existem duas abordagens populares para tais mdulos. Uma chamada "Mdulos CommonJS", e funciona em
torno da funo require que busca um mdulo pelo seu nome e retorna sua interface. A outra abordagem
chamada "AMD", e usa a funo assncrona define que recebe um array de nome de mdulos e uma funo, e
depois de carregar os mdulos, roda a funo com suas interfaces e argumentos.

Exerccios
Nomes dos meses

141
Escreva um simples mdulo similar ao mdulo weekDay , que pode converter os nmeros dos meses (zero-
b ased, assim como o tipo Date ) para nomes, e nomes para nmeros. D a este mdulo seu prprio namespace,
pois ele vai precisar de um array interno com o nome dos meses, mas use JavaScript puro, sem nenhum sistema
de carregamento de mdulos.

// Your code here.

console.log(month.name(2));
// March
console.log(month.number("November"));
// 10

Ele vai seguir o mdulo weekDay praticamente por inteiro. Uma funo annima, chamada imediatamente,
encapsula a varivel que contm o array de nomes, assim como as duas funes que precisam ser exportadas.
As funes so colocadas em um objeto. A interface de objeto retornada armazenada na varivel month .

Dependncias circulares
Um assunto complicado na gesto de dependncias o de dependncias circulares, onde mdulo A depende do
mdulo B, e B tambm depende do mdulo A. Muitos sistemas simplesmente probem isso. CommonJS permite
uma forma limitada disso, onde isso funciona se os mdulos no trocarem seus objetos exportados por padro
com outro valor, e somente comeam a acessar a interface um do outro aps terem finalizados seus
carregamentos.

Voc pode pensar em algo que d suporte para essa funcionalidade ser implementada? Olhe anteriormente a
definio de require , e considere o qu voc deve fazer para permitir isso.

O segredo adicionar o objeto exports criado por um mdulo para requisitar o cache antes de rodar o mdulo de
fato. Isso significa que o mdulo no teria tido ainda uma chance de sobrescrever module.exports , ento no
sabemos se ele deseja exportar outro valor. Depois de carregar, o objeto cache sobrescrito com module.exports ,
que pode ser um valor diferente.

Mas se, no curso de carregar o mdulo, um segundo mdulo carregado e solicita o primeiro mdulo, seu objeto
exports padro, ainda vazio at este ponto, vai estar no cache, e o segundo mdulo vai receber uma referncia
dele. Se ele no tentar fazer nada com o objeto at que o segundo mdulo tenha terminado seu carregamento, as
coisas vo funcionar.

Um retorno a vida eletrnica


Esperando que o captulo 7 ainda esteja um pouco fresco em sua mente, pense novamente no sistema projetado
neste captulo e elabore um separao em mdulo para o cdigo. Para refrescar sua memria, essas so as
funes e tipos definidos naquele captulo, em ordem de apario.

Point
Grid
directions
randomElement
BouncingCritter
elementFromChar
World
charFromElement
Wall
View
directionNames

142
WallFollower
dirPlus
LifeLikeWorld
Plant
PlantEater
SmartPlantEater
Tiger

No exagere em criar muitos mdulos. Um livro que comea um novo captulo para cada pgina provavelmente
vai te deixar nervoso, por todo espao perdido com os ttulos. De forma similar, ter que abrir dez arquivos para ler
um pequeno projeto no til. Vise por trs ou cinco mdulos.

Voc pode escolher ter algumas funes internas ao mdulo, e ento inacessveis a outros mdulos.

No existe uma nica soluo correta aqui. Organizao de mdulos meramente uma questo de gosto.

Aqui est o que eu fiz. Coloquei parenteses em torno de funes internas.

Module "grid"
Point
Grid
directions
Module "world"
(randomElement)
(elementFromChar)
(charFromElement)
View
World
LifeLikeWorld
directions [re-exported]
Module "simple_ecosystem"
(randomElement) [duplicated]
(directionNames)
(dirPlus)
Wall
BouncingCritter
WallFollower
Module "ecosystem"
Wall [duplicated]
Plant
PlantEater
SmartPlantEater
Tiger

Eu reexportei o array directions do mdulo grid para world , ento mdulos criados com eles ( ecosystems ) no
precisam de saber ou se preocupar da existncia do mdulo grid .

Eu tambm dupliquei dois valores minsculos e genricos ( randomElement e Wall ) pois eles so usados como
detalhes internos em contextos diferentes, e no pertencem nas interfaces destes mdulos.

143
Linguagem de programao
"O avaliador que determina qual o significado da expresses em uma linguagem de programao apenas
mais um programa."

Hal Abelson e Gerald Sussman, Estrutura e Interpretao de Programas de Computador

"Quando um estudante perguntou ao mestre sobre a natureza do ciclo de dados e controle, Yuan-Ma
respondeu: 'Pense em um compilador compilando a si mesmo.'"

Mestre Yuan-Ma, O Livro de Programao

Construir sua prpria linguagem de programao surpreendentemente fcil(desde que voc no seja
ambicioso demais) e bastante esclarecedor.

A principal coisa que eu quero mostrar neste captulo que no h mgica envolvida na construo de sua prpria
linguagem. Eu sempre senti que algumas invenes humanas eram imensamente inteligentes e complicadas
que eu nunca seria capaz de compreend-las. Mas com um pouco de leitura e ajustes; tais coisas muitas vezes
acabam por ser muito simples.

Iremos construir uma linguagem de programao chamada Egg. Vai ser uma pequena e simples linguagem mas
poderosa o suficiente para expressar qualquer computao que voc possa imaginar. Ela tambm permite
abstrao simples baseadas em funes.

Parsing
A parte imediatamente mais visvel de uma linguagem de programao sua sintaxe ou notao. Um analisador
um programa que l um pedao de texto e produz uma estrutura de dados que refletem a estrutura do programa
contida nesse texto. Se o texto no faz um programa vlido o analisador deve reclamar e apontar o erro.

Nossa linguagem ter uma sintaxe simples e uniforme. Tudo em Egg uma expresso. Uma expresso pode ser
uma varivel, um Number , uma String , ou uma aplicao. As aplicaes so usados para chamadas de funo,
mas tambm para construes como if ou while .

Para manter o analisador simples, String em Egg no suportam qualquer coisa como escapes e uma sequncia
simplesmente de caracteres que no so aspas duplas envolvidas em aspas duplas. Um nmero uma
sequncia de dgitos. Os nomes das variveis podem consistir de qualquer caractere que no seja um espao
em branco e no tem um significado especial na sintaxe.

As aplicao ser escrita da forma como em JavaScript; colocando parnteses aps uma expresso e com uma
srie de argumentos entre esses parnteses separados por vrgulas.

do(define(x, 10),
if(>(x, 5)),
print("large"),
print("small"))

A uniformidade da lnguagem Egg significa coisas que so operadores de JavaScript(como >) nesta lnguagem
ser apenas variveis normais aplicado apenas como outras funes. E uma vez que a sintaxe tambm no tem
o conceito de um bloco precisamos construir um representador fazendo vrias coisas em seqncia.

144
A estrutura de dados que o analisador ir usar para descrever um programa ser composto de objetos de
expresses cada um dos quais tem uma propriedade de tipo que indica o tipo de expresso que ; e as outras
propriedades para descreverem o seu contedo.

Expresses do tipo "value" representam Strings , literais ou Numbers . O valor da propriedade contm o valor da
cadeia ou o nmero que ele representa. Expresses do tipo "word" so usados para identificadores(nomes).
Esses objetos tm uma propriedade que contm o nome do identificador de uma String . Por fim as expresses
"apply" representam algo que uma aplicao. Eles tm uma propriedade de operador que se refere
expresso que so aplicavis e tm uma propriedade de args que refere-se a um conjunto de expresses de
argumento.

A parte >(x, 5) do programa anterior seria representado assim:

{
type: "apply",
operator: {type: "word", name: ">"},
args: [
{type: "word", name: "x"},
{type: "value", value: 5}
]
}

Essa estrutura de dados chamado de rvore de sintaxe. Se voc imaginar os objetos como pontos de ligaes
entre eles e com linhas entre esses pontos, ele tem uma forma treelike. O fato de que as expresses contem
outras expresses que por sua vez pode conter mais expresses semelhante maneira como dividir ramos e
dividir novamente.

Compare isso com o analisador que escrevemos para o formato de arquivo de configurao no captulo 9 que
tinha uma estrutura simples: dividir a entrada em linhas e tratar essas linhas uma de cada vez. Havia apenas
algumas formas simples de mostrar que uma linha foi permitida.

Aqui temos de encontrar uma abordagem diferente. As expresses no so separados em linhas e elas tm uma
estrutura recursiva. Expresses aplicadas contm outras expresses.

Felizmente, este problema pode ser resolvido com elegncia escrevendo uma funo analisadora que recursiva
de uma forma que reflete a natureza recursiva da linguagem.

Ns definimos uma funo parseExpression que recebe uma string como entrada e retorna um objeto que contm
a estrutura de dados para a expresso no incio da cadeia, depois feito a juno com a parte da cadeia da
esquerda para analisar esta expresso. Ao analisar essa subexpressions (o argumento para um aplicativo, por
exemplo) esta funo pode ser chamado novamente dando origem a expresso argumento bem como o texto nos
mostra. Este texto pode por sua vez contm mais argumentos ou pode ser o parntese de fechamento, que da
termino a lista de argumentos.

Esta a primeira parte do analisador:

145
function parseExpression(program) {
program = skipSpace(program);
var match, expr;
if (match = /^"([^"]*)"/.exec(program))
expr = {type: "value", value: match[1]};
else if (match = /^\d+\b/.exec(program))
expr = {type: "value", value: Number(match[0])};
else if (match = /^[^\s(),"]+/.exec(program))
expr = {type: "word", name: match[0]};
else
throw new SyntaxError("Unexpected syntax: " + program);

return parseApply(expr, program.slice(match[0].length));


}

function skipSpace(string) {
var first = string.search(/\S/);
if (first == -1) return "";
return string.slice(first);
}

Temos que remover os espaos em brancos repetidos no incio de qualquer seqncia do programa pois o Egg
permite qualquer quantidade de espao em branco entre os seus elementos inseridos. Quem tem essa
funcionalidade a da funco skipSpace .

Depois de pular qualquer espao esquerda parseExpression usa trs expresses regulares para detectar os trs
elementos simples(atmicas) que Egg suporta: String , Number e words . O analisador constri um tipo diferente
de estrutura de dados dependendo de sua correspondencia. Se a entrada no coincide com uma destas trs
formas no ser considerado uma expresso vlida e o analisador gerara um erro. SyntaxError um tipo de erro
padro de objeto que gerado quando feita uma tentativa de executar um programa em JavaScript invlido.

Podemos cortar algumas partes que ns comparamos a partir da seqncia e passar isso juntamente com o
objeto para a expresso do parseApply que ira verificar se a expresso uma aplicao. Se assim for ele analisa
uma lista de argumentos entre parnteses.

function parseApply(expr, program) {


program = skipSpace(program);
if (program[0] != "()"
return {expr: expr, rest: program};

program = skipSpace(program.slice(1));
expr = {type: "apply", operator: expr, args: []};
while (program[0] != ")") {
var arg = parseExpression(program);
expr.args.push(arg.expr);
program = skipSpace(arg.rest);
if (program[0] == ",")
program = skipSpace(program.slice(1));
else if (program[0] != ")")
throw new SyntaxError("Expected ',' or ')'");
}
return parseApply(expr, program.slice(1));
}

Se o prximo caracter no programa no um parntese de abertura, este no aplicvel, e parseApply

simplesmente retorna que a expresso foi proferida.

Caso contrrio ele ignora o parntese de abertura e cria o objeto na rvore de sintaxe para essa expresso
aplicvel. Em seguida ele chama recursivamente parseExpression para analisar cada argumento at o parntese
de fechamento ser encontrado. A recursividade indireta atravs da funo parseApply e parseExpression

chamando uns aos outros.

146
Uma expresso de aplicao pode ser aplicado em si prpria(como em multiplier(2)(1) ); parseApply deve
analisar um pedido depois chamar-se novamente para verificar se existe outro par de parnteses.

Isso tudo que precisamos para o analisador do Egg. Ns vamos envolv-lo em uma funo de anlise
conveniente que verifica se ele chegou ao fim da cadeia de entrada aps o anlise da expresso(um programa de
Egg uma nica expresso) e que nos d estrutura de dados do programa.

function parse(program) {
var result = parseExpression(program);
if (skipSpace(result.rest).length > 0)
throw new SyntaxError("Unexpected text after program");
return result.expr;
}

console.log(parse("+(a, 10)"));
// {type: "apply",
// operator: {type: "word", name: "+"},
// args: [{type: "word", name: "a"},
// {type: "value", value: 10}]}

Funcionou! Ele no nos d informao muito til quando h falhas e no armazena a linha e coluna na qual cada
expresso comea, o que pode ser til ao relatar erros mais tarde mas bom o suficiente para nossos
propsitos.

O avaliador
O que podemos fazer com uma rvore de sintaxe de um programa? Execut-lo claro! E isso que o avaliador
faz. Voc entrega-lhe uma rvore de sintaxe e um objeto do environment que associa nomes com os valores, e ele
ir avaliar a expresso que a rvore representa e retornar o valor que esta produz.

function evaluate(expr, env) {


switch(expr.type) {
case "value":
return expr.value;

case "word":
if (expr.name in env)
return env[expr.name];
else
throw new ReferenceError("Undefined variable: " +
expr.name);
case "apply":
if (expr.operator.type == "word" &&
expr.operator.name in specialForms)
return specialForms[expr.operator.name](expr.args,
env);
var op = evaluate(expr.operator, env);
if (typeof op != "function")
throw new TypeError("Applying a non-function.");
return op.apply(null, expr.args.map(function(arg) {
return evaluate(arg, env);
}));
}
}

var specialForms = Object.create(null);

O avaliador possui cdigo para cada um dos tipos de expresso. A expresso de valor literal simplesmente
produz o seu valor(por exemplo, a expresso 100 apenas avalia para o nmero 100). Para uma varivel preciso
verificar se ele est realmente definido no environment atual , se estiver, buscar o valor da varivel.

147
As aplicaes so mais envolvidas. Se eles so de uma forma especial, ns no avaliamos nada e
simplesmente passamos as expresses como argumento junto com o environment para a funo que lida com
essa forma. Se for uma chamada normal ns avaliamos o operador verificamos se ele uma funo e
chamamos com o resultado da avaliao dos argumentos.

Iremos usar os valores de uma funo simples em JavaScript para representar os valores de funo em Egg.
Voltaremos a falar sobre isso mais tarde quando o specialForm chamado fun estiver definido.

A estrutura recursiva de um avaliador se assemelha estrutura de um analisador. Ambos espelham a estrutura


da prpria linguagem. Alm disso, seria possvel integrar o analisador com o avaliador e avaliar durante a anlise,
mas dividindo-se desta forma torna o programa mais legvel.

Isso tudo que precisamos para interpretar Egg. simples assim. Mas sem definir algumas formas especiais e
adicionar alguns valores teis para o environment voc no pode fazer nada com essa linguagem ainda.

Formas especiais
O objecto specialForms utilizado para definir sintaxe especial em Egg. Ele associa palavras com funes que
avaliam essas formas especiais. Atualmente ele est vazio. Vamos adicionar algumas formas.

specialForms["if"] = function(args, env) {


if (args.length != 3)
throw new SyntaxError("Bad number of args to if");

if (evaluate(args[0], env) !== false)


return evaluate(args[1], env);
else
return evaluate(args[2], env);
};

Egg - if espera exatamente trs argumentos. Ele ir avaliar o primeiro, se o resultado no o valor falso ele ir
avaliar a segunda. Caso contrrio a terceira fica avaliada. Esta a forma mais semelhante ao ternrio do
JavaScript ?: estes operadores tem o mesmo significado de if/else em JavaScript. Isso uma expresso e
no uma indicao que produz um valor, ou seja, o resultado do segundo ou terceiro argumento.

Egg difere de JavaScript na forma de como ele lida com o valor de um condio como o valor do if . Ele no vai
tratar as coisas como zero ou cadeia vazia como falsa, somente valorores precisos so falsos.

A razo especial que ns preciso representar o if como uma forma especial, ao invs de uma funo regular
onde todos os argumentos para funes so avaliadas antes que a funo seja chamada, ao passo que se deve
avaliar apenas seu segundo ou terceiro argumento, dependendo do valor do primeiro.

A forma while semelhante.

specialForms["while"] = function(args, env) {


if (args.length != 2)
throw new SyntaxError("Bad number of args to while");

while (evaluate(args[0], env) !== false)


evaluate(args[1], env);

// Since undefined does not exist in Egg, we return false,


// for lack of a meaningful result.
return false;
};

Outro bloco na construo bsico fazer que executa todos os seus argumentos de cima para baixo. O seu valor
o valor produzido pelo ltimo argumento.

148
specialForms["do"] = function(args, env) {
var value = false;
args.forEach(function(arg) {
value = evaluate(arg, env);
});
return value;
};

Para ser capaz de criar variveis e dar-lhes novos valores, vamos criar um specialForms chamado define . Ele
espera uma palavra como primeiro argumento de uma expresso que produz o valor a ser atribudo a essa
palavra que sera seu segundo argumento. Vamos definir sendo tudo uma expresso e ela deve retornar um valor.
Vamos faz-lo retornar o valor que foi atribudo(igual ao operador = de JavaScript).

specialForms["define"] = function(args, env) {


if (args.length != 2 || args[0].type != "word")
throw new SyntaxError("Bad use of define");
var value = evaluate(args[1], env);
env[args[0].name] = value;
return value;
};

Ambiente
O environment aceita avaliar um objeto com propriedades cujos nomes correspondem aos nomes de variveis e
cujos valores correspondem aos valores dessas variveis. Vamos definir um objeto no environment para
representar o escopo global.

Para ser capaz de usar if que acabamos de definir teremos de ter acesso aos valores booleanos . Uma vez que
existem apenas dois valores booleanos ns no precisamos de sintaxe especial para eles. Ns simplesmente
vamos ligar duas variveis em topEnv para os valores verdadeiros e falsos e dai ento us-los.

var topEnv = Object.create(null);

topEnv["true"] = true;
topEnv["false"] = false;

Agora podemos avaliar uma expresso simples que nega um valor booleano .

var prog = parse("if(true, false, true)");


console.log(evaluate(prog, topEnv));
// false

Para suprir os operadores aritmticos e comparaes bsicas vamos adicionar alguns valores para funo de
environment . No interesse de manter um cdigo pequeno vamos utilizar uma nova funo para sintetizar um monte
de funes de operador em um loop ao invz de definir todos eles individualmente.

["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {


topEnv[op] = new Function("a, b", "return a " + op + " b;");
});

muito til fazer uma maneira para que valores de sada sejam vizualidos, por isso vamos colocar alguns
console.log na funo e executa-lo para imprimir.

149
topEnv["print"] = function(value) {
console.log(value);
return value;
};

Isso ja nos proporcionou uma ferramenta elementar e suficiente para escrever programas simples. A seguinte
funo run fornece uma maneira conveniente de escrever e execut-los. Ele cria um enviroment em tempo real,
analisa e avalia as String que damos como um programa nico.

function run() {
var env = Object.create(topEnv);
var program = Array.prototype.slice
.call(arguments, 0).join("\n");
return evaluate(parse(program), env);
}

O uso de Array.prototype.slice.call um truque para transformar um objeto de matriz como argumentos em uma
matriz real; de modo que podemos chamar e juntar cada pedao. No exemplo abaixo iremos percorrer todos os
argumentos dados e tratar cada linha do programa.

run("do(define(total, 0),",
" define(count, 1),",
" while(<(count, 11),",
" do(define(total, +(total, count)),",
" define(count, +(count, 1)))),",
" print(total))");
// 55

Este o programa que j vimos vrias vezes antes que calcula a soma dos nmeros de 1 a 10 escrito em Egg.
evidente que mais feio do que um programa em JavaScript, mas no to ruim para uma linguagem
implementada em menos de 150 linhas de cdigo.

Funes
A linguagem de programao sem funes uma linguagem de programao pobre.

Felizmente, no difcil para adicionar fun a nossa linguagem, que vai tratar todos os argumentos antes do
ltimo como nomes de argumentos da funo e seu ltimo argumento como corpo da funo.

150
specialForms["fun"] = function(args, env) {
if (!args.length)
throw new SyntaxError("Functions need a body");
function name(expr) {
if (expr.type != "word")
throw new SyntaxError("Arg names must be words");
return expr.name;
}
var argNames = args.slice(0, args.length - 1).map(name);
var body = args[args.length - 1];

return function() {
if (arguments.length != argNames.length)
throw new TypeError("Wrong number of arguments");
var localEnv = Object.create(env);
for (var i = 0; i < arguments.length; i++)
localEnv[argNames[i]] = arguments[i];
return evaluate(body, localEnv);
};
};

Funes em Egg tem seu prprio enviroment local assim como em JavaScript. Usamos Object.create para fazer
um novo objeto que tem acesso s variveis do ambiente externo( prototype ) mas que tambm pode conter novas
variveis sem modificar esse escopo exterior.

A funo criada pela especialForm fun cria em ambito local e adiciona as variveis de argumento para isso. Em
seguida ele avalia o corpo da funo neste ambiente e retorna o resultado.

run("do(define(plusOne, fun(a, +(a, 1))),",


" print(plusOne(10)))");
// 11

run("do(define(pow, fun(base, exp,",


" if(==(exp, 0),",
" 1,",
" *(base, pow(base, -(exp, 1)))))),",
" print(pow(2, 10)))");
// 1024

Compilao
O que ns construmos foi um intrprete. Durante a avaliao ele age diretamente sobre a representao do
programa produzido pelo analisador.

A compilao o processo de adicionar mais um passo entre a anlise e a execuo de um programa; que
transforma o programa em algo que possa ser avaliado de forma mais eficiente fazendo o trabalho tanto quanto
possvel com antecedncia. Por exemplo, em lnguas bem desenhadas, bvio para cada uso de uma varivel
ele verifica qual esta se referindo sem realmente executar o programa. Isso pode ser usado para evitar a procura
de uma varivel pelo nome sempre que acessado ou buscado diretamente de algum local pr-determinado da
memria.

Tradicionalmente, compilao envolve a converso do programa para cdigo de mquina no formato raw que o
processador de um computador pode executar. Qualquer processo que converte um programa de uma
representao diferente pode ser encarado como compilao.

Seria possvel escrever uma estratgia de avaliao alternativa para Egg, aquele que primeiro converte o
programa para um programa JavaScript utilizando a nova funo para chamar o compilador JavaScript, e em
seguida executar o resultado. Sendo feito assim Egg executaria muito mais rpido e continuaria bastante simples
de implementar.

151
Se voc est interessado e disposto neste assunto gaste algum tempo com isso, encorajo-vos a tentar
implementar um compilador nos exerccios.

Cheating
Quando definimos if e while , voc provavelmente percebeu que eles eram invlucros triviais em torno do
prprio JavaScript. Da mesma forma, os valores em Egg so antigos valores de JavaScript.

Se voc comparar a execuo de Egg que foi construda em alto nvel utilizando a ajuda de JavaScript com a
quantidade de trabalho e complexidade necessrios para construir uma linguagem de programao utilizando
diretamente a funcionalidade raw fornecido por uma mquina essa diferena enorme. Independentemente
disso este apenas um exemplo; espero ter lhe dado uma impresso de que maneira as linguagens de
programao trabalham.

E quando se trata de conseguir fazer algo, o cheating o jeito mais eficaz de fazer tudo sozinho. Embora a
linguagem que brincamos neste captulo no faz nada de melhor que o JavaScript possui, existem situaes em
que a escrever pequenas lnguas ajuda no entendimento verdadeiro do trabalho.

Essa lngua no possui semelhanas com uma linguagem tpica de programao. Se o JavaScript no vm
equipado com expresses regulares voc pode escrever seu prprio analisador e avaliador para tal sub
linguagem.

Ou imagine que voc est construindo um dinossauro robtico gigante e precisa programar o seu
comportamento. JavaScript pode no ser a forma mais eficaz de fazer isso. Voc pode optar por uma linguagem
que se parece com isso:

behavior walk
perform when
destination ahead
actions
move left-foot
move right-foot

behavior attack
perform when
Godzilla in-view
actions
fire laser-eyes
launch arm-rockets

Isto o que geralmente chamado de linguagem de domnio especfica, uma linguagem adaptada para
expressar um estreito conhecimento de um domnio. Essa linguagem pode ser mais expressiva do que uma
linguagem de um propsito geral. Isto porque ela projetada para expressar exatamente as coisas que precisam
serem expressadas no seu domnio e nada mais.

Exerccios

Arrays
Adicionar suporte para array em Egg construindo as trs funes em topEnv do escopo: array(...) vai ser a
construo de uma matriz contendo os argumentos como valores, length(array) para obter o comprimento de um
array e element(array, n) buscar n elementos de uma matriz.

152
// Modify these definitions...

topEnv["array"] = "...";

topEnv["length"] = "...";

topEnv["element"] = "...";

run("do(define(sum, fun(array,",
" do(define(i, 0),",
" define(sum, 0),",
" while(<(i, length(array)),",
" do(define(sum, +(sum, element(array, i))),",
" define(i, +(i, 1)))),",
" sum))),",
" print(sum(array(1, 2, 3))))");
// 6

Dica:

A maneira mais fcil de fazer isso representar as matrizes de Egg atravz de matrizes do JavaScript.

Os valores adicionados ao enviroment no topEnv deve ser uma funes. Array.prototype.slice ; pode ser utilizado
para converter um array em um object de argumentos numa matriz regular.

Resoluo

Closures
A maneira que definimos o fun permitido que as funes em Egg se chamem em ambiente circundante,
permitindo o corpo da funo utilizar valores locais que eram visveis no momento que a funo foi definida, assim
como as funes em JavaScript fazem.

O programa a seguir ilustra isso: funo f retorna uma funo que adiciona o seu argumento ao argumento de f,
o que significa que ele precisa de acesso ao escopo local dentro de f para ser capaz de utilizar a varivel.

run("do(define(f, fun(a, fun(b, +(a, b)))),",


"print(f(4)(5)))");
// 9

Volte para a definio da forma fun e explique qual o mecanismo feito para que isso funcione.

Dica:

Mais uma vez, estamos cavalgando sobre um mecanismo de JavaScript para obter a funo equivalente em Egg.
Formas especiais so passados para o enviroment local de modo que eles possam ser avaliados pelas suas
sub-formas do enviroment . A funo retornada por fun se fecha sobre o argumento env dada a sua funo de
incluso e usa isso para criar enviroment local da funo quando chamado.

Isto significa que o prototype do enviroment local ser o enviroment em que a funo foi criado, o que faz com que
seja possvel ter acesso as variveis de enviroment da funo. Isso tudo o que h para implementar e
finalizar(embora para compil-lo de uma forma que realmente eficiente, voc precisa de um pouco mais de
trabalho).

Comentrios

153
Seria bom se pudssemos escrever comentrios no Egg. Por exemplo, sempre que encontrar um cardinal ("#") ,
poderamos tratar o resto da linha como um comentrio e ignor-lo, semelhante que Javascript faz com o "//" .

No temos de fazer quaisquer grandes mudanas para que o analisador suporte isto. Ns podemos
simplesmente mudar o skipSpace para ignorar comentrios assim como feito com os espaos em branco; para
que todos os pontos onde skipSpace chamado agora tambm ira ignorar comentrios. Vamos fazer essa
alterao:

// This is the old skipSpace. Modify it...


function skipSpace(string) {
var first = string.search(/\S/);
if (first == -1) return "";
return string.slice(first);
}

console.log(parse("# hello\nx"));
// {type: "word", name: "x"}

console.log(parse("a # one\n # two\n()"));


// {type: "apply",
// operator: {type: "word", name: "a"},
// args: []}

Dica:

Certifique-se de que sua soluo vlida com vrios comentrios em uma linha e principalmente com espao
em branco entre ou depois deles.

Uma expresso regular a maneira mais fcil de resolver isso. Faa algo que corresponda "espaos em branco
ou um comentrio, uma ou mais vezes". Use o mtodo exec ou match para olhar para o comprimento do primeiro
elemento na matriz retornada(desde de o inicio) para saber quantos caracteres precisa para cortar.

Resoluo

Corrigindo o escopo
Atualmente, a nica maneira de atribuir uma varivel um valor utilizando o mtodo define . Esta construo atua
tanto como uma forma para definir novas variveis e dar um novo valor para existentes.

Isto causa um problema de ambiguidade. Quando voc tenta dar uma varivel um novo valor que no esta local,
voc vai acabar definindo uma varivel local com o mesmo nome em seu lugar(Algumas lnguas funcionam
assim por design, mas eu sempre achei uma maneira estranha de lidar com escopo).

Adicionar um specialForm similar ao define dara a varivel um novo valor ou a atualizao da varivel em um
escopo exterior se ele ainda no existir no mbito interno. Se a varivel no definida em tudo lanar um
ReferenceError (que outro tipo de erro padro).

A tcnica de representar escopos como simples objetos tornou as coisas convenientes, at agora, e vai ficar um
pouco no seu caminho neste momento. Voc pode querer usar a funo Object.getPrototypeOf que retorna os
prottipos de um objeto. Lembre-se tambm que os escopos no derivam de Object.prototype , por isso, se voc
quiser chamar hasOwnProperty sobre eles,voc tera que usar esta expresso no muito elegante:

Object.prototype.hasOwnProperty.call(scope, name);

Este mtodo( hasOwnProperty ) busca o prottipo do objeto e depois chama-o em um objeto do escopo.

154
specialForms["set"] = function(args, env) {
// Your code here.
};

run("do(define(x, 4),",
" define(setx, fun(val, set(x, val))),",
" setx(50),",
" print(x))");
// 50
run("set(quux, true)");
// Some kind of ReferenceError

Dica:

Voc vai ter que percorrer um escopo de cada vez usando Object.getPrototypeOf ate ir ao escopo externo. Para
cada um dos escopos use hasOwnProperty para descobrir se a varivel indicado pela propriedade name do
primeiro argumento definida existe nesse escopo. Se isso acontecer defina-o como o resultado da avaliao do
segundo argumento, e em seguida retorne esse valor.

Se o escopo mais externo atingido( Object.getPrototypeOf retornando null ) e no encontramos a varivel, isto
significa que no existe; ento um erro deve ser acionado.

Resoluo

155
JavaScript e o Navegador
"O navegador um ambiente realmente hostil de programao."

Douglas Crockford, The JavaScript Programming Language (video lecture)

A prxima parte deste livro vai falar sobre os navegadores web. Sem os navegadores, no existiria JavaScript. E
mesmo se existisse, ningum daria ateno a ele.

A tecnologia web, desde de o incio, descentralizada no apenas tecnicamente mas tambm na maneira que se
evolui. Vrios fornecedores de navegador tem adicionado funcionalidades ad-hoc e muita das vezes tem sido de
maneiras mal pensadas, que acabam sendo adotadas por outros e finalmente viram um padro.

Isso igualmente a uma beno e uma maldio. Por outro lado, isso refora a no existncia de uma particio
central controlando um sistema mas o mesmo vem sendo melhorado por vrias partes trabalhando com pouca
colaborao (ou, ocasionalmente com franca hostilidade). Sendo assim a forma casual que a Web foi
desenvolvida significa que o sistema resultante no exatamente um brilhante exemplo interno de consistncia.
De fato, algumas partes so completamente bagunadas e confusas.

Redes e a Internet
Redes de computador existem desde 1950. Se voc colocar cabos entre dois ou mais computadores e permitir
que eles enviem dados um para o outro por estes cabos, voc pode fazer todo tipo de coisas maravilhosas.

Se conectando duas mquinas no mesmo prdio permite que ns faamos coisas incrveis, conectando
mquinas por todo o planeta deve ser ainda melhor. A tecnologia para comear a implementao desta viso foi
desenvolvida em meados de 1980, e a rede resultante chamada de Internet. Ela tem vivido desde a sua
promessa.

Um computador pode usar essa rede para enviar bits para outro computador. Para qualquer comunicao efetiva
nascida desse envio de bits, os computadores em ambas as pontas devem conhecer qual a representao de
cada bit. O significado de cada sequncia de bits depende inteiramente do tipo de coisa que est tentando se
expressar e o mecanismo de codificao usado.

Um protocolo de rede descreve um estilo de comunicao em uma rede. Existem protocolos para mandar email,
para receber email, para transferir arquivos, e at mesmo para controlar computadores que foram infectados por
softwares maliciosos.

Por exemplo, um simples protocolo de chat deve consistir em um computador enviando os bits que representam
o texto "CHAT?" para outra mquina, e o outro respondendo "OK!" para confirmar que o protocolo foi entendido.
Eles podem ento proceder e enviar um para o outro strings de texto, ler o texto enviado um para o outro pela
rede, e mostrar o que eles receberam nas suas telas.

A maioria dos protocolos so feitos em cima de outros protocolos. Nosso exemplo de protocolo de chat considera
a rede como um tipo de dispositivo de stream, no qual voc pode enviar bits e receb-los com destino correto e na
ordem correta. Assegurar essas coisas atualmente um problema tcnico bastante difcil.

O TCP (Protocolo de Controle de Transmisso) um protocolo que resolve este problema. Todos os aparelhos
conectados na Internet "falam" ele, e a maioria da comunicao na Internet feita atravs dele.

Uma conexo TCP funciona da seguinte maneira: um computador deve estar esperando, ou ouvindo, outros
computadores que iro comear a falar com ele. Para ser capaz de escutar por diferentes tipos de comunicao
ao mesmo tempo em uma nica mquina, cada ouvinte tem um nmero (chamado de porta) associado a ele. A

156
maioria dos protocolos especificam qual porta deve ser usada por padro. Por exemplo, quando ns queremos
mandar um email usando o protocolo SMTP, a mquina pelo qual enviaremos deve estar escutando na porta 25.

Outro computador pode ento estabelecer uma conexo se conectando na mquina alvo usando o nmero
correto da porta. Se a mquina alvo pode ser encontrada e estiver escutando esta porta, a conexo vai ser criada
com sucesso. O computador ouvinte chamado de servidor, e o computador que est se conectando chamado
de cliente.

Uma conexo atua como um encanamento de via dupla pelo qual bits podem ser transitados s mquinas nas
duas extremidades contendo dados. Uma vez que os bits tenham sido transmitidos com sucesso, eles podem
ser lidos novamente pela mquina do outro lado. Isso um modelo conveniente. Voc pode dizer que o TCP
fornece uma abstrao de uma rede.

A Web
A World Wide Web (no ser confundida com a Internet como um todo) um conjunto de protocolos e formatos que
nos permite visitar pginas web em um navegador. A parte "Web" no nome se refere ao fato destas pginas serem
facilmente ligadas umas nas outras, e ento se ligarem em uma grande malha onde os usurios podem se
mover atravs desta.

Para adicionar contedo na Web, tudo que voc precisa fazer conectar uma mquina a Internet, e deix-la
escutando na porta 80, usando o Hypertext Transfer Protocol (HTTP). Este protocolo permite outros computadores
requisitarem documentos na rede.

Cada documento na Web nomeado por um Universal Resource Locator (URL), que se parece com algo assim:

http://eloquentjavascript.net/12_browser.html
| | | |
protocolo servidor caminho (path)

A primeira parte nos diz que esta URL usa o protocolo HTTP (ao contrrio, por exemplo, do HTTP encriptado, que
deve ser https:// ). Ento vem a parte que identifica de qual servidor ns estamos requisitando o documento. Por
ltimo temos a string de caminho que identifica o documento especfico (ou resource) que estamos interessados.

Cada mquina conectada com a Internet recebe um endereo IP nico, que se parece com 37.187.37.82 . Voc
pode usar isso diretamente como parte da URL. Porm listas de nmeros mais ou menos aleatrios so difceis
de lembrar e estranho de se digitar, ento ao invs disso voc pode registrar um nome de domnio para apontar
para uma mquina especfica ou conjunto de mquinas. Eu registrei eloquentjavascript.net para apontar para o
endereo IP da mquina que eu controlo e posso ento usar o nome do domnio para servir pginas da web.

Se voc digitar a URL anterior na barra de endereos do seu navegador, ela vai tentar retornar e mostrar o
documento dessa URL. Primeiro, seu navegador tem que encontrar qual endereo eloquentjavascript.net se
refere. Ento, usando o protocolo HTTP, ele faz a conexo ao servidor neste endereo e pergunta pelo documento
/12_b rowser.html.

Vamos ver com mais detalhes sobre o protocolo HTTP no captulo 17.

HTML
HTML, que significa Hypertext Markup Language (Linguagem de marcao de hipertexto), o formato de
documento usado para as pginas web. Um documento HTML contm texto, bem como tags que fornecem
estrutura para esse texto, descrevendo coisas como links, pargrafos e cabealhos.

Um documento HTML simples, se parece com este:

157
<!doctype html>
<html>
<head>
<title>My home page</title>
</head>
<body>
<h1>My home page</h1>
<p>Hello, I am Marijn and this is my home page.</p>
<p>I also wrote a book! Read it
<a href="http://eloquentjavascript.net">here</a>.</p>
</body>
</html>

As tags, definidas entre os sinais de menor e maior que (< e >), fornecem informaes sobre a estrutura do
documento. O contedo restante apenas texto puro.

O documento comea com <!doctype html> , que diz ao navegador para interpret-lo como HTML moderno
(HTML5), ao invs de outras verses que foram usadas no passado.

Documentos HTML possuem um head (cabea) e um body (corpo). O head contm informaes sob re o
documento, o body contm o documento em si. Neste caso, ns primeiro declaramos que o ttulo do documento
"My home page" e em seguida, declaramos o body contendo um cabealho ( <h1> , que significa "heading 1" -
As tags de <h2> a <h6> produzem cabealhos menores) e dois pargrafos ( <p> ).

Tags aparecem em diversas formas. Um elemento, como o <body> , um pargrafo ou um link, comea com uma
tag de abertura como em <p> e termina com uma tag de fechamento como em </p> . Algumas tags de abertura,
como aquela para o link ( <a> ), contm informaes extra na forma de pares nome="valor" . Estes so chamados
de atrib utos. Nesse caso, o destino do link indicado pelo atributo href="http://eloquentjavascript.net" , onde href

significa "hypertext reference" (referncia de hipertexto).

Alguns tipos de tags no englobam contedo e assim no necessitam de uma tag de fechamento. Um exemplo
seria <img src="http://example.com/image.jpg"> , que ir mostrar a imagem encontrada na URL informada no atributo
src .

Para sermos capazes de incluir os sinais de menor e maior no texto de um documento, mesmo esses possuindo
um significado especial em HTML, teremos que introduzir mais uma nova forma de notao especial. Uma tag de
abertura simples escrita como &lt; ("less than" - menor que), e uma tag de fechamento escrita como &gt;

("greater than" - maior que). Em HTML, o caractere & (o sinal de "E comercial") seguido por uma palavra e um
ponto e vrgula chamado de "entidade" (entity), e ser substituda pelo caractere que representa.

Essa notao parecida com a forma que as barras invertidas so utilizadas nas strings em JavaScript. Uma vez
que esse mecanismo d ao caractere & um significado especial, este tem que ser representado como &amp; .
Dentro que um atributo, que definido entre aspas duplas, a entidade &quot; pode ser usada para representar
um caractere de aspas duplas.

O HTML interpretado de uma forma notavelmente tolerante a erros. Se uma tag omitida, o navegador ir inseri-
la. A forma com que isto feito foi padronizada, voc pode confiar em todos os navegadores modernos para
realizar tal tarefa.

O documento a seguir ser tratado exatamente como o mostrado anteriormente:

<!doctype html>

<title>My home page</title>

<h1>My home page</h1>


<p>Hello, I am Marijn and this is my home page.
<p>I also wrote a book! Read it
<a href=http://eloquentjavascript.net>here</a>.

158
As tags <html> , <head> e <body> foram retiradas. O navegador sabe que a tag <title> pertence ao head , e que
<h1> pertence ao body . Alm disso, eu no especifiquei o final dos pargrafos, o fato de comear um novo
pargrafo ou fechar o documento ir implicitamente fech-los. As aspas que envolviam o destino do link tambm
foram retiradas.

Esse livro geralmente vai omitir as tags <html> , <head> e <body> dos exemplos para mant-los curtos e
ordenados. Mas eu irei fechar as tags e incluir aspas nos valores de atributos.

Eu geralmente tambm irei omitir o doctype. Isso no deve ser interpretado como um incentivo a omitir
declaraes de doctype. Os navegadores frequentemente iro fazer coisas ridculas quando voc esquece delas.
Voc deve consider-las implicitamente presentes nos exemplos, mesmo quando elas no forem mostradas no
texto.

HTML e JavaScript
No contexto desse livro, a tag mais importante do HTML <script> . Essa tag nos permite incluir trechos de
JavaScript em um documento.

<h1>Testing alert</h1>
<script>alert("hello!");</script>

Esse script ser executado assim que a tag <script> for encontrada enquanto o navegador interpreta o HTML. A
pgina mostrada acima ir exibir uma mensagem de alerta quando aberta.

Incluir programas grandes diretamente no documento HTML impraticvel. A tag <script> pode receber um
atributo src a fim de buscar um arquivo de script (um arquivo de texto contendo um programa em JavaScript) a
partir de uma URL.

<h1>Testing alert</h1>
<script src="code/hello.js"></script>

O arquivo code/hello.js includo aqui contm o mesmo simples programa, alert("hello!") . Quando uma pgina
HTML referencia outras URLs como parte de si, por exemplo um arquivo de imagem ou um script, os navegadores
iro busc-los imediatamente e inclu-los na pgina.

Uma tag de script deve sempre ser fechada com </script> , mesmo quando fizer referncia para um arquivo
externo e no contenha nenhum cdigo. Se voc esquecer disso, o restante da pgina ser interpretado como
parte de um script .

Alguns atributos podem conter um programa JavaScript. A tag <button> mostrada abaixo (que aparece como um
boto na pgina) possui um atributo onclick , cujo contedo ser executado sempre que o boto for clicado.

<button onclick="alert('Boom!');">DO NOT PRESS</button>

Perceba que eu tive que usar aspas simples para a string do atributo onclick porque aspas duplas j esto
sendo usadas para envolver o valor do atributo. Eu tambm poderia ter usado &quot; , mas isso tornaria o
programa difcil de ler.

Na caixa de areia

159
Executar programas baixados da internet potencialmente perigoso. Voc no sabe muito sobre as pessoas por
trs da maioria dos sites que visita e eles no necessariamente so bem intencionados. Executar programas de
pessoas que tenham ms intenes como voc tem seu computador infectado por vrus, seus dados roubados
e suas contas hackeadas.

Contudo, a atrao da Web que voc pode navegar sem necessariamente confiar nas pginas que visita. Esse
o motivo pelo qual os navegadores limitam severamente as funes que um programa JavaScript pode fazer:
eles no podem bisbilhotar os arquivos do seu computador ou modificar qualquer coisa que no esteja
relacionada a pgina em que foi incorporado.

O isolamento de um ambiente de programao dessa maneira chamado de sandb oxing, a ideia que o
programa inofensivo "brincando" em uma "caixa de areia". Mas voc deve imaginar esse tipo especfico de
caixas de areia como tendo sobre si uma gaiola de grossas barras de ao, o que as torna um pouco diferentes
das caixas de areia tpicas de playgrounds.

A parte difcil do sandb oxing permitir que os programas tenham espao suficiente para serem teis e ao mesmo
tempo impedi-los de fazer qualquer coisa perigosa. Vrias funcionalidades teis, como se comunicar com outros
servidores ou ler o contedo da rea de transferncia, podem ser usadas para tarefas problemticas ou invasivas
privacidade.

De vez em quando, algum aparece com uma nova forma de burlar as limitaes de um navegador e fazer algo
prejudicial, variando de vazamentos de alguma pequena informao pessoal at assumir o controle total da
mquina onde o navegador est sendo executado. Os desenvolvedores de navegadores respondem "tapando o
buraco", e tudo est bem novamente at que o prximo problema seja descoberto e divulgado, ao invs de ser
secretamente explorado por algum governo ou mfia.

Compatibilidade e a guerra dos navegadores


No incio da web, um navegador chamado Mosaic dominava o mercado. Depois de alguns anos, quem
desequilibrou a balana foi o Netscape, que por sua vez, foi derrotado pelo Internet Explorer da Microsoft. Nos
momentos em que um nico navegador era dominante, seus desenvolvedores se sentiam no direito de criar,
unilateralmente, novas funcionalidades para a web. Como a maior parte dos usurios usava o mesmo navegador,
os sites simplesmente comearam a usar esses recursos sem se importarem com os outros navegadores.

Essa foi a idade das trevas da compatibilidade, frequentemente chamada de "guerra dos navegadores". Os
desenvolvedores web no tiveram uma web unificada, mas sim duas ou trs plataformas incompatveis. Para
piorar as coisas, os navegadores usados por volta de 2003 eram cheios de b ugs, e, claro que esses b ugs
foram diferentes para cada navegador. A vida era difcil para aqueles que escreviam pginas web.

O Mozilla Firefox, uma ramificao sem fins lucrativos do Netscape, desafiou a hegemonia do Internet Explorer no
final dos anos 2000. A Microsoft no estava particularmente interessada em se manter competitiva nessa poca, o
Firefox levou uma parcela do mercado para longe do IE. Pela mesma poca, a Google introduziu seu navegador
Chrome, e o navegador Safari da Apple ganhou popularidade, levando-nos a uma situao onde existiam quatro
grandes "competidores" nesse seguimento ao invs de um.

Os novos navegadores possuam uma postura mais sria sobre a questo dos padres e de melhores prticas
de engenharia, diminuindo as incompatibilidades e b ugs. A Microsoft, vendo sua cota de mercado se esfarelar,
comeou a adotar essas atitudes. Se voc est comeando a aprender sobre desenvolvimento web hoje,
considere-se com sorte. As ltimas verses da maior parte dos navegadores se comportam de uma maneira
uniforme e possuem relativamente menos b ugs.

Ainda no d para dizer que a situao perfeita. Algumas pessoas que usam a web esto, por motivo de inrcia
ou polticas corporativas, presas a navegadores antigos. Enquanto esses navegadores no "morrerem"
completamente, desenvolver sites que funcionem para eles vai exigir uma grande quantidade de conhecimento

160
"misterioso" sobre suas peculiaridades e defeitos. Este livro no tratar dessas peculiaridades. Pelo contrrio,
tem como objetivo apresentar um estilo de programao moderno e sensato.

161
O Modelo de Objeto de Documentos (DOM)
Quando voc abre uma pgina web em seu navegador, ele resgata o texto em HTML da pgina e o interpreta, de
maneira semelhante ao que nosso interpretador do Captulo 11 fazia. O navegador constri um modelo da
estrutura do documento e depois usa esse modelo para desenhar a pgina na tela.

Um dos "brinquedos" que um programa em JavaScript possui disponvel em sua caixa de ferramentas essa
representao do documento. Voc pode l-la e tambm alter-la. Essa representao age como uma estrutura
viva de dados: quando modificada, a pgina na tela atualizada para refletir as mudanas.

Estrutura do Documento
Voc pode imaginar um documento HTML como um conjunto de caixas aninhadas. Tags como e encapsulam
outras tags, as quais, por sua vez, contm outras tags ou texto. Aqui est o documento de exemplo do ltimo
captulo:

<html>
<head>
<title>Minha home page</title>
</head>
<body>
<h1>Minha home page</h1>
<p>Ol, eu sou Marijn e essa minha home page.</p>
<p>Eu tambm escrevi um livro! leia-o
<a href="http://eloquentjavascript.net">aqui</a>.</p>
</body>
</html>

Essa pgina tem a seguinte estrutura:

162
A estrutura de dados que o navegador usa para representar o documento segue este formato. Para cada caixa h
um objeto, com o qual podemos interagir para descobrir coisas como: qual tag HTML ele representa e quais
caixas e textos ele contm. Essa representao chamada de Modelo de Objeto de Documentos, tambm
apelidada de DOM (do ingls Document Ob ject Model).

A varivel global document nos d acesso esses objetos. Sua propriedade documentElement se refere ao objeto
que representa a tag . Essa propriedade tambm nos fornece as propriedades head e body , alocando objetos
para esses elementos.

rvores
Relembre-se da sintaxe das rvores do Captulo 11 por um momento. A estrutura delas incrivelmente similar a
estrutura de um documento do navegador. Cada n pode se referir a outros ns, "filhos", os quais podem ter, por
sua vez, seus prprios "filhos". Esse formato tpico de estruturas aninhadas, nas quais os elementos podem
conter subelementos que so similares eles mesmos.

Ns chamamos uma estrutura de dados de uma rvore quando ela possui uma estrutura de galhos, sem ciclos
(um n no deve conter ele mesmo, direta ou indiretamente) e possui uma nica, bem definida, raiz. No caso do
DOM, document.documentElement representa a raiz.

rvores aparecem muito em Cincias da Computao. Alm de representar estruturas recursivas como
documentos HTML ou programas, elas tambm so comumente usadas para manter conjuntos ordenados de
dados, pois elementos podem ser tipicamente encontrados ou inseridos de maneira mais eficiente em uma
rvore ordenada do que em um conjunto (ou "array") plano ordenado.

Uma rvore tpica possui diferentes tipos de ns. A rvore de sintaxe para a Egg Language continha variveis,
valores e ns de aplicao. Ns de aplicao sempre tm filhos, diferentemente das variveis e valores, que eram
folhas, ou seja, ns sem filhos.

163
O mesmo vale para o DOM. Ns de elementos comuns, os quais representam tags HTML, determinam a
estrutura do documento. Esses podem possuir ns filhos. Um exemplo de um desses ns o document.body .
Alguns desses ns filhos podem ser folhas, assim como fragmentos de texto ou comentrios (os quais so
escritos entre <!-- e --> em HTML).

Cada objeto que um n do DOM tem a propriedade nodeType, a qual contm um cdigo numrico que identifica
o tipo do n. Elementos comuns tm valor 1, o qual tambm definido como a propriedade constante
document.ELEMENT_NODE . Ns de texto, representando um intervalo de texto no documento, possuem o valor 3
(document.TEXT_NODE). Comentrios tm valor 8 (document.COMMENT_NODE).

Sendo assim, outra maneira de visualizar a rvore do nosso documento :

Na imagem acima, as folhas so os ns de texto e as setas indicam a relao de pai e filho entre os ns.

O Padro
Usar estranhos cdigos numricos para representar tipos de ns no algo muito ao estilo JavaScript de se
fazer. Mais tarde neste captulo, veremos que outras partes da interface DOM tambm se sentem estranhas, no
petencentes. A razo para isso que o DOM no foi concebido apenas para uso com o JavaScript, ao invs disso,
ele tenta definir uma interface com uma linguagem neutra, a qual pode ser usada por outros sistemasno
somente HTML, mas tambm XML, o qual um formato genrico de dados com um sintaxe semelhante ao HTML.

Padres so, geralmente, teis, mas nesse caso, a vantagem (consistncia entre diferentes linguagens), no
to convincente. Possuir uma interface que corretamente integrada com a linguagem que voc est usando vai
fazer voc economizar mais tempo do que uma interface familiar entre diferentes linguagens.

Como um exemplo dessa integrao pob re, considere a propriedade childNodes que os ns de elementos DOM
possuem. Essa propriedade carrega um objeto parecido com um array, com uma propriedade length e
propriedades identificadas por nmeros para acessar os ns filhos. Mas ele uma instncia do tipo NodeList ,
no um array real, logo ele no possui mtodos como slice e forEach .

Alm disso existem outros problemas que so simplesmente ocasionados por um design falho. Por exemplo:
no h nenhuma maneira de criar um novo n e imediatamente adicionar ns filhos ou atributos ele. Ao invs
disso, voc precisa primeiro cri-lo, depois adicionar os filhos, um por um, e s ento definir os atributos um um
usando side effects. Cdigos que interagem muito com o DOM tendem ficar muito longos, repetitivos e feios.

Porm nenhuma dessas falhas fatal, pois JavaScript nos permite criar nossas prprias abstraes. fcil
escrever algumas funes auxiliares que permitem que voc expresse as operaes que quer fazer de maneira
mais curta. Na verdade, muitas lib raries dedicadas programao em browsers j vm com essas ferramentas.

164
Movendo-se Atravs da rvore
Os ns DOM contm uma variedade de ligaes para outros ns prximos. O diagrama a seguir tenta ilustr-los:

Ainda que o diagrama mostre apenas uma ligao de cada tipo, todo n possui uma propriedade parentNode que
aponta para o n que o contm (seu n pai). Dessa mesma maneira, todo n de um elemento (n do tipo 1)
possui a propriedade childNodes que aponta para um objeto parecido com um array, o qual contm seus ns
filhos.

Em teoria, voc pode se mover para qualquer lugar na rvore usando apenas essas ligaes entre ns pais e ns
filhos, porm JavaScript tambm te d acesso outras ligaes muito convenientes. As propriedades firstChild

e lastChild apontam para o primeiro e ltimo elemento filho, respectivamente, ou ento possuem o valor null

(nulo) para ns sem filhos. Similarmente, previousSibling e nextSibling apontam para os ns adjacentes, que
so ns com o mesmo pai que aparecem imediatamente antes ou depois do n em questo. No caso de
usarmos a propriedade previousSibling para um primeiro n filho, ela ir possuir um valor nulo, o mesmo
acontece se usarmos a propriedade nextSibling para o ltimo n filho.

Quando lidamos com uma estrutura de dados aninhada como essa, funes recursivas so geralmente muito
teis. A funo abaixo escaneia um documento procurando por ns de texto contendo uma determinada string e
retorna true quando encontrar um.

function talksAbout(node, string) {


if (node.nodeType == document.ELEMENT_NODE) {
for (var i = 0; i < node.childNodes.length; i++) {
if (talksAbout(node.childNodes[i], string))
return true;
}
return false;
} else if (node.nodeType == document.TEXT_NODE) {
return node.nodeValue.indexOf(string) > -1;
}

}
console.log(talksAbout(document.body, "book"));
// true

A propriedade nodeValue de um n de texto se refere string de texto que ele representa.

165
Encontrando Elementos
Navegar por essas ligaes entre pais, filhos e irmos pode at ser til, assim como no exemplo da funo
acima, a qual passa por todo o documento, mas, se quisermos encontrar um n especfico no documento, uma
m ideia comearmos a busca em document.body e seguirmos cegamente um caminho preestabelecido de
ligaes at finalmente ach-lo. Fazer isso ir formular pressuposies no nosso programa sobre a estrutura
precisa do documentouma estrutura que pode mudar depois. Outro fator complicante que os ns de texto so
criados at mesmo por espao em branco entre ns. A tag body que usamos no comeo deste captulo, por
exemplo, no tem apenas trs filhos (um <h1> e dois <p> 's), na verdade ela tem sete: esses trs e ainda o
espao antes, depois e entre eles.

Ento se quisermos obter o atributo href do link naquele documento, ns no queremos dizer algo como "pegue
o segundo filho do sexto filho do corpo do documento". Seria muito melhor se pudssemos dizer "pegue o
primeiro link nesse documento". E ns podemos.

var link = document.body.getElementsByTagName("a")[0];


console.log(link.href);

Todos os ns possuem um mtodo getElementsByTagName , o qual coleta todos os elementos com o nome passado
que so descendentes (filhos diretos ou indiretos) do n dado e retorna-os no formato de um objeto parecido com
um array.

Para encontrar um n nico especfico, voc pode dar ele um atributo id e usar o mtodo
document.getElementById .

<p>Minha avestruz Gertrude:</p>


<p><img id="gertrude" src="img/ostrich.png"></p>

<script>
var ostrich = document.getElementById("gertrude");
console.log(ostrich.src);
</script>

Um terceiro mtodo, similar a esse, o getElementsByClassName , o qual, assim como getElementsByTagName , faz uma
busca entre os contedos de um n elemento e retorna todos os elementos que possuem a string passada no
seu atributo class .

Alterando o Documento
Quase tudo na estrutura de dados DOM pode ser alterado. Ns de elementos possuem uma variedade de
mtodos que podem ser usados para mudar seu contedo. O mtodo removeChild remove um dado n filho do
documento. Para adicionar um filho, ns podemos usar o mtodo appendChild , o qual coloca n filho no fim da
lista de filhos, ou at o mtodo insertBefore , que insere um n passado como primeiro argumento antes do n
passado como segundo argumento.

<p>Um</p>
<p>Dois</p>
<p>Trs</p>

<script>
var paragraphs = document.body.getElementsByTagName("p");
document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>

166
Um n pode existir no documento em apenas um lugar. Sendo assim, inserir o pargrafo "Trs" na frente do
pargrafo "Um" vai apenas remov-lo do fim do documento e depois inseri-lo na frente, resultando em
"Trs/Um/Dois". Todas as operaes que inserem um n em algum lugar iro, como efeito colateral, fazer com
que ele seja removido de sua posio atual (caso ele tenha uma).

O mtodo replaceChild usado para substituir um n filho por outro. Ele aceita como argumentos dois ns: um
novo n e o n ser substitudo. O n substitudo dever ser um filho do elemento com o qual o mtodo
chamado. Note que ambos replaceChild e insertBefore esperam o seu novo n como primeiro argumento.

Criando Ns
No exemplo seguinte, ns queremos escrever um script que substitua todas as imagens (tags <img> ) no
documento pelo texto que elas possuem no seu atributo alt , o qual especifica uma alternativa textual para
representao da imagem.

Isso envolve no s remover as imagens, mas adicionar um novo n de texto para substitu-las. Para isso, ns
usamos o mtodo document.createTextNode .

<p>The <img src="img/cat.png" alt="Cat"> in the


<img src="img/hat.png" alt="Hat">.</p>

<p><button onclick="replaceImages()">Substituir</button></p>

<script>
function replaceImages() {
var images = document.body.getElementsByTagName("img");
for (var i = images.length - 1; i >= 0; i--) {
var image = images[i];
if (image.alt) {
var text = document.createTextNode(image.alt);
image.parentNode.replaceChild(text, image);
}
}
}
</script>

Dada uma string, o mtodo createTextNode nos d um n do DOM de tipo 3 (um n de texto), que podemos inserir
no nosso documento para que seja mostrado na tela.

O loop (ou repetio) que percorre as imagens comea no fim da lista de ns. Isso necessrio porque a lista de
ns retornada por um mtodo como getElementsByTagName (ou uma propriedade como childNodes ) vivaisto ,
atualizada em tempo real conforme o documento muda. Se ns comessemos pelo incio do documento,
remover a primeira imagem faria com que a lista perdesse seu primeiro elemento, ento na segunda vez que o
loop se repetisse, quando i um, ele iria parar, pois o comprimento da coleo agora tambm um.

Se voc quiser um conjunto slido de ns, em oposio a um conjunto em tempo real, voc pode converter o
conjunto para um array de verdade, chamando o mtodo slice .

var arrayish = {0: "um", 1: "dois", length: 2};


var real = Array.prototype.slice.call(arrayish, 0);
real.forEach(function(elt) { console.log(elt); });
// um
// dois

Para criar ns comuns de elementos (tipo 1), voc pode usar o mtodo document.createElement . Esse mtodo pega
o nome de uma tag e retorna um novo n vazio do tipo fornecido.

167
O exemplo seguir define uma funo elt , a qual cria um n de elemento e trata o resto dos argumentos como
filhos para aquele n. Essa funo depois usada para adicionar uma simples atribuio para uma citao (em
ingls, quote).

<blockquote id="quote">
Nenhum livro pode ser terminado. Enquanto trabalhos nele
ns aprendemos apenas o suficiente para consider-lo imaturo
no momento em que damos as costas a ele.
</blockquote>

<script>
function elt(type) {
var node = document.createElement(type);
for (var i = 1; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child == "string")
child = document.createTextNode(child);
node.appendChild(child);
}
return node;
}

document.getElementById("quote").appendChild(
elt("footer", "",
elt("strong", "Karl Popper"),
", prefcio da segunda edio de ",
elt("em", "A Sociedade Aberta e Seus Inimigos"),
", 1950"));
</script>

Atributos
Alguns atributos de elementos, como href para links, podem ser acessados atravs de uma propriedade com o
mesmo nome do objeto DOM do elemento. Esse o caso para um conjunto limitado de atributos padres
comumente usados.

HTML permite que voc defina qualquer atributo que voc queira em ns. Isso pode ser til, pois pode permitir que
voc guarde informaes extras em um documento. Se voc quiser fazer seus prprios nomes de atributos,
porm, esses atributos no estaro presentes como uma propriedade no n do elemento. Ao invs disso, voc
ter que usar os mtodos getAttribute e setAttribute para trabalhar com eles.

<p data-classified="secret">O cdigo de lanamento 00000000.</p>


<p data-classified="unclassified">Eu tenho dois ps.</p>

<script>
var paras = document.body.getElementsByTagName("p");
Array.prototype.forEach.call(paras, function(para) {
if (para.getAttribute("data-classified") == "secret")
para.parentNode.removeChild(para);
});
</script>

Eu recomendo prefixar os nomes dos atributos inventados com data- , para certificar-se que eles no iro entrar
em conflito com outros atributos.

Como um simples exemplo, ns iremos escrever um "destacador de sintaxe" que procura por tags <pre> (tag
para "pr-formatado", usado para cdigo ou similares em texto plano) com um atributo data-language e tenta
destacar as palavras chaves para aquela linguagem.

168
function highlightCode(node, keywords) {
var text = node.textContent;
node.textContent = ""; // Limpa o n.

var match, pos = 0;


while (match = keywords.exec(text)) {
var before = text.slice(pos, match.index);
node.appendChild(document.createTextNode(before));
var strong = document.createElement("strong");
strong.appendChild(document.createTextNode(match[0]));
node.appendChild(strong);
pos = keywords.lastIndex;
}
var after = text.slice(pos);
node.appendChild(document.createTextNode(after));
}

A funo highlightCode pega um n <pre> e uma expresso regular (com a opo "global" ligada) que identifica
as palavras reservadas da linguagem de programao que o elemento contm.

A propriedade textContent usada para pegar todo o texto dentro do n e depois definida para uma string vazia,
a qual tem o efeito de esvaziar o n. Ns fazemos um loop por todas as ocorrncias das palavras chaves da
linguagem, e fazemos o texto entre essas ocorrncias como ns normais de texto e cercamos as palavras chaves
com a tag <bold> , fazendo com que elas fiquem em negrito.

Ns podemos sublinhar automaticamente todos os cdigos de programas na pgina fazendo um looping entre
todos os elementos <pre> que possuem o atributo data-language e ento chamando a funo highlightCode em
cada um e depois aplicando uma expresso regular adequada para a linguagem que se quer destacar.

var languages = {
javascript: /\b(function|return|var)\b/g /* etc */
};

function highlightAllCode() {
var pres = document.body.getElementsByTagName("pre");
for (var i = 0; i < pres.length; i++) {
var pre = pres[i];
var lang = pre.getAttribute("data-language");
if (languages.hasOwnProperty(lang))
highlightCode(pre, languages[lang]);
}
}

Por exemplo:

<p>Aqui est, a funo identidade:</p>


<pre data-language="javascript">
function id(x) { return x; }
</pre>

<script>highlightAllCode();</script>

Existe um atributo comumente usado, class , o qual uma palavra reservada na linguagem JavaScript. Por razes
histricasalgumas implementaes antigas de JavaScript no conseguiam lidar nomes de propriedades que
correspondiam a palavras chave ou palavras reservadas da linguagema propriedade usada para acessar esse
atributo chamada de className . Voc tambm pode acess-la pelo seu nome real, " class ", usando os mtodos
getAttribute e setAttribute .

Layout
169
Voc provavelmente notou que tipos diferentes de elementos so dispostos de maneiras diferentes. Alguns, como
pargrafos ( <p> ) ou cabealhos ( <h1> ), ocupam toda a largura do documento e so mostrados em linhas
separadas. Esses so chamados de elementos b loco. Outros, como links ( <a> ) ou o elemento <strong> , usado
no exemplo acima, so mostrados na mesma linha, juntamente com o texto que os cerca. Esses elementos so
chamados elementos inline (em linha).

Para qualquer documento, navegadores so capazes de computar um layout, o qual d para cada elemento um
tamanho e uma posio baseando-se em seu tipo e contedo. Esse layout depois usado para desenhar o
documento na tela.

O tamanho e posio de um elemento pode ser acessado atravs de JavaScript. As propriedades offsetWidth e
offsetHeight iro fornecer voc o espao que o elemento ocupa em pixels. Um pixel a unidade bsica de
medida em um navegador e tipicamente corresponde ao menor ponto que sua tela pode mostrar. Do mesmo
modo, clientWidth e clientHeight iro fornecer o espao dentro do elemento, ignorando a largura da borda.

<p style="border: 3px solid red">


Estou encaixotado em
</p>

<script>
var para = document.body.getElementsByTagName("p")[0];
console.log("clientHeight:", para.clientHeight);
console.log("offsetHeight:", para.offsetHeight);
</script>

A maneira mais efetiva de encontrar a posio precisa de um elemento na tela o mtodo getBoundingClientRect .
Ele retorna um objeto com as propriedades top (topo), bottom (baixo), left (esquerda) e right (direita), que
correspondem s posies dos pixels em relao ao canto esquerdo da tela. Se voc quiser que eles sejam
relativos ao documento como um todo, voc dever adicionar a posio atual de rolagem, encontrada partir das
variveis globais pageXOffset e pageYOffset .

Organizar um documento, fazer seu layout, pode ser muito trabalhoso. Para ganhar velocidade, os motores dos
navegadores no fazem uma reorganizao do documento imediatamente a cada vez que ele muda, ao invs
disso eles esperam o mximo que podem. Quando um programa JavaScript que mudou o documento termina de
rodar, o navegador ir ter que computar um novo layout para poder mostrar o documento alterado na tela. Quando
um programa pede pela posio ou tamanho de algo, lendo propriedades como offsetHeight ou chamando
getBoundingClientRect , prover a ele uma informao correta tambm requer computar um layout.

Um programa que repetidamente alterna entre ler informaes sobre a organizao (layout) do DOM e alter-lo,
fora muitas reorganizaes e consequentemente compromete o desempenho. O cdigo seguir mostra um
exemplo disso. Ele contm dois programas diferentes que constroem uma linha de "X" caracteres com 2000
pixels de comprimento e mede quanto tempo cada um leva.

170
<p><span id="one"></span></p>
<p><span id="two"></span></p>

<script>
function time(name, action) {
var start = Date.now(); // Tempo atual milissegundos
action();
console.log(name, "took", Date.now() - start, "ms");
}

time("naive", function() {
var target = document.getElementById("one");
while (target.offsetWidth < 2000)
target.appendChild(document.createTextNode("X"));
});
// naive levou 32 ms

time("clever", function() {
var target = document.getElementById("two");
target.appendChild(document.createTextNode("XXXXX"));
var total = Math.ceil(2000 / (target.offsetWidth / 5));
for (var i = 5; i < total; i++)
target.appendChild(document.createTextNode("X"));
});
// clever levou 1 ms
</script>

Estilizando
Ns vimos que diferentes elementos HTML iro apresentar diferentes comportamentos. Alguns so mostrados
como blocos, outros so mostrados em linha. Alguns adicionam um tipo de estilo, como <strong> fazendo seu
contedo ficar em negrito e <a> fazendo seu contedo azul e sublinhando-o.

A maneira que uma tag <img> mostra uma imagem, e a maneira uma tag <a> faz com que um link seja
acessado quando clicado, esto intimamente ligadas ao tipo do elemento. Mas o estilo padro associado um
elemento, assim como a cor de texto ou sublinhado, pode ser mudado por ns. Veja o exemplo abaixo usando a
propriedade style .

<p><a href=".">Normal link</a></p>


<p><a href="." style="color: green">Link verde</a></p>

Um atributo style pode conter um ou mais declaraes, as quais so propriedades (como por exemplo color )
seguidas por dois pontos e um valor (assim como green ). Caso existam mltiplas declaraes, elas devero ser
separadas por pontos e vrgulas. Por exemplo, " color: red;border:none "

Existem muitos aspectos que podem ser influenciados atravs dessa estilizao. Por exemplo, a propriedade
display controla quando um elemento mostrado como um bloco ou em linha.

Esse texto mostrado <strong>em linha</strong>,


<strong style="display: block">como um bloco</strong>, e
<strong style="display: none">no mostrado</strong>.

A tag block vai acabar em sua prpria linha, pois elementos em blocos no so mostrados em linha com texto ao
seu redor. A ltima tag no mostrada, display: none faz com que um elemento seja exibido na tela. Essa uma
maneira de esconder elementos e comumente prefervel remov-los por completo do documento, tornando
mais fcil revel-los mais tarde.

171
Cdigo JavaScript pode manipular diretamente o estilo de um elemento atravs da propriedade style do n.
Essa propriedade carrega um objeto que possui todas as propriedades possveis para o atributo style . Os
valores dessas propriedades so strings, os quais ns podemos escrever para mudar um aspecto em particular
do estilo do elemento.

<p id="para" style="color: purple">


Texto bonito
</p>

<script>
var para = document.getElementById("para");
console.log(para.style.color);
para.style.color = "magenta";
</script>

Alguns nomes de propriedades de estilo contm traos, como font-family. Devido ao fato desses nomes de
propriedades serem estranhos para serem trabalhados em JavaScript (voc teria que escrever style["font-

family"] ), os nomes de propriedades no objeto style , nestes casos, tm seus traos removidos e a letra aps
eles tornada maiscula ( style.fontFamily ).

Estilos em Cascata
O sistema de estilos para HTML chamado de CSS, que uma abreviao para Cascading Style Sheets (Folhas
de Estilo em Cascata, em portugus). Uma folha de estilos um conjunto de regras de como estilizar os
elementos no documento. Ela pode ser fornecida dentro de uma tag <style> .

<style>
strong {
font-style: italic;
color: grey;
}
</style>
<p>Agora <strong>textos com tag strong</strong> so itlicos e cinza.</p>

A palavra cascata no nome refere-se ao fato de que mltiplas regras so combinadas para produzir o estilo final
de um elemento, aplicando-se em "cascata". No exemplo acima, o estilo padro para as tags <strong> , o qual d
eles font-weight: bold , sobreposto pela regra na tag <style> , que adiciona font-style e color .

Quando mltiplas regras definem um valor para a mesma propriedade, a regra lida mais recentemente tem um
nvel de preferncia maior e vence. Ento se a regra na tag <style> inclusse font-weight: normal , conflitando com
a regra font-weight padro, o texto seria normal e no em negrito. Estilos em um atributo style aplicados
diretamente ao n possuem maior preferncia e sempre vencem.

possvel selecionar outras coisas alm de nomes de tags em regras CSS. Uma regra para .abc aplica-se para
todos os elementos com "abc" no seu atributo classe. Uma regra para #xyz aplica-se para o elemento com um
atributo id de "xyz" (o qual deve ser nico dentro do documento).

172
.subtle {
color: grey;
font-size: 80%;
}
#header {
background: blue;
color: white;
}
/* Elementos p, com classes a e b, e id main */
p.a.b#main {
margin-bottom: 20px;
}

A regra de preferncia favorecendo a regra mais recentemente definida vlida somente quando as regras
possuem a mesma especificidade. A especificidade de uma regra uma medida de o quo precisamente ela
descreve os elementos que seleciona, sendo determinada por um nmero e um tipo (tag, classe ou id) de um
aspecto do elemento que requer. Por exemplo, p.a mais especfico que apenas um p ou apenas um .a ,
ento uma regra composta como essa teria preferncia.

A notao p > a {...} aplica os estilos passados para todas as tags <a> que so filhos diretos de tags <p> . Do
mesmo modo, p a {...} aplica-se todas as tags <a> dentro de tags <p> , sejam elas filhos diretos ou
indiretos.

Seletores de Busca
Ns no iremos usar muitas folhas de estilo neste livro. Ainda assim, entend-las crucial para programar no
navegador, explicar todas as propriedades que elas suportam de maneira correta e a interao entre essas
propriedades levaria dois ou trs livros somente para isso.

A razo principal pela qual eu introduzi a sintaxe de seletoresa notao usada em folhas de estilo para definir a
qual elemento um conjunto de regras se aplica que ns podemos usar essa mesma mini linguagem para
definir uma maneira eficaz de encontrar elementos do DOM.

O mtodo querySelectorAll, que definido em tanto no objeto document quanto nos ns de elementos, leva apenas
uma string seletora e retorna um objeto parecido um array, contendo todos os elementos que encontra.

<p>Se voc sair por a caando


<span class="animal">coelhos</span></p>
<p>E voc souber que vai cair</p>
<p>Diga eles que <span class="character">enquanto fumava narguil,
<span class="animal">uma lagarta</span></span></p>
<p>Lhe deu a ordem</p>

<script>
function count(selector) {
return document.querySelectorAll(selector).length;
}
console.log(count("p")); // Todos os elementos <p>
// 4
console.log(count(".animal")); // Classe animal
// 2
console.log(count("p .animal")); // Animal dentro de <p>
// 2
console.log(count("p > .animal")); // Filhos diretos de <p>
// 1
</script>

Diferentemente de mtodos como getElementsByTagName , o objeto retornado por querySelectorAll no "vivo", ou


seja, atualizado em tempo real. Ele no ir mudar quando voc mudar o documento.

173
O mtodo querySelector (sem a parte All ) funciona de maneira similar. Ele til para quando voc quiser um
nico e especfico elemento. Ele ir retornar apenas o primeiro elemento coincidente com a busca ou null se
nenhum elemento cumprir os critrios de busca.

Posicionando e Animando
A propriedade de estilo position influencia o layout de uma maneira muito poderosa.

Por padro, essa propriedade tem o valor static , significando que o elemento fica em seu lugar "absoluto",
esttico. Quando essa propriedade definida como relative , o elemento ainda ocupa espao no documento,
mas agora as propriedades top e left podem ser usadas para mov-lo em relao ao seu lugar original.
Quando position definida como absolute o elemento removido do fluxo normal do documentoisso , no
ocupa mais espao e pode sobrepor outros elementose suas propriedades top e left podem ser usadas
para posicion-lo de maneira absoluta em relao ao canto superior esquerdo do elemento fechado mais
prximo cuja propriedade position no esttica. Se no houver tal elemento, ele posicionado em relao ao
documento.

Ns podemos usar essa tcnica para criar uma animao. O documento abaixo mostra uma foto de um gato que
flutua seguindo a forma de uma elipse:

<p style="text-align: center">


<img src="img/cat.png" style="position: relative">
</p>
<script>
var cat = document.querySelector("img");
var angle = 0, lastTime = null;
function animate(time) {
if (lastTime != null)
angle += (time - lastTime) * 0.001;
lastTime = time;
cat.style.top = (Math.sin(angle) * 20) + "px";
cat.style.left = (Math.cos(angle) * 200) + "px";
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>

Primeiro a imagem centralizada na pgina e dada uma posio do tipo relative . Ns vamos atualizar
repetidamente as propriedades de estilo top e left dessa imagem para mov-la.

O script usa requestAnimationFrame para agendar a funo animate para rodar sempre que o navegador estiver
preparado para redesenhar na tela. A funo animate por sua vez, chama requestAnimationFrame para agendar a
prxima atualizao do elemento. Quando a janela ou aba do navegador est ativa, isso ir fazer com que as
atualizaes ocorram em uma taxa de cerca de 60 por segundo, o que tende a produzir uma animao com um
bom aspecto.

Se ns apenas atualizssemos o DOM em um loop, a pgina iria congelar e nada seria mostrado na tela.
Navegadores no fazem atualizaes do que mostram enquanto um programa JavaScript est rodando e tambm
no permitem qualquer interao com a pgina durante esse perodo. Esse o motivo pelo qual ns precisamos
da funo requestAnimationFrame ela permite que o navegador saiba que ns estamos satisfeitos por enquanto e
que ele pode ir em frente e fazer as coisas que os navegadores geralmente fazem, como atualizar a tela e
responder s aes do usurio.

Nossa funo de animao recebe como argumento o tempo atual, o qual comparado com o tempo recebido
anteriormente (nesse caso, a varivel lastTime ) para ter certeza que o movimento do gato por milissegundo
estvel, e ento a animao se move suavemente. Se ela se movesse uma porcentagem fixa cada passo, o

174
movimento iria sofrer atraso se, por exemplo, outra tarefa que exige muito processamento no mesmo computador
acabasse impedindo com que a funo fosse executada por uma frao de segundo.

Math.cos (cosseno) e Math.sin (seno) so teis para achar pontos que se localizam em um crculo ao redor de
um ponto (0,0) com o raio de uma unidade. Ambas as funes interpretam seu argumento como a posio nesse
crculo, com 0 significando o ponto na extrema direita do crculo, indo em sentido horrio at 2 (cerca de 6.28)
nos levou ao redor de todo o crculo. Math.cos informa a coordenada x (no plano cartesiano) do ponto que
corresponde dada posio no crculo, enquanto Math.sin informa a coordenada y. Posies (ou ngulos)
maiores que 2 ou abaixo de 0 so vlidosa rotao se repete, de modo que a+2 refere-se ao mesmo ngulo
que a

A animao do gato mantm um contador, angle , para o ngulo atual da animao, e incrementa-o
proporcionalmente ao tempo decorrido a cada vez que a funo animate chamada. Ela pode usar esse ngulo
para computar a posio atual do elemento de imagem. A propriedade de estilo top computada com Math.sin

e multiplicada por 20, que o raio vertical do nosso crculo. O estilo left baseado em Math.cos e multiplicado
por 200, de maneira que o crculo muito mais largo do que alto, resultando em uma rotao elptica.

Note que os estilos geralmente precisam de unidades. Nesse caso, ns temos que inserir " px " para o nmero
com o intuito de dizer ao navegador que ns estamos contando em pixels (no em centmetros, "ems" ou outras
unidades). Isso algo fcil de esquecer. Usar nmeros sem unidades vai resultar em uma regra de estilo
ignoradaexceto se o nmero for 0, que sempre significa a mesma coisa, no importando a unidade.

Resumo
Programas JavaScript podem inspecionar e interferir com o documento atual cujo navegador est mostrando
atravs de uma estrutura de dados chamada DOM. Essa estrutura representa o modelo do documento feito pelo
navegador e um programa JavaScript pode modific-la para mudar o documento que est sendo mostrado.

O DOM organizado como uma rvore, na qual elementos so organizados hierarquicamente de acordo com a
estrutura do documento. Os objetos representando elementos possuem propriedades como parentNode e
childNodes , que podem ser usadas para navegar pela rvore.

A maneira com que um documento mostrada pode ser influenciada atravs da estilizao, tanto anexando
estilos diretamente um n ou definindo regras que aplicam-se certos ns. Existem muitas propriedades de
estilo diferentes, assim como color ou display . JavaScript pode manipular o estilo de um elemento diretamente
atravs de sua propriedade style .

Exerccios
Construa uma Tabela
Ns construmos tabelas de texto plano no Captulo 6. O HTML faz com que mostrar tabelas seja um pouco mais
fcil. Uma tabela em HTML construda com a seguinte estrutura de tags:

175
<table>
<tr>
<th>nome</th>
<th>altura</th>
<th>pas</th>
</tr>
<tr>
<td>Kilimanjaro</td>
<td>5895</td>
<td>Tanzania</td>
</tr>
</table>

Para cada sequncia (linha), a tag <table> contm uma tag <tr> . Dentro dessa tag ns podemos colocar
elementos clula: ou clulas de cabealho ( <th> ) ou clulas comuns ( <td> ).

A mesma fonte de dados usada no Captulo 6 est disponvel novamente na varivel MOUNTAINS , disponvel em
nossa sandb ox e tambm disponvel para download na nossa lista de conjunto de dados no
website(eloquentjavascript.net/code).

Escreva uma funo buildTable que, dado um array de objetos com um mesmo conjunto de propriedades,
construa uma estrutura DOM representando uma tabela. A tabela deve ter uma sequncia (linha) de cabealho
com os nomes das propriedades dentro de elementos <th> e uma linha subsequente por objeto no array, com
seus valores das propriedades em elementos <td> .

A funo `Object.keys, a qual retorna um array contendo os nomes das propriedades que um objeto possui,
provavelmente vai ser til nesse caso.

Uma vez que voc fez a parte bsica funcionar, alinhe as clulas que contm nmeros direita usando a
propriedade style.textAlign com o valor " right ".

<style>
/* Define uma visualizao mais limpa para tabelas */
table { border-collapse: collapse; }
td, th { border: 1px solid black; padding: 3px 8px; }
th { text-align: left; }
</style>

<script>
function buildTable(data) {
// Seu cdigo aqui.
}

document.body.appendChild(buildTable(MOUNTAINS));
</script>

Dicas

Use document.createElement para criar novos ns de elementos, document.createTextNode para criar ns de texto e o
mtodo appendChild para colocar ns dentro de outros ns.

Voc deve fazer um loop atravs das palavras chaves uma vez para preencher a linha do topo e depois novamente
para cada objeto no array para construir linhas com os dados.

No esquea de retornar o elemento <table> que engloba tudo isso no fim da funo.

Elementos por Nome de Tags

176
O mtodo getElementsByTagName retorna todos os elementos filhos com um dado nome de tag . Implemente sua
prpria verso disso, como uma funo normal, a qual recebe um n e uma string (com o nome da tag) como
argumentos e retorna um array contendo todos os ns de elementos descendentes com a dada tag.

Para encontrar o nome de tag de um elemento, use sua propriedade tagName . Mas note que isso ir retornar o
mesmo nome de tag todo em caixa alta. Use os mtodos toLowerCase ou toUpperCase para compensar isso.

<h1>Cabealho com um elemento <span>span</span>.</h1>


<p>Um pargrafo com <span>um</span>, <span>dois</span>
spans.</p>

<script>
function byTagName(node, tagName) {
// Seu cdigo aqui.
}

console.log(byTagName(document.body, "h1").length);
// 1
console.log(byTagName(document.body, "span").length);
// 3
var para = document.querySelector("p");
console.log(byTagName(para, "span").length);
// 2
</script>

Dicas
A soluo expressada mais facilmente com uma funo recursiva, similar funo talksAbout definida
anteriormente neste captulo.

Voc pode chamar byTagname usando ela mesma, ou seja, de maneira recursiva, concatenando os arrays
resultantes para produzir uma sada. Uma aproximao mais eficiente esse problema envolve definir uma
funo interior qual chama a si mesma recursivamente, a qual tem acesso a qualquer posio do array definida
na funo exterior, nas quais ela pode adicionar os elementos que encontrar. No esquea de chamar a funo
interior atravs da funo exterior uma vez.

A funo recursiva deve checar o tipo de n. Aqui ns estamos interessados apenas no n tipo 1
( document.ELEMENT_NODE ). Para tais ns, ns deveremos fazer um loop sobre seus filhos e, para cada filho, ver se ele
cumpre nossos critrios de busca e tambm fazer uma chamada recursiva para inspecionar, por sua vez, seus
prprios filhos.

O Chapu do Gato
Extenda a animao do gato definida anteriormente de modo que tanto o gato quando seu chapu ( <img

src="img/hat.png" ) faam a mesma rbita em lados diferentes da elipse.

Ou faa o chapu circular ao redor do gato. Voc pode ainda alterar a animao de outra maneira que julgar
interessante.

Para tornar mais fcil a tarefa de posicionar mltiplos objetos, provavelmente uma boa idia optar por
posicionamento absoluto. Isso significa que as propriedades top e left so contadas relativamente ao topo
esquerdo do documento. Para evidar usar coordenadas negativas, voc pode simplesmente adicionar um
nmero fixo de pixels para os valores das posies.

177
<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">

<script>
var cat = document.querySelector("#cat");
var hat = document.querySelector("#hat");
// Seu cdigo aqui.
</script>

178
Manipulando eventos
Voc tem o poder sobre sua mente e no sobre eventos externos. Perceba isso e voc encontrara
resistncia.

Marcus Aurelius, Meditations

Alguns programas funcionam com entradas direta do usurio, tais como a interao de mouse e teclado. O tempo
e a ordem de tal entrada no pode ser previsto com antecedncia. Isso requer uma abordagem diferente para
controlar o fluxo do que utilizamos at agora.

Os manipuladores de eventos
Imaginem uma interface onde a nica maneira de descobrir se uma tecla est sendo pressionada ler o estado
atual dessa tecla. Para ser capaz de reagir s presses de teclas voc teria que ler constantemente o estado da
tecla antes que ela fique liberado novamente. Seria perigoso executar outros clculos demoradas pois voc
poderia perder alguma tecla.

assim que tal atributo foi tratado em mquinas primitivas. A um passo para o hardware, o sistema operacional
deve notificar qual a tecla pressionada e coloc-lo em uma fila. Um programa pode ento verificar periodicamente
a fila para novos eventos e reagir ao encontrar.

claro que ha sempre uma responsabilidade de verificar a fila e execut-las vrias vezes, isso necessrio
porque ha uma latncia entre a presso da tecla e a leitura da fila pelo programa com isso o software pode sentir
que no esta tendo resposta. Esta abordagem chamada de polling . A maioria dos programadores tentam
evitar essa abordagem sempre que possvel.

A melhor mecanismo para o sistema subjacente dar ao nosso cdigo a chance de reagir a eventos que
ocorrerem. Os browsers podem fazerem isto por que nos permite registrar funes como manipuladores para
eventos especficos.

<p>Click this document to activate the handler.</p>


<script>
addEventListener("click", function() {
console.log("You clicked!");
});
</script>

A funo addEventListener registra seu segundo argumento sempre que o evento descrito por seu primeiro
argumento chamado.

Eventos e ns do DOM
Cada navegador tem seu manipulador de eventos registrado em um contexto. Quando voc chamar
addEventListener como mostramos anteriormente voc estara chamando um mtodo em todo window no
navegador o escopo global equivalente ao objeto window . Cada elemento DOM tem seu prprio mtodo
addEventListener que permite ouvir eventos especificamente para cada elemento.

179
<button>Click me</button>
<p>No handler here.</p>
<script>
var button = document.querySelector("button");
button.addEventListener("click", function() {
console.log("Button clicked.");
});
</script>

O exemplo atribuiu um manipulador para um n de boto. Assim quando existir um clique no boto o manipulador
sera executado, enquanto no resto do documento no.

Dar a um n um atributo onclick tem um efeito similar. Mas um n tem apenas um atributo onclick para que voc
possa registrar apenas um manipulador por n para que voc no substitua acidentalmente um manipulador que
j foi registrado. O mtodo addEventListener permite que voc adicione vrios manipuladores.

O mtodo removeEventListener quando chamado com argumentos semelhante ao addEventListener , mas ele
remove o manipulador que foi registrado.

<button>Act-once button</button>
<script>
var button = document.querySelector("button");
function once() {
console.log("Done.");
button.removeEventListener("click", once);
}
button.addEventListener("click", once);
</script>

Ele capaz de cancelar um registro de manipulador de uma funo, precisamos dar-lhe um nome para que
possamos utilizar tanto para addEventListener quanto para removeEventListener .

Os objetos de evento
Embora tenhamos ignorado os exemplos anteriores as funes manipuladoras de eventos so passados via
argumento e chamamos de objeto de evento. Este objeto nos d informaes adicionais sobre o evento. Por
exemplo, se queremos saber qual boto do mouse que foi pressionado podemos observar as propriedades do
objeto de evento.

<button>Click me any way you want</button>


<script>
var button = document.querySelector("button");
button.addEventListener("mousedown", function(event) {
if (event.which == 1)
console.log("Left button");
else if (event.which == 2)
console.log("Middle button");
else if (event.which == 3)
console.log("Right button");
});
</script>

As informaes armazenadas em um objeto de evento so diferentes dependendo do tipo de evento. Vamos


discutir vrios tipos mais adiante neste captulo. Propriedade de tipo do objeto sempre detm uma cadeia que
identifica o evento(por exemplo, "click" ou "mousedown" ).

180
Propagao
Os manipuladores de eventos registrados em ns tambm recebero alguns eventos que ocorrem nos filhos. Se
um boto dentro de um pargrafo clicado manipuladores de eventos no pargrafo tambm vai receber o evento
click .

Mas se tanto o pargrafo e o boto tem um manipulador o manipulador mais especfico o do boto e sera
chamado primeiro. O evento foi feito para propagar para o exterior a partir do n onde aconteceu ate o n pai do n
raiz do documento. Finalmente depois de todos os manipuladores registrados em um n especfico tiveram sua
vez manipuladores registrados em todo window tem a chance de responder ao evento.

A qualquer momento um manipulador de eventos pode chamar o mtodo stopPropagation para evitar que os
manipuladores mais acima possam receberem o evento. Isso pode ser til quando por exemplo, se voc tem um
boto dentro de outro elemento clicvel e voc no quer o clique no boto acontea se houver algum
compartamento de clique no elemento exterior.

O exemplo a seguir registra manipuladores "mousedown" em ambos no boto e no pargrafo e em torno dele.
Quando clicado com o boto direito do mouse o manipulador do boto chama stopPropagation , o que impedir o
manipulador no pargrafo de executar. Quando o boto clicado com outro boto do mouse os dois
manipuladores so executados.

<p>A paragraph with a <button>button</button>.</p>


<script>
var para = document.querySelector("p");
var button = document.querySelector("button");
para.addEventListener("mousedown", function() {
console.log("Handler for paragraph.");
});
button.addEventListener("mousedown", function(event) {
console.log("Handler for button.");
if (event.which == 3)
event.stopPropagation();
});
</script>

A maioria dos objetos de evento tem uma propriedade de destino que se refere ao n onde eles se originaram.
Voc pode usar essa propriedade para garantir que voc no est lidando com algo que acidentalmente propagou
a partir de um n que voc no queira lidar.

Tambm possvel usar uma propriedade de destino para lanar uma ampla rede para um tipo especfico de
evento. Por exemplo, se voc tem um n que contm uma longa lista de botes, pode ser mais conveniente
registrar um nico manipulador de clique para o n do exterior e que ele use a propriedade de destino para
descobrir se um boto foi clicado, ao invs de se registrar manipuladores individuais sobre todos os botes.

<button>A</button>
<button>B</button>
<button>C</button>
<script>
document.body.addEventListener("click", function(event) {
if (event.target.nodeName == "BUTTON")
console.log("Clicked", event.target.textContent);
});
</script>

Aes padro

181
Muitos eventos tm sua ao padro que lhes esto associados. Se voc clicar em um link voc ser levado para
outra pgina. Se voc pressionar a seta para baixo o navegador vai rolar a pgina para baixo. Se voc clicar com o
boto direito voc tera um menu e assim por diante.

Para a maioria dos tipos de eventos, os manipuladores de eventos de JavaScript so chamados antes do
comportamento padro. Se o condutor no quer que o comportamento normal acontea pode simplesmente
chamar o mtodo preventDefault no objeto de evento.

Isso pode ser usado para implementar seus prprios atalhos de teclado ou menus. Ele tambm pode ser
utilizado para interferir como um comportamento desagradavelmente que os utilizadores no esperam. Por
exemplo aqui est um link que no podem ser clicvel:

<a href="https://developer.mozilla.org/">MDN</a>
<script>
var link = document.querySelector("a");
link.addEventListener("click", function(event) {
console.log("Nope.");
event.preventDefault();
});
</script>

Tente no fazer tais coisas, a menos que voc tem uma boa razo para isso. Para as pessoas que usam sua
pgina isso pode ser desagradvel quando o comportamento que eles esperam so quebrados.

Dependendo do navegador alguns eventos no podem ser interceptados. No Chrome por exemplo, os atalhos de
teclado para fechar a aba atual (Ctrl- W ou Command-W) no pode ser manipulado por JavaScript.

Evento de tecla
Quando uma tecla do teclado pressionado, o seu browser dispara um evento "keydown" quando ele liberado
um evento de "keyup" emitido.

<p>This page turns violet when you hold the V key.</p>


<script>
addEventListener("keydown", function(event) {
if (event.keyCode == 86)
document.body.style.background = "violet";
});
addEventListener("keyup", function(event) {
if (event.keyCode == 86)
document.body.style.background = "";
});
</script>

O evento "keydown" acionado no s quando a tecla fisicamente empurrada para baixo. Quando uma tecla
pressionada e mantida o evento disparado novamente toda vez que se repete a tecla. Por exemplo se voc
quiser aumentar a acelerao de um personagem do jogo quando uma tecla de seta pressionado e diminuido
somente quando a tecla liberada voc tem que ter cuidado para no aument-lo novamente toda vez que se
repete a tecla ou vai acabar com os valores involuntariamente enormes.

O exemplo anterior nos atentou para a propriedade keyCode do objeto de evento. Isto como voc pode identificar
qual tecla est sendo pressionada ou solta. Infelizmente no sempre bvio traduzir o cdigo numrico para uma
tecla.

Para as teclas de letras e nmeros, o cdigo da tecla associado ser o cdigo de caracteres Unicode associado
as letras maisculas ou nmero impresso na tecla. O mtodo charCodeAt que pertence a propriedade String nos
d uma maneira de encontrar este cdigo.

182
console.log("Violet".charCodeAt(0));
// 86
console.log("1".charCodeAt(0));
// 49

Outras teclas tm cdigos previsveis. A melhor maneira de encontrar os cdigos que voc precisa geralmente
experimentar o registo de um manipulador de eventos de tecla que registra os cdigos de chave que ela recebe
quando pressionado a tecla que voc est interessado.

Teclas modificadoras como Shift, Ctrl, Alt e Command(do Mac) geram eventos de teclas apenas como teclas
normais. Mas quando se olha para as combinaes de teclas, voc tambm pode descobrir se essas teclas so
pressionadas verificando as propriedades de eventos shiftKey , ctrlKey , altKey e metakey tanto para teclado
quanto para mouse.

<p>Press Ctrl-Space to continue.</p>


<script>
addEventListener("keydown", function(event) {
if (event.keyCode == 32 && event.ctrlKey)
console.log("Continuing!");
});
</script>

Os eventos de "keydown" e "keyup" do informaes sobre a tecla fsica que est sendo pressionado. Mas e se
voc est interessado no texto que est sendo digitado? Conseguir o texto a partir de cdigos de tecla estranho.
Em vez disso existe um outro evento "keypress" que acionado logo aps "keydown" (repetido junto com
"keydown" quando a tecla solta) mas apenas para as teclas que produzem entrada de caracteres. A propriedade
charCode no objeto do evento contm um cdigo que pode ser interpretado como um cdigo de caracteres
Unicode . Podemos usar a funo String.fromCharCode para transformar esse cdigo em uma verdadeira cadeia de
caracteres simples.

<p>Focus this page and type something.</p>


<script>
addEventListener("keypress", function(event) {
console.log(String.fromCharCode(event.charCode));
});
</script>

O n DOM onde um evento de tecla se origina depende do elemento que tem o foco quando a tecla for
pressionada. Ns normais no podem ter o foco(a menos que voc de um atributo tabindex ) o focu ocorre
normalmente para os ns links, botes e campos de formulrio. Voltaremos a formar campos no Captulo 18.
Quando nada em particular tem foco document.body o um dos principais eventos dos destinos principais.

Evento de mouse
Pressionar o boto do mouse tambm provoca uma srie de eventos para ser emitido. O "mousedown" e "mouseup"

so semelhantes aos "keydown" e "keyup" e so acionados quando o boto pressionado e liberado. Estes iro
acontecer no DOM que esto abaixo do ponteiro do mouse quando o evento ocorrer.

Aps o evento de "mouseup" um evento "click" acionado no n mais especfico que continha tanto ao
pressionar e liberar o boto. Por exemplo se eu pressionar o boto do mouse em um pargrafo e em seguida,
movo o ponteiro para outro pargrafo e solto o boto o evento de "click" acontecer em ambos pargrafos.

Se dois cliques acontecem juntos um evento de "dblclick" (clique duplo) emitido tambm aps o segundo
evento de clique.

183
Para obter informaes precisas sobre o local onde aconteceu um evento do mouse voc pode olhar para as
suas propriedades pageX e pageY , que contm as coordenadas do evento(em pixels) em relao ao canto
superior esquerdo do documento.

A seguir veja a implementao de um programa de desenho primitivo. Toda vez que voc clique no documento ele
acrescenta um ponto sob o ponteiro do mouse. Veja o Captulo 19 um exemplo de programa de desenho menos
primitivo.

<style>
body {
height: 200px;
background: beige;
}
.dot {
height: 8px; width: 8px;
border-radius: 4px; /* rounds corners */
background: blue;
position: absolute;
}
</style>
<script>
addEventListener("click", function(event) {
var dot = document.createElement("div");
dot.className = "dot";
dot.style.left = (event.pageX - 4) + "px";
dot.style.top = (event.pageY - 4) + "px";
document.body.appendChild(dot);
});
</script>

As propriedades clientX e clientY so semelhantes aos pageX e pageY mas em relao parte do documento
que est sendo rolado. Estes podem ser teis quando se compara a coordena do mouse com as coordenadas
retornados por getBoundingClientRect que tambm retorna coordenadas relativas da viewport .

Movimento do mouse
Toda vez que o ponteiro do mouse se move, um eventos de "mousemove" disparado. Este evento pode ser usado
para controlar a posio do mouse. Uma situao comum em que isso til ao implementar algum tipo de
funcionalidade de arrastar o mouse.

O exemplo a seguir exibe um programa com uma barra e configura os manipuladores de eventos para que ao
arrastar para a esquerda ou direita a barra se torna mais estreita ou mais ampla:

184
<p>Drag the bar to change its width:</p>
<div style="background: orange; width: 60px; height: 20px">
</div>
<script>
var lastX; // Tracks the last observed mouse X position
var rect = document.querySelector("div");
rect.addEventListener("mousedown", function(event) {
if (event.which == 1) {
lastX = event.pageX;
addEventListener("mousemove", moved);
event.preventDefault(); // Prevent selection
}
});

function moved(event) {
if (event.which != 1) {
removeEventListener("mousemove", moved);
} else {
var dist = event.pageX - lastX;
var newWidth = Math.max(10, rect.offsetWidth + dist);
rect.style.width = newWidth + "px";
lastX = event.pageX;
}
}
</script>

Note que o controlador "mousemove" registrado no window . Mesmo que o mouse vai para fora da barra durante o
redimensionamento ns ainda queremos atualizar seu tamanho e parar de arrastar somente quando o mouse
liberado.

Sempre que o ponteiro do mouse entra ou sai de um n um evento de "mouseover" ou "mouseout" disparado.
Esses dois eventos podem ser usados entre outras coisas, para criar efeitos de foco, mostrando um denominado
algo quando o mouse est sobre um determinado elemento.

Infelizmente no to simples de ativar a criao de um tal efeito com "mouseover" e acabar com ela em
"mouseout" . Quando o mouse se move a partir de um n em um dos seus filhos "mouseout" acionado no n pai,
embora o mouse no chegou a deixar extenso do n. Para piorar as coisas esses eventos se propagam assim
como outros eventos, portanto voc tambm receber eventos "mouseout" quando o mouse deixa um dos ns
filhos do n em que o manipulador registrado.

Para contornar este problema, podemos usar a propriedade relatedTarget dos objetos de eventos criados por
esses eventos. Ele garante que no caso de "mouseover" o elemento que o ponteiro do mouse passou antes e no
caso do "mouseout" o elemento que o ponteiro do mouse ira passar. Ns queremos mudar o nosso efeito hover

apenas quando o relatedTarget est fora do nosso n de destino. Neste caso que este evento realmente
representa um cruzamento de fora para dentro do n(ou ao contrrio).

<p>Hover over this <strong>paragraph</strong>.</p>


<script>
var para = document.querySelector("p");
function isInside(node, target) {
for (; node != null; node = node.parentNode)
if (node == target) return true;
}
para.addEventListener("mouseover", function(event) {
if (!isInside(event.relatedTarget, para))
para.style.color = "red";
});
para.addEventListener("mouseout", function(event) {
if (!isInside(event.relatedTarget, para))
para.style.color = "";
});
</script>

185
A funo isInside percorre os links pai do n at ele atingir o topo do documento(quando nulo) ou encontrar o pai
que est procurando.

Devo acrescentar que um efeito hover como isso pode ser muito mais facilmente alcanado utilizando o pseudo
selector em CSS :hover como o exemplo a seguir mostra. Mas quando o seu efeito hover envolve fazer algo mais
complexo do que apenas mudar um estilo no n de destino, voc deve usar o truque com os eventos de
"mouseover" e "mouseout" .

<style>
p:hover { color: red }
</style>
<p>Hover over this <strong>paragraph</strong>.</p>

Evento de rolagem
Sempre que um elemento rolado um evento de "scroll" disparado sobre ele. Isto tem vrios usos como
saber o que o usurio est olhando(para desativar animaes fora da tela ou o envio de relatrios de espionagem
para o seu quartel general) ou apresentar alguma indicao de progresso(por destacar parte de uma tabela de
contedo ou que mostra um nmero de pgina).

O exemplo a seguir desenha uma barra de progresso no canto superior direito do documento e atualiza enchendo
quando rolada para baixo:

<style>
.progress {
border: 1px solid blue;
width: 100px;
position: fixed;
top: 10px; right: 10px;
}
.progress > div {
height: 12px;
background: blue;
width: 0%;
}
body {
height: 2000px;
}
</style>
<div class="progress"><div></div></div>
<p>Scroll me...</p>
<script>
var bar = document.querySelector(".progress div");
addEventListener("scroll", function() {
var max = document.body.scrollHeight - innerHeight;
var percent = (pageYOffset / max) * 100;
bar.style.width = percent + "%";
});
</script>

Um elemento com uma posio fixa muito parecido com um elemento de posio absoluta, mas ambos
impedem a rolagem junto com o resto do documento. O efeito fazer com que nossa barra de progresso pare no
canto. Dentro dele existe outro elemento que redimensionada para indicar o progresso atual. Usamos % em
vez de px como unidade, definimos a largura de modo que quando o elemento dimensionado em relao ao
conjunto da barra.

A varivel innerHeight nos d a altura de window , devemos subtrair do total altura de sua rolagem para no ter
rolagem quando voc chegar no final do documento.(H tambm uma innerWidth que acompanha o innerHeight .)
Ao dividir pageYOffset a posio de rolagem atual menos posio de deslocamento mximo multiplicando por 100

186
obtemos o percentual da barra de progresso .

Chamando preventDefault em um evento de rolagem no impede a rolagem de acontecer. Na verdade o


manipulador de eventos chamado apenas aps da rolagem ocorrer.

Evento de foco
Quando um elemento entra em foco o navegador dispara um evento de "focus" nele. Quando se perde o foco um
eventos de "blur" disparado.

Ao contrrio dos eventos discutidos anteriormente, esses dois eventos no se propagam. Um manipulador em
um elemento pai no notificado quando um filho ganha ou perde o foco do elemento.

O exemplo a seguir exibe um texto de ajuda para o campo de texto que possui o foco no momento:

<p>Name: <input type="text" data-help="Your full name"></p>


<p>Age: <input type="text" data-help="Age in years"></p>
<p id="help"></p>
<script>
var help = document.querySelector("#help");
var fields = document.querySelectorAll("input");
for (var i = 0; i < fields.length; i++) {
fields[i].addEventListener("focus", function(event) {
var text = event.target.getAttribute("data-help");
help.textContent = text;
});
fields[i].addEventListener("blur", function(event) {
help.textContent = "";
});
}
</script>

O objeto window recebe os eventos de "focus" e "blur" quando o usurio move-se para outra aba ou janela do
navegador a qual o documento esta sendo mostrado.

Evento de load
Quando uma pgina termina de carregar o evento "load" disparado no window e no objeto body da pgina.
Isso muitas vezes usado para programar aes de inicializao que exigem que todo o documento tenha sido
construdo.

Lembre-se que o contedo de tags <script> executado imediatamente quando o tag encontrada. As vezes a
tag <script> processada antes do carregamento total da pgina e ela necessita de algum contedo que ainda
no foi carregado.

Elementos como imagens e tags de script carregam arquivo externo e tem um evento de "load" para indica que
os arquivos que eles fazem referncia foram carregados. Eles so como os eventos de focus e no se
propagam.

Quando uma pgina fechada ou navegao colocado em segundo plano um evento de "beforeunload"
acionado. O uso principal deste evento para evitar que o usurio perca o trabalho acidentalmente por fechar um
documento. Prevenir que a pgina seja fechada no feito com o mtodo preventDefault . Ele feito atravs do
envio de uma string a partir do manipulador. A seqncia ser usado em uma caixa de dilogo que pergunta ao
usurio se ele quer permanecer na pgina ou deix-la. Este mecanismo garante que um usurio seja capaz de
deixar a pgina, mesmo se estiver executado um script malicioso que prefere mant-los para sempre, a fim de
for-los a olhar para alguns anncios que leva alguns segundos.

187
Cronograma do Script de execuo
H vrias coisas que podem causar a inicializao da execuo de um script. A leitura de um tag <script> um
exemplo disto. Um disparo de eventos outra. No captulo 13 discutimos a funo requestAnimationFrame que
agenda uma funo a ser chamada antes de redesenhar a prxima pgina. Essa mais uma forma em que um
script pode comear a correr.

importante entender que disparo de eventos podem ocorrer a qualquer momento, quando h dois scripts em
um nico documento eles nunca iram correr no mesmo tempo. Se um script j est em execuo os
manipuladores de eventos e o pedao de cdigo programado em outras formas teram de esperar por sua vez.
Esta a razo pela qual um documento ir congelar quando um script executado por um longo tempo. O
navegador no pode reagir aos cliques e outros eventos dentro do documento porque ele no pode executar
manipuladores de eventos at que o script atual termine sua execuo.

Alguns ambientes de programao permitem que mltiplas threads de execuo se propaguem ao mesmo
tempo.

Fazer vrias coisas ao mesmo tempo torna um programa mais rpido. Mas quando voc tem vrias aes
tocando nas mesmas partes do sistema, ao mesmo tempo torna-se de uma amplitude muito difcil.

O fato de que os programas de JavaScript fazem apenas uma coisa de cada vez torna a nossa vida mais fcil.
Para os casos em que voc precisar realmente fazer vrias coisas ao muito tempo sem o congelamento da
pgina, os navegadores fornecem algo chamado de web workers . Um web workers um ambiente isolado do
JavaScript que funciona ao lado do principal programa de um documento e pode se comunicar com ele apenas
por envio e recebimento de mensagens.

Suponha que temos o seguinte cdigo em um arquivo chamado code/squareworker.js :

addEventListener("message", function(event) {
postMessage(event.data * event.data);
});

Imagine que esta multiplicao de nmeros seja pesado e com uma computao de longa durao e queremos
performance ento colocamos em uma thread em segundo plano. Este cdigo gera um worker que envia
algumas mensagens e produz respostas.

var squareWorker = new Worker("code/squareworker.js");


squareWorker.addEventListener("message", function(event) {
console.log("The worker responded:", event.data);
});
squareWorker.postMessage(10);
squareWorker.postMessage(24);

A funo postMessage envia uma mensagem o que causa um evento de "message" disparado ao receptor. O roteiro
que criou o worker envia e recebe mensagens atravs do objeto Worker , ao passo que as conversaes de
worker para o script que o criou enviado e ouvido diretamente sobre o seu mbito global no compartilhada-se
do mesmo roteiro original.

Definindo temporizadores
A funo requestAnimationFrame similar setTimeout . Ele agenda outra funo a ser chamado mais tarde. Mas em
vez de chamar a funo na prximo redesenho ele espera por uma determinada quantidade de milissegundos.
Esta pgina muda de azul para amarelo depois de dois segundos:

188
<script>
document.body.style.background = "blue";
setTimeout(function() {
document.body.style.background = "yellow";
}, 2000);
</script>
</script>

s vezes voc precisa cancelar uma funo que voc programou. Isto feito atravs do armazenamento do valor
devolvido por setTimeout e logo em seguida chamando clearTimeout .

var bombTimer = setTimeout(function() {


console.log("BOOM!");
}, 500);

if (Math.random() < 0.5) { // 50% chance


console.log("Defused.");
clearTimeout(bombTimer);
}

A funo cancelAnimationFrame funciona da mesma forma que clearTimeout chamando um valor retornado pelo
requestAnimationFrame que ir cancelar esse frame (supondo que ele j no tenha sido chamado).

Um conjunto de funes semelhante so setInterval e clearInterval so usados para definir timers que devem
repetir a cada X milisegundos.

var ticks = 0;
var clock = setInterval(function() {
console.log("tick", ticks++);
if (ticks == 10) {
clearInterval(clock);
console.log("stop.");
}
}, 200);

Debouncing
Alguns tipos de eventos tm o potencial para disparar rapidamente muitas vezes em uma linha(os eventos
"mousemove" e `"scroll" por exemplo). Ao manusear tais eventos, voc deve ter cuidado para no fazer nada muito
demorado ou seu manipulador vai ocupar tanto tempo que a interao com o documento passa a ficar lento e
instvel.

Se voc precisa fazer algo no trivial em tal manipulador voc pode usar setTimeout para se certificar de que voc
no esteja fazendo isso com muita freqncia. Isto geralmente chamado de debouncing de evento. H vrias
abordagens ligeiramente diferentes para isso.

No primeiro exemplo, queremos fazer algo quando o usurio digitar alguma coisa mas no quero imediatamente,
para todos os eventos de tecla. Quando ele esta digitando rapidamente ns s queremos esperar at que uma
pausa feita. Em vez de realizar uma ao imediatamente no manipulador de eventos vamos definir um tempo
limite em seu lugar. Ns tambm limpamos o tempo limite anterior(se houver), de modo que, quando ocorrer os
eventos juntos(mais perto do que o nosso tempo de espera) o tempo de espera do evento anterior ser
cancelado.

189
<textarea>Type something here...</textarea>
<script>
var textarea = document.querySelector("textarea");
var timeout;
textarea.addEventListener("keydown", function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
console.log("You stopped typing.");
}, 500);
});
</script>

Dando um valor indefinido para clearTimeout ou chamando-o em um tempo limite que j tenha demitido, ele no
tera efeito. Assim no temos que ter cuidado sobre quando cham-lo simplesmente fazemos para todos os
eventos.

Podemos usar um padro ligeiramente diferente se quisermos de respostas no espao de modo que eles
fiquem separados por pelo menos um determinado perodo de tempo, mas quero remove-los durante uma srie
de eventos e no depois. Por exemplo, podemos querer responder a eventos de "mousemove" , mostrando as
coordenadas atuais do mouse, mas apenas a cada 250 milisegundos.

<script>
function displayCoords(event) {
document.body.textContent =
"Mouse at " + event.pageX + ", " + event.pageY;
}

var scheduled = false, lastEvent;


addEventListener("mousemove", function(event) {
lastEvent = event;
if (!scheduled) {
scheduled = true;
setTimeout(function() {
scheduled = false;
displayCoords(lastEvent);
}, 250);
}
});
</script>

Sumrio
Os manipuladores de eventos tornam possvel detectar e reagir sobre eventos que no tm controle direto. O
mtodo addEventListener usado para registrar esse manipulador.

Cada evento tem um tipo( "keydown" , "focus" , e assim por diante) que o identifica. A maioria dos eventos so
chamados em um elementos DOM especficos e ento propagam aos ancestrais desse elemento, permitindo
que manipuladores associados a esses elementos possam lidar com eles.

Quando um manipulador de eventos chamado, passado um objeto de evento com informaes adicionais
sobre o mesmo. Este objeto tambm tem mtodos que nos permitem parar a propagao( stopPropagation ) ou
evitar a manipulao padro do navegador do evento( preventDefault ).

Pressionando uma tecla, eventos de "keydown" , "keypress" e "keyup" so disparados. Pressionar um boto do
mouse, eventos de "mousedown" , "mouseup" e "click" so disparados. Movendo o mouse, eventos de "mousemove" ,
"mouseenter" e "mouseout" so disparados.

190
A rolagem pode ser detectado com o evento de "scroll" , e quando a mudana de foco este eventos podem ser
detectadas com o "focus" e "blur" . Quando o documento termina de carregar, um evento de "load" disparado
no window .

Apenas um pedao de programa JavaScript pode ser executado por vez. Manipuladores de eventos e outros
scripts programados tem que esperar at outros scripts terminarem antes de chegar a sua vez.

Exerccios
Censores de teclado
Entre 1928 e 2013, uma lei Turca proibiu o uso das letras Q, W, X em documentos oficiais. Isso foi parte de uma
iniciativa mais ampla para reprimir culturas Kurdish, essas casos ocorreram na lngua utilizada por pessoas
Kurdish mas no para os turcos de Istambul.

Neste exerccio voc esta fazendo uma coisas ridculas com a tecnologia, eu estou pedindo para voc programar
um campo de texto(uma tag <input type="text"> ) onde essas letras no pode ser digitada.

(No se preocupe em copiar e colar algum exemplo.)

<input type="text">
<script>
var field = document.querySelector("input");
// Your code here.
</script>

Dica

A soluo para este exerccio que envolve o impedindo do comportamento padro dos eventos de teclas. Voc
pode lidar com qualquer evento "keypress" ou "keydown" . Se um dos dois tiver preventDefault chamado sobre ele,
a tecla no aparece.

Identificar a letra digitada requer olhar o cdigo de acesso ou propriedade charCode e comparar isso com os
cdigos para as letras que voc deseja filtrar. Em "keydown" voc no precisa se preocupar com letras
maisculas e minsculas, uma vez que precisa somente identificar somente a tecla pressionada. Se voc decidir
lidar com "keypress" que identifica o carter real digitado voc tem que ter certeza que voc testou para ambos os
casos. Uma maneira de fazer isso seria esta:

/[qwx]/i.test(String.fromCharCode(event.charCode))

Soluo

Trilha do mouse
Nos primeiros dias de JavaScript que era a hora de home pages berrantes com lotes de imagens animadas, as
pessoas viram algumas maneiras verdadeiramente inspiradoras para usar a linguagem.

Uma delas foi a "trilha do mouse" a srie de imagens que viriam a seguir o ponteiro do mouse quando voc muda
o cursor atravs da pgina.

Neste exerccio, eu quero que voc implemente um rastro de mouse. Use posicionadores absolutamente ao
elemento <div> com um tamanho fixo e com uma cor de fundo(consulte o cdigo na seo "mouseclick" para um
exemplo). Crie um grupo de tais elementos e quando o mouse se mover exibir a esteira do ponteiro do mouse de
alguma forma.

191
Existem vrias abordagens possveis aqui. Voc pode fazer a sua soluo simples ou complexa; como voc
quiser. Uma soluo simples para comear manter um nmero fixo de elementos da fuga e percorr-las,
movendo-se o prximo a posio atual do rato cada vez que um evento "mousemove" ocorrer.

<style>
.trail { /* className for the trail elements */
position: absolute;
height: 6px; width: 6px;
border-radius: 3px;
background: teal;
}
body {
height: 300px;
}
</style>

<script>
// Your code here.
</script>

Dica

Para criar os elementos o melhor fazer um loop e anex-las ao documento para poder exibir. Para ser capaz de
poder acessar mais tarde para alterar a sua posio e armazenar os elementos da fuga em uma matriz.

Ciclismo atravs deles pode ser feito mantendo uma varivel de contador e adicionando 1 a ela toda vez que o
evento de "mousemove" disparado. O operador resto(% 10) pode ento ser usado para obter um ndice de matriz
vlida para escolher o elemento que voc deseja posicionar durante um determinado evento.

Outro efeito interessante pode ser alcanado por um sistema de modelagem fsica simples. Use o evento
"mousemove" apenas para atualizar um par de variveis que rastreiam a posio do mouse. Em seguida, use
requestAnimationFrame para simular os elementos de rastros sendo atrados para a posio do ponteiro do mouse.
Em cada passo de animao atualizar a sua posio com base na sua posio relativa para o ponteiro do
mouse(opcionalmente programe uma velocidade que armazenado para cada elemento). Descobrir uma boa
maneira de fazer isso com voc.

Soluo

Tab
A interface com abas um padro comum de design. Ele permite que voc selecione um painel de interface
escolhendo entre uma srie de abas que se destaca acima de um outro elemento.

Neste exerccio voc vai implementar uma interface simples com abas. Escreva uma funo asTabs que leva um
n do DOM e cria uma interface com abas mostrando os elementos filho desse n. Voc dever inserir uma lista
de elementos <button> na parte superior do n e para cada elemento filho devera conter o texto recuperado do
atributo tabname de cada boto. Todos exceto um dos filhos originais devem ser escondidos(dando um estilo de
display: none ) atualmente os n disponveis podem ser selecionados com um click nos botes.

Quando funcionar voc devera mudar o estilo do boto ativo.

192
<div id="wrapper">
<div data-tabname="one">Tab one</div>
<div data-tabname="two">Tab two</div>
<div data-tabname="three">Tab three</div>
</div>
<script>
function asTabs(node) {
// Your code here.
}
asTabs(document.querySelector("#wrapper"));
</script>

Dica

Uma armadilha que voc provavelmente vai encontrar que no podera usar diretamente propriedade childNodes

do n como uma coleo de ns na tabulao. Por um lado quando voc adiciona os botes eles tambm se
tornam ns filhos e acabam neste objeto porque em tempo de execuo. Por outro lado os ns de texto criados
para o espao em branco entre os ns tambm esto l e no deve obter os seus prprios guias.

Para contornar isso vamos comear a construir uma matriz real de todos os filhos do wrapper que tm um
nodeType igual a 1.

Ao registrar manipuladores de eventos sobre os botes as funes de manipulador vai precisar saber qual
separador do elemento est associada ao boto. Se eles so criados em um circuito normal voc pode acessar a
varivel de ndice do ciclo de dentro da funo mas no vai dar-lhe o nmero correto pois essa varivel ter
posteriormente sido alterada pelo loop.

Uma soluo simples usar o mtodo forEach para criar as funes de manipulador de dentro da funo
passada. O ndice de loop que passado como um segundo argumento para essa funo, ser uma varivel
local normal e que no sero substitudos por novas iteraes.

Soluo

193
Plataforma de jogo
Toda realidade um jogo.

Iain Banks, The Player of Games

Meu fascnio inicial com computadores foi como o de muitas crianas, originado por jogos de computadores. Fui
convocado para um pequeno mundo simulado por computadores onde eu poderia manipular as histrias (mais
ou menos) que iam se desenrolando, mais, eu suponho, por causa da maneira que eu poderia projetar a minha
imaginao neles do que pelas possibilidades que eles realmente ofereciam.

Eu no desejo uma carreira na programao de jogos a ningum. Assim como a indstria da msica, a
discrepncia entre os muitos jovens ansiosos que querem trabalhar nela e a demanda real para essas pessoas
cria um ambiente no muito saudvel. Mas escrever jogos para se divertir muito legal.

Este captulo vai falar sobre a implementao de um jogo de plataforma simples. Jogos de Plataforma (ou jogos
de "saltar e correr") so os jogos que esperam o jogador para mover uma figura atravs de um mundo que muitas
vezes bidimensional e visto de lado, onde pode ter a possibilidade de muitos saltos para se mover sobre as
coisas.

O jogo
Nosso jogo ser mais ou menos baseado em Dark blue por Thomas Palef. Eu escolhi este jogo porque
divertido, minimalista e pode ser construdo sem muito cdigo. Observe:

A caixa escura representa o jogador, cuja a tarefa coletar as caixas amarelas (moedas), evitando o material
vermelho (lava). Um nvel (level) concludo quando todas as moedas forem recolhidas.

O jogador pode movimentar o personagem com as setas do teclado para a esquerda, para a direita, ou pular com
a seta para cima. Jumping uma especialidade deste personagem do jogo. Ela pode atingir vrias vezes sua
prpria altura e capaz de mudar de direo em pleno ar. Isto pode no ser inteiramente realista mas ajuda a dar
ao jogador a sensao de estar no controle do avatar na tela.

194
O jogo consiste em um fundo fixo como uma grade e com os elementos que se deslocam, sobrepostos ao fundo.
Cada campo na grade pode estar vazio, slido ou ser uma lava. Os elementos mveis so os jogadores, moedas
e alguns pedaos de lava. Ao contrrio da simulao de vida artificial no Captulo 7, as posies destes
elementos no esto limitadas a grade - suas coordenadas podem ser fracionadas, permitindo movimentos
suaves.

A tecnologia
Ns vamos usar o DOM e o navegador para exibir o jogo e iremos ler a entrada do usurio por manipulao de
eventos de teclas.

O cdigo de triagem e manipulao com o teclado apenas uma pequena parte do trabalho que precisamos
fazer para construir este jogo. A parte do desenho simples, uma vez que tudo parece colorido: criamos
elementos no DOM e usamos styling para dar-lhes uma cor de fundo, tamanho e posio.

Podemos representar o fundo como uma tabela, uma vez que uma grade imutvel de quadrados. Os elementos
de movimento livre podem ser cobertos em cima disso, utilizando-se posicionamentos absolutos.

Em jogos e outros programas, que tm que animar grficos e responder entrada do usurio sem demora
notvel, a eficincia importante. O DOM no foi originalmente projetado para grficos de alto desempenho, mas
o melhor que podemos esperar. Voc viu algumas animaes no captulo 13. Em uma mquina moderna um
jogo simples como este tem um bom desempenho mesmo se no estivermos pensando em otimizao.

No prximo captulo vamos explorar uma outra tecnologia do navegador que a tag <canvas> , onde
proporcionado uma forma mais tradicional para desenhar grficos, trabalhando em termos de formas e pixels em
vez de elementos no DOM.

Nveis
No Captulo 7 usamos matrizes de sequncias para descrever uma grade bidimensional. Ns podemos fazer o
mesmo aqui. Ele nos permitir projetar Level sem antes construir um editor de Level .

Um Level simples ficaria assim:

var simpleLevelPlan = [
" ",
" ",
" x = x ",
" x o o x ",
" x @ xxxxx x ",
" xxxxx x ",
" x!!!!!!!!!!!!x ",
" xxxxxxxxxxxxxx ",
" "
];

Tanto a grade (grid) fixa e os elementos mveis so inclusos no plano. Os caracteres x representam paredes, os
caracteres de espao so para o espao vazio e os ! representam algo fixo, sees de lava que no se
mechem.

O @ define o local onde o jogador comea. Todo o uma moeda e o sinal de igual = representa um bloco de
lava que se move para trs e para a frente na horizontal. Note que a grade para essas regras ser definida para
conter o espao vazio, e outra estrutura de dados usada para rastrear a posio de tais elementos em
movimento.

195
Vamos apoiar dois outros tipos de lava em movimento: O personagem pipe ( | ) para blocos que se deslocam
verticalmente e v por gotejamento de lava verticalmente. Lava que no salta para trs e nem para a frente s se
move para baixo pulando de volta sua posio inicial quando atinge o cho.

Um jogo inteiro composto por vrios Levels que o jogador deve completar. Um Level concludo quando todas
as moedas forem recolhidas. Se o jogador toca a lava o Level atual restaurado sua posio inicial e o jogador
pode tentar novamente.

A leitura de um level
O construtor a seguir cria um objeto de Level . Seu argumento deve ser uma matriz de sequncias que define o
Level .

function Level(plan) {
this.width = plan[0].length;
this.height = plan.length;
this.grid = [];
this.actors = [];

for (var y = 0; y < this.height; y++) {


var line = plan[y], gridLine = [];
for (var x = 0; x < this.width; x++) {
var ch = line[x], fieldType = null;
var Actor = actorChars[ch];
if (Actor)
this.actors.push(new Actor(new Vector(x, y), ch));
else if (ch == "x")
fieldType = "wall";
else if (ch == "!")
fieldType = "lava";
gridLine.push(fieldType);
}
this.grid.push(gridLine);
}

this.player = this.actors.filter(function(actor) {
return actor.type == "player";
})[0];
this.status = this.finishDelay = null;
}

Para deixar o cdigo pequeno, no verificamos entradas erradas. Ele assume que voc sempre entrega um plano
de level adequado, completo, com a posio de incio do jogador e com outros itens essenciais.

Um level armazena a sua largura e altura juntamente com duas matrizes, uma para a grade e um para os agentes
que so os elementos dinmicos. A grade representada como uma matriz de matrizes onde cada uma das
sries internas representam uma linha horizontal, e cada quadrado contm algo ou nulo; para as casas vazias,
ou uma string, indicaremos o tipo do quadrado ("muro" ou "lava").

A matriz contm objetos que rastreiam a posio atual e estado dos elementos dinmicos no level. Cada um
deles dever ter uma propriedade para indicar sua posio (as coordenadas do seu canto superior esquerdo),
uma propriedade size dando o seu tamanho, e uma propriedade type que mantm uma cadeia que identifica o
elemento ("lava", "dinheiro" ou "jogador").

Depois de construir a grid (grade), usaremos o mtodo de filtro para encontrar o objeto jogador que ns
armazenamos em uma propriedade do level . A propriedade status controla se o jogador ganhou ou perdeu.
Quando isto acontece, finishDelay usado para manter o Level ativo durante um curto perodo de tempo, de
modo que uma animao simples pode ser mostrada (repor imediatamente ou avanar o Level ficaria mais
fcil). Este mtodo pode ser usado para descobrir se um Level foi concludo.

196
Level.prototype.isFinished = function() {
return this.status != null && this.finishDelay < 0;
};

Atores
Para armazenar a posio e o tamanho de um ator vamos voltar para o nosso tipo Vector que agrupa uma
coordenada x e y para coordenar um objeto.

function Vector(x, y) {
this.x = x; this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
Vector.prototype.times = function(factor) {
return new Vector(this.x * factor, this.y * factor);
};

O mtodo de escalas temporais de um vetor nos passa uma determinada quantidade. Isso ser til para quando
precisarmos de multiplicar um vetor de velocidade por um intervalo de tempo, para obter a distncia percorrida
durante esse tempo.

Na seo anterior, o objeto actorChars foi usado pelo construtor Level para associar personagens com as
funes do construtor. O objeto parece com isso:

var actorChars = {
"@": Player,
"o": Coin,
"=": Lava, "|": Lava, "v": Lava
};

Trs personagens esto sendo mapeados para o objeto Lava . O construtor Level passa o caractere fonte do ator
como o segundo argumento para o construtor, e o construtor de Lava usa isso para ajustar o seu comportamento
(saltando horizontalmente, saltando verticalmente ou gotejando).

O tipo do jogador construdo da seguinte forma. A velocidade esta sendo armazenada com velocidade atual, que
vai ajudar a simular movimento e gravidade.

function Player(pos) {
this.pos = pos.plus(new Vector(0, -0.5));
this.size = new Vector(0.8, 1.5);
this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";

Como um jogador tem a altura de um quadrado e meio, a sua posio inicial est sendo definida para ser a
metade de um quadrado acima da posio em que o @ personagem apareceu. Desta forma a sua parte inferior
fica alinhada com a parte inferior do quadrado que apareceu.

Ao construir um objeto Lava dinamicamente preciso inicializar o objeto de uma forma diferente, dependendo do
personagem que se baseia. Lava Dinmica se move longitudinalmente em sua velocidade dada at atingir um
obstculo. Nesse ponto, se ele tem uma propriedade repeatPos ele vai pular de volta sua posio inicial
( gotejamento ). Se isso no acontecer, ele ir inverter a sua velocidade e continuar no outro sentido (pular). O
construtor s configura as propriedades necessrias. O mtodo que faz o movimento real ser escrito mais tarde.

197
function Lava(pos, ch) {
this.pos = pos;
this.size = new Vector(1, 1);
if (ch == "=") {
this.speed = new Vector(2, 0);
} else if (ch == "|") {
this.speed = new Vector(0, 2);
} else if (ch == "v") {
this.speed = new Vector(0, 3);
this.repeatPos = pos;
}
}
Lava.prototype.type = "lava";

Coin so atores simples. A maioria dos blocos simplesmente esperam em seus lugares. Mas para animar o
jogo eles recebem um pouco de "oscilao", um ligeiro movimento vertical de vai e volta. Para controlar isto, um
objeto coin armazena uma posio da base, bem como uma propriedade que controla a oscilao da fase do
movimento no salto. Juntas essas propriedades determinam a posio real da moeda (armazenada na
propriedade pos ).

function Coin(pos) {
this.basePos = this.pos = pos.plus(new Vector(0.2, 0.1));
this.size = new Vector(0.6, 0.6);
this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";

No captulo 13 vimos que Math.sin nos d a coordenada y de um ponto em um crculo. Isso para coordenar
um vai e vem em forma de onda suave medida que avanamos o crculo, fazendo a funo seno se tornar til
para a modelagem de um movimento ondulatrio.

Para evitar uma situao em que todas as moedas se movam para cima ou para baixo de forma sncrona, a fase
inicial de cada moeda aleatria. A fase da onda de Math.Sin , a largura de uma onda produzida, de 2.
Multiplicamos o valor retornado pelo Math.random por esse nmero para dar a posio inicial de uma moeda de
forma aleatria.

Agora escrevemos todas as peas necessrias para representar o Level nesse estado.

var simpleLevel = new Level(simpleLevelPlan);


console.log(simpleLevel.width, "by", simpleLevel.height);
// 22 by 9

A tarefa a seguir deve exibir tais levels na tela, e assim modelar o tempo do movimento entre deles.

Tarefa de encapsulamento
A maior parte do cdigo neste captulo no ira se preocupar com o encapsulamento. Isto tem duas razes. Em
primeiro lugar o encapsulamento exige esforo extra. Em programas maiores isso requer conceitos adicionais de
interfaces a serem introduzidas. Como s h cdigo para voc enviar ao leitor que esta jogando com seus olhos
vidrados, fiz um esforo para manter o programa pequeno.

Em segundo lugar, os vrios elementos neste jogo esto to ligados que se o comportamento de um deles
mudar, improvvel que qualquer um dos outros seriam capazes de ficar na mesma ordem. As interfaces e os
elementos acabam codificando uma srie de suposies sobre a forma de como o jogo funciona. Isso os torna
muito menos eficazes - sempre que voc altera uma parte do sistema, voc ainda tem que se preocupar com a
forma como ela afeta as outras partes, isto porque suas interfaces no cobrem a nova situao.

198
Alguns pontos de corte que existem em um sistema so as separaes atravs de interfaces rigorosas, mas em
outros casos no. Tentar encapsular algo que no um limite adequado uma maneira de desperdiar uma
grande quantidade de energia. Quando voc est cometendo este erro, normalmente voc vai perceber que suas
interfaces estaro ficando desajeitadamente grandes e detalhadas, e que elas precisam ser modificadas muitas
vezes, durante a evoluo do programa.

H uma coisa que vamos encapsular neste captulo que o subsistema de desenho. A razo para isso que ns
vamos mostrar o mesmo jogo de uma maneira diferente no prximo captulo. Ao colocar o desenho atrs de uma
interface, podemos simplesmente carregar o mesmo programa de jogo l e ligar um novo mdulo para exibio.

Desenho
O encapsulamento do cdigo de desenho feito atravs da definio de um objeto de exibio de um
determinado Level . O tipo de exibio que definimos neste captulo chamado de DOMDisplay , e usaremos
elementos simples do DOM para mostrar o Level .

Ns estaremos usando uma folha de estilo para definir as cores reais e outras propriedades fixas dos elementos
que faro parte do jogo. Tambm seria possvel atribuir diretamente as propriedades de estilo dos elementos
quando os criamos, mas queremos produzir programas mais detalhados.

A seguinte funo auxiliar fornece uma maneira curta para criar um elemento e dar-lhe uma classe:

function elt(name, className) {


var elt = document.createElement(name);
if (className) elt.className = className;
return elt;
}

O modo de exibio criado dando-lhe um elemento pai a que se deve acrescentar e um objeto de Level .

function DOMDisplay(parent, level) {


this.wrap = parent.appendChild(elt("div", "game"));
this.level = level;

this.wrap.appendChild(this.drawBackground());
this.actorLayer = null;
this.drawFrame();
}

Levando em considerao o fato de que appendChild retorna o elemento ao criar o contedo do elemento, ento
podemos armazen-lo na suas propriedade com apenas uma nica instruo.

O fundo do Level , que nunca muda, desenhado apenas uma vez. Os atores so redesenhados toda vez que o
display for atualizado. A propriedade actorLayer ser utilizada para controlar o elemento que contm os agentes,
de modo que elas possam ser facilmente removidas e substitudas.

Nossas coordenadas e tamanhos so rastreadas em unidades relativas ao tamanho do grid , onde o tamanho
ou distncia de 1 significa uma unidade do grid . Ao definir os tamanhos de pixel vamos ter que escalar essas
coordenadas, tudo no jogo seria ridiculamente pequeno em um nico pixel por metro quadrado. A varivel de
escala indica o nmero de pixels que uma nica unidade ocupa na tela.

199
var scale = 20;

DOMDisplay.prototype.drawBackground = function() {
var table = elt("table", "background");
table.style.width = this.level.width * scale + "px";
this.level.grid.forEach(function(row) {
var rowElt = table.appendChild(elt("tr"));
rowElt.style.height = scale + "px";
row.forEach(function(type) {
rowElt.appendChild(elt("td", type));
});
});
return table;
};

Como mencionado anteriormente o fundo desenhado com um elemento <table> . Este corresponde estrutura
da propriedade grid onde cada linha transformada em uma linha da tabela (elemento <tr> ). As cordas na
grade so usadas como nomes de classe para a clula da tabela (elemento <td> ). O seguinte CSS ajuda a olhar
o resultado do quadro como o fundo que queremos:

.background { background: rgb(52, 166, 251);


table-layout: fixed;
border-spacing: 0; }
.background td { padding: 0; }
.lava { background: rgb(255, 100, 100); }
.wall { background: white; }

Alguns deles ( table-layout , border-spacing e padding ) so simplesmente usados para suprimir o comportamento
padro indesejado. Ns no queremos que o layout da tabela dependa do contedo de suas clulas, e ns no
queremos espao entre as clulas da tabela ou padding dentro deles.

A regra de background define a cor de fundo. No CSS permitido as cores serem especificadas tanto com palavras
( write ) tanto com um formato como RGB ( R, G, B ) onde os componentes so vermelho, verde e azul ou
separados em trs nmeros de 0 a 255. Assim em rgb(52, 166, 251) , o componente vermelho de 52 o verde
166 e azul 251. Como o componente azul maior, a cor resultante ser azulada. Voc pode ver que na regra das
lavas o primeiro nmero (vermelho) o maior.

Chamamos a cada ator criado por um elemento no DOM, e para ele definimos sua posio e o tamanho desse
elemento com base nas propriedades do ator. Os valores devem ser multiplicados por escala e convertidos para
unidades de pixels do jogo.

DOMDisplay.prototype.drawActors = function() {
var wrap = elt("div");
this.level.actors.forEach(function(actor) {
var rect = wrap.appendChild(elt("div",
"actor " + actor.type));
rect.style.width = actor.size.x * scale + "px";
rect.style.height = actor.size.y * scale + "px";
rect.style.left = actor.pos.x * scale + "px";
rect.style.top = actor.pos.y * scale + "px";
});
return wrap;
};

Para dar mais classe ao elemento separamos os nomes de classe com espaos. No cdigo CSS abaixo
mostramos a classe ator que nos d os atores com sua posio absoluta. O seu nome o tipo usado como uma
classe extra para dar-lhes uma uma cor diferente. No temos que definir a classe de lava novamente porque
vamos reutilizar a classe para os quadradinhos de lava que definimos anteriormente.

200
.actor { position: absolute; }
.coin { background: rgb(241, 229, 89); }
.player { background: rgb(64, 64, 64); }

Quando se atualiza a exibio, o mtodo drawFrame que foi passado primeiro remove os velhos grficos do ator, se
houver algum, e em seguida redesenha-os em suas novas posies. Pode ser tentador tentar reutilizar os
elementos DOM para os atores, mas para fazer esse trabalho seria preciso uma grande quantidade de fluxo de
informao adicional entre o cdigo de exibio e o cdigo de simulao. Precisaramos associar os atores com
os elementos do DOM e o cdigo de desenho, a remoo dos elementos feita quando seus atores
desaparecem. Uma vez que normalmente no teremos bastante atores no jogo, redesenhar todos eles no custa
caro.

DOMDisplay.prototype.drawFrame = function() {
if (this.actorLayer)
this.wrap.removeChild(this.actorLayer);
this.actorLayer = this.wrap.appendChild(this.drawActors());
this.wrap.className = "game " + (this.level.status || "");
this.scrollPlayerIntoView();
};

Ao adicionar o estado atual do Level com um nome de classe para o wrapper podemos denominar que o ator do
jogador esta ligeiramente diferente quando o jogo est ganho ou perdido, para isso basta adicionar uma regra no
CSS que tem efeito apenas quando o jogador tem um elemento ancestral com uma determinada classe.

.lost .player {
background: rgb(160, 64, 64);
}

.won .player {
box-shadow: -4px -7px 8px white, 4px -7px 8px white;
}

Depois de tocar em lava a cor do jogador ficara vermelho escuro escaldante. Quando a ltima moeda for coletada
ns usamos duas caixas brancas com sombras borradas, um para o canto superior esquerdo e outro para o
canto superior direito, para criar um efeito de halo branco.

No podemos assumir que os Level sempre se encaixem na janela de exibio. por isso que a chamada
scrollPlayerIntoView necessria e garante que se o Level est saindo do visor ns podemos rolar o viewport

para garantir que o jogador est perto de seu centro. O seguinte CSS d ao elemento DOM o embrulho do jogo
com um tamanho mximo e garante que qualquer coisa que no se destaca da caixa do elemento no visvel.
Tambm damos ao elemento exterior uma posio relativa, de modo que os atores esto posicionados no seu
interior em relao ao canto superior esquerdo do Level .

.game {
overflow: hidden;
max-width: 600px;
max-height: 450px;
position: relative;
}

No mtodo scrollPlayerIntoView encontramos a posio do jogador e atualizamos a posio de rolagem do


elemento conforme seu envolvimento. Vamos mudar a posio de rolagem atravs da manipulao das
propriedades desses elementos com os eventos de scrollLeft e scrollTop para quando o jogador estiver muito
perto do canto.

201
DOMDisplay.prototype.scrollPlayerIntoView = function() {
var width = this.wrap.clientWidth;
var height = this.wrap.clientHeight;
var margin = width / 3;

// The viewport
var left = this.wrap.scrollLeft, right = left + width;
var top = this.wrap.scrollTop, bottom = top + height;

var player = this.level.player;


var center = player.pos.plus(player.size.times(0.5))
.times(scale);

if (center.x < left + margin)


this.wrap.scrollLeft = center.x - margin;
else if (center.x > right - margin)
this.wrap.scrollLeft = center.x + margin - width;
if (center.y < top + margin)
this.wrap.scrollTop = center.y - margin;
else if (center.y > bottom - margin)
this.wrap.scrollTop = center.y + margin - height;
};

A forma de como o centro do jogador encontrado mostra como os mtodos em nosso tipo Vector permite
calcular os objetos a serem escritos de forma legvel. Para encontrar o centro do ator ns adicionamos a sua
posio (o canto superior esquerdo) e a metade do seu tamanho. Esse o centro em coordenadas de Level

mas precisamos dele em coordenadas de pixel, por isso em seguida vamos multiplicar o vetor resultante de
nossa escala de exibio.

Em seguida uma srie de verificaes so feitas para a posio do jogador dentro e fora do intervalo permitido.
Note-se que, as vezes, isto ir definir as coordenadas absolutas de rolagem, abaixo de zero ou fora da rea de
rolagem do elemento. Isso bom pois o DOM vai ser obrigado a ter valores verdadeiros. Definir scrollLeft para
-10 far com que ele torne 0 .

Teria sido um pouco mais simples tentar deslocarmos o jogador para o centro da janela. Mas isso cria um efeito
bastante chocante. Como voc est pulando a viso vai mudar constantemente de cima e para baixo. mais
agradvel ter uma rea "neutra" no meio da tela onde voc pode se mover sem causar qualquer rolagem.

Finalmente, vamos precisar de algo para limpar um Level para ser usado quando o jogo se move para o prximo
Level ou redefine um Level .

DOMDisplay.prototype.clear = function() {
this.wrap.parentNode.removeChild(this.wrap);
};

Estamos agora em condies de apresentar o nosso melhor Level atualmente.

<link rel="stylesheet" href="css/game.css">

<script>
var simpleLevel = new Level(simpleLevelPlan);
var display = new DOMDisplay(document.body, simpleLevel);
</script>

A tag <link> quando usado com rel="stylesheet" torna-se uma maneira de carregar um arquivo CSS em uma
pgina. O arquivo game.css contm os estilos necessrios para o nosso jogo.

Movimento e coliso
202
Agora estamos no ponto em que podemos comear a adicionar movimento, que um aspecto mais interessante
do jogo. A abordagem bsica tomada pela maioria dos jogos como este consiste em dividir o tempo em
pequenos passos, e para cada etapa movemos os atores por uma distncia correspondente a sua velocidade
(distncia percorrida por segundo), multiplicada pelo tamanho do passo em tempo (em segundos).

Isto fcil. A parte difcil lidar com as interaes entre os elementos. Quando o jogador atinge uma parede ou o
cho ele no devem simplesmente se mover atravs deles. O jogo deve notar quando um determinado
movimento faz com que um objeto bata sobre outro objeto e responder adequadamente. Para paredes o
movimento deve ser interrompido. As moedas devem serem recolhidas e assim por diante.

Resolver este problema para o caso geral uma grande tarefa. Voc pode encontrar as bibliotecas, geralmente
chamadas de motores de fsica, que simulam a interao entre os objetos fsicos em duas ou trs dimenses.
Ns vamos ter uma abordagem mais modesta neste captulo, apenas manipularemos as colises entre objetos
retangulares e manusearemos de uma forma bastante simplista.

Antes de mover o jogador ou um bloco de lava, testamos se o movimento iria lev-los para dentro de uma parte
no vazio de fundo. Se isso acontecer, ns simplesmente cancelamos o movimento por completo. A resposta a tal
coliso depende do tipo de ator - o jogador vai parar, enquanto um bloco de lava se recupera.

Essa abordagem requer alguns passos para termos uma forma reduzida, uma vez que o objeto que esta em
movimento para antes dos objetos se tocarem. Se os intervalos de tempo (os movimentos dos passos) so muito
grandes, o jogador iria acabar em uma distncia perceptvel acima do solo. A outra abordagem
indiscutivelmente melhor mas mais complicada, que seria encontrar o local exato da coliso e se mudar para l.
Tomaremos uma abordagem simples de esconder os seus problemas, garantindo que a animao prossiga em
pequenos passos.

Este mtodo nos diz se um retngulo (especificado por uma posio e um tamanho) coincide com qualquer
espao no vazio na grid de fundo:

Level.prototype.obstacleAt = function(pos, size) {


var xStart = Math.floor(pos.x);
var xEnd = Math.ceil(pos.x + size.x);
var yStart = Math.floor(pos.y);
var yEnd = Math.ceil(pos.y + size.y);

if (xStart < 0 || xEnd > this.width || yStart < 0)


return "wall";
if (yEnd > this.height)
return "lava";
for (var y = yStart; y < yEnd; y++) {
for (var x = xStart; x < xEnd; x++) {
var fieldType = this.grid[y][x];
if (fieldType) return fieldType;
}
}
};

Este mtodo calcula o conjunto de quadrados que o body se sobrepe usando Math.floor e Math.ceil nas
coordenadas do body . Lembre-se que as unidades de tamanho dos quadrados so 1 por 1. Arredondando os
lados de uma caixa de cima para baixo temos o quadrado da gama de fundo que tem os toques nas caixas.

203
Se o corpo se sobressai do Level , sempre retornaremos "wall" para os lados e na parte superior e "lava" para
o fundo. Isso garante que o jogador morra ao cair para fora do mundo. Quando o corpo esta totalmente no interior
da grid , nosso loop sobre o bloco de quadrados encontra as coordenadas por arredondamento e retorna o
contedo do primeira nonempty .

Colises entre o jogador e outros atores dinmicos (moedas, lava em movimento) so tratadas depois que o
jogador se mudou. Quando o movimento do jogador coincide com o de outro ator, se for uma moeda feito o
efeito de recolha ou se for lava o efeito de morte ativado.

Este mtodo analisa o conjunto de atores, procurando um ator que se sobrepe a um dado como um argumento:

Level.prototype.actorAt = function(actor) {
for (var i = 0; i < this.actors.length; i++) {
var other = this.actors[i];
if (other != actor &&
actor.pos.x + actor.size.x > other.pos.x &&
actor.pos.x < other.pos.x + other.size.x &&
actor.pos.y + actor.size.y > other.pos.y &&
actor.pos.y < other.pos.y + other.size.y)
return other;
}
};

Atores e aes
O mtodo animate do tipo Level d a todos os atores do level a chance de se mover. Seu argumento step traz
o tempo do passo em segundos. O objeto key contm informaes sobre as teclas que o jogador pressionou.

var maxStep = 0.05;

Level.prototype.animate = function(step, keys) {


if (this.status != null)
this.finishDelay -= step;

while (step > 0) {


var thisStep = Math.min(step, maxStep);
this.actors.forEach(function(actor) {
actor.act(thisStep, this, keys);
}, this);
step -= thisStep;
}
};

Quando a propriedade status do level tem um valor no nulo (que o caso de quando o jogador ganhou ou
perdeu), devemos contar para baixo a propriedade finishDelay que controla o tempo entre o ponto onde o jogador
ganhou ou perdeu e o ponto onde ns paramos de mostrar o Level .

O loop while corta o passo de tempo onde estamos animando em pedaos pequenos. Ele garante que nenhum
passo maior do que maxStep tomado. Por exemplo um passo de 0,12 segundo iria ser cortado em dois passos
de 0,05 segundos e um passo de 0,02.

Objetos do ator tem um mtodo act que toma como argumentos o tempo do passo, o objeto do level que
contm as chaves de objeto. Aqui est um exemplo para o tipo de ator (Lava) que ignora as teclas de objeto:

204
Lava.prototype.act = function(step, level) {
var newPos = this.pos.plus(this.speed.times(step));
if (!level.obstacleAt(newPos, this.size))
this.pos = newPos;
else if (this.repeatPos)
this.pos = this.repeatPos;
else
this.speed = this.speed.times(-1);
};

Ele calcula uma nova posio atravs da adio do produto do tempo do passo e a sua velocidade atual para sua
antiga posio. Se nenhum bloco de obstculos tem uma nova posio ele se move para l. Se houver um
obstculo, o comportamento depende do tipo da lava: lava e bloco de gotejamento tem uma propriedade
repeatPos para ele poder saltar para trs quando bater em algo. Saltando, a lava simplesmente inverte sua
velocidade (multiplica por -1) a fim de comear a se mover em outra direo.

As moedas usam seu mtodo act para se mover. Elas ignoram colises uma vez que esto simplesmente
oscilando em torno de seu prprio quadrado, e colises com o jogador sero tratadas pelo mtodo act do
jogador.

var wobbleSpeed = 8, wobbleDist = 0.07;

Coin.prototype.act = function(step) {
this.wobble += step * wobbleSpeed;
var wobblePos = Math.sin(this.wobble) * wobbleDist;
this.pos = this.basePos.plus(new Vector(0, wobblePos));
};

A propriedade wobble atualizada para controlar o tempo e em seguida utilizada como um argumento para
math.sin para criar uma onda que usada para calcular sua nova posio.

Isso deixa o prprio jogador. O movimento do jogador tratado separadamente para cada eixo, porque bater no
cho no deve impedir o movimento horizontal, e bater na parede no deve parar a queda ou o movimento de
saltar. Este mtodo implementa a parte horizontal:

var playerXSpeed = 7;

Player.prototype.moveX = function(step, level, keys) {


this.speed.x = 0;
if (keys.left) this.speed.x -= playerXSpeed;
if (keys.right) this.speed.x += playerXSpeed;

var motion = new Vector(this.speed.x * step, 0);


var newPos = this.pos.plus(motion);
var obstacle = level.obstacleAt(newPos, this.size);
if (obstacle)
level.playerTouched(obstacle);
else
this.pos = newPos;
};

O movimento calculado com base no estado das teclas de seta esquerda e direita. Quando um movimento faz
com que o jogador bata em alguma coisa o mtodo playerTouched que chamado no level que lida com
coisas como morrer na lava ou coletar moedas. Caso contrrio o objeto atualiza a sua posio.

Movimento vertical funciona de forma semelhante, mas tem que simular salto e gravidade.

205
var gravity = 30;
var jumpSpeed = 17;

Player.prototype.moveY = function(step, level, keys) {


this.speed.y += step * gravity;
var motion = new Vector(0, this.speed.y * step);
var newPos = this.pos.plus(motion);
var obstacle = level.obstacleAt(newPos, this.size);
if (obstacle) {
level.playerTouched(obstacle);
if (keys.up && this.speed.y > 0)
this.speed.y = -jumpSpeed;
else
this.speed.y = 0;
} else {
this.pos = newPos;
}
};

No incio do mtodo o jogador acelerado verticalmente para ter em conta a gravidade. Ao saltar a velocidade da
gravidade praticamente igual a todas as outras constantes neste jogo que foram criadas por tentativa e erro. Eu
testei vrios valores at encontrar uma combinao agradvel.

Em seguida feito uma verificao para identificar se h obstculos novamente. Se bater em um obstculo h
dois resultados possveis. Quando a seta para cima pressionada e estamos nos movendo para baixo (ou seja,
a coisa que bater abaixo de ns) a velocidade definida como um valor relativamente grande e negativo. Isso faz
com que o jogador salte. Se esse no for o caso, ns simplesmente esbarramos em alguma coisa e a velocidade
zerada.

O mtodo atual parece com isso:

Player.prototype.act = function(step, level, keys) {


this.moveX(step, level, keys);
this.moveY(step, level, keys);

var otherActor = level.actorAt(this);


if (otherActor)
level.playerTouched(otherActor.type, otherActor);

// Losing animation
if (level.status == "lost") {
this.pos.y += step;
this.size.y -= step;
}
};

Depois de se mover o mtodo verifica os outros atores que o jogador est colidindo e chamado o playerTouched

novamente quando encontra um. Desta vez ele passa o objeto ator como segundo argumento, isto , porque se o
outro ator uma moeda, playerTouched precisa saber qual moeda est sendo coletada.

Finalmente quando o jogador morre (toca lava), montamos uma pequena animao que faz com que ele se
"encolha" ou "afunde" reduzindo a altura do objeto jogador.

E aqui o mtodo que manipula as colises entre o jogador e outros objetos:

206
Level.prototype.playerTouched = function(type, actor) {
if (type == "lava" && this.status == null) {
this.status = "lost";
this.finishDelay = 1;
} else if (type == "coin") {
this.actors = this.actors.filter(function(other) {
return other != actor;
});
if (!this.actors.some(function(actor) {
return actor.type == "coin";
})) {
this.status = "won";
this.finishDelay = 1;
}
}
};

Quando a lava tocada, o status do jogo definido como "lost" . Quando uma moeda tocada essa moeda
removida do conjunto de atores e se fosse a ltima, o estado do jogo definido como "won" .

Isso nos da a opo do Level de ser animado. Tudo o que est faltando agora o cdigo que aciona a animao.

Rastreamento de teclas
Para um jogo como este ns no queremos que as teclas tenham efeito apenas quando presionadas. Pelo
contrrio, queremos que o seu efeito (movimentar a figura do jogador) continue movendo o jogador enquanto as
teclas estiverem pressionadas.

Precisamos criar um manipulador de teclas que armazena o estado atual da esquerda, direita e cima das teclas
de seta. Ns tambm queremos chamar preventDefault para essas teclas para no dar rolagem da pgina.

A funo a seguir, quando dado um objeto com o cdigo da tecla e com o nome de propriedade como valores, vai
retornar um objeto que rastreia a posio atual dessas teclas. Ele registra manipuladores de eventos para
"keydown" e "keyup" e, quando o cdigo de tecla no evento est presente no conjunto de cdigos que est sendo
rastreado, executada a atualizao do objeto.

var arrowCodes = {37: "left", 38: "up", 39: "right"};

function trackKeys(codes) {
var pressed = Object.create(null);
function handler(event) {
if (codes.hasOwnProperty(event.keyCode)) {
var down = event.type == "keydown";
pressed[codes[event.keyCode]] = down;
event.preventDefault();
}
}
addEventListener("keydown", handler);
addEventListener("keyup", handler);
return pressed;
}

Note como o mesmo manipulador da funo usado para ambos os tipos de eventos. Ele olha para a
propriedade type do objeto de evento para determinar se o estado da tecla deve ser atualizado para true
("keydown") ou falso ("keyup").

Executar o jogo

207
A funo requestAnimationFrame que vimos no captulo 13 fornece uma boa maneira de animar um jogo. Mas sua
interface bastante primitiva para us-la, o que nos obriga a ficar controlando sua ltima chamada para executar
a funo requestAnimationFrame novamente aps cada frame.

Vamos definir uma funo auxiliar que envolve as partes chatas em uma interface conveniente e nos permitir
simplesmente chamar runAnimation dando-lhe uma funo que espera uma diferena de tempo como um
argumento e desenh-la em um quadro nico. Quando a funo de armao retorna o valor falso a animao
para.

function runAnimation(frameFunc) {
var lastTime = null;
function frame(time) {
var stop = false;
if (lastTime != null) {
var timeStep = Math.min(time - lastTime, 100) / 1000;
stop = frameFunc(timeStep) === false;
}
lastTime = time;
if (!stop)
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}

Temos que definir um passo de quadros mximo de 100 milissegundos(um dcimo de segundo). Quando a aba
ou janela do navegador com a pgina estiver oculto as chamadas requestAnimationFrame ser suspenso at que a
aba ou janela mostrado novamente. Neste caso a diferena entre lasttime ser todo o tempo em que a pgina
estiver oculta. Avanando o jogo, que em uma nica etapa vai parecer fcil mas podemos ter um monte de
trabalho (lembre-se o tempo-splitting no mtodo de animao).

A funo tambm converte os passos de tempo para segundos, que so uma quantidade mais fcil de pensar do
que milissegundos.

A funo de execuo do Level toma um objeto do Level no construtor de uma exposio e opcionalmente uma
funo. Ele exibe o Level (em document.body) e permite que o usurio pea por ele. Quando o Level est
terminado(perda ou ganho), Level de execuo, limpa o visor, para a animao e caso a funo andthen for dada,
chama essa funo com o status do Level .

var arrows = trackKeys(arrowCodes);

function runLevel(level, Display, andThen) {


var display = new Display(document.body, level);
runAnimation(function(step) {
level.animate(step, arrows);
display.drawFrame(step);
if (level.isFinished()) {
display.clear();
if (andThen)
andThen(level.status);
return false;
}
});
}

Um jogo uma sequncia de Level . Sempre que o jogador morre o Level atual reiniciado. Quando um Level

concludo vamos passar para o prximo Level . Isso pode ser expresso pela seguinte funo o que leva um
conjunto de planos de Level (arrays de strings) e um construtor de exibio:

208
function runGame(plans, Display) {
function startLevel(n) {
runLevel(new Level(plans[n]), Display, function(status) {
if (status == "lost")
startLevel(n);
else if (n < plans.length - 1)
startLevel(n + 1);
else
console.log("You win!");
});
}
startLevel(0);
}

Estas funes mostram um estilo peculiar de programao. Ambos runAnimation e Level de execuo so
funes de ordem superior, mas no so no estilo que vimos no captulo 5. O argumento da funo usado para
organizar as coisas para acontecer em algum momento no futuro e nenhuma das funes retorna alguma coisa
til. A sua tarefa de certa forma, agendar aes. Envolvendo estas aes em funes nos d uma maneira de
armazen-las com um valor de modo que eles podem ser chamados no momento certo.

Este estilo de programao geralmente chamado de programao assncrona. Manipulao de eventos


tambm um exemplo deste estilo, vamos ver muito mais do que quando se trabalha com tarefas que podem
levar uma quantidade arbitrria de tempo, como solicitaes de rede no captulo 17 e entrada e sada em geral no
Captulo 20.

H um conjunto de planos de Level disponveis na varivel GAME_LEVELS . Esta pgina alimenta runGame ,
comeando um jogo real:

<link rel="stylesheet" href="css/game.css">

<body>
<script>
runGame(GAME_LEVELS, DOMDisplay);
</script>
</body>

Veja se voc pode vencer. Aqui eu espero vrios Level construdos.

Exerccio
Fim do Jogo
tradicional para jogos de plataforma ter o incio do jogador com um nmero limitado de vidas e subtrair uma vida
cada vez que ele morre. Quando o jogador est sem vidas, o jogo ser reiniciado desde o incio. Ajuste runGame

para implementar as trs vidas ao iniciar.

209
<link rel="stylesheet" href="css/game.css">

<body>
<script>
// The old runGame function. Modify it...
function runGame(plans, Display) {
function startLevel(n) {
runLevel(new Level(plans[n]), Display, function(status) {
if (status == "lost")
startLevel(n);
else if (n < plans.length - 1)
startLevel(n + 1);
else
console.log("You win!");
});
}
startLevel(0);
}
runGame(GAME_LEVELS, DOMDisplay);
</script>
</body>

Dica

A soluo mais bvia seria, tornar a vida uma varivel que vive em runGame e portanto visvel para o
encerramento do startLevel .

Uma outra abordagem que se encaixa com o esprito do resto da funo seria, adicionar um segundo parmetro
para o startLevel que d o nmero de vidas. Quando todo o estado de um sistema armazenado nos
argumentos para uma funo, chamar essa funo fornece uma maneira elegante de fazer a transio para um
novo estado.

Em qualquer caso, quando o Level est perdido dever agora existir duas transies de estado possveis. Se
esse for a ltima vida vamos voltar ao Level zero com o montante inicial de vidas. Se no vamos repetir o Level

atual com menos uma vida restante.

Pausar o jogo
Faa o possvel para fazer uma pausa(suspenso) e retomar o jogo pressionando a tecla Esc.

Isso pode ser feito alterando a execuo funo do Level para usar outro manipulador de eventos de teclado e
interromper ou retomar a animao sempre que a tecla Esc pressionada.

A interface runAnimation no pode se responsabilizar por isso primeira vista, mas basta voc reorganizar a
maneira que RUNLEVEL chamado.

Quando voc tem que trabalhar no h outra coisa que voc pode tentar. O caminho que temos vindo a registrar
manipuladores de eventos de teclas um pouco problemtico. O objeto keys uma varivel global e seus
manipuladores de eventos so mantidas ao redor mesmo quando nenhum jogo est sendo executado. Pode-se
dizer que isso pode vazar para fora do nosso sistema. Estender trackKeys nos da uma maneira de fornecer o
cancelamento do registro e de seus manipuladores e em seguida mudar a execuo do Level para registrar
seus tratadores quando comea e cancela o registro novamente quando ele for concludo.

210
<link rel="stylesheet" href="css/game.css">

<body>
<script>
// The old runLevel function. Modify this...
function runLevel(level, Display, andThen) {
var display = new Display(document.body, level);
runAnimation(function(step) {
level.animate(step, arrows);
display.drawFrame(step);
if (level.isFinished()) {
display.clear();
if (andThen)
andThen(level.status);
return false;
}
});
}
runGame(GAME_LEVELS, DOMDisplay);
</script>
</body>

Dicas

Uma animao pode ser interrompida retornando um valor falso na funo dada ao runAnimation . Ele pode ser
continuado chamando runAnimation novamente.

Para comunicar que a animao deve ser interrompido a funo passada para runAnimation deve retornar falso;
voc pode usar uma varivel que tanto o manipulador de eventos e a funo tenha acesso.

Quando encontrar uma maneira de cancelar o registro dos manipuladores registrados por trackKeys lembre-se
que o mesmo valor funo exata que foi passado para addEventListener deve ser passado para
removeEventListener para remover com xito um manipulador. Assim o valor da funo manipuladora criada em
trackKeys devera estar disponvel para o cdigo que cancela os manipuladores.

Voc pode adicionar uma propriedade para o objeto retornado por trackKeys contendo um ou outro valor da funo
ou um mtodo que manipula ou remove o registro diretamente.

211
Desenhando no canvas
Desenhar uma decepo.

M.C. Escher, citado por Bruno Ernst em The Magic Mirror of M.C. Escher.

Os Browsers permitem de vrias maneiras de mostrarem grficos. A maneira mais simples usar um estilos
para posio e cor de elementos regulares do DOM. Isso pode ser impraticvel, como ficou claro no jogo do
captulo anterior. Podemos adicionar parcialmente uma transparncia no fundo das imagens e ainda girar ou
inclinar algum usando o estilo de transform .

Mas estaramos usando o DOM para algo que no foi originalmente projetado. Algumas tarefas, tais como
desenhar uma linha entre pontos arbitrrios so extremamente difceis de fazer com elementos regulares em
HTML.

Existem duas alternativas. O primeiro baseado em DOM mas utiliza Scalable Vector Graphics( SVG ) ao invs de
elementos HTML. Pense em SVG como um dialeto para descrever documentos que se concentra em formas ao
invs de texto. Voc pode embutir um documento SVG em um documento HTML ou voc pode inclu-lo atravs de
uma tag <img> .

A segunda alternativa chamado de canvas . A tela um nico elemento DOM que encapsula uma imagem. Ele
fornece uma interface de programao para desenhar formas para o espao ocupado pelo n. A principal
diferena entre um canvas e uma imagem de SVG , que em SVG a descrio original das formas preservada
de modo que eles podem ser movidos ou redimensionados em qualquer momento. O canvas por outro lado,
converte as formas para pixels(pontos coloridos em um rastro), logo eles so desenhados e no guardam
informaes do que estes pixels representam. A nica maneira de mover uma forma em canvas limpar a tela(ou
a parte da tela em torno) e redesenhar uma forma em uma nova posio.

SVG
Este livro no vai entrar no assunto SVG em detalhes, mas vou explicar brevemente como ele funciona. No final do
captulo eu vou voltar para os trade-offs que voc deve considerar ao decidir qual mecanismo de desenho
adequado para uma determinada aplicao.

Este um documento HTML com uma imagem SVG simples:

<p>Normal HTML here.</p>


<svg xmlns="http://www.w3.org/2000/svg">
<circle r="50" cx="50" cy="50" fill="red"/>
<rect x="120" y="5" width="90" height="90"
stroke="blue" fill="none"/>
</svg>

O atributo xmlns muda um elemento(e seus filhos) a um namespace diferente de XML. Este namespace
identificado por um URL, especificando o dialeto que estamos falando no momento. As tags <circle> e <rect>

que no existem em HTML no tm um significado em SVG para desenhar formas usando o estilo e posio
especificada para seus atributos.

Essas tags criam elementos no DOM assim como as tags em HTML. Por exemplo, isso muda a cor para ciano do
elemento <circle> :

var circle = document.querySelector("circle");


circle.setAttribute("fill", "cyan");

212
O elemento canvas
Grfico em canvas pode ser desenhado com a tag <canvas> . Voc pode dar a um elemento a largura e altura em
pixel para determinar o seu tamanho.

A nova tela esta vazia, o que significa que totalmente transparente e portanto simplesmente mostra-se com um
espao vazio no documento.

A tag <canvas> destina-se a apoiar os diferentes estilos de desenho. Para ter acesso a uma verdadeira interface
de desenho primeiro precisamos criar um contexto que um objeto, cujos mtodos fornecem a interface de
desenho. Atualmente existem dois estilos de desenho amplamente suportados: "2d" para grficos
bidimensionais e "Web GL" para grficos tridimensionais atravs da interface OpenGL .

Este livro no vai discutir Web GL. Ns esturemos as duas dimenses. Mas se voc estiver interessado em
grficos tridimensionais eu encorajo-vos a olhar para Web GL, que fornece uma interface muito direta com o
hardware com grfico moderno e permite que voc processe cenas eficientemente complicadas utilizando
JavaScript.

Um contexto criado atravs do mtodo getContext sobre o elemento <canvas> .

<p>Before canvas.</p>
<canvas width="120" height="60"></canvas>
<p>After canvas.</p>
<script>
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
context.fillStyle = "red";
context.fillRect(10, 10, 100, 50);
</script>

Depois de criar o objeto de contexto, o exemplo desenha um retngulo vermelho de 100 pixels de largura e 50
pixels de altura em relao ao seu canto superior esquerdo nas coordenadas (10,10).

Assim como em HTML e SVG o sistema que a tela usa puts(0,0) no canto superior esquerdo de coordenadas, e o
eixo y positivo vai para baixo. Ento (10,10) de 10 pixels abaixo e a direita do canto superior esquerdo.

Preenchimento e traado
Na interface uma forma pode ser cheia ou seja, sua rea dada uma determinada cor padro; ou pode ser
riscada o que significa que uma linha desenhada ao longo de sua borda. A mesma terminologia utilizada por
SVG.

O mtodo fillRect preenche um retngulo. preciso ter as coordenadas x e y do canto superior esquerdo do
retngulo, em seguida a sua largura e a sua altura. Um mtodo semelhante strokeRect desenha o contorno de
um retngulo.

Nenhum dos mtodos tem parmetros. A cor do preenchimento e a espessura do traado no so determinados
por argumento do mtodo(como voc espera), mas sim pelas propriedades do contexto do objecto.

As definies de fillStyle pode alterar o jeito que as formas so preenchidas. Ele pode ser definido como uma
string que especifica uma cor de qualquer modo que compreendido por CSS.

A propriedade strokeStyle funciona de forma semelhante, mas determina a cor usada para uma linha. A largura
da linha determinada pela propriedade lineWidth que pode conter qualquer nmero positivo.

213
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.strokeStyle = "blue";
cx.strokeRect(5, 5, 50, 50);
cx.lineWidth = 5;
cx.strokeRect(135, 5, 50, 50);
</script>

Quando nenhuma largura ou altura especificado como atributo, como no exemplo anterior um elemento de tela
adquire uma largura padro de 300 pixels e altura de 150 pixels.

Paths
Um path uma sequncia de linhas. A interface de uma tela 2D tem uma abordagem peculiar de descrever esse
path . Isso feito inteiramente atravs dos efeitos colaterais. Os paths no constituem valores que podem ser
armazenados ou repassados. Se voc deseja fazer algo com um path , voc faz uma sequncia de chamadas de
mtodo para descrever sua forma.

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
for (var y = 10; y < 100; y += 10) {
cx.moveTo(10, y);
cx.lineTo(90, y);
}
cx.stroke();
</script>

Este exemplo cria um path com um nmero de segmentos de linha horizontal e faz traos usando o mtodo
stroke . Cada segmento criado com lineTo comea na posio atual do path . Esta posio normalmente o fim
do ltimo segmento a no ser que moveTo seja chamado. Nesse caso, o prximo segmento comeara na posio
passada para moveTo .

Ao preencher um path (usando o mtodo fill ) cada forma preenchido separadamente. Um path pode conter
vrias formas, cada movimento com moveTo inicia um novo. Mas o path tem de ser fechado(ou seja o seu incio e
fim devem ficar na mesma posio) antes de ser preenchido. Se o path no estiver fechado a linha adicionada
a partir de sua extremidade para o comeo da forma delimitada pelo path como completado e preenchido.

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(50, 10);
cx.lineTo(10, 70);
cx.lineTo(90, 70);
cx.fill();
</script>

Este exemplo estabelece um tringulo cheio. Note que apenas dois dos lados do tringulo so explicitamente
desenhados. A terceira a partir do canto inferior direito ate o topo; implcito e no estar l quando voc traar o
path .

Voc tambm pode usar o mtodo closePath para fechar explicitamente um path atravs da adio de um
segmento da linha atual de volta ao incio do path . Este segmento desenhado traando o path .

214
Curvas
Um path tambm pode conter linhas com curvas. Estes infelizmente um pouco mais complexo do que
desenhar linhas retas. O mtodo quadraticCurveTo desenha uma curva ate um ponto considerado. Para determinar
a curvatura da linha dado no mtodo um ponto de controle e um ponto de destino. Imagine o seguinte, ponto de
controle uma atrao a linha, o que da a ela sua curvatura. A linha no passa pelo ponto de controle. Ao contrrio
disso a direo da linha nos seus pontos de incio e fim fica alinhado, com a linha puxando para o ponto de
controle. O exemplo a seguir ilustra isso:

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 90);
// control=(60,10) goal=(90,90)
cx.quadraticCurveTo(60, 10, 90, 90);
cx.lineTo(60, 10);
cx.closePath();
cx.stroke();
</script>

Ns desenharemos uma curva quadrtica a partir da esquerda para a direita com (60,10) no ponto de controle e
depois colocamos dois segmentos da linha passando por esse ponto de controle de volta para o incio da linha. O
resultado lembra um pouco uma insgnia do Star Trek. Voc pode ver o efeito do ponto de controle: as linhas que
saem dos cantos inferiores comeam na direo do ponto de controle e em seguida se curva em direo a seu
alvo.

O mtodo bezierCurve desenha um tipo semelhante de uma curva. Em vez de um nico ponto de controle este
tem dois, um para cada um dos pontos das extremidades da linha. Aqui um esboo semelhante para ilustrar o
comportamento de uma tal curva:

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 90);
// control1=(10,10) control2=(90,10) goal=(50,90)
cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
cx.lineTo(90, 10);
cx.lineTo(10, 10);
cx.closePath();
cx.stroke();
</script>

Os dois pontos de controle especificam a direo em ambas as extremidades da curva. Quanto mais eles esto
longe de seu ponto correspondente, maior a curva que vai nesse sentido.

Tais curvas pode ser difcil de trabalhar, nem sempre evidente encontrar a forma dos pontos de controle que
proporcionam a forma que voc est procurando. s vezes voc pode calcular, e s vezes voc apenas tem que
encontrar um valor apropriado por tentativa e erro.

Fragmentos arcs de um crculo so mais fceis de se trabalhar. O mtodo arcTo no leva menos de cinco
argumentos. Os quatro primeiros argumentos agem um pouco como os argumentos para quadraticCurveTo .O
primeiro par fornece uma espcie de ponto de controle e o segundo par da o destino a linha. O quinto argumento
fornece o raio do arco. O mtodo vai conceitualmente projetar um canto da linha que vai para o ponto de controle e
em seguida volta ao ponto de destino para que ele faa parte de um crculo com o raio dado. O mtodo arcTo

chega ento a uma parte arredondada bem como uma linha a partir da posio de partida ate o incio de uma
parte arredondada.

215
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 10);
// control=(90,10) goal=(90,90) radius=20
cx.arcTo(90, 10, 90, 90, 20);
cx.moveTo(10, 10);
// control=(90,10) goal=(90,90) radius=80
cx.arcTo(90, 10, 90, 90, 80);
cx.stroke();
</script>

O mtodo arcTo no vai desenhar a linha a partir da parte final do arredondamento para a posio do objetivo,
embora a palavra no seu nome sugere o que ele faz. Voc pode acompanhar com uma chamada de lineTo com
o mesmo objetivo de coordena e acrescentar uma parte da linha.

Para desenhar um crculo voc poderia usar quatro chamadas para arcTo (cada um que giram 90 graus). Mas o
mtodo arcTo fornece uma maneira mais simples. preciso um par de coordenadas para o centro do arco, um
raio e em seguida um ngulo de incio e fim.

Esses dois ltimos parmetros tornam possvel desenhar apenas uma parte do crculo. Os ngulos so medidos
em radianos no em graus. Isso significa que um crculo completo tem um ngulo de 2 ou 2 * Math.PI que
de cerca de 6,28 . O ngulo comea a contar a partir do ponto da direita do centro do crculo e vai a partir do
sentido horrio. Voc pode usar um comeo de 0 e um fim maior do que 2 (digamos 7) para desenhar um
crculo completo.

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
// center=(50,50) radius=40 angle=0 to 7
cx.arc(50, 50, 40, 0, 7);
// center=(150,50) radius=40 angle=0 to
cx.arc(150, 50, 40, 0, 0.5 * Math.PI);
cx.stroke();
</script>

A imagem resultante contm uma linha no crculo(primeira chamada de arc ) a esquerda do quarto do
crculo(segunda chamada). Como outros mtodos esto ligados ao desenho de um path , uma linha traada
ligado ao segmento do arco anterior por padro. Se voc quiser evitar isso teria que chamar moveTo ou iniciar um
novo path .

Desenho de um grfico de pizza


Imagine que voc acabou de conseguir um emprego na EconomiCorp Inc. e sua primeira misso desenhar um
grfico de pizza dos resultados da pesquisa de satisfao do cliente.

A varivel dos resultados contm uma matriz de objetos que representam as respostas da pesquisa.

var results = [
{name: "Satisfied", count: 1043, color: "lightblue"},
{name: "Neutral", count: 563, color: "lightgreen"},
{name: "Unsatisfied", count: 510, color: "pink"},
{name: "No comment", count: 175, color: "silver"}
];

216
Para desenhar um grfico de pizza, traamos um nmero de fatias, cada um composto por um arco e um par de
linhas para o centro desse arco. Podemos calcular o ngulo ocupado por cada arco dividindo um crculo
completo(2) pelo nmero total de respostas, em seguida multiplicamos esse nmero(o ngulo por resposta)
pelo nmero de pessoas que fizeram determinadas escolhas.

<canvas width="200" height="200"></canvas>


<script>
var cx = document.querySelector("canvas").getContext("2d");
var total = results.reduce(function(sum, choice) {
return sum + choice.count;
}, 0);
// Start at the top
var currentAngle = -0.5 * Math.PI;
results.forEach(function(result) {
var sliceAngle = (result.count / total) * 2 * Math.PI;
cx.beginPath();
// center=100,100, radius=100
// from current angle, clockwise by slice's angle
cx.arc(100, 100, 100,
currentAngle, currentAngle + sliceAngle);
currentAngle += sliceAngle;
cx.lineTo(100, 100);
cx.fillStyle = result.color;
cx.fill();
});
</script>

Mas um grfico que no nos diz o que significa no til. Ns precisamos de uma maneira para desenhar o texto
na tela.

Texto
Um contexto de desenho em canvas 2D fornece os mtodos fillText e strokeText . Este ltimo pode ser til para
delinear as letras mas geralmente fillText o que voc precisa. Ele vai encher o texto com a cor atual de
fillColor .

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.font = "28px Georgia";
cx.fillStyle = "fuchsia";
cx.fillText("I can draw text, too!", 10, 50);
</script>

Voc pode especificar o tamanho, estilo e tipo da letra do texto com a propriedade font . Este exemplo apenas d
um tamanho de fonte e nome da famlia. Voc pode adicionar o itlico ou negrito para o incio de uma sequncia
de caracteres.

Os dois ltimos argumentos para fillText (e strokeText ) fornecem a posio em que a fonte desenhado. Por
padro a posio do incio da linha indica a base alfabtica do texto, que a linha que as letras ficam no tendo
partes penduradas; em letras como j ou p voc pode mudar a posio horizontal definindo a propriedade
textAlign para end ou center ou posicionamento vertical definindo textBaseline para top , middle ou bottom .

Vamos voltar ao nosso grfico de pizza para corrigir o problema de rotular as fatias nos exerccios no final do
captulo.

Imagens

217
Na computao grfica uma distino feita frequentemente entre grficos vetoriais e bitmap. O primeiro como
iremos fazer neste captulo; a especificao de uma imagem dando uma descrio lgica de formas. Os grficos
de bitmap no especificam formas reais, mas sim trabalham com dados de pixel (rastros de pontos coloridos).

O mtodo drawImage nos permite desenhar dados de pixel em canvas . Estes dados de pixel pode ter origem a
partir de uma tag <img> ou <canvas> , e nem todos so visveis no documento atual. O exemplo a seguir cria um
elemento <img> e carrega um arquivo de imagem nele. Mas no iniciado imediatamente; a elaborao desta
imagem no ocorreu porque o browser ainda no buscou por isso. Para lidar com tal situao registramos um
manipulador de eventos( "load" ) para fazer o desenho depois que a imagem for carregada.

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
var img = document.createElement("img");
img.src = "img/hat.png";
img.addEventListener("load", function() {
for (var x = 10; x < 200; x += 30)
cx.drawImage(img, x, 10);
});
</script>

Por padro, drawImage vai desenhar a imagem em seu tamanho original. Voc tambm pode dar-lhe dois
argumentos adicionais para ditar uma largura e altura diferentes.

drawImage recebe nove argumentos, ele pode ser utilizado para desenhar apenas um fragmento de uma imagem.
Do segundo ao quinto argumento indicam o retngulo(x, y, largura e altura) na imagem de origem que deve ser
copiado, do sexto ao nono argumentos indica o retngulo(na tela) em que deve ser copiado.

Isso pode ser usado para embalar vrias sprites(elementos de imagem) em um nico arquivo de imagem, em
seguida desenhar apenas a parte que voc precisa. Por exemplo, ns temos esta imagem contendo uma
personagem do jogo em vrias poses:

Ao alternar a pose que traamos, podemos mostrar uma animao que que simula o movimento de andar do
personagem.

Para animar a imagem em uma tela o mtodo clearRect til. Assemelha-se a fillRect mas ao invs de colorir
o retngulo, torna-se transparente removendo os pixels previamente desenhados.

Sabemos que a cada sprite so sub-imagens de 24 pixels de largura por 30 pixels de altura. O cdigo a seguir
carrega as imagens, e em seguida define um intervalo(temporizador de repetio) para desenhar os quadros
seguintes:

218
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
var img = document.createElement("img");
img.src = "img/player.png";
var spriteW = 24, spriteH = 30;
img.addEventListener("load", function() {
var cycle = 0;
setInterval(function() {
cx.clearRect(0, 0, spriteW, spriteH);
cx.drawImage(img,
// source rectangle
cycle * spriteW, 0, spriteW, spriteH,
// destination rectangle
0, 0, spriteW, spriteH);
cycle = (cycle + 1) % 8;
}, 120);
});
</script>

A varivel cycle mapeia nossa posio na animao. A cada quadro ele incrementado e em seguida cortado de
volta para o intervalo de 0 a 7 usando o operador restante. Esta varivel usada para calcular a coordenada x

que o sprite tem para a pose atual da imagem.

Transformaes
Mas e se queremos que o nosso personagem ande para a esquerda em vez de para a direita? Poderamos
acrescentar um outro conjunto de sprites, claro. Mas tambm podemos instruir a tela para desenhar a imagem
de outra maneira.

Chamar o mtodo scale far com que qualquer coisa desenhada depois possa ser escalado. Este mtodo tem
dois parmetros, um para definir uma escala horizontal e um para definir uma escala vertical.

<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.scale(3, .5);
cx.beginPath();
cx.arc(50, 50, 40, 0, 7);
cx.lineWidth = 3;
cx.stroke();
</script>

scaling far tudo sobre a imagem desenhada incluindo: a largura da linha a ser esticado ou espremido,
conforme especificado. Dimensionamento por um valor negativo vai inverter a imagem ao redor. A inverso
acontece em torno do ponto(0,0); o que significa que tudo ir virar a direo do sistema de coordenadas. Quando
uma escala horizontal de -1 aplicada, a forma desenhada em x na posio 100 vai acabar na posio -100.

Ento para transformar uma imagem em torno no podemos simplesmente adicionar cx.scale (-1, 1) antes da
chamada drawImage pois ira mover a nossa imagem fora da tela onde no ser mais possvel v-la. Voc pode
ajustar as coordenadas dadas a drawImage para compensar esse desenho da imagem em x na posio -50 em
vez de 0. Outra soluo que no exige que o cdigo faa o desenho para saber sobre a mudana de escala,
ajustar o eixo em torno do qual a escala acontece.

H vrios outros mtodos alm de scale que influenciam no sistema de coordenadas para o canvas . Voc pode
girar formas posteriormente desenhados com o mtodo de rotation e mov-los com o mtodo de translate .
interessante e confuso saber que estas transformaes so realizados no estilo de pilha, o que significa que
cada uma acontece em relao s transformaes anteriores.

219
Ento se ns fizermos um translate de 10 pixels na horizontal por duas vezes, tudo ser desenhada 20 pixels
para a direita. Se primeiro mover o centro do sistema de coordenadas de (50,50) e em seguida girar 20
graus(0.1 em radianos) a rotao vai acontecer em torno do ponto (50,50).

Mas se ns primeiro girarmos 20 graus e em seguida aplicarmos um translate de (50,50), o translate ira
acontecer na rotao do sistema de coordenadas e assim produzir uma orientao diferente. A ordem em que as
transformaes so aplicadas sera assunto nos prximos tpicos.

Para inverter uma imagem em torno da linha vertical em uma determinada posio x podemos fazer o seguinte:

function flipHorizontally(context, around) {


context.translate(around, 0);
context.scale(-1, 1);
context.translate(-around, 0);
}

Ns deslocamos o eixo-y para onde queremos que o nosso espelho fique e aplicamos, finalmente deslocamos
o eixo-y de volta ao seu lugar adequado no universo espelhado. O quadro a seguir explica por que isso funciona:

Isto mostra o sistemas de coordenadas antes e aps o espelhamento do outro lado da linha central. Se
desenharmos um tringulo em uma posio positiva x, estaria por padro no lugar onde tringulo 1 esta. Uma
chamada para flipHorizontally faz primeiro um translate para a direita, o que nos leva ao tringulo 2. Em
seguida scale lanado e o tringulo volta para a posio 3. Este no o lugar onde ele deveria estar se fosse
espelhada na linha dada. O segundo translate para correes da chamadas esta cancelando o translate inicial
e faz tringulo 4 aparecer exatamente onde deveria.

Agora podemos desenhar um personagem espelhado na posio (100,0) rodando o mundo em torno do centro
vertical do personagem.

220
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
var img = document.createElement("img");
img.src = "img/player.png";
var spriteW = 24, spriteH = 30;
img.addEventListener("load", function() {
flipHorizontally(cx, 100 + spriteW / 2);
cx.drawImage(img, 0, 0, spriteW, spriteH,
100, 0, spriteW, spriteH);
});
</script>

Armazenar e limpando transformaes


Tudo sobre transformaes fica por aqui. Qualquer outra coisa que desenhar depois desse personagem
espelhado tambm ficara espelhado. Isso pode ser um problema.

possvel salvar a transformao atual, fazer algum desenho e transformar e em seguida restaurar a velho
transformao. Isso geralmente a coisa certa a fazer para uma funo que necessita se transformar
temporariamente o sistema de coordenadas. Em primeiro lugar vamos salvar qualquer que seja a transformao
do cdigo que chamou a funo que estava utilizando. Em seguida a funo faz a sua parte(no topo da
transformao existente) possivelmente adicionando mais transformaes. E finalmente revertemos a
transformao que ns fizemos.

Os salvar e o restaurar nos mtodos em contexto canvas 2D realizam um tipo de gerenciamento na


transformao. Eles conceitualmente mantm uma pilha de estados de transformao. Quando voc chama o
salvar o estado atual colocado na pilha, e quando voc chama o restaurar, o estado no topo da pilha retirado e
utilizado a transformao atual do contexto.

A funo de ramificao no exemplo a seguir ilustra o que voc pode fazer com uma funo que altera a
transformao e em seguida chama outra funo que continua a desenhar com a transformao dada no
desenho anterior.

Esta funo desenha uma forma que lembra um desenho de uma rvore com linhas; movendo o sistema de
coordenadas do centro para o fim da linha, e chamando ele novamente. A primeiro rotate acontece para a
esquerda e depois para a direita. Cada chamada reduz o comprimento do ramo desenhado e a recursividade
para quando o comprimento cai abaixo de 8.

<canvas width="600" height="300"></canvas>


<script>
var cx = document.querySelector("canvas").getContext("2d");
function branch(length, angle, scale) {
cx.fillRect(0, 0, 1, length);
if (length < 8) return;
cx.save();
cx.translate(0, length);
cx.rotate(-angle);
branch(length * scale, angle, scale);
cx.rotate(2 * angle);
branch(length * scale, angle, scale);
cx.restore();
}
cx.translate(300, 0);
branch(60, 0.5, 0.8);
</script>

221
Se as chamadas para salvar e restaurar no estivessem l, a segunda chamada recursiva dos galho acabariam
com a mesma posio de rotao criado pela primeira chamada. No estariam ligados ao ramo atual, mas
estaria a direita do ramo desenhado pela primeira chamada. A forma resultante tambm poderia ser interessante
mas no definitivamente uma rvore.

De volta para o jogo


Agora sabemos o suficiente sobre desenho no canvas para comearmos a trabalhar em um sistema de
visualizao baseada em canvas para o jogo a partir do captulo anterior. O novo visual no ser apenas
mostrando caixas coloridas. Mas vamos usar drawImage para desenhar imagens que representam os elementos
do jogo.

Vamos definir um tipo de objeto CanvasDisplay , suportando a mesma interface que DOMDisplay a partir do captulo
15, ou seja os mtodos drawFrame e clear .

Este objeto mantm um pouco mais de informao do que DOMDisplay . Ao invs de usar a posio de rolagem do
seu elemento DOM, ele controla o seu prprio visor, que nos diz qual parte do nvel atualmente que estamos
olhando. Ele tambm rastreia o tempo e usa isso para decidir qual quadro da animao deve ser usado. E
finalmente ele mantm uma propriedade flipPlayer de modo que mesmo quando o jogador ainda est de p ele
continua voltada para a direo do ltimo movimento.

function CanvasDisplay(parent, level) {


this.canvas = document.createElement("canvas");
this.canvas.width = Math.min(600, level.width * scale);
this.canvas.height = Math.min(450, level.height * scale);
parent.appendChild(this.canvas);
this.cx = this.canvas.getContext("2d");

this.level = level;
this.animationTime = 0;
this.flipPlayer = false;

this.viewport = {
left: 0,
top: 0,
width: this.canvas.width / scale,
height: this.canvas.height / scale
};

this.drawFrame(0);
}

CanvasDisplay.prototype.clear = function() {
this.canvas.parentNode.removeChild(this.canvas);
};

O contador animationTime a razo pela qual passou o tamanho do passo para drawFrame no Captulo 15 embora
DOMDisplay no utilizasse. Nossa nova funo drawFrame iremos utilizar para controlar o tempo de modo que
possa alternar entre quadros de animao com base no tempo atual.

CanvasDisplay.prototype.drawFrame = function(step) {
this.animationTime += step;

this.updateViewport();
this.clearDisplay();
this.drawBackground();
this.drawActors();
};

222
Diferente do controle de tempo, o mtodo atualiza a janela de exibio para a posio atual do jogador, preenche
toda a tela com uma cor de fundo, desenha o fundo e os atores. Note que que diferente da abordagem no
captulo 15 onde traamos o plano de fundo toda vez que movemos qualquer elemento do DOM envolvido.

Como as formas em uma tela so apenas pixels, depois que atrado, no h nenhuma maneira de remov-los. A
nica maneira de atualizar a exibio de tela limpar e redesenhar a cena.

O mtodo updateViewport semelhante ao mtodo de scrollPlayerIntoView no DOMDisplay . Ele verifica se o jogador


est demasiado perto da borda da tela e move a janela de exibio quando for o caso.

CanvasDisplay.prototype.updateViewport = function() {
var view = this.viewport, margin = view.width / 3;
var player = this.level.player;
var center = player.pos.plus(player.size.times(0.5));

if (center.x < view.left + margin)


view.left = Math.max(center.x - margin, 0);
else if (center.x > view.left + view.width - margin)
view.left = Math.min(center.x + margin - view.width,
this.level.width - view.width);
if (center.y < view.top + margin)
view.top = Math.max(center.y - margin, 0);
else if (center.y > view.top + view.height - margin)
view.top = Math.min(center.y + margin - view.height,
this.level.height - view.height);
};

As chamadas para Math.max e Math.min garantem que a janela de exibio no acabe mostrando espao fora do
nvel. Math.max(x, 0) tem o efeito de assegurar que o nmero resultante no seja inferior a zero. Math.min da
mesma forma, garante que um valor permanea abaixo de um dado vinculado.

Ao limpar a tela vamos usar uma cor ligeiramente diferente dependendo se o jogo for ganho(mais claro) ou
perdido(mais escura).

CanvasDisplay.prototype.clearDisplay = function() {
if (this.level.status == "won")
this.cx.fillStyle = "rgb(68, 191, 255)";
else if (this.level.status == "lost")
this.cx.fillStyle = "rgb(44, 136, 214)";
else
this.cx.fillStyle = "rgb(52, 166, 251)";
this.cx.fillRect(0, 0,
this.canvas.width, this.canvas.height);
};

Para desenhar o plano de fundo, corremos por entre as telhas que so visveis na janela de exibio atual,
usando o mesmo truque usado em obstacleAt no captulo anterior.

223
var otherSprites = document.createElement("img");
otherSprites.src = "img/sprites.png";

CanvasDisplay.prototype.drawBackground = function() {
var view = this.viewport;
var xStart = Math.floor(view.left);
var xEnd = Math.ceil(view.left + view.width);
var yStart = Math.floor(view.top);
var yEnd = Math.ceil(view.top + view.height);

for (var y = yStart; y < yEnd; y++) {


for (var x = xStart; x < xEnd; x++) {
var tile = this.level.grid[y][x];
if (tile == null) continue;
var screenX = (x - view.left) * scale;
var screenY = (y - view.top) * scale;
var tileX = tile == "lava" ? scale : 0;
this.cx.drawImage(otherSprites,
tileX, 0, scale, scale,
screenX, screenY, scale, scale);
}
}
};

Azulejos que no esto vazias(null) so desenhados com drawImage . A imagem otherSprites contm os outros
elementos do jogo. Como os azulejos da parede, a telha de lava, e o sprite para uma moeda.

Azulejos de fundo so 20 por 20 pixels, usaremos a mesma escala que usamos no DOMDisplay . Assim o
deslocamento para telhas de lava de 20(o valor da varivel de escala) e o deslocamento para paredes 0.

Ns no nos incomodamos em esperar a imagem do sprite carregar. Chamando drawImage com uma imagem
que no foi carregado e simplesmente ele no ira fazer nada. Assim no chamaremos o jogo corretamente para
os primeiros frames enquanto a imagem ainda est sendo carregado, mas isso no um problema grave, desde
que mantenhamos a atualizao da tela na cena correta, assim que carregamento terminar.

O carcter para caminhar que foi utilizado, sera usado para representar o jogador. O cdigo que chama ele
precisa pegar a posio da sprite com base no movimento atual do jogador. Os primeiros oito sprites contm uma
animao curta. Quando o jogador est se movendo ao longo de um cho os ciclos so alternados entre as
propriedades de animationTime da tela. Este medido em segundos, e queremos mudar os quadros 12 vezes por
segundo, assim que o tempo multiplicado por 12. Quando o jogador est parado, vamos traar a nona Sprite.
Durante saltos que so reconhecidos pelo fato de que a velocidade vertical no zero, ns usamos o dcimo
elemento que esta na sprite mais a direita.

Porque os sprites so ligeiramente mais largo do que o jogador? 24 ao invs de 16 pixels? Isso para permitir
algum espao para os ps e braos em movimento, o mtodo tem de ajustar a coordenada x e largura por um
determinado montante( playerXOverlap ).

224
var playerSprites = document.createElement("img");
playerSprites.src = "img/player.png";
var playerXOverlap = 4;

CanvasDisplay.prototype.drawPlayer = function(x, y, width,


var sprite = 8, player = this.level.player;
width += playerXOverlap * 2;
x -= playerXOverlap;
if (player.speed.x != 0)
this.flipPlayer = player.speed.x < 0;

if (player.speed.y != 0)
sprite = 9;
else if (player.speed.x != 0)
sprite = Math.floor(this.animationTime * 12) % 8;

this.cx.save();
if (this.flipPlayer)
flipHorizontally(this.cx, x + width / 2);

this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height);


this.cx.restore();
};

O mtodo drawPlayer chamado por drawActors , que responsvel pela elaborao de todos os atores no jogo.

CanvasDisplay.prototype.drawActors = function() {
this.level.actors.forEach(function(actor) {
var width = actor.size.x * scale;
var height = actor.size.y * scale;
var x = (actor.pos.x - this.viewport.left) * scale;
var y = (actor.pos.y - this.viewport.top) * scale;
if (actor.type == "player") {
this.drawPlayer(x, y, width, height);
} else {
var tileX = (actor.type == "coin" ? 2 : 1) * scale;
this.cx.drawImage(otherSprites,
tileX, 0, width, height,
x, y, width, height);
}
}, this);
};

Ao desenhar algo que no o jogador, verificamos o seu tipo para encontrar o deslocamento correto na sprite. A
telha de lava encontrado no deslocamento 20 o sprite moeda encontrada em 40(duas vezes escala).

Ns temos que subtrair a posio da janela de exibio ao computar a posio do ator, (0,0) corresponde ao
canto superior esquerdo da janela da exibio do nosso canvas na parte superior esquerda do level . Ns
tambm poderamos ter usado o translate para isso. De qualquer maneira funcionaria.

O documento minsculo mostrado a seguir conecta o novo display em runGame :

<body>
<script>
runGame(GAME_LEVELS, CanvasDisplay);
</script>
</body>

Escolhendo uma interface grfica


Sempre que voc precisar gerar grficos no navegador, voc pode escolher entre HTML, SVG, e canvas . No h
uma abordagem melhor que funciona em todas as situaes. Cada opo tem pontos fortes e fracos.

225
HTML tem a vantagem de ser simples. Ele se integra bem com textos. Ambos SVG e Canvas permitem que voc
desenhe texto mas eles no ajudam no posicionamento ou envolvimento quando ocupam mais de uma linha. Em
uma imagem baseada em HTML fcil incluir blocos de texto.

SVG pode ser usado para produzir grficos ntidos que ficam bem em qualquer nvel de zoom. mais difcil de
usar do que HTML mas tambm muito mais potente.

Ambos SVG e HTML podem construrem uma estrutura de dados(DOM) que represente uma imagem. Isto torna
possvel modificar os elementos depois de serem desenhados. Se voc precisa mudar vrias vezes uma
pequena parte de um grande desenho em resposta ao que o usurio est fazendo ou como parte de uma
animao em canvas isso pode ser extremamente caro. O DOM tambm nos permite registrar manipuladores de
eventos de mouse sobre cada elemento da imagem(mesmo em formas desenhadas com SVG). E isso no pode
ser feito em canvas .

Mas a abordagem orientada a pixel da tela pode ser uma vantagem quando o desenho usa uma enorme
quantidade de elementos minsculos. O fato de no se criar uma estrutura de dados, mas de apenas cham-los
repetidamente sobre a mesma superfcie de pixel, canvas d um menor custo em performance.

H tambm efeitos, como renderizar uma cena de um pixel de cada vez(por exemplo, fazer um desenho de raios)
ou ps-processamento de uma imagem com JavaScript(com efeito de embaado ou distorcida) que s pode ser
realisticamente manipulados por uma tcnica baseada em pixel.

Em alguns casos, voc pode querer combinar vrias destas tcnicas. Por exemplo, voc pode desenhar um
grfico com SVG ou canvas , mas mostrar a informao textual posicionando um elemento HTML em cima da
imagem.

Para aplicaes que no exigem muito, no importa muito por qual interface voc ira escolher. A segunda exibio
feita para o nosso jogo neste captulo poderia ter sido implementado com qualquer uma dessas trs tecnologias
de grficos, uma vez que no precisamos desenhar texto nem lidar com a interao do mouse ou trabalhar com
um nmero extraordinariamente grande de elementos.

Sumrio
Neste captulo, discutimos as tcnicas para desenhar grficos no navegador, com foco no elemento <canvas> .

Um n canvas representa uma rea em um documento que o nosso programa pode desenhar. Este desenho
feito atravs do contexto do objeto de desenho, criado com o mtodo getContext .

A interface de desenho em 2D nos permite preencher e traar vrias formas. Propriedade fillStyle do contexto
determina como as formas so preenchidas. As propriedades strokeStyle e lineWidth controlam a forma de
como as linhas so desenhadas.

Retngulos e pedaos de texto podem ser tiradas com uma nica chamada de mtodo. Os mtodos fillRect e
strokeRect desenham retngulos e os mtodos fillText e strokeText desenham texto. Para criar formas
personalizadas preciso primeiro construir um path .

Chamando beginPath inicia um novo caminho. Uma srie de outros mtodos podem adicionar linhas e curvas
para o path atual. Por exemplo lineTo pode adicionar uma linha reta. Quando um caminho terminado ele pode
ser preenchido com o mtodo fill ou traado com o mtodo stroke .

Mover os pixels de uma imagem ou de outra tela no nosso canvas realizado com o mtodo drawImage . Por
padro esse mtodo desenha a imagem da origem por inteiro, mas passando mais parmetros voc pode copiar
uma rea especfica da imagem. Usamos isso para o nosso jogo onde copiamos poses individuais do
personagem do jogo a partir de uma imagem que tinha muitas cenas.

226
Transformaes permitem que voc desenhe uma forma de mltiplas orientaes. Um contexto de desenho em
2D tem uma transformao em curso que pode ser alterado com os mtodos translate , scale e rotate . Estes
iro afetar todas as operaes dos desenhos subsequentes. Um estado de transformao podem ser salvas
com o mtodo save e restaurado com o mtodo restore .

Ao desenhar uma animao sobre uma tela, o mtodo clearRect pode ser usado para limpar parte da tela antes
de redesenh-la novamente.

Exerccios
Shapes
Escreva um programa que tira as seguintes formas de uma tela:

Um trapzio(um retngulo que mais largo de um lado)


Um diamante vermelho(um retngulo rotacionado em 45 graus ou radianos)
A linha em ziguezague
Uma espiral composta de 100 segmentos de linha reta
Uma estrela amarela

Ao desenhar os dois ltimos, voc pode querer referir-se a explicao do Math.cos e Math.sin do captulo 13 que
descreve como obter coordenadas em um crculo usando essas funes.

Eu recomendo a criao de uma funo para cada forma. Passar a posio e outras propriedades como algo
opcional tais como o tamanho ou o nmero de pontos. A alternativa para tirar o hard-code do seu cdigo, tende
tornar o cdigo fcil de ler e modificar.

<canvas width="600" height="200"></canvas>


<script>
var cx = document.querySelector("canvas").getContext("2d");

// Your code here.


</script>

Dicas

O trapzio(1) fcil desenhar usando um path . Escolha as coordenadas do centro adequado e adicione cada um
dos quatro cantos em torno dele.

O diamante(2) pode ser desenhado de forma fcil com um path , uma maneira interessante pode ser feito com
transform e ratation . Para usar rotation voc ter que aplicar um truque semelhante ao que fizemos na funo
flipHorizontally . Voc pode girar em torno do centro do seu retngulo e no em torno do ponto (0,0), primeiro voc
deve utilizar o translate em seguida rotation e ento translate para voltar.

Para o ziguezague(3) torna-se impraticvel escrever uma novo path para cada lineTo do segmento de uma
linha. Em vez disso voc deve usar um loop. Voc pode desenhar com dois segmentos de linha( direita e depois
esquerda). Use a regularidade(2%) do ndice de loop para decidir se vai para a esquerda ou direita.

227
Voc tambm vai precisar de um loop para a espiral(4). Se voc desenhar uma srie de pontos com cada ponto
que se move mais ao longo de um crculo e ao redor do centro do espiral, voc comeara a fazer um crculo. Se
durante o loop voc variar o raio do crculo em que voc est colocando o ponto atual o resultado sera um espiral.

A estrela(5) representado construda a partir de linhas quadraticCurveTo . Voc tambm pode tirar uma com linhas
retas. Divida um crculo em oito pedaos, ou um pedao para cada ponto que voc quer que sua estrela tenha.
Desenhar linhas entre estes pontos, tornam as curvas na direo do centro da estrela. Com quadraticCurveTo ,
voc pode usar o centro como o ponto de controle.

Grfico de pizza
No incio do captulo vimos um exemplo de programa que desenhou um grfico de pizza. Modifique este programa
para que o nome de cada categoria seja mostrado e fique ao lado de cada fatia que representa. Tente encontrar
uma maneira agradvel de mostrar e posicionar automaticamente este texto. Voc pode assumir que as
categorias no so menores do que 5 por cento.

Voc pode precisar de novo do Math.sin e Math.cos conforme descrito no exerccio anterior.

<canvas width="600" height="300"></canvas>


<script>
var cx = document.querySelector("canvas").getContext("2d");
var total = results.reduce(function(sum, choice) {
return sum + choice.count;
}, 0);

var currentAngle = -0.5 * Math.PI;


var centerX = 300, centerY = 150;
// Add code to draw the slice labels in this loop.
results.forEach(function(result) {
var sliceAngle = (result.count / total) * 2 * Math.PI;
cx.beginPath();
cx.arc(centerX, centerY, 100,
currentAngle, currentAngle + sliceAngle);
currentAngle += sliceAngle;
cx.lineTo(centerX, centerY);
cx.fillStyle = result.color;
cx.fill();
});
</script>

Dicas

Voc vai precisar chamar fillText , definir textAlign e textBaseline para as propriedades do contexto de tal forma
que o texto acabe onde quiser.

Uma forma sensata para posicionar os rtulos seria colocar o texto na linha que vai do centro de uma fatia ate o
meio. Voc no quer colocar o texto diretamente de encontro ao lado da fatia mas sim mover o texto para o lado da
fatia por um determinado nmero de pixels.

O ngulo desta linha currentAngle + 0,5 * sliceAngle . O cdigo a seguir encontra-se em uma posio sobre esta
linha de 120 pixels para centro:

var middleAngle = currentAngle + 0.5 * sliceAngle;


var textX = Math.cos(middleAngle) * 120 + centerX;
var textY = Math.sin(middleAngle) * 120 + centerY;

Para textBaseline o valor "middle" provavelmente uma abordagem a ser utilizada. O que for usado para
textAlign depende do lado do crculo em que estamos. esquerda deve ser "center" , a direita deve usar
"rigth" ,e left para texto que estiver posicionado longe do pedao.

228
Se voc no tem certeza de como descobrir qual lado do crculo um determinado ngulo esta, olhe para a
explicao de Math.cos no exerccio anterior. O cosseno de um ngulo nos diz qual coordenada x corresponde,
que por sua vez nos diz exatamente que lado do crculo em que estamos.

Quicando a bola
Use a tcnica requestAnimationFrame que vimos no Captulo 13 e no Captulo 15 para desenhar uma caixa com uma
bola quicando dentro. A bola se move a uma velocidade constante e rebate nos lados da caixa quando tocada.

<canvas width="400" height="400"></canvas>


<script>
var cx = document.querySelector("canvas").getContext("2d");

var lastTime = null;


function frame(time) {
if (lastTime != null)
updateAnimation(Math.min(100, time - lastTime) / 1000);
lastTime = time;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

function updateAnimation(step) {
// Your code here.
}
</script>

Dicas

A caixa fcil de desenhar com strokeRect . Definir uma varivel que contm o seu tamanho e definir duas
variveis da largura e altura da sua caixa. Para criar uma bola redonda, inicie um path chamando arc(x, y, raio,

0, 7) que cria um arco que vai de zero pra cima para um crculo completo, e depois preencha.

Para modelar a posio da bola e velocidade, voc pode usar o tipo vetor a partir do captulo 15. D uma
velocidade de partida de preferncia um que no puramente vertical ou horizontal, e a cada quadro multiplique a
velocidade com a quantidade de tempo que decorreu. Quando a bola fica muito perto de uma parede vertical
inverta o componente x em sua velocidade. Da mesma forma inverta o componente y quando ela atinge uma
parede na horizontal.

Depois de encontrar a nova posio e velocidade da bola, use clearRect para excluir a cena e redesenh-lo
usando a nova posio.

Espelhamento pre computado


Uma coisa ruim sobre transformation que eles diminuem a qualidade do desenho de bitmaps. Para grficos
vectoriais o efeito menos grave uma vez que apenas alguns pontos(por exemplo, o centro de um crculo)
precisam de ser transformado, aps ser desenhado normalmente. Para uma imagem de bitmap a posio de
cada pixel tem que ser transformado, embora seja possvel que os navegadores vo ficar mais inteligente sobre
isso no futuro, atualmente este provoca um aumento considervel no tempo em que leva para desenhar um
bitmap.

Em um jogo como o nosso, onde estamos desenhando apenas uma nica entidade grfica e transformando, isto
no um problema. Mas imagine que precisamos desenhar centenas de personagens ou milhares de partculas
em rotao de uma exploso.

Pense em uma maneira que nos permite desenhar um personagem invertido sem carregar arquivos de imagem
e sem ter que ficar transformando drawImage a cada frame que se chama.

229
Dica

A chave para a soluo o fato de que ns podemos usar um elemento de tela como uma imagem de origem ao
usar drawImage . possvel criar um elemento extra de <canvas> sem adicion-lo ao documento e colocar nossas
sprites invertidas. Ao desenhar um quadro real, ns apenas copiamos as sprites j invertidos para a tela principal.

Alguns cuidados seria necessria porque as imagens no so carregadas instantaneamente. Fazemos o


desenho invertido apenas uma vez e se fizermos isso antes do carregamento das imagens ele no vai chamar
nada. Um manipulador "load" sobre a imagem pode ser usada para desenhar as imagens invertidas para o
canvas extra. Esta rea pode ser usado como uma fonte de desenho imediatamente(ele vai simplesmente ficar
em branco at que desenhar o personagem aparea).

230
HTTP
O sonho por trs da Web o de um espao comum de informaes no qual podemos nos comunicar
compartilhando informaes. Sua universalidade essencial. O fato de que um link pode apontar para
qualquer coisa, seja ela pessoal, local ou global, seja ela um rascunho ou algo refinado.

Tim Berners-Lee, The World Wide Web: A very short personal history

O Hypertext Transfer Protocol, j mencionado no captulo 12, o mecanismo no qual dados so requisitados e
entregues na World Wide Web . Esse captulo descreve o protocolo com mais detalhes e explica como o
JavaScript executado no navegador tem acesso a ele.

O Protocolo
Se voc digitar eloquentjavascript.net/17_http.html na barra de endereos do seu navegador, ele ir,
primeiramente, procurar o endereo do servidor associado ao domnio eloquentjavascript.net e, em seguida,
tentar abrir uma conexo TCP com ele na porta 80, a porta padro para trfego HTTP. Se o servidor existir e aceitar
a conexo, o navegador enviar algo parecido com:

GET /17_http.html HTTP/1.1


Host: eloquentjavascript.net
User-Agent: Your browser's name

Ento, por meio da mesma conexo, o servidor responde.

HTTP/1.1 200 OK
Content-Length: 65585
Content-Type: text/html
Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT

<!doctype html>
... the rest of the document

O navegador participa da resposta aps a linha em branco e a mostra como um documento HTML.

A informao enviada pelo cliente chamada de requisio (request) e inicia com essa linha:

GET /17_http.html HTTP/1.1

A primeira palavra o mtodo da requisio. GET significa que queremos acessar o recurso em questo. Outros
mtodos comuns so DELETE para deletar um recurso, PUT para substitu-lo e POST para enviar uma informao.
Note que o servidor no obrigado a processar todas as requisies que receber. Se voc acessar um website
aleatrio e fizer uma requisio DELETE em sua pgina principal, ele provavelmente ir recusar essa ao.

A parte aps o nome do mtodo o caminho do recurso ao qual a requisio est sendo aplicada. No caso mais
simples, um recurso simplesmente um arquivo no servidor, entretanto, o protocolo no requer que o recurso
seja necessariamente um arquivo. Um recurso pode ser qualquer coisa que possa ser transferida como se fosse
um arquivo. Muitos servidores geram as respostas na medida em que so solicitados. Por exemplo, se voc
acessar twitter.com/marijnjh, o servidor ir procurar em seu banco de dados por um usurio chamado marijnjh e,
se encontr-lo, ir gerar a pgina de perfil desse usurio.

231
Aps o caminho do recurso, a primeira linha da requisio menciona HTTP/1.1 para indicar a verso do protocolo
HTTP que est sendo usada.

A resposta do servidor ir iniciar tambm com a verso, seguida pelo status da resposta, representado
primeiramente por um cdigo de trs dgitos e, em seguida, por um texto legvel.

HTTP/1.1 200 OK

Os status code (cdigos de status) que iniciam com o nmero 2 indicam que a requisio foi bem-sucedida.
Cdigos que comeam com 4, indicam que houve algum problema com a requisio. O cdigo de resposta HTTP
provavelmente mais famoso o 404, que significa que o recurso solicitado no foi encontrado. Cdigos que
comeam com 5 indicam que houve um erro no servidor e que a culpa no da requisio.

A primeira linha de uma requisio ou resposta pode ser seguida por qualquer quantidade de headers
(cabealhos). Eles so representados por linhas na forma de "nome: valor" que especificam informaes extra
sobre a requisio ou resposta. Os headers abaixo fazem parte do exemplo de resposta usado anteriormente:

Content-Length: 65585
Content-Type: text/html
Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT

Eles nos informam o tamanho e o tipo do documento da resposta. Nesse caso, um documento HTML com
65.585 bytes. Alm disso, ele nos mostra quando foi a ltima vez que o documento foi modificado.

Na maioria das vezes, o cliente ou o servidor decidem quais headers sero includos em uma requisio ou
resposta, apesar de alguns serem obrigatrios. Por exemplo, o header Host , que especifica o hostname, deve
ser includo na requisio pois o servidor pode estar servindo mltiplos hostnames em um mesmo endereo IP e,
sem esse header, o servidor no saber qual host o cliente est tentando se comunicar.

Aps os headers, tanto as requisies quanto as respostas podem incluir uma linha em branco seguida por um
b ody (corpo), que contm os dados que esto sendo enviados. As requisies GET e DELETE no enviam
nenhum tipo dado, mas PUT e POST enviam. De maneira similar, alguns tipos de resposta, como respostas de
erro, no precisam de um b ody.

Navegadores e o HTTP
Como vimos no exemplo anterior, o navegador ir fazer uma requisio quando submetermos uma URL na barra
de endereos. Quando a pgina HTML resultante faz referncias a outros arquivos como imagens e arquivos
JavaScript, eles tambm so requisitados.

Um website razoavelmente complicado pode facilmente ter algo em torno de dez a duzentos recursos. Para ser
capaz de busc-los rapidamente, ao invs de esperar pelo retorno das respostas de cada requisio feita, os
navegadores fazem vrias requisies simultaneamente. Tais documentos so sempre requisitados usando
requisies GET .

Pginas HTML podem incluir formulrios, que permitem ao usurio preencher e enviar informaes para o
servidor. Esse um exemplo de um formulrio:

<form method="GET" action="example/message.html">


<p>Name: <input type="text" name="name"></p>
<p>Message:<br><textarea name="message"></textarea></p>
<p><button type="submit">Send</button></p>
</form>

232
Esse cdigo descreve um formulrio com dois campos: um campo menor que solicita um nome e um campo
maior que solicita que o usurio escreva uma mensagem. Quando voc clicar no boto Send (enviar), a
informao contida nos campos sero convertidas em uma query string. Quando o mtodo do atributo do
elemento <form> for GET (ou o mtodo for omitido), a query string associada URL contida em action eo
navegador executa a requisio GET para essa URL.

GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1

O incio de uma query string indicado por um ponto de interrogao seguido por pares de nomes e valores,
correspondendo ao atributo name de cada campo do formulrio e seus respectivos valores. O caractere &
usado para separar os pares.

A mensagem codificada na URL anterior "Yes?", mesmo que o ponto de interrogao tenha sido substitudo por
um cdigo estranho. Alguns caracteres nas query strings precisam ser escapados. O ponto de interrogao,
representado como %3F , um desses casos. Parece haver uma regra no escrita de que cada formato necessita
ter sua prpria forma de escapar caracteres. Esse formato que est sendo usado chamado de URL encoding e
utiliza o sinal de porcentagem seguido por dois dgitos hexadecimais que representam o cdigo daquele
caractere. Nesse caso, o 3F significa 63 na notao decimal, que o cdigo do caractere de interrogao. O
JavaScript fornece as funes encodeURIComponent e decodeURIComponent para codificar e decodificar esse formato.

console.log(encodeURIComponent("Hello & goodbye"));


// Hello%20%26%20goodbye
console.log(decodeURIComponent("Hello%20%26%20goodbye"));
// Hello & goodbye

Se alterarmos o mtodo do atributo do formulrio HTML no exemplo anterior para POST , a requisio HTTP que
ser feita para enviar o formulrio ir usar o mtodo POST e a query string ser adicionada ao corpo da requisio,
ao invs de ser colocada diretamente na URL.

POST /example/message.html HTTP/1.1


Content-length: 24
Content-type: application/x-www-form-urlencoded

name=Jean&message=Yes%3F

Por conveno, o mtodo GET usado para requisies que no produzem efeitos colaterais, tais como fazer
uma pesquisa. Requisies que alteram alguma coisa no servidor, como criar uma nova conta ou postar uma
nova mensagem, devem ser expressadas usando outros mtodos, como POST . Aplicaes client-side, como os
navegadores, sabem que no devem fazer requisies POST cegamente, mas frequentemente faro requisies
GET implcitas para, por exemplo, pr-carregar um recurso que ele acredita que o usurio ir precisar no curto
prazo.

O prximo captulo ir retomar o assunto formulrios e explicar como podemos desenvolv-los usando
JavaScript.

XMLHttpRequest
A interface pela qual o JavaScript do navegador pode fazer requisies HTTP chamada de XMLHttpRequest

(observe a forma inconsistente de capitalizao). Ela foi elaborada pela Microsoft, para o seu navegador Internet
Explorer, no final dos anos 90. Naquela poca, o formato de arquivo XML era muito popular no contexto dos
softwares corporativos, um mundo no qual sempre foi a casa da Microsoft. O formato era to popular que o
acrnimo XML foi adicionado ao incio do nome de uma interface para o HTTP, a qual no tinha nenhuma relao
com o XML.

233
Mesmo assim, o nome no completamente sem sentido. A interface permite que voc analise os documentos
de resposta como XML, caso queira. Combinar dois conceitos distintos (fazer uma requisio e analisar a
resposta) em uma nica coisa com certeza um pssimo design.

Quando a interface XMLHttpRequest foi adicionada ao Internet Explorer, foi permitido s pessoas fazerem coisas
com JavaScript que eram bem difceis anteriormente. Por exemplo, websites comearam a mostrar listas de
sugestes enquanto o usurio digitava algo em um campo de texto. O script mandava o texto para o servidor
usando HTTP enquanto o usurio estivesse digitando. O servidor, que tinha um banco de dados com possveis
entradas, comparava as possveis entradas com a entrada parcial digitada pelo usurio, enviando de volta
possveis combinaes de resultados para mostrar ao usurio. Isso era considerado espetacular, pois as
pessoas estavam acostumadas a aguardar por uma atualizao completa da pgina para cada interao com o
website.

O outro navegador relevante naquela poca, chamado Mozilla (mais tarde Firefox), no queria ficar para trs. Para
permitir que as pessoas pudessem fazer coisas similares em seu navegador, eles copiaram a interface, incluindo
o controverso nome. A prxima gerao de navegadores seguiram esse exemplo e, por isso, a interface
XMLHttpRequest um padro atualmente.

Enviando uma requisio


Para fazer uma simples requisio, criamos um objeto de requisio com o construtor XMLHttpRequest e
chamamos os seus mtodos open e send .

var req = new XMLHttpRequest();


req.open("GET", "example/data.txt", false);
req.send(null);
console.log(req.responseText);
// This is the content of data.txt

O mtodo open configura a requisio. Nesse caso, escolhemos fazer uma requisio GET para o arquivo
example/data.txt. As URLs que no comeam com um nome de protocolo (como por exemplo http:) so relativas,
ou seja, so interpretadas em relao ao documento atual. Quando elas iniciam com uma barra (/), elas
substituem o caminho atual, que a parte aps o nome do servidor. No caso de no iniciarem com uma barra, a
parte do caminho em questo at (e incluindo) a ultima barra colocada em frente URL relativa.

Aps abrir a requisio, podemos envi-la usando o mtodo send . O argumento a ser enviado o corpo da
requisio. Para requisies GET , podemos passar null . Se o terceiro argumento passado para open for
false , o mtodo send ir retornar apenas depois que a resposta da nossa requisio for recebida. Podemos ler
a propriedade responseText do objeto da requisio para acessar o corpo da resposta.

As outras informaes includas na resposta tambm podem ser extradas desse objeto. O status code (cdigo de
status) pode ser acessado por meio da propriedade status e a verso legvel em texto do status pode ser
acessada por meio da propriedade statusText . Alm disso, os cabealhos podem ser lidos com
getResponseHeader .

var req = new XMLHttpRequest();


req.open("GET", "example/data.txt", false);
req.send(null);
console.log(req.status, req.statusText);
// 200 OK
console.log(req.getResponseHeader("content-type"));
// text/plain

234
Os nomes dos cabealhos so case-insensitive (no faz diferena entre letras maisculas e minsculas). Eles
so normalmente escritos com letra maiscula no incio de cada palavra, como por exemplo "Content-Type".
Entretanto, as respectivas variaes "content-type" e "cOnTeNt-TyPe" fazem referncia ao mesmo cabealho.

O navegador ir automaticamente adicionar alguns cabealhos da requisio, tais como "Host" e outros
necessrios para o servidor descobrir o tamanho do corpo da requisio. Mesmo assim, voc pode adicionar os
seus prprios cabealhos usando o mtodo setRequestHeader . Isso necessrio apenas para usos avanados e
requer a cooperao do servidor ao qual voc est se comunicando (o servidor livre para ignorar cabealhos
que ele no sabe lidar).

Requisies Assncronas
Nos exemplos que vimos, a requisio finaliza quando a chamada ao mtodo send retorna. Isso conveniente
pois significa que as propriedades como responseText ficam disponveis imediatamente. Por outro lado, o nosso
programa fica aguardando enquanto o navegador e o servidor esto se comunicando. Quando a conexo ruim, o
servidor lento ou o arquivo muito grande, o processo pode demorar um bom tempo. Ainda pior, devido ao fato de
que nenhum manipulador de evento pode ser disparado enquanto nosso programa est aguardando, todo o
documento ficar no responsivo.

Se passarmos true como terceiro argumento para open , a requisio assncrona. Isso significa que quando
chamar o mtodo send , a nica coisa que ir acontecer imediatamente o agendamento da requisio que ser
enviada. Nosso programa pode continuar a execuo e o navegador ir ser responsvel por enviar e receber os
dados em segundo plano.

Entretanto, enquanto a requisio estiver sendo executada, ns no podemos acessar a resposta. necessrio
um mecanismo que nos avise quando os dados estiverem disponveis.

Para isso, precisamos escutar o evento "load" no objeto da requisio.

var req = new XMLHttpRequest();


req.open("GET", "example/data.txt", true);
req.addEventListener("load", function() {
console.log("Done:", req.status);
});
req.send(null);

Assim como o uso de requestAnimationFrame no Captulo 15, essa situao nos obriga a usar um estilo assncrono
de programao, encapsulando as coisas que precisam ser executadas aps a requisio em uma funo e
preparando-a para que possa ser chamada no momento apropriado. Voltaremos nesse assunto mais a frente.

Recuperando Dados XML


Quando o recurso recebido pelo objeto XMLHttpRequest um documento XML, a propriedade responseXML do objeto
ir conter uma representao desse documento. Essa representao funciona de forma parecida com o DOM,
discutida no Captulo 13, exceto que ela no contm funcionalidades especficas do HTML, como por exemplo a
propriedade style . O objeto contido em responseXML corresponde ao objeto do documento. Sua propriedade
documentElement se refere tag mais externa do documento XML. No documento a seguir (example/fruit.xml), essa
propriedade seria a tag <fruits> :

235
<fruits>
<fruit name="banana" color="yellow"/>
<fruit name="lemon" color="yellow"/>
<fruit name="cherry" color="red"/>
</fruits>

Podemos recuperar esse arquivo da seguinte forma:

var req = new XMLHttpRequest();


req.open("GET", "example/fruit.xml", false);
req.send(null);
console.log(req.responseXML.querySelectorAll("fruit").length);
// 3

Documentos XML podem ser usados para trocar informaes estruturadas com o servidor. Sua forma (tags dentro
de outras tags) faz com que seja fcil armazenar a maioria dos tipos de dados, sendo uma alternativa melhor do
que usar um simples arquivo de texto. Entretanto, extrair informaes da interface DOM um pouco trabalhoso e
os documentos XML tendem a ser verbosos. Normalmente uma ideia melhor se comunicar usando dados
JSON, os quais so mais fceis de ler e escrever tanto para programas quanto para humanos.

var req = new XMLHttpRequest();


req.open("GET", "example/fruit.json", false);
req.send(null);
console.log(JSON.parse(req.responseText));
// {banana: "yellow", lemon: "yellow", cherry: "red"}

HTTP Sandboxing
Fazer requisies HTTP usando scripts em uma pgina web levanta preocupaes em relao segurana. A
pessoa que controla o script pode no ter os mesmos interesses que a pessoa do computador o qual o script
est executando. Mais especificamente, se eu visitar themafia.org, eu no quero que seus scripts possam fazer
requisies para myb ank.com, usando informaes de identificao do meu navegador, com instrues para
transferir todo meu dinheiro para alguma conta da mfia.

possvel que websites se protejam desses tipos de ataques, porm, preciso muito esforo e muitos websites
falham ao faz-lo. Por essa razo, os navegadores nos protegem no permitindo que scripts faam requisies
HTTP para outros domnios (nomes como themafia.org e myb ank.com).

Isso pode ser um problema irritante quando construmos sistemas que queiram acessar diferentes domnios por
razes legtimas. Felizmente, servidores podem incluir um cabealho como esse em sua resposta para indicar
explicitamente aos navegadores que permitido requisies que venham de outros domnios:

Access-Control-Allow-Origin: *

Abstraindo Requisies
No Captulo 10, em nossa implementao do mdulo AMD, usamos uma funo hipottica chamada
backgroundReadFile . Ela recebia um nome de arquivo e uma funo e, aps o carregamento do arquivo, chamava a
funo com o contedo recuperado. Aqui est uma implementao simples dessa funo:

236
function backgroundReadFile(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(req.responseText);
});
req.send(null);
}

Essa simples abstrao torna mais fcil usar XMLHttpRequest para fazer simples requisies GET . Se voc est
escrevendo um programa que precisa fazer requisies HTTP, uma boa ideia usar uma funo auxiliar para que
voc no acabe repetindo o esquisito padro XMLHttpRequest por todo o seu cdigo.

O nome da funo argumento ( callback ) um termo frequentemente usado para descrever funes como essa.
Uma funo callb ack fornecida para outro cdigo de forma que possa "ser chamada" mais tarde.

No difcil escrever uma funo utilitria HTTP adaptada para o que sua aplicao est fazendo. A funo
anterior apenas faz requisies GET e no nos d controle sobre os cabealhos ou sobre o corpo da requisio.
Voc pode escrever outra variao da funo para requisies POST ou uma verso genrica que suporte vrios
tipos de requisies. Muitas bibliotecas JavaScript tambm fornecem funes wrappers para XMLHttpRequest .

O principal problema com a funo wrapper anterior sua capacidade de lidar com falhas. Quando a requisio
retorna um cdigo de status que indica um erro (400 para cima), ela no faz nada. Isso pode ser aceitvel em
algumas circunstncias, mas imagine que colocamos na pgina um indicador de "carregando" para dizer que
estamos carregando informao. Se a requisio falhar por causa de queda do servidor ou a conexo for
interrompida, a pgina ir apenas manter o seu estado, parecendo que est fazendo alguma coisa. O usurio ir
esperar um tempo, ficar impaciente e, por fim, considerar que o site problemtico.

Ns tambm devemos ter uma opo de ser notificado quando a requisio falhar para que possamos tomar
uma ao apropriada. Por exemplo, podemos remover a mensagem "carregando" e informar ao usurio que algo
errado aconteceu.

Manipular erros em cdigo assncrono ainda mais trabalhoso do que manipul-los em cdigo sncrono.
Frequentemente, adiamos parte do nosso trabalho colocando-o em uma funo callb ack e, por isso, o escopo de
um bloco try acaba no fazendo sentido. No cdigo a seguir, a exceo no ser capturada pois a chamada
funo backgroundReadFile retorna imediatamente. O controle de execuo ento sai do bloco try e a funo que
foi fornecida no ser chamada at um momento posterior.

try {
backgroundReadFile("example/data.txt", function(text) {
if (text != "expected")
throw new Error("That was unexpected");
});
} catch (e) {
console.log("Hello from the catch block");
}

Para lidar com requisies que falharam, precisamos permitir que uma funo adicional seja passada para
nosso wrapper e cham-la quando ocorrer algo errado. Alternativamente, podemos usar a conveno de que,
caso a requisio falhar, um argumento adicional descrevendo o problema passado para a chamada regular da
funo callb ack. Segue um exemplo:

237
function getURL(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(req.responseText);
else
callback(null, new Error("Request failed: " +
req.statusText));
});
req.addEventListener("error", function() {
callback(null, new Error("Network error"));
});
req.send(null);
}

Adicionamos um manipulador para o evento de "error" (erro), o qual ser sinalizado quando a requisio falhar
por completo. Tambm chamamos a funo callb ack com um argumento de erro quando a requisio finaliza
com um cdigo de status que indica um erro.

O cdigo que utiliza getUrl deve, ento, verificar se um erro foi fornecido e, caso tenha sido, trat-lo.

getURL("data/nonsense.txt", function(content, error) {


if (error != null)
console.log("Failed to fetch nonsense.txt: " + error);
else
console.log("nonsense.txt: " + content);
});

Isso no funciona quando se trata de excees. Ao encadear vrias aes assncronas, uma exceo em
qualquer ponto da cadeia ainda ir (a no ser que voc envolva cada funo em seu prprio bloco try / catch )
chegar ao topo e abortar a sequncia de aes.

Promises
Para projetos complicados, escrever cdigo assncrono usando o estilo de callb acks difcil de ser feito
corretamente. fcil esquecer de verificar um erro ou permitir que uma exceo inesperada encerre a execuo
do programa. Alm disso, lidar com erros quando os mesmos devem ser passados por um fluxo de mltiplas
funes callb ack e blocos catch tedioso.

J foram feitas vrias tentativas para resolver esse problema usando abstraes adicionais. Uma das mais bem-
sucedidas chamada de promises. Promises encapsulam uma ao assncrona em um objeto, que pode ser
passado e instrudo a fazer certas coisas quando a ao finalizar ou falhar. Essa interface est definida para ser
parte da prxima verso da linguagem JavaScript, mas j pode ser usada em forma de biblioteca.

A interface para usar promises no muito intuitiva, mas poderosa. Esse captulo ir brevemente descrev-la.
Voc pode encontrar mais informaes em promisejs.org

Para criar um objeto promise, chamamos o construtor Promise passando uma funo que inicia a ao
assncrona. O construtor chama essa funo passando dois argumentos, os quais tambm so funes. A
primeira funo deve ser chamada quando a ao terminar com sucesso e a segunda quando ela falhar.

Mais uma vez, segue nosso wrapper para requisies GET , dessa vez retornando uma promise. Ns vamos
apenas cham-lo de get dessa vez.

238
function get(url) {
return new Promise(function(succeed, fail) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
succeed(req.responseText);
else
fail(new Error("Request failed: " + req.statusText));
});
req.addEventListener("error", function() {
fail(new Error("Network error"));
});
req.send(null);
});
}

Note que a interface da funo em si bem mais simples. Voc passa uma URL e ela retorna uma promise. Essa
promise atua como um manipulador do resultado da requisio. Ela possui um mtodo then que pode ser
chamado com duas funes: uma para tratar o sucesso e outra para tratar a falha.

get("example/data.txt").then(function(text) {
console.log("data.txt: " + text);
}, function(error) {
console.log("Failed to fetch data.txt: " + error);
});

At agora, isso apenas outra forma de expressar a mesma coisa que j havamos expressado. apenas
quando voc precisa encadear aes que as promises fazem uma diferena significativa.

Chamar o mtodo then produz uma nova promise, a qual o resultado (o valor passado ao manipulador em caso
de sucesso) depende do valor de retorno da primeira funo fornecida ao then . Essa funo pode retornar outra
promise para indicar que mais tarefas assncronas esto sendo executadas. Nesse caso, a promise retornada
pelo then ir esperar pela promise retornada pela funo manipuladora, obtendo sucesso ou falhando com o
mesmo valor quando for resolvida. Quando a funo manipuladora retornar uma valor que no seja uma promise,
a promise retornada pelo then imediatamente retorna com sucesso usando esse valor como seu resultado.

Isso significa que voc pode usar then para transformar o resultado de uma promise. Por exemplo, o cdigo a
seguir retorna uma promise a qual o resultado o contedo de uma dada URL representada como JSON:

function getJSON(url) {
return get(url).then(JSON.parse);
}

A ltima chamada ao then no especificou um manipulador para falhas. Isso permitido. O erro ser passado
para a promise retornada pelo then , que exatamente o que queremos getJSON no sabe o que fazer quando
algo der errado mas, esperanosamente, a funo que o chamou sabe.

Como um exemplo que demonstra o uso de promises, iremos construir um programa que carrega um nmero de
arquivos JSON do servidor e, enquanto isso feito, mostra a palavra "carregando". Os arquivos JSON contm
informaes sobre pessoas, com links para arquivos que representam outras pessoas em condies como
father , mother ou spouse (pai, me ou cnjuge).

Ns queremos recuperar o nome da me do cnjuge de example/b ert.json e, se algo der errado, remover o texto
"carregando" e mostrar uma mensagem de erro. Segue uma forma de como isso pode ser feito usando promises:

239
<script>
function showMessage(msg) {
var elt = document.createElement("div");
elt.textContent = msg;
return document.body.appendChild(elt);
}

var loading = showMessage("Loading...");


getJSON("example/bert.json").then(function(bert) {
return getJSON(bert.spouse);
}).then(function(spouse) {
return getJSON(spouse.mother);
}).then(function(mother) {
showMessage("The name is " + mother.name);
}).catch(function(error) {
showMessage(String(error));
}).then(function() {
document.body.removeChild(loading);
});
</script>

O programa acima relativamente compacto e legvel. O mtodo catch similar ao then , exceto que ele espera
um manipulador de erro como argumento e passar pelo resultado sem alter-lo em caso de sucesso. Muito
parecido com o catch em um bloco try , o controle de execuo ir continuar normalmente depois que a falha
capturada. Dessa forma, o then que executa ao final e responsvel por remover a mensagem de "carregando",
sempre executado, mesmo se algo der errado.

Voc pode pensar na interface de promise como uma implementao de uma linguagem prpria para o controle
de fluxo assncrono. As chamadas adicionais de mtodos e expresses de funes fazem com que o cdigo
parea um pouco estranho, mas no to estranhos quanto se tivssemos que lidar com todos os erros ns
mesmos.

Apreciando o HTTP
Quando estamos construindo um sistema que requer comunicao entre um programa executando JavaScript no
navegador (client-side) e um programa em um servidor (server-side), existem vrias maneiras diferentes de
modelar essa comunicao.

Um modelo bastante usado o de chamadas de procedimentos remotos. Nesse modelo, a comunicao segue o
padro de chamadas normais de funo, exceto pelo fato de que a funo est sendo executada em outra
mquina. Essa chamada envolve fazer uma requisio ao servidor, incluindo o nome da funo e seus
argumentos. A resposta para essa requisio contm o valor retornado.

Quando estiver pensando sobre chamadas de procedimentos remotos, o HTTP apenas um veculo para a
comunicao, e voc provavelmente escrever uma camada de abstrao para escond-la.

Outra abordagem construir sua comunicao em torno do conceito de recursos e mtodos HTTP. Ao invs de
um procedimento remoto chamado addUser , utilizar uma requisio PUT para /users/larry . Ao invs de passar
as propriedades daquele usurio como argumentos da funo, voc define um formato de documento ou usa um
formato existente que represente um usurio. O corpo da requisio PUT para criar um novo recurso
simplesmente tal documento. Um recurso pode ser acessado por meio de uma requisio GET para a URL do
recurso (por exemplo, /user/larry ), o qual retorna o documento que representa tal recurso.

Essa segunda abordagem torna mais fcil utilizar as funcionalidades que o HTTP fornece, como suporte para
cache de recursos (mantendo uma cpia no lado do cliente). Alm disso, ajuda na coerncia de sua interface,
visto que os recursos so mais fceis de serem compreendidos do que um monte de funes.

240
Segurana e HTTPS
Dados que trafegam pela Internet tendem a seguir uma longa e perigosa estrada. Para chegar ao seu destino, a
informao passa por vrios ambientes desde redes Wi-Fi em cafeterias at redes controladas por vrias
empresas e estados. Em qualquer ponto dessa rota, os dados podem ser inspecionados e, at mesmo,
modificados.

Se for importante que algo seja secreto, como a senha da sua conta de email, ou que chegue ao destino final
sem ser modificado, como o nmero da conta que voc ir transferir dinheiro por meio do site do seu banco, usar
simplesmente HTTP no bom o suficiente.

O protocolo HTTP seguro, o qual as URLs comeam com https://, encapsula o trfego HTTP de forma que dificulta
a leitura e alterao. Primeiramente, o cliente verifica se o servidor de fato quem ele diz ser, obrigando-o a provar
que possui um certificado criptogrfico emitido por uma autoridade certificadora que o navegador reconhea. Por
fim, todos os dados que trafegam pela conexo so criptografados de forma que assegure que eles estejam
protegidos contra espionagem e violao.

Por isso, quando funciona corretamente, o HTTPs previne ambas situaes onde algum finja ser o website ao
qual voc estava tentando se comunicar e quando algum est vigiando sua comunicao. O protocolo no
perfeito e j houveram vrios incidentes onde o HTTPs falhou por causa de certificados forjados, roubados e
software corrompido. Mesmo assim, trivial burlar o HTTP simples, enquanto que burlar o HTTPs requer um certo
nvel de esforo que apenas estados ou organizaes criminosas sofisticadas esto dispostas a fazer.

Resumo
Vimos nesse captulo que o HTTP um protocolo para acessar recursos usando a Internet. O cliente envia uma
requisio, a qual contm um mtodo (normalmente GET ) e um caminho que identifica o recurso. O servidor
ento decide o que fazer com a requisio e responde com um cdigo de status e o corpo da resposta. Tanto
requisies quanto respostas podem conter headers (cabealhos) que fornecem informao adicional.

Navegadores fazem requisies GET para acessar recursos necessrios para mostrar uma pgina web. Uma
pgina pode tambm conter formulrios, os quais permitem que informaes inseridas pelo usurio sejam
enviadas juntas com a requisio quando o formulrio submetido. Voc aprender mais sobre esse assunto no
prximo captulo.

A interface na qual o JavaScript do navegador pode fazer requisies HTTP chamada XMLHttpRequest . Voc
normalmente pode ignorar o "XML" do nome (mas mesmo assim precisa digit-lo). Existem duas formas em que
a interface pode ser usada. A primeira forma sncrona, bloqueando toda a execuo at que a requisio finalize.
A segunda assncrona, precisando usar um manipulador de eventos para avisar que a resposta chegou. Em
quase todos os casos prefervel usar a forma assncrona. Fazer uma requisio algo parecido com o cdigo a
seguir:

var req = new XMLHttpRequest();


req.open("GET", "example/data.txt", true);
req.addEventListener("load", function() {
console.log(req.status);
});
req.send(null);

Programao assncrona traioeira. Promises so interfaces que tornam a programao assncrona um pouco
mais fcil, ajudando a rotear condies de erro e excees para os manipuladores corretos e abstraindo muitos
elementos repetitivos e suscetveis a erro presentes nesse estilo de programao.

241
Exerccios
Negociao de contedo
Uma das coisas que o HTTP pode fazer, mas que no discutimos nesse captulo, chamada de negociao de
contedo. O cabealho Accept de uma requisio pode ser usado para dizer ao servidor qual o tipo de documento
que o cliente gostaria de receber. Muitos servidores ignoram esse cabealho, mas quando o servidor sabe como
converter um recurso de vrias formas, ele pode olhar esse cabealho e enviar a forma que o cliente prefere.

A URL eloquentjavascript.net/author configurada para responder tanto com formato simples de texto quanto com
HTML ou JSON, dependendo de como o cliente solicita. Esses formatos so identificados pelos tipos de mdia
text/plain , text/html e application/json .

Envie requisies para recuperar todos os trs formatos desse recurso. Use o mtodo setRequestHeader do seu
objeto XMLHttpRequest para definir o valor do cabealho chamado Accept para um dos tipos de mdia descritos
acima. Certifique-se de configurar o cabealho aps chamar o mtodo open e antes de chamar o mtodo send .

Por fim, tente solicitar pelo tipo de mdia application/rainbows+unicorns e veja o que acontece.

// Your code here.

Dicas:

Veja os vrios exemplos que usam XMLHttpRequest nesse captulo, para ter uma ideia de como so as chamadas
de mtodos que envolvem fazer uma requisio. Voc pode usar uma requisio sncrona (informando false

como o terceiro parmetro para open ), se preferir.

Solicitar por um tipo de mdia inexistente, far com que a resposta seja retornada com o cdigo 406, "Not
acceptab le" (No aceitvel), que o cdigo que o servidor deve retornar quando ele no pode satisfazer o
cabealho Accept .

Esperando por mltiplas promises


O construtor Promise, possui um mtodo chamado all que, quando fornecido um array de promises, retorna
uma promise que aguarda a finalizao de todas as promises do array. O mtodo all , ento, finaliza com
sucesso, gerando um array com os valores dos resultados. Se qualquer uma das promises do array falhar, a
promise retornada pelo all tambm falha, recebendo o valor de falha da promise que falhou inicialmente.

Tente implementar algo parecido com isso usando uma funo normal chamada all .

Observe que depois que a promise resolvida (obtendo sucesso ou falhado), ela no pode ter sucesso ou falhar
novamente, ignorando as chamadas s funes posteriores que tentam resolv-la. Isso pode facilitar a maneira
que voc manipula as falhas em sua promise.

242
function all(promises) {
return new Promise(function(success, fail) {
// Your code here.
});
}

// Test code.
all([]).then(function(array) {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(function(success) {
setTimeout(function() { success(val); },
Math.random() * 500);
});
}
all([soon(1), soon(2), soon(3)]).then(function(array) {
console.log("This should be [1, 2, 3]:", array);
});
function fail() {
return new Promise(function(success, fail) {
fail(new Error("boom"));
});
}
all([soon(1), fail(), soon(3)]).then(function(array) {
console.log("We should not get here");
}, function(error) {
if (error.message != "boom")
console.log("Unexpected failure:", error);
});

Dicas:

A funo passada ao construtor Promise ter que chamar o mtodo then para cada uma das promises do array
fornecido. Quando uma delas obtiver sucesso, duas coisas precisam acontecer. O valor resultante precisa ser
armazenado na posio correta em um array de resultados e devemos, tambm, verificar se essa foi a ltima
promise pendente, finalizando nossa prpria promise caso tenha sido.

O ltimo pode ser feito usando um contador, que inicializado com o valor do tamanho do array fornecido e do
qual subtramos uma unidade cada vez que uma promise for bem-sucedida. No momento em que o contador
chega ao valor zero, terminamos. Certifique-se de lidar com a situao em que o array fornecido vazio e,
consequentemente, nenhuma promise ser resolvida.

Tratar falhas requer um pouco mais de esforo, mas acaba sendo extremamente simples. Basta passar, para
cada promise do array, a funo que lida com a falha da promise responsvel por encapsular as promises do
array, de forma que uma falha em qualquer uma delas, ir disparar a falha para a promise encapsuladora.

243
Captulo 18

Formulrios e Campos de Formulrios


Em ingls:

"I shall this very day, at Doctors feast,My bounden service duly pay thee.But one thing! For insurance sake,
I pray thee,Grant me a line or two, at least.Mephistopheles, in Goethe's Faust"

Mephistopheles, in Goethe's Faust

Formulrios e Campos de Formulrio


Formulrios foram introduzidos brevemente no captulo anterior como uma forma de apresentar informaes
fornecidas pelo usurio atravs do HTTP. Eles foram projetados para uma web pr-javaScript, atribuindo essa
interao com o servidor sempre fazendo a navegao para uma nova pgina.

Mas os seus elementos so parte do DOM como o resto da pgina, e os elementos DOM representam campos
de formulrios suportados um nmero de propriedades e eventos que no so presente em outros elementos.
Esses tornam possveis para inspecionar e controlar os campos inputs com programas JavaScript e fazem coisas
como adicionar funcionalidades para um formulrio tradicional ou usam formulrios e campos como blocos de
construo em aplicaes JavaScript.

Campos
Um formulrio web consiste em qualquer nmero de campos input agrupados em uma tag <form> . O HTML
permite que um nmero de diferentes estilos de campos, que vo desde simples on/off checkboxes para drop-
down menus e campos de texto. Este livro no vai tentar discutir de forma abrangente todos os tipos de campos,
mas ns podemos iniciar com a viso geral.

Muitos tipos de campos usam a tag <input> . Essa tag um tipo de atributo usado para estilos do campo. Estes
so alguns tipos de <input> comumente usados:

text Um campo de texto em uma nica linha

password O mesmo que o campo de texto, mas esconde o texto que digitado

checkbox Um modificador on/off

radio Campo de mltipla escolha

file Permite que o usurio escolha um arquivo de seu computador

Os campos de formulrios no aparecem necessariamente em uma tag <form> . Voc pode coloc-los em
qualquer lugar. Esses campos no podem ser apresentados (somente em um formulrio como um todo), mas ao
retornar para o input com JavaScript, muitas vezes no querem submeter campos de qualquer maneira.

244
<p><input type="text" value="abc">(text)</p>
<p><input type="password" value="abc">(password)</p>
<p><input type="checkbox" checked>(checkbox)</p>
<p><input type="radio" value="A" name="choice">
<input type="radio" value="B" name="choice" checked>
<input type="radio" value="C" name="choice">(radio)</p>
<p><input type="file" checked> (file)</p>

A interface JavaScript de tais elementos se diferem com o tipo dos elementos. Ns vamos passar por cima de
cada um deles no final do captulo.

Campos de texto de vrias linhas tm a sua prpria tag <textarea> , principalmente porque quando usando um
atributo para especificar um valor de vrias linhas poderia ser inicialmente estranho. A tag <textarea> requer um
texto que usado entre as duas tags </textarea> , ao invs de utilizar o texto no atributo value.

<textarea>
um
dois
trs
</textarea>

Sempre que o valor de um campo de formulrio modificado, ele dispara um evento "change".

Focus
Diferentemente da maioria dos elementos em um documento HTML, campos de formulrio podem obter o foco do
teclado. Quando clicado, ou ativado de alguma outra forma, eles se tornam o elemento ativo no momento, o
principal destinatrio de entrada do teclado.

Se o documento tem um campo de texto, o campo focado quando texto digitado. Outros campos respondem
diferente ao evento de teclado. Por exemplo, um menu <select> vai para uma opo que contm o texto que o
usurio digitou e responde s teclas de seta, movendo sua seleo para cima e para baixo.

Podemos controlar focus do JavaScript com os mtodos focus e b lur. O primeiro modifica o foco do elemento que
chamado no DOM, e do segundo remove o focus. O valor no document.activeElement corresponde atualmente ao
elemento focado.

<input type="text">

document.querySelector("input").focus();
console.log(document.activeElement.tagName);
// INPUT
document.querySelector("input").blur();
console.log(document.activeElement.tagName);
// BODY

Para algumas pginas, espera-se que o usurio interaja com um campo de formulrio imediatamente. JavaScript
pode ser usado para ser d um focus nesse campo quando o documento carregado, mas o HTML tambm
fornece o atributo autofocus , que faz o mesmo efeito, mas permite que o navegador saiba o que estamos
tentando realizar. Isso faz com que seja possvel o navegador desativar o comportamento quando no o caso,
por exemplo, quando o usurio tem focado em outra coisa.

<input type="text" autofocus>

245
Navegadores tradicionais tambm permitem que o usurio mova o foco atravs do documento pressionando a
tecla [Tab]. Ns podemos influenciar a ordem na qual os elementos recebem o focus com o atributo tabindex .
Seguindo o exemplo do documento vai pular o foco do input text para o boto OK em vez de passar em primeiro
pelo link de help.

<input type="text" tabindex=1> <a href=".">(help)</a>


<button onclick="console.log('ok')" tabindex=2>OK</button>

Por padro, a maioria dos tipos de elementos HTML no podem ser focado. Mas voc pode adicionar um atributo
tabindex a qualquer elemento, o que tornar focalizvel.

Campos desativados
Todos o campos dos formulrios podem ser desabilitados por meio do seu atributo disabled , que tambm existe
como uma propriedade no elemento do objeto DOM.

<button>I'm all right</button>


<button disabled>I'm out</button>

Campos desabilitados no podem ser focalizados ou alterados, e ao contrrio de campos ativos, eles ficam cinza
e desbotado.

Quando um programa est sendo processado quando uma ao causada por algum boto ou outro controle, que
poderia requerer comunicao com o servidor e assim levar um tempo, pode ser uma boa ideia para desabilitar o
controle at que a ao termine. Dessa forma, quando o usurio fica impaciente e clica novamente, eles no vo
acidentalmente repetir a sua ao.

Formulrios Como um Todo


Quando o campo contido em um elemento <form> , elemento DOM deve estar em uma propriedade <form> que
faz a ligao por trs do fomulrio com o elemento DOM. O elemento <form> , tem uma propriedade chamada
elements que contm uma coleo de array-like dos campos internos dentro dele.

O atributo name de um campo de formulrio determina como seu valor ser identificado quando o formulrio
enviado. Ele tambm pode ser usado como um nome de propriedade quando acessar elements de propriedades
do formulrio, que atua tanto como um objeto array-like (acessvel pelo nmero) e um map (acessvel pelo nome).

<form action="example/submit.html">
Name: <input type="text" name="name"><br>
Password: <input type="password" name="password"><br>
<button type="submit">Log in</button>
</form>

var form = document.querySelector("form");


console.log(form.elements[1].type);
// password
console.log(form.elements.password.type);
// password
console.log(form.elements.name.form == form);
// true

Um boto com um atributo type do submit, quando pressionado, faz com que o formulrio seja enviado.
Pressionando Enter quando um campo de formulrio focado tem alguns efeitos.

246
O envio de um formulrio normalmente significa que o navegador navega para a pgina indicada pelo atributo
action , utilizando uma requisio GET ou POST. Mas Antes que isso acontea, um evento "submit" disparado.
Esse evento pode ser manipulado pelo JavaScript, e o manipulador pode impedir o comportamento padro
chamando preventDefault no objeto evento.

<form action="example/submit.html">
Value: <input type="text" name="value">
<button type="submit">Save</button>
</form>

var form = document.querySelector("form");


form.addEventListener("submit", function(event) {
console.log("Saving value", form.elements.value.value);
event.preventDefault();
});

Interceptar eventos sub mit em JavaScript tem vrios usos. Podemos escrever cdigo para verificar se o valores
que o usurio digitou faz sentido imediatamente mostrar uma mensagem de erro, em vez de enviar o formulrio.
Ou ns podemos desabilitar o modo regular de enviar o formulrio por completo, como no exemplo anterior,
temos o nosso programa que manipula o input , possivelmente usando XMLHttRequest para envi-lo para um
servidor sem recarregar a pgina.

Campos de Texto
Campos criados pela tag <input> com um tipo de text ou password, bem como uma tag textarea , compartilha
uma interface comum. Seus elementos DOM tem uma propriedade de valor que mantm o seu contedo atual
como um valor de string. A definio dessa propriedade para outra sequncia altera o contedo dos campos.

As propriedades selectionEnd e selectionEnd` de campos de texto nos do informaes sobre o curso e seleo
do texto. Quando no temos nada selecionado, estas duas propriedades tem o mesmo nmero o que indica a
posio do cursor. Por exemplo, 0 indica o incio do texto, e 10 indica o curso est aps o dcimo caractere.
Quando uma parte do campo selecionada as duas propriedades sero diferentes, nos dando o final e inicio do
texto selecionado. Essas propriedades tambm podem ser gravadas como valores.

Como exemplo, imagine que voc est escrevendo um artigo sobre Khasekhemwy, mas tem alguns problemas
para soletrar o seu nome. As seguintes linhas de cdigo at a tag <textarea> com um manipulador de eventos
que, quando voc pressionar F2, a string "Khasekhemwy" inserida para voc.

< textarea > < / textarea >

var textarea = document.querySelector("textarea");


textarea.addEventListener("keydown", function(event) {
// The key code for F2 happens to be 113
if (event.keyCode == 113) {
replaceSelection(textarea, "Khasekhemwy");
event.preventDefault();
}
});
function replaceSelection(field, word) {
var from = field.selectionStart, to = field.selectionEnd;
field.value = field.value.slice(0, from) + word +
field.value.slice(to);
// Put the cursor after the word
field.selectionStart = field.selectionEnd =
from + word.length;
}

247
A funo replaceSelection substitui a parte selecionada de um campo de texto com a palavra dada e em seguida,
move o cursor depois que a palavra de modo que o usurio pode continuar a escrever.

O evento altera um campo de texto e no dispara cada vez que algo digitado. Em vez disso, ele acionado
quando o campo perde o foco aps o seu contedo foi alterado. Para responder imediatamente a mudanas em
um campo de texto voc deve registrar um manipulador para o evento "input" em vez disso, que acionado para
cada vez que o usurio digitar um caractere, exclui do texto, ou de outra forma manipula o contedo do campo.

O exemplo a seguir mostra um campo de texto e um contador que mostra o comprimento atual do texto inserido:

< input type="text" > length: < span id="length" >0< / span >

var text = document.querySelector("input");


var output = document.querySelector("#length");
text.addEventListener("input", function() {
output.textContent = text.value.length;
});

Checkboxes e radio buttons


Um Checkbox uma alternncia binria simples. Seu valor pode ser extrado ou alterado por meio de sua
propriedade checked, que tem um valor booleano.

<input type="checkbox" id="purple"> <label for="purple">Make this page purple</label>

var checkbox = document.querySelector("#purple");


checkbox.addEventListener("change", function() {
document.body.style.background =
checkbox.checked ? "mediumpurple" : "";
});

A tag <label> usada para associar uma parte de texto com um campo de entrada. Seu atributo dever acessar o
id do campo. Clicando no label ir ativar o campo, que se concentra nele e alterna o seu valor quando um
checkbox ou button radio.

Um radio button semelhante a um checkbox, mas est implicitamente ligado a outros radio buttons com o
mesmo atributo de nome, de modo que apenas um deles pode estar ativo a qualquer momento.

Color:

<input type="radio" name="color" value="mediumpurple"> Purple


<input type="radio" name="color" value="lightgreen"> Green
<input type="radio" name="color" value="lightblue"> Blue

var buttons = document.getElementsByName("color");


function setColor(event) {
document.body.style.background = event.target.value;
}
for (var i = 0; i < buttons.length; i++)
buttons[i].addEventListener("change", setColor);

O mtodo document.getElementsByName nos d todos os elementos com um determinado atributo name. O exemplo
faz um loop sobre aqueles (com um loop regular for , no forEach, porque a coleo retornada no uma matriz
real) e registra um manipulador de eventos para cada elemento. Lembre-se que os eventos de objetos tem uma

248
propriedade target referindo-se ao elemento que disparou o evento. Isso muitas vezes til para manipuladores
de eventos como este, que ser chamado em diferentes elementos e precisa de alguma forma de acessar o
target atual.

Campos Select
Os campos select so conceitualmente similares aos radio buttons, eles tambm permitem que o usurio
escolha a partir de um conjunto de opes. Mas onde um boto de opo coloca a disposio das opes sob o
nosso controle, a aparncia de uma tag <select> determinada pelo browser.

Campos select tambm tm uma variante que mais parecido com uma lista de checkboxes, em vez de radio
boxes. Quando dado o atributo mltiplo, um <select> tag vai permitir que o usurio selecione qualquer nmero de
opes, em vez de apenas uma nica opo.

<select multiple>
<option>Pancakes</option>
<option>Pudding</option>
<option>Ice cream</option>
</select>

Isto, na maioria dos navegadores, mostra-se diferente do que um campo select no-mltiplo, que comumente
desenhado como um controle drop-down que mostra as opes somente quando voc abrir.

O atributo size da tag <select> usada para definir o nmero de opes que so visveis ao mesmo tempo, o que
lhe d o controle sobre a aparncia do drop-down. Por exemplo, definir o atributo size para "3" far com que o
campo mostre trs linhas, se ele tem a opo de multiple habilitado ou no.

Cada tag <option> tem um valor. Este valor pode ser definido com um atributo de value, mas quando isso no for
dado, o texto dentro do option ir contar como o valor do option . O valor da propriedade de um elemento
<select> reflete a opo selecionada no momento. Para um campo multiple , porm, esta propriedade no
significa muito, uma vez que vai possuir o valor apenas uma das opes escolhidas no momento.

As tags <option> de um campo <select> pode ser acessada como um objeto de array-like atravs de opes
propriedade do campo. Cada opo tem uma propriedade chamada selected, o que indica se essa opo for
selecionada. A propriedade tambm pode ser escrita para marcar ou desmarcar uma opo.

O exemplo a seguir extrai os valores selecionados a partir de um campo de seleo mltiplo e as utiliza para
compor um nmero binrio de bits individuais. Segure Ctrl (ou Comand no Mac) para selecionar vrias opes.

<select multiple>
<option value="1">0001</option>
<option value="2">0010</option>
<option value="4">0100</option>
<option value="8">1000</option>
</select> = <span id="output">0</span>

249
var select = document.querySelector("select");
var output = document.querySelector("#output");
select.addEventListener("change", function() {
var number = 0;
for (var i = 0; i < select.options.length; i++) {
var option = select.options[i];
if (option.selected)
number += Number(option.value);
}
output.textContent = number;
});

Campo Arquivo (Campo File)


Os campos de arquivo - (file), foram originalmente concebidos como uma maneira de fazer upload de arquivos de
uma mquina do navegador atravs de um formulrio. Em navegadores modernos, eles tambm fornecem uma
maneira de ler esses arquivos a partir de programas de JavaScript. O campo atua como uma forma de porteiro). O
script no pode simplesmente comear a ler arquivos privados do computador do usurio, mas se o usurio
seleciona um arquivo em tal campo, o navegador interpreta que a ao no sentido de que o script pode ler o
arquivo.

Um campo file geralmente parece um boto rotulado com algo como "escolha o arquivo" ou "procurar", com
informaes sobre o arquivo escolhido ao lado dele.

<input type="file">

var input = document.querySelector("input");


input.addEventListener("change", function() {
if (input.files.length > 0) {
var file = input.files[0];
console.log("You chose", file.name);
if (file.type)
console.log("It has type", file.type);
}
});

A propriedade files de um elemento campo file um objeto de array-like (novamente, no um array autntico)
que contm os arquivos escolhidos no campo. inicialmente vazio. A razo no simplesmente uma propriedade
de arquivo que os campos file tambm suportam um atributo mltiplo, o que torna possvel selecionar vrios
arquivos ao mesmo tempo.

Objetos na propriedade files tm propriedades como name (o nome do arquivo), size (o tamanho do arquivo em
bytes), e type (o tipo de mdia do arquivo, como text/plain ou image/jpeg).

O que ele no tem uma propriedade que contm o contedo do arquivo. Como um pouco mais complicado.
Desde a leitura de um arquivo do disco pode levar tempo, a interface ter de ser assncrona para evitar o
congelamento do documento. Voc pode pensar o construtor FileReader como sendo semelhante a
XMLHttpRequest, mas para arquivos.

<input type="file" multiple>

250
var input = document.querySelector("input");
input.addEventListener("change", function() {
Array.prototype.forEach.call(input.files, function(file) {
var reader = new FileReader();
reader.addEventListener("load", function() {
console.log("File", file.name, "starts with",
reader.result.slice(0, 20));
});
reader.readAsText(file);
});
});

A leitura de um arquivo feita atravs da criao de um objeto FileReader, registrando um manipulador de eventos
"load" para ele, e chamando seu mtodo readAsText, dando-lhe o arquivo para leitura. Uma vez finalizado o
carregamento,a propriedade de leitura result tem o contedo do arquivo. O exemplo usa Array.prototype.forEach

para iterar o array, uma vez em um loop (lao) normal, seria estranho obter os objetos file e read a partir de um
manipulador de eventos. As variveis poderiam compartilhar todas as iteraes do loop.

FileReaders tambm aciona um evento "error" ao ver o arquivo falhar por algum motivo. O prprio objeto de erro vai
acabar na propriedade de "error" de leitura. Se voc no quer lembrar dos detalhes de mais uma interface
assncrona inconsistente, voc pode envolv-lo em uma Promise (ver Captulo 17) como este:

function readFile(file) {
return new Promise(function(succeed, fail) {
var reader = new FileReader();
reader.addEventListener("load", function() {
succeed(reader.result);
});
reader.addEventListener("error", function() {
fail(reader.error);
});
reader.readAsText(file);
});
}

possvel ler apenas parte de um arquivo chamando slice sobre ele e passando o resultado (uma chamada de
um objeto b lob ) para o leitor de arquivos.

Armazenamento de dados Cliente-side


Pginas simples em HTML com um pouco de JavaScript pode ser um timo meio para "mini aplicativos"
programas auxiliares -Pequenos ajudantes que automatizam coisas cotidianas. Ao ligar alguns campos de
formulrio com os manipuladores de eventos, voc pode fazer qualquer coisa, desde a converso entre graus
Celsius e Fahrenheit s senhas de computao de uma senha mestra e um nome de site.

Quando tal aplicativo precisa lembrar-se de algo entre as sesses, voc no pode usar variveis JavaScript uma
vez que aqueles so jogados fora a cada vez que uma pgina fechada. Voc pode configurar um servidor,
conectar-se Internet, e ter o seu aplicativo de armazenamento de alguma coisa l. Vamos ver como fazer isso no
captulo 20. Mas isso adiciona um monte de trabalho extra e complexidade. s vezes suficiente apenas para
manter os dados no navegador. Mas como?

Voc pode armazenar dados de string data de uma forma que ainda continue ao recarregar a pgina, colocando-o
no objeto localStorage. Este objeto permite-lhe apresentar valores de strings sob nomes (tambm strings), como
neste exemplo:

251
localStorage.setItem("username", "marijn");
console.log(localStorage.getItem("username"));
// marijn
localStorage.removeItem("username");

Um valor em localStorage continua na pgina at que seja substitudo, ele removido com removeItem, ou o
usurio apaga seus dados locais.

Sites de domnios diferentes obtm diferentes espaos de armazenamento. Isso significa que os dados
armazenados em localStorage por um determinado site pode, a princpio, ser lido (e sobrescritos) por scripts
desse mesmo site.

Os navegadores tambm impor um limite para o tamanho dos dados de um site pode armazenar em
localStorage, tipicamente na ordem de poucos megabytes. Esta restrio, juntamente com o fato de encher os
discos rgidos das pessoas com lixo no realmente vivel, impede esse recurso de ocupar muito espao.

O cdigo a seguir implementa uma simples aplicao de anotaes. Ele mantm notas do usurio como um
objeto, associando ttulos de notas com strings de contedo. Este objeto codificado como JSON e armazenados
em localStorage. O usurio pode selecionar uma nota de um campo <select> e mudar o texto da nota em um
<textarea> . A nota pode ser adicionado clicando em um boto.

Notes:

<select id="list"></select>
<button onclick="addNote()">new</button><br>
<textarea id="currentnote" style="width: 100%; height: 10em">
</textarea>

252
var list = document.querySelector("#list");
function addToList(name) {
var option = document.createElement("option");
option.textContent = name;
list.appendChild(option);
}

// Initialize the list from localStorage


var notes = JSON.parse(localStorage.getItem("notes")) ||
{"shopping list": ""};
for (var name in notes)
if (notes.hasOwnProperty(name))
addToList(name);

function saveToStorage() {
localStorage.setItem("notes", JSON.stringify(notes));
}

var current = document.querySelector("#currentnote");


current.value = notes[list.value];

list.addEventListener("change", function() {
current.value = notes[list.value];
});
current.addEventListener("change", function() {
notes[list.value] = current.value;
saveToStorage();
});

function addNote() {
var name = prompt("Note name", "");
if (!name) return;
if (!notes.hasOwnProperty(name)) {
notes[name] = "";
addToList(name);
saveToStorage();
}
list.value = name;
current.value = notes[name];
}

O script inicializa a varivel notes para o valor armazenado em localStorage ou um valor que no existe, para
objeto simples notes "shopping list" vazio . A leitura de um campo que no existe de localStorage ser nulo.
Passando nulo para JSON.parse ir analisar uma string "null" e retornar null. Assim, o operador || pode ser
utilizada para fornecer um valor default de uma situao como esta.

Sempre que as alteraes de dados de notas (quando uma nova nota adicionado ou uma nota existente
modificada), a funo saveToStorage chamado para atualizar o campo de armazenamento. Se esta aplicao foi
destinado a lidar com milhares de notas, em vez de muitos, isso seria muito caro, e ns teramos que chegar a
uma maneira mais complicada para armazen-los, como dar cada nota de seu prprio campo de
armazenamento.

Quando o usurio adiciona uma nova nota, o cdigo deve atualizar o campo de texto explicitamente, mesmo que o
campo <select> tenha um manipulador de "change" que faz a mesma coisa. Isso necessrio porque o eventos
"change" disparam apenas quando o usurio altera o valor do campo, e no quando um script executa.

H um outro objeto semelhante para LocalStorage chamado sessionStorage . A diferena entre as duas que o
contedo de sessionStorag esquecido no fim de cada sesso, o que para a maioria dos navegadores significa
quando o navegador fechado.

Sumrio
253
HTML pode expressar vrios tipos de campos de formulrio, tais como text fields, checkboxes, campos multiple-
choice, e file pickers.

Esses campos podem ser inspecionados e manipulados com JavaScript. Eles acionam o evento "change"
quando alterado, o evento "input" quando o texto digitado, e vrios eventos de teclado. Estes eventos permitem-
nos a perceber quando o usurio est interagindo com os campos. Propriedades como value (para texto e
seleo campos) ou checked (para checkboxes e radio buttons)so usados para ler ou definir o contedo do
campo.

Quando um formulrio enviado, o evento "submit" dispara. Um manipulador de JavaScript pode chamar
preventDefault para impedir que que dispare o evento submit. Elementos de campo de formulrio no precisam
ser envolvidos em tags <form> .

Quando o usurio tenha selecionado um campo de seu sistema de arquivos local em um campo picker field, a
interface FileReader pode ser usado para acessar o contedo deste arquivo a partir de um programa de
JavaScript.

Os objetos LocalStorage e sessionStorage pode ser usado para guardar informaes de uma forma que continue
mesmo recarregando a pgina. O primeiro salva os dados para sempre (ou at que o usurio decida limp-la), e
o segundo salv-lo at que o navegador fechado.

Exerccios
A JavaScript workbench
Construa uma interface que permite que as pessoas a digitem e executem partes do cdigo JavaScript.

Coloque um boto ao lado de um campo <textarea> , ao ser pressionado, usa o construtor Function vimos no
Captulo 10 para dividir o texto em uma funo e cham-lo. Converter o valor de retorno da funo, ou qualquer
erro que elevado,em uma string e exibi-lo depois de o campo de texto.

<textarea id="code">return "hi";</textarea>


<button id="button">Run</button>
<pre id="output"></pre>

<script>
// Your code here.
</script>

Use document.querySelector ou document.getElementById para ter acesso aos elementos definidos em seu HTML. Um
manipulador de eventos para o " click " ou eventos no boto " mousedown " pode ter a propriedade value do campo
de texto e chamada new Function nele.

Certifique-se de envolver tanto a chamada para a new function e a chamada para o seu resultado em um bloco try
para que voc possa capturar excees que ela produz. Neste caso, ns realmente no sabemos que tipo de
exceo que estamos procurando, ento pegar tudo.

A propriedade textContent do elemento de sada pode ser usada para preench-lo com uma mensagem de string.
Ou, se voc quiser manter o contedo antigo ao redor, criar um novo n de texto usando document.createTextNode
e anex-lo ao elemento. Lembre-se de adicionar um caractere de nova linha at o fim, de modo que nem todas as
sadas apaream em uma nica linha.

Autocompletion

254
Estender um campo de texto para quando o usurio digitar uma lista de valores sugeridos mostrado abaixo do
campo. Voc tem um conjunto de possveis valores disponveis e deve mostrar aqueles que comeam com o texto
que foi digitado. Quando uma sugesto clicada, substitua o valor atual do campo de texto com ele.

<input type="text" id="field">


<div id="suggestions" style="cursor: pointer"></div>

// Builds up an array with global variable names, like


// 'alert', 'document', and 'scrollTo'
var terms = [];
for (var name in window)
terms.push(name);

// Your code here.

O melhor evento para a atualizao da lista de sugestes " input ", uma vez que ser acionado imediatamente
quando o contedo do campo alterado.

Em seguida, um loop por meio do array de termos e ver se eles comeam com a string dada. Por exemplo, voc
poderia chamar indexOf e ver se o resultado zero. Para cada sequncia correspondente, adicionar um elemento
para as sugestes <div> . Voc deve, provavelmente, cada vez que voc inicia comear vazio e atualizar as
sugestes, por exemplo, definindo sua textContent para a string vazia.

Voc poderia adicionar um manipulador de evento " click " [para cada elemento ou adicionar um nico para fora
<div> que prend-los e olhar para a propriedade target do evento para descobrir qual sugesto foi clicada.]

Para obter o texto sugesto de um n DOM, voc pode olhar para a sua textContent ou definir um atributo para
armazenar explicitamente o texto quando voc cria o elemento.

Conways Game of Life


Jogo da Vida de Conway uma simulao simples que cria a "vida" artificial em uma grade, cada clula, que ao
vivo ou no. Cada gerao (virar), as seguintes regras so aplicadas:

Qualquer clula viva com menos de dois ou mais de trs vizinhos vivos morre.

Qualquer clula viva com dois ou trs vizinhos vivos vive para a prxima gerao.

Qualquer clula morta com exatamente trs vizinhos vivos se torna um ce ao vivo

Um vizinho definido como qualquer clula adjacente, inclusive na diagonal adjacentes.

Nota-se que estas regras so aplicadas a toda a rede de uma s vez, e no um quadrado de cada vez. Isso
significa que a contagem de vizinhos baseia-se na situao no incio da produo, e mudanas acontecendo com
as clulas vizinhas durante esta gerao no deve influenciar o novo estado de uma dada clula.

Implementar este jogo usando qualquer estrutura de dados que voc ache mais apropriado. Use Math.random
para preencher a grade com um padro aleatrio inicialmente. Exibi-lo como uma grade de campos de
checkboxes, com um boto ao lado dele para avanar para a prxima gerao. Quando os controlos de utilizador
ou desmarca as checkboxes , as alteraes devem ser includos no clculo a prxima gerao.

<div id="grid"></div>
<button id="next">Next generation</button>

255
<script>
// Your code here.
</script>

Para resolver o problema de ter conceitualmente as alteraes ocorram ao mesmo tempo, tente ver o clculo de
uma gerao como uma funo pura, que tem uma grelha e produz uma nova grade que representa a curva
seguinte.

Representando a grade pode ser feito em qualquer das formas mostradas nos captulos 7 e 15. Contando
vizinhos vivos podem ser feitas com dois loops aninhados, percorrer coordenadas adjacentes. Tome cuidado para
no contar as clulas fora do campo e ignorar o celular no centro, cujos vizinhos estamos contando.

Fazer alteraes em check-boxes em vigor na prxima gerao pode ser feito de duas maneiras. Um manipulador
de eventos pode perceber essas alteraes e atualizar a grade atual para refleti-los, ou voc poderia gerar uma
nova grade a partir dos valores nas caixas de seleo antes de calcular o prximo turno.

Se voc optar utilizar manipuladores de eventos, voc pode querer anexar atributos que identificam a posio que
cada caixa corresponde ao modo que fcil descobrir qual clula de mudar.

Para desenhar a rede de caixas de seleo, voc ou pode usar um elemento <table> (olhe o Captulo 13) ou
simplesmente coloc-los todos no mesmo elemento e colocar <br> (quebra de linha) elementos entre as linhas.

256
Um programa de pintura
"Eu olho para muitas cores antes de mim. Eu olho para minha tela em branco. Ento, eu tento aplicar cores
como palavras que moldam poemas, como as notas que formam a msica." Joan Miro

O material do captulo anterior d para voc todo os elementos que voc precisa para construir uma aplicao
web simples. Nesse captulo, vamos fazer exatamente isso.

Nosso aplicativo ser um programa de desenho baseado na web, aos moldes do Microsoft Paint. Voc poder
us-lo para abrir arquivos de imagem, rabiscar sobre ela com o mouse e salv-la. Isso como vai se parecer:

Pintar pelo computador timo. Voc no precisa se preocupar com materiais, habilidades ou talento. Voc
apenas precisa comear a manchar.

Implementao
A interface para o programa de pintura mostra um grande elemento <canvas> na parte superior, com os campos
do formulrio abaixo dele. O usurio desenha na imagem ao selecionar uma ferramenta de um campo <select> e
em seguida clicando ou arrastando em toda a tela. Existem ferramentas para desenhar linhas, apagar partes da
imagem, adicionar texto e assim por diante.

Clicando na tela, ser delegado o evento mousedown para a ferramenta selecionada no momento, que poder
manipul-lo em qualquer maneira que escolher. A ferramenta de desenhar linha, por exemplo, vai ouvir os eventos
de mousemove at que o boto do mouse seja liberado e desenhar linhas atravs do caminho do mouse usando
a cor atual e o tamanho do pincel.

Cor e o tamanho do pincel so selecionados com campos adicionais no formulrio. Esses so ativados para
atualizar a tela desenhando o contedo fillStyle , strokeStyle e lineWidth toda hora que eles forem alterados.

257
Voc pode carregar uma imagem no programa de duas formas. A primeira usa um campo de arquivo, onde o
usurio pode selecionar um arquivo no seu computador. A segunda pede uma URL e vai pegar a imagem na
internet.

Imagens so salvas em um lugar atpico. O link de salvar est no canto direito da imagem ao lado do tamanho do
pincel. Ele pode ser seguido, compartilhado ou salvo. Eu vou explicar como isso possvel em um momento.

Construindo o DOM
A interface do nosso programa criado a partir de mais de 30 elementos DOM. Ns precisamos construir esses
de alguma maneira.

HTML o formato mais bvio para definir estrutura complexas do DOM. Mas, separando o programa em pedaos
de HTML e um script dificultada pelo fato de muitos elementos do DOM precisar de manipuladores de eventos
ou ser tocado por outro script de alguma outra forma. Assim, nosso script precisa fazer muitas chamadas de
querySelector (ou similar), afim de encontrar algum elemento DOM que ele precise para agir.

Seria bom se a estrutura DOM para cada parte da nossa interface fosse definida perto do cdigo JavaScript que
vai interagir com ela. Assim, eu escolhi por fazer toda a criao dos ns do DOM no JavaScript. Como ns vimos
no Captulo 13, a interface integrada para a criao da estrutura DOM terrivelmente verbosa. Se vamos fazer um
monte de construes DOM, precisamos de uma funo auxiliar.

Essa funo auxiliar uma funo estendida da funo elt a partir do Captulo 13. Ela cria um elemento com o
nome e os atributos dado e acrescenta todos os argumentos que ela recebe como ns filho, automaticamente
convertendo strings em ns de texto.

function elt(name, attributes) {


var node = document.createElement(name);
if (attributes) {
for (var attr in attributes)
if (attributes.hasOwnProperty(attr))
node.setAttribute(attr, attributes[attr]);
}
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child == "string")
child = document.createTextNode(child);
node.appendChild(child);
}
return node;
}

Isso nos permite criar elementos facilmente, sem fazer nosso cdigo fonte to longo e maante quanto um
contrato de usurio final corporativo.

A fundao
O ncleo do nosso programa a funo createPaint , que acrescenta a interface de pintura em um elemento do
DOM que dado como argumento. Porque ns queremos construir nosso programa pedao por pedao,
definimos um objeto chamado controls , que realizar funes para inicializar vrios controles abaixo da imagem.

258
var controls = Object.create(null);

function createPaint(parent) {
var canvas = elt("canvas", {width: 500, height: 300});
var cx = canvas.getContext("2d");
var toolbar = elt("div", {class: "toolbar"});
for (var name in controls)
toolbar.appendChild(controls[name](cx));

var panel = elt("div", {class: "picturepanel"}, canvas);


parent.appendChild(elt("div", null, panel, toolbar));
}

Cada controle tem acesso ao contexto da tela de desenho e, atravs da propriedade tela desse contexto, para o
elemento <canvas> . A maior parte do estado do programa vive nesta tela - que contm a imagem atual bem como
a cor selecionada (em sua propriedade fillStyle ) e o tamanho do pincel (em sua propriedade lineWidth ).

Ns envolvemos a tela e os controles em elementos <div> com classes que seja possvel adicionar um pouco
de estilo, como uma borda cinza envolta da imagem.

Ferramenta de seleo
O primeiro controle que ns adicionamos o elemento <select> que permite o usurio a selecionar uma
ferramenta de desenho. Tal como com os controles, vamos usar um objeto para coletar as vrias ferramentas de
modo que no temos que codific-las em um s lugar e ns podemos adicionar ferramentas novas mais tarde.
Esse objeto associa os nomes das ferramentas com a funo que dever ser chamada quando ela for
selecionada e quando for clicado na tela.

var tools = Object.create(null);

controls.tool = function(cx) {
var select = elt("select");
for (var name in tools)
select.appendChild(elt("option", null, name));

cx.canvas.addEventListener("mousedown", function(event) {
if (event.which == 1) {
tools[select.value](event, cx);
event.preventDefault();
}
});

return elt("span", null, "Tool: ", select);


};

O campo de ferramenta preenchida com elementos <option> para todas as ferramentas que foram definidas e
um manipulador mousedown no elemento canvas cuida de chamar a funo para a ferramenta atual, passando
tanto o objeto do evento quanto o contexto do desenho como argumentos. E tambm chama preventDefault para
que segurando o boto do mouse e arrastando no cause uma seleo do navegador em qualquer parte da
pgina.

A ferramenta mais bsica a ferramenta de linha, o qual permite o usurio a desenhar linhas com o mouse. Para
colocar o final da linha no lugar certo, temos que ser capazes de encontrar as coordenadas relativas do canvas
que um determinado evento do mouse corresponde. O mtodo getBoundingClientRect , brevemente mencionado no
Captulo 13, pode nos ajudar aqui. Nos diz que um elemento exibido, relativo ao canto top-left da tela. As
propriedades clientX e clientY dos eventos do mouse tambm so relativas a esse canto, ento podemos
subtrair o canto top-left da tela a partir deles para obter uma posio em relao a esse canto.

259
function relativePos(event, element) {
var rect = element.getBoundingClientRect();
return {x: Math.floor(event.clientX - rect.left),
y: Math.floor(event.clientY - rect.top)};
}

Vrias das ferramentas de desenho precisam ouvir os eventos mousemove at que o boto do mouse mantiver
pressionado. A funo trackDrag cuida de registrar e cancelar o registro de eventos para essas situaes.

function trackDrag(onMove, onEnd) {


function end(event) {
removeEventListener("mousemove", onMove);
removeEventListener("mouseup", end);
if (onEnd)
onEnd(event);
}
addEventListener("mousemove", onMove);
addEventListener("mouseup", end);
}

Essa funo recebe dois argumentos. Um a funo para chamar a cada evento mousemove e o outro uma
funo para chamar quando o boto do mouse deixa de ser pressionado. Qualquer argumento pode ser omitido
quando no for necessrio.

A ferramenta de linha usa esses dois mtodos auxiliares para fazer o desenho real.

tools.Line = function(event, cx, onEnd) {


cx.lineCap = "round";

var pos = relativePos(event, cx.canvas);


trackDrag(function(event) {
cx.beginPath();
cx.moveTo(pos.x, pos.y);
pos = relativePos(event, cx.canvas);
cx.lineTo(pos.x, pos.y);
cx.stroke();
}, onEnd);
};

A funo inicia por definir a propriedade do contexto de desenho lineCap para round , que faz com que ambas as
extremidades de um caminho traado, fique arredondada e no na forma padro quadrada. Esse o truque para
se certificar de que vrias linhas separadas, desenhadas em resposta a eventos separados, paream a mesma,
coerente. Com larguras maiores de linhas, voc vai ver as lacunas nos cantos se voc usar as linhas planas
padro.

Ento, para cada evento mousemove que ocorre enquanto o boto do mouse est pressionado, um simples
segmento de linha entre o boto do mouse apertado e a nova posio, usando qualquer strokeStyle e lineWidth

comea a ser desenhado no momento.

O argumento onEnd para a ferramenta de linha simplesmente passado atravs do trackDrag . O caminho
normal para executar as ferramentas no vai passar o terceiro argumento, portando, quando usar a ferramenta
linha, esse argumento mantm undefined e nada acontece no final do movimento do mouse. O argumento est l
para nos permitir implementar a ferramenta de apagar na parte superior da ferramenta da linha com muito pouco
cdigo adicional.

260
tools.Erase = function(event, cx) {
cx.globalCompositeOperation = "destination-out";
tools.Line(event, cx, function() {
cx.globalCompositeOperation = "source-over";
});
};

A propriedade globalCompositeOperation influencia o modo como as operaes de desenhar em uma tela de


desenho altera a cor dos pixels que tocam. Por padro, o valor da propriedade source-over , o que significa que a
cor do desenho sobreposta sobre a cor j existente naquele ponto. Se a cor opaca, vai apenas substituir a cor
antiga, mas, se parcialmente transparente, as duas sero misturadas.

A ferramenta apagar define globalCompositeOperation para destination-out , que tem o efeito de apagar os pixels que
tocamos, tornando-os transparentes novamente.

Isso nos d duas ferramentas para nosso programa de pintura. Ns podemos desenhar linhas pretas em um
nico pixel de largura (o padro strokeStyle e lineWidth para uma tela) e apag-los novamente. um trabalho,
mesmo que ainda bastante limitado, programa de pintura.

Cor e tamanho do pincel


Partindo do princpio que os usurios vo querer desenhar em cores diferentes do preto e usar tamanhos
diferentes de pincis, vamos adicionar controles para essas duas definies.

No Captulo 8, eu discuti um nmero de diferentes campos de formulrio. Campo de cor no estava entre
aqueles. Tradicionalmente, os navegadores no tem suporte embutido para seletores de cores, mas nos ltimos
anos, uma srie de tipos de campos foram padronizados. Uma delas <input type='color'> . Outros incluem
date , email , url e number . Nem todos os navegadores suportam eles ainda - no momento da escrita, nenhuma
verso do Internet Explorer suporta campos de cor. O tipo padro de uma tag <input> text , e quando um tipo
no suportado usado, os navegadores iro trat-lo como um campo de texto. Isso significa que usurios do
Internet Explorer executando o nosso programa de pintura vo ter que digitar o nome da cor que quiser, ao invs
de selecion-la a partir de um componente conveniente.

controls.color = function(cx) {
var input = elt("input", {type: "color"});
input.addEventListener("change", function() {
cx.fillStyle = input.value;
cx.strokeStyle = input.value;
});
return elt("span", null, "Color: ", input);
};

Sempre que o valor do campo cor muda, o fillStyle e strokeStyle do contexto so atualizados para manter o
novo.

O campo para configurar o tamanho do pincel funciona de forma semelhante.

261
controls.brushSize = function(cx) {
var select = elt("select");
var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100];
sizes.forEach(function(size) {
select.appendChild(elt("option", {value: size},
size + " pixels"));
});
select.addEventListener("change", function() {
cx.lineWidth = select.value;
});
return elt("span", null, "Brush size: ", select);
};

O cdigo gera opes a partir de uma array de tamanhos de pincel e, novamente, garante que o lineWidth seja
atualizado quando um tamanho de pincel escolhido.

Salvando
Para explicar a implementao do link salvar, eu preciso falar sobre data URLs . Um data URL uma URL com
dados: como seu protocolo. Ao contrrio de http : normal e https : URLS, URLs de dados no apontam para
algum recurso mas sim, contm todo o recurso em si. Esta uma URL de dados contendo um simples
documento HTML.

data:text/html,<h1 style="color:red">Hello!</h1>

Essas URLs so teis para vrias tarefas, tais como a incluso de pequenas imagens diretamente em um
arquivo de folha de estilo. Eles tambm nos permitem linkar para arquivos que ns criamos no lado do cliente, no
navegador, sem antes mover para algum servidor.

Elementos canvas tem um mtodo conveniente, chamado toDataURL , que ir retornar a URL de dados que contm
a imagem no canvas como um arquivo de imagem. Ns no queremos para atualizar nosso link de salvar toda
vez que a imagem for alterada. Para imagens grandes, que envolve a transferncia de um monte de dados em um
link, seria visivelmente lento. Em vez disso, ns atualizaremos o atributo href do link sempre que o foco do
teclado estiver sobre ele ou o mouse movido sobre ele.

controls.save = function(cx) {
var link = elt("a", {href: "/"}, "Save");
function update() {
try {
link.href = cx.canvas.toDataURL();
} catch (e) {
if (e instanceof SecurityError)
link.href = "javascript:alert(" +
JSON.stringify("Can't save: " + e.toString()) + ")";
else
throw e;
}
}
link.addEventListener("mouseover", update);
link.addEventListener("focus", update);
return link;
};

Assim, o link fica calmamente ali sentado, apontando para a coisa errada, mas quando o usurio se aproxima, ele
magicamente se atualiza para apontar para a imagem atual.

Se voc carregar uma imagem grande, alguns navegadores vo ter problemas com as URLs de dados gigantes
que essa produz. Para imagens pequenas, essa abordagem funciona sem problemas.

262
Mas aqui estamos, mais uma vez para correr para as sutilezas do navegador sandboxing. Quando uma imagem
carregada a partir de uma URL para outro domnio, se a resposta do servidor no incluir o header que diz ao
navegador que o recurso pode ser usado para outro domnio (ver o Captulo 17), a tela ir conter as informaes
que o usurio pode olhar, mas que o script no pode.

Podemos ter solicitado uma imagem que contenha informaes privadas (por exemplo, um grfico que mostre o
saldo da conta bancria do usurio) usando a sesso do usurio. Se os scripts puderem obter as informaes
dessa imagem, eles podem espionar o usurio de formas indesejveis.

Para evitar esse tipo vazamento de informaes, os navegadores ir deixar a tela to manchada quando se trada de
uma imagem que o script no pode ver. Dados de pixel, incluindo URLs de dados, no podem ser extrados de
uma tela manchada. Voc pode escrever para ele, mas voc no pode mais ler.

por isso que precisamos das instrues try/catch na funo update para o link salvar. Quando o canvas ficou
corrompido, chamando toDataURL ir lanar uma exceo que uma instncia do SecurityError . Quando isso
acontece, ns definimos o link para apontar para outro tipo de URL, usando o javascript : protocolo. Esses links
simplesmente executam o script dado aps os dois pontos para que o link ir mostrar uma janela de alert

informando o usurio do problema quando clicado.

Carregando arquivos de imagem


Os dois controles finais so usados para carregar arquivos de imagens locais e a partir de URLs. Vamos precisar
da seguinte funo auxiliar, que tenta carregar um arquivo de imagem a partir de uma URL e substitui o contedo
do canvas por ela.

function loadImageURL(cx, url) {


var image = document.createElement("img");
image.addEventListener("load", function() {
var color = cx.fillStyle, size = cx.lineWidth;
cx.canvas.width = image.width;
cx.canvas.height = image.height;
cx.drawImage(image, 0, 0);
cx.fillStyle = color;
cx.strokeStyle = color;
cx.lineWidth = size;
});
image.src = url;
}

Ns queremos alterar o tamanho do canvas para ajustar precisamente a imagem. Por alguma razo, alterar o
tamanho do canvas vai causar perca das configuraes do contexto do desenho como fillStyle e lineWidth ,
ento a funo salva elas e restaura depois de ter atualizado o tamanho do canvas .

O controle para o carregamento de um arquivo local utiliza a tcnica FileReader a partir do Captulo 18. Alm do
mtodo readAsText que ns usamos l, tais objetos de leitura tambm tem um mtodo chamado readAsDataURL ,
que exatamente o que precisamos aqui. Ns carregamos o arquivo que o usurio escolheu como URL de
dados e passaremos para loadImageURL para coloc-lo no canvas .

263
controls.openFile = function(cx) {
var input = elt("input", {type: "file"});
input.addEventListener("change", function() {
if (input.files.length == 0) return;
var reader = new FileReader();
reader.addEventListener("load", function() {
loadImageURL(cx, reader.result);
});
reader.readAsDataURL(input.files[0]);
});
return elt("div", null, "Open file: ", input);
};

Carregando um arquivo atravs de uma URL ainda mais simples. Mas, com o campo de texto, menos limpo
quando o usurio termina de escrever a URL, por isso ns no podemos simplesmente ouvir pelos eventos
change . Ao invs disso, vamos envolver o campo de um formulrio e responder quando o formulrio for enviado,
seja porque o usurio pressionou Enter ou porque clicou no boto de carregar.

controls.openURL = function(cx) {
var input = elt("input", {type: "text"});
var form = elt("form", null,
"Open URL: ", input,
elt("button", {type: "submit"}, "load"));
form.addEventListener("submit", function(event) {
event.preventDefault();
loadImageURL(cx, form.querySelector("input").value);
});
return form;
};

Ns temos agora definidos todos os controles que o nosso simples programa de pintura precisa, mas isso ainda
poderia usar mais algumas ferramentas.

Finalizando
Ns podemos facilmente adicionar uma ferramenta de texto que pea para o usurio qual o texto que deve
desenhar.

tools.Text = function(event, cx) {


var text = prompt("Text:", "");
if (text) {
var pos = relativePos(event, cx.canvas);
cx.font = Math.max(7, cx.lineWidth) + "px sans-serif";
cx.fillText(text, pos.x, pos.y);
}
};

Voc pode adicionar campos extras para o tamanho da fonte, mas para simplificar, sempre use uma fonte sans-

serif e baseie o tamanho da fonte com o tamanho atual do pincel. O tamanho mnimo de 7 pixels porque texto
menor do que isso, ilegvel.

Outra ferramenta indispensvel para a desenhos de computao amadores a ferramenta tinta spray. Este
desenha pontos em locais aleatrios sobre o pincel, desde que o mouse pressionado, criando mais denso ou
salpicado menos densa com base em quo rpido ou lento os movimentos do mouse so.

264
tools.Spray = function(event, cx) {
var radius = cx.lineWidth / 2;
var area = radius * radius * Math.PI;
var dotsPerTick = Math.ceil(area / 30);

var currentPos = relativePos(event, cx.canvas);


var spray = setInterval(function() {
for (var i = 0; i < dotsPerTick; i++) {
var offset = randomPointInRadius(radius);
cx.fillRect(currentPos.x + offset.x,
currentPos.y + offset.y, 1, 1);
}
}, 25);
trackDrag(function(event) {
currentPos = relativePos(event, cx.canvas);
}, function() {
clearInterval(spray);
});
};

A ferramenta spray usa setInterval para cuspir pontos coloridos a cada 25 milissegundos enquanto o boto do
mouse pressionado. A funo trackDrag usada para manter o currentPos apontando para a posio atual do
mouse e para desligar o intervalo quando o mouse liberado.

Para determinar quantos pontos sero desenhados a cada intervalo de cliques, a funo calcula a rea do pincel
e divide por 30. Para encontrar uma posio aleatria sob o pincel, usada a funo randomPointLnRadius .

function randomPointInRadius(radius) {
for (;;) {
var x = Math.random() * 2 - 1;
var y = Math.random() * 2 - 1;
if (x * x + y * y <= 1)
return {x: x * radius, y: y * radius};
}
}

A funo gera pontos no quadrado entre (-1,-1) e (1,1). Usando o teorema de Pitgoras, ele testa se o ponto
gerado encontra-se dentro de um crculo de raio 1. Logo que a funo encontrar esse ponto, ele retorna o ponto
multiplicado pelo argumento de raio.

O crculo necessrio para a distribuio uniforme dos pontos. A maneira simples de gerar um ponto aleatrio
dentro de um crculo seria a utilizao de um ngulo e distncia aleatria e chamar Math.sin e Math.cos para criar
o ponto correspondente. Mas com esse mtodo, os pontos so mais provveis de aparecerem perto do centro do
crculo. Existem outras maneiras de contornar isso, mas elas so mais complicadas do que o ciclo anterior.

Ns agora temos um programa de pintura funcionando. Execute o cdigo abaixo para experiment-lo.

<link rel="stylesheet" href="css/paint.css">

<body>
<script>createPaint(document.body);</script>
</body>

Exerccios
Ainda h muito espao para melhorias nesse programa. Vamos adicionar mais algumas funcionalidades como
exerccio.

Retngulos

265
Definir uma ferramenta chamada Retngulo que preenche um retngulo (veja o mtodo fillRect a partir do
Captulo 16) com a cor atual. O retngulo deve espalhar a partir do ponto onde o usurio pressionar o boto do
mouse para o ponto onde ele liberado. Note-se que este ltimo pode estar acima ou a esquerda do primeiro.

Uma vez que ele funcionar, voc vai perceber que um pouco chocante no ver o retngulo como voc est
arrastando o mouse para definir o seu tamanho. Voc pode chegar a uma maneira de mostrar algum tipo de
retngulo durante o movimento do mouse, sem realmente ir desenhando no canvas at que o boto do mouse
seja liberado?

Se nada lhe vem a mente, relembre position: absolute discutido no Captulo 13, que pode ser usado para
sobrepor um n no resto do documento. As propriedades pagex e pageY de um evento de mouse pode ser usada
para a posicionar um elemento precisamente sob o mouse, definindo os estilos left , top , width e height para
os valores corretos de pixel.

<script>
tools.Rectangle = function(event, cx) {
// Your code here.
};
</script>

<link rel="stylesheet" href="css/paint.css">


<body>
<script>createPaint(document.body);</script>
</body>

Dicas

Voc pode utilizar relativePos para encontrar o canto correspondente ao incio do arrasto do mouse. Descobrir
aonde as extremidades de arrasto termina pode ser com trackDrag ou registrando seu prprio manipulador de
eventos.

Quando voc tem dois cantos do retngulo, voc deve de alguma forma traduzi-los em argumentos que o
fillRect espera: O canto top-lef , width e height do retngulo. Math.min pode ser usado para encontrar a
coordenada mais a esquerda X e a coordenada superior Y. Para obter o width e height , voc pode chamar
Math.abs (o valor absoluto) sobre a diferena entre os dois lados.

Mostrando o retngulo durante o arrastar do mouse requer um conjunto semelhante de nmeros, mas no contexto
de toda a pgina em vez de em relao ao canvas . Considere escrever um findRect , que converte dois pontos em
um objeto com propriedades top , left , width e height , de modo que voc no tenha que escrever a mesma
lgica duas vezes.

Voc pode, ento, criar uma <div> e definir seu style.position como absolute . Ao definir os estilos de
posicionamento, no se esquea de acrescentar px para os nmeros. O n deve ser adicionado ao documento
(voc pode acrescent-la document.body ) e tambm remover novamente quando o arrasto do mouse terminar e o
retngulo real for desenhado na tela.

Seletor de cores
Outra ferramenta que comumente encontrada em programas grficos um seletor de cores, o que permite que
o usurio clique na imagem e seleciona a cor sob o ponteiro do mouse. Construa este.

Para esta ferramenta, precisamos de uma maneira para acessar o contedo do canvas . O mtodo toDataURL

mais ou menos faz isso, mas recebendo informaes de pixel para fora de tal URL de dados difcil. Em vez
disso, vamos usar o mtodo getImageData no contexto do desenho, que retorna um pedao retangular da imagem
como um objeto com propriedades width , height e dados. A propriedade de dados contm um array de nmeros
de 0 a 255, com quatro nmeros para representar, componentes de cada pixel vermelho, verde, azul e opacidade.

266
Este exemplo recupera os nmeros para um nico pixel de uma tela de uma vez, quando o canvas est em
branco (todos os pixels so preto transparente) e uma vez quando o pixel foi colorido de vermelho.

function pixelAt(cx, x, y) {
var data = cx.getImageData(x, y, 1, 1);
console.log(data.data);
}

var canvas = document.createElement("canvas");


var cx = canvas.getContext("2d");
pixelAt(cx, 10, 10);
// [0, 0, 0, 0]

cx.fillStyle = "red";
cx.fillRect(10, 10, 1, 1);
pixelAt(cx, 10, 10);
// [255, 0, 0, 255]

Os argumentos para getImageData indica o incio das coordenadas x e y do retngulo que queremos recuperar,
seguido por width e height .

Ignore transparncia durante este exerccio e se importe apenas com os trs primeiros valores para um
determinado pixel. Alm disso, no se preocupe com a atualizao do campo de cor quando o usurio escolher
uma cor. Apenas certifique-se de que fillStyle do contexto do desenho e strokeStyle foram definidos com a cor
sob o cursor do mouse.

Lembre-se que essas propriedades aceita qualquer cor que o CSS entende, que inclui o rgb (R, G, B) estilo que
voc viu no Captulo 15..

O mtodo getImageData est sujeito as mesmas restries como toDataURL ir gerar um erro quando a tela conter
pixels que se originam a partir de outro domnio. Use um try/catch para relatar tais erros com um dilogo de
alerta.

<script>
tools["Pick color"] = function(event, cx) {
// Your code here.
};
</script>

<link rel="stylesheet" href="css/paint.css">


<body>
<script>createPaint(document.body);</script>
</body>

Exibir dicas
Voc de novo vai precisar usar relativePos para descobrir qual pixel foi clicado. A funo pixelAt no exemplo
demonstra como obter os valores para um determinado pixel. Colocar eles em uma string rgb exige apenas
algumas concatenaes.

Certifique-se de verificar se a exceo que voc pegar uma instncia de SecurityError de modo que voc no
trate acidentalmente o tipo errado de exceo.

Preenchimento
Este um exerccio mais avanado do que os dois anteriores, e isso vai exigir que voc projete uma soluo no
trivial para um problema complicado. Certifique-se de que voc tem bastante tempo e pacincia antes de comear
a trabalhar neste exerccio e no desanime por falhas iniciais.

267
A ferramenta preenchimento de cores do pixel sob o mouse e todo o grupo de pixels em torno dele que tm a
mesma cor. Para efeitos deste exerccio, vamos considerar esse grupo para incluir todos os pixels que podem ser
alcanadas a partir de nosso pixel inicial movendo em um nico pixel de medidas horizontais e verticais (no
diagonal), sem nunca tocar um pixel que tenha uma cor diferente a partir do pixel de partida.

A imagem a seguir ilustra o conjunto de pixels coloridos quando a ferramenta de preenchimento usada no pixel
marcado:

O preenchimento no vaza atravs de aberturas diagonais e no toca pixels que no so acessveis, mesmo que
tenham a mesma cor que o pixel alvo.

Voc vai precisar mais uma vez do getImageData para descobrir a cor para cada pixel. provavelmente uma boa
ideia para buscar a imagem inteira de uma s vez e, em seguida, selecionar os dados de pixel do array resultante.
Os pixels so organizados nesse array de uma forma semelhante aos elementos de rede, no Captulo 7, uma
linha de cada vez, exceto que cada pixel representado por quatro valores. O primeiro valor para o pixel em (x, y)
a posio (x + y x width) x 4.

No incluam o quarto valor (alpha) desta vez, j que queremos ser capazes de dizer a diferena entre pixels pretos
e vazios.

Encontrar todos os pixels adjacentes com a mesma cor exige que voc "ande" sobre a superfcie do pixel, um pixel
para cima, baixo, esquerda ou direita, at que os novos pixels da mesma cor possam ser encontrados. Mas voc
no vai encontrar todos os pixels em um grupo na primeira caminhada. Em vez disso, voc tem que fazer algo
semelhante para o retrocesso feito pela expresso de correspondncia regular, descrito no Captulo 9. Sempre
que for possvel proceder por mais de uma direo for possvel, voc deve armazenar todas as instrues que
voc no tomar imediatamente e voltar para elas mais tarde, quando terminar a sua caminhada atual.

Em uma imagem de tamanho normal, h um grande nmero de pixels. Assim, voc deve ter o cuidado de fazer a
quantidade mnima de trabalho necessrio ou o seu programa vai levar muito tempo para ser executado. Por
exemplo, todos os passos devem ignorar pixels vistos por caminhadas anteriores, de modo que ele no refaa o
trabalho que j foi feito.

Eu recomendo chamando fillRect para pixels individuais quando um pixel que deve ser colorido encontrado e
manter alguma estrutura de dados que informe sobre todos os pixels que j foram analisados.

<script>
tools["Flood fill"] = function(event, cx) {
// Your code here.
};
</script>

<link rel="stylesheet" href="css/paint.css">


<body>
<script>createPaint(document.body);</script>
</body>

Exibir dicas

268
Dado um par de coordenadas de partida e os dados da imagem para todo o canvas, esta abordagem deve
funcionar:

Criar uma array para armazenar informaes sobre coordenadas j coloridas.


Criar uma array de lista de trabalho para segurar coordenadas que devem ser analisadas. Coloque a posio
inicial na mesma.
Quando a lista de trabalho estiver vazia, estamos prontos.
Remova um par de coordenadas a partir da lista de trabalho.
Se essas coordenadas j esto em nosso array de pixels coloridos, volte para o passo 3.
Colorir o pixel nas coordenadas atuais e adicionar as coordenadas para o array de pixels coloridos.
Adicionar as coordenadas de cada pixel adjacente cuja cor a mesma que a cor original do pixel inicial para a
lista de trabalho.
Retorne ao passo 3.

A lista de trabalho pode ser simplesmente um array de objetos vetoriais. A estrutura de dados que rastreia pixels
coloridos sero consultados com muita freqncia. Buscar por toda a coisa toda vez que um novo pixel visitado
vai custar muito tempo. Voc poderia, ao invs de criar um array que tenha um valor nele para cada pixel, usando
novamente x + y esquema de largura para a associao de posies com pixels. Ao verificar se um pixel j foi
colorido, voc pode acessar diretamente o campo correspondente ao pixel atual.

Voc pode comparar cores, executando sobre a parte relevante do array de dados, comparando um campo de
cada vez. Ou voc pode "condensar" uma cor a um nico nmero ou o texto e comparar aqueles. Ao fazer isso,
certifique-se de que cada cor produza um valor nico. Por exemplo, a simples adio de componentes da cor no
segura, pois vrias cores ter a mesma soma.

Ao enumerar os vizinhos de um determinado ponto, tenha o cuidado de excluir os vizinhos que no esto dentro
da tela ou o seu programa poder correr em uma direo para sempre.

269
Node.js
"Um estudante perguntou Os programadores de antigamente usavam somente mquinas simples e
nenhuma linguagem de programao, mas mesmo assim eles construram lindos programas. Por que ns
usamos mquinas complicadas e linguagens de programao?. Fu-Tzu respondeu Os construtores de
antigamente usaram somente varas e barro, mas mesmo assim eles construram lindas cabanas." Mestre
Yuan-Ma, The Book of Programming

At agora voc vem aprendendo e usando a linguagem JavaScript num nico ambiente: o navegador. Esse
captulo e o prximo vo introduzir brevemente voc ao Node.js, um programa que permite que voc aplique suas
habilidades de JavaScript fora do navegador. Com isso, voc pode construir desde uma ferramenta de linha de
comando at servidores HTTP dinmicos.

Esses captulos visam te ensinar conceitos importantes nos quais o Node.js foi construdo, e tambm te dar
informao suficiente para escrever alguns programas teis. Esses captulos no detalham completamente o
funcionamento do Node.

Voc vem executando o cdigo dos captulos anteriores diretamente nessas pginas, pois eram pura e
simplesmente JavaScript ou foram escritos para o navegador, porm os exemplos de cdigos nesse captulo so
escritos para o Node e no vo rodar no navegador.

Se voc quer seguir em frente e rodar os cdigos desse captulo, comece indo em http://nodejs.org e seguindo as
instrues de instalao para o seu sistema operacional. Guarde tambm esse site como referncia para uma
documentao mais profunda sobre Node e seus mdulos integrados.

Por Trs dos Panos


Um dos problemas mais difceis em escrever sistemas que se comunicam atravs de uma rede administrar a
entrada e sada ou seja, ler escrever dados na rede, num disco rgido, e outros dispositivos. Mover os dados
desta forma consome tempo, e planejar isso de forma inteligente pode fazer uma enorme diferena na velocidade
em que um sistema responde ao usurio ou s requisies da rede.

A maneira tradicional de tratar a entrada e sada ter uma funo, como readfile , que comea a ler um arquivo e
s retorna quando o arquivo foi totalmente lido. Isso chamado I/O sncrono (I/O quer dizer input/output ou
entrada/sada).

Node foi inicialmente concebido para o propsito de tornar a assincronicidade I/O mais fcil e conveniente. Ns j
vimos interfaces sncronas antes, como o objeto XMLHttpRequest do navegador, discutido no Captulo 17. Uma
interface assncrona permite que o script continue executando enquanto ela faz seu trabalho e chama uma funo
de callb ack quando est finalizada. Isso como Node faz todo seu I/O.

JavaScript ideal para um sistema como Node. uma das poucas linguagens de programao que no tem
uma maneira embutida de fazer I/O. Dessa forma, JavaScript poderia encaixar-se bastante na abordagem
excntrica do Node para o I/O sem acabar ficando com duas interfaces inconsistentes. Em 2009, quando Node foi
desenhado, as pessoas j estavam fazendo I/O baseado em funes de callb ack no navegador, ento a
comunidade em volta da linguagem estava acostumada com um estilo de programao assncrono.

Assincronia
Eu vou tentar ilustrar I/O sncrono contra I/O assncrono com um pequeno exemplo, onde um programa precisa
buscar recursos da Internet e ento fazer algum processamento simples com o resultado dessa busca.

270
Em um ambiente sncrono, a maneira bvia de realizar essa tarefa fazer uma requisio aps outra. Esse
mtodo tem a desvantagem de que a segunda requisio s ser realizada aps a primeira ter finalizado. O
tempo total de execuo ser no mnimo a soma da durao das duas requisies. Isso no um uso eficaz da
mquina, que vai estar inativa por boa parte do tempo enquanto os dados so transmitidos atravs da rede.

A soluo para esse problema, num sistema sncrono, iniciar threads de controle. (D uma olhada no Captulo
14 para uma discusso sobre threads.) Uma segunda thread poderia iniciar a segunda requisio, e ento
ambas as threads vo esperar os resultados voltarem, e aps a ressincronizao elas vo combinar seus
resultados.

No seguinte diagrama, as linhas grossa representam o tempo que o programa gastou em seu processo normal,
e as linhas finas representam o tempo gasto esperando pelo I/O. Em um modelo sncrono, o tempo gasto pelo I/O
faz parte da linha do tempo de uma determinada thread de controle. Em um modelo assncrono, iniciar uma ao
de I/O causa uma diviso na linha do tempo, conceitualmente falando. A thread que iniciou o I/O continua rodando,
e o I/O finalizado juntamente ela, chamando uma funo de callb ack quando finalizada.

Uma outra maneira de mostrar essa diferena que essa espera para que o I/O finalize implcita no modelo
sncrono, enquanto que explcita no assncrono. Mas assincronia uma faca de dois gumes. Ela faz com que
expressivos programas que seguem uma linha reta se tornem mais estranhos.

No captulo 17, eu j mencionei o fato de que todos esses callb acks adicionam um pouco de rudo e rodeios para
um programa. Se esse estilo de assincronia uma boa ideia ou no, em geral isso pode ser discutido. De
qualquer modo, levar algum tempo para se acostumar.

Mas para um sistema baseado em JavaScript, eu poderia afirmar que esse estilo de assincronia com callback
uma escolha sensata. Uma das foras do JavaScript sua simplicidade, e tentar adicionar mltiplas threads de
controle poderia causar uma grande complexidade. Embora os callb acks no tendem a ser cdigos simples,
como conceito, eles so agradavelmente simples e ainda assim poderosos o suficiente para escrever servidores
web de alta performance.

O Comando Node
Quando Node.js est instalado em um sistema, ele disponibiliza um programa chamado node , que usado para
executar arquivos JavaScript. Digamos que voc tenha um arquivo chamado ola.js , contendo o seguinte cdigo:

var mensagem = "Ol mundo";


console.log(mensagem);

Voc pode ento rodar node a partir da linha de comando para executar o programa:

$ node ola.js
Ol mundo

271
O mtodo console.log no Node tem um funcionamento bem parecido ao do navegador. Ele imprime um pedao
de texto. Mas no Node, o texto ser impresso pelo processo padro de sada, e no no console JavaScript do
navegador.

Se voc rodar node sem especificar nenhum arquivo, ele te fornecer um prompt no qual voc poder escrever
cdigos JavaScript e ver o resultado imediatamente.

$ node
> 1 + 1
2
> [-1, -2, -3].map(Math.abs)
[1, 2, 3]
> process.exit(0)
$

A varivel process , assim como a varivel console , est disponvel globalmente no Node. Ela fornece vrias
maneiras de inspecionar e manipular o programa atual. O mtodo exit finaliza o processo e pode receber um
cdigo de sada, que diz ao programa que iniciou node (nesse caso, a linha de comando) se o programa foi
completado com sucesso (cdigo zero) ou se encontrou algum erro (qualquer outro cdigo).

Para encontrar os argumentos de linha de comando recebidos pelo seu script, voc pode ler process.argv , que
um array de strings. Note que tambm estaro inclusos o nome dos comandos node e o nome do seu script,
fazendo com que os argumentos comecem na posio 2. Se showargv.js contm somente o statement
console.log(process.argv) , voc pode rod-lo dessa forma:

$ node showargv.js one --and two


["node", "/home/braziljs/showargv.js", "one", "--and", "two"]

Todas as variveis JavaScript globais, como Array , Math and JSON , esto presentes tambm no ambiente do
Node. Funcionalidades relacionadas ao navegador, como document e alert esto ausentes.

O objeto global do escopo, que chamado window no navegador, passa a ser global no Node, que faz muito
mais sentido.

Mdulos
Alm de algumas variveis que mencionei, como console e process , Node tambm colocou pequenas
funcionalidades no escopo global. Se voc quiser acessar outras funcionalidades embutidas, voc precisa pedir
esse mdulo ao sistema.

O sistema de mdulo CommonJS, baseado na funo require , esto descritos no Captulo 10. Esse sistema
construdo em Node e usado para carregar desde mdulos integrados at bibliotecas transferidas, ou at
mesmo, arquivos que fazem parte do seu prprio programa.

Quando require chamado, Node tem que transformar a string recebida em um arquivo real a ser carregado.
Nomes de caminhos que comeam com "/", "./", ou "../" so resolvidos relativamente ao atual caminho do mdulo,
aonde "./" significa o diretrio corrente, "../" para um diretrio acima, e "/" para a raiz do sistema de arquivos. Ento
se voc solicitar por "./world/world" do arquivo /home/braziljs/elife/run.js , Node vai tentar carregar o arquivo
/home/braziljs/elife/world/world.js . A extenso .js pode ser omitida.

Quando uma string recebida pelo require no parece ter um caminho relativo ou absoluto, fica implcito que ela
se refere a um mdulo integrado ou que est instalado no diretrio node_modules . Por exemplo, require(fs)

disponibilizar o mdulo de sistema de arquivos integrado ao Node, require("elife") vai tentar carregar a
biblioteca encontrada em node_modules/elife . A maneira mais comum de instalar bibliotecas como essas
usando NPM, que em breve ns vamos discutir.

272
Para ilustrar o uso do require , vamos configurar um projeto simples que consiste de dois arquivos. O primeiro
chamado main.js , que define um script que pode ser chamado da linha de comando para alterar uma string.

var garble = require("./garble");

// O ndice 2 possui o valor do primeiro parmetro da linha de comando


var parametro = process.argv[2];

console.log(garble(parametro));

O arquivo garble.js define uma biblioteca para alterar string, que pode ser usada tanto da linha de comando
quanto por outros scripts que precisam ter acesso direto a funo de alterar.

module.exports = function(string) {
return string.split("").map(function(ch) {
return String.fromCharCode(ch.charCodeAt(0) + 5);
}).join("");
}

Lembre-se que substituir module.exports , ao invs de adicionar propriedades ele, nos permite exportar um valor
especfico do mdulo. Nesse caso, ns fizemos com que o resultado ao requerer nosso arquivo garble seja a
prpria funo de alterar.

A funo separa a string recebida em dois caracteres nicos separando a string vazia e ento substituindo cada
caractere cujo cdigo cinco pontos maior. Finalmente, o resultado reagrupado novamente numa string.

Agora ns podemos chamar nossa ferramenta dessa forma:

$ node main.js JavaScript


Of{fXhwnuy

Instalando com NPM


NPM, que foi brevemente discutido no Captulo 10, um repositrio online de mdulos JavaScript, muitos deles
escritos para Node. Quando voc instala o Node no seu computador, voc tambm instala um programa chamado
npm , que fornece uma interface conveniente para esse repositrio.

Por exemplo, um mdulo que voc vai encontrar na NPM figlet , que pode converter texto em ASCII art
desenhos feitos de caracteres de texto. O trecho a seguir mostra como instalar e usar esse mdulo:

273
$ npm install figlet
npm GET https://registry.npmjs.org/figlet
npm 200 https://registry.npmjs.org/figlet
npm GET https://registry.npmjs.org/figlet/-/figlet-1.0.9.tgz
npm 200 https://registry.npmjs.org/figlet/-/figlet-1.0.9.tgz
figlet@1.0.9 node_modules/figlet
$ node
> var figlet = require("figlet");
> figlet.text("Hello world!", function(error, data) {
if (error)
console.error(error);
else
console.log(data);
});
_ _ _ _ _ _ _
| | | | ___| | | ___ __ _____ _ __| | __| | |
| |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | |
| _ | __/ | | (_) | \ V V / (_) | | | | (_| |_|
|_| |_|\___|_|_|\___/ \_/\_/ \___/|_| |_|\__,_(_)

Depois de rodar npm install , NPM j vai ter criado um diretrio chamado node_modules . Dentro desse diretrio
haver um outro diretrio chamado figlet , que vai conter a biblioteca. Quando rodamos node e chamamos
require("figlet") , essa biblioteca carregada, e ns podemos chamar seu mtodo text para desenhar
algumas letras grandes.

Talvez de forma inesperada, ao invs de retornar a string que faz crescer as letras, figlet.text tm uma funo de
callb ack que passa o resultado para ela. Ele tambm passa outro parmetro no callb ack, error , que vai possuir
um objeto de erro quando alguma coisa sair errada ou nulo se tudo ocorrer bem.

Isso um padro comum em Node. Renderizar alguma coisa com figlet requer a biblioteca para ler o arquivo
que contm as formas das letras. Lendo esse arquivo do disco uma operao assncrona no Node, ento
figlet.text no pode retornar o resultado imediatamente. Assincronia , de certa forma, infecciosaqualquer
funo que chamar uma funo assincronamente precisa se tornar assncrona tambm.

Existem muito mais coisas no NPM alm de npm install . Ele pode ler arquivos package,json , que contm
informaes codificadas em JSON sobre o programa ou biblioteca, como por exemplo outras bibliotecas que
depende. Rodar npm install em um diretrio que contm um arquivo como esse vai instalar automaticamente
todas as dependncias, assim como as dependncias das dependncias. A ferramenta npm tambm usada
para publicar bibliotecas para o repositrio NPM online de pacotes para que as pessoas possam encontrar,
transferir e us-los.

Esse livro no vai abordar detalhes da utilizao do NPM. D uma olhada em npmjs.org para uma documentao
mais detalhada e para uma maneira simples de procurar por bibliotecas.

O mdulo de arquivos de sistema


Um dos mdulos integrados mais comuns que vm com o Node o mdulo "fs" , que significa file system. Esse
mdulo fornece funes para o trabalho com arquivos de diretrios.

Por exemplo, existe uma funo chamada readFile , que l um arquivo e ento chama um callb ack com o
contedo desse arquivo.

var fs = require("fs");
fs.readFile("file.txt", "utf8", function(error, text) {
if (error)
throw error;
console.log("The file contained:", text);
});

274
O segundo argumento passado para readFile indica a codificao de caracteres usada para decodificar o arquivo
numa string. Existem muitas maneiras de codificar texto em informao binria, mas a maioria dos sistemas
modernos usam UTF-8 para codificar texto, ento a menos que voc tenha razes para acreditar que outra forma
de codificao deve ser usada, passar "utf8" ao ler um arquivo de texto uma aposta segura. Se voc no passar
uma codificao, o Node vai assumir que voc est interessado na informao binria e vai te dar um objeto
Buffer ao invs de uma string. O que por sua vez, um objeto array-like que contm nmeros representando os
b ytes nos arquivos.

var fs = require("fs");
fs.readFile("file.txt", function(error, buffer) {
if (error)
throw error;
console.log("The file contained", buffer.length, "bytes.",
"The first byte is:", buffer[0]);
});

Uma funo similar, writeFile , usada para escrever um arquivo no disco.

var fs = require("fs");
fs.writeFile("graffiti.txt", "Node was here", function(err) {
if (err)
console.log("Failed to write file:", err);
else
console.log("File written.");
});

Aqui, no foi necessrio especificar a codificao de caracteres, pois a funo writeFile assume que recebeu
uma string e no um objeto Buffer , e ento deve escrever essa string como texto usando a codificao de
caracteres padro, que UTF-8.

O mdulo "fs" contm muitas outras funes teis: readdir que vai retornar os arquivos em um diretrio como
um array de strings, stat vai buscar informao sobre um arquivo, rename vai renomear um arquivo, unlink vai
remover um arquivo, e assim por diante. Veja a documentao em nodejs.org para especificidades.

Muitas das funes em "fs" vm com variantes sncronas e assncronas. Por exemplo, existe uma verso
sncrona de readFile chamada readFileSync .

var fs = require("fs");
console.log(fs.readFileSync("file.txt", "utf8"));

Funes sncronas requerem menos formalismo na sua utilizao e podem ser teis em alguns scripts, onde a
extra velocidade oferecida pela assincronia I/O irrelevante. Mas note que enquanto tal operao sncrona
executada, seu programa fica totalmente parado. Se nesse perodo ele deveria responder ao usurio ou a outras
mquinas na rede, ficar preso com um I/O sncrono pode acabar produzindo atrasos inconvenientes.

O Mdulo HTTP
Outro principal o "http" . Ele fornece funcionalidade para rodar servidores HTTP e realizar requisies HTTP.

Isso tudo que voc precisa para rodar um simples servidor HTTP:

275
var http = require("http");
var server = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<h1>Hello!</h1><p>You asked for <code>" +
request.url + "</code></p>");
response.end();
});
server.listen(8000);

Se voc rodar esse script na sua mquina, voc pode apontar seu navegador para o endereo
http://localhost:8000/hello para fazer uma requisio no seu servidor. Ele ir responder com uma pequena pgina
HTML.

A funo passada como um argumento para createServer chamada toda vez que um cliente tenta se conectar ao
servidor. As variveis request e response so os objetos que representam a informao que chega e sai. A
primeira contm informaes sobre a requisio, como por exemplo a propriedade url , que nos diz em qual URL
essa requisio foi feita.

Para enviar alguma coisa de volta, voc chama mtodos do objeto response . O primeiro, writeHead , vai escrever
os cabealhos de resposta (veja o Captulo 17). Voc define o cdigo de status (200 para "OK" nesse caso) e um
objeto que contm valores de cabealho. Aqui ns dizemos ao cliente que estaremos enviando um documento
HTML de volta.

Em seguida, o corpo da resposta (o prprio documento) enviado com response.write . Voc pode chamar esse
mtodo quantas vezes voc quiser para enviar a resposta pea por pea, possibilitando que a informao seja
transmitida para o cliente assim que ela esteja disponvel. Finalmente, response,end assina o fim da resposta.

A chamada de server.listen faz com que o servidor comece a esperar por conexes na porta 8000. Por isso voc
precisa se conectar a localhost:8000, ao invs de somente localhost (que deveria usar a porta 80, por padro),
para se comunicar com o servidor.

Para parar de rodar um script Node como esse, que no finaliza automaticamente pois est aguardando por
eventos futuros (nesse caso, conexes de rede), aperte Ctrl+C.

Um servidor real normalmente faz mais do que o que ns vimos no exemplo anteriorele olha o mtodo da
requisio (a propriedade method ) para ver que ao o cliente est tentando realizar e olha tambm a URL da
requisio para descobrir que recurso essa ao est executando. Voc ver um servidor mais avanado daqui a
pouco neste captulo.

Para agir como um cliente HTTP, ns podemos usar a funo request no mdulo "http" .

var http = require("http");


var request = http.request({
hostname: "eloquentjavascript.net",
path: "/20_node.html",
method: "GET",
headers: {Accept: "text/html"}
}, function(response) {
console.log("Server responded with status code",
response.statusCode);
});
request.end();

O primeiro parmetro passado para request configura a requisio, dizendo pro Node qual o servidor que ele
deve se comunicar, que caminho solicitar daquele servidor, que mtodo usar, e assim por diante. O segundo
parmetro a funo que dever ser chamada quando uma resposta chegar. informado um objeto que nos
permite inspecionar a resposta, para descobrir o seu cdigo de status, por exemplo.

276
Assim como o objeto response que vimos no servidor, o objeto request nos permite transmitir informao na
requisio com o mtodo write e finalizar a requisio com o mtodo end . O exemplo no usa write porque
requisies GET no devem conter informao no corpo da requisio.

Para fazer requisies para URLs HTTP seguras (HTTPS), o Node fornece um pacote chamado https , que
contm sua prpria funo request , parecida a http.request .

Streams
Ns j vimos dois exemplos de streams em HTTPso, consecutivamente, o objeto de resposta no qual o
servidor pode escrever e o objeto de requisio que foi retornado do http.request .

Strams de gravao so um conceito amplamente usado nas interfaces Node. Todos os streams de gravao
possuem um mtodo write , que pode receber uma string ou um objeto Buffer . Seus mtodos end fecham a
transmisso e, se passado um parmetro, tambm vai escrever alguma informao antes de fechar. Ambos
mtodos podem receber um callb ack como um parmetro adicional, que eles vo chamar ao fim do escrever ou
fechar a transmisso.

possvel criar streams de gravao que apontam para um arquivo com a funo fs.createWritebleStram . Ento
voc pode usar o mtodo write no objeto resultante para escrever o arquivo pea por pea, ao invs de escrever
tudo de uma s vez com o fs.writeFile .

Streams de leitura so um pouco mais fechados. Em ambos a varivel request que foi passada para a funo de
callb ack do servidor HTTP e a varivel response para o cliente HTTP so streams de leitura. (Um servidor l os
pedidos e ento escreve as respostas, enquanto que um cliente primeiro escreve um pedido e ento l a
resposta.) Para ler de um stream usamos manipuladores de eventos, e no mtodos.

Objetos que emitem eventos no Node tm um mtodo chamado on que similar ao mtodo addEventListener no
navegador. Voc d um nome de evento e ento uma funo, e isso ir registrar uma funo para ser chamada
toda vez que um dado evento ocorrer.

Streams de leitura possuem os eventos "data" e "end" . O primeiro acionado sempre que existe alguma
informao chegando, e o segundo chamado sempre que a stream chega ao fim. Esse modelo mais
adequado para um streamming de dados, que pode ser imediatamente processado, mesmo quando todo
documento ainda no est disponvel. Um arquivo pode ser lido como uma stream de leitura usando a funo
fs.createReadStream .

O seguinte cdigo cria um servidor que l o corpo da requisio e o devolve em caixa alta para o cliente via stream:

var http = require("http");


http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
request.on("data", function(chunk) {
response.write(chunk.toString().toUpperCase());
});
request.on("end", function() {
response.end();
});
}).listen(8000);

A varivel chunk enviada para o manipulador de dados ser um Buffer binrio, que ns podemos converter para
uma string chamando toString nele, que vai decodific-lo usando a codificao padro (UTF-8).

O seguinte trecho de cdigo, se rodado enquanto o servidor que transforma letras em caixa alta estiver rodando,
vai enviar uma requisio para esse servidor e retornar a resposta que obtiver:

277
var http = require("http");
var request = http.request({
hostname: "localhost",
port: 8000,
method: "POST"
}, function(response) {
response.on("data", function(chunk) {
process.stdout.write(chunk.toString());
});
});
request.end("Hello server");

O exemplo escreve no process.stdout (a sada padro de processos, como uma stream de escrita) ao invs de
usar console.log . Ns no podemos usar console.log porque isso adicionaria uma linha extra depois de cada
pedao de texto escrito, o que adequado no nosso exemplo.

Um servidor de arquivos simples


Vamos combinar nossas novas descobertas sobre servidores HTTP e conversas sobre sistema de arquivos e
criar uma ponte entre eles: um servidor HTTP que permite acesso remoto ao sistema de arquivos. Um servidor
desse tipo possui diversos usurios. Ele permite que aplicaes web guardem e compartilhem dados ou d
direito para um determinado grupo de pessoas compartilhar muitos arquivos.

Quando lidamos com arquivos de recursos HTTP, os mtodos HTTP GET , PUT e DELETE podem ser usados,
respectivamente, para ler, escrever e apagar esses arquivos. Ns vamos interpretar o caminho na requisio
como o caminho do arquivo referido por aquela requisio.

Provavelmente ns no queremos compartilhar todo nosso sistema de arquivos, ento ns vamos interpretar
esses caminhos como se comeassem no diretrio de trabalho do servidor, que o diretrio no qual ele
comeou. Se eu rodar o servidor de /home/braziljs/public/ (ou C:\Users\braziljs\public\ no Windows), ento a
requisio por /file.txt deve ser referir a /home/braziljs/public/file.txt ( ou C:\Users\braziljs\public\file.txt ).

Ns vamos construir um programa pea por pea, usando um objeto chamado methods para guardar as funes
que tratam os vrios mtodos HTTP.

var http = require("http"), fs = require("fs");

var methods = Object.create(null);

http.createServer(function(request, response) {
function respond(code, body, type) {
if (!type) type = "text/plain";
response.writeHead(code, {"Content-Type": type});
if (body && body.pipe)
body.pipe(response);
else
response.end(body);
}
if (request.method in methods)
methods[request.method](urlToPath(request.url),
respond, request);
else
respond(405, "Method " + request.method +
" not allowed.");
}).listen(8000);

Isso vai comear um servidor que apenas retorna erro 405 nas respostas, que o cdigo usado para indicar que
dado mtodo no est sendo tratado pelo servidor.

278
A funo respond passada para as funes que tratam os vrios mtodos e agem como callb ack para finalizar a
requisio. Isso carrega um cdigo de status do HTTP, um corpo e opcionalmente um tipo contedo como
argumentos. Se o valor passado para o corpo um stream de leitura, ele ter um mtodo pipe , que ser usado
para encaminhar uma stream de leitura para uma stream de escrita. Caso contrrio, assumimos que o corpo ser
null (no h corpo) ou uma string passada diretamente para o mtodo end da resposta.

Para obter um caminho de uma URL em uma requisio, a funo urlToPath usa o mdulo " url " embutido no
Node para parsear a URL. Ela pega o nome do caminho, que ser algo parecido a /file.txt , o decodifica para
tirar os cdigos de escape (como %20 e etc), e coloca um nico ponto para produzir um caminho relativo ao
diretrio atual.

function urlToPath(url) {
var path = require("url").parse(url).pathname;
return "." + decodeURIComponent(path);
}

provvel que voc esteja preocupado com a segurana da funo urlToPath , e voc est certo, deve se
preocupar mesmo. Ns vamos retornar a ela nos exerccios.

Ns vamos fazer com que o mtodo GET retorne uma lista de arquivos quando lermos um diretrio e retornar o
contedo do arquivo quando lermos um arquivo regular.

Uma questo delicada que tipo de cabealho Content-Type ns devemos adicionar quando retornar um
contedo de um arquivo. Tendo em vista que esses arquivos podem ser qualquer coisa, nosso servidor no pode
simplesmente retornar o mesmo tipo para todos eles. Mas o NPM pode ajudar com isso. O pacote mime

(indicadores de tipo de contedo como text/plain tambm so chamados MIME types) sabe o tipo adequado de
um grande nmero de extenses de arquivos.

Se voc rodar o seguinte comando npm no diretrio aonde o script do servidor est, voc estar apto a usar
require("mime") para acessar essa biblioteca:

$ npm install mime


npm http GET https://registry.npmjs.org/mime
npm http 304 https://registry.npmjs.org/mime
mime@1.2.11 node_modules/mime

Quando um arquivo requisitado no existe, o cdigo de erro HTTP adequado a ser retornado 404. Ns vamos
usar fs.stat , que obtm informaes sobre um arquivo, para saber se o arquivo existe e/ou se um diretrio.

methods.GET = function(path, respond) {


fs.stat(path, function(error, stats) {
if (error && error.code == "ENOENT")
respond(404, "File not found");
else if (error)
respond(500, error.toString());
else if (stats.isDirectory())
fs.readdir(path, function(error, files) {
if (error)
respond(500, error.toString());
else
respond(200, files.join("\n"));
});
else
respond(200, fs.createReadStream(path),
require("mime").lookup(path));
});
};

279
Como ele pode levar um bom tempo para encontrar o arquivo no disco, fs.stat assncrono. Quando o arquivo
no existe, fs.stat vai passar um objeto de erro com "ENOENT" em uma propriedade chamada code para o seu
callb ack. Isso seria muito bom se o Node definisse diferentes subtipos de Error para diferentes tipos de erros,
mas ele no o faz. Ao invs disso, Node coloca um cdigo obscuro, inspirado no sistema Unix l.

Ns vamos reportar qualquer erro que no esperamos com o cdigo de status 500, que indica que o problema
est no servidor, ao contrrio dos cdigos que comeam com 4 (como o 404), que se referem a requisies ruins.
Existem algumas situaes nas quais isso no totalmente preciso, mas para um programa pequeno de exemplo
como esse, dever ser bom o suficiente.

O objeto status retornado pelo fs.stat nos diz uma poro de coisas sobre um arquivo, tais como tamanho
(propriedade size ) e sua data de modificao (propriedade mtime ). Nosso interesse aqui saber se isso um
diretrio ou um arquivo regular, e quem nos diz isso o mtodo isDirectory .

Ns usamos fs.readdir para ler a lista de arquivos em um diretrio e, ainda em outro callback, retornar o
resultado para o usurio. Para arquivos comuns, ns criamos uma stream de leitura com o fs.createReadStream e
passamos ela ao respond , junto com o tipo de contedo que o mdulo "mime" nos deu para esse nome de
arquivo.

O cdigo que trata as requisies de DELETE um pouco mais simples.

methods.DELETE = function(path, respond) {


fs.stat(path, function(error, stats) {
if (error && error.code == "ENOENT")
respond(204);
else if (error)
respond(500, error.toString());
else if (stats.isDirectory())
fs.rmdir(path, respondErrorOrNothing(respond));
else
fs.unlink(path, respondErrorOrNothing(respond));
});
};

Voc deve estar se perguntando porque tentar deletar um arquivo inexistente retornar um status 204, e no um
erro. Quando o arquivo que ser deletado no existe, voc pode dizer que o objetivo da requisio j foi cumprido.
O padro HTTP recomenda que as pessoas faam requisies idempotentes, o que significa que independente
da quantidade de requisies, elas no devem produzir um resultado diferente.

function respondErrorOrNothing(respond) {
return function(error) {
if (error)
respond(500, error.toString());
else
respond(204);
};
}

Quando uma resposta HTTP no contm nenhum dado, o status 204 ("no content") pode ser usado para indicar
isso. Tendo em vista que a gente precisa construir callb acks que reportam um erro ou retornam uma resposta 204
em diferentes situaes, eu escrevi uma funo chamada respondErrorOrNothing que cria esse callb ack.

Aqui est a funo que trata as requisies PUT :

280
methods.PUT = function(path, respond, request) {
var outStream = fs.createWriteStream(path);
outStream.on("error", function(error) {
respond(500, error.toString());
});
outStream.on("finish", function() {
respond(204);
});
request.pipe(outStream);
};

Aqui, ns no precisamos checar se o arquivo existe - se ele existe, ns simplesmente sobrescrevemos ele.
Novamente ns usamos pipe para mover a informao de um stream de leitura para um de escrita, nesse caso
de uma requisio para um arquivo. Se a criao do stream falhar, um evento "error" disparado e reportado na
nossa resposta. Quando a informao for transferida com sucesso, pipe vai fechar ambos streams, o que vai
disparar o evento "finish" no stream de escrita. Quando isso acontecer, ns podemos reportar sucesso na
nossa resposta para o cliente com um status 204.

O script completo para o servidor est disponvel em eloquentjavascript.net/code/file_server.js. Voc pode fazer o
download e rod-lo com Node pra comear seu prprio servidor de arquivos. E claro, voc pode modific-lo e
extend-lo para resolver os exerccios desse captulo ou para experimentar.

A ferramente de linha de comando curl , amplamente disponvel em sistemas Unix, pode ser usada para fazer
requisies HTTP. A sesso a seguir um rpido teste do nosso servidor. Note que -X usado para para
escolher o mtodo da requisio e -d usado para incluir o corpo da requisio.

$ curl http://localhost:8000/file.txt
File not found
$ curl -X PUT -d hello http://localhost:8000/file.txt
$ curl http://localhost:8000/file.txt
hello
$ curl -X DELETE http://localhost:8000/file.txt
$ curl http://localhost:8000/file.txt
File not found

A primeira requisio feita para o arquivo file.txt falha pois o arquivo ainda no existe. A requisio PUT cria o
arquivo, para que ento a prxima requisio consiga encontr-lo com sucesso. Depois de deletar o arquivo com
uma requisio DELETE , o arquivo passa a no ser encontrado novamente.

Tratamento de erros
No cdigo para o servidor de arquivos, existem seis lugares aonde ns estamos explicitando excees de rota
que ns no sabemos como trat-los como respostas de erro. Como excees so passadas como argumentos
e, portanto, no so automaticamente propagadas para os callb acks, elas precisam ser tratadas a todo momento
de forma explcita. Isso acaba completamente com a vantagem de tratamento de excees, isto , a habilidade de
centralizar o tratamento das condies de falha.

O que acontece quando alguma coisa joga uma exceo em seu sistema? Como no estamos usando nenhum
bloco try , a exceo vai propagar para o topo da pilha de chamada. No Node, isso aborta o programa e escreve
informaes sobre a exceo (incluindo um rastro da pilha) no programa padro de stream de erros.

Isso significa que nosso servidor vai colidir sempre que um problema for encontrado no cdigo do prprio
servidor, ao contrrio dos problemas assncronos, que so passados como argumentos para os callb acks. Se
ns quisermos tratar todas as excees levantadas durante o tratamento de uma requisio, para ter certeza que
enviamos uma resposta, precisamos adicionar blocos de try/catch para todos os callb acks.

281
Isso impraticvel. Muitos programas em Node so escritos para fazer o menor uso possvel de excees,
assumindo que se uma exceo for levantada, aconteceu algo que o programa no conseguiu resolver, e colidir
a resposta certa.

Outra abordagem usar promessas, que foram introduzidas no Captulo 17. Promessas capturam as excees
levantadas por funes de callb ack e propagam elas como falhas. possvel carregar uma biblioteca de
promessa no Node e us-la para administrar seu controle assncrono. Algumas bibliotecas Node fazem
integrao com as promessas, mas as vezes trivial envolv-las. O excelente mdulo "promise" do NPM contm
uma funo chamada denodeify , que converte uma funo assncrona como a fs.readFile para uma funo de
retorno de promessa.

var Promise = require("promise");


var fs = require("fs");

var readFile = Promise.denodeify(fs.readFile);


readFile("file.txt", "utf8").then(function(content) {
console.log("The file contained: " + content);
}, function(error) {
console.log("Failed to read file: " + error);
});

A ttulo de comparao, eu escrevi uma outra verso do servidor de arquivos baseado em promessas, que voc
pode encontrar em eloquentjavascript.net/code/file_server_promises.js. Essa verso um pouco mais clara pois
as funes podem retornar seus resultados, ao invs de ter que chamar callb acks, e a rota de excees est
implcito, ao invs de explcito.

Eu vou mostrar algumas linhas do servidor de arquivos baseado em promessas para ilustrar a diferena no estilo
de programao.

O objeto fsp que usado por esse cdigo contm estilos de promessas variveis para determinado nmero de
funes fs , envolvidas por Promise.denodeify . O objeto retornado, com propriedades code e body , vai se tornar o
resultado final de uma cadeia de promessas, e vai ser usado para determinar que tipo de resposta vamos
mandar pro cliente.

methods.GET = function(path) {
return inspectPath(path).then(function(stats) {
if (!stats) // Does not exist
return {code: 404, body: "File not found"};
else if (stats.isDirectory())
return fsp.readdir(path).then(function(files) {
return {code: 200, body: files.join("\n")};
});
else
return {code: 200,
type: require("mime").lookup(path),
body: fs.createReadStream(path)};
});
};

function inspectPath(path) {
return fsp.stat(path).then(null, function(error) {
if (error.code == "ENOENT") return null;
else throw error;
});
}

A funo inspectPath simplesmente envolve o fs.stat , que trata o caso de arquivo no encontrado. Nesse caso,
ns vamos substituir a falha por um sucesso que representa null . Todos os outros erros so permitidos a
propagar. Quando a promessa retornada desses manipuladores falha, o servidor HTTP responde com um status
500.

282
Resumo
Node um sistema bem ntegro e legal que permite rodar JavaScript em um contexto fora do navegador. Ele foi
originalmente concebido para tarefas de rede para desempenhar o papel de um n na rede. Mas ele se permite a
realizar todas as tarefas de script, e se escrever JavaScript algo que voc gosta, automatizar tarefas de rede com
Node funciona de forma maravilhosa.

O NPM disponibiliza bibliotecas para tudo que voc possa imaginar (e algumas outras coisas que voc
provavelmente nunca pensou), e permite que voc atualize e instale essas bibliotecas rodando um simples
comando. Node tambm vm com um bom nmero de mdulos embutidos, incluindo o mdulo "fs" , para
trabalhar com sistema de arquivos e o "http" , para rodar servidores HTTP e fazer requisies HTTP.

Toda entrada e sada no Node feita de forma assncrona, a menos que voc explicitamente use uma variante
sncrona da funo, como a fs.readFileSync . Voc fornece as funes de callb ack e o Node vai cham-las no
tempo certo, quando o I/O que voc solicitou tenha terminado.

Exerccios
Negociao de Contedo, novamente
No Captulo 17, o primeiro exerccio era fazer vrias requisies para eloquentjavascript.net/author, pedindo por
tipos diferentes de contedo passando cabealhos Accept diferentes.

Faa isso novamente usando a funo http.request do Node. Solicite pelo menos os tipos de mdia text/plain ,
text/html e application/json . Lembre-se que os cabealhos para uma requisio podem ser passados como
objetos, na propriedade headers do primeiro argumento da http.request .

Escreva o contedo das respostas para cada requisio.

Dica: No se esquea de chamar o mtodo end no objeto retornado pela http.request para de fato disparar a
requisio.

O objeto de resposta passado ao callb ack da http.request um stream de leitura. Isso significa que ele no
muito trivial pegar todo o corpo da resposta dele. A funo a seguir l todo o stream e chama uma funo de
callb ack com o resultado, usando o padro comum de passar qualquer erro encontrado como o primeiro
argumento do callb ack:

function readStreamAsString(stream, callback) {


var data = "";
stream.on("data", function(chunk) {
data += chunk.toString();
});
stream.on("end", function() {
callback(null, data);
});
stream.on("error", function(error) {
callback(error);
});
}

Corrigindo uma falha


Para um fcil acesso remoto aos arquivos, eu poderia adquirir o hbito de ter o servidor de arquivos definido
nesse captulo na minha mquina, no diretrio /home/braziljs/public/ . E ento, um dia, eu encontro algum que
tenha conseguido acesso a todos as senhas que eu gravei no navegador.

283
O que aconteceu?

Se ainda no est claro para voc, pense novamente na funo urlToPath definida dessa forma:

function urlToPath(url) {
var path = require("url").parse(url).pathname;
return "." + decodeURIComponent(path);
}

Agora considere o fato de que os caminhos para as funes "fs" podem ser relativos-eles podem conter "../"
para voltar a um diretrio acima. O que acontece quando um cliente envia uma requisio para uma dessas URLs
abaixo?

http://myhostname:8000/../.config/config/google-chrome/Default/Web%20Data
http://myhostname:8000/../.ssh/id_dsa
http://myhostname:8000/../../../etc/passwd

Mudar o urlToPath corrige esse problema. Levando em conta o fato de que o Node no Windows permite tanto
barras quanto contrabarras para separar diretrios.

Alm disso, pense no fato de que assim que voc expor algum sistema meia b oca na internet, os b ugs nesse
sistema podem ser usado para fazer coisas ruins para sua mquina.

Dicas Basta remover todas as recorrncias de dois pontos que tenham uma barra, uma contrabarra ou as
extremidades da string. Usando o mtodo replace com uma expresso regular a maneira mais fcil de fazer
isso. No se esquea da flag g na expresso, ou o replace vai substituir somente uma nica instncia e as
pessoas ainda poderiam incluir pontos duplos no caminho da URL a partir dessa medida de segurana! Tambm
tenha certeza de substituir depois de decodificar a string, ou seria possvel despistar o seu controle que codifica
pontos e barras.

Outro caso de preocupao potencial quando os caminhos comeam com barra, que so interpretados como
caminhos absolutos. Mas por conta do urlToPath colocar um ponto na frente do caminho, impossvel criar
requisies que resultam em tal caminho. Mltiplas barras numa linha, dentro do caminho, so estranhas mas
sero tratadas como uma nica barra pelo sistema de arquivos.

Criando diretrios
Embora o mtodo DELETE esteja envolvido em apagar diretrios (usando fs.rmdir ), o servidor de arquivos no
disponibiliza atualmente nenhuma maneira de criar diretrios.

Adicione suporte para o mtodo MKCOL , que deve criar um diretrio chamando fs.mkdir . MKCOL no um mtodo
bsico do HTTP, mas ele existe nas normas da Web DAV, que especifica um conjunto de extenses para o HTTP,
tornando-o adequado para escrever recursos, alm de os ler.

Dicas Voc pode usar a funo que implementa o mtodo DELETE como uma planta baixa para o mtodo MKCOL .
Quando nenhum arquivo encontrado, tente criar um diretrio com fs.mkdir . Quando um diretrio existe naquele
caminho, voc pode retornar uma resposta 204, ento as requisies de criao de diretrio sero idempotentes.
Se nenhum diretrio de arquivo existe, retorne um cdigo de erro. O cdigo 400 ("b ad request") seria o mais
adequado nessa situao.

Um espao pblico na rede

284
Uma vez que o servidor de arquivos serve qualquer tipo de arquivo e ainda inclui o cabealho Content-Type , voc
pode us-lo para servir um website. Mas uma vez que seu servidor de arquivos permita que qualquer um delete e
sobrescreva arquivos, seria um tipo interessante de website: que pode ser modificado, vandalizado e destrudo
por qualquer um que gaste um tempo para criar a requisio HTTP correta. Mas ainda assim, seria um website.

Escreva uma pgina HTML bsica que inclui um simples arquivo JavaScript. Coloque os arquivos num diretrio
servido pelo servidor de arquivos e abra isso no seu navegador.

Em seguida, como um exerccio avanado ou como um projeto de fim de semana, combine todo o conhecimento
que voc adquiriu desse livro para construir uma interface mais amigvel pra modificar o website de dentro do
website.

Use um formulrio HTML (Captulo 18) para editar os contedos dos arquivos que fazem parte do website,
permitindo que o usurio atualize eles no servidor fazendo requisies HTTP como vimos no Captulo 17.

Comece fazendo somente um nico arquivo editvel. Ento faa de uma maneira que o usurio escolha o arquivo
que quer editar. Use o fato de que nosso servidor de arquivos retorna uma lista de arquivos durante a leitura de
um diretrio.

No trabalhe diretamente no cdigo do servidor de arquivos, tendo em vista que se voc cometer um engano voc
vai afetar diretamente os arquivos que esto l. Ao invs disso, mantenha seu trabalho em um diretrio sem
acessibilidade pblica e copie ele pra l enquanto testa.

Se seu computador est diretamente ligado a internet, sem um firewall, roteador, ou outro dispositivo interferindo,
voc pode ser capaz de convidar um amigo para usar seu website. Para checar, v at whatismyip.com, copie e
cole o endereo de IP que ele te deu na barra de endereo do seu navegador, e adicione :8000 depois dele para
selecionar a porta correta. Se isso te levar ao seu website, est online para qualquer um que quiser ver.

Dicas Voc pode criar um elemento <textarea> para conter o contedo do arquivo que est sendo editado. Uma
requisio GET , usando XMLHttpRequest , pode ser usada para pegar o atual contedo do arquivo. Voc pode usar
URLs relativas como index.html, ao invs de http://localhost:8000/index.html, para referir-se aos arquivos do
mesmo servidor que est rodando o script.

Ento, quando o usurio clicar num boto (voc pode usar um elemento <form> e um evento "submit" ou um
simples manipulador "click" ), faa uma requisio PUT para a mesma URL, com o contedo do <textarea> no
corpo da requisio para salvar o arquivo.

Voc pode ento adicionar um elemento <select> que contenha todos os arquivos na raiz do servidor adicionando
elementos <option> contendo as linhas retornadas pela requisio GET para a URL /. Quando um usurio
seleciona outro arquivo (um evento "change" nesse campo), o script deve buscar e mostrar o arquivo. Tambm
tenha certeza que quando salvar um arquivo, voc esteja usando o nome do arquivo selecionado.

Infelizmente, o servidor muito simplista para ser capaz de ler arquivos de subdiretrios de forma confivel, uma
vez que ele no nos diz se a coisa que est sendo buscado com uma requisio GET um arquivo ou um
diretrio. Voc consegue pensar em uma maneira de extender o servidor para solucionar isso?

285
Projeto - Website de compartilhamento de habilidades
Uma reunio de compartilhamento de habilidades um evento onde as pessoas com um interesse em comum
se juntam e do pequenas apresentaes informais sobre coisas que eles sabem. Em uma reunio de
compartilhamento de habilidade de jardinagem algum pode explicar como cultivar um Aipo. Ou em um grupo de
compartilhamento de habilidades orientadas para a programao voc poderia aparecer e dizer a todos sobre
Node.js.

Tais reunies muitas vezes tambm so chamados de grupos de usurios quando eles esto falando sobre
computadores. Isso uma tima maneira de aprofundar o seu conhecimento e aprender sobre novos
desenvolvimentos ou simplesmente reunir pessoas com interesses semelhantes. Muitas cidades tm grandes
grupos de JavaScript. Eles so tipicamente livre para assistir ou visitar.

Neste ltimo captulo do projeto o nosso objetivo a criao de um site para gerenciar estas palestras dadas em
um encontro de compartilhamento de habilidade. Imagine um pequeno grupo de pessoas que se encontra
regularmente no escritrio de um dos membros para falar sobre Monociclo. O problema que quando um
organizador de alguma reunio anterior muda de cidade ningum se apresentar para assumir esta tarefa.
Queremos um sistema que permite que os participantes proponha e discuta as palestras entre si sem um
organizador central.

Assim como no captulo anterior, o cdigo neste captulo escrito em Node.js, e execut-lo diretamente em uma
pgina HTML improvvel que funcione. O cdigo completo para o projeto pode ser baixado aqui.

Projeto
H uma parte do servidor para este projeto escrito em Node.js e a outra parte do cliente escrito para o browser. O
servidor armazena os dados do sistema e fornece para o cliente. Ela tambm serve os arquivos HTML e
JavaScript que implementam o sistema do lado do cliente.

O servidor mantm uma lista de palestras propostas para a prxima reunio e o cliente mostra esta lista. Cada
palestra tem um nome do apresentador, um ttulo, um resumo e uma lista de comentrios dos participantes. O
cliente permite que os usurios proponha novas palestras (adicionando a lista), exclua as palestras e comente
sobre as palestras existentes. Sempre que o usurio faz tal mudana o cliente faz uma solicitao HTTP para
informar para o servidor o que fazer.

286
O aplicativo ser configurada para mostrar uma exibio em tempo real das atuais palestras propostas e seus
comentrios. Sempre que algum apresentar uma nova palestra ou adicionar um comentrio, todas as pessoas
que tm a pgina aberta no navegador devem vizualizarem a mudana imediatamente. Isto coloca um pouco de
um desafio, pois no h path para um servidor web abrir uma conexo com um cliente nem h uma boa maneira
de saber o que os clientes est olhando atualmente no site.

Uma soluo comum para este problema chamado de long polling que passa a ser uma das motivaes para
o projeto ser em Node.

Long polling
Para ser capaz de se comunicar imediatamente com um cliente que algo mudou precisamos de uma conexo
com o cliente. Os navegadores no tradicionais, bloqueiam de qualquer maneira tais conexes que deveriam ser
aceitas pelo cliente; toda essa ligao deve ser feita via servidor o que no muito prtico.

Ns podemos mandar o cliente abrir a conexo e mant-la de modo que o servidor possa us-la para enviar
informaes quando for preciso.

Uma solicitao HTTP permite apenas um fluxo simples de informaes, onde o cliente envia a solicitao e o
servidor devolve uma nica resposta. H uma tecnologia que chamamos de soquetes web, suportado pelos
navegadores modernos, isso torna possvel abrir as ligaes para a troca de dados arbitrria. um pouco difcil
us-las corretamente.

Neste captulo vamos utilizar uma tcnica relativamente simples, long polling , onde os clientes continuamente
pedem ao servidor para obter novas informaes usando solicitaes HTTP e o servidor simplesmente barrara
sua resposta quando ele no houver nada de novo para relatar.

Enquanto o cliente torna-se constantemente um long polling aberto, ele ira receber informaes do servidor
imediatamente. Por exemplo, se Alice tem o nosso aplicativo de compartilhamento de habilidade aberto em seu
navegador, ele ter feito um pedido de atualizaes e estara a espera de uma resposta a esse pedido. Quando
Bob submeter uma palestra sobre a extrema Downhill Monociclo o servidor vai notificar que Alice est esperando por
atualizaes e enviar essas informaes sobre a nova palestra como uma resposta ao seu pedido pendente. O
navegador de Alice receber os dados e atualizara a tela para mostrar a nova palestra.

Para evitar que as conexes excedam o tempo limite (sendo anulado por causa de uma falta de atividade)
podemos definir uma tcnica que define um tempo mximo para cada pedido do long polling ; aps esse tempo
o servidor ir responder de qualquer maneira mesmo que ele no tenha nada a relatar, dai ento o cliente inicia

287
um novo pedido. Reiniciar o pedido periodicamente torna a tcnica mais robusta a qual permite aos clientes se
recuperarem de falhas de conexo temporrias ou de problemas no servidor.

Um servidor que esta ocupado usando long polling pode ter milhares de pedidos em espera com conexes TCP

em aberto. Node torna fcil de gerenciar muitas conexes sem criar uma thread separada com controle para cada
uma, sendo assim, Node uma boa opo para esse sistema.

Interface HTTP
Antes de comearmos a comunicar servidor e cliente vamos pensar sobre o ponto em que feita a comunicao:
a interface HTTP .

Vamos basear nossa interface em JSON e como vimos no servidor de arquivos a partir do captulo 20 vamos tentar
fazer um bom uso dos mtodos HTTP . A interface centrado em torno de um path /talks . Paths que no
comeam com /talks sero usado para servir arquivos estticos como: cdigo HTML e JavaScript que sero
implementados no sistema do lado do cliente.

A solicitao do tipo GET para /talks devolve um documento JSON como este:

{"serverTime": 1405438911833,
"talks": [{"title": "Unituning",
"presenter": "Carlos",
"summary": "Modifying your cycle for extra style",
"comment": []}]}

O campo serverTime vai ser usado para fazer a sondagem de long polling . Voltarei a explicar isso mais adiante.

Para criar um novo talk preciso uma solicitao do tipo PUT para a URL /talks/unituning/ , onde aps a segunda
barra o ttulo da palestra. O corpo da solicitao PUT deve conter um objeto JSON que tem o apresentador e o
sumrio como propriedade do corpo da solicitao.

O ttulos da palestra pode conter espaos e outros caracteres que podem no aparecerem normalmente em um
URL, a string do ttulo deve ser codificado com a funo encodeURIComponent ao construir a URL.

console.log("/talks/" + encodeURIComponent("How to Idle"));


// /talks/How%20to%20Idle

O pedido para criao de uma palestra parecido com isto:

PUT /talks/How%20to%20Idle HTTP/1.1


Content-Type: application/json
Content-Length: 92

{"presenter": "Dana",
"summary": "Standing still on a unicycle"}

Estas URLs tambm suportam requisies GET para recuperar a representao do JSON de uma palestra ou
DELETE para excluso de uma palestra.

Para adicionar um comentrio a uma palestra necessrio uma solicitao POST para uma URL

/talks/Unituning/comments com um objeto JSON contendo o autor e a mensagem como propriedades do corpo da
solicitao.

288
POST /talks/Unituning/comments HTTP/1.1
Content-Type: application/json
Content-Length: 72

{"author": "Alice",
"message": "Will you talk about raising a cycle?"}

Para termos apoio do long polling precisamos de pedidos GET para /talks . Podemos incluir um parmetro de
consulta chamado changesSince ele ser usado para indicar que o cliente est interessado em atualizaes que
aconteceram desde de um determinado tempo. Quando existem tais mudanas eles so imediatamente
devolvidos. Quando no h a resposta adiada at que algo acontea em um determinado perodo de tempo
(vamos determinar 90 segundos).

O tempo deve ser indicado em nmeros por milissegundos decorridos desde do incio de 1970, o mesmo tipo de
nmero que retornado por Date.now() . Para garantir que ele recebeu todas as atualizaes sem receber a
mesma atualizao repetida; o cliente deve passar o tempo da ltima informao recebida ao servidor. O relgio
do servidor pode no ser exatamente sincronizado com o relgio do cliente e mesmo se fosse seria impossvel
para o cliente saber a hora exata em que o servidor enviou uma resposta porque a transferncia de dados atravs
de rede pode ter um pouco de atraso.

Esta a razo da existncia da propriedade serverTime em respostas enviadas a pedidos GET para /talks . Essa
propriedade diz ao cliente o tempo preciso do servidor em que os dados foram recebidos ou criados. O cliente
pode ento simplesmente armazenar esse tempo e pass-los no seu prximo pedido de polling para certificar
de que ele vai receber exatamente as atualizaes que no tenha visto antes.

GET /talks?changesSince=1405438911833 HTTP/1.1

(time passes)

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 95

{"serverTime": 1405438913401,
"talks": [{"title": "Unituning",
"deleted": true}]}

Quando a palestra alterada, criada ou tem um comentrio adicionado; a representao completa da palestra
estar includa na prxima resposta de solicitao que o cliente busca. Quando a palestra excluda, somente o
seu ttulo e a propriedade que ser excluida da onde ela estar includa. O cliente pode ento adicionar os ttulos
das palestras que no esto sendo exibidas na pgina, atualizar as que ja esto sendo exibidas ou remover
aquelas que foram excludas.

O protocolo descrito neste captulo no ir fazer qualquer controle de acesso. Todos podem comentar, modificar a
palestras e at mesmo excluir. Uma vez que a Internet est cheia de arruaceiros ao colocarmos um sistema on-
line sem proteo adicional provvel que acabe em um desastre.

Uma soluo simples seria colocar um sistema de proxy reverso por trs, sendo ele um servidor HTTP que
aceita conexes de fora do sistema e os encaminha para servidores HTTP que esto sendo executados
localmente. O proxy pode ser configurado para exigir um nome de usurio e senha, voc pode ter certeza de que
somente os participantes do grupo de compartilhamento de habilidade tenham essa senha.

O servio
Vamos comear a escrever cdigo do lado do servidor. O cdigo desta seo executado em Node.js .

289
Roteamento
O nosso servidor ir utilizar http.createServer para iniciar um servidor HTTP . Na funo que lida com um novo
pedido, iremos distinguir entre os vrios tipos de solicitaes (conforme determinado pelo mtodo e o path ) que
suportamos. Isso pode ser feito com uma longa cadeia de if mas h uma maneira mais agradvel.

As rotas um componente que ajuda a enviar uma solicitao atravs de uma funo. Voc pode dizer para as
rotas que os pedidos combine com um path usando expresso regular /^\/talks\/([^\/]+)$/ (que corresponde a
/talks/ seguido pelo ttulo) para tratar por uma determinada funo. Isso pode ajudar a extrair as partes
significativas de um path , neste caso o ttulo da palestra, que estar envolto entre os parnteses na expresso
regular, aps disto passado para o manipulador de funo.

H uma srie de bons pacotes de roteamento na NPM mas vamos escrever um ns mesmos para ilustrar o
princpio.

Este router.js que exigir mais tarde do nosso mdulo de servidor:

var Router = module.exports = function() {


this.routes = [];
};

Router.prototype.add = function(method, url, handler) {


this.routes.push({method: method,
url: url,
handler: handler});
};

Router.prototype.resolve = function(request, response) {


var path = require("url").parse(request.url).pathname;

return this.routes.some(function(route) {
var match = route.url.exec(path);
if (!match || route.method != request.method)
return false;

var urlParts = match.slice(1).map(decodeURIComponent);


route.handler.apply(null, [request, response]
.concat(urlParts));
return true;
});
};

O mdulo exporta o construtor de Router . Um objeto de Router permite que novos manipuladores sejam
registados com o mtodo add e resolver os pedidos com o mtodo resolve .

Este ltimo ir retornar um booleano que indica se um manipulador foi encontrado. H um mtodo no conjunto de
rotas que tenta uma rota de cada vez (na ordem em que elas foram definidos) e retorna a verdadeira quando
alguma for correspondida.

As funes de manipulao so chamadas com os objetos de solicitao e resposta. Quando algum grupo da
expresso regular corresponder a uma URL, as string que correspondem so passadas para o manipulador
como argumentos extras. Essas sequncias tem que ser uma URL decodificada tendo a URL codificada assim
%20-style code .

Servindo arquivos
Quando um pedido no corresponde a nenhum dos tipos de solicitao que esta sendo definidos em nosso
router o servidor deve interpretar como sendo um pedido de um arquivo que esta no diretrio pblico. Seria
possvel usar o servidor de arquivos feito no Captulo 20 para servir esses arquivos; nenhuma destas solicitaes

290
sera do tipo PUT e DELETE , ns gostaramos de ter recursos avanados como suporte para armazenamento em
cache. Ento vamos usar um servidor de arquivo esttico a partir de uma NPM.

Optei por ecstatic . Este no o nico tipo de servidor NPM, mas funciona bem e se encaixa para nossos
propsitos. O mdulo de ecstatic exporta uma funo que pode ser chamada com um objeto de configurao
para produzir uma funo de manipulao de solicitao. Ns usamos a opo root para informar ao servidor
onde ele devera procurar pelos arquivos. A funo do manipulador aceita solicitao e resposta atravs de
parmetros que pode ser passado diretamente para createServer onde criado um servidor que serve apenas
arquivos. Primeiro verificamos se na solicitaes no ha nada de especial, por isso envolvemos em uma outra
funo.

var http = require("http");


var Router = require("./router");
var ecstatic = require("ecstatic");

var fileServer = ecstatic({root: "./public"});


var router = new Router();

http.createServer(function(request, response) {
if (!router.resolve(request, response))
fileServer(request, response);
}).listen(8000);

response e respondJSON sero funes auxiliares utilizadas em todo o cdigo do servidor para ser capaz de enviar
as respostas com uma nica chamada de funo.

function respond(response, status, data, type) {


response.writeHead(status, {
"Content-Type": type || "text/plain"
});
response.end(data);
}

function respondJSON(response, status, data) {


respond(response, status, JSON.stringify(data),
"application/json");
}

Recursos das palestras


O servidor mantm as palestras que tm sido propostas em um objeto chamado talks , cujos os ttulos so
propriedades de nomes de uma palestra. Estes sero expostos como recursos HTTP sob /talks/[title] e por
isso precisamos adicionar manipuladores ao nosso roteador que implementaram vrios mtodos que podem
serem utilizados pelo o cliente.

O manipulador de solicitaes serve uma nica resposta, quer seja alguns dados do tipo JSON da palestra, uma
resposta de 404 ou um erro.

var talks = Object.create(null);

router.add("GET", /^\/talks\/([^\/]+)$/,
function(request, response, title) {
if (title in talks)
respondJSON(response, 200, talks[title]);
else
respond(response, 404, "No talk '" + title + "' found");
});

A excluso de um talk feito para remove-la do objeto de talks .

291
router.add("DELETE", /^\/talks\/([^\/]+)$/,
function(request, response, title) {
if (title in talks) {
delete talks[title];
registerChange(title);
}
respond(response, 204, null);
});

A funo registerChange que iremos definir; notifica alteraes enviando uma solicitao de long polling ou
simplemente espera.

Para ser capaz de obter facilmente o contedo do body de uma solicitao de JSON , teremos que definir uma
funo chamada readStreamAsJSON que l todo o contedo de um stream , analisa o JSON e em seguida chama
uma funo de retorno.

function readStreamAsJSON(stream, callback) {


var data = "";
stream.on("data", function(chunk) {
data += chunk;
});
stream.on("end", function() {
var result, error;
try { result = JSON.parse(data); }
catch (e) { error = e; }
callback(error, result);
});
stream.on("error", function(error) {
callback(error);
});
}

Um manipulador que precisa ler respostas JSON o manipulador PUT que usado para criar novas palestras.
Nesta request devemos verificar se os dados enviados tem um apresentador e o ttulo como propriedades
ambos do tipo String . Quaisquer dados que vm de fora do sistema pode conter erros e ns no queremos
corromper o nosso modelo de dados interno ou mesmo travar quando os pedidos ruins entrarem.

Se os dados se parecem vlidos o manipulador armazena-os em um novo objeto que representa uma nova
palestra no objeto talks , possivelmente substituindo uma palestra que j exista com este ttulo e mais uma vez
chama registerChange .

router.add("PUT", /^\/talks\/([^\/]+)$/,
function(request, response, title) {
readStreamAsJSON(request, function(error, talk) {
if (error) {
respond(response, 400, error.toString());
} else if (!talk ||
typeof talk.presenter != "string" ||
typeof talk.summary != "string") {
respond(response, 400, "Bad talk data");
} else {
talks[title] = {title: title,
presenter: talk.presenter,
summary: talk.summary,
comments: []};
registerChange(title);
respond(response, 204, null);
}
});
});

292
Para adicionar um comentrio a uma palestra, funciona de forma semelhante. Usamos readStreamAsJSON para
obter o contedo do pedido, validar os dados resultantes e armazen-los como um comentrio quando for vlido.

router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
function(request, response, title) {
readStreamAsJSON(request, function(error, comment) {
if (error) {
respond(response, 400, error.toString());
} else if (!comment ||
typeof comment.author != "string" ||
typeof comment.message != "string") {
respond(response, 400, "Bad comment data");
} else if (title in talks) {
talks[title].comments.push(comment);
registerChange(title);
respond(response, 204, null);
} else {
respond(response, 404, "No talk '" + title + "' found");
}
});
});

Ao tentar adicionar um comentrio a uma palestra inexistente claro que devemos retornar um erro 404.

Apoio a long polling


O aspecto mais interessante do servidor a parte que trata de long polling . Quando uma requisio GET chega
para /talks pode ser um simples pedido de todas as palestras ou um pedido de atualizao com um parmetro
changesSince .

Haver vrias situaes em que teremos que enviar uma lista de palestra para o cliente de modo que primeiro
devemos definir uma pequena funo auxiliar que atribuir um campo servertime para tais respostas.

function sendTalks(talks, response) {


respondJSON(response, 200, {
serverTime: Date.now(),
talks: talks
});
}

O manipulador precisa olhar para os parmetros de consulta da URL do pedido para ver se o parmetro
changesSince foi enviado. Se voc entregar a url para o mdulo da funo parse teremos um segundo
argumento que ser true ; tambm teremos que analisar parte por parte de uma URL. Se o objeto que ele
retornou tem uma propriedade query removemos o outro objeto que mapeia os parmetros de nomes para os
valores.

293
router.add("GET", /^\/talks$/, function(request, response) {
var query = require("url").parse(request.url, true).query;
if (query.changesSince == null) {
var list = [];
for (var title in talks)
list.push(talks[title]);
sendTalks(list, response);
} else {
var since = Number(query.changesSince);
if (isNaN(since)) {
respond(response, 400, "Invalid parameter");
} else {
var changed = getChangedTalks(since);
if (changed.length > 0)
sendTalks(changed, response);
else
waitForChanges(since, response);
}
}
});

Quando o parmetro changesSince no enviado, o manipulador simplesmente acumula uma lista de todas as
palestras e retorna.

Caso contrrio o parmetro changeSince tem que ser verificado primeiro para certificar-se de que um nmero
vlido. A funo getChangedTalks a ser definido em breve retorna um array de palestras que mudaram desde um
determinado tempo. Se retornar um array vazio significa que o servidor ainda no tem nada para armazenar no
objeto de resposta e retorna de volta para o cliente (usando waitForChanges ), o que pode tambm ser respondida
em um momento posterior.

var waiting = [];

function waitForChanges(since, response) {


var waiter = {since: since, response: response};
waiting.push(waiter);
setTimeout(function() {
var found = waiting.indexOf(waiter);
if (found > -1) {
waiting.splice(found, 1);
sendTalks([], response);
}
}, 90 * 1000);
}

O mtodo splice utilizado para cortar um pedao de um array . Voc d um ndice e uma srie de elementos
para transforma um array removendo o restante dos elementos aps o ndice dado. Neste caso ns
removeremos um nico elemento do objeto que controla a resposta de espera cujo ndice encontramos pelo
indexOf . Se voc passar argumentos adicionais para splice seus valores sero inseridas no array na posio
determinada substituindo os elementos removidos.

Quando um objeto de resposta armazenado no array de espera o tempo ajustado imediatamente.


Determinamos 90 segundos para ser o tempo limite do pedido, caso ele ainda estiver a espera ele envia uma
resposta de array vazio e remove a espera.

Para ser capaz de encontrar exatamente essas palestras que foram alterados desde um determinado tempo
precisamos acompanhar o histrico de alteraes. Registrando a mudana com registerChange , podemos
escutar as mudana juntamente com o tempo atual do array chamado de waiting . Quando ocorre uma alterao
isso significa que h novos dados, ento todos os pedidos em espera podem serem respondidos
imediatamente.

294
var changes = [];

function registerChange(title) {
changes.push({title: title, time: Date.now()});
waiting.forEach(function(waiter) {
sendTalks(getChangedTalks(waiter.since), waiter.response);
});
waiting = [];
}

Finalmente getChangedTalks poder usar o array de mudanas para construir uma srie de palestras alteradas,
incluindo no objetos uma propriedade de deleted para as palestras que no existem mais. Ao construir esse
array , getChangedTalks tem de garantir que ele no incluiu a mesma palestra duas vezes; isso pode acontecer se
houver vrias alteraes em uma palestra desde o tempo dado.

function getChangedTalks(since) {
var found = [];
function alreadySeen(title) {
return found.some(function(f) {return f.title == title;});
}
for (var i = changes.length - 1; i >= 0; i--) {
var change = changes[i];
if (change.time <= since)
break;
else if (alreadySeen(`change.title))
continue;
else if (change.title in talks)
found.push(talks[change.title]);
else
found.push({title: change.title, deleted: true});
}
return found;
}

Aqui concluimos o cdigo do servidor. Executando o programa definido at agora voc vai ter um servidor rodando
na porta 8000 que serve arquivos do subdiretrio public ao lado de uma interface de gerenciamento de palestras
sob a URL /talks .

O cliente
A parte do cliente onde vamos gerenciar as palestras, basicamente isso consiste em trs arquivos: uma pgina
HTML, uma folha de estilo e um arquivo JavaScript.

HTML
Servir arquivos com o nome de index.html uma conveno amplamente utilizado para servidores web quando
uma solicitao feita diretamente de um path , onde corresponde a um diretrio. O mdulo de servidor de
arquivos que usamos foi o ecstatic , ele suporta esta conveno. Quando um pedido feito para o path / o
servidor procura pelo arquivo em ./public/index.html ( ./public a raiz que especificamos) e retorna esse arquivo
se for encontrado.

Se quisermos uma pgina para mostrar quando um navegador estiver apontado para o nosso servidor devemos
coloca-l em public/index.html . Esta a maneira que o nosso arquivo index ir comear:

295
<!doctype html>

<title>Skill Sharing</title>
<link rel="stylesheet" href="skillsharing.css">

<h1>Skill sharing</h1>

<p>Your name: <input type="text" id="name"></p>

<div id="talks"></div>

Ele define o ttulo do documento e inclui uma folha de estilo que define alguns estilos, adicionei uma borda em
torno das palestras. Em seguida ele adiciona um input de nome; onde esperado que o usurio coloque seu
nome para que ele possa ser redirecionado para a observao das palestras.

O elemento <div> com o id "talks" conter a lista atual de todas as palestras. O script preenche a lista
quando recebe as palestras do servidor.

Segue o formulrio que usado para criar uma nova palestra:

<form id="newtalk">
<h3>Submit a talk</h3>
Title: <input type="text" style="width: 40em" name="title">
<br>
Summary: <input type="text" style="width: 40em" name="summary">
<button type="submit">Send</button>
</form>

Um script ir adicionar um manipulador de evento "submit" para este formulrio, a partir do qual ele far a
solicitao HTTP que informar ao servidor sobre a palestra.

Em seguida, vem um bloco bastante misterioso, que tem seu estilo de exibio definido como none , impedindo
que ele aparea na pgina. Voc consegue adivinhar para o que serve?

<div id="template" style="display: none">


<div class="talk">
<h2></h2>
<div>by <span class="name"></span></div>
<p>[object Object]</p>
<div class="comments"></div>
<form>
<input type="text" name="comment">
<button type="submit">Add comment</button>
<button type="button" class="del">Delete talk</button>
</form>
</div>
<div class="comment">
<span class="name"></span>:
</div>
</div>

Criar estruturas de DOM com JavaScript complicado e produz um cdigo feio. Voc pode tornar o cdigo um
pouco melhor atravs da introduo de funes auxiliares como a funo elt do captulo 13, mas o resultado vai
ficar ainda pior do que no HTML que foi pensado como uma linguagem de domnio especfico para expressar
estruturas do DOM.

Para criar uma estrutura DOM para as palestras, o nosso programa vai definir um sistema de templates simples
que utiliza estruturas includas no DOM que estejam escondidas no documento para instanciar novas estruturas,
substituindo os espaos reservados entre chaves duplas para os valores de uma palestra em especfico.

296
Por fim, o documento HTML inclui um arquivo de script que contm o cdigo do lado do cliente.

<script src="skillsharing_client.js"></script>

O inicio
A primeira coisa que o cliente tem que fazer quando a pgina carregada pedir ao servidor um conjunto atual de
palestras. Uma vez que estamos indo fazer um monte de solicitaes HTTP , vamos novamente definir um
pequeno invlucro em torno XMLHttpRequest que aceita um objeto para configurar o pedido, bem como um
callback para chamar quando o pedido for concludo.

function request(options, callback) {


var req = new XMLHttpRequest();
req.open(options.method || "GET", options.pathname, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(null, req.responseText);
else
callback(new Error("Request failed: " + req.statusText));
});
req.addEventListener("error", function() {
callback(new Error("Network error"));
});
req.send(options.body || null);
}

O pedido inicial mostra as palestras que recebeu na tela e inicia o processo de long polling chamando o mtodo
waitForChanges .

var lastServerTime = 0;

request({pathname: "talks"}, function(error, response) {


if (error) {
reportError(error);
} else {
response = JSON.parse(response);
displayTalks(response.talks);
lastServerTime = response.serverTime;
waitForChanges();
}
});

A varivel lastServerTime usado para controlar o tempo da ltima atualizao que foi recebido do servidor. Aps o
pedido inicial as palestras exibidas pelo cliente correspondem ao tempo da resposta das palestras que foram
devolvidas pelo servidor. Assim a propriedade serverTime que foi includa na resposta fornece um valor inicial
apropriado para lastServerTime .

Quando a solicitao falhar ns no queremos que a nossa pgina no faa nada sem explicao. Assim
definimos uma funo simples chamada de reportError que pelo menos ir mostra ao usurio uma caixa de
dilogo que diz que algo deu errado.

function reportError(error) {
if (error)
alert(error.toString());
}

297
A funo verifica se existe um erro real, alertando somente quando houver. Dessa forma podemos passar
diretamente esta funo para solicitar pedidos onde podemos ignorar a resposta. Isso garante que se a
solicitao falhar o erro ser relatado ao usurio.

Resultados das palestras


Para ser capaz de atualizar a visualizao das palestras quando as mudanas acontecem, o cliente deve se
manter atualizado das palestras que esto sendo mostradas. Dessa forma quando uma nova verso de uma
palestra que j est na tela sofre atualizaes ela deve ser substitudo pela atual. Da mesma forma quando a
informao que vem de uma palestra deve ser eliminada o elemento do DOM pode ser removido direto do
documento.

A funo displayTalks usada tanto para construir a tela inicial tanto para atualiz-la quando algo muda. Ele vai
usar o objeto shownTalks que associa os ttulos da palestras com os ns do DOM para lembrar das palestras que
se tem atualmente na tela.

var talkDiv = document.querySelector("#talks");


var shownTalks = Object.create(null);

function displayTalks(talks) {
talks.forEach(function(talk) {
var shown = shownTalks[talk.title];
if (talk.deleted) {
if (shown) {
talkDiv.removeChild(shown);
delete shownTalks[talk.title];
}
} else {
var node = drawTalk(talk);
if (shown)
talkDiv.replaceChild(node, shown);
else
talkDiv.appendChild(node);
shownTalks[talk.title] = node;
}
});
}

Para construir a estrutura DOM para as palestras usaremos os templates que foram includas no documento
HTML. Primeiro temos que definir o mtodo instantiateTemplate que verifica e preenche com um template .

O parmetro name o nome do template . Para buscar o elemento de templates buscamos um elemento cujo
nome da classe corresponda ao nome do template que o filho do elemento com id do template . O mtodo
querySelector facilita essa busca. Temos templates nomeados como talk e comment na pgina HTML.

298
function instantiateTemplate(name, values) {
function instantiateText(text) {
return text.replace(/\{\{(\w+)\}\}/g, function(_, name) {
return values[name];
});
}
function instantiate(node) {
if (node.nodeType == document.ELEMENT_NODE) {
var copy = node.cloneNode();
for (var i = 0; i < node.childNodes.length; i++)
copy.appendChild(instantiate(node.childNodes[i]));
return copy;
} else if (node.nodeType == document.TEXT_NODE) {
return document.createTextNode(
instantiateText(node.nodeValue));
}
}

var template = document.querySelector("#template ." + name);


return instantiate(template);
}

O mtodo cloneNode cria uma cpia de um n. Ele no copia os filhos do n a menos que true seja enviado
como primeiro argumento. A funo instancia recursivamente uma cpia do template preenchendo onde o
template deve aparecer.

O segundo argumento para instantiateTemplate deve ser um objecto cujas propriedades so strings com os
mesmos atributos que esto presente no teplate . Um espao reservado como {{title}} ser substitudo com o
valor da propriedade do atributo title .

Esta uma abordagem bsica para a implementao de um template mas suficiente para implementar o
drawTalk .

function drawTalk(talk) {
var node = instantiateTemplate("talk", talk);
var comments = node.querySelector(".comments");
talk.comments.forEach(function(comment) {
comments.appendChild(
instantiateTemplate("comment", comment));
});

node.querySelector("button.del").addEventListener(
"click", deleteTalk.bind(null, talk.title));

var form = node.querySelector("form");


form.addEventListener("submit", function(event) {
event.preventDefault();
addComment(talk.title, form.elements.comment.value);
form.reset();
});
return node;
}

Depois de instanciar o template de talk h vrias coisas que precisamos fazermos. Em primeiro lugar os
comentrios tm que ser preenchido pelo template comments e anexar os resultados no n da classe commnets . Em
seguida os manipuladores de eventos tem que anexar um boto que apaga a palestra e um formulrio que
adiciona um novo comentrio.

Atualizando o servidor

299
Os manipuladores de eventos registrados pela drawTalk chamam a funo deleteTalk e addComment para executar
as aes necessrias para excluir uma palestra ou adicionar um comentrio. Estes tero de construirem as
URLs que se referem as palestras com um determinado ttulo para o qual se define a funo auxiliar de talkURL .

function talkURL(title) {
return "talks/" + encodeURIComponent(title);
}

A funo deleteTalk dispara uma requisio DELETE e informa o erro quando isso falhar.

function deleteTalk(title) {
request({pathname: talkURL(title), method: "DELETE"},
reportError);
}

Adicionar um comentrio requer a construo de uma representao JSON dos comentrios e delegar que ele
seja parte de um pedido POST .

function addComment(title, comment) {


var comment = {author: nameField.value, message: comment};
request({pathname: talkURL(title) + "/comments",
body: JSON.stringify(comment),
method: "POST"},
reportError);
}

A varivel nameField usado para definir a propriedade autor de um comentrio com referncia no campo <input>

na parte superior da pgina que permite que o usurio especifique o seu nome. Ns tambm inserimos o nome
no localStorage para que ele no tem que ser preenchido novamente a cada vez que a pgina recarregada.

var nameField = document.querySelector("#name");

nameField.value = localStorage.getItem("name") || "";

nameField.addEventListener("change", function() {
localStorage.setItem("name", nameField.value);
});

O formulrio na parte inferior da pgina propoe uma nova palestra, ele recebe um manipulador de evento
"submit" . Este manipulador impede o efeito padro do evento (o que causaria um recarregamento da pgina)
passando a ter o comportamento de disparar uma solicitao PUT para criar uma palestra e limpar o formulrio.

var talkForm = document.querySelector("#newtalk");

talkForm.addEventListener("submit", function(event) {
event.preventDefault();
request({pathname: talkURL(talkForm.elements.title.value),
method: "PUT",
body: JSON.stringify({
presenter: nameField.value,
summary: talkForm.elements.summary.value
})}, reportError);
talkForm.reset();
});

Notificando mudanas

300
Gostaria de salientar que as vrias funes que alteram o estado do pedido de criao, excluso da palestras ou
a adio de um comentrio no fazem absolutamente nada para garantir que as mudanas que eles fazem sejam
visveis na tela. Eles simplesmente dizem ao servidor que contam com o mecanismo de long polling para
acionar as atualizaes apropriadas para a pgina.

Dado o mecanismo que implementamos em nosso servidor e da maneira que definimos displayTalks para lidar
com atualizaes das palestras que j esto na pgina, o long polling surpreendentemente simples.

function waitForChanges() {
request({pathname: "talks?changesSince=" + lastServerTime},
function(error, response) {
if (error) {
setTimeout(waitForChanges, 2500);
console.error(error.stack);
} else {
response = JSON.parse(response);
displayTalks(response.talks);
lastServerTime = response.serverTime;
waitForChanges();
}
});
}

Esta funo chamada uma vez quando o programa inicia e em seguida continua a chamar assegurando que um
pedido de polling esteja sempre ativo. Quando a solicitao falhar no podemos chamar o mtodo reportError

pois se o servidor cair a cada chamada uma popup ir aparecer para o usurio deixando nosso programa bem
chato de se usar. Em vez disso o output do erro dever aparecer no console (para facilitar a depurao) e uma
outra tentativa feita em dois segundos e meio depois.

Quando o pedido for bem-sucedido os novos dados colocado na tela e lastServerTime atualizado para refletir o
fato de que recebemos dados correspondentes nesse novo momento. O pedido imediatamente reiniciado para
esperar pela prxima atualizao.

Se voc executar o servidor e abrir duas janelas do navegador em localhost:8000/ um ao lado do outro voc vai
observar que as aes que voc executa em uma janela so imediatamente visveis no outro.

Exerccios
Os exerccios a seguir vai envolver uma modificao definida neste captulo. Para trabalhar com elas certifique-se
de baixar o cdigo primeiro e ter instalado Node.

Persistncia no disco
O servidor de compartilhamento de habilidade mantm seus dados puramente na memria. Isto significa que
quando o servidor travar ou reiniciar por qualquer motivo todas as palestras e comentrios sero perdidos.

Estenda o servidor e faa ele armazenar os dados da palestra em disco. Ache uma forma automtica de recarrega
os dados quando o servidor for reiniciado. No se preocupe com performance faa o mais simples possvel e
funcional.

Dica:

A soluo mais simples que posso dar para voc transformar todas as palestras em objeto JSON e coloca-las
em um arquivo usando fs.writeFile . J existe uma funo ( registerChange ) que chamada toda vez que temos
alteraes no servidor. Ela pode ser estendida para escrever os novos dados no disco.

301
Escolha um nome para o arquivo, por exemplo ./talks.json . Quando o servidor iniciado ele pode tentar ler esse
arquivo com fs.readFile e se isso for bem sucedido o servidor pode usar o contedo de arquivo como seus
dados iniciais.

Porm cuidado, as palestras comeam como um prottipo menos como um objeto para que possa ser operado
normalmente. JSON.parse retorna objetos regulares com Object.prototype como sendo do seu prottipo. Se voc
usar JSON como formato de arquivo voc ter que copiar as propriedades do objeto retornados por JSON.parse em
um novo objeto.

Melhorias nos templates


A maioria dos sistemas de templates fazem mais do que apenas preencher algumas strings. No mnimo
permitem a incluso de condicional em alguma parte do template como if ou uma repetio de um pedao de
template semelhante a um while .

Se formos capazes de repetir um pedao de template para cada elemento em um array no precisaremos de
um segundo template ( comment ). Em vez disso poderamos especificar que o template talk verifica os conjuntos
das propriedade de uma palestra e dos comentrios realizando uma interao para cada comentrio que esteja
no array .

Ele poderia ser assim:

<div class="comments">
<div class="comment" template-repeat="comments">
<span class="name"></span>:
</div>
</div>

A idia que sempre que um n com um atributo template-repeat encontrado durante a instanciao do
template, o cdigo faz um loop sobre o array na propriedade chamada por esse atributo. Para cada elemento do
array ele adiciona um exemplo de n. O contexto do template (a varivel com valores em instantiateTemplate )
durante este ciclo aponta para o elemento atual do array para que ` seja o objeto de um comentrio e no o contexto

original do objeto talk`.

Reescreva instantiateTemplate para implementar isso e em seguida altere os templates para usar este recurso e
remover a prestao explcita dos comentrios na funo drawTalk .

Como voc gostaria de acrescentar a instanciao condicional de ns tornando-se possvel omitir partes do
template quando um determinado valor falso ou verdadeiro?

Dica:

Voc poderia mudar instantiateTemplate de modo que sua funo interna no tenha apenas um n mas tambm
um contexto atual como um argumento. Voc pode verificar se no loop sobre os ns filhos de um n exista um
atributo filho em template-repeat . Se isso acontecer no instancie, em vez de um loop sobre o array indicado pelo
valor do atributo, faa uma instancia para cada elemento do array passando o elemento atual como contexto.

Condicionais pode ser implementado de uma forma semelhante aos atributos de chamadas, por exemplo
template-when e template-unless quando inserido no template ir instanciar ou no um n dependendo de uma
determinada propriedade que pode ser verdadeiro ou falso.

Os unscriptables
Quando algum visita o nosso site com um navegador que tenha JavaScript desabilitado ou o navegador que no
suporta a execuo de JavaScript eles vo conseguir ver uma pgina inopervel e completamente quebrada. Isso
no bom.

302
Alguns tipos de aplicaes web realmente no pode ser feito sem JavaScript. Para outros voc simplesmente no
tem o oramento ou pacincia para se preocupar com os clientes que no podem executar scripts. Mas para
pginas com um grande pblico uma forma educada apoiar os usurios que no tenha suporte a script.

Tente pensar de uma maneira com que o site de compartilhamento de habilidade poderia ser configurado para
preservar a funcionalidade bsica quando executado sem JavaScript. As atualizaes automticas no seria mais
suportada e as pessoas teram que atualizar sua pgina da maneira antiga. Seria bom sermos capazes de
possibilitar esses usurios de ver as palestras existentes, criar novas e apresentar comentrios.

No se sinta obrigado a implementar isso. Esboar uma soluo o suficiente. Ser que a abordagem vista
mais ou menos elegante do que fizemos inicialmente?

Dica:

Dois aspectos centrais da abordagem feita neste captulo como a interface HTTP e um template do lado do cliente
de renderizao no funcionam sem JavaScript. Formulrios HTML normais podem enviar solicitaes GET e
POST , mas no solicitaes PUT ou DELETE e os dados podem serem enviados apenas por uma URL fixa.

Assim o servidor teria que ser revisto para aceitar comentrios, novas palestras e remover palestras atravs de
solicitaes POST, cujos corpos no podem serem JSONs, ento devemos converter para o formato codificado
em uma URL para conseguirmos usar os formulrios HTML (ver Captulo 17). Estes pedidos teriam que retornar a
nova pgina inteira para que os usurios vejam os novos estados depois que feito alguma mudana. Isso no
seria muito difcil de fazer e poderia ser aplicado conjuntamente com uma interface HTTP mais "clean".

O cdigo para mostrar as palestras teria que ser duplicado no servidor. O arquivo index.html ao invs de ser um
arquivo esttico, teria de ser gerado dinamicamente adicionando um manipulador para que o roteador incluia as
palestras e comentrios atuais quando for servido.

303

Você também pode gostar