Beruflich Dokumente
Kultur Dokumente
Introduction
1.1
1.2
Estrutura do Programa
1.3
Funes
1.4
1.5
1.6
1.7
1.8
1.9
Expresses Regulares
1.10
Mdulos
1.11
1.12
JavaScript e o Navegador
1.13
1.14
Manipulando Eventos
1.15
1.16
Desenhando no Canvas
1.17
HTTP
1.18
1.19
1.20
Node.js
1.21
1.22
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
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.
128
64
32
16
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
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
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
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
possui a
Quando vrios operadores de mesma precedncia aparecem prximos uns aos outros, como por exemplo
+ 1
(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
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
Infinity
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
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
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.
Ele no efetua a adio, mas concatena, ou seja, junta duas strings em uma nica string. O prximo exemplo
produzir a string
"concatenate"
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
usam dois valores so chamados de operadores b inrios, enquanto que aqueles que recebem apenas um, so
chamados de operadores unrios. O operador
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
>
<
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
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
>=
igual a).
console.log("Itchy" != "Scratchy")
// true
<=
==
(igual a) e
!=
(no
Existe apenas um valor no JavaScript que no igual a ele mesmo, que o valor
NaN
NaN
supostamente usado para indicar o resultado de alguma operao que no tenha sentido e, por isso, ele
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
O operador
||
indica o valor lgico or ou, em portugus, ou. Ele produz um valor verdadeiro se qualquer um dos
!true
produz
false
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
>
&&
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
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
undefined
null
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).
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
, portanto, quando voc perceber que est recebendo esse valor em algum lugar
==
NaN
os tipos forem diferentes, o JavaScript usa um conjunto de regras complicadas e confusas para determinar o que
fazer, sendo que, na maioria dos casos, ele tenta apenas converter um dos valores para o mesmo tipo do outro
valor. Entretanto, quando
null
ou
undefined
null
ou
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
null
com o operador
==
).
true
false
NaN
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:
===
!==
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
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
, no importa o que
false && X
Resumo
Ns vimos 4 tipos de valores do JavaScript neste captulo. Nmeros, strings, booleanos e valores indefinidos.
Alguns valores so criados digitando seu nome (
true
null
) ou valores (13,
"abc"
), comparao (
==
11
!=
===
!==
<
>
<=
>=
&&
||
) e lgica (
,e
),
typeof
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
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;
var
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.
caught
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
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
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
function
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
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
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
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
F12
browser
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
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
console
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
return
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
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
true
se o
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
um valor do tipo
string
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:
if
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
expresso escrita entre parnteses logo aps a palavra-chave e seguida por uma declarao a ser executada.
A funo
funo
isNaN
Number
NaN
true
NaN
theNumber
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,
if
18
if
else
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");
num
menor que 10. Se for, ele escolhe esse caminho, mostra "Small"
else
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.
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
while
if
while
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
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
12
number
incrementado por
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
<= 10
do
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
while
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
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
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
break
false
break
) uma maneira fcil de testar se um nmero divisvel por outro. Se for, o resto da
for
nesse exemplo no contm a parte que checa pelo fim do loop. Isso significa que o loop no
true
break
break
, seu programa ficar preso em um loop infinito. Um programa preso em um loop infinito nunca vai
continue
similar ao
break
encontrado no corpo de um loop, o controle de execuo pula para fora do corpo e continua
counter += 1;
22
result *= 2
para dobrar o
result
ou
counter -= 1
Para
counter += 1
counter -= 1
counter++
counter--
switch
H um construtor chamado
switch
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
switch
switch
fornece, ou para
default
case
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"
break
break
cloudy
, 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
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 (
//
Um
// comentrio
/*
*/
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) {}
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.
console.log()
#
##
###
####
#####
######
#######
string
escrevendo
.length
aps ela.
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
FizzBuzz
25
+ = "#"
console.log()
Fizz
Fizz
Buzz
FizzBuzz
(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 (
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
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
Quando voc tiver o programa que gere este padro, defina a varivel
funcione para qualquer
size
size = 8
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 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
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.
square
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
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
power
square
return
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
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
no topo do exemplo:
29
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
flat
mountain
As funes
flat
mountain
result
count
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
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
var
No captulo 5, ns vamos discutir as coisas maravilhosas que podem ser feitas quando passamos valores de
funo para outras funes.
31
. A palavra-chave
function
tambm pode
function square(x) {
return x * x;
}
square
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");
console.log
greet
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
console.log
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
greet
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
com apenas um argumento, ela assumir o valor 2 para o expoente e a funo se comportar com um expoente
ao quadrado.
33
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
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
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
function
manipulador que possibilita executar instrues computacionais que foram "congeladas" para um uso posterior.
No exemplo,
multiplier
twice
.A
ltima linha do exemplo chama o valor armazenado nessa varivel, fazendo com que o cdigo "congelado" (
number * factor;
multiplier
number
factor
return
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
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
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
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
null
Para entender melhor como essa funo produz o resultado que estamos esperando, vamos analisar todas as
chamadas a
find
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!
(1 + 5)
(1 + 5)
(1 * 3)
find
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
porque aps duas chamadas recursivas acaba encontrando o nmero 13. Essa chamada recursiva mais interna
retorna uma
string
e cada operador
||
string
adiante, retornando
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
string
while
string
que representa o
printZeroPaddedWithLabel
38
zeroPad
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.
printZeroPaddedWithLabel
zeroPad
coincidncia que a segunda verso til em mais situaes do que a primeira. Funes que criam valores so
mais fceis de serem combinadas de diferentes maneiras do que funes que produzem efeitos colaterais
diretamente.
Uma funo "pura" um tipo especfico de funo que produz valores e que no gera efeitos colaterais, como
tambm no depende de efeitos colaterais de outros cdigos por exemplo, ela no utiliza variveis globais que
podem ser alteradas por outros cdigos. Uma funo pura tem a caracterstica de, ser sempre chamada com os
mesmos argumentos, produzir o mesmo valor (e no far nada alm disso). Isso acaba fazendo com que seja
fcil de entendermos como ela funciona. Uma chamada para tal funo pode ser mentalmente substituda pelo
seu resultado, sem alterar o significado do cdigo. Quando voc no tem certeza se uma funo pura est
funcionando corretamente, voc pode test-la simplesmente invocando-a. Sabendo que ela funciona nesse
contexto, funcionar em qualquer outro contexto. Funes que no so "puras" podem retornar valores diferentes
baseados em vrios tipos de fatores e produzem efeitos colaterais que podem fazer com que seja difcil de testar
e pensar sobre elas.
Mesmo assim, no h necessidade de se sentir mal ao escrever funes que no so "puras" ou comear uma
"guerra santa" para eliminar cdigos impuros. Efeitos colaterais so teis em algumas situaes. No existe uma
verso "pura" de
console.log
, por exemplo, e
console.log
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
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
min
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
40
if
else if
else
find
do exemplo recursivo
findSolution
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
string
"string".charAt(N)
, similar a como
). O primeiro caractere est na posio zero, o que faz com que o ltimo seja encontrado na
string.length -1
countBs
string
length
) dois, e
string
countChar
string
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
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
counter
41
var
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
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]
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
myString
myString.length
(para
max
no objeto
Math
length
do valor contido
(que um conjunto de
43
null
undefined
As duas formas mais comuns de acessar propriedades no JavaScript so usando ponto e colchetes. Ambos
value.x
value[x]
value
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]
value.x
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
toUpperCase
toLowerCase
que faz.
Curiosamente, mesmo que a chamada para
funo tem acesso string
"Doh"
toUpperCase
toUpperCase
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
pop
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
join
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
no exemplo anterior.
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"
46
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"
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
object3
object1
tambm altera o
==
do JavaScript ir retornar
true
false
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
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
"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 tornou um esquilo, mas o evento no ocorreu (por exemplo "pizza"). Isso aconteceu quatro vezes, e j que o
nmero binrio
10
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
Math
Math.sqrt
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
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
que procura pelo valor informado no array (nesse exemplo o nome do evento), e retorna o ndice onde ele
indexOf
encontrado.
O corpo do loop presente na funo
tableFor
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?
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
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
in
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
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
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.
push
pop
unshift
O programa anterior gerencia uma lista de tarefas. Voc pode adicionar tarefas no final da lista chamando
rememberTo("eat")
urgentlyRememberTo
whatIsNext()
para acessar
indexOf
lastIndexOf
Ambos
indexOf
lastIndexOf
slice
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
concat
slice
slice
pode ser usado para unir arrays, parecido com o que o operador
concat
slice
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"]
length
toUpperCase
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
indexOf
das strings pode receber uma string contendo mais de um caractere, enquanto
O mtodo
trim
remove todos os espaos vazios (espaos, linhas, tabs e caracteres similares) do comeo e do
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
O Objeto Arguments
Sempre que uma funo invocada, uma varivel especial chamada
arguments
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
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
arguments
console.log
. Essas funes
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");
squirrel
55
O Objeto Math
Como vimos anteriormente,
(mximo),
Math.max
O objeto
Math
Math.min
Math
(mnimo) e
Math.sqrt
(raiz quadrada).
usado como um container para agrupar uma srie de funcionalidades relacionadas. Existe
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
Math
max
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
(seno) e
tan
Math
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
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
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,
Math.ceil
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
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
value["propName"]
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
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
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
range
start
at o valor
end
(incio) e
start
end
(incluindo-o).
sum
range
tamanho do "incremento" usado para construir o array. Se nenhum valor for atribudo ao tamanho do incremento, o
array de elementos ser percorrido em incrementos de um, correspondendo ao comportamento original. A
chamada funo
range(1, 10, 2)
deve retornar
[1, 3, 5, 7, 9]
range(5, 2, -1)
produza
[5, 4, 3, 2]
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
[]
<=
<
Para verificar se o argumento opcional de incremento foi fornecido, voc pode verificar o
comparar o valor do argumento com
undefined
arguments.length
ou
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)
Invertendo um array
Os arrays possuem o mtodo
reverse
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
reverse
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
Dicas
Existem duas maneiras bvias de implementar
do incio ao fim e usar o mtodo
unshift
reverseArray
push
um pouco estranha (
).
reverseArrayInPlace
for
array.slice(0)
reverseArray
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
baixo, pois voc no precisa lidar com o elemento do meio de um array com tamanho mpar) e substituir o
elemento na posio
array.length - 1 - i
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
}
}
};
Uma das vantagens das listas que elas podem compartilhar partes de sua estrutura. Por exemplo, se eu
criasse dois novos valores
(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
prepend
listToArray
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
nth
Dicas
Construir uma lista mais fcil de ser feito de trs para frente. Portanto,
arrayToList
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}
listToArray
nth
), o seguinte loop
for
value
node
null
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
value
rest
da lista em questo.
Comparao "profunda"
O operador
==
compara objetos pelas suas identidades. Entretanto, algumas vezes, voc pode preferir comparar
deepEqual
true
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
typeof null
tambm produz
"object"
Dicas
O teste para saber se est lidando com um objeto real dever ser parecido com
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
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
true
61
false
sum
range
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
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
console
array
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
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
forEach
da funo.
var numbers = [1, 2, 3, 4, 5], sum = 0;
forEach(numbers, function(number) {
sum += number;
});
console.log(sum);
// 15
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
array
array
number
manualmente.
forEach
forEach
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
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
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
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
arg1
arg2
deixaria explcito quantos seriam suficientes. Essa soluo limita algumas informaes de
arguments.length
array
como
objeto
apply
. Voc passa um
array
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
. O primeiro argumento do
apply
, estamos passando
null
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
JSON
JSON
arrays
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
JSON.stringify
JSON.parse
JSON
string
O varivel
est disponvel na
ANCESTRY_FILE
do meu arquivo
como uma
JSON
string
sandbox
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", }, ]
array
test
test
retornado.
Trs pessoas no arquivo estavam vivas e jovens em 1924: meu av, minha av e minha tia-av.
Observe como a funo
filter
array
forEach
filter
um mtodo padro de
arrays
67
array
console.log(ancestry.filter(function(person) {
return person.father == "Carel Haverbeke";
}));
// [{name: "Carolus Haverbeke", }]
array
map
transforma um
array
de ancestrais
array
array
array
array
array
"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
map
arrays
arrays
exemplo atual, a soma do nosso intervalo de nmeros, um exemplo disso. Outro exemplo seria encontrar uma
pessoa com um ano de vida no conjunto de dados.
Uma operao de ordem superior que representa este padro chamada de reduce (diminui o tamanho do
array
). Voc pode pensar nisso como dobrar a matriz, um elemento por vez. Quando somado os nmeros, voc
inicia com o nmero zero e, para cada elemento, combina-o com a soma atual adicionando os dois.
Os parmetros para a funo
reduce
so, alm do
filter
map
array
68
array
padro do mtodo
reduce
array
contm apenas um elemento, voc no precisa enviar um valor inicial. O mtodo ir pegar o primeiro elemento do
array
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
plus
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
e produzir um novo
array
intermedirios
arrays
um pouco custoso.
Passar uma funo para
forEach
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
loop
loop
que realize
loop
P
vezes, onde
M x N x P
loop
loop
loop
loop
dentro
interno),
de fora se
externo. Se esse
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.
70
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
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
Podemos ento usar isso para calcular a quantidade de DNA que meu av compartilhou com Pauwels van
Haverbeke e depois dividir por quatro.
71
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
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
Binding
O mtodo
bind
, est presente em todas as funes, ele cria uma nova funo que chama a funo original mas
bind
string
. Ao chamar
filter
isInSet
cujos nomes esto em um conjunto especfico. Ns podemos escrever uma expresso de funo que faz a
72
chamada para
isInSet
isInSet
console.log(ancestry.filter(isInSet.bind(null, theSet)));
// same result
A chamada usando
bind
isInSet
com
theset
apply
null
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
array
filter
array
reduce
forEach
map
array
em
um valor nico.
Funes tm o mtodo
apply
bind
array
que usado para criar uma verso parcial da funo que foi aplicada.
Exerccios
Juntando
Use o mtodo
reduce
array
concat
para juntar um
array
de
arrays
em um nico
array
73
average
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 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
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
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
array
groupBy
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
74
Durante o processo de agrupamento, mantenha um objeto que associa os nomes dos sculos (nmeros) com
os
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
array
array
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
&&
true
predicada
retorna apenas
true
array
array
array
ou
false
. Assim como o
array
true
true
every
true
some
some
(todos) e
every
every
some
Dica:
As funes podem seguir um padro semelhante definio de
forEach
nica exceo que eles devem retornar imediatamente (com o valor direita) quando a funo predicada retorna
true
ou
false
array
75
return
aps o
loop
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
//
right now.'
apply
bind
this
para dar a sada do tipo de coelho que est falando. Lembrando que
apply
chamado
call
this
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]
78
apply
bind
,o
call
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
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
Object.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
Object.create
para
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
valor de outro objeto, esse novo objeto ser retornado a partir da chamada.
Um objeto criado com
new
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
Object.prototype
Coelho
, que
esse objeto assim como seu prototype. Ento, para adicionar um mtodo
construtor
prototype
fala
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
prototype
Function.prototype
, visto que os
height country
5895 Tanzania
Everest
8848 Nepal
Mount Fuji
3776 Japan
Mont Blanc
4808 Italy/France
Vaalserberg
323 Netherlands
Denali
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)
height
width
underscore uma forma de indicar (para leitores humanos) que este argumento no ser usado.
A funo
rowHeights
map
map
reduce
(assim como
rows
filter
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
81
colWidths
rows
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
drawRow
drawRow
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"]
["name", "----"]
Os blocos para uma linha, que devem todos ter a mesma largura, devem aparecer prximos um ao outro na sada
final. A segunda chamada a
map
em
drawRow
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
minWidth
82
repetido
times
repeat
draw
"));
}
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
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
height country
//
//
Kilimanjaro
//
etcetera
A funo padro
5895
Tanzania
Object.keys
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
minHeight
minWidth
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
get
ou
set
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
set
, no objeto passado
defineProperty
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
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;
};
TextCell
minHeight
draw
minWidth
de
TextCell
. Um
RTextCell
agora basicamente
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
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
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
deriva de
TextCell.prototype
RTextCell
uma instncia de
TextCell
porque
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
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
instanceof
Exerccios
87
Um tipo de vetor
Escreva um construtor
parmetros
D ao prottipo de
Vector
dois mtodos,
plus
minus
this
eo
passado no parmetro).
Adicione uma propriedade getter
(
x, y
length
) at a origem (0,0).
Dicas
Sua soluo pode seguir o padro do construtor
Rabbit
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
distncia que estamos procurando igual ao quadrado da coordenada x mais o quadrado da coordenada y.
Assim,
(x2 + y2)
Math.sqrt
JavaScript.
Outra clula
Implemente uma clula do tipo
descrita anteriormente neste captulo. Ela deve envolver outra clula (como
clula resultante tem pelo menos a largura (
width
) e altura (
height
UnderlinedCell
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
draw
inner
minWidth
Math.max
).
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
para seus primeiros 5 elementos - ou menos, se a sequncia tiver menos do que cinco
elementos.
Ento implemente um tipo de objeto
ArraySeq
from
to
RangeSeq
em seu construtor).
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
Definio
Para tornar esta tarefa gerencivel, vamos simplificar radicalmente o conceito de um mundo. Ou seja, um mundo
ser uma
grid
grid
Strings
grid
do mundo usando um
##",
"#
#",
"#
#####
"##
"###
"#
##
#",
##
#",
#",
###
"#
####
"#
##
"# o
"#
#",
#",
o
#",
o
### #",
#",
"############################"];
Os caracteres
"#"
"O"
representam os bichos. Os
toString
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);
};
grid
.A
grid
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
grid
array
de
arrays
tendo duas
"top middle",
"top 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.
"top middle",
"top 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
grid
91
"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
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
offsets
var directions = {
"n":
new Vector( 1,
0),
1),
"s":
new Vector( 0,
1),
1),
"w":
0),
new Vector(-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
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
uma criatura sentada esquerda(oeste) de um muro vai ter ["ne", "e", "se"] ao chamar
findAll
passando o
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
Math.random
para obter um ndice aleatrio. Vamos usar isso de novo mais tarde porque a aleatoriedade pode ser til em
simulaes.
Para escolher uma direo aleatria o construtor de
BouncingCritter
chama
Object.keys
randomElement
em uma matriz de
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"
this.direction
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
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
originChar
originChar
toString
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.
//
//
#####
//
##
//
###
//
//
####
//
##
//
# o
//
//
############################
This
##
#
#
##
##
###
#
o
#
o
### #
#
e seu escopo
forEach
forEach
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
loop
. Podemos criar
Isso um erro de
design
este problema. Enquanto isso existem solues alternativas. Um padro comum dizer
uma
bind
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
test
somado ao resultado do
forEach
map
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
grid
, chamaremos uma
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
. Quando ele encontra um ele chama o mtodo para obter uma ao e realiza a ao quando ela for
"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);
};
forEach
grid
para
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"
"move"
bicho na prximo
Perceba que
quadrado
letAct
null
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(
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);
};
grid
grid
e se o
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
animateWorld
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
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
dirPlus
significa 45 graus no sentido horrio para norte quando retornar "ne". Da mesma forma
dirPlus("n", 1)
dirPlus("s", -2)
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"
if
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
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
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
letAct
actionTypes
O novo mtodo
letAct
verifica primeiro se uma ao foi devolvido, ento se a funo manipuladora para este tipo
true
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"
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;
};
checkDestination
. Se no vlido, se o destino
false
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
Reproduzir custa duas vezes mais o nvel de energia de um bicho recm-nascido. Ento primeiro criamos o beb
(hipoteticamente) usando
elementFromChar
podemos encontrar o seu nvel de energia e testar se o pai tem energia suficiente para traz-lo com sucesso no
100
grid
pai.
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};
};
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
######",
"##
***
"#
*##**
"#
***
"#
**##",
**
O
O
"#
"#
#**
"#***
##**
*##",
##**
*#",
##***
#",
##**
#",
#*
"#*
"##****
#",
O
O
###***
#",
**#",
*###",
"############################"],
{"#": Wall,
"O": PlantEater,
"*": Plant}
);
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
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
######",
"##
***
"#
*##**
"#
***
"#
**##",
**
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
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
####
"#
"#
##*
"#
##***
"#* **
"#* **
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
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
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"
function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}
canYouSpotTheProblem();
// ReferenceError: counter is not defined
var
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
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
Person
foi bem sucedida, mas retornou um valor indefinido e criou uma varivel
"use strict"
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);
};
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
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
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
adicionais sobre o que o programa est fazendo. Neste caso queremos tomar os
valores de 13, 1 at 0.
1.5e-323
n / = base
console.log
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
Isto uma boa estratgia. Agora qualquer cdigo que chamar a funo
promptNumber
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
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
stack
stack
stack
de uma forma ampliada no seria muito til. Eles apenas fornecem uma
nova maneira de explodir o seu programa. Seu poder reside no fato de que voc pode definir "obstculos" ao
longo do seu
stack
para capturar a exceo. Depois voc pode fazer alguma coisa com ele no ponto em que a
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
catch
try
catch
catch
ou do bloco
try
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
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
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.
withContext
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
withContext
exceo, e o contexto nunca ser definido de volta para o seu valor antigo.
110
ser exibido no
stack
pela
try
finally
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
context
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
withContext
explodiu,
withContext
context
Captura seletiva
Quando uma exceo percorre todo o caminho at o final do
stack
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
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
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
for (;;) {
try {
var dir = promtDirection("Where?"); // typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}
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
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
Error.prototype
para que
instanceof Error
retorne
Error
true
para objetos de
SyntaxError
ReferenceError
stack
stacktrace
em plataformas que suportam a criao de um objeto de erro regular pode usar a propriedade de
para si prprio.
Agora
promptDirection
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);
}
InputError
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
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
catch
try/catch
at a parte inferior do
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
MultiplicatorUnitFailure
MultiplicatorUnitFailure
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
MultiplicatorUnitFailure
loop
try
. O bloco
MultiplicatorUnitFailure
catch
fica responsvel
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
A caixa trancada
Considere o seguinte objeto:
114
stack
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;
}
};
array
_content
withBoxUnlocked
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
withBoxUnlocked
finally
destravar a caixa e em seguida chamar a funo que vem de argumento dentro da funo
finally
. E no
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.
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.
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
- \s
- \D
- \W
caracteres no alfanumricos
- \S
- . (ponto)
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
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
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)
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)
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
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
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)
while
match =
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
Sequncia de caracteres
/[abc]/
/[^abc]/
/[0-9]/
/x+/
/x+?/
/x*/
/x?/
/x{2,4}/
/(abc)+/
/a|b|c/
/\d/
Caracteres dgitos
/\w/
/\s/
/./
/\b/
/^/
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"]);
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
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.
de um objeto date.
A funo
dayName
names
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
dayName
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
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.
dayNumber
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
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
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.
string
eval
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
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
exports
module
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
carregarem algo em algum servidor web distante, a pgina vai ficar congelada por um doloroso longo
require
, e colocando-
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
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); }
};
});
define
, que pega o nome do arquivo e uma funo, e vai chamar a funo com o contedo do arquivo
require
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
139
mdulo cacheado, ns usamos o objeto existente. Caso contrrio, ns criamos um novo objeto (com o valor de
loaded
funo
igual a
false
backgroundReadFile
construtor
Function
. Uma vez que o arquivo tenha sido carregado, seu contedo rodado usando o
define
. A propriedade
usada para informar a esta chamada sobre o mdulo objeto que est sendo carregado
runIfDepsLoaded
necessrio carregar nenhuma dependncia) e uma vez para cada dependncia que termina seu carregamento.
Quando todas as dependncias esto l, ns chamamos
moduleFunction
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
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
define
Exerccios
Nomes dos meses
141
Date
weekDay
) 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
exports
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
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.
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
grid
) no
randomElement
Wall
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
String
ou
while
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
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
"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
argumento.
A parte
>(x, 5)
{
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
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
parseExpression
String
Number
words
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
parseApply
parseExpression
parseApply
parseExpression
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",
//
//
//
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
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
environment
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.
environment
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
?:
if/else
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
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.
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
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).
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
booleanos
topEnv
booleanos
booleano
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
muito til fazer uma maneira para que valores de sada sejam vizualidos, por isso vamos colocar alguns
console.log
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
analisa e avalia as
String
enviroment
em tempo real,
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
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),",
"
"
"
// 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
ltimo como nomes de argumentos da funo e seu ltimo argumento como corpo da funo.
150
enviroment
prototype
Object.create
para fazer
especialForm
fun
print(plusOne(10)))");
// 11
run("do(define(pow, fun(base, exp,",
"
if(==(exp, 0),",
"
1,",
"
"
// 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
raw
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
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
element(array, n)
buscar
152
topEnv
do escopo:
length(array)
array(...)
vai ser a
do(define(i, 0),",
"
define(sum, 0),",
"
while(<(i, length(array)),",
"
"
"
"
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
Array.prototype.slice
Resoluo
Closures
A maneira que definimos o
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
fun
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
enviroment
prototype
enviroment
do
env
enviroment
local ser o
enviroment
enviroment
finalizar(embora para compil-lo de uma forma que realmente eficiente, voc precisa de um pouco mais de
trabalho).
Comentrios
153
Seria bom se pudssemos escrever comentrios no Egg. Por exemplo, sempre que encontrar um cardinal
poderamos tratar o resto da linha como um comentrio e ignor-lo, semelhante que Javascript faz com o
("#")
"//"
No temos de fazer quaisquer grandes mudanas para que o analisador suporte isto. Ns podemos
simplesmente mudar o
skipSpace
para ignorar comentrios assim como feito com os espaos em branco; para
skipSpace
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",
//
//
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
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
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
escopo exterior se ele ainda no existir no mbito interno. Se a varivel no definida em tudo lanar um
ReferenceError
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
hasOwnProperty
Object.prototype
que retorna os
, por isso, se voc
Object.prototype.hasOwnProperty.call(scope, name);
Este mtodo(
hasOwnProperty
154
"
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
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
155
null
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
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>
body
head
(cabea) e um
<h2>
body
(corpo). O
head
contm o documento em si. Neste caso, ns primeiro declaramos que o ttulo do documento
<h6>
body
contendo um cabealho (
<p>
<a>
<body>
<h1>
<p>
).
</p>
nome="valor"
. Estes so chamados
href="http://eloquentjavascript.net"
, onde
href
<img src="http://example.com/image.jpg">
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
<
>
("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
&
"
&
158
As tags
<h1>
<html>
<head>
pertence ao
body
<body>
<title>
pertence ao
head
, e que
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>
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>
JavaScript em um documento.
<h1>Testing alert</h1>
<script>alert("hello!");</script>
<script>
src
<script>
pode receber um
a fim de buscar um arquivo de script (um arquivo de texto contendo um programa em JavaScript) a
O arquivo
code/hello.js
alert("hello!")
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>
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>
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
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.
"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
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>
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
documentElement
head
body
se refere ao objeto
, alocando objetos
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
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
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
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
previousSibling
nextSibling
null
so ns com o mesmo pai que aparecem imediatamente antes ou depois do n em questo. No caso de
usarmos a propriedade
previousSibling
nextSibling
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
}
console.log(talksAbout(document.body, "book"));
// true
A propriedade
nodeValue
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
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
href
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);
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
getElementsByClassName
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
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
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
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
createTextNode
getElementsByTagName
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
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
elt
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
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
data-
168
data-language
<pre>
(tag
e tenta
A funo
highlightCode
pega um n
<pre>
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>
Ns podemos sublinhar automaticamente todos os cdigos de programas na pgina fazendo um looping entre
todos os elementos
<pre>
data-language
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>
class
className
setAttribute
Layout
169
class
Voc provavelmente notou que tipos diferentes de elementos so dispostos de maneiras diferentes. Alguns, como
pargrafos (
<p>
) ou cabealhos (
<h1>
<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
getBoundingClientRect
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
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
<a>
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
Um atributo
style
green
color
color: red;border:none
"
Existem muitos aspectos que podem ser influenciados atravs dessa estilizao. Por exemplo, a propriedade
display
A tag
block
vai acabar em sua prpria linha, pois elementos em blocos no so mostrados em linha com texto ao
display: none
maneira de esconder elementos e comumente prefervel remov-los por completo do documento, tornando
mais fcil revel-los mais tarde.
171
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"]
style.fontFamily
style
style["font-
).
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
<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
style
, conflitando com
aplicados
id
172
#xyz
.abc
aplica-se para
.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
ou apenas um
.a
p > a {...}
mesmo modo,
p a {...}
<a>
<a>
dentro de tags
<p>
<p>
. Do
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
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"));
// 4
console.log(count(".animal"));
// Classe animal
// 2
console.log(count("p .animal"));
// 2
console.log(count("p > .animal")); // Filhos diretos de <p>
// 1
</script>
getElementsByTagName
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
nico e especfico elemento. Ele ir retornar apenas o primeiro elemento coincidente com a busca ou
null
se
Posicionando e Animando
A propriedade de estilo
position
static
position
top
definida como
left
relative
absolute
top
left
para posicion-lo de maneira absoluta em relao ao canto superior esquerdo do elemento fechado mais
prximo cuja propriedade
position
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>
requestAnimationFrame
top
left
animate
. Ns vamos atualizar
relative
animate
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
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
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
Math.sin
maiores que 2 ou abaixo de 0 so vlidosa rotao se repete, de modo que a+2 refere-se ao mesmo ngulo
que a
angle
animate
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
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
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
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>
<table>
<th>
<tr>
) ou clulas comuns (
<td>
).
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>
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
right
".
<style>
/* Define uma visualizao mais limpa para tabelas */
table
{ border-collapse: collapse; }
{ text-align: left; }
</style>
<script>
function buildTable(data) {
// Seu cdigo aqui.
}
document.body.appendChild(buildTable(MOUNTAINS));
</script>
Dicas
Use
document.createElement
mtodo
appendChild
document.createTextNode
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>
176
O mtodo
getElementsByTagName
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
ou
toLowerCase
toUpperCase
Dicas
A soluo expressada mais facilmente com uma funo recursiva, similar funo
talksAbout
definida
byTagname
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
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
esquerdo do documento. Para evidar usar coordenadas negativas, voc pode simplesmente adicionar um
nmero fixo de pixels para os valores das posies.
177
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 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
window
window
no
179
<button>Click me</button>
<p>No handler here.</p>
<script>
var button = document.querySelector("button");
button.addEventListener("click", function() {
console.log("Button clicked.");
});
</script>
O exemplo atribuiu um manipulador para um n de boto. Assim quando existir um clique no boto o manipulador
sera executado, enquanto no resto do documento no.
Dar a um n um atributo
onclick
onclick
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
addEventListener
, mas ele
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>
"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
stopPropagation
manipuladores mais acima possam receberem o evento. Isso pode ser til quando por exemplo, se voc tem um
boto dentro de outro elemento clicvel e voc no quer o clique no boto acontea se houver algum
compartamento de clique no elemento exterior.
O exemplo a seguir registra manipuladores
"mousedown"
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"
emitido.
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
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
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
Os eventos de
"keydown"
"keyup"
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
"keypress"
"keydown"
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
String.fromCharCode
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
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
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"
acontecer no DOM que esto abaixo do ponteiro do mouse quando o evento ocorrer.
Aps o evento de
"mouseup"
um evento
"click"
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
Para obter informaes precisas sobre o local onde aconteceu um evento do mouse voc pode olhar para as
suas propriedades
pageX
pageY
As propriedades
clientX
clientY
so semelhantes aos
pageX
pageY
que est sendo rolado. Estes podem ser teis quando se compara a coordena do mouse com as coordenadas
retornados por
getBoundingClientRect
viewport
Movimento do mouse
Toda vez que o ponteiro do mouse se move, um eventos de
"mousemove"
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
"mousemove"
registrado no
window
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"
"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"
"mouseout"
apenas quando o
"mouseover"
relatedTarget
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
185
hover
A funo
isInside
percorre os links pai do n at ele atingir o topo do documento(quando nulo) ou encontrar o pai
: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"
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
pageYOffset
innerWidth
que acompanha o
innerHeight
.)
a posio de rolagem atual menos posio de deslocamento mximo multiplicando por 100
186
preventDefault
Evento de foco
Quando um elemento entra em foco o navegador dispara um evento de
eventos de
"blur"
"focus"
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"
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>
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
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
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
<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
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
worker
que envia
A funo
postMessage
que criou o
worker
worker
Worker
"message"
para o script que o criou enviado e ouvido diretamente sobre o seu mbito global no compartilhada-se
Definindo temporizadores
A funo
requestAnimationFrame
similar
setTimeout
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
A funo
cancelAnimationFrame
requestAnimationFrame
frame
clearTimeout
setInterval
clearInterval
timers
que devem
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
debouncing
de evento. H vrias
189
clearTimeout
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
Sumrio
Os manipuladores de eventos tornam possvel detectar e reagir sobre eventos que no tm controle direto. O
mtodo
addEventListener
"keydown"
"focus"
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.
190
) ou
"mousemove"
window
"focus"
"blur"
"scroll"
"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">
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"
preventDefault
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
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
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>
"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
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
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
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
Level
var simpleLevelPlan = [
"
",
"
",
"
= x
"
",
o o
"
x @
",
xxxxx
"
xxxxx
",
",
"
x!!!!!!!!!!!!x
",
"
xxxxxxxxxxxxxx
",
"
"
];
espao vazio
e os
representam paredes, os
mechem.
O
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
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
Level
Level
A leitura de um level
O construtor a seguir cria um objeto de
Level
Level
function Level(plan) {
this.width = plan[0].length;
this.height = plan.length;
this.grid = [];
this.actors = [];
for (var y = 0; y < this.height; y++) {
var line = plan[y], gridLine = [];
for (var x = 0; x < this.width; x++) {
var ch = line[x], fieldType = null;
var Actor = actorChars[ch];
if (Actor)
this.actors.push(new Actor(new Vector(x, y), ch));
else if (ch == "x")
fieldType = "wall";
else if (ch == "!")
fieldType = "lava";
gridLine.push(fieldType);
}
this.grid.push(gridLine);
}
this.player = this.actors.filter(function(actor) {
return actor.type == "player";
})[0];
this.status = this.finishDelay = null;
}
Para deixar o cdigo pequeno, no verificamos entradas erradas. Ele assume que voc sempre entrega um plano
de level adequado, completo, com a posio de incio do jogador e com outros itens essenciais.
Um level armazena a sua largura e altura juntamente com duas matrizes, uma para a grade e um para os agentes
que so os elementos dinmicos. A grade representada como uma matriz de matrizes onde cada uma das
sries internas representam uma linha horizontal, e cada quadrado contm algo ou nulo; para as casas vazias,
ou uma string, indicaremos o tipo do quadrado ("muro" ou "lava").
A matriz contm objetos que rastreiam a posio atual e estado dos elementos dinmicos no level. Cada um
deles dever ter uma propriedade para indicar sua posio (as coordenadas do seu canto superior esquerdo),
uma propriedade
size
type
grid
finishDelay
level
. A propriedade
status
Level
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
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
Level
Lava
. O construtor
Lava
Level
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
Lava
gotejamento
repeatPos
). 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
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";
Math.sin
nos d a coordenada
um vai e vem em forma de onda suave medida que avanamos o crculo, fazendo a funo
seno
se tornar til
Math.random
Math.Sin
forma aleatria.
Agora escrevemos todas as peas necessrias para representar o
Level
nesse estado.
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
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
appendChild
Level
, que nunca muda, desenhado apenas uma vez. Os atores so redesenhados toda vez que o
actorLayer
grid
grid
, onde o tamanho
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
grid
<table>
<td>
<tr>
). As cordas na
.background td { padding: 0;
.lava
.wall
{ background: white;
Alguns deles (
table-layout
border-spacing
padding
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
write
padding
R, G, B
166 e azul 251. Como o componente azul maior, a cor resultante ser azulada. Voc pode ver que na regra das
lavas
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
drawFrame
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();
};
Level
wrapper
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
Level
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
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
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
DOMDisplay.prototype.clear = function() {
this.wrap.parentNode.removeChild(this.wrap);
};
Level
atualmente.
A tag
<link>
pgina. O arquivo
game.css
rel="stylesheet"
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:
body
body
se sobrepe usando
Math.floor
Math.ceil
nas
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
"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
key
level
step
traz
Quando a propriedade
status
do
level
finishDelay
while
corta o passo de tempo onde estamos animando em pedaos pequenos. Ele garante que nenhum
maxStep
tomado. Por exemplo um passo de 0,12 segundo iria ser cortado em dois passos
act
level
que
contm as chaves de objeto. Aqui est um exemplo para o tipo de ator (Lava) que ignora as teclas de objeto:
204
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
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
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
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
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
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
"lost"
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
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
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
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
requestAnimationFrame
Vamos definir uma funo auxiliar que envolve as partes chatas em uma interface conveniente e nos permitir
simplesmente chamar
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
lasttime
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
terminado(perda ou ganho),
Level
Level
Level
est
andthen
for dada,
Level
Level
Level
Level
Level
208
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
runGame
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
Dica
A soluo mais bvia seria, tornar a vida uma varivel que vive em
encerramento do
startLevel
runGame
Uma outra abordagem que se encaixa com o esprito do resto da funo seria, adicionar um segundo parmetro
para o
startLevel
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
Level
Level
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
runAnimation
maneira que
RUNLEVEL
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
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
Level
seus tratadores quando comea e cancela o registro novamente quando ele for concludo.
210
para registrar
Dicas
Uma animao pode ser interrompida retornando um valor
continuado chamando
runAnimation
falso
na funo dada ao
runAnimation
novamente.
Para comunicar que a animao deve ser interrompido a funo passada para
runAnimation
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
para remover com xito um manipulador. Assim o valor da funo manipuladora criada em
211
trackKeys
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>
canvas
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
canvas
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
SVG
Este livro no vai entrar no assunto
captulo eu vou voltar para os
SVG
trade-offs
O atributo
xmlns
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>
212
O elemento canvas
Grfico em canvas pode ser desenhado com a tag
<canvas>
<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
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
lineWidth
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
paths
path
path
moveTo
Ao preencher um
lineTo
moveTo
path
path
(usando o mtodo
fill
moveTo
path
path
path
pode conter
path
<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
closePath
path
atravs da adio de um
214
path
Curvas
Um
path
tambm pode conter linhas com curvas. Estes infelizmente um pouco mais complexo do que
quadraticCurveTo
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
arcTo
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
arcTo
arcTo
fornece uma maneira mais simples. preciso um par de coordenadas para o centro do arco, um
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
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>
arc
) a esquerda do quarto do
path
ligado ao segmento do arco anterior por padro. Se voc quiser evitar isso teria que chamar
novo
path
moveTo
ou iniciar um
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
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
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
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
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
<img>
<img>
ou
<canvas>
canvas
imagem no ocorreu porque o browser ainda no buscou por isso. Para lidar com tal situao registramos um
manipulador de eventos(
"load"
<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
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
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
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
drawImage
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
rotation
canvas
. 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
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).
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
lanado e o tringulo volta para a posio 3. Este no o lugar onde ele deveria estar se fosse
translate
translate
inicial
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>
canvas
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.
canvas
canvas
drawImage
do jogo.
Vamos definir um tipo de objeto
15, ou seja os mtodos
CanvasDisplay
drawFrame
clear
DOMDisplay
a partir do captulo
DOMDisplay
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
O contador
DOMDisplay
animationTime
drawFrame
drawFrame
no Captulo 15 embora
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
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.
Math.max(x, 0)
Math.min
da
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
0, scale, scale,
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.
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
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
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
O mtodo
drawPlayer
chamado por
drawActors
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
level
. Ns
display
em
runGame
<body>
<script>
runGame(GAME_LEVELS, CanvasDisplay);
</script>
</body>
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
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
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
getContext
strokeStyle
lineWidth
fillStyle
do contexto
controlam a forma de
beginPath
para o
path
fillText
path
strokeText
fillRect
inicia um novo caminho. Uma srie de outros mtodos podem adicionar linhas e curvas
lineTo
fill
pode adicionar uma linha reta. Quando um caminho terminado ele pode
stroke
canvas
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
restore
.
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
Math.cos
Math.sin
do captulo 13 que
Dicas
O trapzio(1) fcil desenhar usando um
path
ratation
flipHorizontally
deve utilizar o
. Para usar
rotation
path
. 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 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
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
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
Dicas
Voc vai precisar chamar
fillText
, definir
textAlign
textBaseline
120 pixels
para centro:
Para
textBaseline
textAlign
"rigth"
,e
o valor
"middle"
"center"
Se voc no tem certeza de como descobrir qual lado do crculo um determinado ngulo esta, olhe para a
explicao de
Math.cos
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
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
transformation
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
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
<canvas>
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
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
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
DELETE
GET
PUT
para substitu-lo e
POST
Note que o servidor no obrigado a processar todas as requisies que receber. Se voc acessar um website
aleatrio e fizer uma requisio
DELETE
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
HTTP/1.1
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
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
Navegadores e o HTTP
Como vimos no exemplo anterior, o navegador ir fazer uma requisio quando submetermos uma URL na barra
de endereos. Quando a pgina HTML resultante faz referncias a outros arquivos como imagens e arquivos
JavaScript, eles tambm so requisitados.
Um website razoavelmente complicado pode facilmente ter algo em torno de dez a duzentos recursos. Para ser
capaz de busc-los rapidamente, ao invs de esperar pelo retorno das respostas de cada requisio feita, os
navegadores fazem vrias requisies simultaneamente. Tais documentos so sempre requisitados usando
requisies
GET
Pginas HTML podem incluir formulrios, que permitem ao usurio preencher e enviar informaes para o
servidor. Esse um exemplo de um formulrio:
<form method="GET" action="example/message.html">
<p>Name: <input type="text" name="name"></p>
<p>Message:<br><textarea name="message"></textarea></p>
<p><button type="submit">Send</button></p>
</form>
232
Esse cdigo descreve um formulrio com dois campos: um campo menor que solicita um nome e um campo
maior que solicita que o usurio escreva uma mensagem. Quando voc clicar no boto Send (enviar), a
informao contida nos campos sero convertidas em uma query string. Quando o mtodo do atributo do
elemento
<form>
for
GET
GET
action
eo
O incio de uma query string indicado por um ponto de interrogao seguido por pares de nomes e valores,
correspondendo ao atributo
name
&
%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
POST
POST
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
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
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.
open
send
XMLHttpRequest
O mtodo
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
open
for
ir retornar apenas depois que a resposta da nossa requisio for recebida. Podemos ler
responseText
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
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
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
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
open
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.
requestAnimationFrame
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.
XMLHttpRequest
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
se refere tag mais externa do documento XML. No documento a seguir (example/fruit.xml), essa
<fruits>
235
<fruits>
<fruit name="banana" color="yellow"/>
<fruit name="lemon" color="yellow"/>
<fruit name="cherry" color="red"/>
</fruits>
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
XMLHttpRequest
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
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
POST
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
try
e a funo que
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
"error"
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.
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
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
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.
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
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
then
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.
then
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
then
getJSON
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>
catch
similar ao
then
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
then
try
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
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
simplesmente tal documento. Um recurso pode ser acessado por meio de uma requisio
recurso (por exemplo,
/user/larry
GET
para a URL do
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
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
Accept
application/rainbows+unicorns
setRequestHeader
do seu
send
Dicas:
Veja os vrios exemplos que usam
XMLHttpRequest
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
all
all
sucesso, gerando um array com os valores dos resultados. Se qualquer uma das promises do array falhar, a
promise retornada pelo
all
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
then
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
Campos
Um formulrio web consiste em qualquer nmero de campos
input
<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>
comumente usados:
text
password
checkbox
Um modificador on/off
radio
file
<form>
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
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>
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
<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>
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
Campos desativados
Todos o campos dos formulrios podem ser desabilitados por meio do seu atributo
disabled
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.
<form>
<form>
<form>
que
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>
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>
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
Campos de Texto
Campos criados pela tag
<input>
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
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>
que, quando voc pressionar F2, a string "Khasekhemwy" inserida para voc.
< textarea > < / textarea >
247
A funo
replaceSelection
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 >
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
O mtodo
document.getElementsByName
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>
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>
<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
multiple
option
option
multiple
significa muito, uma vez que vai possuir o valor apenas uma das opes escolhidas no momento.
As tags
<option>
de um campo
<select>
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
A propriedade
files
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
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.
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>
Notes:
<select id="list"></select>
<button onclick="addNote()">new</button><br>
<textarea id="currentnote" style="width: 100%; height: 10em">
</textarea>
252
<select>
notes
notes
localStorage
localStorage
||
ser nulo.
pode ser
saveToStorage
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
esquecido no fim de cada sesso, o que para a maioria dos navegadores significa
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>
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
click
de texto e chamada
nele.
new Function
new function
mousedown
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>
input
indexOf
para as sugestes
<div>
click
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.
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>
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>
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
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
fillStyle
strokeStyle
257
lineWidth
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
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
DOM que dado como argumento. Porque ns queremos construir nosso programa pedao por pedao,
definimos um objeto chamado
controls
258
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
fillStyle
<div>
lineWidth
).
Ferramenta de seleo
O primeiro controle que ns adicionamos o elemento
<select>
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);
};
mousedown
no elemento
canvas
<option>
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
da tela a partir deles para obter uma posio em relao a esse canto.
259
trackDrag
mousemove
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);
};
lineCap
para
round
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
segmento de linha entre o boto do mouse apertado e a nova posio, usando qualquer
strokeStyle
lineWidth
onEnd
trackDrag
. O caminho
normal para executar as ferramentas no vai passar o terceiro argumento, portando, quando usar a ferramenta
linha, esse argumento mantm
undefined
apagar
cdigo adicional.
260
A propriedade
globalCompositeOperation
desenho altera a cor dos pixels que tocam. Por padro, o valor da propriedade
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
strokeStyle
lineWidth
url
number
<input type='color'>
. Outros incluem
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);
};
fillStyle
strokeStyle
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);
};
array
lineWidth
seja
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
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
canvas
toDataURL
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
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
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
acontece, ns definimos o link para apontar para outro tipo de URL, usando o
SecurityError
javascript
canvas
ficou
. Quando isso
simplesmente executam o script dado aps os dois pontos para que o link ir mostrar uma janela de
alert
canvas
por ela.
canvas
canvas
readAsText
canvas
FileReader
fillStyle
lineWidth
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,
Enter
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
264
setInterval
trackDrag
currentPos
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
Retngulo
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
seja liberado?
Se nada lhe vem a mente, relembre
position: absolute
pagex
pageY
left
top
width
height
para
Dicas
Voc pode utilizar
relativePos
trackDrag
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
height
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
<div>
e definir seu
style.position
document.body
px
como
absolute
. Ao definir os estilos de
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
width
height
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
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
rgb (R, G, B)
estilo que
getImageData
toDataURL
try/catch
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
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
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
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
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.
readfile
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
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
$ node ola.js
Ol mundo
271
node
O mtodo
console.log
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
A varivel
process
console
node
exit
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
Array
Math
and
window
JSON
document
alert
esto ausentes.
global
mais sentido.
Mdulos
Alm de algumas variveis que mencionei, como
console
process
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
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
do arquivo
. A extenso
require
/home/braziljs/elife/run.js
.js
node_modules
node_modules/elife
. Por exemplo,
require("elife")
require(fs)
main.js
require
, que define um script que pode ser chamado da linha de comando para alterar uma string.
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("");
}
module.exports
especfico do mdulo. Nesse caso, ns fizemos com que o resultado ao requerer nosso arquivo
garble
seja a
figlet
desenhos feitos de caracteres de texto. O trecho a seguir mostra como instalar e usar esse mdulo:
273
_ _
_
__
| |_| |/ _ \ | |/ _ \
\ \ /\ / / _ \| '__| |/ _` | |
__/ | | (_) |
|_| |_|\___|_|_|\___/
Depois de rodar
_____
_ _
| | | | ___| | | ___
\ V
V / (_) | |
| | (_| |_|
\_/\_/ \___/|_|
|_|\__,_(_)
npm install
_ __| | __| | |
figlet
node_modules
text
node
e chamamos
para desenhar
figlet.text
callb ack que passa o resultado para ela. Ele tambm passa outro parmetro no callb ack,
tm uma funo de
error
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
que contm as formas das letras. Lendo esse arquivo do disco uma operao assncrona no Node, ento
figlet.text
funo que chamar uma funo assincronamente precisa se tornar assncrona tambm.
Existem muito mais coisas no NPM alm de
npm install
package,json
, que contm
informaes codificadas em JSON sobre o programa ou biblioteca, como por exemplo outras bibliotecas que
depende. Rodar
npm install
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.
"fs"
readFile
274
readFile
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
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.");
});
Buffer
writeFile
"fs"
um array de strings,
readdir
stat
rename
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"
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
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
url
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
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
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"
request
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
response
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
GET
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
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
fs.createWritebleStram
. Ento
no objeto resultante para escrever o arquivo pea por pea, ao invs de escrever
fs.writeFile
response
request
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
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"
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
toString
Buffer
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
O exemplo escreve no
usar
console.log
process.stdout
. Ns no podemos usar
console.log
GET
PUT
DELETE
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/
(ou
C:\Users\braziljs\public\
/home/braziljs/public/file.txt
( ou
no Windows), ento a
C:\Users\braziljs\public\file.txt
methods
).
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
para encaminhar uma stream de leitura para uma stream de escrita. Caso contrrio, assumimos que o corpo ser
null
end
urlToPath
da resposta.
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
diretrio atual.
function urlToPath(url) {
var path = require("url").parse(url).pathname;
return "." + decodeURIComponent(path);
}
urlToPath
GET
Content-Type
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
npm
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.
279
Como ele pode levar um bom tempo para encontrar o arquivo no disco,
no existe,
fs.stat
"ENOENT"
fs.stat
callb ack. Isso seria muito bom se o Node definisse diferentes subtipos de
Error
code
para o seu
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
mtime
fs.readdir
isDirectory
resultado para o usurio. Para arquivos comuns, ns criamos uma stream de leitura com o
passamos ela ao
respond
"mime"
fs.createReadStream
arquivo.
O cdigo que trata as requisies de
DELETE
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
Aqui, ns no precisamos checar se o arquivo existe - se ele existe, ns simplesmente sobrescrevemos ele.
Novamente ns usamos
pipe
"finish"
pipe
"error"
disparado e reportado na
curl
requisies HTTP. A sesso a seguir um rpido teste do nosso servidor. Note que
escolher o mtodo da requisio e
-d
-X
$ 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
file.txt
PUT
cria o
arquivo, para que ento a prxima requisio consiga encontr-lo com sucesso. Depois de deletar o arquivo com
uma requisio
DELETE
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
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
"promise"
fs.readFile
do NPM contm
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
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
null
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
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
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
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
end
http.request
requisio.
O objeto de resposta passado ao callb ack da
http.request
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);
});
}
/home/braziljs/public/
O que aconteceu?
Se ainda no est claro para voc, pense novamente na funo
urlToPath
function urlToPath(url) {
var path = require("url").parse(url).pathname;
return "." + decodeURIComponent(path);
}
"fs"
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
replace
na expresso, ou o
replace
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
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
fs.rmdir
), o servidor de arquivos no
MKCOL
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
fs.mkdir
MKCOL
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.
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
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
, 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"
PUT
<form>
e um evento
"submit"
ou um
<textarea>
no
<option>
<select>
"change"
GET
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
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
long polling
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
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
HTTP
long polling
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
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
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
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
/talks
JSON
HTTP
/talks
Paths
que no
sero usado para servir arquivos estticos como: cdigo HTML e JavaScript que sero
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
PUT
PUT
long polling
para a
URL
JSON
string
encodeURIComponent
ao construir a URL.
GET
JSON
de uma palestra ou
com um objeto
JSON
POST
para uma
URL
solicitao.
288
long polling
changesSince
precisamos de pedidos
GET
para
/talks
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()
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
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
proxy
HTTP
HTTP
que
pode ser configurado para exigir um nome de usurio e senha, voc pode ter certeza de que
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
HTTP
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
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
/^\/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
NPM
princpio.
Este
router.js
add
Router
. Um objeto de
Router
booleano
resolve
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
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
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
root
onde ele devera procurar pelos arquivos. A funo do manipulador aceita solicitao e resposta atravs de
parmetros que pode ser passado diretamente para
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
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
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
long polling
ou
simplemente espera.
Para ser capaz de obter facilmente o contedo do
funo chamada
readStreamAsJSON
body
de uma solicitao de
stream
JSON
, analisa o
e em seguida chama
request
ambos do tipo
PUT
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
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.
/talks
changesSince
long polling
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
true
url
URL
parse
teremos um segundo
; tambm teremos que analisar parte por parte de uma URL. Se o objeto que ele
query
valores.
293
Quando o parmetro
changesSince
palestras e retorna.
Caso contrrio o parmetro
vlido. A funo
changeSince
getChangedTalks
array
array
waitForChanges
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
para transforma um
array
array
removeremos um nico elemento do objeto que controla a resposta de espera cujo ndice encontramos pelo
indexOf
splice
array
na posio
array
Determinamos 90 segundos para ser o tempo limite do pedido, caso ele ainda estiver a espera ele envia uma
resposta de
array
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
isso significa que h novos dados, ento todos os pedidos em espera podem serem respondidos
imediatamente.
294
Finalmente
getChangedTalks
poder usar o
getChangedTalks
array
deleted
tem de garantir que ele no incluiu a mesma palestra duas vezes; isso pode acontecer se
Aqui concluimos o cdigo do servidor. Executando o programa definido at agora voc vai ter um servidor rodando
na porta
sob a
8000
URL
/talks
public
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
ecstatic
./public/index.html
./public
path
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
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
nome para que ele possa ser redirecionado para a observao das palestras.
O elemento
<div>
com o
id "talks"
script
preenche a lista
Um
script
solicitao
"submit"
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
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
HTML
inclui um arquivo de
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
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
Quando a solicitao falhar ns no queremos que a nossa pgina no faa nada sem explicao. Assim
definimos uma funo simples chamada de
reportError
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.
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
name
o nome do
template
templates
template
templates
instantiateTemplate
templates
298
talk
template
comment
template
. O mtodo
na pgina HTML.
O mtodo
cloneNode
true
seja enviado
preenchendo onde o
deve aparecer.
instantiateTemplate
title
teplate
{{title}}
strings
com os
template
template
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
template comments
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
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
DELETE
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
A varivel
nameField
<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.
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)
Notificando mudanas
300
PUT
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
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
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
lastServerTime
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/
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
registerChange
alteraes no servidor. Ela pode ser estendida para escrever os novos dados no disco.
301
JSON
e coloca-las
./talks.json
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
Object.prototype
como formato de arquivo voc ter que copiar as propriedades do objeto retornados por
JSON.parse
em
um novo objeto.
while
if
array
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
array
template-repeat
Reescreva
array
para que
instantiateTemplate
talk`.
instantiateTemplate
para implementar isso e em seguida altere os templates para usar este recurso e
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
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
array
array
indicado pelo
Condicionais pode ser implementado de uma forma semelhante aos atributos de chamadas, por exemplo
template-when
template-unless
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
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