Sie sind auf Seite 1von 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

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.

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

128

64

32

16

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.

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

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
mesma precedncia que

e, de forma similar, os operadores

possui a

possuem a mesma precedncia entre si.

Quando vrios operadores de mesma precedncia aparecem prximos uns aos outros, como por exemplo
+ 1

, eles so aplicados da esquerda para a direita:

(1 - 2) + 1

1 - 2

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
representar a operao de resto.
144 % 12

produz

X % Y

o resto da diviso de

por

. Por exemplo,

314 % 100

usado para
produz

14

. 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
clculo

Infinity - 1

Infinity

-Infinity

continua sendo

, que so usados para representar os infinitos positivo e negativo. O

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
Infinity - Infinity

0 / 0

(zero dividido por zero),

ou, ento, realizar quaisquer outras operaes numricas que no resultem em um nmero

preciso e significativo.

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
linha e, de forma similar, um

aparecer aps uma barra invertida, ele ser interpretado como uma quebra de

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
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.

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

false

respectivamente).

Comparaes
Essa uma maneira de produzir valores Booleanos:
console.log(3 > 2)
// true
console.log(3 < 2)
// false

Os sinais

>

<

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),

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

<=

(menor que ou igual a),

==

(igual a) e

!=

(no

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 (


valor que dado ele. Por exemplo,

!true

produz

false

). Ele um operador unrio que inverte o

produz

!false

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,

||

operador

>

&&

, em seguida vm os operadores de comparao (

possui a menor precedncia, depois vem o


==

, 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

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

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

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,
operador
nmero

+
1

null

se torna

e, na segunda, a string

"5"

se torna o nmero

. J na terceira expresso, o

tenta efetuar uma concatenao de string antes de tentar executar a adio numrica e, por isso, o
convertido para a string

"1"

Quando algo que no pode ser mapeado como um nmero de forma bvia (tais como
convertido para um nmero, o valor
NaN

continuam produzindo

NaN

NaN

"five"

ou

undefined

produzido. Quaisquer outras operaes aritmticas realizadas com

, 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

apenas se ambos os lados forem

undefined
null

ou

aparece em algum dos lados do operador, ser produzido verdadeiro


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
(ou

!=

ou

null

undefined

, voc pode simplesmente compar-lo a

null

com o operador

==

).

Mas e se voc quiser testar se algo se refere ao valor preciso


nmeros para valores booleanos afirmam que
outros valores contam como

true

false

? As regras de converso de strings e

e empty strings contam como

NaN

. Por causa disso, expresses como

0 == false

false

, enquanto todos os

"" == false

retornam

true

Para casos assim, onde voc no quer qualquer converso automtica de tipos acontecendo, existem dois tipos
extras de operadores:

===

!==

. O primeiro teste se o valor precisamente igual ao outro, e o segundo testa se

ele no precisamente igual. Ento

falso como esperado.

"" === false

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

&&

||

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
false

&&

trabalha similarmente, mas ao contrrio. Quando o valor sua esquerda algo que se torne

, 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

resultado vai ser verdadeiro, e


X

, no importa o que

- pode ser uma expresso que faa algo terrvel - o

nunca avaliado. O mesmo acontece para

false && X

, que falso, e vai ignorar

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 (


%

), um para concatenao de string (

), comparao (

==

11

!=

===

!==

<

>

<=

>=

&&

||

) e lgica (

,e

),

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
"psychoanalysis"

22

ou

) 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

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
No pode haver espaos includos. Dgitos podem tambm ser parte dos nomes de variveis -

catch22

var

).

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

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

14

undefined

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
in
try

case

catch

instanceof
typeof

continue

interface

var

void

let

while

debugger
new
with

default

null
yield

delete

package

do

private

else

false

protected

finally
public

for

return

function
static

if
switch

implements
throw

true

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

usou a

alert

string

que foi passada como o texto a ser

mostrado na caixa de dilogo. Os valores passados para funes so chamados de


funo

alert

arguments

(argumentos). A

precisa somente de um deles, mas outras funes podem precisar de diferentes quantidades ou

tipos de argumentos.

A Funo
A funo

alert

console.log

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


quando voc pressiona

F12

browser

fica oculta por padro, mas muitos browsers abrem

, 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
entre eles:
console.log(Math.max(2, 4));

16

Math.max

, que pega dois nmeros e retorna o maior

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

confirm

O ambiente fornecido pelos navegadores contm algumas outras funes para mostrar janelas. Voc pode
perguntar a um usurio uma questo Ok/Cancel usando
usurio clica em OK e

false

confirm

. Isto retorna um valor booleano:

true

se o

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

um valor do tipo

string

, e ns queremos um nmero. Existem funes similares chamadas

que convertem valores para estes tipos.

17

String

prompt

Boolean

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
funo

isNaN

Number

uma funo padro do JavaScript que retorna


retorna

NaN

true

apenas se o argumento fornecido for

NaN

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

.A

quando voc fornece a ela uma string que no representa um nmero vlido. Por isso,

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

expresso entre parnteses e seguida por uma declarao, similar ao

if

while

acompanhada por uma

. 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 (

). 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,


nmero

12

number

incrementado por

. No incio de cada repetio, ele comparado com o

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

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

. A nica diferena entre eles que o

while

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
neg-lo, e todas as strings exceto

""

faz com que o valor seja convertido para o tipo Booleano antes de

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
Aqui est o cdigo que calcula 2 usando

for

while

ao invs de

21

while

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
declarao especial chamada

break

false

no a nica maneira que um loop pode parar. Existe uma

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


Se voc no incluir a declarao
resultado

true

break

contida nele seja executada.

ou acidentalmente escrever uma condio que sempre produza um

break

, 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

continue

similar ao

break

, de modo que tambm influencia o progresso de um loop. Quando

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

counter -= 1

, existem equivalentes mais curtos:

Resolvendo um valor com

counter++

counter--

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

para o rtulo correspondente ao valor que

switch

fornece, ou para

default

case

. O programa vai pular

se nenhum valor for encontrado. Ento

ele comea a executar as declaraes, e continua a passar pelos rtulos, at encontrar uma declarao
Em alguns casos, como no exemplo
cases

um

case "sunny"

(ele recomenda "ir l fora" para ambos os tempos

break

break

, pode ser usado para compartilhar algum cdigo entre os


sunny

cloudy

). Mas tenha cuidado: fcil esquecer de

, 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

/*

*/

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 (
do

for

else

switch

) e loops (

while

).

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
adicionando 1 (

+ = 1

hash

de caracteres. Voc pode ir de 1 para 2

). Voc pode ir de "#" para "##", adicionando um caractere (

acompanhar de perto o nmero, de impresso do programa.

FizzBuzz
25

+ = "#"

). Assim, a soluo pode

Escreva um programa que imprima usando


nmeros divisveis por 3, imprima
Buzz

console.log()

todos os nmeros de 1 a 100 com duas excees. Para

ao invs do nmero, e para nmeros divisveis por 5 (e no 3), imprima

Fizz

Quando o programa estiver funcionando, modifique-o para imprimir


ambos por 3 e 5 (e continue imprimindo

Fizz

Buzz

FizzBuzz

para nmeros que so divisveis

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
funcione para qualquer

size

size = 8

e altere programa para que ele

, 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
Utilize

console.log

\n

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
receber uma srie de parmetros (nesse caso, somente

function

. Funes podem

) 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,
recebe nenhum parmetro, enquanto

power

recebe dois:

28

makeNoise

no

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


makeNoise

power

, que produz apenas um efeito colateral. A declarao

square
return

acima, e outras no, como no exemplo de


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
uma expresso aps, ir fazer com que o retorno da funo seja

undefined

return

sem

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
funo

f2

. A primeira funo

no declara

f1

declara a varivel como local e ento muda apenas seu valor. J a segunda

localmente, portanto sua referncia a

no topo do exemplo:

29

est associada varivel global

definida

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

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

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


variveis definidas dentro de

landscape

30

landscape

no consegue ver as

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

ser usada no incio da declarao, como demonstrado abaixo:

31

. A palavra-chave

function

tambm pode

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


para o incio dessa funo (linha 2). Em seguida, invocado

console.log

greet

faz com que o controle pule

(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

valor da funo). Quando voc ler

"congela" o cdigo que est em seu corpo e o envolve em um pacote (o

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" (
number * factor;
multiplier
number

) seja executado. Ele continua tendo acesso varivel

factor

return

que foi criada na chamada de

e, alm disso, tem acesso ao argumento que foi passado a ele (o valor 5) por meio do parmetro

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

, no h muitos benefcios em usar a abordagem mais elegante. Em outros casos, um

power

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


vezes, explorando as solues que comeam com
que comea com

(1 + 5)

(1 + 5)

(1 * 3)

find

invoca a si mesma duas

. A primeira chamada tenta achar a soluo

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
retornado at retornar para a chamada inicial. Nesse momento, o operador
chamadas inicie o processo de explorao pelo outro caminho

(1 * 3)

||

null

faz com que a pilha de

. 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
string

.length

aps o valor de uma

. Por isso, o lao de repetio

string

while

nos fornecer o tamanho (quantidade de caracteres) daquela

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,
colateral: imprimir uma linha. A segunda verso,

printZeroPaddedWithLabel
zeroPad

, invocada para produzir um efeito

, 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

certamente til. Algumas operaes so tambm mais

console.log

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


uma cadeia de declaraes

if

else if

else

find

do exemplo recursivo

findSolution

que testam qual dos trs casos se aplica. O

neste captulo, com


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
voc acessa seu tamanho com
(por exemplo,
posio

"b"

"s".length

string

escrevendo

. O valor retornado ser uma

string

"string".charAt(N)

, similar a como

contendo somente um caractere

). O primeiro caractere est na posio zero, o que faz com que o ltimo seja encontrado na

string.length -1

. Em outras palavras, uma

suas respectivas posies so


Escreva uma funo

countBs

string

com dois caracteres possui tamanho (

length

) dois, e

que receba uma

string

como nico argumento e retorna o nmero que indica

quantos caracteres "B", em maisculo, esto presentes na


Em seguida, escreva uma funo chamada

countChar

string

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
usarmos um ndice de zero at uma unidade abaixo que seu tamanho (

string

< string.length

sejam verificados se
). 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

41

var

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
acessar o tamanho de uma string) e

Math.max

acessam uma propriedade em algum valor. No primeiro caso, acessamos a propriedade


em

myString

myString.length

(para

(funo que retorna o valor mximo). Essas so expresses que

. No segundo, acessamos a propriedade chamada

max

no objeto

Math

length

do valor contido

(que um conjunto de

valores e funes relacionados matemtica).


Praticamente todos os valores no JavaScript possuem propriedades. As nicas excees so
Se voc tentar acessar a propriedade em algum deles, voc receber um erro.
null.length;
// TypeError: Cannot read property 'length' of null

43

null

undefined

As duas formas mais comuns de acessar propriedades no JavaScript so usando ponto e colchetes. Ambos
value.x

value[x]

acessam uma propriedade em

diferena est em como o

value

, mas no necessariamente a mesma propriedade. A

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
"x",

value[x]

tenta avaliar a expresso

value.x

acessa a propriedade chamada

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
Se voc deseja extrair a propriedade cujo nome o valor que est armazenado na varivel

value.length

, 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:

ou

value[2]

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
funo tem acesso string

"Doh"

toUpperCase

no passe nenhum argumento, de alguma forma a

, 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
ler pela primeira vez a propriedade

wolf

undefined

, o que acontece quando tentamos

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
string para

"rat"

"cat"

, impossvel que outro cdigo altere os caracteres dessa

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
valor de

object1

object2

object2

. A varivel

propriedades de

object1

esto associadas ao mesmo ob jeto e, por isso, alterar

object3

object1

tambm altera o

aponta para um objeto diferente, o qual inicialmente contm as mesmas

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 (
1]

[76, 9, 4,

). Podemos tambm usar outras formas de representaes, como por exemplo um array contendo dois arrays

com dois elementos cada (


"01"

[[76, 9], [4, 1]]

), ou at mesmo um objeto com propriedades nomeadas de

"11"

. 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.


raiz quadrada, fornecida pelo objeto

Math

Math.sqrt

a funo que calcula a

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
indexOf

hasEvent

testa se uma entrada contm ou no o evento em questo. Os arrays possuem um mtodo

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
valor de sua correlao e ento adicionamos ao objeto.

51

phis

, calculamos o

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:
// bread:

0.1371988681
-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

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


shift

unshift

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

e remover o primeiro item da lista. A funo

urgentlyRememberTo

whatIsNext()

para acessar

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

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


tambm possuem o mtodo
O mtodo

concat

slice

ir pegar todos os elementos aps o ndice de incio. Strings

com um comportamento similar.

slice

pode ser usado para unir arrays, parecido com o que o operador

exemplo a seguir mostra ambos

concat

slice

faz com as strings. O

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

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

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
mtodo

charAt

length

das strings. Para acessar caracteres individuais de uma string, podemos usar o

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


normalmente percorrem por todos os valores em seu objeto

arguments

console.log

. Essas funes

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,
(mximo),

Math.max

O objeto

Math

Math.min

Math

uma caixa de ferramentas com funes relacionadas a nmeros, tais como

(mnimo) e

Math.sqrt

(raiz quadrada).

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

segura dentro do objeto

Math

est contida de forma

max

, 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
(cosseno),

sin

Math

, caso precise realizar clculos trigonomtricos,

(seno) e

tan

Math

pode ajud-lo. Ele contm

(tangente), tanto quanto suas funes inversas

aos

asin

cos

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
arredonda o nmero para o menor valor inteiro mais prximo) no resultado de

56

Math.random

Math.floor

(que

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.round

Math.ceil

(para arredondar o valor para o maior nmero inteiro mais prximo) e

(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
usando

null

value.propName

undefined

ou

, a maioria dos valores no JavaScript possuem propriedades e so acessados

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

que recebe dois argumentos,

range

contendo todos os nmeros a partir do valor


Em seguida, escreva a funo

start

at o valor

end

(incio) e

start

end

(fim), e retorna um array

(incluindo-o).

que recebe um array de nmeros como argumento e retorna a soma desses

sum

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

valores negativos, fazendo com que

[1, 3, 5, 7, 9]

range(5, 2, -1)

produza

. Certifique-se de que funcione tambm com

[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
vazio) e, em seguida, chamar vrias vezes o seu mtodo

push

[]

(um novo array

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
operador

<=

<

, voc dever usar o

para checar o final do seu loop.

Para verificar se o argumento opcional de incremento foi fornecido, voc pode verificar o
comparar o valor do argumento com

undefined

arguments.length

ou

. 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

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
do incio ao fim e usar o mtodo

unshift

reverseArray

. A primeira simplesmente iterar o array fornecido

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


frente faz com que seja necessrio usar uma notao
-

push

um pouco estranha (

. Iterar um array de trs para

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


(

for

array.slice(0)

reverseArray

ou at mesmo copiar o array inteiro

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

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}

{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
[1, 2, 3]

arrayToList

que constri uma estrutura de dados similar estrutura anterior quando fornecido

como argumento e, escreva tambm, a funo

Alm disso, implemente uma funo auxiliar

prepend

que produz um array a partir de uma lista.

listToArray

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}

list = {value: X, rest:

para adicionar um elemento.

Para percorrer uma lista (no caso de

listToArray

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,


isso, o corpo da funo pode acessar a propriedade
node

value

node

aponta para a prxima sublista e, por

para pegar o elemento atual. Ao final de cada iterao,

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


+ 1

value

do seu n head (inicial). Para acessar o elemento

, 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
pela verificao de suas propriedades, voc pode usar o operador

typeof

. Se ele produzir

===

"object"

para isso) ou
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


encontrada e retornar

true

apenas ao final da funo.

61

false

quando qualquer diferena for

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

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

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


uma varivel contadora, uma checagem do tamanho do

console

array

". Mas utiliza um jeito redundante que envolve

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

, digitar errado

length

como

lenght

, confundir as variveis

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 (
ter que busc-lo fora do

array

De fato, no precisamos definir um mtodo


Quando um

array

number

) pelo elemento atual, ao invs de simplesmente

manualmente.
forEach

. Ele esta disponvel como um mtodo padro em

fornecido para o mtodo agir sobre ele, o

forEach

arrays

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

espera apenas um argumento obrigatrio:

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

um parmetro da funo externa. Mas como as funes internas

esto dentro do ambiente externo, podemos usar a varivel

. 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

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

noisy

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

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

, mas mesmo assim isso no

deixaria explcito quantos seriam suficientes. Essa soluo limita algumas informaes de
arguments.length

como por exemplo

. 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
(ou um

array

como

objeto

apply

. Voc passa um

array

) 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

para o objeto

e retorna, apenas estes argumentos, para


apply

. O primeiro argumento do

apply

. Ela faz isso passando seus prprios argumentos

, 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
JavaScript. O mtodo
arrays

forEach

array

so bastante usadas em

uma funo mais primitiva. Existe outras variantes disponveis como mtodos em

. 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

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

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

est disponvel na

ANCESTRY_FILE

do meu arquivo

como uma

JSON

string

sandbox

deste captulo para download no site, onde est o contedo

. 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


computao. A funo
includo no

array

test

test

, com um valor de funo, para preencher uma lacuna na

chamada para cada elemento, e o seu valor de retorno determina se um elemento

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


Assim como

forEach

filter

um mtodo padro de

arrays

que foi dado.

. O exemplo define uma funo s para mostrar o

que ela faz internamente. A partir de agora vamos us-lo assim:

67

array

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

Transformando com map


Digamos que temos um

de objetos que representam pessoas, produzido atravs do

array

de alguma forma. Mas queremos um


O mtodo

map

transforma um

array

de ancestrais

de nomes o que mais fcil para ler.

array

aplicando uma funo para todos os elementos e constri um novo

array

partir dos valores retornados. O novo

array

ter o mesmo tamanho do

array

enviado, mas seu contedo

array

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

filter

tambm um mtodo padro de

map

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

funo menos simples do que o

so, alm do

filter

map

array

, uma funo para combinao e um valor inicial. Esta

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

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

gigante, ele est bem composto nos conceitos que interessamos

loop

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
pode fazer algo com o

array

array

mais elegante expresso em uma sequncia separada onde cada passo

e produzir um novo

array

. Mas a construo de todos esses

intermedirios

arrays

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
de um

loop

(diretamente, ou atravs de um

o cdigo dentro do
repete e
loop

loop

interno acaba rodando

o nmero de vezes que o

interno tiver outro

loop

loop

que realize

loop
P

externo chamando uma funo que executa um


NxM

vezes, onde

o nmero de vezes que o

interno se repete dentro de cada interao do

voltas, seu bloco rodar

M x N x P

loop

loop

loop

loop

dentro

interno),

de fora se

externo. Se esse

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
condensa um

array

reduce

, que

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

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
pessoa est em um determinado conjunto de

bind

string

. Ele define uma funo

. Ao chamar

filter

isInSet

, que nos diz se uma

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
primeiro argumento do

apply

null

, utilizado para as chamadas de mtodo, semelhante ao

. 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

um

fornece uma grande quantidade de funes de ordem superior -

array

filter

para construir um novo

array

com valores filtrados,

cada elemento colocado atravs de uma funo e

reduce

forEach

map

faz algo com cada elemento de

para construir um novo array onde

para combinar todos os elementos de um

array

em

um valor nico.
Funes tm o mtodo

apply

que pode ser usado para chamar um

tambm possuem um mtodo

bind

array

especificando seus argumentos. Elas

que usado para criar uma verso parcial da funo que foi aplicada.

Exerccios
Juntando
Use o mtodo

reduce

juntamente com o mtodo

que tem todos os elementos de entrada do

array

concat

para juntar um

array

de

arrays

em um nico

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

73

average

definida anteriormente neste captulo.

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

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
remover os elementos nulos antes de passar o

array

para o mtodo

average

hasKnownMother

filter

para

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


aceitar um

array

groupBy

que abstrai a operao de agrupamento. Ele deve

como argumento e uma funo que calcula cada elemento do grupo de

objeto que mapeia os nomes dos grupos de

arrays

array

e retorna um

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

de objetos de pessoas ou idades. J que no sabemos quais agrupamentos iremos encontrar,

arrays

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

para ele. Em seguida adicione a pessoa (ou idade) no

array

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
tambm vm com os mtodos padres

Arrays

predicada que quando chamada com um


operador
retorna

&&

true

predicada

retorna apenas

true

array

como argumento retorna


true

array

retorna quando algum elemento do

array

tiver um valor como


some

ou

false

. Assim como o

array
true

true

every

. Sendo assim, a funo


. Ele no processa mais

encontrar o que precisa no primeiro elemento

ele no percorrer os outros elementos.

Escreva duas funes, que se comporte como esses mtodos,


array

true

para cada elemento do

elementos do que o necessrio - por exemplo, se o predicado


do

(alguns). Ambos recebem uma funo

some

como valor quando as expresses de ambos os lados forem

quando a funo predicada retorna

some

(todos) e

every

every

some

, exceto se eles receberem um

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

retorne um valor correto quando atingir o final do

array

75

return

aps o

loop

; para que a funo

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 contrarevoluo 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


ambos os mtodos

apply

bind

this

para dar a sada do tipo de coelho que est falando. Lembrando que

podem usar o primeiro argumento para simular chamadas de mtodos. Esse

primeiro argumento, na verdade, usado para passar um valor ao


Existe um mtodo parecido ao

apply

chamado

call

this

. Ele tambm chama a funo na qual ele um mtodo e

aceita argumentos normalmente, ao invs de um array. Assim como


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

apply

bind

,o

call

pode ser passado

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


Array.prototype

Function.prototype

, e arrays derivam do

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
A funo

Object.getPrototypeOf

toString

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
construtor. O construtor ter sua varivel

this

new

vai fazer com que ela seja tratada como um

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


por padro possui um objeto vazio que deriva do

Object.prototype

Coelho

, que

. Toda instncia criada com esse construtor ter

esse objeto assim como seu prototype. Ento, para adicionar um mtodo
construtor

prototype

fala

aos coelhos criados com o

, 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

construtores so funes. Sua propriedade

prototype

Function.prototype

, visto que os

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()
minWidth()

retorna um nmero indicando a altura mnima que esta clula necessita (em linhas).
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

array de clulas e envolve isso em um

map

As coisas so um pouco mais difceis na funo


colunas. No mencionei at agora que

map

reduce

para computar a altura mxima de um

para fazer isso em todas as linhas no array


colWidths

(assim como

rows

porque o array externo um array de linhas, no de


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,
elemento para cada ndice da coluna. A chamada

reduce

pega a largura da clula mais larga nesse ndice.


Aqui est o cdigo para desenhar a tabela:

81

colWidths

roda sobre o array

constri um array com um

rows

exterior para cada ndice e

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"]

duas linhas e ser representada pelo array

["name", "----"]

, onde uma clula sublinhada deve conter

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
A funo

drawRow

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

em cada ocorrncia do seu argumento e retorna um array com as partes. O mtodo

minWidth

com maior largura nesse array.

82

, que corta uma string


encontra a linha

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


string

repetido

times

repeat

, que constri uma linha na qual o valor um argumento

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

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

//

etcetera

A funo padro

5895

Tanzania

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

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
setSomething

getSomething

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


equivalente a

TextCell

minHeight

, exceto que seu mtodo

draw

minWidth

de

TextCell

. Um

RTextCell

agora basicamente

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.prototype
Array

deriva de

TextCell.prototype

RTextCell

uma instncia de

TextCell

porque

. O operador pode ser aplicado a construtores padro como

. 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
prototype

new

para criar objetos. O prottipo do novo objeto ser o objeto encontrado na propriedade

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
parmetros

D ao prottipo de

Vector

que represente um vetor em duas dimenses do espao. Ele recebe os

(nmeros), que deve salvar em propriedades de mesmo nome.


Vector

dois mtodos,

plus

minus

novo vetor que tem a soma ou diferena dos valores

, que pegam outro vetor como parmetro e retornam um


e

dos dois vetores (o vetor que est em

this

eo

passado no parmetro).
Adicione uma propriedade getter
(

x, y

length

ao prottipo que calcula o tamanho do vetor - isto , a distncia do ponto

) 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
distncia do ponto

(0, 0)

at

(x, y)

Object.defineProperty

. Para calcular a

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,

o nmero que voc quer, e

(x2 + y2)

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
clula resultante tem pelo menos a largura (

width

) e altura (

height

faz) e assegurar que a

UnderlinedCell

) 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
minHeight

devem chamar atravs dos mtodos correspondentes na clula interna (

nenhum nmero menor que o tamanho dado retornado (possivelmente usando


No se esquea de adicionar um mtodo

draw

inner

minWidth

), mas assegure-se que

Math.max

).

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
e chama

console.log

logFive

que pega um objeto sequencial

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


(recebendo os argumentos

from

to

RangeSeq

que itera sobre um intervalo de inteiros

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

a sequncia chegar ao fim. Mas agora voc tem um problema quando a sequncia realmente tiver

null

quando
. 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

bidimensional onde cada entidade ocupa um quadrado da

grid

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

### #",
#",

"############################"];

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

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
relacionadas da

grid

grid

deve preocupar-se com as coisas

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 * largura)

(x, y)

encontrado na posio

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
pode ser til ao decidir que ao tomar. Quando o mtodo

act

grid

. Mas essa viso limitada

chamado o objeto de verificao permite que o

bicho inspecione seus arredores. Ns vamos nomear oito quadrados vizinhos para ser as coordenadas:
para norte,

"ne"

"n"

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":

0),

new Vector(-1,

"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

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

nomes de direo. Ns tambm poderamos termos usado

chama

Object.keys

randomElement

em uma matriz de

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
grid

Strings

que representa a

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

primeiro criamos uma instncia do tipo correto, observando o construtor do carter aplicando

elementFromChar

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

//

//

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

This

##
#
#

##

##

###

#
o

#
o

### #
#

e seu escopo

O construtor do mundo contm uma chamada de


dentro da funo do

forEach

forEach

. Uma coisa interessante que podemos notar que

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
uma varivel local na funo exterior da

this.grid

grid

para acessar nada de fora de dentro do

, onde a funo interna tera acesso a ela.


94

loop

. Podemos criar

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

valor de um elemento do

e retorna o valor do

array

prop

que esta dentro do objeto

test

somado ao resultado do

A maioria dos mtodos que mapeiam matrizes tais como

forEach

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
determinada funo para cada elemento da

grid

forEach

para o nosso tipo de

grid

, chamaremos uma

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
mtodo

act

grid

usando o mtodo

forEach

que acabamos de definir a procura de objetos com um

. 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

conseguirmos acessar corretamente as funes internas. O mtodo

para ser a referncia da


letAct

grid

para

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"

refere a um sentido vlido caso o


quadrado

ele ser ignorado. Se


quadrado

"move"

de destino e ao se mover novamente vamos definir

bicho na prximo
Perceba que

quadrado

letAct

ele ter uma propriedade de direo que se

na direo referida estiver vazio(


null

null

). Iremos definir o bicho para o

para este quadrado visitado e armazenar o

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


personagem correspondente ao elemento. Para coordenadas fora da

grid

grid

e se o

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

segundo at que voc aperte o boto de

que executa uma animao, movendo o mundo com trs voltas por

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

significa 45 graus no sentido horrio para norte quando retornar "ne". Da mesma forma

dirPlus("n", 1)
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

"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,
prottipo global, mas que substitui o mtodo

letAct

LifelikeWorld

. O novo mtodo

deve executar uma ao para vrias funes armazenados no objeto

, cujo seu prottipo baseado no

letAct

delega o trabalho do que realmente

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
de ao como

{type: "grow"}

"crescer"

e sera usado pelas plantas. Quando um objeto

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

no est vazio ou se o bicho no tem energia necessria; o movimento retorna

. Se no vlido, se o destino

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

###***

#",
**#",
*###",

"############################"],
{"#": 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
no velho no mundo do vale. Veja como que as tarifas esto. Ajuste um pouco mais se necessrio.

102

PlantEater

// Your code here


function SmartPlantEater() {}
animateWorld(new LifelikeWorld(
["############################",
"#####

######",

"##

***

"#

*##**

"#

***

"#

**##",
**
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(
["####################################################",
"#

####

"#

"#

##*

"#

##***

"#* **

"#* **

O O

****

***

#########

"###

***

######

****

**

"##

**

"###

##
O

##

** #",

##
***

*****

#",

###
*

#",
** #",

***

#
O

**#",
######",

@
##

*#",
**#",

"#

*#",
**#",

#
#

"#*

##",

****

"#*

"#

OO

##########

##

"#

###",

########

##

"#

"#

****

##

***

#####

*
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

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 = Math.floor (n / base)

n / = base

o que ns realmente queremos

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 palavrachave) 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

Captulo 3. Uma exceo exibida no


Se as excees tivessem um

stack

stack

stack

que foi mencionado no

indicando todos os contextos de chamadas que ele encontrou.

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

usada para gerar uma exceo. Para tratar uma excesso basta envolver um pedao de

throw

cdigo em um bloco

try

a ser lanada o bloco

, seguido pela palavra-chave

catch

. Quando o cdigo no bloco

try

causa uma exceo

chamado. O nome da varivel (entre parnteses) aps captura ser vinculado ao

valor de exceo. Aps o termino do bloco


try/catch

catch

catch

ou do bloco

try

o controle prossegue sob toda a instruo

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
exceo foi criado, o chamado

stack

stack

e sobre chamadas que existia quando a

de rastreamento. Esta informao armazenada na propriedade do

stack

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

exceo, e o contexto nunca ser definido de volta para o seu valor antigo.
110

ser exibido no

stack

pela

tem mais uma declarao. Eles podem ser seguidos por um

try

finally

com ou sem o bloco

catch

. O bloco

significa "no importa o que acontea execute este cdigo depois de tentar executar o cdigo do bloco

finally

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

pode ou vai causar uma exceo. Mas ns no sabemos o que ou qual exceo que sera lanada.

111

try

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.");
}
}

a construo de um loop infinito de forma intencionalmente que no para sozinho. Ns quebramos o

for (;;)

circuito de fora somente quando uma direo vlida fornecida. Mas a mal escrita do
em um erro de "varivel indefinida". O bloco

catch

promptDirection

resultar

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";

prototype

InputError

feito para derivar-se de

Error.prototype

para que

instanceof Error

. Nome a propriedade tambm dada para tipos de erro padro (

retorne

Error

true

para objetos de

SyntaxError

ReferenceError

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
para si prprio.
Agora

promptDirection

pode lanar um erro.


112

stack

do objeto

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 tornarse 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

stack

de chamadas se desencadeie o bloco

. O valor da exceo ser capturado pelo bloco

catch

try/catch

at a parte inferior do

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

em outros 50 por cento levanta uma exceo do tipo


esta funo

MultiplicatorUnitFailure

que em 50 por cento dos casos multiplica dois nmeros e

MultiplicatorUnitFailure

. Escreva uma funo que envolva

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

para relanar a exceo quando no uma instncia de


repetida quando ele uma instncia de

MultiplicatorUnitFailure

Para refazer o processo, voc pode usar um

loop

try

. O bloco

MultiplicatorUnitFailure

catch

fica responsvel

e garantir que a chamada

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

A caixa trancada
Considere o seguinte objeto:

114

stack

e entrar em loop infinito.

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

desbloqueada. No permitido acessar a propriedade

_content

Escreva uma funo chamada

withBoxUnlocked

mas voc pode obt-lo apenas quando a caixa for


diretamente.

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

destravar a caixa e em seguida chamar a funo que vem de argumento dentro da funo
finally

. Sua funo deve ser


withBoxUnlocked

. E no

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

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
re.exec(input)

como a condio no bloco

while

, podemos buscar no incio de cada iterao.


126

match =

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}/
/(abc)+/
/a|b|c/

Entre duas e quatro ocorrncias


Agrupamento
Padres alternativos

/\d/

Caracteres dgitos

/\w/

Caracteres alfanumricos ("caracteres palavra")

/\s/
/./
/\b/

caracteres espao em branco


Todos caracteres exceto quebras de linha
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 subnamespaces 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

A mais bvia maneira o operador padro especial

de cdigo) e rod-los no contexto do programa atual.

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


backgroundReadFile

define

, vamos supor que tambm temos uma funo

, 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

funo

igual a

) e armazenamos isso em cache. Ns tambm comeamos a carregar o mdulo, usando a

false

backgroundReadFile

construtor

Function

. Uma vez que o arquivo tenha sido carregado, seu contedo rodado usando o

assumido que este arquivo tambm contenha uma (nica) chamada a


define.currentModule

define

. A propriedade

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

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


b ased, assim como o tipo

Date

weekDay

, que pode converter os nmeros dos meses (zero-

) 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 (

precisam de saber ou se preocupar da existncia do mdulo


Eu tambm dupliquei dois valores minsculos e genricos (

grid

) no

randomElement

Wall

) pois eles so usados como

detalhes internos em contextos diferentes, e no pertencem nas interfaces destes mdulos.

143

ecosystems

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

mas tambm para construes como


Para manter o analisador simples,

, ou uma aplicao. As aplicaes so usados para chamadas de funo,


if

String

ou

while

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

ou

literais

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

elementos simples(atmicas) que Egg suporta:

String

usa trs expresses regulares para detectar os trs

Number

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


chamando uns aos outros.
146

parseApply

parseExpression

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

147

, se estiver, buscar o valor da varivel.

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

existem apenas dois valores

booleanos

vamos ligar duas variveis em

topEnv

booleanos

. Uma vez que

ns no precisamos de sintaxe especial para eles. Ns simplesmente


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

analisa e avalia as

String

enviroment

em tempo real,

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

um novo objeto que tem acesso s variveis do ambiente externo(

prototype

Object.create

para fazer

) 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

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

construo de uma matriz contendo os argumentos como valores,


array

element(array, n)

buscar

elementos de uma matriz.

152

topEnv

do escopo:

length(array)

array(...)

vai ser a

para obter o comprimento de um

// 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
para converter um

array

enviroment

em um

no

object

topEnv

deve ser uma funes.

Array.prototype.slice

; pode ser utilizado

de argumentos numa matriz regular.

Resoluo

Closures
A maneira que definimos o

permitido que as funes em Egg se chamem em ambiente circundante,

fun

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

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

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
sub-formas do

enviroment

incluso e usa isso para criar


Isto significa que o

enviroment

. A funo retornada por

prototype

enviroment

do

se fecha sobre o argumento

env

dada a sua funo de

local da funo quando chamado.

enviroment

seja possvel ter acesso as variveis de

local de modo que eles possam ser avaliados pelas suas


fun

local ser o

enviroment

enviroment

em que a funo foi criado, o que faz com que

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

que todos os pontos onde

para ignorar comentrios assim como feito com os espaos em branco; para

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

prottipos de um objeto. Lembre-se tambm que os escopos no derivam de


quiser chamar

hasOwnProperty

Object.prototype

que retorna os
, por isso, se voc

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
cada um dos escopos use

hasOwnProperty

Object.getPrototypeOf

ate ir ao escopo externo. Para

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

significa que no existe; ento um erro deve ser acionado.


Resoluo

155

null

) e no encontramos a varivel, isto

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
documento, o

body

head

(cabea) e um

"My home page" e em seguida, declaramos o


As tags de

<h2>

body

(corpo). O

head

contm informaes sob re o

contm o documento em si. Neste caso, ns primeiro declaramos que o ttulo do documento
<h6>

body

contendo um cabealho (

produzem cabealhos menores) e dois pargrafos (

Tags aparecem em diversas formas. Um elemento, como o


tag de abertura como em

<p>

como aquela para o link (

<a>

<body>

<h1>

<p>

).

, um pargrafo ou um link, comea com uma

e termina com uma tag de fechamento como em


), contm informaes extra na forma de pares

de atrib utos. Nesse caso, o destino do link indicado pelo atributo

, que significa "heading 1" -

</p>

. Algumas tags de abertura,

nome="valor"

. Estes so chamados

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
src

<img src="http://example.com/image.jpg">

, que ir mostrar a imagem encontrada na URL informada no atributo

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

Dentro que um atributo, que definido entre aspas duplas, a entidade

&quot;

&amp;

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 inserila. 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
<h1>

<html>

<head>

pertence ao

body

<body>

foram retiradas. O navegador sabe que a tag

<title>

pertence ao

head

, e que

. 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>

<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
atributo

src

<script>

pode receber um

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
boto na pgina) possui um atributo

onclick

<button>

mostrada abaixo (que aparece como um

, 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

sendo usadas para envolver o valor do atributo. Eu tambm poderia ter usado
programa difcil de ler.

Na caixa de areia

159

porque aspas duplas j esto


&quot;

, mas isso tornaria o

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

que representa a tag . Essa propriedade tambm nos fornece as propriedades

head

body

se refere ao objeto
, 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

<!--

-->

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

propriedades identificadas por nmeros para acessar os ns filhos. Mas ele uma instncia do tipo
no um array real, logo ele no possui mtodos como

slice

forEach

e
NodeList

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
e

lastChild

firstChild

apontam para o primeiro e ltimo elemento filho, respectivamente, ou ento possuem o valor

(nulo) para ns sem filhos. Similarmente,

previousSibling

nextSibling

null

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

acontece se usarmos a propriedade

para um primeiro n filho, ela ir possuir um valor nulo, o mesmo

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
exemplo, no tem apenas trs filhos (um

<h1>

e dois

<p>

body

que usamos no comeo deste captulo, por

'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

, o qual coleta todos os elementos com o nome passado

getElementsByTagName

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
document.getElementById

id

e usar o mtodo

<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
documento. Para adicionar um filho, ns podemos usar o mtodo
lista de filhos, ou at o mtodo

insertBefore

removeChild

appendChild

remove um dado n filho do

, o qual coloca n filho no fim da

, 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

esperam o seu novo n como primeiro argumento.

insertBefore

Criando Ns
No exemplo seguinte, ns queremos escrever um script que substitua todas as imagens (tags
documento pelo texto que elas possuem no seu atributo

alt

<img>

) no

, 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

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
o nome de uma tag e retorna um novo n vazio do tipo fornecido.

167

document.createElement

. Esse mtodo pega

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

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
para "pr-formatado", usado para cdigo ou similares em texto plano) com um atributo
destacar as palavras chaves para aquela linguagem.

168

data-language

<pre>

(tag

e tenta

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
getAttribute

className

setAttribute

. Voc tambm pode acess-la pelo seu nome real, "

Layout
169

class

", usando os mtodos

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
offsetHeight

offsetWidth

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

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

right

top

(topo),

bottom

(baixo),

left

(esquerda) e

(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

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
getBoundingClientRect

offsetHeight

ou chamando

, 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
contedo ficar em negrito e
A maneira que uma tag

<a>

<img>

<strong>

fazendo seu

fazendo seu contedo azul e sublinhando-o.

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

seguidas por dois pontos e um valor (assim como


separadas por pontos e vrgulas. Por exemplo, "

green

color

). Caso existam mltiplas declaraes, elas devero ser

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

Essa propriedade carrega um objeto que possui todas as propriedades possveis para o atributo

do n.

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
family"]

), os nomes de propriedades no objeto

eles tornada maiscula (

style.fontFamily

style

style["font-

, nestes casos, tm seus traos removidos e a letra aps

).

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
eles

font-weight: bold

, sobreposto pela regra na tag

<style>

, que adiciona

font-style

<strong>

color

, o qual d

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
a regra

font-weight

<style>

inclusse

font-weight: normal

padro, o texto seria normal e no em negrito. Estilos em um atributo

style

, conflitando com
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
todos os elementos com "abc" no seu atributo classe. Uma regra para
atributo

id

de "xyz" (o qual deve ser nico dentro do documento).

172

#xyz

.abc

aplica-se para

aplica-se para o elemento com um

.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

ou apenas um

.a

ento uma regra composta como essa teria preferncia.


A notao

p > a {...}

mesmo modo,

aplica os estilos passados para todas as tags

p a {...}

aplica-se todas as tags

<a>

<a>

dentro de tags

que so filhos diretos de tags

<p>

<p>

. Do

, 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

seja, atualizado em tempo real. Ele no ir mudar quando voc mudar o documento.
173

no "vivo", ou

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


mas agora as propriedades
Quando

position

top

definida como

left

relative

, o elemento ainda ocupa espao no documento,

podem ser usadas para mov-lo em relao ao seu lugar original.

absolute

o elemento removido do fluxo normal do documentoisso , no

ocupa mais espao e pode sobrepor outros elementose suas propriedades

top

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


repetidamente as propriedades de estilo
O script usa

requestAnimationFrame

top

left

animate

. Ns vamos atualizar

dessa imagem para mov-la.

para agendar a funo

preparado para redesenhar na tela. A funo

relative

animate

para rodar sempre que o navegador estiver

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


e multiplicada por 20, que o raio vertical do nosso crculo. O estilo

left

top

computada com

baseado em

Math.cos

Math.sin

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
childNodes

parentNode

, 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
atravs de sua propriedade

color

style

ou

display

. JavaScript pode manipular o estilo de um elemento diretamente

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

elementos clula: ou clulas de cabealho (

<th>

<tr>

. Dentro dessa tag ns podemos colocar

) 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
seus valores das propriedades em elementos

<td>

<th>

e uma linha subsequente por objeto no array, com

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

mtodo

appendChild

para criar novos ns de elementos,

document.createTextNode

para criar ns de texto e o

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

mesmo nome de tag todo em caixa alta. Use os mtodos

ou

toLowerCase

. Mas note que isso ir retornar o

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 (
src="img/hat.png"

<img

) 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

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

navegador o escopo global equivalente ao objeto


addEventListener

window

window

no

. Cada elemento DOM tem seu prprio mtodo

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

tem um efeito similar. Mas um n tem apenas um atributo

onclick

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
O mtodo

addEventListener

removeEventListener

permite que voc adicione vrios manipuladores.

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

em ambos no boto e no pargrafo e em torno dele.

"mousedown"

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
um evento de

"keyup"

"keydown"

quando ele liberado

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
d uma maneira de encontrar este cdigo.
182

charCodeAt

que pertence a propriedade

String

nos

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

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"

"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
charCode
Unicode

que acionado logo aps

"keypress"

"keydown"

(repetido junto com

quando a tecla solta) mas apenas para as teclas que produzem entrada de caracteres. A propriedade

"keydown"

no objeto do evento contm um cdigo que pode ser interpretado como um cdigo de caracteres

. 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
so semelhantes aos

"keydown"

"keyup"

"mousedown"

"mouseup"

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"

evento de clique.

183

(clique duplo) emitido tambm aps o segundo

Para obter informaes precisas sobre o local onde aconteceu um evento do mouse voc pode olhar para as
suas propriedades

pageX

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

clientY

so semelhantes aos

pageX

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
"mouseout"

"mouseover"

. Quando o mouse se move a partir de um n em um dos seus filhos

e acabar com ela em

"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
esses eventos. Ele garante que no caso de
caso do

"mouseout"

apenas quando o

"mouseover"

relatedTarget

dos objetos de eventos criados por

o elemento que o ponteiro do mouse passou antes e no

o elemento que o ponteiro do mouse ira passar. Ns queremos mudar o nosso efeito
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

hover

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"

"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
vez de

px

em

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


Ao dividir

pageYOffset

innerWidth

que acompanha o

innerHeight

.)

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
eventos de

"blur"

"focus"

nele. Quando se perde o foco um

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"

"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
tag

<script>

<script>

executado imediatamente quando o tag encontrada. As vezes a

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
os arquivos que eles fazem referncia foram carregados. Eles so como os eventos de

"load"
focus

para indica que

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
envio de uma

string

preventDefault

. Ele feito atravs do

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
exemplo disto. Um disparo de eventos outra. No captulo 13 discutimos a funo

<script>

requestAnimationFrame

um

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

que criou o
worker

worker

envia uma mensagem o que causa um evento de

envia e recebe mensagens atravs do objeto

Worker

"message"

disparado ao receptor. O roteiro

, ao passo que as conversaes de

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

requestAnimationFrame

funciona da mesma forma que

que ir cancelar esse

Um conjunto de funes semelhante so

frame

clearTimeout

chamando um valor retornado pelo

(supondo que ele j no tenha sido chamado).

setInterval

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

no esteja fazendo isso com muita freqncia. Isto geralmente chamado de

para se certificar de que voc

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(
evitar a manipulao padro do navegador do evento(
Pressionando uma tecla, eventos de
mouse, eventos de
"mouseenter"

"mousedown"

"mouseout"

"keydown"

"mouseup"

preventDefault

"keypress"

"click"

stopPropagation

).

"keyup"

so disparados. Pressionar um boto do

so disparados. Movendo o mouse, eventos de

so disparados.

190

) ou

"mousemove"

A rolagem pode ser detectado com o evento de


detectadas com o
no

window

"focus"

"blur"

"scroll"

, e quando a mudana de foco este eventos podem ser

. Quando o documento termina de carregar, um evento de

"load"

disparado

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
cdigos para as letras que voc deseja filtrar. Em

"keydown"

charCode

e comparar isso com os

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
atributo

tabname

display: none

<button>

na parte superior do n e para cada elemento filho devera conter o texto recuperado do

de cada boto. Todos exceto um dos filhos originais devem ser escondidos(dando um estilo de

) 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
nodeType

wrapper

que tm um

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
Um

Level

Level

sem antes construir um editor de

Level

simples ficaria assim:

var simpleLevelPlan = [
"

",

"

",

"

= x

"

",

o o

"

x @

",

xxxxx

"

xxxxx

",

",

"

x!!!!!!!!!!!!x

",

"

xxxxxxxxxxxxxx

",

"

"

];

Tanto a grade (grid) fixa e os elementos mveis so inclusos no plano. Os caracteres


caracteres de espao so para o

espao vazio

e os

representam paredes, os

representam algo fixo, sees de lava que no se

mechem.
O

define o local onde o jogador comea. Todo

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 (


verticalmente e

) para blocos que se deslocam

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

as moedas forem recolhidas. Se o jogador toca a lava o

Level

concludo quando todas

atual restaurado sua posio inicial e o jogador

Level

pode tentar novamente.

A leitura de um level
O construtor a seguir cria um objeto de
Level

Level

. Seu argumento deve ser uma matriz de sequncias que define o

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


Quando isto acontece,

finishDelay

level

. A propriedade

usado para manter o

status

Level

controla se o jogador ganhou ou perdeu.

ativo durante um curto perodo de tempo, de

modo que uma animao simples pode ser mostrada (repor imediatamente ou avanar o
fcil). Este mtodo pode ser usado para descobrir se um

Level

196

foi concludo.

Level

ficaria mais

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
coordenada

Vector

que agrupa uma

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

como o segundo argumento para o construtor, e o construtor de

Lava

Level

passa o caractere fonte do ator

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

personagem que se baseia.

dinamicamente preciso inicializar o objeto de uma forma diferente, dependendo do


Lava Dinmica

se move longitudinalmente em sua velocidade dada at atingir um

obstculo. Nesse ponto, se ele tem uma propriedade


(

gotejamento

repeatPos

ele vai pular de volta sua posio inicial

). 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

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
Multiplicamos o valor retornado pelo

Math.random

Math.Sin

, a largura de uma onda produzida, de 2.

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

elementos simples do DOM para mostrar o

Level

DOMDisplay

, e usaremos

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
display

Level

, que nunca muda, desenhado apenas uma vez. Os atores so redesenhados toda vez que o

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
ou distncia de 1 significa uma unidade do

grid

grid

, onde o tamanho

. 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


da propriedade

grid

<table>

. Este corresponde estrutura

onde cada linha transformada em uma linha da tabela (elemento

grade so usadas como nomes de classe para a clula da tabela (elemento

<td>

<tr>

). As cordas na

). 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

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
A regra de
(

background

dentro deles.

define a cor de fundo. No CSS permitido as cores serem especificadas tanto com palavras

) tanto com um formato como RGB (

write

padding

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
scrollPlayerIntoView

Level

sempre se encaixem na janela de exibio. por isso que a chamada

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

perto do canto.

201

scrollTop

para quando o jogador estiver muito

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
far com que ele torne

-10

scrollLeft

para

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

ou redefine um

Level

Level

para ser usado quando o jogo se move para o prximo

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

pgina. O arquivo

game.css

rel="stylesheet"

torna-se uma maneira de carregar um arquivo CSS em uma

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


coordenadas do

body

body

se sobrepe usando

Math.floor

Math.ceil

nas

. 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

para

"lava"

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

o tempo do passo em segundos. O objeto

key

level

a chance de se mover. Seu argumento

step

traz

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

ganhou ou perdeu e o ponto onde ns paramos de mostrar o


O loop

while

que controla o tempo entre o ponto onde o jogador


Level

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
math.sin

wobble

atualizada para controlar o tempo e em seguida utilizada como um argumento para

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


Isso nos da a opo do

Level

"won"

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"

"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

que vimos no captulo 13 fornece uma boa maneira de animar um jogo. Mas sua

requestAnimationFrame

interface bastante primitiva para us-la, o que nos obriga a ficar controlando sua ltima chamada para executar
a funo

novamente aps cada frame.

requestAnimationFrame

Vamos definir uma funo auxiliar que envolve as partes chatas em uma interface conveniente e nos permitir
simplesmente chamar

dando-lhe uma funo que espera uma diferena de tempo como um

runAnimation

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

aba ou janela mostrado novamente. Neste caso a diferena entre

lasttime

ser suspenso at que a

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
funo. Ele exibe o

Level

toma um objeto do

Level

Level

no construtor de uma exposio e opcionalmente uma

(em document.body) e permite que o usurio pea por ele. Quando o

terminado(perda ou ganho),

Level

Level

de execuo, limpa o visor, para a animao e caso a funo

chama essa funo com o status do

Level

est

andthen

for dada,

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

concludo vamos passar para o prximo


conjunto de planos de

Level

Level

Level

atual reiniciado. Quando um

Level

. Isso pode ser expresso pela seguinte funo o que leva um

(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

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
para implementar as trs vidas ao iniciar.

209

runGame

<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
encerramento do

startLevel

runGame

e portanto visvel para o

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

interromper ou retomar a animao sempre que a tecla


A interface

runAnimation

maneira que

RUNLEVEL

para usar outro manipulador de eventos de teclado e


Esc

pressionada.

no pode se responsabilizar por isso primeira vista, mas basta voc reorganizar a
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

seus tratadores quando comea e cancela o registro novamente quando ele for concludo.

210

para registrar

<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
continuado chamando

runAnimation

falso

na funo dada ao

runAnimation

. Ele pode ser

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
que o mesmo valor funo exata que foi passado para
removeEventListener
trackKeys

addEventListener

trackKeys

lembre-se

deve ser passado para

para remover com xito um manipulador. Assim o valor da funo manipuladora criada em

devera estar disponvel para o cdigo que cancela os manipuladores.

Voc pode adicionar uma propriedade para o objeto retornado por


ou um mtodo que manipula ou remove o registro diretamente.

211

trackKeys

contendo um ou outro valor da funo

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
captulo eu vou voltar para os

SVG

trade-offs

em detalhes, mas vou explicar brevemente como ele funciona. No final do


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>

<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

retngulo, em seguida a sua largura e a sua altura. Um mtodo semelhante

do canto superior esquerdo do

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
string

fillStyle

pode alterar o jeito que as formas so preenchidas. Ele pode ser definido como uma

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

path

uma sequncia de linhas. A interface de uma tela 2D tem uma abordagem peculiar de descrever esse

. Isso feito inteiramente atravs dos efeitos colaterais. Os

no constituem valores que podem ser

paths

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


stroke

path

com um nmero de segmentos de linha horizontal e faz traos usando o mtodo

. Cada segmento criado com

do ltimo segmento a no ser que


passada para

moveTo

Ao preencher um

lineTo

moveTo

comea na posio atual do

path

. Esta posio normalmente o fim

seja chamado. Nesse caso, o prximo segmento comeara na posio

path

(usando o mtodo

vrias formas, cada movimento com

fill

moveTo

) cada forma preenchido separadamente. Um

inicia um novo. Mas o

fim devem ficar na mesma posio) antes de ser preenchido. Se o

path
path

a partir de sua extremidade para o comeo da forma delimitada pelo

path

pode conter

tem de ser fechado(ou seja o seu incio e


no estiver fechado a linha adicionada

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

segmento da linha atual de volta ao incio do

para fechar explicitamente um


path

path

atravs da adio de um

. Este segmento desenhado traando o

214

path

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
mtodo

arcTo

arcTo

(cada um que giram 90 graus). Mas o

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
de cerca de

6,28

ou

2 * Math.PI

que

. 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

e um fim maior do que

(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
novo

path

moveTo

ou iniciar um

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
delinear as letras mas geralmente
fillColor

fillText

strokeText

. Este ltimo pode ser til para

o que voc precisa. Ele vai encher o texto com a cor atual de

fillText

<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
textAlign

para

end

ou

center

ou

voc pode mudar a posio horizontal definindo a propriedade

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

partir de uma tag


elemento

<img>

nos permite desenhar dados de pixel em

<img>

ou

<canvas>

canvas

. Estes dados de pixel pode ter origem a

, e nem todos so visveis no documento atual. O exemplo a seguir cria um

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

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
chamada

drawImage

cx.scale (-1, 1)

antes da

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

girar formas posteriormente desenhados com o mtodo de

rotation

canvas

e mov-los com o mtodo de

. Voc pode

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
o

eixo-y

eixo-y

para onde queremos que o nosso espelho fique e aplicamos, finalmente deslocamos

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
seguida

scale

flipHorizontally

faz primeiro um

translate

para a direita, o que nos leva ao tringulo 2. Em

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
visualizao baseada em

canvas

para comearmos a trabalhar em um sistema de

para o jogo a partir do captulo anterior. O novo visual no ser apenas

canvas

mostrando caixas coloridas. Mas vamos usar

para desenhar imagens que representam os elementos

drawImage

do jogo.
Vamos definir um tipo de objeto
15, ou seja os mtodos

CanvasDisplay

drawFrame

clear

, suportando a mesma interface que

DOMDisplay

a partir do captulo

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
DOMDisplay

animationTime

a razo pela qual passou o tamanho do passo para

no utilizasse. Nossa nova funo

drawFrame

drawFrame

no Captulo 15 embora

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

nvel.

tem o efeito de assegurar que o nmero resultante no seja inferior a zero.

Math.max(x, 0)

Math.min

garantem que a janela de exibio no acabe mostrando espao fora do


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
tambm poderamos ter usado o

translate

canvas

na parte superior esquerda do

level

. Ns

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

uma abordagem melhor que funciona em todas as situaes. Cada opo tem pontos fortes e fracos.
225

. No h

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
Um n

canvas

<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


determina como as formas so preenchidas. As propriedades

strokeStyle

lineWidth

fillStyle

do contexto

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
strokeRect

desenham retngulos e os mtodos

personalizadas preciso primeiro construir um


Chamando

beginPath

para o

atual. Por exemplo

path

fillText
path

strokeText

fillRect

desenham texto. Para criar formas

inicia um novo caminho. Uma srie de outros mtodos podem adicionar linhas e curvas

ser preenchido com o mtodo

lineTo
fill

pode adicionar uma linha reta. Quando um caminho terminado ele pode

ou traado com o mtodo

Mover os pixels de uma imagem ou de outra tela no nosso

stroke

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

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

.
pode ser usado para limpar parte da tela antes

clearRect

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

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
transform

ratation

flipHorizontally

deve utilizar o

. Para usar

rotation

path

, uma maneira interessante pode ser feito com

voc ter que aplicar um truque semelhante ao que fizemos na funo

. Voc pode girar em torno do centro do seu retngulo e no em torno do ponto (0,0), primeiro voc

translate

em seguida

rotation

e ento

translate

Para o ziguezague(3) torna-se impraticvel escrever uma novo

para voltar.
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

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

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
linha de

120 pixels

currentAngle + 0,5 * sliceAngle

. O cdigo a seguir encontra-se em uma posio sobre esta

para centro:

var middleAngle = currentAngle + 0.5 * sliceAngle;


var textX = Math.cos(middleAngle) * 120 + centerX;
var textY = Math.sin(middleAngle) * 120 + centerY;

Para

textBaseline

textAlign
"rigth"

,e

o valor

"middle"

provavelmente uma abordagem a ser utilizada. O que for usado para

depende do lado do crculo em que estamos. esquerda deve ser


left

para texto que estiver posicionado longe do pedao.


228

"center"

, a direita deve usar

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
0, 7)

path

chamando

arc(x, y, raio,

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
canvas

"load"

sobre a imagem pode ser usada para desenhar as imagens invertidas para o

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.


mtodos comuns so

DELETE

GET

significa que queremos acessar o recurso em questo. Outros

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
nenhum tipo dado, mas

PUT

POST

GET

DELETE

no enviam

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

(ou o mtodo for omitido), a query string associada URL contida em

GET

navegador executa a requisio

GET

action

eo

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

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


ser feita para enviar o formulrio ir usar o mtodo

POST

POST

, a requisio HTTP que

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
navegadores, sabem que no devem fazer requisies
GET

POST

POST

. Aplicaes client-side, como os

cegamente, mas frequentemente faro requisies

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
chamamos os seus mtodos

open

send

XMLHttpRequest

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

configura a requisio. Nesse caso, escolhemos fazer uma requisio

open

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
requisio. Para requisies
false

, o mtodo

a propriedade

send

GET

, podemos passar

null

send

. O argumento a ser enviado o corpo da

. Se o terceiro argumento passado para

open

for

ir retornar apenas depois que a resposta da nossa requisio for recebida. Podemos ler

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
acessada por meio da propriedade
getResponseHeader

statusText

status

e a verso legvel em texto do status pode ser

. Alm disso, os cabealhos podem ser lidos com

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
pois significa que as propriedades como

responseText

send

retorna. Isso conveniente

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

chamar o mtodo

como terceiro argumento para

open

, a requisio assncrona. Isso significa que quando

, a nica coisa que ir acontecer imediatamente o agendamento da requisio que ser

send

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

documentElement

. O objeto contido em

responseXML

corresponde ao objeto do documento. Sua propriedade

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
O nome da funo argumento (

callback

XMLHttpRequest

por todo o seu cdigo.

) 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
funo

try

acaba no fazendo sentido. No cdigo a seguir, a exceo no ser capturada pois a chamada

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 bemsucedidas 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
apenas cham-lo de

get

GET

, dessa vez retornando uma promise. Ns vamos

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

capturada. Dessa forma, o

then

try

, o controle de execuo ir continuar normalmente depois que a falha

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
recurso (por exemplo,

/user/larry

GET

para a URL do

), 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

application/json

Envie requisies para recuperar todos os trs formatos desse recurso. Use o mtodo
objeto

XMLHttpRequest

para definir o valor do cabealho chamado

Accept

acima. Certifique-se de configurar o cabealho aps chamar o mtodo


Por fim, tente solicitar pelo tipo de mdia

application/rainbows+unicorns

setRequestHeader

do seu

para um dos tipos de mdia descritos


open

e antes de chamar o mtodo

send

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
como o terceiro parmetro para

open

false

), 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 dropdown 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
so alguns tipos de

<input>

<input>

. Essa tag um tipo de atributo usado para estilos do campo. Estes

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
texto que usado entre as duas tags

</textarea>

<textarea>

requer um

, 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

faz a ligao por trs do fomulrio com o elemento DOM. O elemento

<form>

<form>

que

, 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
Cada tag

<option>

habilitado ou no.

tem um valor. Este valor pode ser definido com um atributo de value, mas quando isso no for

dado, o texto dentro do


<select>

multiple

option

ir contar como o valor do

option

. O valor da propriedade de um elemento

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
<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

<select>

e mudar o texto da nota em um

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


objeto simples

notes

Passando nulo para

notes

para o valor armazenado em

localStorage

ou um valor que no existe, para

"shopping list" vazio . A leitura de um campo que no existe de


JSON.parse

localStorage

ir analisar uma string "null" e retornar null. Assim, o operador

||

ser nulo.
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
contedo de

sessionStorag

LocalStorage

chamado

sessionStorage

. A diferena entre as duas que o

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 multiplechoice, 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 permitemnos 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

manipulador de eventos para o "

click

de texto e chamada

nele.

new Function

para ter acesso aos elementos definidos em seu HTML. Um

" ou eventos no boto "

Certifique-se de envolver tanto a chamada para a

new function

mousedown

" pode ter a propriedade value do campo

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

para as sugestes

<div>

e ver se o resultado zero. Para cada sequncia correspondente, adicionar um elemento


. 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 "
<div>

click

" [para cada elemento ou adicionar um nico para fora

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
simplesmente coloc-los todos no mesmo elemento e colocar

256

<br>

<table>

(olhe o Captulo 13) ou

(quebra de linha) elementos entre as linhas.

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>

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

257

lineWidth

toda hora que eles forem alterados.

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

Ns envolvemos a tela e os controles em elementos

<div>

lineWidth

).

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


um manipulador

mousedown

no elemento

canvas

<option>

para todas as ferramentas que foram definidas e

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
propriedades

clientX

subtrair o canto

top-left

clientY

top-left

da tela. As

dos eventos do mouse tambm so relativas a esse canto, ento podemos

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


pressionado. A funo

trackDrag

mousemove

at que o boto do mouse mantiver

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

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

, o que significa que a

source-over

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

para uma tela) e apag-los novamente. um trabalho,

lineWidth

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
date

email

url

number

<input type='color'>

. Outros incluem

. 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

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
dados: como seu protocolo. Ao contrrio de

http

: normal e

https

data URLs

. Um

data URL

uma URL com

: 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

a imagem no

tem um mtodo conveniente, chamado

canvas

toDataURL

, que ir retornar a URL de dados que contm

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
corrompido, chamando

toDataURL

try/catch

na funo

update

para o link salvar. Quando o

ir lanar uma exceo que uma instncia do

acontece, ns definimos o link para apontar para outro tipo de URL, usando o

SecurityError

javascript

canvas

ficou

. Quando isso

: 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


tamanho do

canvas

canvas

para ajustar precisamente a imagem. Por alguma razo, alterar o

vai causar perca das configuraes do contexto do desenho como

ento a funo salva elas e restaura depois de ter atualizado o tamanho do


O controle para o carregamento de um arquivo local utiliza a tcnica
mtodo

readAsText

canvas

FileReader

fillStyle

lineWidth

a partir do Captulo 18. Alm do

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
serif

sans-

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

mouse pressionado. A funo

para cuspir pontos coloridos a cada 25 milissegundos enquanto o boto do

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

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

pageY

de um evento de mouse pode ser usada

para a posicionar um elemento precisamente sob o mouse, definindo os estilos

left

top

width

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

height

do retngulo.

Math.min

coordenada mais a esquerda X e a coordenada superior Y. Para obter o


Math.abs

pode ser usado para encontrar a


width

height

, voc pode chamar

(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
um objeto com propriedades

top

left

canvas

width

. Considere escrever um
height

findRect

, que converte dois pontos em

, de modo que voc no tenha que escrever a mesma

lgica duas vezes.


Voc pode, ento, criar uma

<div>

e definir seu

style.position

posicionamento, no se esquea de acrescentar


(voc pode acrescent-la

document.body

px

como

absolute

. Ao definir os estilos de

para os nmeros. O n deve ser adicionado ao documento

) 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

como um objeto com propriedades

width

no contexto do desenho, que retorna um pedao retangular da imagem


,

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

seguido por

height

width

indica o incio das coordenadas x e y do retngulo que queremos recuperar,

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

pixels que se originam a partir de outro domnio. Use um

toDataURL

try/catch

ir gerar um erro quando a tela conter

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

demonstra como obter os valores para um determinado pixel. Colocar eles em uma

pixelAt

string rgb

no exemplo
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
executar arquivos JavaScript. Digamos que voc tenha um arquivo chamado

ola.js

console.log(mensagem);

node

a partir da linha de comando para executar o programa:

$ node ola.js
Ol mundo

271

, que usado para

, contendo o seguinte cdigo:

var mensagem = "Ol mundo";

Voc pode ento rodar

node

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


cdigo de sada, que diz ao programa que iniciou

node

exit

finaliza o processo e pode receber um

(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
um array de strings. Note que tambm estaro inclusos o nome dos comandos
fazendo com que os argumentos comecem na posio 2. Se
console.log(process.argv)

showargv.js

node

process.argv

, que

e o nome do seu script,

contm somente o statement

, 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

Node. Funcionalidades relacionadas ao navegador, como


O objeto global do escopo, que chamado

window

JSON

document

, esto presentes tambm no ambiente do


e

alert

esto ausentes.

no navegador, passa a ser

global

no Node, que faz muito

mais sentido.

Mdulos
Alm de algumas variveis que mencionei, como

console

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"

/home/braziljs/elife/world/world.js

Quando uma string recebida pelo

do arquivo

. A extenso
require

/home/braziljs/elife/run.js
.js

pode ser omitida.

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

disponibilizar o mdulo de sistema de arquivos integrado ao Node,


biblioteca encontrada em

, Node vai tentar carregar o arquivo

node_modules/elife

. Por exemplo,

require("elife")

require(fs)

vai tentar carregar a

. A maneira mais comum de instalar bibliotecas como essas

usando NPM, que em breve ns vamos discutir.


272

Para ilustrar o uso do


chamado

main.js

require

, vamos configurar um projeto simples que consiste de dois arquivos. O primeiro

, 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);
});
_

_ _

_
__

| |_| |/ _ \ | |/ _ \

\ \ /\ / / _ \| '__| |/ _` | |

__/ | | (_) |

|_| |_|\___|_|_|\___/

Depois de rodar

_____

_ _

| | | | ___| | | ___

\ V

V / (_) | |

| | (_| |_|

\_/\_/ \___/|_|

|_|\__,_(_)

npm install

, NPM j vai ter criado um diretrio chamado

haver um outro diretrio chamado


require("figlet")

_ __| | __| | |

figlet

node_modules

. Dentro desse diretrio

, que vai conter a biblioteca. Quando rodamos

, essa biblioteca carregada, e ns podemos chamar seu mtodo

text

node

e chamamos

para desenhar

algumas letras grandes.


Talvez de forma inesperada, ao invs de retornar a string que faz crescer as letras,

figlet.text

callb ack que passa o resultado para ela. Ele tambm passa outro parmetro no callb ack,

tm uma funo de

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,

, usada para escrever um arquivo no disco.

writeFile

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


uma string e no um objeto

Buffer

writeFile

assume que recebeu

, 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:

um array de strings,

readdir

que vai retornar os arquivos em um diretrio como

vai buscar informao sobre um arquivo,

stat

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
sncrona de

readFile

"fs"

vm com variantes sncronas e assncronas. Por exemplo, existe uma verso

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
servidor. As variveis

request

response

createServer

chamada toda vez que um cliente tenta se conectar ao

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,
A chamada de

server.listen

response,end

assina o fim da resposta.

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

requisio com o mtodo

que vimos no servidor, o objeto

request

. O exemplo no usa

write

porque

Para fazer requisies para URLs HTTP seguras (HTTPS), o Node fornece um pacote chamado

https

, que

requisies

write

e finalizar a requisio com o mtodo

nos permite transmitir informao na


end

no devem conter informao no corpo da requisio.

GET

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
voc pode usar o mtodo

write

tudo de uma s vez com o

fs.createWritebleStram

. Ento

no objeto resultante para escrever o arquivo pea por pea, ao invs de escrever

fs.writeFile

Streams de leitura so um pouco mais fechados. Em ambos a varivel


callb ack do servidor HTTP e a varivel

response

request

que foi passada para a funo de

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"

"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

uma string chamando

toString

Buffer

binrio, que ns podemos converter para

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
usar

console.log

process.stdout

(a sada padro de processos, como uma stream de escrita) ao invs de

. 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

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
requisio por

/file.txt

/home/braziljs/public/

deve ser referir a

(ou

C:\Users\braziljs\public\

/home/braziljs/public/file.txt

( ou

no Windows), ento a

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

Para obter um caminho de uma URL em uma requisio, a funo

end

urlToPath

da resposta.

usa o mdulo "

Node para parsear a URL. Ela pega o nome do caminho, que ser algo parecido a
tirar os cdigos de escape (como

%20

/file.txt

url

" embutido no

, o decodifica para

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
(indicadores de tipo de contedo como

text/plain

mime

tambm so chamados MIME types) sabe o tipo adequado de

um grande nmero de extenses de arquivos.


Se voc rodar o seguinte comando
require("mime")

npm

no diretrio aonde o script do servidor est, voc estar apto a usar

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,
no existe,

vai passar um objeto de erro com

fs.stat

"ENOENT"

fs.stat

assncrono. Quando o arquivo

em uma propriedade chamada

callb ack. Isso seria muito bom se o Node definisse diferentes subtipos de

Error

code

para o seu

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

(propriedade

retornado pelo

size

fs.stat

nos diz uma poro de coisas sobre um arquivo, tais como tamanho

) e sua data de modificao (propriedade

mtime

diretrio ou um arquivo regular, e quem nos diz isso o mtodo


Ns usamos

fs.readdir

). Nosso interesse aqui saber se isso um

isDirectory

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
passamos ela ao

respond

, junto com o tipo de contedo que o mdulo

"mime"

fs.createReadStream

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
Aqui est a funo que trata as requisies

PUT

280

respondErrorOrNothing

que cria esse callb ack.

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


nossa resposta. Quando a informao for transferida com sucesso,
disparar o evento

"finish"

pipe

"error"

disparado e reportado na

vai fechar ambos streams, o que vai

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
escolher o mtodo da requisio e

-d

-X

usado para para

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

281

try/catch

para todos os callb acks.

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
uma funo chamada

denodeify

, que converte uma funo assncrona como a

"promise"

fs.readFile

do NPM contm

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

funes

fs

que usado por esse cdigo contm estilos de promessas variveis para determinado nmero de

, envolvidas por

Promise.denodeify

. O objeto retornado, com propriedades

code

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
trabalhar com sistema de arquivos e o

"http"

"fs"

, para

, 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

Faa isso novamente usando a funo

do Node. Solicite pelo menos os tipos de mdia

text/html

application/json

objetos, na propriedade

http.request

diferentes.
text/plain

. Lembre-se que os cabealhos para uma requisio podem ser passados como

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
isso. No se esquea da flag

com uma expresso regular a maneira mais fcil de fazer

replace

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

Quando nenhum arquivo encontrado, tente criar um diretrio com

fs.mkdir

MKCOL

. 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
requisio

GET

, usando

<textarea>

XMLHttpRequest

para conter o contedo do arquivo que est sendo editado. Uma

, 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
simples manipulador

"click"

), faa uma requisio

PUT

<form>

e um evento

"submit"

para a mesma URL, com o contedo do

ou um

<textarea>

no

corpo da requisio para salvar o arquivo.


Voc pode ento adicionar um elemento
elementos

<option>

<select>

que contenha todos os arquivos na raiz do servidor adicionando

contendo as linhas retornadas pela requisio

seleciona outro arquivo (um evento

"change"

GET

para a URL /. Quando um usurio

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
informar para o servidor o que fazer.

286

HTTP

para

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

pedem ao servidor para obter novas informaes usando solicitaes

HTTP

, onde os clientes continuamente

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


fazer um bom uso dos mtodos
comeam com

/talks

JSON

HTTP

e como vimos no servidor de arquivos a partir do captulo 20 vamos tentar

. A interface centrado em torno de um path

/talks

Paths

que no

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

Para criar um novo talk preciso uma solicitao do tipo


barra o ttulo da palestra. O corpo da solicitao

PUT

PUT

long polling

para a

URL

. Voltarei a explicar isso mais adiante.


/talks/unituning/

deve conter um objeto

JSON

, onde aps a segunda

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


DELETE

GET

para recuperar a representao do

JSON

de uma palestra ou

para excluso de uma palestra.

Para adicionar um comentrio a uma palestra necessrio uma solicitao


/talks/Unituning/comments

com um objeto

JSON

POST

para uma

URL

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


consulta chamado

long polling

changesSince

precisamos de pedidos

GET

para

/talks

. Podemos incluir um parmetro de

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 online 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

aceita conexes de fora do sistema e os encaminha para servidores


localmente. O

proxy

HTTP

HTTP

que

que esto sendo executados

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

289

Node.js

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
suportamos. Isso pode ser feito com uma longa cadeia de

if

path

) que

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
/talks/

path

usando expresso regular

/^\/talks\/([^\/]+)$/

(que corresponde a

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


registados com o mtodo
Este ltimo ir retornar um

add

Router

. Um objeto de

Router

permite que novos manipuladores sejam

e resolver os pedidos com o mtodo

booleano

resolve

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


%20-style code

URL

decodificada tendo a

URL

codificada assim

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

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

onde criado um servidor que serve apenas

createServer

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

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
propriedades de nomes de uma palestra. Estes sero expostos como recursos

talks
HTTP

, cujos os ttulos so

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
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


291

talks

JSON

da palestra, uma

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
funo chamada

readStreamAsJSON

body

de uma solicitao de

que l todo o contedo de um

stream

JSON

, analisa o

, teremos que definir uma


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


Nesta

request

ambos do tipo

PUT

que usado para criar novas palestras.

devemos verificar se os dados enviados tem um apresentador e o ttulo como propriedades


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
chama

talks

registerChange

, possivelmente substituindo uma palestra que j exista com este ttulo e mais uma vez

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
para

/talks

changesSince

long polling

. Quando uma requisio

GET

chega

pode ser um simples pedido de todas as palestras ou um pedido de atualizao com um parmetro
.

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


changesSince

foi enviado. Se voc entregar a

argumento que ser

true

url

URL

do pedido para ver se o parmetro

para o mdulo da funo

parse

teremos um segundo

; 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
vlido. A funo

changeSince

getChangedTalks

tem que ser verificado primeiro para certificar-se de que um nmero

a ser definido em breve retorna um

determinado tempo. Se retornar um

array

array

de palestras que mudaram desde um

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

para transforma um

array

array

. Voc d um ndice e uma srie de elementos

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
escutar as mudana juntamente com o tempo atual do

array

chamado de

registerChange

waiting

, podemos

. 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

incluindo no objetos uma propriedade de


array

getChangedTalks

array

de mudanas para construir uma srie de palestras alteradas,

deleted

para as palestras que no existem mais. Ao construir esse

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
sob a

8000

URL

que serve arquivos do subdiretrio

/talks

public

ao lado de uma interface de gerenciamento de palestras

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 solicitao feita diretamente de um


arquivos que usamos foi o

ecstatic

servidor procura pelo arquivo em

uma conveno amplamente utilizado para servidores web quando


path

, onde corresponde a um diretrio. O mdulo de servidor de

, ele suporta esta conveno. Quando um pedido feito para o

./public/index.html

./public

path

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

295

index

ir comear:

<!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

solicitao

ir adicionar um manipulador de evento


HTTP

"submit"

para este formulrio, a partir do qual ele far a

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

que contm o cdigo do lado do cliente.

script

<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
pequeno invlucro em torno
callback

XMLHttpRequest

HTTP

, vamos novamente definir um

que aceita um objeto para configurar o pedido, bem como um

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


waitForChanges

long polling

chamando o mtodo

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
apropriado para

lastServerTime

serverTime

que foi includa na resposta fornece um valor inicial

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

usar o objeto

usada tanto para construir a tela inicial tanto para atualiz-la quando algo muda. Ele vai

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


HTML. Primeiro temos que definir o mtodo
O parmetro

name

o nome do

template

facilita essa busca. Temos

templates

template

templates

que foram includas no documento

que verifica e preenche com um

. Para buscar o elemento de

nome da classe corresponda ao nome do


querySelector

instantiateTemplate

templates

298

talk

buscamos um elemento cujo

que o filho do elemento com id do


nomeados como

template

comment

template

. O mtodo

na pgina HTML.

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

como primeiro argumento. A funo instancia recursivamente uma cpia do


template

true

seja enviado

preenchendo onde o

deve aparecer.

O segundo argumento para

instantiateTemplate

mesmos atributos que esto presente no


valor da propriedade do atributo

title

deve ser um objecto cujas propriedades so

teplate

. Um espao reservado como

{{title}}

strings

com os

ser substitudo com o

Esta uma abordagem bsica para a implementao de um


drawTalk

template

template

mas suficiente para implementar o

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

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

. Em

Os manipuladores de eventos registrados pela

drawTalk

chamam a funo

deleteTalk

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


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

PUT

para criar uma palestra e limpar o formulrio.

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

para

long polling

acionar as atualizaes apropriadas para a pgina.


Dado o mecanismo que implementamos em nosso servidor e da maneira que definimos
com atualizaes das palestras que j esto na pgina, o

long polling

displayTalks

para lidar

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
em um arquivo usando

fs.writeFile

. J existe uma funo (

registerChange

alteraes no servidor. Ela pode ser estendida para escrever os novos dados no disco.

301

JSON

e coloca-las

) que chamada toda vez que temos

Escolha um nome para o arquivo, por exemplo


arquivo com

./talks.json

. Quando o servidor iniciado ele pode tentar ler esse

e se isso for bem sucedido o servidor pode usar o contedo de arquivo como seus

fs.readFile

dados iniciais.
Porm cuidado, as palestras comeam como um prottipo menos como um objeto para que possa ser operado
normalmente.
usar

JSON

JSON.parse

retorna objetos regulares com

Object.prototype

como sendo do seu prottipo. Se voc

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
template semelhante a um

while

if

ou uma repetio de um pedao de

Se formos capazes de repetir um pedao de template para cada elemento em um


um segundo template (

array

). Em vez disso poderamos especificar que o template

comment

no precisaremos de

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, o cdigo faz um loop sobre o
array

array

template-repeat

encontrado durante a instanciao do

na propriedade chamada por esse atributo. Para cada elemento do

ele adiciona um exemplo de n. O contexto do template (a varivel com valores em

durante este ciclo aponta para o elemento atual do


original do objeto

Reescreva

array

para que

instantiateTemplate

` seja o objeto de um comentrio e no o contexto

talk`.

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

valor do atributo, faa uma instancia para cada elemento do

array

array

indicado pelo

passando o elemento atual como contexto.

Condicionais pode ser implementado de uma forma semelhante aos atributos de chamadas, por exemplo
template-when

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
POST

, mas no solicitaes

PUT

ou

DELETE

e os dados podem serem enviados apenas por uma

URL

GET

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
O cdigo para mostrar as palestras teria que ser duplicado no servidor. O arquivo

HTTP

mais "clean".

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

Das könnte Ihnen auch gefallen