0% acharam este documento útil (0 voto)
3 visualizações236 páginas

JavaScript - Patterns - (2010) PTBR

Enviado por

Arthur Tatay
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
3 visualizações236 páginas

JavaScript - Patterns - (2010) PTBR

Enviado por

Arthur Tatay
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 236

Machine Translated by Google

Machine Translated by Google


Machine Translated by Google

Padrões JavaScript
Machine Translated by Google
Machine Translated by Google

Padrões JavaScript

Stoyan Stefanov

Pequim Cambridge Farnham Köln Sebastopol Tóquio


Machine Translated by Google

Padrões JavaScript
por Stoyan Stefanov

Copyright © 2010 Yahoo!, Inc.. Todos os direitos reservados.


Impresso nos Estados Unidos da América.

Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

Os livros da O'Reilly podem ser adquiridos para uso educacional, comercial ou promocional de vendas. Edições online
também estão disponíveis para a maioria dos títulos (http:// my.safaribooksonline.com). Para mais informações, entre em
contato com nosso departamento de vendas corporativo/institucional: (800) 998-9938 ou [email protected].

Editora: Mary Treseler Indexador: Potomac Indexing, LLC


Editora de produção: Teresa Elsey Designer de capa: Karen Montgomery
Editora de texto: ContentWorks, Inc. Designer de Interiores: David Futato
Revisora: Teresa Elsey Ilustrador: Roberto Romano

Histórico da
impressão: setembro de 2010: primeira edição.

Nutshell Handbook, o logotipo do Nutshell Handbook e o logotipo da O'Reilly são marcas registradas da O'Reilly Media, Inc.
JavaScript Patterns, a imagem de uma perdiz européia e a identidade visual relacionada são marcas comerciais da O'Reilly
Media, Inc.

Muitas das designações usadas por fabricantes e vendedores para distinguir seus produtos são reivindicadas como marcas
registradas. Onde essas designações aparecem neste livro, e a O'Reilly Media, Inc., estava ciente de uma reivindicação de
marca registrada, as designações foram impressas em letras maiúsculas ou iniciais maiúsculas.

Embora tenham sido tomadas todas as precauções na preparação deste livro, o editor e o autor não assumem nenhuma
responsabilidade por erros ou omissões ou por danos resultantes do uso das informações aqui contidas.

ISBN: 978-0-596-80675-0

[SB]

1284038177
Machine Translated by Google

Para minhas meninas: Eva, Zlatina e Nathalie


Machine Translated by Google
Machine Translated by Google

Índice

Prefácio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii

1. Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Padrões 1
JavaScript: Conceitos 3
Orientado a Objeto 3
Sem aulas 4
Protótipos 4
Ambiente 5
ECMAScript 5 5
JSLintGenericName
6
O Console 6

2. Essenciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Escrevendo código sustentável 9


Minimizando globais 10
O problema com os globais 11
Efeitos colaterais ao esquecer var 12
Acesso ao objeto global 13
Padrão de var único 13
Elevação: um problema com vars dispersos 14
para loops 15
for-in loops 17
(Não) Aumentando Protótipos Integrados 19
alternar padrão 20
Evitando Typecasting implícito 21
Evitando eval() 21
Conversões de número com parseInt() 23
Convenções de Codificação 23
Recuo 24
Chaves 24

vii
Machine Translated by Google

Localização da chave de abertura 25


Espaço em branco 26
Convenções de nomenclatura 28
Capitalizando Construtores 28
Separando palavras 28
Outros padrões de nomenclatura 29
Escrevendo comentários 30
Escrevendo documentos da API 30
Exemplo YUIDoc 31
Escrever para ser lido 34
Avaliações por pares 35
Minify…In Production Run 36
Resumo do 37
JSLint 37

3. Literais e Construtores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Objeto Literal 39
A Sintaxe Literal do Objeto 40
Objetos de um Construtor 41
Captura do Construtor de Objetos 41
Funções Construtoras Personalizadas 42
Valores de retorno do construtor 43
Padrões para impor novos 44
Convenção de nomes 45
Usando isso 45
Construtor de auto-invocação 46
Array Literal 46
Matriz Literal Sintaxe 47
Curiosidade do Construtor de Arrays 47
Verifique se há Array-ness 48
JSON 49
Trabalhando com JSON 49
Expressão Regular Literal 50
Sintaxe Literal de Expressão Regular 51
Wrappers primitivos 52
Objetos de erro 53
Resumo 54

4. Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Fundo 57
Desambiguação da Terminologia 58
Declarações versus expressões: nomes e elevação 59
Nome da função Propriedade 60

viii | Índice
Machine Translated by Google

função de elevação 61
Padrão de retorno de chamada 62
Um exemplo de retorno de chamada 63
Chamadas de retorno e escopo 64
Ouvintes de eventos assíncronos 66
Tempo limite 66
Chamadas de retorno em bibliotecas 67
Funções de retorno 67
Funções autodefinidas 68
Funções imediatas 69
Parâmetros de uma função imediata 70
Valores retornados de funções imediatas 71
Benefícios e uso 72
Inicialização imediata do objeto 73
Ramificação de tempo inicial 74
Propriedades da Função—Um Padrão de Memoização 76
Objetos de configuração 77
Curry 79
Aplicativo de função 79
Aplicação Parcial 80
Escovando 81
Quando usar Curry 83
Resumo 84

5. Padrões de criação de objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87


Padrão de Namespace 87
Função de Namespace de Finalidade Geral 89
Declarando Dependências 90
Propriedades e métodos privados 92
Membros Privados 92
Métodos Privilegiados 93
Falhas de privacidade 93
Literais de objeto e privacidade 94
Protótipos e Privacidade 95
Revelando funções privadas como métodos públicos 96
Padrão de módulo 97
Revelando o Padrão do Módulo 99
Módulos que criam construtores 100
Importando globais para um módulo 101
Padrão de caixa de areia 101
Um Construtor Global 101
Adicionando Módulos 103
Implementando o Construtor 104

Índice | ix
Machine Translated by Google

Membros Estáticos 105


Membros Estáticos Públicos 105
Membros Estáticos Privados 107
Constantes do objeto 109
padrão de encadeamento 110
Prós e Contras do método Chaining Pattern 111
method() 112
Resumo 113

6. Padrões de reutilização de código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115


Padrões de herança clássicos versus modernos 115
Resultado esperado ao usar a herança clássica 116
Padrão clássico nº 1 - o padrão padrão 117
Seguindo a Cadeia de Protótipos 117
Desvantagens ao usar o padrão nº 1 119
Padrão Clássico # 2 - Rent-a-Constructor 120
A Cadeia Protótipo 121
Herança múltipla por construtores emprestados 122
Prós e Contras do Padrão Construtor de Empréstimo 123
Padrão Clássico #3 - Alugar e Definir Protótipo 123
Padrão Clássico Nº 4 - Compartilhe o Protótipo 124
Padrão Clássico #5 - Um Construtor Temporário 125
Armazenando a Superclasse 126
Redefinindo o ponteiro do construtor 127
classe 128
Herança Prototípica 130
Discussão 132
Adição ao ECMAScript 5 132
Herança por Copiar Propriedades 133
Exemplo 135
de Métodos de 136
Empréstimo de Mix-ins: Empréstimo 137
de Array e Função 137
de Bind.prototype.bind() 138
Resumo 139

7. Padrões de projeto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141

solteiro 141
Usando novo 142
Instância em uma propriedade estática 143
Instância em um Closure 144
Fábrica 146
Fábrica de Objetos Integrada 148

x | Índice
Machine Translated by Google

Iterador 149
Decorador 151
Uso 151
Implementação 151
Implementação usando uma lista 154
Estratégia 155
Exemplo de validação de dados 156
Fachada 158
Proxy 159
Um exemplo 160
Proxy como cache 167
Mediador 167
Exemplo de Mediador 168
Observador 171
Exemplo #1: assinaturas de revistas 171
Exemplo nº 2: o jogo de pressionamento de tecla 175
Resumo 178

8. DOM e padrões de navegador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181


Separação de preocupações 181
Script DOM 183
Acesso DOM 183
Manipulação de DOM 184
Eventos 185
Tratamento de Eventos 186
Delegação de Evento 188
Scripts de longa duração 189
setTimeout() 189
Trabalhadores da Web 190

Script Remoto 190


XMLHttpRequest 191
JSONP 192
Molduras e Beacons de Imagem 195
Implantando JavaScript 196
Combinando Scripts 196
Minificando e Comprimindo 197
Cabeçalho Expirado 197
Usando uma 197
CDN Estratégias de 198
Carregamento O Lugar do Elemento <script> 199
Chunking HTTP 200
Elemento <script> Dinâmico para Downloads Sem Bloqueio Lazy-Loading 201
203

Índice | XI
Machine Translated by Google

Carregando sob demanda 203


Pré-carregando JavaScript 205
Resumo 206

Índice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

xiii | Índice
Machine Translated by Google

Prefácio

Padrões são soluções para problemas comuns. Um passo adiante, os padrões são modelos para resolver
categorias de problemas.

Os padrões ajudam você a dividir um problema em blocos semelhantes a Lego e a se concentrar nas partes
exclusivas do problema, enquanto abstrai muitos detalhes do tipo “estive lá, fiz isso, peguei a camiseta”.

Os padrões também nos ajudam a nos comunicar melhor simplesmente fornecendo um vocabulário comum.

Portanto, é importante identificar e estudar padrões.

Público-alvo
Este livro não é um livro para iniciantes; é direcionado a desenvolvedores profissionais e programadores
que desejam levar suas habilidades em JavaScript para o próximo nível.

Alguns dos fundamentos (como loops, condicionais e encerramentos) não são discutidos. Se você achar
que precisa revisar alguns desses tópicos, consulte a lista de leituras sugeridas.

Ao mesmo tempo, alguns tópicos (como criação ou içamento de objetos) podem parecer básicos demais
para estarem neste livro, mas são discutidos de uma perspectiva de padrões e, na minha opinião, são
essenciais para aproveitar o poder da linguagem.

Se você está procurando melhores práticas e padrões poderosos para ajudá-lo a escrever um código
JavaScript melhor, sustentável e robusto, este livro é para você.

Convenções utilizadas neste livro


As seguintes convenções tipográficas são usadas neste livro:

Itálico
Indica novos termos, URLs, endereços de e-mail, nomes de arquivo e extensões de arquivo.

xiii
Machine Translated by Google

Largura constante
Usado para listagens de programas, bem como dentro de parágrafos para se referir a elementos de
programas como nomes de variáveis ou funções, bancos de dados, tipos de dados, variáveis de
ambiente, instruções e palavras-chave.
Largura constante em
negrito Mostra comandos ou outro texto que deve ser digitado literalmente pelo usuário.
Itálico de largura constante

Mostra o texto que deve ser substituído por valores fornecidos pelo usuário ou por valores
determinados pelo contexto.

Este ícone significa uma dica, sugestão ou nota geral.

Este ícone significa um aviso ou cuidado.

Usando exemplos de código


Este livro está aqui para ajudá-lo a realizar seu trabalho. Em geral, você pode usar o código deste livro em
seus programas e documentação. Você não precisa entrar em contato conosco para obter permissão, a
menos que esteja reproduzindo uma parte significativa do código. Por exemplo, escrever um programa que
usa vários blocos de código deste livro não requer permissão. A venda ou distribuição de um CD-ROM com
exemplos dos livros da O'Reilly requer permissão. Responder a uma pergunta citando este livro e citando
um código de exemplo não requer permissão. Incorporar uma quantidade significativa de código de exemplo
deste livro na documentação do seu produto requer permissão.

Agradecemos, mas não exigimos, atribuição. Uma atribuição geralmente inclui o título, autor, editora e
ISBN. Por exemplo: “JavaScript Patterns, por Stoyan Stefanov (O'Reilly). Copyright 2010 Yahoo!, Inc.,
9780596806750.”

Se você acha que seu uso de exemplos de código está fora do uso justo ou da permissão dada aqui, sinta-
se à vontade para nos contatar em [email protected].

Safari® Livros Online

O Safari Books Online é uma biblioteca digital sob demanda que permite que você pesquise
facilmente mais de 7.500 livros e vídeos de referência criativa e tecnológica para encontrar
rapidamente as respostas de que precisa.

xiv | Prefácio
Machine Translated by Google

Com uma assinatura, você pode ler qualquer página e assistir a qualquer vídeo de nossa biblioteca online.
Leia livros em seu telefone celular e dispositivos móveis. Acesse novos títulos antes que eles estejam
disponíveis para impressão e obtenha acesso exclusivo a manuscritos em desenvolvimento e poste
comentários para os autores. Copie e cole amostras de código, organize seus favoritos, baixe capítulos,
marque seções importantes, crie notas, imprima páginas e aproveite vários outros recursos que
economizam tempo.

A O'Reilly Media carregou este livro no serviço Safari Books Online. Para ter acesso digital completo a
este livro e a outros sobre tópicos semelhantes da O'Reilly e de outras editoras, inscreva-se gratuitamente
em http:// my.safaribooksonline.com.

Como entrar em contato conosco

Envie comentários e perguntas sobre este livro para a editora: O'Reilly Media, Inc.

1005 Gravenstein Highway North


Sebastopol, CA 95472
800-998-9938 (nos Estados Unidos ou Canadá)
707-829-0515 (internacional ou local)
707-829-0104 (fax)

Temos uma página web para este livro, onde listamos errata, exemplos e qualquer informação adicional.
Você pode acessar esta página em:

http:// oreilly.com/ catalog/ 9780596806750 Para

comentar ou fazer perguntas técnicas sobre este livro, envie um e-mail para:

[email protected]

Para obter mais informações sobre nossos livros, conferências, Centros de recursos e o
O'Reilly Network, consulte nosso site em:

http:// oreilly.com

Agradecimentos
Sou eternamente grato aos incríveis revisores que compartilharam sua energia e conhecimento para
fazer um livro muito melhor para o bem da comunidade. Seus blogs e streams no Twitter são uma fonte
constante de admiração, observações precisas, grandes ideias e padrões.

• Dmitry Soshnikov (http:// dmitrysoshnikov.com, @DmitrySoshnikov) • Andrea

Giammarchi (http:// webreflection.blogspot.com, @WebReflection) • Asen Bozhilov (http://


asenbozhilov.com, @abozhilov) • Juriy Zaytsev (http://
perfectionkills.com, @kangax)

Prefácio | xv
Machine Translated by Google

• Ryan Grove (http:// wonko.com, @yaypie) •


Nicholas Zakas (http:// nczonline.net, @slicknet) • Remy
Sharp (http:// remysharp.com, @rem) • Iliyan Peychev

Créditos
Alguns dos padrões do livro foram identificados pelo autor, com base na experiência e em estudos
de bibliotecas populares de JavaScript, como jQuery e YUI. Mas a maioria dos padrões é
identificada e descrita pela comunidade JavaScript; portanto, este livro é resultado do trabalho
coletivo de muitos desenvolvedores. Para não interromper a narrativa com história e créditos, uma
lista de referências e leitura adicional sugerida é fornecida no site complementar do livro em http://
www.jspatterns.com/ book/ reading/.

Se eu perdi um artigo bom e original na lista de referências, por favor, aceite minhas desculpas e
entre em contato comigo para que eu possa adicioná-lo à lista online em http:// jspatterns.com.

Leitura
Este não é um livro para iniciantes e alguns tópicos básicos, como loops e condições, são
ignorados. Se você precisa aprender mais sobre o idioma, seguem alguns títulos sugeridos:

• JavaScript Orientado a Objetos por sinceramente (Packt Publishing) •


JavaScript: O Guia Definitivo por David Flanagan (O'Reilly) • JavaScript:
as partes boas por Douglas Crockford (O'Reilly) • Pro JavaScript Design
Patterns por Ross Hermes e Dustin Diaz (Apress) • JavaScript de alto desempenho
por Nicholas Zakas (O'Reilly) • JavaScript profissional para
desenvolvedores da Web por Nicholas Zakas (Wrox)

XVI | Prefácio
Machine Translated by Google

CAPÍTULO 1

Introdução

JavaScript é a linguagem da Web. Começou como uma forma de manipular alguns tipos selecionados
de elementos em uma página da web (como imagens e campos de formulário), mas cresceu
tremendamente. Além de scripts de navegador do lado do cliente, atualmente você pode usar
JavaScript para programar para uma variedade cada vez maior de plataformas. Você pode escrever
código do lado do servidor (usando .NET ou Node.js), aplicativos de desktop (que funcionam em
todos os sistemas operacionais) e extensões de aplicativos (por exemplo, para Firefox ou Photoshop),
aplicativos móveis e scripts de linha de comando.

JavaScript também é uma linguagem incomum. Ele não possui classes e as funções são objetos de
primeira classe usados para muitas tarefas. Inicialmente a linguagem foi considerada deficiente por
muitos desenvolvedores, mas nos anos mais recentes esses sentimentos mudaram. Curiosamente,
linguagens como Java e PHP começaram a adicionar recursos como encerramentos e funções
anônimas, que os desenvolvedores de JavaScript têm apreciado e dado como certo por um tempo.

O JavaScript é dinâmico o suficiente para que você possa fazer com que pareça com outra linguagem
com a qual você já se sinta confortável. Mas a melhor abordagem é abraçar suas diferenças e estudar
seus padrões específicos.

Padrões
Um padrão no sentido mais amplo da palavra é um “tema de eventos ou objetos recorrentes… pode
ser um modelo ou modelo que pode ser usado para gerar coisas” (http://en.wikipedia.org/ wiki/
Pattern ).

No desenvolvimento de software, um padrão é uma solução para um problema comum. Um padrão


não é necessariamente uma solução de código pronta para copiar e colar, mas sim uma melhor
prática, uma abstração útil e um modelo para resolver categorias de problemas.

1
Machine Translated by Google

É importante identificar padrões porque:

• Eles nos ajudam a escrever um código melhor usando práticas comprovadas e não reinventam a roda.
• Eles fornecem um nível de abstração - o cérebro pode reter apenas um tanto em um determinado
momento, então, quando você pensa sobre um problema mais complexo, ajuda se você não se
incomodar com os detalhes de baixo nível, mas responsabilizá-los por si mesmo blocos de construção
contidos (padrões).
• Eles melhoram a comunicação entre desenvolvedores e equipes, que geralmente estão em locais
remotos e não se comunicam face a face. Simplesmente colocar um rótulo em alguma técnica ou
abordagem de codificação torna mais fácil garantir que estamos falando sobre a mesma coisa. Por
exemplo, é mais fácil dizer (e pensar) “função imediata” do que “essa coisa em que você envolve a
função entre parênteses e no final coloca outro par de parênteses para invocar a função exatamente
onde você definiu
multou.”

Este livro discute os seguintes tipos de padrões:

• Padrões de design •
Padrões de codificação
• Antipadrões

Padrões de projeto são aqueles inicialmente definidos pelo livro “Gang of Four” (assim nomeado após
seus quatro autores), publicado originalmente no distante ano de 1994 sob o título Design Patterns:
Elements of Reusable Object-Oriented Software. Exemplos de padrões de projeto são single gleton,
factory, decorator, observer e assim por diante. O problema dos padrões de design em relação ao
JavaScript é que, embora independentes de linguagem, os padrões de design foram estudados
principalmente sob a perspectiva de linguagens fortemente tipadas, como C++ e Java.
Às vezes, não faz sentido aplicá-los literalmente em uma linguagem dinâmica de tipagem livre, como
JavaScript. Às vezes, esses padrões são soluções alternativas que lidam com a natureza fortemente
tipada das linguagens e a herança baseada em classe.
Em JavaScript pode haver alternativas mais simples. Este livro discute as implementações de JavaScript
de vários padrões de projeto no Capítulo 7.

Os padrões de codificação são muito mais interessantes; são padrões específicos de JavaScript e boas
práticas relacionadas aos recursos exclusivos da linguagem, como os vários usos de funções. Padrões
de codificação JavaScript são o tópico principal do livro.

Você pode encontrar um antipadrão ocasional no livro. Os antipadrões têm um som um pouco negativo ou
até mesmo ofensivo em seu nome, mas não precisa ser o caso. Um antipadrão não é o mesmo que um
bug ou um erro de codificação; é apenas uma abordagem comum que causa mais problemas do que
resolve. Os antipadrões são claramente marcados com um comentário no código.

2 | Capítulo 1 Introdução
Machine Translated by Google

JavaScript: Conceitos
Vamos examinar rapidamente alguns conceitos importantes que fornecem um contexto para os capítulos
seguintes.

O JavaScript orientado

a objetos é uma linguagem orientada a objetos, que geralmente surpreende os desenvolvedores que já
examinaram a linguagem e a descartaram. Qualquer coisa que você veja em um pedaço de código
JavaScript tem uma boa chance de ser um objeto. Apenas cinco tipos primitivos não são objetos: number,
string, boolean, null e undefined, e os três primeiros têm representação de objeto correspondente na
forma de wrappers primitivos (discutidos no próximo capítulo). Número, string e valores primitivos
booleanos são facilmente convertidos em objetos pelo programador ou, às vezes, nos bastidores pelo
interpretador JavaScript.

As funções também são objetos. Eles podem ter propriedades e métodos.

A coisa mais simples que você faz em qualquer linguagem é definir uma variável. Bem, em JavaScript,
quando você define uma variável, você já está lidando com objetos. Primeiro, a variável torna-se
automaticamente uma propriedade de um objeto interno conhecido como Objeto de Ativação (ou uma
propriedade do objeto global se for uma variável global). Em segundo lugar, essa variável também é
semelhante a um objeto porque tem suas próprias propriedades (chamadas de atributos), que determinam
se a variável pode ser alterada, excluída ou enumerada em um loop for-in . Esses atributos não são
expostos diretamente no ECMAScript 3, mas a edição 5 oferece métodos especiais de script para
manipulá-los.

Então, quais são os objetos? Por fazerem tantas coisas, devem ser muito especiais.
Na verdade, eles são extremamente simples. Um objeto é apenas uma coleção de propriedades
nomeadas, uma lista de pares chave-valor (quase idêntico a um array associativo em outras linguagens).
Algumas das propriedades podem ser funções (objetos de função), caso em que as chamamos de
métodos.

Outra coisa sobre os objetos que você cria é que você pode modificá-los a qualquer momento.
(Embora o ECMAScript 5 apresente APIs para evitar mutações.) Você pode pegar um objeto e adicionar,
remover e atualizar seus membros. Se você estiver preocupado com privacidade e acesso, veremos
padrões para isso também.

E uma última coisa a ter em mente é que existem dois tipos principais de objetos:

Nativo
Descrito no padrão ECMAScript
Hospedar

Definido pelo ambiente do host (por exemplo, o ambiente do navegador)

Os objetos nativos podem ainda ser categorizados como integrados (por exemplo, Array, Date) ou
definidos pelo usuário (var o = {};).

JavaScript: Conceitos | 3
Machine Translated by Google

Objetos host são, por exemplo, janela e todos os objetos DOM. Se você está se perguntando se está
usando objetos de host, tente executar seu código em um ambiente diferente do navegador. Se
funcionar bem, provavelmente você está usando apenas objetos nativos.

Sem aulas
Você verá esta afirmação repetida várias vezes ao longo do livro: Não há classes em JavaScript. Este
é um conceito novo para programadores experientes em outras linguagens e leva mais do que algumas
repetições e mais do que um pequeno esforço para “desaprender” classes e aceitar que o JavaScript
lida apenas com objetos.

Não ter classes torna seus programas mais curtos - você não precisa ter uma classe para criar um
objeto. Considere esta criação de objeto semelhante ao Java:
// Criação do objeto Java
AlôOO alô_oo = new AlôOO();

Repetir a mesma coisa três vezes parece uma sobrecarga quando se trata de criar objetos simples. E,
na maioria das vezes, queremos manter nossos objetos simples.

Em JavaScript, você cria um objeto em branco quando precisa de um e, em seguida, começa a


adicionar membros interessantes a ele. Você compõe objetos adicionando primitivas, funções ou
outros objetos a eles como suas propriedades. Um objeto “em branco” não está totalmente em branco;
ele já vem com algumas propriedades internas, mas não possui propriedades “próprias”. Falaremos
mais sobre isso no próximo capítulo.

Uma das regras gerais do livro Gang of Four diz: “Prefira a composição de objetos à herança de
classes”. Isso significa que, se você pode criar objetos a partir de peças disponíveis, essa é uma
abordagem muito melhor do que criar longas cadeias de herança e classificações de pai-filho. Em
JavaScript é fácil seguir este conselho - simplesmente porque não há classes e a composição de
objetos é o que você faz de qualquer maneira.

Protótipos O

JavaScript tem herança, embora essa seja apenas uma maneira de reutilizar o código. (E há um
capítulo inteiro sobre reutilização de código.) A herança pode ser realizada de várias maneiras, que
geralmente usam protótipos. Um protótipo é um objeto (sem surpresas) e toda função que você cria
obtém automaticamente uma propriedade de protótipo que aponta para um novo objeto em branco.
Este objeto é quase idêntico a um objeto criado com um objeto literal ou construtor Object() , exceto
que sua propriedade construtor aponta para a função que você cria e não para o Object() embutido .
Você pode adicionar membros a esse objeto em branco e, posteriormente, fazer com que outros
objetos herdem desse objeto e usem suas propriedades como suas.

Discutiremos a herança em detalhes, mas, por enquanto, lembre-se de que o protótipo é um objeto
(não uma classe ou algo especial) e toda função tem uma propriedade de protótipo .

4 | Capítulo 1 Introdução
Machine Translated by Google

Ambiente
Os programas JavaScript precisam de um ambiente para serem executados. O habitat natural de um
programa JavaScript é o navegador, mas esse não é o único ambiente. Os padrões no livro são
principalmente relacionados ao núcleo do JavaScript (ECMAScript), portanto, são agnósticos em relação
ao ambiente. As exceções são:

• Capítulo 8, que lida especificamente com padrões de navegador • Alguns

outros exemplos que ilustram aplicações práticas de um padrão

Os ambientes podem fornecer seus próprios objetos de host, que não são definidos no padrão ECMA
Script e podem ter comportamento não especificado e inesperado.

ECMAScript 5
A linguagem de programação JavaScript central (excluindo DOM, BOM e objetos de host extras) é baseada
no padrão ECMAScript , ou ES para abreviar. A versão 3 do padrão foi aceita oficialmente em 1999 e é a
atualmente implementada nos navegadores.
A versão 4 foi abandonada e a versão 5 foi aprovada em dezembro de 2009, 10 anos após a anterior.

A versão 5 adiciona alguns novos objetos, métodos e propriedades incorporados à linguagem, mas sua
adição mais importante é o chamado modo estrito, que na verdade remove recursos da linguagem, tornando
os programas mais simples e menos propensos a erros. Por exemplo, o uso da instrução with tem sido
contestado ao longo dos anos. Agora, no modo estrito ES5, ele gera um erro, embora esteja tudo bem se
for encontrado no modo não estrito. O modo estrito é acionado por uma string comum, que as
implementações mais antigas da linguagem simplesmente ignoram. Isso significa que o uso do modo estrito
é compatível com versões anteriores, pois não gerará erros em navegadores mais antigos que não o
entendam.

Uma vez por escopo (escopo de função, escopo global ou no início de uma string passada para eval()),
você pode usar a seguinte string:

function my()
{ "usar
estrito"; // resto da função...
}

Isso significa que o código na função é executado no subconjunto estrito da linguagem. Para navegadores
mais antigos, isso é apenas uma string não atribuída a nenhuma variável, portanto não é usada e, no
entanto, não é um erro.

O plano para a linguagem é que, no futuro, o modo estrito seja o único permitido.
Nesse sentido, o ES5 é uma versão de transição – os desenvolvedores são encorajados, mas não forçados,
a escrever código que funcione em modo estrito.

ECMAScript 5 |5
Machine Translated by Google

O livro não explora os padrões relacionados às adições específicas do ES5, porque no momento em que este
livro foi escrito não havia nenhum navegador que implementasse o ES5. Mas os exemplos neste livro promovem
uma transição para o novo padrão ao:

• Garantir que os exemplos de código oferecidos não gerarão erros no modo estrito • Evitar e

apontar construções obsoletas, como arguments.callee • Chamar padrões ES3 que tenham

equivalentes internos do ES5, como Object.create()

JSLintGenericName

JavaScript é uma linguagem interpretada sem verificações estáticas em tempo de compilação. Portanto, é
possível implantar um programa quebrado com um simples erro de digitação sem perceber. É aqui que o JSLint
ajuda.

JSLint (http:// jslint.com) é uma ferramenta de qualidade de código JavaScript criada por Douglas Crockford que
inspeciona seu código e avisa sobre possíveis problemas. É altamente recomendável que você execute seu
código por meio do JSLint. A ferramenta “vai ferir seus sentimentos”, como adverte seu criador, mas apenas no
começo. Você pode aprender rapidamente com seus erros e adotar os hábitos essenciais de um programador
JavaScript profissional. Não ter nenhum erro de JSLint em seu código também ajuda você a ter mais confiança
no código, sabendo que você não cometeu uma simples omissão ou erro de sintaxe com pressa.

A partir do próximo capítulo, você verá que o JSLint é bastante mencionado. Todo o código no livro passa com
sucesso na verificação do JSLint (com as configurações padrão, atuais no momento da escrita), exceto por
algumas ocasiões claramente marcadas como antipadrões.

Em suas configurações padrão, o JSLint espera que seu código seja compatível com o modo estrito.

O Console

O objeto console é usado em todo o livro. Este objeto não faz parte da linguagem e sim do ambiente e está
presente na maioria dos navegadores atuais. No Firefox, por exemplo, vem com a extensão Firebug. O console
do Firebug tem uma IU que permite digitar e testar rapidamente pequenos trechos de código JavaScript e
também brincar com a página carregada no momento (consulte a Figura 1-1). Também é altamente recomendado
como uma ferramenta de aprendizado e exploração. Funcionalidades semelhantes estão disponíveis em
navegadores WebKit (Safari e Chrome) como parte do Web Inspector e no IE a partir da versão 8 como parte
das Ferramentas do desenvolvedor.

A maioria dos exemplos de código no livro usa o objeto console em vez de solicitar alert()s ou atualizar a página
atual, porque é uma maneira fácil e discreta de imprimir alguma saída.

6 | Capítulo 1 Introdução
Machine Translated by Google

Figura 1-1. Usando o console do Firebug

Frequentemente usamos o método log(), que imprime todos os parâmetros passados para ele, e
algumas vezes dir(), que enumera o objeto passado para ele e imprime todas as propriedades.
Aqui está um
exemplo de uso: console.log("test", 1,
{}, [1,2,3]); console.dir({um: 1, dois: {três: 3}});

Quando você digita no console, não precisa usar console.log(); você pode simplesmente omiti-lo.
Para evitar confusão, alguns trechos de código também o ignoram e assumem que você está
testando o código no console:

janela.nome === janela['nome']; // verdadeiro

É como se usássemos o seguinte:


console.log(window.name === window['name']);

e imprimiu true no console.

O Console | 7
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 2

Essenciais

Este capítulo discute práticas recomendadas, padrões e hábitos essenciais para escrever código
JavaScript de alta qualidade, como evitar globais, usar declarações de var únicas, pré-cachear
comprimento em loops, seguir convenções de codificação e muito mais. O capítulo também inclui alguns
hábitos não necessariamente relacionados ao código em si, mas mais sobre o processo geral de criação
de código, incluindo escrever a documentação da API, realizar revisões por pares e executar o JSLint.
Esses hábitos e práticas recomendadas podem ajudá-lo a escrever códigos melhores, mais
compreensíveis e de fácil manutenção — códigos dos quais se orgulhar (e ser capaz de descobrir) ao
revisitá-los meses e anos depois.

Escrevendo código sustentável


Bugs de software são caros para corrigir. E seu custo aumenta com o tempo, especialmente se os bugs
se infiltrarem no produto lançado publicamente. É melhor se você puder corrigir um bug imediatamente,
assim que o encontrar; é quando o problema que seu código resolve ainda está fresco em sua cabeça.
Caso contrário, você passa para outras tarefas e esquece tudo sobre esse código específico. Revisitar
o código depois de algum tempo requer:

• Tempo para reaprender e entender o problema • Tempo


para entender o código que deve resolver o problema

Outro problema, específico de projetos ou empresas maiores, é que a pessoa que eventualmente
conserta o bug não é a mesma que criou o bug (e também não é a mesma pessoa que encontrou o
bug). Portanto, é fundamental reduzir o tempo necessário para entender o código, seja escrito por você
mesmo há algum tempo ou por outro desenvolvedor da equipe. É fundamental tanto para o resultado
final (receita comercial) quanto para a felicidade do desenvolvedor, porque todos nós preferimos
desenvolver algo novo e empolgante em vez de gastar horas e dias mantendo o código legado antigo.

Outro fato da vida relacionado ao desenvolvimento de software em geral é que normalmente mais
tempo é gasto lendo código do que escrevendo . Em momentos em que você está focado e aprofundado
em um problema, você pode sentar e em uma tarde criar uma quantidade considerável de código.

9
Machine Translated by Google

O código provavelmente funcionará naquele momento, mas conforme o aplicativo amadurece, muitas outras
coisas acontecem que exigem que seu código seja revisado, revisado e ajustado. Por exemplo:

• Bugs são descobertos. •

Novos recursos são adicionados ao aplicativo. • O

aplicativo precisa funcionar em novos ambientes (por exemplo, novos navegadores aparecem no
mercado). • O código é

reaproveitado. • O código é

totalmente reescrito do zero ou portado para outra arquitetura ou mesmo outra linguagem.

Como resultado das mudanças, as poucas horas-homem gastas escrevendo o código inicialmente acabam
em semanas-homem gastas lendo-o. É por isso que a criação de código sustentável é fundamental para o
sucesso de um aplicativo.
Código sustentável significa código que:

• É legível

• É consistente

• É previsível •

Parece que foi escrito pela mesma pessoa • Está


documentado

O restante deste capítulo aborda esses pontos quando se trata de escrever JavaScript.

Minimizando globais
JavaScript usa funções para gerenciar o escopo. Uma variável declarada dentro de uma função é local para
essa função e não está disponível fora da função. Por outro lado, variáveis globais são aquelas declaradas
fora de qualquer função ou simplesmente utilizadas sem serem declaradas.

Todo ambiente JavaScript tem um objeto global acessível quando você o usa fora de qualquer função. Cada
variável global que você cria torna-se uma propriedade do objeto global. Nos navegadores, por conveniência,
há uma propriedade adicional do objeto global chamada janela que (geralmente) aponta para o próprio
objeto global. O trecho de código a seguir mostra como criar e acessar uma variável global em um ambiente
de navegador:
myglobal = "olá"; // antipadrão
console.log(myglobal); // "olá"
console.log(window.myglobal); // "olá"
console.log(window["myglobal"]); // "olá"
console.log(this.myglobal); // "olá"

10 | Capítulo 2: Fundamentos
Machine Translated by Google

O problema com os globais


O problema com variáveis globais é que elas são compartilhadas entre todo o código em seu
aplicativo JavaScript ou página da web. Eles vivem no mesmo namespace global e sempre há uma
chance de nomear colisões — quando duas partes separadas de um aplicativo definem variáveis
globais com o mesmo nome, mas com propósitos diferentes.

Também é comum que as páginas da Web incluam código não escrito pelos desenvolvedores da
página, por exemplo:

• Uma biblioteca JavaScript de terceiros


• Scripts de um parceiro de publicidade •
Código de um script de rastreamento e análise de usuário de terceiros
• Diferentes tipos de widgets, emblemas e botões

Digamos que um dos scripts de terceiros defina uma variável global, chamada, por exemplo,
resultado. Posteriormente, em uma de suas funções, você define outra variável global chamada
resultado. O resultado disso é que a última variável de resultado substitui as anteriores e o script de
terceiros pode simplesmente parar de funcionar.

Portanto, é importante ser um bom vizinho dos outros scripts que podem estar na mesma página e
usar o mínimo possível de variáveis globais. Mais adiante no livro, você aprenderá sobre estratégias
para minimizar o número de globais, como o padrão de namespace ou as funções imediatas
autoexecutáveis, mas o padrão mais importante para ter menos globais é sempre usar var para
declarar variáveis .

É surpreendentemente fácil criar globais involuntariamente por causa de dois recursos do JavaScript.
Primeiro, você pode usar variáveis sem mesmo declará-las. Em segundo lugar, o JavaScript tem a
noção de globais implícitos, o que significa que qualquer variável que você não declarar torna-se
uma propriedade do objeto global (e é acessível exatamente como uma variável global declarada
corretamente). Considere o seguinte exemplo:
function sum(x, y) { //
antipadrão: resultado global
implícito = x +
y; resultado de retorno;
}

Neste código, o resultado é usado sem ser declarado. O código funciona bem, mas depois de chamar
a função, você acaba com mais um resultado variável no namespace global que pode ser uma fonte
de problemas.

A regra geral é sempre declarar variáveis com var, conforme demonstrado na versão aprimorada da
função sum() : function sum(x, y) { var
result = x + y;
resultado de retorno;

Minimizando globais | 11
Machine Translated by Google

Outro antipadrão que cria globais implícitos é encadear atribuições como parte de uma declaração
var . No trecho a seguir, a é local, mas b se torna global, o que provavelmente não é o que você
pretendia fazer:
// antipadrão, não use
função foo() { var
a = b = 0;

// ...
}

Se você está se perguntando por que isso acontece, é por causa da avaliação da direita para a
esquerda. Primeiro, a expressão b = 0 é avaliada e, neste caso, b não é declarada. O valor de
retorno dessa expressão é 0 e é atribuído à nova variável local declarada com var a. Em outras
palavras, é como se você tivesse digitado:
var a = (b = 0);

Se você já declarou as variáveis, encadear atribuições é bom e não cria globais inesperados.
Exemplo: function foo() { var a,
b; // ... a = b =
0; //
ambos
locais
}

Ainda outro motivo para evitar globais é a portabilidade. Se você deseja que seu código
seja executado em diferentes ambientes (hosts), é perigoso usar globais porque você pode
sobrescrever acidentalmente um objeto host que não existe em seu ambiente original
(então você pensou que o nome era seguro para usar), mas que não em alguns dos outros.

Efeitos colaterais ao esquecer var


Há uma pequena diferença entre os globais implícitos e os explicitamente definidos - a diferença
está na capacidade de indefinir essas variáveis usando o operador delete :

• Os globais criados com var (aqueles criados no programa fora de qualquer função) não podem
ser excluídos.

• Globais implícitos criados sem var (independentemente se criados dentro de funções) podem ser
excluído.

Isso mostra que os globais implícitos não são tecnicamente variáveis reais, mas são propriedades
do objeto global. As propriedades podem ser excluídas com o operador delete , enquanto as
variáveis não podem:

// define três globais var


global_var = 1;
global_novar = 2; // antipadrão
(função () {

12 | Capítulo 2: Fundamentos
Machine Translated by Google

global_fromfunc = 3; // antipadrão }());

// tentativa de excluir
delete global_var; // false
delete global_novar; //
verdadeiro deletar global_fromfunc; // verdadeiro

// teste o tipo de
exclusão de global_var; //
"número" typeof global_novar; // tipo
"indefinido" global_fromfunc; // "indefinido"

No modo estrito do ES5, as atribuições a variáveis não declaradas (como os dois antipadrões no
snippet anterior) gerarão um erro.

Acesso ao objeto global Nos


navegadores, o objeto global pode ser acessado de qualquer parte do código por meio da
propriedade window (a menos que você tenha feito algo especial e inesperado, como declarar
uma variável local chamada janela). Mas em outros ambientes essa propriedade de conveniência
pode ser chamada de outra coisa (ou mesmo não disponível para o programador). Se você
precisar acessar o objeto global sem codificar a janela do identificador, poderá fazer o seguinte
em qualquer nível de escopo de função aninhada:
var global = (função () { return
this; } ());

Dessa forma, você sempre pode obter o objeto global, porque dentro das funções que foram
invocadas como funções (ou seja, não como constritores com new) isso sempre deve apontar
para o objeto global. Na verdade, esse não é mais o caso do ECMAScript 5 no modo estrito,
portanto, você deve adotar um padrão diferente quando seu código estiver no modo estrito. Por
exemplo, se estiver desenvolvendo uma biblioteca, você pode agrupar o código da biblioteca em
uma função imediata (discutida no Capítulo 4) e, a partir do escopo global, passar uma referência
a isso como um parâmetro para sua função imediata.

Padrão var único Usar


uma instrução var única no início de suas funções é um padrão útil a ser adotado. Tem os
seguintes benefícios:

• Fornece um único local para procurar todas as variáveis locais necessárias para a função
• Evita erros lógicos quando uma variável é usada antes de ser definida (consulte “Hoisting: A
Problema com vars Scattered” na página 14)
• Ajuda você a lembrar de declarar variáveis e, portanto, minimizar globais • É
menos código (para digitar e transferir pela rede)

Minimizando globais | 13
Machine Translated by Google

O padrão de var único se parece com isso:


function func()
{ var a =
1, b =
2, soma = a
+ b, myobject
=
{}, i, j;

// corpo da função...

Você usa uma instrução var e declara várias variáveis delimitadas por vírgulas. É uma boa
prática também inicializar a variável com um valor inicial no momento em que você a declara.
Isso pode evitar erros lógicos (todas as variáveis não inicializadas e declaradas são
inicializadas com o valor indefinido) e também melhorar a legibilidade do código. Ao examinar
o código posteriormente, você pode ter uma ideia sobre o uso pretendido de uma variável com
base em seu valor inicial - por exemplo, era para ser um objeto ou um número inteiro?
Você também pode fazer algum trabalho real no momento da declaração, como no caso de sum =
a + b no código anterior. Outro exemplo é quando se trabalha com referências DOM (Document
Object Model). Você pode atribuir referências DOM a variáveis locais junto com a declaração
única, como demonstra o código a seguir:
function updateElement()
{ var el = document.getElementById("resultado"),
style = el.style;

// faz algo com el e style...

Elevação: um problema com vars dispersos O

JavaScript permite que você tenha várias instruções var em qualquer lugar de uma função, e todas
agem como se as variáveis fossem declaradas no início da função. Esse comportamento é
conhecido como elevação. Isso pode levar a erros lógicos quando você usa uma variável e depois
a declara na função. Para JavaScript, desde que uma variável esteja no mesmo escopo (mesma
função), ela é considerada declarada, mesmo quando utilizada antes da declaração var . Dê uma
olhada neste exemplo:
// antipadrão
meunome = "global"; // variável global
function func()
{ alert(meunome); // "indefinido"
var meunome =
"local"; alert(meunome); // "local"

} função();

14 | Capítulo 2: Fundamentos
Machine Translated by Google

Neste exemplo, você pode esperar que o primeiro alert() solicite "global" e o segundo,
"local". É uma expectativa razoável porque, no momento do primeiro alerta, myname não foi
declarado e, portanto, a função provavelmente deveria “ver” o myname global. Mas não é
assim que funciona. O primeiro alerta dirá “indefinido” porque meunome é considerado
declarado como uma variável local para a função. (Embora a declaração venha depois.)
Todas as declarações de variáveis são levantadas para o topo da função. Portanto, para
evitar esse tipo de confusão, é melhor declarar antecipadamente todas as variáveis que
você pretende usar.
O trecho de código anterior se comportará como se tivesse sido implementado da seguinte forma:

meunome = "global"; // variável global


function func()
{ var meunome; // o mesmo que -> var meunome =
indefinido; alert(meunome); //
"indefinido"
meunome = "local"; alert(meunome); // "local"

} função();

Para completar, vamos mencionar que, na verdade, no nível de implementação, as


coisas são um pouco mais complexas. Existem dois estágios de manipulação de
código, onde variáveis, declarações de função e parâmetros formais são criados no
primeiro estágio, que é o estágio de análise e inserção do contexto. Na segunda
etapa, a etapa de execução do código em tempo de execução, são criadas as
expressões de função e os identificadores não qualificados (variáveis não
declaradas). Mas, para fins práticos, podemos adotar o conceito de hoisting, que na
verdade não é definido pelo padrão ECMAScript, mas é comumente usado para
descrever o comportamento.

Loops for Nos

loops for , você itera sobre arrays ou objetos semelhantes a arrays, como argumentos e objetos HTMLCollection .
O padrão de loop for usual se parece com o seguinte:

// loop abaixo do
ideal for (var i = 0; i < myarray.length; i++) { //
faz algo com myarray[i]
}

Um problema com esse padrão é que o comprimento da matriz é acessado a cada iteração do loop. Isso pode
tornar seu código mais lento, especialmente quando myarray não é um array, mas um objeto HTMLCollection .

HTMLCollections são objetos retornados por métodos DOM, como:

• document.getElementsByName() •

document.getElementsByClassName() •

document.getElementsByTagName()

para Loops | 15
Machine Translated by Google

Há também várias outras HTMLCollections, que foram introduzidas antes do padrão DOM e ainda estão em
uso hoje. Inclui (entre outros):

document.images
Todos os elementos IMG na página
document.links
Todos os elementos A

document.forms Todos

os formulários

document.forms[0].elements Todos
os campos no primeiro formulário da página

O problema com as coleções é que elas são consultas ao vivo no documento subjacente (a página HTML).
Isso significa que toda vez que você acessa o comprimento de qualquer coleção, está consultando o DOM
ativo e as operações do DOM são caras em geral.

É por isso que um padrão melhor para loops for é armazenar em cache o comprimento da matriz (ou coleção)
sobre a qual você está iterando, conforme mostrado no exemplo a seguir:
for (var i = 0, max = myarray.length; i < max; i++) { // faz algo
com myarray[i]
}

Dessa forma, você recupera o valor de length apenas uma vez e o utiliza durante todo o loop.

Armazenar em cache o comprimento ao iterar em HTMLCollections é mais rápido em todos os navegadores


— algo entre duas vezes mais rápido (Safari 3) e 190 vezes (IE7). (Para obter mais detalhes, consulte
JavaScript de alto desempenho por Nicholas Zakas [O'Reilly].)

Observe que, quando você pretende modificar explicitamente a coleção no loop (por exemplo, adicionando
mais elementos DOM), provavelmente gostaria que o comprimento fosse atualizado e não
constante.

Seguindo o padrão var único , você também pode retirar o var do loop e fazer o loop como: function looper()
{ var i = 0, max,

minhaarray = [];

// ...

for (i = 0, max = myarray.length; i < max; i++) { //


fazer algo com myarray[i]
}
}

Esse padrão tem o benefício da consistência porque você se apega ao padrão de var único .
Uma desvantagem é que torna um pouco mais difícil copiar e colar loops inteiros durante a refatoração do
código. Por exemplo, se você estiver copiando o loop de uma função para outra,

16 | Capítulo 2: Fundamentos
Machine Translated by Google

você deve certificar-se de também transportar i e max para a nova função (e provavelmente excluí-los da
função original se eles não forem mais necessários lá).

Um último ajuste no loop seria substituir i++ por qualquer uma dessas expressões:

i=i+1i
+= 1

JSLint solicita que você faça isso; a razão é que ++ e -- promovem “excessiva astúcia”. Se você discordar
disso, pode definir a opção JSLint plusplus como false. (É verdadeiro por padrão.) Mais adiante no livro, o
último padrão é usado: i += 1.

Duas variações do padrão for introduzem algumas microotimizações porque:

• Use uma variável a menos (sem

máximo) • Contagem regressiva até 0, que geralmente é mais rápida porque é mais eficiente comparar
com 0 do que com o comprimento da matriz ou com qualquer coisa diferente de 0

O primeiro padrão modificado é:


var i, myarray = [];

for (i = myarray.length; i--;) {


// faz algo com myarray[i]
}

E o segundo usa um loop while : var


myarray = [], i =
myarray.length;

while (i--) { //
faz algo com myarray[i]
}

Estas são micro-otimizações e só serão notadas em operações de desempenho crítico. Além disso, o
JSLint reclamará sobre o uso de i--.

For-in Loops
loops for-in devem ser usados para iterar sobre objetos não array. O loop com for-in também é chamado
de enumeração.

Tecnicamente, você também pode usar for-in para percorrer arrays (porque em JavaScript arrays são
objetos), mas isso não é recomendado. Isso pode levar a erros lógicos se o objeto de matriz já tiver sido
aumentado com funcionalidade personalizada. Além disso, a ordem (a sequência) de listagem das
propriedades não é garantida em um for-in. Portanto, é preferível usar loops for normais com arrays e loops
for-in para objetos.

É importante usar o método hasOwnProperty() ao iterar sobre as propriedades do objeto para filtrar as
propriedades que vêm da cadeia de protótipos.

Loops for-in | 17
Machine Translated by Google

Considere o seguinte exemplo: // o


objeto var
man =
{ mãos:
2,
pernas: 2, cabeças: 1
};

// em algum outro lugar no


código // um método foi adicionado a
todos os objetos if (typeof Object.prototype.clone ===
"undefined") { Object.prototype.clone = function () {};
}

Neste exemplo, temos um objeto simples chamado man definido com um objeto literal. Em
alguns lugares, antes ou depois de man ser definido, o protótipo do objeto foi aumentado com
um método útil chamado clone(). A cadeia de protótipos está ativa, o que significa que todos
os objetos obtêm acesso automaticamente ao novo método. Para evitar que o método clone()
apareça ao enumerar man, você precisa chamar hasOwnProperty() para filtrar as propriedades
do protótipo. Deixar de fazer a filtragem pode resultar na exibição da função clone() , que é
um comportamento indesejado em quase todos os cenários:
//
1. // loop for-in
for (var i in man) { if
(man.hasOwnProperty(i)) { // filtro
console.log(i, ":", man[i]);
}

}/
* resultado no console
mãos: 2
pernas:
2 cabeças:
1 */

//
2. // antipadrão: //
loop for-in sem verificar hasOwnProperty() for (var i in
man) { console.log(i,
":", man[i]);

} /*
resultado no console
mãos: 2
pernas:
2 cabeças: 1
clonar: função()
*/

18 | Capítulo 2: Fundamentos
Machine Translated by Google

Outro padrão para usar hasOwnProperty() é chamar esse método de Object.prototype , assim:

for (var i in man) { if


(Object.prototype.hasOwnProperty.call(man, i)) { // filtro
console.log(i, ":", man[i]);
}
}

O benefício é que você pode evitar nomear colisões caso o objeto man tenha redefinido
hasOwnProperty. Além disso, para evitar as longas pesquisas de propriedade até Object, você
pode usar uma variável local para "cachear":

var i,
hasOwn = Object.prototype.hasOwnProperty;
for (i in man) { if
(hasOwn.call(man, i)) { // filtro
console.log(i, ":", man[i]);
}
}

Estritamente falando, não usar hasOwnProperty() não é um erro. Dependendo da tarefa


e da confiança que você tem no código, você pode ignorá-la e acelerar um pouco os
loops. Mas quando você não tem certeza sobre o conteúdo do objeto (e sua cadeia de
protótipos), é mais seguro apenas adicionar a verificação hasOwnProperty() .

Uma variação de formatação (que não passa JSLint) pula uma chave e coloca o if na mesma linha.
O benefício é que a instrução do loop parece mais um pensamento completo (“para cada elemento
que possui uma propriedade própria X, faça algo com X”). Além disso, há menos recuo antes de
chegar ao objetivo principal do loop:
// Aviso: não passa JSLint var i,

hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // filtro
console.log(i, ":", man[i]);
}

(Não) Aumentando Protótipos Integrados


Aumentar a propriedade de protótipo das funções do construtor é uma maneira poderosa de
adicionar funcionalidade, mas às vezes pode ser muito poderoso.

É tentador aumentar os protótipos de construtores integrados, como Object(), Array() ou Function(),


mas isso pode prejudicar seriamente a manutenção, porque tornará seu código menos previsível.
Outros desenvolvedores que usam seu código provavelmente esperarão que os métodos
JavaScript integrados funcionem consistentemente e não esperarão suas adições.

(Não) Aumentando Protótipos Embutidos | 19


Machine Translated by Google

Além disso, as propriedades que você adiciona ao protótipo podem aparecer em loops que não
usam hasOwnProperty(), portanto, podem criar confusão.

Portanto, é melhor não aumentar os protótipos integrados. Você pode fazer uma exceção da regra
somente quando todas estas condições forem atendidas:

1. Espera-se que versões futuras de ECMAScript ou implementações de JavaScript implementem


essa funcionalidade como um método integrado de forma consistente. Por exemplo, você
pode adicionar métodos descritos em ECMAScript 5 enquanto espera que os navegadores
os atualizem. Nesse caso, você está apenas definindo os métodos úteis com antecedência.
2. Você verifica se sua propriedade ou método personalizado já não existe — talvez já
implementado em algum outro lugar no código ou já faça parte do mecanismo JavaScript de
um dos navegadores compatíveis.
3. Você documenta e comunica claramente a mudança à equipe.

Se essas três condições forem atendidas, você pode prosseguir com a adição personalizada ao
protótipo, seguindo este padrão: if
(typeof Object.protoype.myMethod !== "function")
{ Object.protoype.myMethod = function ()
{ // implementação...
};
}

alternar padrão
Você pode melhorar a legibilidade e robustez de suas instruções switch seguindo este padrão:

var inspecionar_me = 0,
resultado = '';

alternar (inspecionar_me) {
caso 0:
resultado = "zero";
quebrar;
caso 1:
resultado = "um";
quebrar;
padrão:
resultado = "desconhecido";
}

As convenções de estilo seguidas neste exemplo simples são:

• Alinhando cada caso com chave (uma exceção à regra de recuo de chaves). • Recuar o código
dentro de cada caso. • Terminar cada caso
com uma pausa clara;

20 | Capítulo 2: Fundamentos
Machine Translated by Google

• Evitar falhas (quando você omite a quebra intencionalmente). Se você está absolutamente
convencido de que um fall-through é a melhor abordagem, certifique-se de documentar tais
casos, porque eles podem parecer erros para os leitores de seu código. • Terminar a
troca com um padrão: para garantir que sempre haja um resultado são, mesmo
se nenhum dos casos coincidir.

Evitando Typecasting implícito


O JavaScript faz typecasts implicitamente de variáveis quando você as compara. É por isso que
comparações como false == 0 ou"" == 0 retorna verdadeiro.

Para evitar confusão causada pelo typecasting implícito, sempre use os operadores === e !== que
verificam os valores e o tipo das expressões que você compara:
var zero = 0;
if (zero === false) { //
não está executando porque zero é 0, não é falso
}

// antipadrão if
(zero == false) { //
este bloco é executado...
}

Há outra escola de pensamento que concorda com a opinião de que é redundante usar ===
quando == é suficiente. Por exemplo, quando você usa typeof , sabe que ele retorna uma string,
então não há razão para usar igualdade estrita. No entanto, JSLint requer igualdade estrita; faz
com que o código pareça consistente e reduz o esforço mental ao ler o código. (“Isso é ==
intencional ou uma omissão?”)

Evitando eval()
Se você identificar o uso de eval() em seu código, lembre-se do mantra “eval() é mau”. Essa função
pega uma string arbitrária e a executa como código JavaScript. Quando o código em questão é
conhecido de antemão (não determinado em tempo de execução), não há razão para usar eval().
Se o código for gerado dinamicamente em tempo de execução, geralmente há uma maneira melhor
de atingir a meta sem eval(). Por exemplo, apenas usar a notação de colchetes para acessar
propriedades dinâmicas é melhor e mais simples:
// antipadrão
var propriedade =
"nome"; alert(eval("obj." + propriedade));

// propriedade
var preferida = "nome";
alert(obj[propriedade]);

O uso de eval() também tem implicações de segurança, porque você pode estar executando um
código (por exemplo, vindo da rede) que foi adulterado. Isso é comum

Evitando Typecasting implícito | 21


Machine Translated by Google

antipadrão ao lidar com uma resposta JSON de uma solicitação Ajax. Nesses casos, é melhor
usar os métodos integrados dos navegadores para analisar a resposta JSON e garantir que ela
seja segura e válida. Para navegadores que não suportam JSON.parse() nativamente, você
pode usar uma biblioteca de JSON.org.

Também é importante lembrar que passar strings para setInterval(), setTimeout() e o construtor
Function() é, na maior parte, semelhante ao uso de eval() e, portanto, deve ser evitado. Nos
bastidores, o JavaScript ainda precisa avaliar e executar a string que você passa como código
de programação:
// antipadrões
setTimeout("myFunc()", 1000);
setTimeout("minhaFunc(1, 2, 3)", 1000);

// preferencial
setTimeout(myFunc, 1000);
setTimeout(function ()
{ myFunc(1, 2,
3); }, 1000);

Usar o novo construtor Function() é semelhante a eval() e deve ser abordado com cuidado. Pode
ser uma construção poderosa, mas muitas vezes é mal utilizada. Se for absolutamente necessário
usar eval(), considere usar new Function() . Há um pequeno benefício potencial porque o código
avaliado em new Function() será executado em um escopo de função local, portanto, quaisquer
variáveis definidas com var no código que está sendo avaliado não se tornarão globais
automaticamente. Outra maneira de evitar globais automáticas é agrupar a chamada eval() em
uma função imediata (mais sobre funções imediatas no Capítulo 4).

Considere o seguinte exemplo. Aqui apenas un permanece como uma variável global poluindo
o namespace:
console.log(typeof un); // "indefinido"
console.log(typeof deux); // "indefinido"
console.log(typeof trois); // "indefinido"

var jsstring = "var un = 1; console.log(un);";


eval(jsstring); // registra "1"

jsstring = "var deux = 2; console.log(deux);"; nova


Função(jsstring)(); // registra "2"

jsstring = "var trois = 3; console.log(trois);"; (função


()
{ eval(jsstring); }
()); // registra "3"

console.log(typeof un); // "número"


console.log(typeof deux); // "indefinido"
console.log(typeof trois); // "indefinido"

Outra diferença entre eval() e o construtor Function é que eval() pode interferir na cadeia de
escopo, enquanto Function é muito mais restrito. Não importa onde você execute Function, ele
vê apenas o escopo global. Então ele pode fazer menos variável local

22 | Capítulo 2: Fundamentos
Machine Translated by Google

poluição. No exemplo a seguir, eval() pode acessar e modificar uma variável em seu escopo externo,
enquanto Function não pode (observe também que usar Function ou new Function é idêntico):

(function ()
{ var local = 1;
eval("local = 3; console.log(local)"); // registra 3
console.log(local); // registra
3 }());

(function ()
{ var local = 1;
Function("console.log(typeof local);")(); // logs undefined }());

Conversões de número com parseInt()

Usando parseInt() você pode obter um valor numérico de uma string. A função aceita um segundo
parâmetro radix, que geralmente é omitido, mas não deveria ser. Os problemas ocorrem quando a string
a ser analisada começa com 0: por exemplo, uma parte de uma data inserida em um campo de formulário.
Strings que começam com 0 são tratadas como números octais (base 8) em ECMAScript 3; no entanto,
isso mudou no ES5. Para evitar inconsistência e resultados inesperados, sempre especifique o parâmetro
radix:
var mês = "06",
ano = "09";
mês = parseInt(mês, 10);
ano = parseInt(ano, 10);

Neste exemplo, se você omitir o parâmetro radix como parseInt(year), o valor retornado será 0, porque
“09” assume o número octal (como se você fizesse parseInt(year, 8)) e 09 não é um dígito válido em
básico 8.

Maneiras alternativas de converter uma string em um número incluem:


+"08" // resultado é 8
Número("08") // 8

Geralmente são mais rápidos que parseInt(), porque parseInt(), como o nome sugere, analisa e não
simplesmente converte. Mas se você está esperando uma entrada como “08 hello”, parseInt() retornará
um número, enquanto os outros falharão com NaN.

Convenções de Codificação

É importante estabelecer e seguir convenções de codificação — elas tornam seu código consistente,
previsível e muito mais fácil de ler e entender. Um novo desenvolvedor que ingressa na equipe pode ler
as convenções e ser produtivo muito mais cedo, entendendo o código escrito por qualquer outro membro
da equipe.

Convenções de Codificação | 23
Machine Translated by Google

Muitos flamewars foram travados em reuniões e em listas de discussão sobre aspectos específicos
de certas convenções de codificação (por exemplo, o recuo do código — tabulações ou espaços?).
Portanto, se é você quem está sugerindo a adoção de convenções em sua organização, esteja
preparado para enfrentar resistências e ouvir opiniões diferentes, mas igualmente fortes. Lembre-
se de que é muito mais importante estabelecer e seguir consistentemente uma convenção,
qualquer convenção, do que quais serão os detalhes exatos dessa convenção.

Recuo
Código sem recuo é impossível de ler. A única coisa pior é o código com indentação
inconsistente, porque parece que está seguindo uma convenção, mas pode ter surpresas
confusas ao longo do caminho. É importante padronizar o uso da indentação.

Alguns desenvolvedores preferem recuo com tabulações, porque qualquer um pode ajustar seu
editor para exibir as tabulações com o número de espaços individualmente preferido. Alguns
preferem espaços - geralmente quatro. Não importa, desde que todos na equipe sigam a mesma
convenção. Este livro, por exemplo, usa indentação de quatro espaços, que também é o padrão
em JSLint.

E o que você deve recuar? A regra é simples — qualquer coisa entre chaves. Isso
significa os corpos das funções, loops (do, while, for, for-in), ifs, switches e propriedades
do objeto na notação literal do objeto. O código a seguir mostra alguns exemplos de
uso de indentação:
function outer(a, b)
{ var c =
1, d
= 2,
inner; if (a
> b) { interno =
função
() { return { r: c - d
};

}; } else
{ interno = function ()
{ return
{ r: c + d
};
};

} return interior;
}

Chaves Chaves

sempre devem ser usadas, mesmo nos casos em que são opcionais. Tecnicamente, se você tiver
apenas uma instrução em um if ou for, as chaves não são necessárias, mas você

24 | Capítulo 2: Fundamentos
Machine Translated by Google

deve sempre usá-los de qualquer maneira. Isso torna o código mais consistente e fácil de atualizar.

Imagine que você tenha um loop for com apenas uma instrução. Você pode omitir as chaves e não
haverá erro de sintaxe: // prática
ruim para (var i =
0; i < 10; i += 1) alert(i);

Mas e se, mais tarde, você adicionar outra linha no corpo do loop?

// prática ruim para


(var i = 0; i < 10; i += 1) alert(i); alerta(i
+
" "
é + (i % 2 ? "ímpar" : "par"));

O segundo alerta está fora do loop, embora o recuo possa enganá-lo. A melhor coisa a fazer a longo
prazo é sempre usar as chaves, mesmo para blocos de uma linha:
// melhor
for (var i = 0; i < 10; i += 1) { alert(i);

Da mesma forma para condições


if : //
ruim if
(true)

alert(1); senão alert(2);

// melhor if
(true)
{ alert(1); }
else
{ alerta(2);
}

Localização da chave de

abertura Os desenvolvedores também tendem a ter preferências sobre onde a chave de abertura deve
estar - na mesma linha ou na linha seguinte? if
(verdadeiro)
{ alert("É VERDADE!");
}

Ou:

se for verdade)
{
alert("É VERDADE!");
}

Convenções de Codificação | 25
Machine Translated by Google

Neste exemplo específico, é uma questão de preferência, mas há casos em que o programa pode se
comportar de maneira diferente dependendo de onde está a chave. Isso ocorre por causa do
mecanismo de inserção de ponto-e-vírgula — o JavaScript não é exigente quando você escolhe não
terminar suas linhas corretamente com um ponto-e-vírgula e o adiciona para você. Esse comportamento
pode causar problemas quando uma função retorna um objeto literal e a chave de abertura está na
próxima linha:

// aviso: valor de retorno inesperado


function func()
{ return
{
Nome: "Batman"
};
}

Se você espera que esta função retorne um objeto com uma propriedade name , ficará surpreso.
Devido aos pontos e vírgulas implícitos, a função retorna indefinido. O código anterior é equivalente a
este: // aviso: valor de retorno
inesperado function func() {

retornar
indefinido; // código inacessível
segue... {
Nome: "Batman"
};
}

Em conclusão, sempre use chaves e sempre coloque a abertura na mesma linha da instrução anterior:
function func() { return { name:
"Batman"

};
}

Uma observação sobre ponto e vírgula: Assim como com as chaves, você sempre deve
usar ponto e vírgula, mesmo quando estão implícitos nos analisadores JavaScript.
Isso não apenas promove a disciplina e uma abordagem mais rigorosa do código, mas
também ajuda a resolver ambiguidades, como mostra o exemplo anterior.

Espaço em

branco O uso de espaço em branco também pode contribuir para melhorar a legibilidade e a
consistência do código. Em frases escritas em inglês, você usa intervalos após vírgulas e pontos. Em
JavaScript, você segue a mesma lógica e adiciona intervalos após expressões tipo lista (equivalente
a vírgulas) e declarações finais (equivalente a completar um “pensamento”).

26 | Capítulo 2: Fundamentos
Machine Translated by Google

Bons lugares para usar um espaço em branco incluem:

• Após o ponto-e-vírgula que separa as partes de um loop for : por exemplo, for (var i
= 0; i < 10; i += 1) {...}
• Inicializando múltiplas variáveis (i e max) em um loop for : for (var i = 0, max = 10; i < max; i += 1) {...}

• Após as vírgulas que delimitam os itens do array: var a = [1, 2, 3]; • Depois de

vírgulas em propriedades de objetos e depois de dois-pontos que dividem nomes de propriedades e seus
valores: var o = {a: 1, b: 2}; • Delimitando

argumentos de função: myFunc(a, b, c) • Antes das chaves em

declarações de função: function myFunc() {} • Depois de função em expressões de função

anônima: var myFunc = function () {};

Outro bom uso para o espaço em branco é separar todos os operadores e seus operandos com espaços, o que
basicamente significa usar um espaço antes e depois de +, -, *, =, <, >, <=, >=, ===, != =, &&, ||, +=, e assim por
diante: // espaçamento

generoso e consistente // torna o código


mais fácil de ler // permitindo que ele
"respire" var d = 0, a = b + 1; se
(a && b &&
c) { d = a %
c; a += d;

// antipadrão //
espaços ausentes ou inconsistentes //
tornam o código confuso var
d= 0, a
=b+1;
se (a&& b&&c)
{ d=a
%c; a+= d;
}

E uma observação final sobre espaço em branco — espaçamento entre chaves. É bom usar um espaço:

• Antes de abrir chaves ({) em funções, casos if-else , loops e objetos literais • Entre a chave de fechamento (})

e else ou while

Um argumento contra o uso liberal de espaço em branco pode ser o aumento do tamanho do arquivo, mas a
redução (discutida posteriormente no capítulo) cuida dessa questão.

Um aspecto frequentemente negligenciado da legibilidade do código é o uso de espaço em branco vertical.


Você pode usar linhas em branco para separar unidades de código, assim como os parágrafos são usados
na literatura para separar ideias.

Convenções de Codificação | 27
Machine Translated by Google

Convenções de nomenclatura

Outra maneira de tornar seu código mais previsível e sustentável é adotar convenções de
nomenclatura. Isso significa escolher nomes para suas variáveis e funções de maneira consistente.

Abaixo estão algumas sugestões de convenção de nomenclatura que você pode adotar como está
ou ajustar ao seu gosto. Mais uma vez, ter uma convenção e segui-la consistentemente é muito
mais importante do que o que essa convenção realmente é.

Capitalizando Construtores
JavaScript não tem classes, mas tem funções construtoras invocadas com new:
var adam = new Pessoa();

Como os construtores ainda são apenas funções, ajuda se você puder dizer, apenas olhando para
o nome de uma função, se ela deveria se comportar como um construtor ou como uma função
normal.

Nomear construtores com uma primeira letra maiúscula fornece essa dica. O uso de letras
minúsculas para funções e métodos indica que eles não devem ser chamados com new:
function MeuConstrutor() {...}
function minhaFunção() {...}

No próximo capítulo, há alguns padrões que permitem forçar programaticamente seus construtores
a se comportarem como construtores, mas simplesmente seguir a convenção de nomenclatura é
útil por si só, pelo menos para programadores que leem o código-fonte.

Separando palavras

Quando você tem várias palavras em um nome de variável ou função, é uma boa ideia seguir uma
convenção sobre como as palavras serão separadas. Uma convenção comum é usar o chamado
estojo de camelo. Seguindo a convenção camel case, você digita as palavras em minúsculas,
apenas capitalizando a primeira letra de cada palavra.

Para seus construtores, você pode usar letras maiúsculas, como em MyConstructor(), e para nomes
de funções e métodos, você pode usar letras minúsculas, como em myFunction(), calculateArea()
e getFirstName().

E as variáveis que não são funções? Os desenvolvedores geralmente usam letras minúsculas para
nomes de variáveis, mas outra boa ideia é usar todas as palavras em minúsculas delimitadas por
um sublinhado: por exemplo, first_name, favorite_bands e old_company_name. Essa notação ajuda
você a distinguir visualmente entre funções e todos os outros identificadores — primitivos e objetos.

28 | Capítulo 2: Fundamentos
Machine Translated by Google

ECMAScript usa maiúsculas e minúsculas para métodos e propriedades, embora os nomes de propriedades
com várias palavras sejam raros (propriedades lastIndex e ignoreCase de objetos de expressão regular).

Outros padrões de nomenclatura

Às vezes, os desenvolvedores usam uma convenção de nomenclatura para criar ou substituir recursos de
linguagem.

Por exemplo, não há como definir constantes em JavaScript (embora existam algumas incorporadas, como
Number.MAX_VALUE), então os desenvolvedores adotaram a convenção de usar letras maiúsculas para
nomear variáveis que não devem mudar de valor durante a vida do programa, como:

// constantes preciosas, por favor não toque


var PI = 3.14,
MAX_WIDTH = 800;
Há outra convenção que compete pelo uso de letras maiúsculas: usar letras maiúsculas para nomes de
variáveis globais. Nomear globais com letras maiúsculas pode reforçar a prática de minimizar seu número e
pode torná-los facilmente distinguíveis.

Outro caso de uso de uma convenção para imitar a funcionalidade é a convenção de membros privados.
Embora você possa implementar a verdadeira privacidade em JavaScript, às vezes os desenvolvedores
acham mais fácil usar apenas um prefixo de sublinhado para denotar um método ou propriedade privada.
Considere o seguinte exemplo:

var pessoa =
{ getName: function () ' '
{ return this._getFirst() + + this._getLast();
},
_getFirst: function () { // ...

},
_getLast: function () { // ...

}
};
Neste exemplo, getName() deve ser um método público, parte da API estável, enquanto _getFirst() e _getLast()
devem ser privados. Eles ainda são métodos públicos normais, mas usar o prefixo de sublinhado avisa os
usuários do objeto pessoa que esses métodos não têm garantia de funcionar no próximo lançamento e não
devem ser usados diretamente. Observe que o JSLint reclamará dos prefixos de sublinhado, a menos que
você defina a opção nomen: false.

A seguir estão algumas variedades da convenção _private :

• Usando um sublinhado à direita para significar privado, como em name_ e getElements_() • Usando

um prefixo de sublinhado para propriedades _protected e dois para __private


propriedades

Convenções de nomenclatura | 29
Machine Translated by Google

• No Firefox, algumas propriedades internas que não fazem parte tecnicamente da linguagem estão
disponíveis e são nomeadas com um prefixo de dois sublinhados e um sufixo de dois sublinhados,
como __proto__ e __parent__

Escrevendo comentários

Você tem que comentar seu código, mesmo que seja improvável que alguém que não seja você o
toque. Muitas vezes, quando você está profundamente envolvido em um problema, pensa que é óbvio
o que o código faz, mas quando volta ao código depois de uma semana, tem dificuldade em lembrar
como funcionou exatamente.

Você não deve exagerar comentando o óbvio: cada variável ou linha. Mas você geralmente precisa
documentar todas as funções, seus argumentos e valores de retorno, e também qualquer algoritmo ou
técnica interessante ou incomum. Pense nos comentários como dicas para os futuros leitores do
código; os leitores precisam entender o que seu código faz sem ler muito mais do que apenas os
comentários e os nomes das funções e propriedades. Quando você tem, por exemplo, cinco ou seis
linhas de código executando uma tarefa específica, o leitor pode pular os detalhes do código se você
fornecer uma descrição de uma linha descrevendo a finalidade do código e por que ele está ali . Não
há nenhuma regra rígida e rápida ou proporção de comentários para código; algumas partes do código
(pense em expressões regulares) podem exigir mais comentários do que código.

O hábito mais importante, mas o mais difícil de seguir, é manter os comentários


atualizados, porque comentários desatualizados podem enganar e ser muito piores do que
nenhum comentário.

E como você verá na próxima seção, os comentários podem ajudá-lo a gerar automaticamente a
documentação.

Escrevendo documentos da API

A maioria dos desenvolvedores considera escrever documentação uma tarefa chata e pouco
recompensadora. Mas isso não tem que ser o caso. A documentação da API pode ser gerada
automaticamente a partir de comentários no código. Dessa forma, você pode ter a documentação
escrita sem realmente escrevê-la. A maioria dos programadores acha essa ideia fascinante, porque a
geração automática de uma referência legível a partir de palavras-chave específicas e “comandos”
especialmente formatados se parece muito com a programação real.

Tradicionalmente, os documentos de API vêm do mundo Java, onde um utilitário chamado javadoc é
distribuído junto com o Java SDK (Software Developer Kit). Mas a mesma ideia foi portada para muitos
outros idiomas. E para JavaScript existem duas excelentes ferramentas, gratuitas e de código aberto: o
JSDoc Toolkit (http:// code.google.com/ p/ jsdoc-toolkit/) e YUIDoc (http:// yuilibrary.com/ projects/ yuidoc).

30 | Capítulo 2: Fundamentos
Machine Translated by Google

O processo de geração da documentação da API inclui:

• Escrever blocos de código especialmente formatados •

Executar uma ferramenta para analisar o código e os comentários •

Publicar os resultados da ferramenta, que geralmente são páginas HTML

A sintaxe especial que você precisa aprender consiste em cerca de uma dúzia de tags, que se parecem com isto:

/**
*
@valor da etiqueta
*/

Por exemplo, digamos que você tenha uma função chamada reverse() que inverte uma string. Pega um parâmetro
string e no final retorna outra string. A documentação poderia ser assim:

/**
* Inverte uma string
*
*
@param {String} string de entrada para reverter
* @return {String} A string invertida */ var

reverse = function (input) { // ... return


output;

};

Você pode ver que @param é a tag para parâmetros de entrada e @return é a tag usada para documentar valores
de retorno. A ferramenta de documentação analisa essas tags e produz um conjunto de documentos HTML bem
formatados no final.

YUIDoc Exemplo YUIDoc

foi originalmente criado com o objetivo de documentar a biblioteca YUI (Yahoo! User Interface), mas pode ser usado
para qualquer projeto. Ele possui algumas convenções que você deve seguir para fazer o melhor uso da ferramenta,
por exemplo, a noção de módulos e classes. (Embora não existam classes em JavaScript.)

Vamos ver um exemplo completo de geração de documentação usando YUIDoc.

A Figura 2-1 mostra uma prévia da documentação bem formatada que você obterá no final.
Na verdade, você pode ajustar o modelo HTML para torná-lo ainda melhor e mais personalizado para as necessidades,
a aparência e o comportamento do seu projeto.

Para uma demonstração ao vivo do exemplo, visite http:// jspatterns.com/ book/ 2/.

Neste exemplo, todo o aplicativo consiste em apenas um arquivo (app.js) com apenas um módulo (myapp) nele.
Você aprenderá mais sobre os módulos nos próximos capítulos, mas, por enquanto, apenas pense no módulo como
uma tag de comentário necessária para fazer o YUIDoc funcionar.

Escrevendo Documentos API | 31


Machine Translated by Google

Figura 2-1. Documentação gerada por YUIDoc

O conteúdo de app.js começa assim:


/**
*
Meu aplicativo JavaScript
*
* @module
meuaplicativo */

Em seguida, você define um objeto em branco para usar como namespace:

var MYAPP = {};

E então você define um objeto math_stuff que tem dois métodos: sum() e multi():
/**
* Um utilitário de matemática
*
@namespace
MYAPP * @class

math_stuff */ MYAPP.math_stuff = {

/**
* Soma dois números

32 | Capítulo 2: Fundamentos
Machine Translated by Google

*
* @method
*
sum @param {Number} a Primeiro
*
número @param {Number} b O
segundo número * @return {Number} A soma

das duas entradas */


sum: function (a, b) { return a + b;
},

/**
* Multiplica dois números
*
* @método multi
*
@param {Number} a Primeiro
*
número @param {Number} b O segundo
número * @return {Number} As duas entradas

multiplicadas */ multi:
function (a,*b;
b) { return a
}
};

E isso completa a primeira “aula”. Observe as tags destacadas:

@namespace
A referência global que contém seu objeto.
@class
Um nome impróprio (sem classes em JavaScript) que é usado para significar um objeto ou
uma função construtora.
@method
Define um método em um objeto e especifica o nome do método.
@param
Lista os argumentos que uma função usa. Os tipos dos parâmetros estão entre chaves,
seguidos do nome do parâmetro e sua descrição.
@return
Assim como @param, apenas descreve o valor retornado pelo método e não possui nome.

Para a segunda “classe”, vamos usar uma função construtora e adicionar um método ao seu
protótipo, apenas para ter uma ideia de como o sistema de documentação funciona com as
diferentes formas de criar objetos:
/**
* Constrói objetos Person *
@class Person
* @constructor
*
@namespace
*
MYAPP @param {String} primeiro
*
Nome @param {String} último

Sobrenome */ MYAPP.Person = function (first, last) {

Escrevendo Documentos API | 33


Machine Translated by Google

/**
* Nome da pessoa
*
@property first_name
*
@type String
*/
this.first_name = first; /** *

Último nome (família) da pessoa @property


*
last_name @type String
*
*/ this.last_name
=
last;

};

/**
* Retorna o nome do objeto pessoa
*
* @method
getName * @return {String} O nome da

pessoa */ MYAPP.Person.prototype.getName
' ' =
function () { return this.first_name + + this.last_name;
};

Na Figura 2-1, você pode ver como é a documentação gerada para esse construtor Person . As partes destacadas
aqui são:

• @constructor indica que esta “classe” é na verdade uma função construtora • @property e

@type descrevem as propriedades de um objeto O sistema YUIDoc é

independente de linguagem e apenas analisa os blocos de comentários, não o código JavaScript. A desvantagem
é que você precisa declarar os nomes das propriedades, parâmetros e métodos nos comentários, por exemplo,
@property first_name. O benefício é que, quando você se sentir confortável com ele, poderá usar o mesmo
sistema para documentar o código em qualquer idioma.

Escrever para ser lido


Escrever os comentários para os blocos de documentos da API não é apenas uma maneira preguiçosa de fornecer
documentação de referência, mas também serve a outro propósito - melhorar a qualidade do código fazendo com
que você revisite seu código.

Qualquer escritor ou editor lhe dirá que a edição é importante: provavelmente o passo mais importante na produção
de um bom livro ou artigo. Colocar no papel é apenas o primeiro passo, o primeiro rascunho. O rascunho comunica
algumas informações ao leitor, mas provavelmente não da maneira mais clara, estruturada ou fácil de seguir.

O mesmo se aplica à escrita de código. Quando você se senta e resolve um problema, essa solução é apenas um
primeiro rascunho. Ele produz a saída desejada, mas o faz da melhor maneira?
É fácil de ler, entender, manter e atualizar? Quando você revisita seu código, de preferência

34 | Capítulo 2: Fundamentos
Machine Translated by Google

depois de algum tempo, você certamente verá áreas para melhoria - maneiras de tornar o código mais fácil
de seguir, remover algumas ineficiências e assim por diante. Isso é essencialmente edição e pode ajudar
enormemente em seu objetivo de criar código de alta qualidade. Mas, na maioria das vezes, trabalhamos com
prazos apertados (“aqui está o problema, a solução é para ontem”) e não há tempo para edição. É por isso
que escrever documentos de API é uma oportunidade de edição.

Muitas vezes, quando você escreve blocos de comentários de documentos, você revisita o problema. Às
vezes, revisitar deixa claro que, por exemplo, esse terceiro parâmetro para um método é realmente necessário
com mais frequência do que o segundo, e o segundo quase sempre terá como padrão true, então faz sentido
ajustar a interface do método e trocá-los.

Escrever para ser lido significa escrever código, ou mesmo apenas a API, com a ideia de que outra pessoa o
lerá. Esse fato por si só fará com que você edite e pense em maneiras melhores de resolver o problema que
tem em mãos.

Falando sobre os primeiros rascunhos, também há a ideia de “planejar jogar um fora”. Pode parecer um pouco
exagerado no começo, mas faz muito sentido, especialmente quando você tem um projeto de missão crítica
em mãos (e vidas humanas dependem disso). A ideia é que a primeira solução encontrada seja jogada fora e
você comece do zero. A primeira solução pode ser uma solução funcional, mas nada mais é do que um
rascunho, um exemplo de como resolver um problema. A segunda solução sempre será melhor, porque agora
você tem uma compreensão muito mais profunda do problema. Na segunda solução, você também não tem
permissão para copiar e colar da primeira, o que ajuda a evitar que você use atalhos ou se decida pela
solução não perfeita.

Avaliações por pares

Outra maneira de melhorar seu código é fazer com que ele seja revisado por pares. As revisões por pares
podem ser formais e padronizadas, até mesmo auxiliadas por ferramentas especializadas, e essa é uma
ótima maneira de tornar as revisões uma parte simplificada do processo de desenvolvimento. Mas não ter
tempo para pesquisar e adotar ferramentas de revisão não deve atrapalhar. Você pode simplesmente pedir
ao desenvolvedor ao seu lado para dar uma olhada no seu código ou pode orientá-lo.

Novamente, assim como escrever documentos de API ou qualquer tipo de documentação, as revisões por
pares ajudam você a escrever um código mais claro, simplesmente porque você sabe que alguém precisará
ler e entender o que você está fazendo.

As revisões por pares são uma boa prática não apenas porque o código resultante é melhor, mas também
porque o revisor e o criador compartilham e trocam conhecimento e aprendem com as experiências e
abordagens individuais de cada um.

Se você é uma empresa individual e não tem colegas para revisar seu código, isso também não deve ser um
obstáculo. Você sempre pode abrir o código de pelo menos uma parte do código ou simplesmente blogar
sobre uma parte interessante dele e, dessa forma, fazer com que o mundo seja seu revisor.

Avaliações por pares | 35


Machine Translated by Google

Outra boa prática é fazer com que seu sistema de controle de código-fonte (CVS, Subversion, Git)
envie uma notificação por e-mail à equipe sempre que alguém verificar o código. A maioria desses
e-mails não será lida, mas de vez em quando uma revisão espontânea por pares pode acontecer
quando alguém decide fazer uma pausa no trabalho e dar uma olhada no código que você acabou
de fazer check-in.

Minimizar…em produção
Minificação é o processo de eliminação de espaços em branco, comentários e outras partes não
essenciais do código JavaScript para diminuir o tamanho dos arquivos JavaScript que precisam
ser transferidos do servidor para o navegador. Isso geralmente é feito por uma ferramenta (um
minificador), como o Yahoo! YUICompressor ou Google's Closure Compiler e ajuda a acelerar o
tempo de carregamento da página. É importante reduzir os scripts prontos para produção porque
isso resulta em economia significativa, muitas vezes reduzindo o tamanho pela metade.

Aqui está uma amostra de um código minificado (isso faz parte do utilitário de eventos da Biblioteca
YUI2):
YAHOO.util.CustomEvent=function(D,C,B,A){this.type=D;this.scope=C||window;this.silent
=B;this.signature=A||YAHOO.util.CustomEvent .LIST;this.subscribers=[];if(!this.silent) {}var
E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new
YAHOO.util.CustomEvent(E,this, verdadeiro);}...

Além de remover espaços em branco, novas linhas e comentários, os minificadores também


renomeiam as variáveis para nomes mais curtos (mas somente quando for seguro fazê-lo), como
os parâmetros D, C, B, A no código anterior . Minificadores podem renomear apenas variáveis
locais porque renomear globais pode quebrar o código. É por isso que é uma boa prática usar
variáveis locais sempre que possível. Se você usar uma variável global, como uma referência
DOM, mais de uma ou duas vezes em uma função, é uma boa prática atribuí-la a uma variável local.
Isso agilizará as pesquisas ao resolver um nome de variável e, portanto, o código ficará mais
rápido durante o tempo de execução, além de minificar melhor e ser baixado mais rapidamente.

Como observação, o Google Closure Compiler também tentará minimizar as variáveis globais (no
modo “avançado”), o que é arriscado e requer alguma atenção e disciplina de sua parte para
aproveitar essa minificação extra.

Minificar seu código de produção é importante porque ajuda no desempenho da página, mas você
deve deixar esse trabalho para o minificador. É um erro tentar escrever um código pré-minificado.
Você sempre deve usar nomes de variáveis descritivos, espaço em branco e recuo consistentes,
comentários e assim por diante. O código que você escreve será lido (por humanos), então torne
fácil para o mantenedor entendê-lo rapidamente e deixe o minificador (a máquina) cuidar da
redução dos tamanhos dos arquivos.

36 | Capítulo 2: Fundamentos
Machine Translated by Google

Executar JSLint

O JSLint já foi apresentado no capítulo anterior e também é mencionado várias vezes neste capítulo.
Neste ponto, você provavelmente está convencido de que é um bom padrão de programação
executar o JSLint em seu código.

O que o JSLint procura? Ele procura violações de alguns dos padrões discutidos neste capítulo
( padrão var único , raiz para parseInt(), sempre usando chaves) e muitas outras violações, incluindo:

• Código inacessível

• Usando variáveis antes de serem definidas •


Caracteres UTF não seguros

• Uso de void, with ou eval •


Caracteres com escape inadequado em expressões regulares

JSLint é escrito em JavaScript (e provavelmente passaria por uma verificação de JSLint); a boa
notícia é que ele está disponível como uma ferramenta baseada na Web e como um código para
download para várias plataformas e interpretadores de JavaScript. Você pode baixá-lo e executá-lo
localmente em qualquer plataforma usando WSH (Windows Scripting Host, parte de todos os
sistemas Windows), JSC (JavaScriptCore, parte do Mac OSX) ou Rhino (interpretador de JavaScript da Mozilla).

É uma ótima ideia baixar JSLint e integrá-lo com seu editor de texto para que você adote o hábito de
executá-lo toda vez que salvar um arquivo. (Criar um atalho de teclado para ele também pode ajudar.)

Resumo
Neste capítulo, examinamos o que significa escrever código sustentável - um tópico importante não
apenas para o sucesso de um projeto de software, mas também para a sanidade e o bem-estar dos
desenvolvedores e das pessoas ao seu redor. Em seguida, falamos sobre algumas das melhores
práticas e padrões essenciais, incluindo:

• Reduzindo o número de globais, idealmente para um por aplicativo • Usando


um único var por função, o que ajuda a manter um olho em todas as variáveis em um único local e
evita surpresas causadas pelo comportamento de elevação variável • loops for ,
loops for-in , switches, “eval () é mau,” não aumentando built-in
protótipos
• Seguindo convenções de codificação (espaço em branco consistente, recuo, usando chaves e
ponto e vírgula mesmo quando são opcionais) e convenções de nomenclatura (para construtores,
funções e variáveis)

Resumo | 37
Machine Translated by Google

O capítulo também discutiu algumas práticas adicionais, não relacionadas ao código,


mas ao processo de programação em geral - escrever comentários, criar documentação
de API gerada, realizar revisões por pares, não tentar escrever código minificado em
detrimento da legibilidade e sempre verificar seu código com JSLint.

38 | Capítulo 2: Fundamentos
Machine Translated by Google

CAPÍTULO 3

Literais e Construtores

Os padrões de notação literal disponíveis em JavaScript permitem definições de objeto mais concisas,
mais expressivas e menos propensas a erros. Este capítulo discute literais como objeto, array e literais de
expressão regular e por que eles são preferíveis ao uso de funções construtoras incorporadas equivalentes,
como Object() e Array(). O formato JSON é apresentado para demonstrar como os literais de array e
objeto são usados para definir um formato de transferência de dados. O capítulo também discute
construtores personalizados e maneiras de impor new para garantir que os construtores se comportem
conforme o esperado.

Para estender a mensagem principal do capítulo (que é evitar construtores e usar literais em vez disso), há
uma discussão sobre os construtores de wrapper integrados Number(), String() e Boolean() e como eles
se comparam ao número primitivo , string e valores booleanos. Por fim, há uma observação rápida sobre o
uso dos diferentes construtores incorporados Error() .

Objeto Literal
Quando você pensa em objetos em JavaScript, simplesmente pense em tabelas hash de pares chave-
valor (semelhante ao que é chamado de “arrays associativos” em outras linguagens). Os valores podem
ser primitivos ou outros objetos; em ambos os casos, eles são chamados de propriedades. Os valores
também podem ser funções, caso em que são chamados de métodos.

Os objetos personalizados que você cria em JavaScript (em outras palavras, os objetos nativos definidos
pelo usuário ) são mutáveis a qualquer momento. Muitas das propriedades dos objetos nativos integrados
também são mutáveis. Você pode começar com um objeto em branco e adicionar funcionalidade a ele à
medida que avança. A notação literal de objeto é ideal para esse tipo de criação de objeto sob demanda.

Considere o seguinte exemplo: // começa


com um objeto vazio var dog = {};

// adiciona uma
propriedade dog.name = "Benji";

39
Machine Translated by Google

// agora adiciona um
método dog.getName = function
() { return dog.name;
};

No exemplo anterior, você começa do zero — um objeto em branco. Em seguida, você adiciona uma
propriedade e um método a ela. A qualquer momento na vida do programa, você pode:

• Alterar os valores de propriedades e métodos, por exemplo:

dog.getName = function () { //
redefine o método para retornar //
um valor codificado
return "Fido";
};
• Remova propriedades/métodos completamente:

deletar dog.name;
• Adicione mais propriedades e métodos:

dog.say = function ()
{ return "Woof!";
};
cão.pulgas = verdadeiro;

Você não é obrigado a começar com um objeto vazio. O padrão de objeto literal permite adicionar
funcionalidade ao objeto no momento da criação, como demonstra o próximo exemplo.

var dog =
{ name: "Benji",
getName: function ()
{ return this.name;
}
};

Você verá “objeto em branco” e “objeto vazio” em alguns lugares ao longo


deste livro. É importante entender que isso é para simplificar e não existe um
objeto vazio em JavaScript. Mesmo o objeto {} mais simples já tem
propriedades e métodos herdados de Object.prototype. Por “vazio”
entenderemos um objeto que não possui propriedades próprias além das
herdadas.

A sintaxe do literal do objeto Se

você não está acostumado com a notação do literal do objeto, pode parecer um pouco estranho no
começo, mas quanto mais você usá-lo, mais você vai adorar. Em essência, as regras de sintaxe são:

• Envolva o objeto entre chaves ({ e }). • Delimitar


por vírgula as propriedades e métodos dentro do objeto. Uma vírgula após o último par nome-valor é
permitida, mas produz erros no IE, portanto, não a use.

40 | Capítulo 3: Literais e Construtores


Machine Translated by Google

• Separe os nomes das propriedades e os valores das propriedades com


dois pontos. • Ao atribuir o objeto a uma variável, não se esqueça do ponto e vírgula após o
fechando }.

Objetos de um Construtor Não há

classes em JavaScript e isso permite uma grande flexibilidade, porque você não precisa saber nada
sobre seu objeto com antecedência; você não precisa de um “plano” de classe.
Mas o JavaScript também possui funções de construtor, que usam sintaxe semelhante à criação de
objeto baseada em classe em Java ou outras linguagens.

Você pode criar objetos usando suas próprias funções de construtor ou usando alguns dos
construtores integrados, como Object(), Date(), String() e assim por diante.

Aqui está um exemplo mostrando duas maneiras equivalentes de criar dois objetos idênticos:

// one way -- usando uma var


literal car = {goes: "far"};

// outra maneira -- usando um construtor


interno // aviso: este é um
antipadrão var car =
new Object(); carro.vai = "longe";
Como você pode ver neste exemplo, um benefício óbvio da notação literal é que ela é mais curta para
digitar. Outra razão pela qual o literal é o padrão preferido para a criação de objetos é que ele enfatiza
que os objetos são simplesmente hashes mutáveis e não algo que precisa ser elaborado a partir de
uma “receita” (de uma classe).

Outra razão para usar literais em oposição ao construtor Object é que não há resolução de escopo.
Como é possível que você tenha criado um construtor local com o mesmo nome, o interpretador precisa
pesquisar a cadeia de escopo do local em que você está chamando Object( ) até encontrar o construtor
Object global.

Object Constructor Catch Você

não tem razão para usar o novo construtor Object() quando você pode usar um objeto literal, mas você
pode estar herdando o código legado escrito por outros, então você deve estar ciente de um "recurso"
deste construtor (ou ainda outra razão para não usá-lo). A característica em questão é que o construtor
Object() aceita um parâmetro e, dependendo do valor passado, pode decidir delegar a criação do objeto
para outro construtor integrado e retornar um objeto diferente do esperado.

A seguir estão alguns exemplos de como passar um número, uma string e um valor booleano para new
Object(); o resultado é que você obtém objetos criados com um construtor diferente:

// Aviso: antipadrões à frente

// um objeto vazio
var o = new Object();

Objeto Literal | 41
Machine Translated by Google

console.log(o.constructor === Objeto); // verdadeiro

// um objeto numérico
var o = new Object(1);
console.log(o.constructor === Número); // true
console.log(o.toFixed(2)); // "1,00"

// um objeto string
var o = new Object("Eu sou uma string");
console.log(o.constructor === String); // true // objetos
normais não possuem um método substring() //
mas objetos string possuem
console.log(typeof o.substring); // "função"

// um objeto booleano
var o = new Object(true);
console.log(o.constructor === Booleano); // verdadeiro

Esse comportamento do construtor Object() pode levar a resultados inesperados quando o valor que
você passa para ele é dinâmico e não é conhecido até o tempo de execução. Novamente, para
concluir, não use new Object(); use o literal de objeto mais simples e confiável.

Funções Construtoras Personalizadas

Além do padrão literal de objeto e das funções de construtor integradas, você pode criar objetos
usando suas próprias funções de construtor personalizadas, como demonstra o exemplo a seguir:

var adão = new Pessoa("Adão");


adam.say(); // "Eu sou Adão"

Esse novo padrão se parece muito com a criação de um objeto em Java usando uma classe
chamada Person. A sintaxe é semelhante, mas na verdade em JavaScript não há classes e Person
é apenas uma função.
Veja como a função do construtor Person pode ser definida.

var Pessoa = função (nome)


{ this.name = nome;
this.say = function ()"{ return
"Eu sou + this.name;
};
};

Quando você invoca a função construtora com new, acontece o seguinte dentro da função:

• Um objeto vazio é criado e referenciado por esta variável, herdando o protótipo


da função.

• Propriedades e métodos são adicionados ao objeto referenciado por this. • O


objeto recém-criado referenciado por this é retornado no final implicitamente (se nenhum outro
objeto foi retornado explicitamente).

42 | Capítulo 3: Literais e Construtores


Machine Translated by Google

É como se algo assim acontecesse nos bastidores:


var Pessoa = função (nome) {

// cria um novo objeto //


usando o objeto literal // var
this = {};

// adicionar propriedades e
métodos this.name
= name; this.say = function
"
() { return "Eu sou + this.name;
};

// retorna isso;
};

Para simplificar este exemplo, o método say() foi adicionado a ele. O resultado é que sempre
que você chama new Person(), uma nova função é criada na memória. Isso obviamente é
ineficiente, porque o método say() não muda de uma instância para outra.
A melhor opção é adicionar o método ao protótipo de Person:
Person.prototype.say
" = function () { return
"Eu sou + este.nome;
};

Falaremos mais sobre protótipos e herança nos próximos capítulos, mas lembre-se de que
membros reutilizáveis, como métodos, devem ir para o protótipo.

Há mais uma coisa que ficará clara mais adiante neste livro, mas vale a pena mencioná-la aqui
para fins de completude. Dissemos que dentro do construtor algo assim acontece nos bastidores:

// var this = {};

Essa não é toda a verdade, porque o objeto “vazio” não está realmente vazio; herdou do protótipo
da Pessoa . Então é mais assim:
// var this = Object.create(Person.prototype);

Discutiremos o que Object.create() significa mais adiante neste livro.

Valores de retorno do construtor

Quando invocada com new, uma função construtora sempre retorna um objeto; por padrão, é o
objeto referido por this. Se você não adicionar nenhuma propriedade a isso dentro de seu
construtor, um objeto “vazio” será retornado (“vazio” além de herdar do protótipo do construtor).

Os construtores retornam isso implicitamente, mesmo quando você não tem uma instrução de
retorno na função. Mas você pode devolver qualquer outro objeto de sua escolha. No próximo
exemplo, um novo objeto referenciado por that é criado e retornado.

Funções Construtoras Personalizadas | 43


Machine Translated by Google

var Criador de objetos = função () {

// esta propriedade `name` será ignorada //


porque o construtor // decide
retornar outro objeto em seu lugar this.name = "É
isso";

// criando e retornando um novo objeto var


that = {};
that.name = "E é isso"; devolva
isso;
};

// teste
var o = new Objectmaker();
console.log(o.name); // "E é isso"

Como você pode ver, você tem a liberdade de retornar qualquer objeto em seus construtores,
desde que seja um objeto. A tentativa de retornar algo que não seja um objeto (como uma string
ou um falso booleano, por exemplo) não causará um erro, mas simplesmente será ignorado, e
o objeto referenciado por this será retornado em seu lugar.

Padrões para impor novos


Como já mencionado, os construtores ainda são apenas funções, mas invocados com new. O
que acontece se você esquecer new ao invocar um construtor? Isso não causará erros de
sintaxe ou de tempo de execução, mas pode levar a erros lógicos e comportamento inesperado.
Isso porque quando você esquece new, isso dentro do construtor vai apontar para o objeto
global. (Nos navegadores, isso apontará para a janela.)

Quando seu construtor tem algo parecido com this.member e você invoca o construtor sem new,
na verdade você está criando uma nova propriedade do objeto global chamado membro e
acessível através de window.member ou simplesmente membro. Esse comportamento é
altamente indesejável, porque você sabe que deve sempre se esforçar para manter o espaço
de nome global limpo.
// função do
construtor Waffle()
{ this.tastes = "gostoso";
}

// um novo objeto
var good_morning = new Waffle();
console.log(typeof good_morning); // "objeto"
console.log(good_morning.tastes); // "delicioso"

// antipadrão: //
esquecido `novo` var
good_morning = Waffle();
console.log(typeof good_morning); // "indefinido"
console.log(window.tastes); // "delicioso"

44 | Capítulo 3: Literais e Construtores


Machine Translated by Google

Esse comportamento indesejado é tratado no ECMAScript 5 e, no modo estrito, ele não apontará mais
para o objeto global. Se o ES5 não estiver disponível, ainda há algo que você pode fazer para garantir
que uma função construtora sempre se comporte como uma, mesmo se chamada sem new.

Convenção de

nomenclatura A alternativa mais simples é usar uma convenção de nomenclatura, conforme discutido
no capítulo anterior, onde você coloca a primeira letra em maiúscula em nomes de construtores
(MyConstructor) e em minúsculas em funções e métodos “normais” (myFunction).

Usando isso

Seguir uma convenção de nomenclatura certamente pode ajudar, mas apenas sugere e não impõe o
comportamento correto. Aqui está um padrão que ajuda a garantir que seu construtor sempre se
comporte como um construtor. Em vez de adicionar todos os membros a isso, você os adiciona a isso
e depois retorna isso.

função Waffle() { var


que = {};
that.tastes = "gostoso";
devolva isso;
}

Para objetos mais simples, você nem precisa de uma variável local como essa; você pode simplesmente
retornar um objeto de um literal assim:
function Waffle()
{ return
{ tastes: "yummy"
};
}

Usar qualquer uma das implementações acima Waffle() sempre retorna um objeto, independentemente
de como é chamado:

var primeiro = new


Waffle(), segundo
= Waffle(); console.log(first.tastes); //
"gostoso" console.log(second.tastes); // "delicioso"

O problema com esse padrão é que o link para o protótipo é perdido, portanto, qualquer membro que
você adicionar ao protótipo Waffle() não estará disponível para os objetos.

Observe que o nome da variável é apenas uma convenção; não faz parte da língua. Você pode usar
qualquer nome, onde outros nomes de variáveis comuns incluem self e me.

Padrões para Imposição de novos | 45


Machine Translated by Google

Construtor autoinvocado Para

lidar com a desvantagem do padrão anterior e ter propriedades de protótipo disponíveis para os
objetos de instância, considere a seguinte abordagem. No construtor você verifica se esta é uma
instância do seu construtor, e se não, o construtor invoca a si mesmo novamente, desta vez
corretamente com new:
function Waffle() {

if (!(esta instância de Waffle))


{ return new Waffle();
}

this.tastes = "gostoso";

}
Waffle.prototype.wantAnother = verdadeiro;

// teste de invocações var


first = new Waffle(), second =
Waffle();

console.log(first.tastes); // "gostoso"
console.log(second.tastes); // "delicioso"

console.log(first.wantAnother); // true
console.log(second.wantAnother); // verdadeiro

Outra maneira geral de verificar a instância é comparar com arguments.callee em vez de codificar
o nome do construtor.
if (!(esta instânciadeargumentos.callee)) { return
novos argumentos.callee();
}

Esse padrão usa o fato de que dentro de cada função, um objeto chamado arguments é criado
contendo todos os parâmetros passados para a função quando ela foi invocada. E os argumentos
têm uma propriedade chamada callee, que aponta de volta para a função que foi chamada. Esteja
ciente de que arguments.callee não é permitido no modo estrito do ES5, então é melhor limitar
seu uso futuro e também remover quaisquer instâncias caso você as encontre no código existente.

Array Literal
Arrays em JavaScript, como a maioria das outras coisas na linguagem, são objetos. Eles podem
ser criados com a função de construtor incorporada Array(), mas também têm uma notação literal
e, assim como o literal de objeto, a notação de literal de matriz é mais simples e preferida.

Veja como você pode criar dois arrays com os mesmos elementos de duas maneiras diferentes
— usando o construtor Array() e usando o padrão literal.

46 | Capítulo 3: Literais e Construtores


Machine Translated by Google

// array de três elementos //


warning: antipattern var
a = new Array("itsy", "bitsy", "spider");

// exatamente o mesmo
array var a = ["itsy", "bitsy", "spider"];

console.log(tipo de a); // "objeto", porque arrays são objetos


console.log(a.constructor === Array); // verdadeiro

Sintaxe Literal de
Array Não há muito na notação de literal de array: é apenas uma lista de elementos
delimitada por vírgulas e toda a lista está entre colchetes. Você pode atribuir qualquer
tipo de valor aos elementos do array, incluindo objetos ou outros arrays.

A sintaxe literal da matriz é simples, direta e elegante. Afinal, um array é apenas uma lista de
valores indexada por zero. Não há necessidade de complicar as coisas (e escrever mais código)
incluindo um construtor e usando o operador new .

Curiosidade do Construtor Array


Mais uma razão para ficar longe de new Array() é evitar uma possível armadilha que este
construtor tem guardado para você.

Quando você passa um único número para o construtor Array() , ele não se torna o valor do
primeiro elemento do array. Em vez disso, ele define o comprimento da matriz. Isso significa que
new Array(3) cria um array com comprimento de 3, mas sem elementos reais. Se você tentar
acessar qualquer um dos elementos, obterá o valor indefinido porque os elementos não existem.
O exemplo de código a seguir mostra o comportamento diferente quando você usa o literal e o
construtor com um único valor.
// uma matriz de um
elemento
var a = [3];
console.log(a.comprimento); // 1 console.log(a[0]); // 3

// um array de três elementos


var a = new Array(3);
console.log(a.comprimento); //
3 console.log(typeof a[0]); // "indefinido"

Embora esse comportamento possa ser um pouco inesperado, fica pior quando você passa um
número de ponto flutuante para new Array() em vez de um número inteiro. Isso resulta em um
erro porque o ponto flutuante não é um valor válido para o comprimento da matriz:
// usando array literal
var a = [3.14];
console.log(a[0]); // 3.14

Array Literal | 47
Machine Translated by Google

var a = new Array(3.14); // RangeError: comprimento de array


inválido console.log(typeof a); // "indefinido"

Para evitar possíveis erros ao criar arrays dinâmicos em tempo de execução, é muito mais seguro
manter a notação literal de array.

Existem alguns usos inteligentes do construtor Array() , por exemplo, para


repetir strings. O trecho a seguir retorna uma string com 255 espaços em
branco (por que não 256, vou deixar o leitor curioso pensar):

var branco = new Array(256).join(' ');

Verifique a existência de

matriz O uso do operador typeof com operandos de matriz retorna “objeto”.


console.log(typeof [1, 2]); // "objeto"

Embora esse comportamento faça sentido (arrays são objetos), não é muito útil. Freqüentemente,
você precisa saber se um valor é realmente uma matriz. Às vezes, você pode ver o código verificando
a presença da propriedade length ou algum método de array, como slice(), para determinar o “array-
ness”. Mas essas verificações não são robustas porque não há razão para que um objeto não array
não deva ter propriedades e métodos com os mesmos nomes. Além disso, algumas vezes as
pessoas usam instância de Array, mas essa verificação funciona incorretamente quando usada em
quadros em algumas versões do IE.

ECMAScript 5 define um novo método Array.isArray(), que retorna true se o argumento for um array.
Por exemplo: Array.isArray([]); //
verdadeiro

// tentando enganar a
verificação // com um objeto
tipo array

Array.isArray({ length:
1, "0": 1, slice: function () {} }); // falso

Caso este novo método não esteja disponível em seu ambiente, você pode fazer a verificação
chamando o método Object.prototype.toString() . Se você invocar o método call() de toString no
contexto de um array, ele deve retornar a string “[object Array]”. Se o contexto for um objeto, ele deve
retornar a string “[object Object]”. Então você pode fazer algo assim: if (typeof Array.isArray ===
"undefined")
{ Array.isArray = function (arg) {

return Object.prototype.toString.call(arg) === "[matriz de objetos]";


};
}

48 | Capítulo 3: Literais e Construtores


Machine Translated by Google

JSON
Agora que você está familiarizado com os literais de array e objeto discutidos anteriormente, vamos
dar uma olhada em JSON, que significa JavaScript Object Notation e é um formato de transferência
de dados. É leve e conveniente para trabalhar em vários idiomas, especialmente em JavaScript.

Na verdade, não há nada de novo para aprender sobre JSON. É apenas uma combinação da matriz e
da notação literal do objeto. Aqui está um exemplo de uma string JSON:
{"nome": "valor", "algum": [1, 2, 3]}

A única diferença de sintaxe entre JSON e o objeto literal é que os nomes de propriedade precisam
ser colocados entre aspas para serem JSON válidos. Em literais de objeto, as aspas são necessárias
apenas quando os nomes das propriedades não são identificadores válidos, por exemplo, eles têm
espaços {"primeiro nome": "Dave"}.

Em strings JSON, você não pode usar funções ou literais de expressão regular.

Trabalhando com JSON

Conforme mencionado no capítulo anterior, não é recomendado avaliar cegamente qualquer string
JSON com eval() por causa das implicações de segurança. É melhor usar o método JSON.parse() ,
que faz parte da linguagem desde ES5 e é fornecido nativamente pelos mecanismos JavaScript em
navegadores modernos. Para mecanismos JavaScript mais antigos, você pode usar a biblioteca
JSON.org (http:// www.json.org/ json2.js) para obter acesso ao objeto JSON e seus métodos.

// uma string JSON de


entrada var jstr = '{"mykey": "my value"}';

// antipadrão
var data = eval('(' + jstr + ')');

// dados var
preferidos = JSON.parse(jstr);

console.log(data.mykey); // "meu valor"

Se você já usa uma biblioteca JavaScript, é provável que ela venha com um utilitário para analisar
JSON, então você pode não precisar da biblioteca JSON.org adicional. Por exemplo, usando YUI3,
você pode fazer:
// uma string JSON de
entrada var jstr = '{"mykey": "my value"}';

// analisa a string e a transforma em um objeto //


usando uma instância
YUI YUI().use('json-parse', function (Y)
{ var data = Y.JSON.parse(jstr);
console.log( data.mykey); // "meu valor" });

JSON | 49
Machine Translated by Google

No jQuery, existe o método parseJSON() :


// uma string JSON de
entrada var jstr = '{"mykey": "my value"}';

dados var = jQuery.parseJSON(jstr);


console.log(data.mykey); // "meu valor"

O oposto do método JSON.parse() é JSON.stringify(). Ele pega qualquer objeto ou array (ou um
primitivo) e o serializa em uma string JSON.
var cachorro
= { nome:
"Fido", dob: new
Date(), pernas: [1, 2, 3, 4]
};

var jsonstr = JSON.stringify(cachorro);

// jsonstr é agora: //
{"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}

Expressão Regular Literal


As expressões regulares em JavaScript também são objetos e você tem duas opções para criá-las:

• Usando o novo construtor RegExp() •


Usando a expressão regular literal

O código de exemplo a seguir demonstra duas maneiras de criar uma expressão regular que
corresponde a uma barra invertida:

// expressão regular literal var re


= /\\/gm;

// construtor var
re = new RegExp("\\\\", "gm");

Como você pode ver, a notação literal de expressão regular é mais curta e não o força a pensar
em termos de construtores semelhantes a classes. Portanto, é preferível usar o literal.

Além disso, ao usar o construtor RegExp() , você também precisa escapar das aspas e geralmente
precisa escapar duas vezes das barras invertidas, conforme mostrado no trecho anterior, em que
precisamos de quatro barras invertidas para corresponder a uma única. Isso torna seus padrões
de expressão regular mais longos e difíceis de ler e modificar. As expressões regulares são difíceis
o suficiente para começar, e qualquer chance de simplificá-las é bem-vinda, então é melhor manter
a notação literal.

50 | Capítulo 3: Literais e Construtores


Machine Translated by Google

Sintaxe Literal de Expressão Regular


A notação literal de expressão regular usa barras para agrupar o padrão de expressão
regular usado para correspondência. Após a segunda barra, você pode colocar os
modificadores de padrão na forma de letras sem aspas:

• g—Correspondência global •
m—Multilinha

• i — Correspondência sem distinção entre maiúsculas e minúsculas

Os modificadores de padrão podem aparecer em qualquer ordem ou combinação:

var re = /padrão/ gmi;

O uso do literal de expressão regular ajuda a escrever um código mais conciso ao chamar métodos como
String.prototype.replace() que aceitam objetos de expressão regular como parâmetros.

var no_letters = "abc123XYZ".replace(/[az]/gi, "");


console.log(no_letters); // 123
Uma razão para usar new RegExp() é que o padrão não é conhecido antecipadamente, mas é criado como uma
string em tempo de execução.

Outra distinção entre o literal de expressão regular e o construtor é que o literal cria um objeto apenas uma vez
durante o tempo de análise. Se você criar a mesma expressão regular em um loop, o objeto criado anteriormente
será retornado com todas as suas propriedades (como lastIndex) já definidas desde a primeira vez. Considere o
exemplo a seguir como uma ilustração de como o mesmo objeto é retornado duas vezes.

função getRE()
{ var re = /[az]/;
re.foo = "barra";
retornar re;
}

var reg = getRE(),


re2 = getRE();

console.log(reg === re2); // true


reg.foo = "baz";
console.log(re2.foo); // "baz"

Este comportamento mudou no ES5 e o literal também cria novos objetos. O


comportamento também foi corrigido em muitos ambientes de navegador,
portanto, não é confiável.

E uma última observação de que chamar RegExp() sem new (como uma função, não como um construtor) se
comporta da mesma forma que com new.

Expressão Regular Literal | 51


Machine Translated by Google

Wrappers primitivos
JavaScript tem cinco tipos de valores primitivos: número, string, booleano, nulo e indefinido. Com
exceção de null e undefined, os outros três possuem os chamados objetos wrapper primitivos.
Os objetos wrapper podem ser criados usando os construtores integrados Number(), String() e
Boolean().

Para ilustrar a diferença entre um número primitivo e um objeto numérico, considere o seguinte
exemplo:
// um número primitivo
var n = 100;
console.log(tipo de n); // "número"

// um objeto Number
var nobj = new Number(100);
console.log(typeof nobj); // "objeto"

Os objetos wrapper têm algumas propriedades e métodos úteis — por exemplo, objetos
numéricos têm métodos como toFixed() e toExponential(). Os objetos string têm os métodos
substring(), charAt() e toLowerCase() (entre outros) e uma propriedade de comprimento . Esses
métodos são convenientes e podem ser um bom motivo para decidir criar um objeto, em vez de
usar um primitivo. Mas os métodos também funcionam em primitivos - assim que você invoca
um método, o primitivo é temporariamente convertido em um objeto nos bastidores e se comporta
como se fosse um objeto. // uma string primitiva
pode ser usada como um objeto var s =
"hello";
console.log(s.toUpperCase()); // "OLÁ"

// o próprio valor pode atuar como um objeto


"monkey".slice(3, 6); // "chave"

// o mesmo para
números (22 / 7).toPrecision(3); // "3.14"

Como as primitivas podem atuar como objetos assim que você precisar, muitas vezes não há
razão para usar os construtores de wrapper mais detalhados. Por exemplo, você não precisa
escrever new String("hi"); quando você pode simplesmente usar "oi":
// evite estes: var
s = new String("minha string"); var n =
novo Número(101); var b =
new Boolean(true);

// melhor e mais simples:


var s = "minha string";
var n = 101;
var b = verdadeiro;

Um motivo para usar os objetos wrapper é quando você deseja aumentar o valor e o estado
persistente. Como os primitivos não são objetos, eles não podem ser aumentados com
propriedades.

52 | Capítulo 3: Literais e Construtores


Machine Translated by Google

// string primitiva
var greet = "Olá";

// primitivo é convertido em um objeto //


para usar o método split() greet.split('
')[0]; // "Olá"

// tentar aumentar uma primitiva não é um erro


greet.smile = true;

// mas na verdade não funciona


typeof greet.smile; // "indefinido"
No snippet anterior, a saudação foi convertida apenas temporariamente em um objeto para fazer
o acesso à propriedade/método funcionar sem erros. Por outro lado, se a saudação fosse definida
como um objeto usando new String(), a propriedade de sorriso aumentado funcionaria conforme
o esperado. Aumentar uma string, número ou valor booleano raramente é usado e, a menos que
seja isso que você precisa, provavelmente não precisa dos construtores de wrapper.

Quando usados sem new, os construtores wrapper convertem o argumento passado para eles
em um valor primitivo:
typeof Number(1); // "number"
typeof Number("1"); // "number"
typeof Number(new Number()); // "número"
tipo de String(1); // tipo "string"
de Boolean(1); // "boleano"

Objetos de erro

O JavaScript possui vários construtores de erro integrados, como Error(), SyntaxError(), TypeError() e outros, que são
usados com a instrução throw . Os objetos de erro criados por esses construtores têm as seguintes propriedades:

nome
A propriedade name da função construtora que criou o objeto; pode ser o “Error” geral ou
um construtor mais especializado, como “RangeError”

mensagem
A string passada para o construtor ao criar o objeto

Os objetos de erro têm outras propriedades, como o número da linha e o nome do arquivo onde
o erro ocorreu, mas essas propriedades extras são extensões de navegador implementadas de
forma inconsistente nos navegadores e, portanto, não são confiáveis.

Por outro lado, throw funciona com qualquer objeto, não necessariamente um objeto criado com
um dos construtores de erro, então você pode optar por lançar seus próprios objetos. Tais objetos
de erro podem ter as propriedades “nome”, “mensagem” e qualquer outro tipo de informação
que você queira passar para ser tratada pela instrução catch . Você pode ser criativo quando se
trata de seus objetos de erro personalizados e usá-los para restaurar o estado do aplicativo de
volta ao normal.

Objetos de erro | 53
Machine Translated by Google

try
{ // algo ruim aconteceu, lança um erro throw
{ name:
"MyErrorType", // mensagem de tipo de erro
personalizado:
"oops", extra: "Isso foi bastante
embaraçoso", remédio: genericErrorHandler // quem deve lidar com isso

}; } catch (e)
{ // informa o usuário
alert(e.message); // "ops"

// trata o erro graciosamente


e.remedy(); // chama genericErrorHandler()
}

Os construtores de erro invocados como funções (sem new) se comportam da mesma forma que os
construtores (com new) e retornam os mesmos objetos de erro.

Resumo
Neste capítulo, você aprendeu sobre diferentes padrões literais, que são alternativas mais simples
ao uso de funções construtoras. O capítulo discutiu:

• Notação literal de objeto — Uma maneira elegante de criar objetos como pares de valores-chave
delimitados por vírgulas, entre colchetes. •
Funções de construtor—Construtores integrados (que quase sempre têm uma notação literal
melhor e mais curta) e construtores personalizados

• Maneiras de garantir que os construtores personalizados sempre se comportem como se


fossem invocados com new • Notação literal de matriz — Listas de valores delimitadas por
vírgulas entre colchetes • JSON — Um formato de dados que consiste
em objetos e literais de matriz •
Literais de expressão regular • Outros construtores integrados evitar: String(), Number(), Boolean() e o
construtores Error() diferentes

Em geral, com exceção do construtor Date() , raramente há necessidade de usar os outros


construtores integrados. A tabela a seguir resume esses construtores e seus padrões literais
correspondentes e preferenciais.

54 | Capítulo 3: Literais e Construtores


Machine Translated by Google

Construtores embutidos (evitar) Literais e primitivos (preferir)

var o = new Object(); var o = {};


var a = new Array(); var a = [];
var re = new RegExp( var re = /[az]/g;
"[az]",
"g"
);
var s = new String(); var var s = "";
n = new Número(); var n = 0;
var b = new Booleano(); var b = falso;
lançar novo erro("uh-oh"); lançar {
nome: "Erro",
mensagem: "uh-oh"
};

... ou

lançar Erro("uh-oh");

Resumo | 55
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 4

Funções

Dominar as funções é uma habilidade essencial para o programador JavaScript porque a linguagem tem muitos
usos para elas. Eles executam uma variedade de tarefas para as quais outras linguagens podem ter sintaxe
especial.

Neste capítulo, você aprenderá sobre as diferentes maneiras de definir uma função em JavaScript, aprenderá
sobre expressões de função e declarações de função e verá como o escopo local e a variável hoisting funcionam.
Em seguida, você aprenderá sobre vários padrões que ajudam suas APIs (fornecendo melhores interfaces para
suas funções), inicializações de código (com menos globais) e desempenho (em outras palavras - prevenção de
trabalho).

Vamos nos aprofundar nas funções, começando primeiro revisando e esclarecendo os fundamentos importantes.

Fundo
Há duas características principais das funções em JavaScript que as tornam especiais — a primeira é que as
funções são objetos de primeira classe e a segunda é que elas fornecem escopo.

Funções são objetos que:

• Podem ser criados dinamicamente em tempo de execução, durante a execução do programa • Podem

ser atribuídos a variáveis, podem ter suas referências copiadas para outras variáveis, podem ser aumentados
e, exceto em alguns casos especiais, podem ser excluídos • Podem ser passados como

argumentos para outras funções e também podem ser retornados por outras
funções

• Podem ter suas próprias propriedades e métodos

Então pode acontecer que uma função A, sendo um objeto, tenha propriedades e métodos, um dos quais seja
outra função B. Então B pode aceitar uma função C como argumento e, quando executada, pode retornar outra
função D. Em primeira vista, são muitas funções para acompanhar. Mas quando você se sentir confortável com
as várias aplicações das funções, poderá apreciar o poder, a flexibilidade e a expressividade

57
Machine Translated by Google

que as funções podem oferecer. Em geral, quando você pensa em uma função em JavaScript,
pense em um objeto, com a única característica especial de que esse objeto é invocável, ou seja,
pode ser executado.

O fato de funções serem objetos torna-se óbvio quando você vê o novo construtor Function() em
ação:

// antipadrão //
apenas para fins de
demonstração var add = new Function('a, b', 'return
a + b'); adicionar(1, 2); // retorna 3

Nesse código, não há dúvida de que add() é um objeto; afinal, ele foi criado por um construtor.
Usar o construtor Function() não é uma boa ideia (é tão ruim quanto eval()) porque o código é
passado como uma string e avaliado. Também é inconveniente escrever (e ler) porque você precisa
escapar das aspas e tomar cuidado extra se quiser recuar corretamente o código dentro da função
para facilitar a leitura.

A segunda característica importante é que as funções fornecem escopo. Em JavaScript não há


escopo local de chaves; em outras palavras, os blocos não criam escopo. Há apenas escopo de
função. Qualquer variável definida com var dentro de uma função é uma variável local, invisível fora
da função. Dizer que as chaves não fornecem escopo local significa que se você definir uma
variável com var dentro de uma condição if ou dentro de um loop for ou while , isso não significa
que a variável é local para aquele if ou for. É apenas local para a função de envolvimento e, se não
houver função de envolvimento, torna-se uma variável global.
Conforme discutido no Capítulo 2, minimizar o número de globais é um bom hábito, portanto as
funções são indispensáveis quando se trata de manter o escopo da variável sob controle.

Eliminação da ambiguidade da

terminologia Vamos dedicar um momento para discutir a terminologia em torno do código usado
para definir uma função, porque usar nomes precisos e aceitos é tão importante quanto o código
ao falar sobre padrões.

Considere o seguinte trecho: //


expressão de função nomeada
var add = function add(a, b)
{ return a + b;
};

O código anterior mostra uma função, que usa uma expressão de função nomeada.

Se você ignorar o nome (a segunda inclusão no exemplo) na expressão de função, obterá uma
expressão de função sem nome , também conhecida simplesmente como expressão de função ou
mais comumente como função anônima. Um exemplo é:
// expressão de função, também conhecida como função
anônima var add = function (a,
b) { return a + b;
};

58 | Capítulo 4: Funções
Machine Translated by Google

Portanto, o termo mais amplo é “expressão de função” e a “expressão de função nomeada” é


um caso específico de uma expressão de função, que define o nome opcional.

Quando você omite a segunda adição e termina com uma expressão de função sem nome,
isso não afetará a definição e as invocações consecutivas da função. A única diferença é que
a propriedade name do objeto de função será uma string em branco. A propriedade name é
uma extensão da linguagem (não faz parte do padrão ECMA), mas amplamente disponível em
muitos ambientes. Se você mantiver o segundo add, a propriedade add.name conterá a string
“add”. A propriedade name é útil ao usar depuradores, como Firebug, ou ao chamar a mesma
função recursivamente de si mesma; caso contrário, você pode simplesmente ignorá-lo.

Finalmente, você tem declarações de função. Elas se parecem mais com as funções usadas
em outras linguagens:
function foo() { //
o corpo da função vai aqui
}

Em termos de sintaxe, as expressões de função nomeada e as declarações de função são


semelhantes, especialmente se você não atribuir o resultado da expressão de função a uma
variável (como veremos no padrão de retorno de chamada mais adiante no capítulo). Às vezes,
não há outra maneira de saber a diferença entre uma declaração de função e uma expressão
de função nomeada além de observar o contexto em que a função ocorre, como você verá no próximo
seção.

Há diferença de sintaxe entre os dois no ponto e vírgula à direita. O ponto e vírgula não é
necessário em declarações de função, mas é obrigatório em expressões de função, e você
sempre deve usá-lo, mesmo que o mecanismo de inserção automática de ponto e vírgula possa
fazer isso por você.

O termo literal de função também é comumente usado. Pode significar uma


expressão de função ou uma expressão de função nomeada. Devido a
essa ambigüidade, provavelmente é melhor não usá-lo.

Declarações versus expressões: nomes e hoisting Então, o que


você deve usar — declarações de função ou expressões de função? Nos casos em que
sintaticamente você não pode usar uma declaração, esse dilema é resolvido para você. Os
exemplos incluem passar um objeto de função como um parâmetro ou definir métodos em
literais de objeto:
// esta é uma expressão de função, //
passada como um argumento para a função `callMe`
callMe(function () {
// Sou uma expressão de função sem
nome // também conhecida como função
anônima });

Fundo | 59
Machine Translated by Google

// esta é uma expressão de função nomeada


callMe(function me() {
// Eu sou uma expressão de função
nomeada // e meu nome
é "eu" });

// outra expressão de função var


myobject = { say:
function () {
// Eu sou uma expressão de função
}
};

As declarações de função só podem aparecer no “código do programa”, ou seja, dentro dos


corpos de outras funções ou no espaço global. Suas definições não podem ser atribuídas a
variáveis ou propriedades, ou aparecer em chamadas de função como parâmetros. Aqui está
um exemplo do uso permitido de declarações de função, onde todas as funções foo(), bar() e
local() são definidas usando o padrão de declaração de função: //
função de
escopo global foo() {}

function local() { //
escopo local
function bar() {}
return bar;
}

Propriedade de nome da
função Outra coisa a considerar ao escolher um padrão de definição de função é a
disponibilidade da propriedade de nome somente leitura . Novamente, essa propriedade não
é padrão, mas está disponível em muitos ambientes. Em declarações de função e expressões
de função nomeada, a propriedade name é definida. Em expressões de funções anônimas,
depende da implementação; pode ser indefinido (IE) ou definido com uma string vazia (Firefox,
WebKit):
function foo() {} // declaração var bar
= function () {}; // expressão var baz = function
baz() {}; // expressão nomeada

foo.name; // "foo"
bar.name; // ""
baz.name; // "baz"

A propriedade name é útil ao depurar código no Firebug ou em outros depuradores. Quando o


depurador precisa mostrar um erro em uma função, ele pode verificar a presença da
propriedade name e usá-la como um indicador. A propriedade name também é usada para
chamar a mesma função recursivamente de dentro dela mesma. Se você não estivesse
interessado nesses dois casos, uma expressão de função sem nome seria mais fácil e menos detalhada.

60 | Capítulo 4: Funções
Machine Translated by Google

O argumento contra declarações de função e a razão para preferir expressões de função é


que as expressões destacam que funções são objetos como todos os outros objetos e não
alguma construção especial de linguagem.

É tecnicamente possível usar uma expressão de função nomeada e atribuí-la a


uma variável com um nome diferente, por exemplo:
var foo = função bar() {};

No entanto, o comportamento deste uso não é implementado corretamente em


alguns navegadores (IE), portanto, não é recomendável usar este padrão.

Elevação de função
Da discussão anterior, você pode concluir que o comportamento das declarações de função
é praticamente equivalente a uma expressão de função nomeada . Isso não é exatamente
verdade, e uma diferença está no comportamento de içamento.

O termo hoisting não é definido no ECMAScript, mas é comum e uma boa


maneira de descrever o comportamento.

Como você sabe, todas as variáveis, não importa onde no corpo da função elas são declaradas,
são colocadas no topo da função nos bastidores. O mesmo se aplica a funções porque são
apenas objetos atribuídos a variáveis. A única “pegadinha” é que ao usar uma declaração de
função, a definição da função também é levantada, não apenas sua declaração. Considere
este trecho: // antipadrão // apenas
para ilustração

// funções globais
function foo()
{ alert('global foo');

} function bar()
{ alert('barra global');
}

function hoistMe() {

console.log(tipo de foo); // "função"


console.log(typeof bar); // "indefinido"

foo(); // "foo local"


bar(); // TypeError: bar não é uma função

// declaração da função: //
a variável 'foo' e sua implementação são levantadas

Fundo | 61
Machine Translated by Google

function foo()
{ alert('local foo');
}

// expressão da função: //
apenas a variável 'bar' é içada // não
a implementação var bar
= function () { alert('barra
local');
};

} hoistMe();

Neste exemplo, você vê que, assim como nas variáveis normais, a mera presença de foo e bar
em qualquer lugar na função hoistMe() os move para o topo, sobrescrevendo o foo e bar globais.
A diferença é que a definição de local foo() é elevada ao topo e funciona bem; embora seja
definido posteriormente. A definição de bar() não é içada, apenas sua declaração. É por isso que
até que a execução do código atinja a definição de bar() , ela é indefinida e não pode ser usada
como uma função (enquanto ainda evita que a bar() global seja “vista” na cadeia de escopo).

Agora que a base necessária e a terminologia em torno das funções estão fora do caminho,
vamos ver alguns dos bons padrões relacionados a funções que o JavaScript tem a oferecer,
começando com o padrão de retorno de chamada. Novamente, é importante lembrar os dois
recursos especiais das funções em JavaScript:

• São objetos. •
Oferecem escopo local.

Padrão de retorno de chamada

As funções são objetos, o que significa que podem ser passadas como argumentos para outras
funções. Quando você passa a função introduzBugs() como um parâmetro para a função
writeCode(), então, em algum ponto, é provável que writeCode() execute (ou chame)
introduzaBugs(). Neste caso, a introdução de Bugs() é chamada de função de retorno de chamada ou simplesmente
ligar de volta:

function writeCode(callback) { //
faça alguma coisa...
callback(); // ...

function introduzBugs()
{ // ... fazer bugs
}

writeCode(introduzirBugs);

62 | Capítulo 4: Funções
Machine Translated by Google

Observe como o introduBugs() é passado como um argumento para o writeCode() sem os


parênteses. Parênteses executam uma função enquanto neste caso queremos passar apenas uma
referência à função e deixar writeCode() executá-la (em outras palavras, chamá-la de volta) quando
apropriado.

Um exemplo de retorno

de chamada Vamos dar um exemplo e começar sem um retorno de chamada primeiro e depois
refatorar. Imagine que você tem uma função de propósito geral que faz um trabalho complicado e
retorna um grande conjunto de dados como resultado. Essa função genérica poderia ser chamada,
por exemplo, findNodes(), e sua tarefa seria rastrear a árvore DOM de uma página e retornar um
array de elementos de página que são interessantes
para você: var findNodes =
function () { var i = 100000, // loop grande e pesado
nodes = [], // armazena o resultado
encontrado; // o próximo nó
encontrado
while
(i) { i -= 1; // lógica complexa
aqui... nodes.push(found);

} nós de retorno;
};

É uma boa ideia manter essa função genérica e fazer com que ela simplesmente retorne uma matriz
de nós DOM, sem fazer nada com os elementos reais. A lógica de modificar nós poderia estar em
uma função diferente, por exemplo uma função chamada hide() que, como o nome sugere, esconde
os nós da página:
var esconder = função (nós) {
var i = 0, max = nós.comprimento;
for (; i < max; i += 1)
{ nodes[i].style.display = "nenhum";
}
};

// executando as funções
hide(findNodes());

Essa implementação é ineficiente, porque hide() precisa percorrer novamente o array de nós
retornado por findNodes(). Seria mais eficiente se você pudesse evitar esse loop e ocultar os nós
assim que os selecionasse em findNodes(). Mas se você implementar a lógica de ocultação em
findNodes(), ela não será mais uma função genérica devido ao acoplamento da lógica de
recuperação e modificação. Digite o padrão de retorno de chamada - você passa sua lógica de
ocultação de nó como uma função de retorno de chamada e delega sua execução:
// refatorou findNodes() para aceitar um callback var
findNodes = function (callback) { var i =
100000, nodes
= [], found;

Padrão de retorno de chamada | 63


Machine Translated by Google

// verifica se callback pode ser


chamado if (typeof callback !== "function")
{ callback = false;
}

enquanto (i)
{ i -= 1;

// lógica complexa aqui...

// agora callback:
if (callback)
{ callback(found);
}

nodes.push(encontrado);

} nós de retorno;
};

A implementação é direta; a única tarefa adicional que findNodes() por formulários é verificar se
um retorno de chamada opcional foi fornecido e, em caso afirmativo, executá-lo. O retorno de
chamada é opcional, portanto, o findNodes() refatorado ainda pode ser usado como antes e não
quebrará o código antigo que depende da API antiga.

A implementação de hide() será muito mais simples agora porque não precisa percorrer os nós: //
uma função de
retorno de chamada
var hide = function (node) {
node.style.display = "nenhum";
};

// encontra os nós e os esconde à medida que


avança findNodes(hide);

O retorno de chamada pode ser uma função existente, conforme mostrado no código anterior,
ou pode ser uma função anônima, que você cria ao chamar a função principal. Por exemplo, veja
como você pode mostrar nós usando a mesma função genérica findNodes() : //
passando um callback anônimo
findNodes(function (node)
{ node.style.display = "block"; });

Chamadas de retorno e escopo

Nos exemplos anteriores, a parte onde o callback é executado era assim: callback(parameters);

Embora isso seja simples e seja bom o suficiente em muitos casos, geralmente há cenários em
que o retorno de chamada não é uma função anônima única ou uma função global, mas é um

64 | Capítulo 4: Funções
Machine Translated by Google

método de um objeto. Se o método de retorno de chamada usar isso para se referir ao objeto ao qual pertence,
isso pode causar um comportamento inesperado.

Imagine que o callback é a função paint(), que é um método do objeto chamado


meuaplicativo:

var meuaplicativo
= {}; myapp.color =
"verde"; myapp.paint = function (nó) {
node.style.color = this.color;
};

A função findNodes() faz algo assim: var findNodes = function


(callback) { // ... if (typeof callback ===

"function") { callback(found);

} // ...
};

Se você chamar findNodes(myapp.paint), não funcionará como esperado, porque this.color não será definido.
O objeto this irá se referir ao objeto global, porque findNodes() é uma função global. Se findNodes() fosse um
método de um objeto chamado dom (como dom.findNodes()), então isso dentro do retorno de chamada se
referiria a dom em vez do esperado myapp.

A solução para esse problema é passar a função callback e além disso passar o objeto ao qual esse callback
pertence:

findNodes(meuaplicativo.paint, meuaplicativo);

Então você também precisa modificar findNodes() para vincular esse objeto que você passa:

var findNodes = function (callback, callback_obj) {


//... if
(tipo de retorno de chamada === "função") {
callback.call(callback_obj, encontrado);

} // ...
};

Haverá mais tópicos sobre vinculação e uso de call() e apply() em capítulos futuros.

Outra opção para passar um objeto e um método para ser usado como callback é passar o método como uma
string, para não repetir o objeto duas vezes. Em outras palavras: findNodes(myapp.paint, myapp);

pode se tornar:

findNodes("paint", meuaplicativo);

Em seguida, findNodes () faria algo ao longo destas linhas:

Padrão de retorno de chamada | 65


Machine Translated by Google

var findNodes = function (callback, callback_obj) {

if (tipo de callback === "string") {


callback = callback_obj[callback];
}

//... if
(tipo de retorno de chamada === "função") {
callback.call(callback_obj, encontrado);

} // ...
};

Ouvintes de eventos assíncronos O padrão

de retorno de chamada tem muitos usos diários; por exemplo, quando você anexa um ouvinte de evento a um
elemento em uma página, na verdade está fornecendo um ponteiro para uma função de retorno de chamada
que será chamada quando o evento ocorrer. Aqui está um exemplo simples de como console.log() é passado
como um retorno de chamada ao ouvir o evento click do documento:

document.addEventListener("clique", console.log, false);

A maior parte da programação do navegador do lado do cliente é orientada a eventos. Quando a página termina
de carregar, ela dispara um evento load . Em seguida, o usuário interage com a página e faz com que vários
eventos sejam disparados, como clique, pressionamento de tecla, mouseover, mousemove e assim por diante.
O JavaScript é especialmente adequado para programação orientada a eventos, devido ao padrão de retorno
de chamada, que permite que seus programas funcionem de forma assíncrona, ou seja, fora de ordem.

“Não nos ligue, nós ligamos para você” é uma frase famosa em Hollywood, onde muitos candidatos fazem
testes para o mesmo papel em um filme. Seria impossível para a equipe de elenco atender telefonemas de
todos os candidatos o tempo todo. No JavaScript orientado a eventos assíncronos, há um fenômeno
semelhante. Apenas, em vez de fornecer seu número de telefone, você fornece uma função de retorno de
chamada para ser chamado quando for a hora certa. Você pode até fornecer mais retornos de chamada do que
o necessário, porque certos eventos podem nunca acontecer.
Por exemplo, se o usuário nunca clicar em “Compre agora!” então sua função que valida o formato do número
do cartão de crédito nunca será chamada de volta.

Tempo limite

Outro exemplo do padrão de retorno de chamada é quando você usa os métodos de tempo limite fornecidos
pelo objeto janela do navegador : setTimeout() e setInterval(). Esses métodos também aceitam e executam
callbacks: var thePlotThickens = function ()
{ console.log('500ms later...');

};
setTimeout(thePlotThickens, 500);

Observe novamente como a função thePlotThickens é passada como uma variável, sem parênteses, porque
você não quer que ela seja executada imediatamente, mas simplesmente aponta para ela

66 | Capítulo 4: Funções
Machine Translated by Google

para uso posterior por setTimeout(). Passar a string "thePlotThickens()" em vez de um ponteiro de função
é um antipadrão comum semelhante a eval().

Chamadas de retorno em bibliotecas

O retorno de chamada é um padrão simples e poderoso, que pode ser útil ao projetar uma biblioteca. O
código que entra em uma biblioteca de software deve ser o mais genérico e reutilizável possível, e os
callbacks podem ajudar nessa generalização. Você não precisa prever e implementar todos os recursos
que puder imaginar, porque isso vai sobrecarregar a biblioteca e a maioria dos usuários nunca precisará
de uma grande parte desses recursos. Em vez disso, você se concentra na funcionalidade principal e
fornece “ganchos” na forma de retornos de chamada, o que permitirá que os métodos da biblioteca sejam
facilmente construídos, estendidos e personalizados.

Funções de retorno
As funções são objetos, portanto, podem ser usadas como valores de retorno. Isso significa que uma
função não precisa retornar algum tipo de valor de dados ou array de dados como resultado de sua execução.
Uma função pode retornar outra função mais especializada ou pode criar outra função sob demanda,
dependendo de algumas entradas.

Aqui está um exemplo simples: uma função faz algum trabalho, provavelmente alguma inicialização
única, e então trabalha em seu valor de retorno. O valor retornado passa a ser outra função, que também
pode ser executada:

var setup = function ()


{ alert(1);
função de retorno ()
{ alerta(2);
};
};

// usando a função de
configuração var my = setup(); //
alerta 1 my(); // alertas 2

Como setup() envolve a função retornada, ele cria um encerramento e você pode usar esse encerramento
para armazenar alguns dados privados, que podem ser acessados pela função retornada, mas não pelo
código externo. Um exemplo seria um contador que fornece um valor incrementado toda vez que você o
chama:

var configuração = função ()


{ var contagem
= 0; função de retorno
() { retorno (contagem += 1);
};
};

// uso
var next = setup();
próximo(); // retorna 1

Retornando Funções | 67
Machine Translated by Google

próximo(); // 2
próximo(); // 3

Funções autodefinidas
As funções podem ser definidas dinamicamente e podem ser atribuídas a variáveis. Se você
criar uma nova função e atribuí-la à mesma variável que já contém outra função, estará
substituindo a função antiga pela nova. De certa forma, você está reciclando o antigo ponteiro
de função para apontar para uma nova função. E tudo isso pode acontecer dentro do corpo da
antiga função. Nesse caso, a função sobrescreve e se redefine com uma nova implementação.
Isso provavelmente parece mais complicado do que é; vamos dar uma olhada em um exemplo
simples: var scareMe
= function () { alert("Boo!");
scareMe =
function () { alert("Double
boo!");
};
};

// usando a função de autodefinição


scareMe(); // Vaia!
Me assuste(); // Vaia dupla!

Esse padrão é útil quando sua função tem algum trabalho preparatório inicial a fazer e precisa
ser feito apenas uma vez. Como não há motivo para repetir o trabalho quando isso pode ser
evitado, uma parte da função pode não ser mais necessária. Nesses casos, a função de
autodefinição pode atualizar sua própria implementação.

Usar esse padrão obviamente pode ajudar no desempenho de seu aplicativo, porque sua
função redefinida simplesmente funciona menos.

Outro nome para esse padrão é “definição de função preguiçosa”, porque a


função não é definida corretamente até a primeira vez que é usada e fica
preguiçosa depois, fazendo menos trabalho.

Uma desvantagem do padrão é que quaisquer propriedades que você adicionou anteriormente
à função original serão perdidas quando ela se redefinir. Além disso, se a função for usada
com um nome diferente, por exemplo, atribuída a uma variável diferente ou usada como
método de um objeto, a parte de redefinição nunca acontecerá e o corpo da função original
será executado.

Vejamos um exemplo onde a função scareMe() é usada de forma que um objeto de primeira
classe seria usado:

1. Uma nova propriedade é adicionada.

2. O objeto de função é atribuído a uma nova variável.

68 | Capítulo 4: Funções
Machine Translated by Google

3. A função também é usada como método.

Considere o seguinte trecho: // 1.


adicionando uma nova
propriedade scareMe.property = "adequadamente";

// 2. atribuindo um nome diferente var


prank = scareMe;

// 3. usando como método


var spooky =
{ boo: scareMe
};

// chamando com um novo


nome prank(); //
"Vaia!" peça(); //
"Vaia!" console.log(brincadeira.propriedade); // "apropriadamente"

// chamando como um
método spooky.boo(); //
"Vaia!" spooky.boo(); //
"Vaia!" console.log(spooky.boo.property); // "apropriadamente"

// usando a função autodefinida


scareMe(); // Vaia dupla! Me
assuste(); // Vaia dupla!
console.log(scareMe.property); // indefinido

Como você pode ver, a autodefinição não aconteceu como você provavelmente esperava quando a função
foi atribuída a uma nova variável. Toda vez que o prank() era chamado, ele alertava “Boo!”
Ao mesmo tempo, ele substituiu a função global scareMe() , mas o próprio prank() continuou vendo a
definição antiga, incluindo a propriedade de propriedade. O mesmo aconteceu quando a função foi usada
como o método boo() do objeto spooky . Todas essas invocações continuaram reescrevendo o ponteiro
global scareMe() para que, quando fosse chamado, tivesse o corpo atualizado alertando “Double boo” desde
a primeira vez. Também não era mais capaz de ver scareMe.property.

Funções imediatas
O padrão de função imediata é uma sintaxe que permite executar uma função assim que ela é definida. Aqui
está um exemplo:
(function ()
{ alert('cuidado!'); }());

Esse padrão é, em essência, apenas uma expressão de função (nomeada ou anônima), que é executada
logo após sua criação. O termo função imediata não é definido no padrão ECMAScript, mas é curto e ajuda
a descrever e discutir o padrão.

Funções Imediatas | 69
Machine Translated by Google

O padrão consiste nas seguintes partes:

• Você define uma função usando uma expressão de função. (Uma declaração de função não
trabalhar.)

• Você adiciona um conjunto de parênteses no final, o que faz com que a função seja executada
imediatamente.
• Você envolve toda a função entre parênteses (obrigatório somente se você não atribuir a função
a uma variável).

A seguinte sintaxe alternativa também é comum (observe a colocação dos parênteses de fechamento),
mas o JSLint prefere o primeiro: (function ()
{ alert('cuidado!'); })
();

Esse padrão é útil porque fornece uma caixa de proteção de escopo para seu código de inicialização.
Pense no seguinte cenário comum: Seu código precisa executar algumas tarefas de configuração
quando a página é carregada, como anexar manipuladores de eventos, criar objetos e assim por
diante. Todo esse trabalho precisa ser feito apenas uma vez, então não há razão para criar uma
função nomeada reutilizável. Mas o código também requer algumas variáveis temporárias, que não
serão necessárias após a conclusão da fase de inicialização. Seria uma má ideia criar todas essas
variáveis como globais. É por isso que você precisa de uma função imediata — para agrupar todo o
seu código em seu escopo local e não vazar nenhuma variável no escopo global:
(function () {

var dias = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],


hoje = new Date(), '+
dias[hoje.getDay()] + ', ' + hoje.getDate(); msg = 'Hoje é

alerta(msg);

}()); // "Hoje é sexta, 13"

Se esse código não fosse agrupado em uma função imediata, as variáveis days, today e msg seriam
todas variáveis globais, sobras do código de inicialização.

Parâmetros de uma função imediata


Você também pode passar argumentos para funções imediatas, como demonstra o exemplo a seguir:

// imprime:
// Conheci Joe Black na sexta-feira, 13 de agosto de 2010 23:26:59 GMT-0800 (PST)

(função (quem, quando) {


" " "
console.log("Eu conheci + quem + sobre
+ quando);

}("Joe Black", new Date()));

70 | Capítulo 4: Funções
Machine Translated by Google

Comumente, o objeto global é passado como argumento para a função imediata para que
seja acessível dentro da função sem a necessidade de usar window: dessa forma torna o
código mais interoperável em ambientes fora do navegador:
(função (global) {

// acessa o objeto global via `global`

}(esse));

Observe que, em geral, você não deve passar muitos parâmetros para uma função imediata,
porque pode rapidamente se tornar um fardo rolar constantemente para cima e para baixo da
função para entender como ela funciona.

Valores retornados de funções imediatas


Assim como qualquer outra função, uma função imediata pode retornar valores e esses valores
de retorno podem ser atribuídos a
variáveis: var result =
(function ()
{ return 2 + 2; }());

Outra maneira de conseguir o mesmo é omitir os parênteses que envolvem a função, porque
eles não são necessários quando você atribui o valor de retorno de uma função imediata a
uma variável. Omitir o primeiro conjunto de parênteses fornece o seguinte:
var resultado = função ()
{ return 2 +
2; }();

Essa sintaxe é mais simples, mas pode parecer um pouco enganosa. Deixando de notar o ()
no final da função, alguém lendo o código pode pensar que o resultado aponta para uma
função. Na verdade result aponta para o valor retornado pela função imediata, neste caso o
número 4.

Ainda outra sintaxe que realiza os mesmos resultados é:


var resultado = (função ()
{ return 2 +
2; })();

Os exemplos anteriores retornaram um valor inteiro primitivo como resultado da execução da


função imediata. Mas, em vez de um valor primitivo, uma função imediata pode retornar
qualquer tipo de valor, incluindo outra função. Você pode então usar o escopo da função
imediata para armazenar alguns dados em particular, específicos para a função interna que você retorna.

No próximo exemplo, o valor retornado pela função imediata é uma função, que será atribuída
à variável getResult e simplesmente retornará o valor de res, valor que foi pré-computado e
armazenado no fechamento da função imediata:
var getResult = (função () { var
res = 2 + 2;

Funções Imediatas | 71
Machine Translated by Google

função de retorno ()
{ retorno res;

}; }());

As funções imediatas também podem ser usadas quando você define as propriedades do objeto. Imagine
que você precisa definir uma propriedade que provavelmente nunca mudará durante a vida útil do objeto,
mas antes de defini-la, um pouco de trabalho precisa ser executado para descobrir o valor correto.
Você pode usar uma função imediata para agrupar esse trabalho e o valor retornado da função imediata
se tornará o valor da propriedade. O código a seguir mostra um exemplo:

var o =
{ mensagem: (function ()
{ var quem =
"eu", o que =
""
devolver o que + "ligar"; + quem;
}()),
getMsg: function ()
{ return this.message;
}

}; // uso
o.getMsg(); // "me ligue"
o.message; // "liga para mim"
Neste exemplo, o.message é uma propriedade de string, não uma função, mas precisa de uma função,
que executa enquanto o script está carregando e que ajuda a definir a propriedade.

Benefícios e uso O padrão

de função imediata é amplamente utilizado. Isso ajuda você a agrupar uma quantidade de trabalho que
deseja fazer sem deixar nenhuma variável global para trás. Todas as variáveis que você definir serão
locais para as funções de auto-invocação e você não precisa se preocupar em poluir o espaço global com
variáveis temporárias.

Outros nomes para o padrão de função imediata incluem função de “auto-


invocação” ou “auto-execução”, porque a função se executa assim que é
definida.

Esse padrão também é frequentemente usado em bookmarklets, porque os bookmarklets são executados
em qualquer página e manter o namespace global limpo (e seu código de bookmarklet discreto) é crítico.

O padrão também permite agrupar recursos individuais em módulos independentes.


Imagine que sua página é estática e funciona bem sem nenhum JavaScript. Em seguida, no espírito de
aprimoramento progressivo, você adiciona um trecho de código que aprimora a página de alguma forma.
Você pode agrupar esse código (também pode chamá-lo de “módulo” ou “recurso”) em um código imediato

72 | Capítulo 4: Funções
Machine Translated by Google

e verifique se a página funciona bem com e sem ele. Em seguida, você pode adicionar mais
aprimoramentos, removê-los, testá-los, permitir que o usuário os desative e
breve.

Você pode usar o modelo a seguir para definir uma parte da funcionalidade; vamos chamá-lo de módulo1:

// module1 definido em module1.js


(function () {

// todo o código do módulo 1 ...

}());

Seguindo o mesmo modelo, você pode codificar seus outros módulos. Então, quando chegar a hora de
liberar o código para o site ao vivo, você decide quais recursos estão prontos para o horário nobre e
mescla os arquivos correspondentes usando seu script de construção.

Inicialização imediata do objeto


Outra maneira de se proteger da poluição do escopo global, semelhante ao padrão de funções imediatas
descrito anteriormente, é o seguinte padrão de inicialização de objeto imediato .
Esse padrão usa um objeto com um método init() , que é executado imediatamente após a criação do
objeto. A função init() cuida de todas as tarefas de inicialização.

Aqui está um exemplo do padrão de objeto imediato:

({
// aqui você pode definir valores de configuração //
também conhecidos como constantes de
configuração
maxwidth: 600, maxheight: 400,

// você também pode definir métodos


utilitários gimmeMax: function () {
return this.maxwidth + "x" + this.maxheight;
},

// inicializa o
init: function ()
{ console.log(this.gimmeMax()); //
mais tarefas de inicialização...

} }).iniciar();

Em termos de sintaxe, você aborda esse padrão como se estivesse criando um objeto normal usando o
objeto literal. Você também coloca o literal entre parênteses (operador de agrupamento), que instrui o
mecanismo JavaScript a tratar as chaves como objeto literal, não como um bloco de código. (Não é um
loop if ou for .) Após fechar os parênteses, chame o método init() imediatamente.

Inicialização imediata do objeto | 73


Machine Translated by Google

Você também pode agrupar o objeto e a invocação init() em parênteses de agrupamento em vez de agrupar
apenas o objeto. Em outras palavras, ambos funcionam: ({...}).init(); ({...}.iniciar());

Os benefícios desse padrão são os mesmos do padrão de função imediata: você protege o namespace
global enquanto executa as tarefas de inicialização pontuais. Pode parecer um pouco mais complicado em
termos de sintaxe em comparação com apenas agrupar um monte de código em uma função anônima,
mas se suas tarefas de inicialização forem mais complicadas (como costumam ser), isso adiciona estrutura
a todo o procedimento de inicialização. Por exemplo, funções auxiliares privadas são claramente
distinguíveis porque são propriedades do objeto temporário, enquanto em um padrão de função imediata,
é provável que sejam apenas funções espalhadas.

Uma desvantagem desse padrão é que a maioria dos minificadores de JavaScript podem não minificar
esse padrão de forma tão eficiente quanto o código simplesmente agrupado em uma função. As
propriedades e métodos privados não serão renomeados para nomes mais curtos porque, do ponto de
vista de um minificador, não é trivial fazer isso com segurança. No momento em que escrevo, o Closure
Compiler do Google no modo “avançado” é o único minificador que renomeia as propriedades do objeto
imediato para nomes mais curtos, transformando o exemplo anterior em algo como:

({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.
a())}}).b ();

Esse padrão é adequado principalmente para tarefas pontuais e não há


acesso ao objeto após a conclusão do init() . Se você quiser manter uma
referência ao objeto depois que ele for feito, você pode conseguir isso
facilmente adicionando return this; no final de init().

Ramificação de tempo inicial

A ramificação de tempo de inicialização (também chamada de ramificação de tempo de carregamento) é


um padrão de otimização. Quando você sabe que uma determinada condição não mudará ao longo da
vida do programa, faz sentido testar a condição apenas uma vez. O sniffing do navegador (ou detecção de
recursos) é um exemplo típico.

Por exemplo, depois de detectar que XMLHttpRequest é suportado como um objeto nativo, não há chance
de que o navegador subjacente mude no meio da execução do programa e, de repente, você precisará
lidar com objetos ActiveX. Como o ambiente não muda, não há motivo para seu código continuar farejando
(e chegar à mesma conclusão) toda vez que você precisar de outro objeto XHR.

Descobrir os estilos calculados de um elemento DOM ou anexar manipuladores de eventos são outros
candidatos que podem se beneficiar do padrão de ramificação em tempo inicial. A maioria dos
desenvolvedores codificou - pelo menos uma vez em sua vida de programação do lado do cliente - um
utilitário com métodos para anexar e remover ouvintes de eventos, como no exemplo a seguir:

74 | Capítulo 4: Funções
Machine Translated by Google

//
BEFORE var
utils = { addListener: function (el, type, fn)
{ if (typeof window.addEventListener === 'function')
{ el.addEventListener(type, fn, false); }
else if (typeof document.attachEvent === 'function') { // IE
el.attachEvent('on' + tipo, fn); }
else { // navegadores mais
antigos el['on' + type] = fn;
}
},
removeListener: function (el, type, fn) { //
praticamente o mesmo...
}
};

O problema com esse código é que ele é um pouco ineficiente. Toda vez que você chama
utils.addListener() ou utils.removeListener(), as mesmas verificações são executadas repetidamente.

Usando a ramificação de tempo de inicialização, você fareja os recursos do navegador uma vez,
durante o carregamento inicial do script. Nesse momento, você redefine como a função funcionará
ao longo da vida útil da página. O seguinte é um exemplo de como você pode abordar esta tarefa:
// DEPOIS

// a interface var
utils =
{ addListener: null,
removeListener: null
};

// a implementação if
(typeof window.addEventListener === 'function')
{ utils.addListener = function (el, type, fn)
{ el.addEventListener(type, fn, false);
};
utils.removeListener = function (el, type, fn)
{ el.removeEventListener(type, fn, false);
};
} else if (typeof document.attachEvent === 'function') { // IE
utils.addListener = function (el, type, fn)
{ el.attachEvent('on' + type, fn);
};
utils.removeListener = function (el, type, fn)
{ el.detachEvent('on' + type, fn);

}; } else { // navegadores
mais antigos utils.addListener = function (el, type, fn) {
el['on' + tipo] = fn;
};
utils.removeListener = function (el, type, fn) {
el['on' + tipo] = null;
};
}

Ramificação em tempo de inicialização | 75


Machine Translated by Google

Aqui é o momento de mencionar uma palavra de cautela contra o sniffing do navegador. Ao usar esse padrão, não
suponha demais os recursos do navegador. Por exemplo, se você percebeu que o navegador não oferece suporte a
window.addEventListener, não assuma apenas que o navegador com o qual está lidando é o IE e também não oferece
suporte nativo a XMLHttpRequest , embora tenha sido verdade em algum momento no histórico do navegador. Pode
haver casos em que você pode presumir com segurança que os recursos andam juntos, como .addEventListener
e .removeEventListener, mas, em geral, os recursos do navegador mudam independentemente. A melhor estratégia é
farejar os recursos separadamente e, em seguida, usar a ramificação do tempo de carregamento para fazer a detecção
apenas uma vez.

Propriedades da Função—Um Padrão de Memoização


Funções são objetos, então elas podem ter propriedades. Na verdade, eles têm propriedades e métodos prontos para
uso. Por exemplo, toda função, independentemente da sintaxe usada para criá-la, obtém automaticamente uma
propriedade length contendo o número de argumentos que a função espera: function func(a, b, c) {}
console.log(func.length); // 3

Você pode adicionar propriedades personalizadas às suas funções a qualquer momento. Um caso de uso para
propriedades personalizadas é armazenar em cache os resultados (o valor de retorno) de uma função, para que na
próxima vez que a função for chamada, ela não precise refazer cálculos potencialmente pesados. O armazenamento
em cache dos resultados de uma função também é conhecido como memoização.

No exemplo a seguir, a função myFunc cria um cache de propriedade, acessível normalmente via myFunc.cache. A
propriedade cache é um objeto (um hash) onde o parâmetro passado para a função é usado como uma chave e o
resultado do cálculo é o valor. O resultado pode ser qualquer estrutura de dados complicada que você possa precisar:

var myFunc = function (param) { if (!


myFunc.cache[param]) { var
result = {}; // ...
operação cara ...
myFunc.cache[param] = resultado;

} return myFunc.cache[param];
};

// armazenamento em
cache myFunc.cache = {};

O código anterior assume que a função leva apenas um parâmetro de argumento e é um tipo de dados primitivo (como
uma string). Se você tiver mais parâmetros e mais complexos, uma solução genérica seria serializá-los. Por exemplo,
você pode serializar o objeto de argumentos como uma string JSON e usar essa string como uma chave em seu objeto
de cache :

var minhaFunção = função () {

var cachekey = JSON.stringify(Array.prototype.slice.call(argumentos)),

76 | Capítulo 4: Funções
Machine Translated by Google

resultado;

if (!myFunc.cache[cachekey])
{ resultado =
{}; // ... operação cara ...
myFunc.cache[cachekey] = resultado;

} return myFunc.cache[cachekey];
};

// armazenamento em
cache myFunc.cache = {};

Esteja ciente de que na serialização, a “identificação” dos objetos é perdida. Se você tiver dois objetos
diferentes que tenham as mesmas propriedades, ambos compartilharão a mesma entrada de cache.

Outra maneira de escrever a função anterior é usar arguments.callee para se referir à função em vez de
codificar o nome da função. Embora isso seja possível atualmente, esteja ciente de que arguments.callee
não é permitido no modo estrito ECMAScript 5: var myFunc = function (param) {

var f = argumentos.callee,
resultado;

if (!f.cache[param])
{ resultado =
{}; // ... operação cara ...
f.cache[param] = resultado;

} return f.cache[param];
};

// armazenamento em
cache myFunc.cache = {};

Objetos de configuração
O padrão de objeto de configuração é uma forma de fornecer APIs mais limpas, especialmente se você
estiver construindo uma biblioteca ou qualquer outro código que será consumido por outros programas.

É um fato da vida que os requisitos de software mudam à medida que o software é desenvolvido e mantido.
Muitas vezes acontece que você começa a trabalhar com alguns requisitos em mente, mas mais
funcionalidades são adicionadas posteriormente.

Imagine que você está escrevendo uma função chamada addPerson(), que aceita um nome e um sobrenome
e adiciona uma pessoa a uma lista:
função adicionarPessoa(primeiro, último) {...}

Objetos de configuração | 77
Machine Translated by Google

Mais tarde, você aprende que, na verdade, a data de nascimento também precisa ser armazenada e,
opcionalmente, o sexo e o endereço. Então você modifica a função adicionando os novos parâmetros
(colocando com cuidado os parâmetros opcionais no final da lista):
function addPerson(primeiro, último, dob, sexo, endereço) {...}

Neste ponto a assinatura desta função já está ficando um pouco mais longa. E então você aprende que
precisa adicionar um nome de usuário e é absolutamente necessário, não opcional. Agora o chamador
da função terá que passar até mesmo os parâmetros opcionais e cuidado para não misturar a ordem
dos parâmetros: addPerson("Bruce", "Wayne",
new Date(), null, null, "batman");

Passar um grande número de parâmetros não é conveniente. Uma abordagem melhor é substituir
todos os parâmetros por apenas um e torná-lo um objeto; vamos chamá-lo de conf, para “configuração”:
addPerson(conf);

Então o usuário da função pode fazer:

var conf =
{ nome de usuário:
"batman", primeiro:
"Bruce", último: "Wayne"
};
addPessoa(conf);

As vantagens dos objetos de configuração são:

• Não há necessidade de lembrar os parâmetros e sua ordem •


Você pode pular parâmetros opcionais com segurança
• Mais fácil de ler e manter

• Mais fácil de adicionar e remover parâmetros

Os contras dos objetos de configuração são:

• Você precisa se lembrar dos nomes dos parâmetros • Os nomes


das propriedades não podem ser minificados

Esse padrão pode ser útil quando sua função cria elementos DOM, por exemplo, ou na definição dos
estilos CSS de um elemento, porque elementos e estilos podem ter um grande número de atributos e
propriedades opcionais.

78 | Capítulo 4: Funções
Machine Translated by Google

Curry
O restante do capítulo discute o tópico de currying e aplicação de funções parciais .
Mas antes de mergulharmos neste tópico, vamos primeiro ver o que exatamente significa aplicativo de função .

Aplicação de função Em

algumas linguagens de programação puramente funcionais, uma função não é descrita como sendo chamada
ou invocada, mas sim aplicada. Em JavaScript, temos a mesma coisa — podemos aplicar uma função usando
o método Function.prototype.apply(), porque as funções em JavaScript são, na verdade, objetos e possuem
métodos.

Aqui está um exemplo de um aplicativo de função:

// define uma função


var sayHi = function (who) {
return "Olá" + (quem? ", " + quem: "") + "!";
};

// invoca uma função


sayHi(); // "Olá"
sayHi('mundo'); // "Olá Mundo!"

// aplica uma função


sayHi.apply(null, ["olá"]); // "Olá Olá!"

Como você pode ver no exemplo, tanto invocar uma função quanto aplicá-la tem o mesmo resultado. apply()
recebe dois parâmetros: o primeiro é um objeto para vincular a isso dentro da função, o segundo é uma matriz
ou argumentos, que então se torna o objeto de argumentos semelhante a uma matriz disponível dentro da
função. Se o primeiro parâmetro for nulo, isso aponta para o objeto global, que é exatamente o que acontece
quando você chama uma função que não é um método de um objeto específico .

Quando uma função é um método de um objeto, não há nenhuma referência nula passada (como no exemplo
anterior). Aqui o objeto se torna o primeiro argumento para apply():

var alien =
{ sayHi: function (who)
{ return "Olá" + (quem ? ", " + quem: "") + "!";
}
};

alien.sayHi('mundo'); // "Olá Mundo!"


sayHi.apply(alien, ["humanos"]); // "Olá, humanos!"
No snippet anterior, isso dentro de sayHi() aponta para alien. No exemplo anterior, isso aponta para o objeto
global.

Como os dois exemplos demonstram, verifica-se que o que pensamos em chamar uma função não é muito
mais do que açúcar sintático, equivalente a um aplicativo de função.

caril | 79
Machine Translated by Google

Observe que, além de apply(), há um método call() do objeto Function.prototype , mas ainda
é apenas um açúcar de sintaxe em cima de apply(). Às vezes é melhor usar o açúcar:
quando você tem uma função que recebe apenas um parâmetro, pode economizar o trabalho
de criar arrays com apenas um elemento:
// o segundo é mais eficiente, salva um array
sayHi.apply(alien, ["humans"]); // "Olá, humanos!"
sayHi.call(alien, "humanos"); // "Olá, humanos!"

Aplicação Parcial
Agora que sabemos que chamar uma função é, na verdade, aplicar um conjunto de
argumentos a uma função, é possível passar apenas alguns dos argumentos, não todos?
Na verdade, isso é semelhante a como você faria normalmente, se estivesse lidando com
uma função matemática manualmente.
Digamos que você tenha uma função add() que soma dois números: x e y. O trecho a seguir
mostra como você pode abordar uma solução dado que x é 5 e y é 4:
// para fins de ilustração // não
é JavaScript válido

// temos esta função


function add(x, y)
{ return x + y;
}

// e conhecemos os argumentos
add(5, 4);

// passo 1 -- substitui um argumento


function add(5, y)
{ return 5 + y;
}

// passo 2 -- substitui o outro argumento


function add(5, 4)
{ return 5 + 4;
}

Neste snippet, as etapas 1 e 2 não são JavaScript válido, mas é assim que você resolveria
esse problema manualmente. Você pega o valor do primeiro argumento e substitui o x
desconhecido pelo valor conhecido 5 em toda a função. Em seguida, repita com o mesmo
até ficar sem argumentos.
A etapa 1 neste exemplo poderia ser chamada de aplicação parcial: aplicamos apenas o
primeiro argumento. Ao executar uma aplicação parcial, você não obtém um resultado (uma
solução), mas obtém outra função.
O próximo snippet demonstra o uso de um método parcialApply() imaginário:
var add = function (x, y)
{ return x + y;

80 | Capítulo 4: Funções
Machine Translated by Google

};

// aplicativo completo
add.apply(null, [5, 4]); // 9

// aplicativo parcial var


newadd = add.partialApply(null, [5]); //
aplicando um argumento à nova função
newadd.apply(null, [4]); // 9

Como você pode ver, a aplicação parcial nos dá outra função, que pode ser chamada com os
outros argumentos. Na verdade, isso seria equivalente a algo como add(5)(4), porque add(5)
retorna uma função que pode ser chamada com (4). Novamente, o familiar add(5, 4) pode ser
pensado como não muito mais do que açúcar sintático em vez de usar add(5)(4).

Agora, de volta à Terra: não há método parcialApply() e as funções em JavaScript não se


comportam assim por padrão. Mas você pode criá-los, porque o JavaScript é dinâmico o
suficiente para permitir isso.

O processo de fazer com que uma função entenda e manipule uma aplicação parcial é chamado
currying.

Currying
Currying não tem nada a ver com o prato indiano picante; vem do nome do matemático Haskell
Curry. (A linguagem de programação Haskell também leva seu nome.) Currying é um processo
de transformação — transformamos uma função. Um nome alternativo para currying poderia ser
schönfinkelisation, em homenagem ao nome de outro matemático, Moses Schönfinkel, o
inventor original dessa transformação.

Então, como schönfinkelify (ou schönfinkelize ou curry) uma função? Outras linguagens
funcionais podem ter isso embutido na própria linguagem e todas as funções são atualizadas
por padrão. Em JavaScript, podemos modificar a função add() em uma função curried que irá
lidar com a aplicação parcial.

Vamos dar um exemplo: //


a curried add() //
aceita lista parcial de argumentos
function add(x, y)
{ var oldx = x, oldy = y;
if (typeof oldy === "undefined") { // função de
retorno parcial (newy)
{ return oldx + newy;
};

} // retorno completo
da aplicação x + y;
}

// teste

caril | 81
Machine Translated by Google

typeof add(5); // "função"


add(3)(4); // 7

// cria e armazena uma nova função


var add2000 = add(2000);
add2000(10); // 2010

Neste snippet, na primeira vez que você chama add(), ele cria um fechamento em torno da função
interna que ele retorna. O fechamento armazena os valores originais x e y nas variáveis privadas
oldx e oldy. O primeiro, oldx, é usado quando a função interna é executada. Se não houver
aplicação parcial e ambos x e y forem passados, a função procederá simplesmente para adicioná-los.
Essa implementação de add() é um pouco mais detalhada do que o necessário, apenas para fins
de ilustração. Uma versão mais compacta é mostrada no próximo trecho, onde não há oldx e
oldy, simplesmente porque o x original é armazenado no encerramento implicitamente e
reutilizamos y como uma variável local em vez de criar uma nova variável newy como fizemos no
anterior exemplo:
// uma adição
curried // aceita lista parcial de
argumentos function
add(x, y) { if (typeof y === "undefined") { //
função de retorno
parcial (y) { return x + y;
};

} // retorno completo
da aplicação x + y;
}

Nesses exemplos, a própria função add() cuidou de aplicações parciais. Mas podemos fazer o
mesmo de uma forma mais genérica? Em outras palavras, podemos transformar qualquer função
em uma nova que aceite parâmetros parciais? O próximo trecho mostra um exemplo de uma
função de uso geral, vamos chamá-la de schonfinkelize(), que faz exatamente isso. Usamos o
nome schonfinkelize() em parte porque é difícil de pronunciar e em parte porque soa como um
verbo (usar “curry” pode ser ambíguo) e precisamos de um verbo para denotar que se trata de
uma transformação de uma função.

Aqui está a função currying de propósito geral:


function schonfinkelize(fn) { var
slice = Array.prototype.slice,
stored_args = slice.call(arguments, 1);
return function () { var
new_args = slice.call(argumentos),
args = stored_args.concat(new_args);
return fn.apply(null, args);
};
}

A função schonfinkelize() é provavelmente um pouco mais complicada do que deveria ser, mas
apenas porque os argumentos não são um array real em JavaScript. Pegar emprestado o método
slice() de Array.prototype nos ajuda a transformar argumentos em um array e trabalhar mais

82 | Capítulo 4: Funções
Machine Translated by Google

convenientemente com ele. Quando schonfinkelize() é chamado pela primeira vez, ele armazena
uma referência privada ao método slice() (chamado slice) e também armazena os argumentos
com os quais foi chamado (em stored_args), removendo apenas o primeiro, porque o primeiro
argumento é o função sendo curry. Então schonfinkelize() retorna uma nova função. Quando a
nova função é chamada, ela tem acesso (através do encerramento) aos argumentos já
armazenados em particular stored_args e à referência de fatia . A nova função tem que mesclar
apenas os argumentos antigos parcialmente aplicados (stored_args) com os novos (new_args) e
então aplicá-los à função original fn (também disponível privadamente no encerramento).

Agora, munidos de uma maneira geral de tornar qualquer função atualizada, vamos tentar com
alguns testes: // uma
função normal
function add(x, y)
{ return x + y;
}

// curry uma função para obter uma nova


função var newadd = schonfinkelize(add,
5); newadd(4); // 9

// outra opção -- chama a nova função diretamente


schonfinkelize(add, 6)(7); // 13

A função de transformação schonfinkelize() não está limitada a parâmetros únicos ou currying


em uma única etapa. Aqui estão mais alguns exemplos de uso:
// uma função normal
function add(a, b, c, d, e) { return
a + b + c + d + e;
}

// funciona com qualquer número de


argumentos schonfinkelize(add, 1, 2, 3)(5, 5); // 16

// currying em duas
etapas var addOne = schonfinkelize(add,
1); adicionarUm(10, 10, 10,
10); // 41 var addSix = schonfinkelize(addOne,
2, 3); addSeis(5, 5); // 16

Quando usar Currying


Quando você se encontra chamando a mesma função e passando principalmente os mesmos
parâmetros, então a função é provavelmente uma boa candidata para currying. Você pode criar
uma nova função dinamicamente aplicando parcialmente um conjunto de argumentos à sua
função. A nova função manterá os parâmetros repetidos armazenados (para que você não
precise passá-los todas as vezes) e os usará para preencher previamente a lista completa de
argumentos que a função original espera.

caril | 83
Machine Translated by Google

Resumo
Em JavaScript o conhecimento e uso adequado das funções é fundamental. Este capítulo discutiu o
histórico e a terminologia relacionada às funções. Você aprendeu sobre os dois recursos importantes das
funções em JavaScript, a saber:

1. Funções são objetos de primeira classe; eles podem ser passados como valores e aumentados com
propriedades e métodos.

2. As funções fornecem escopo local, o que outras chaves não fornecem. Também algo a ter em mente
é que as declarações de variáveis locais são levantadas para o topo do local
escopo.

A sintaxe para criar funções inclui:

1. Expressões de funções nomeadas

2. Expressões de funções (iguais às anteriores, mas sem nome), também conhecidas como
funções anônimas 3.

Declarações de função, semelhantes à sintaxe de função em outras linguagens

Depois de cobrir o histórico e a sintaxe das funções, você aprendeu sobre vários padrões úteis, que podem
ser agrupados nas seguintes categorias:

1. Padrões de API, que ajudam a fornecer interfaces melhores e mais limpas para suas funções
ções. Esses padrões incluem:

Padrões de retorno
de chamada Passar uma função como

um argumento Objetos de
configuração Ajudam a manter o número de argumentos para uma função sob

controle Funções de
retorno Quando o valor de retorno de uma função é outra função

Escovando
Quando novas funções são criadas com base nas existentes mais uma lista parcial de argumentos

2. Padrões de inicialização, que ajudam a executar tarefas de inicialização e configuração (muito comuns
quando se trata de páginas e aplicativos da Web) de maneira mais clara e estruturada, sem poluir o
namespace global com variáveis temporárias. Isso inclui: Funções imediatas Executadas assim que

são definidas Inicialização


imediata do objeto Tarefas de inicialização

estruturadas em um objeto anônimo


que fornece um método para ser chamado imediatamente

84 | Capítulo 4: Funções
Machine Translated by Google

Ramificação no tempo
de inicialização Ajuda a ramificar o código apenas uma vez durante a execução inicial do código,
em vez de muitas vezes durante a vida útil do aplicativo

3. Padrões de desempenho, que ajudam a acelerar o código. Esses incluem:


Memorização
Usando propriedades de função para que os valores calculados não sejam calculados novamente

Funções de autodefinição
Sobrescrever-se com novos corpos para fazer menos trabalho a partir da segunda invocação e
depois

Resumo | 85
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 5

Padrões de Criação de Objetos

Criar objetos em JavaScript é fácil — você usa o objeto literal ou usa funções construtoras. Neste
capítulo vamos além disso e veremos alguns padrões adicionais para a criação de objetos.

A linguagem JavaScript é simples e direta e, muitas vezes, não há nenhuma taxa de sincronização
especial para recursos com os quais você pode estar acostumado em outras linguagens, como
namespaces, módulos, pacotes, propriedades privadas e membros estáticos. Este capítulo leva você
através de padrões comuns para implementar, substituir ou apenas pensar de maneira diferente sobre esses recursos.

Damos uma olhada no namespace, declaração de dependência, padrão de módulo e padrões de


sandbox - eles ajudam você a organizar e estruturar o código do aplicativo e atenuar o efeito dos globais
implícitos. Outros tópicos de discussão incluem membros privados e privilegiados, membros estáticos e
privados estáticos, constantes de objeto, encadeamento e uma maneira inspirada em classe para definir
construtores.

Padrão de Namespace
Namespaces ajudam a reduzir o número de globais exigidos por nossos programas e, ao mesmo tempo,
também ajudam a evitar colisões de nomenclatura ou prefixação excessiva de nomes.

O JavaScript não possui namespaces incorporados à sintaxe da linguagem, mas esse é um recurso
bastante fácil de obter. Em vez de poluir o escopo global com muitas funções, objetos e outras variáveis,
você pode criar um (e idealmente apenas um) objeto global para seu aplicativo ou biblioteca. Em seguida,
você pode adicionar toda a funcionalidade a esse objeto.

Considere o seguinte exemplo:

// ANTES: 5 globais
// Aviso: antipadrão

// construtores
função Parent() {}
função Child() {}

// uma variável

87
Machine Translated by Google

var some_var = 1;

// alguns objetos
var module1 = {};
module1.data = {a: 1, b: 2}; var
módulo2 = {};

Você pode refatorar esse tipo de código criando um único objeto global para sua aplicação, chamado, por
exemplo, MYAPP, e alterar todas as suas funções e variáveis para que se tornem propriedades do seu
objeto global:

// DEPOIS: 1 global

// objeto global var


MYAPP = {};

// construtores
MYAPP.Parent = function () {};
MYAPP.Child = função () {};

// uma variável
MYAPP.some_var = 1;

// um contêiner de objeto
MYAPP.modules = {};

// objetos aninhados
MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {a: 1, b: 2};
MYAPP.modules.module2 = {};

Para o nome do objeto de namespace global, você pode escolher, por exemplo, o nome de seu aplicativo
ou biblioteca, seu nome de domínio ou o nome de sua empresa. Freqüentemente, os desenvolvedores
usam a convenção de tornar a variável global ALL CAPS, para que ela se destaque para os leitores do
código. (Mas lembre-se de que todas as maiúsculas também são frequentemente usadas para constantes.)

Esse padrão é uma boa maneira de criar um namespace para seu código e evitar colisões de nomes em
seu próprio código e colisões entre seu código e código de terceiros na mesma página, como bibliotecas ou
widgets JavaScript. Este padrão é altamente recomendado e perfeitamente aplicável para muitas tarefas,
mas tem algumas desvantagens:

• Um pouco mais para digitar; prefixar cada variável e função soma no total
quantidade de código que precisa ser baixado

• Apenas uma instância global significa que qualquer parte do código pode modificar o global
instância e o restante da funcionalidade obtém o estado atualizado • Nomes

aninhados longos significam pesquisas de resolução de propriedade mais longas (mais lentas)

O padrão sandbox discutido posteriormente neste capítulo aborda essas desvantagens.

88 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Função de namespace de uso geral À medida

que a complexidade de um programa aumenta e algumas partes do código são divididas em


diferentes arquivos e incluídas condicionalmente, torna-se inseguro assumir apenas que seu
código é o primeiro a definir um determinado namespace ou uma propriedade dentro dele.
Algumas das propriedades que você está adicionando ao namespace podem já existir e você pode substituí-las.
Portanto, antes de adicionar uma propriedade ou criar um namespace, é melhor verificar primeiro
se ele ainda não existe, conforme mostrado neste
exemplo: //
unsafe var
MYAPP
= {}; // melhor se (tipo de MYAPP ===
"indefinido") { var MYAPP = {};

} // ou menor
var MYAPP = MYAPP || {};

Você pode ver como essas verificações adicionadas podem resultar rapidamente em muitos códigos
repetidos. Por exemplo, se você quiser definir MYAPP.modules.module2, terá que fazer três
verificações, uma para cada objeto ou propriedade que estiver definindo. É por isso que é útil ter
uma função reutilizável que cuide dos detalhes de namespace. Vamos chamar essa função de
namespace() e usá-la assim:
// usando uma função de namespace
MYAPP.namespace('MYAPP.modules.module2');

// equivalente a: //
var MYAPP = { //
módulos: { //
módulo2: {} // // };
}

A seguir está um exemplo de implementação da função de namespace. Essa implementação é não


destrutiva, o que significa que, se existir um namespace, ele não será recriado:
var MYAPP = MYAPP || {};

MYAPP.namespace = function (ns_string)


{ var parts = ns_string.split('.'), pai =
MYAPP, i;

// retira global inicial redundante if


(parts[0] === "MYAPP") { parts
= parts.slice(1);
}

for (i = 0; i < partes.comprimento; i += 1) {


// cria uma propriedade se ela não existir if
(typeof parent[parts[i]] === "undefined") {
pai[partes[i]] = {};
}

Padrão de namespace | 89
Machine Translated by Google

pai = pai[partes[i]];

} return pai;
};

Essa implementação permite todos esses usos: //


atribui o valor retornado a um var local var
module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // verdadeiro

// pula `MYAPP` inicial


MYAPP.namespace('modules.module51');

// namespace
longo MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');

A Figura 5-1 mostra como os namespaces no exemplo anterior se parecem quando


inspecionados no Firebug.

Figura 5-1. Espaço de nomes MYAPP inspecionado no Firebug

Declarando Dependências
As bibliotecas JavaScript geralmente são modulares e com namespace, o que permite incluir
apenas os módulos necessários. Por exemplo, em YUI2 há uma variável global YAHOO,
que serve como um namespace, e então módulos que são propriedades da variável global,
como YAHOO.util.Dom (o módulo DOM) e YAHOO.util.Event (módulo Eventos ).

É uma boa ideia declarar os módulos dos quais seu código depende na parte superior de
sua função ou módulo. A declaração envolve criar apenas uma variável local e apontar para
o módulo desejado:

90 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

var myFunction = function () { //


dependências var
event = YAHOO.util.Event, dom =
YAHOO.util.Dom;

// usa variáveis de evento e dom //


para o resto da função...
};

Este é um padrão extremamente simples, mas ao mesmo tempo tem inúmeros benefícios:

• A declaração explícita de dependências sinaliza aos usuários de seu código o específico


arquivos de script que eles precisam para garantir que sejam incluídos na página.

• A declaração inicial na parte superior da função facilita a localização e resolução


dependências.

• Trabalhar com uma variável local (como dom) é sempre mais rápido do que trabalhar com uma global
(como YAHOO) e ainda mais rápido do que trabalhar com propriedades aninhadas de uma variável
global (como YAHOO.util.Dom), resultando em melhor desempenho . Ao seguir esse padrão de
declaração de dependência, a resolução do símbolo global é realizada apenas uma vez na função.
Depois disso, é usada a variável local, que é muito mais rápida.

• Ferramentas avançadas de minificação, como YUICompressor e compilador Google Closure, renomearão


variáveis locais (portanto, o evento provavelmente se tornará apenas um caractere como A), resultando
em código menor, mas nunca em variáveis globais, porque não é seguro fazer isso.

O trecho a seguir é uma ilustração do efeito do padrão de declaração de dependência no código minificado.
Embora test2(), que segue o padrão, pareça um pouco mais complicado porque requer mais linhas de código
e uma variável extra, na verdade resulta em menos código após a minificação, significando menos código
que o usuário precisa baixar:

função test1()
{ alert(MYAPP.modules.m1);
alert(MYAPP.modules.m2);
alert(MYAPP.modules.m51);
}

/*
corpo do teste1
minificado: alert(MYAPP.modules.m1);alert(MYAPP.modules.m2);alert(MYAPP.modules.m51)
*/

função test2() { var


módulos = MYAPP.modules;
alerta(módulos.m1);
alerta(módulos.m2);
alerta(módulos.m51);
}

/*
corpo test2 minificado:

Declarando Dependências | 91
Machine Translated by Google

var a=MYAPP.modules;alert(a.m1);alert(a.m2);alert(a.m51) */

Propriedades e métodos privados


JavaScript não tem sintaxe especial para denotar propriedades e métodos privados, protegidos ou
públicos, ao contrário de Java ou outras linguagens. Todos os membros do objeto são públicos:

var myobj =
{ myprop:
1, getProp: function ()
{ return this.myprop;
}
};
console.log(myobj.myprop); // `myprop` é publicamente acessível
console.log(myobj.getProp()); // getProp() também é público

O mesmo acontece quando você usa funções construtoras para criar objetos; todos os membros
ainda são
públicos: function
Gadget() { this.name
= 'iPod'; this.stretch = function
() { return 'iPad';
};

} var brinquedo = new


Gadget(); console.log(brinquedo.nome); //
`name` é público console.log(toy.stretch()); // stretch() é público

Membros Privados
Embora a linguagem não tenha sintaxe especial para membros privados, você pode implementá-los
usando um encerramento. Suas funções de construtor criam um fechamento e quaisquer variáveis que
fazem parte do escopo do fechamento não são expostas fora do construtor.
No entanto, essas variáveis privadas estão disponíveis para os métodos públicos: métodos definidos
dentro do construtor e expostos como parte dos objetos retornados. Vejamos um exemplo onde name é
um membro privado, não acessível fora do construtor:

function Gadget() { //
membro privado
var name = 'iPod'; //
public function
this.getName = function ()
{ return name;
};

} var brinquedo = new Gadget();

// `name` é indefinido, é privado


console.log(toy.name); // indefinido

92 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

// método público tem acesso a `name`


console.log(toy.getName()); // "iPod"
Como você pode ver, é fácil obter privacidade em JavaScript. Tudo o que você precisa fazer é agrupar os
dados que deseja manter privados em uma função e garantir que sejam locais para a função, o que
significa não disponibilizá-los fora da função.

Métodos privilegiados A

noção de métodos privilegiados não envolve nenhuma sintaxe específica; é apenas um nome dado aos
métodos públicos que têm acesso aos membros privados (e, portanto, têm mais privilégios).

No exemplo anterior, getName() é um método privilegiado porque tem acesso “especial” ao nome da
propriedade privada.

Falhas de privacidade

Existem alguns casos extremos quando você está preocupado com a privacidade:

• Algumas versões anteriores do Firefox permitem que um segundo parâmetro seja passado para eval() ,
que é um objeto de contexto que permite que você entre no escopo privado da função. Da mesma
forma, a propriedade __parent__ no Mozilla Rhino permitiria o acesso ao escopo privado. Esses
casos extremos não se aplicam a navegadores amplamente usados atualmente.

• Quando você está retornando diretamente uma variável privada de um método privilegiado e essa
variável é um objeto ou array, o código externo pode modificar a variável privada porque ela é passada
por referência.

Vamos examinar o segundo caso um pouco mais de perto. A seguinte implementação do Gadget parece
inocente:

function Gadget()
{ // private member
var specs =
{ screen_width:
320, screen_height:
480, color: "white"
};

// public function
this.getSpecs = function ()
{ return specs;
};
}

O problema aqui é que getSpecs() retorna uma referência ao objeto specs . Isso permite que o usuário do
Gadget modifique as especificações aparentemente ocultas e privadas:

var toy = new Gadget(),


specs = toy.getSpecs();

Propriedades e métodos privados | 93


Machine Translated by Google

especificações.cor = "preto";
especificações.preço = "grátis";

console.dir(toy.getSpecs());

Esse resultado da impressão no console do Firebug é mostrado na Figura 5-2.

Figura 5-2. O objeto privado foi modificado

A solução para esse comportamento inesperado é ter cuidado para não passar referências a
objetos e arrays que você deseja manter privados. Uma maneira de conseguir isso é fazer com
que getSpecs() retorne um novo objeto contendo apenas alguns dos dados que podem ser
interessantes para o consumidor do objeto. Isso também é conhecido como Princípio da Menor
Autoridade (POLA), que afirma que você nunca deve dar mais do que o necessário. Nesse caso,
se o consumidor do Gadget estiver interessado em saber se o gadget cabe em uma determinada
caixa, ele precisa apenas das dimensões. Assim, em vez de fornecer tudo, você pode criar
getDimensions(), que retorna um novo objeto contendo apenas largura e altura. Você pode não
precisar implementar getSpecs() .

Outra abordagem, quando você precisa passar todos os dados, é criar uma cópia do objeto de
especificações , usando uma função de clonagem de objeto de uso geral. O próximo capítulo
oferece duas dessas funções — uma chamada extend() e faz uma cópia rasa do objeto fornecido
(copia apenas os parâmetros de nível superior). O outro é chamado extendDeep(), que faz uma
cópia profunda, copiando recursivamente todas as propriedades e suas propriedades aninhadas.

Literais de objeto e privacidade

Até agora, examinamos apenas exemplos de uso de construtores para obter privacidade. Mas e
os casos em que seus objetos são criados com literais de objeto? Ainda é possível ter membros
privados?

Como você viu antes, tudo o que você precisa é de uma função para agrupar os dados privados.
Portanto, no caso de literais de objeto, você pode usar o encerramento criado por uma função
imediata anônima adicional. Aqui
está um exemplo: var myobj; // este será
o objeto
(function () { //
private members var name = "my, oh my";

// implementa a parte pública //


nota -- sem `var`

94 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

myobj =
{ // método privilegiado
getName: function ()
{ return name;
}

}; }());

myobj.getName(); // "meu, oh meu"

A mesma ideia, mas com uma implementação ligeiramente diferente, é fornecida no exemplo a seguir:
var myobj
= (function () { // private
members var name
= "my, oh my";

// implementa a parte pública


return
{ getName: function ()
{ return name;
}

}; }());

myobj.getName(); // "meu, oh meu"

Este exemplo também é o esqueleto do que é conhecido como “padrão de módulo”, que examinaremos
daqui a pouco.

Protótipos e privacidade Uma

desvantagem dos membros privados quando usados com construtores é que eles são recriados toda vez
que o construtor é chamado para criar um novo objeto.

Na verdade, esse é um problema com qualquer membro que você adicionar a isso dentro dos construtores.
Para evitar a duplicação de esforços e economizar memória, você pode adicionar propriedades e métodos
comuns à propriedade protótipo do construtor. Desta forma, as partes comuns são compartilhadas entre
todas as instâncias criadas com o mesmo construtor. Você também pode compartilhar os membros
privados ocultos entre as instâncias. Para fazer isso, você pode usar uma combinação de dois padrões:
propriedades privadas dentro de construtores e propriedades privadas em objetos literais. Como a
propriedade protótipo é apenas um objeto, ela pode ser criada com os literais do objeto.

Aqui está um exemplo de como você pode conseguir isso:

function Gadget() { //
membro privado
var name = 'iPod'; //
public function
this.getName = function ()
{ return name;
};

Propriedades e métodos privados | 95


Machine Translated by Google

Gadget.prototype = (function () { //
membro privado
var browser = "Mobile Webkit"; //
membros do protótipo público
return
{ getBrowser: function ()
{ return browser;
}

}; }());

var brinquedo = new


Gadget(); console.log(toy.getName()); // método "próprio"
privilegiado console.log(toy.getBrowser()); // método de protótipo privilegiado

Revelando Funções Privadas como Métodos Públicos


O padrão de revelação é sobre ter métodos privados, que você também expõe como
métodos públicos. Isso pode ser útil quando toda a funcionalidade de um objeto é crítica
para o funcionamento do objeto e você deseja protegê-lo o máximo possível. Mas, ao
mesmo tempo, você deseja fornecer acesso público a algumas dessas funcionalidades
porque isso também pode ser útil. Quando você expõe métodos publicamente, você os
torna vulneráveis; alguns dos usuários de sua API pública podem modificá-la, mesmo
involuntariamente. No ECMAScript 5 você tem a opção de congelar um objeto, mas não nas versões anteriores
Entre no padrão de revelação (o termo cunhado por Christian Heilmann originalmente era
“padrão de módulo revelador”).
Vamos dar um exemplo, construindo sobre um dos padrões de privacidade - os membros
privados em objetos literais:
var meuarray;

(função () {

var astr = "[matriz de objetos]",


toString = Object.prototype.toString;

function isArray(a)
{ return toString.call(a) === astr;
}

função indexOf(palheiro, agulha) { var i =


0, max =
palheiro.comprimento;
for (; i < max; i += 1) { if
(palheiro[i] === agulha) { return i;

} return ÿ1;
}

96 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

myarray =
{ isArray:
isArray, indexOf:
indexOf, inArray: indexOf
};

}());

Aqui você tem duas variáveis privadas e duas funções privadas — isArray() e indexOf(). Perto do
final da função imediata, o objeto myarray é preenchido com a funcionalidade que você decide tornar
disponível publicamente. Nesse caso, o mesmo indexOf() privado é exposto como indexOf no estilo
ECMAScript 5 e inspirado em PHP emArray. Testando o novo objeto myarray :

myarray.isArray([1,2]); // true
myarray.isArray({0: 1}); // false
myarray.indexOf(["a", "b", "z"], "z"); // 2
myarray.inArray(["a", "b", "z"], "z"); // 2

Agora, se algo inesperado acontecer, por exemplo, ao indexOf() público, o indexOf() privado ainda
é seguro e, portanto, o inArray() continuará funcionando:
myarray.indexOf = nulo;
myarray.inArray(["a", "b", "z"], "z"); // 2

Padrão de módulo
O padrão de módulo é amplamente usado porque fornece estrutura e ajuda a organizar seu código
conforme ele cresce. Ao contrário de outras linguagens, o JavaScript não possui sintaxe especial
para pacotes, mas o padrão de módulo fornece as ferramentas para criar partes de código
independentes independentes, que podem ser tratadas como caixas pretas de funcionalidade e
adicionadas, substituídas ou removidas de acordo com os requisitos (em constante mudança) do
software que você está escrevendo.

O padrão de módulo é uma combinação de vários padrões descritos até agora no livro, a saber:

• Namespaces •
Funções imediatas

• Membros privados e privilegiados •


Declarando dependências A

primeira etapa é configurar um namespace. Vamos usar a função namespace() do início deste
capítulo e iniciar um exemplo de módulo utilitário que fornece métodos de array úteis:
MYAPP.namespace('MYAPP.utilities.array');

O próximo passo é definir o módulo. O padrão usa uma função imediata que fornecerá escopo
privado se a privacidade for necessária. A função imediata retorna um objeto—

Padrão de módulo | 97
Machine Translated by Google

o módulo atual com sua interface pública, que estará disponível para os consumidores do módulo:

MYAPP.utilities.array = (function ()
{ return
{ // todo...

}; }());

Em seguida, vamos adicionar alguns métodos à interface pública:

MYAPP.utilities.array = (function ()
{ return
{ inArray: function (agulha, palheiro) { // ...

},
isArray: função (a) { // ...

}; }());

Usando o escopo privado fornecido pela função imediata, você pode declarar algumas propriedades e
métodos privados conforme necessário. Bem no topo da função imediata também estará o lugar para
declarar quaisquer dependências que seu módulo possa ter. Após as declarações de variáveis, você
pode, opcionalmente, colocar qualquer código de inicialização único que ajude a configurar o módulo. O
resultado final é um objeto retornado pela função imediata que contém a API pública do seu módulo:
MYAPP.namespace('MYAPP.utilities.array');

MYAPP.utilities.array = (função () {

// dependências
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,

// propriedades
privadas array_string = "[object
Array]", ops = Object.prototype.toString;

// métodos
privados // ...

// fim da variável

// opcionalmente procedimentos init


únicos // ...

// retorno da
API pública {

inArray: function (needle, haystack) { for


(var i = 0, max = haystack.length; i < max; i += 1) {
if (palheiro[i] === agulha) {

98 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

retornar verdadeiro;
}
}
},

isArray: function (a)


{ return ops.call(a) === array_string;

} // ... mais métodos e propriedades

}; }());

O padrão de módulo é uma maneira amplamente usada e altamente recomendada para organizar seu código,
especialmente à medida que ele cresce.

Revelando o padrão do módulo Já

discutimos o padrão de revelação neste capítulo enquanto examinamos os padrões de privacidade. O padrão do
módulo pode ser organizado de forma semelhante, onde todos os métodos são mantidos privados e você só
expõe aqueles que você decidir no final, enquanto configura a API pública.

O acima pode se tornar:

MYAPP.utilities.array = (função () {

// propriedades privadas
var array_string = "[object Array]", ops =
Object.prototype.toString,

// métodos privados
inArray = function (palheiro, agulha) { for
(var i = 0, max = haystack.length; i < max; i += 1) {
if (palheiro[i] === agulha) { return
i;
}

} return ÿ1;
},
isArray = function (a)
{ return ops.call(a) === array_string;

}; // fim da variável

// revelando o retorno da
API
pública { isArray:
isArray, indexOf: inArray

}; }());

Padrão de módulo | 99
Machine Translated by Google

Módulos que criam construtores


O exemplo anterior produziu um objeto MYAPP.utilities.array, mas às vezes é mais
conveniente criar seus objetos usando funções construtoras. Você ainda pode fazer
isso usando o padrão de módulo. A única diferença é que a função imediata que
envolve o módulo retornará uma função no final, e não um objeto.
Considere o seguinte exemplo do padrão de módulo que cria uma função construtora
MYAPP.utilities.Array:
MYAPP.namespace('MYAPP.utilities.Array');
MYAPP.utilities.Array = (função () {

// dependências
var uobj = MYAPP.utilities.object, ulang
= MYAPP.utilities.lang,

// propriedades e métodos privados...


Constr;

// fim da variável

// opcionalmente procedimentos init


únicos // ...

// API pública -- construtor


Constr = function (o)
{ this.elements = this.toArray(o);

}; // API pública -- protótipo


Constr.prototype =
{ construtor: MYAPP.utilities.Array,
versão: "2.0",
toArray: function (obj) { for
(var i = 0, a = [], len = obj.length ; i < len; i += 1) { a[i] = obj[i];

} retorna um;
}
};

// retorna o construtor // a
ser atribuído ao novo namespace return
Constr;

}());

A forma de usar este novo construtor será assim:


var arr = new MYAPP.utilities.Array(obj);

100 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Importando globais para um módulo


Em uma variação comum do padrão, você pode passar argumentos para a função imediata
que envolve o módulo. Você pode passar quaisquer valores, mas geralmente são
referências a variáveis globais e até mesmo ao próprio objeto global. A importação de
globais ajuda a acelerar a resolução do símbolo global dentro da função imediata, porque
as variáveis importadas se tornam locais para a função:
MYAPP.utilities.module = (função (aplicativo, global) {

// referências ao objeto global // e ao


objeto de namespace app global // agora
estão localizadas

}(MYAPP, este));

Padrão de caixa de areia

O padrão sandbox aborda as desvantagens do padrão namespace, a saber:

• Confiança em uma única variável global para ser a global do aplicativo. No padrão de
espaçamento de nomes, não há como ter duas versões do mesmo aplicativo ou biblioteca
executadas na mesma página, pois ambas precisam do mesmo nome de símbolo global,
por exemplo, MYAPP.
• Nomes longos e pontilhados para digitar e resolver no tempo de execução, por exemplo,
MYAPP.utilities.array.

Como o nome sugere, o padrão sandbox fornece um ambiente para os módulos “jogarem”
sem afetar outros módulos e seus sandboxes pessoais.

O padrão é muito usado no YUI versão 3, por exemplo, mas lembre-se de que a discussão a
seguir é um exemplo de implementação de referência e não tenta descrever como a
implementação de sandbox do YUI3 funciona.

Um Construtor Global

No padrão de namespace, você tem um objeto global; no padrão sandbox, o único global é
um construtor: vamos chamá-lo de Sandbox(). Você cria objetos usando esse construtor e
também passa uma função de retorno de chamada, que se torna o ambiente isolado em área
restrita para seu código.

O uso do sandbox ficará assim:


new Sandbox(function (box)
{ // seu código
aqui... });

A caixa de objeto será como MYAPP no exemplo de namespace — ela terá toda a
funcionalidade de biblioteca necessária para fazer seu código funcionar.

Padrão de caixa de areia | 101


Machine Translated by Google

Vamos adicionar mais duas coisas ao padrão:

• Com alguma magia (reforçando o novo padrão do Capítulo 3), você pode assumir o novo e não exigi-
lo ao criar o objeto. • O construtor Sandbox()
pode aceitar um argumento de configuração adicional (ou argumentos) especificando nomes de
módulos necessários para esta instância de objeto. Queremos que o código seja modular, portanto,
a maior parte da funcionalidade fornecida pelo Sandbox() estará contida em módulos.

Com esses dois recursos adicionais, vamos ver alguns exemplos de como será o código para estanciar
objetos.

Você pode omitir new e criar um objeto que usa alguns módulos fictícios “ajax” e “evento” como:

Sandbox(['ajax', 'evento'], função (caixa) {


// console.log(caixa); });

Este exemplo é semelhante ao anterior, mas desta vez os nomes dos módulos de tempo são passados
como argumentos individuais:
Sandbox('ajax', 'dom', function (box) { //
console.log(box); });

E que tal usar um argumento curinga “*” para significar “usar todos os módulos disponíveis”?
Por conveniência, digamos também que, quando nenhum módulo for passado, o sandbox assumirá '*'.
Então, duas maneiras de usar todos os módulos disponíveis serão:
Sandbox('*', function (box) { //
console.log(box); });

Sandbox(function (box) { //
console.log(box); });

E mais um exemplo de uso do padrão ilustra como você pode instanciar objetos sandbox várias vezes -
e você pode até aninhar um dentro do outro sem que os dois interfiram:

Sandbox('dom', 'evento', função (caixa) {

// trabalha com dom e evento

Sandbox('ajax', função (caixa) {


// outro objeto "caixa" em sandbox //
esta "caixa" não é a mesma que //
a "caixa" fora desta função

//...

// feito com Ajax

102 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

});

// nenhum traço do módulo Ajax aqui

});

Como você pode ver nesses exemplos, ao usar o padrão sandbox, você pode proteger o namespace
global tendo seu código agrupado em funções de retorno de chamada.

Se precisar, você também pode usar o fato de que funções são objetos e armazenam alguns dados
como propriedades “estáticas” do construtor Sandbox() .

E, finalmente, você pode ter diferentes instâncias dependendo do tipo de módulos que você precisa
e essas instâncias funcionam independentemente umas das outras.

Agora vamos ver como você pode abordar a implementação do construtor Sandbox() e seus módulos
para dar suporte a toda essa funcionalidade.

Adicionando

módulos Antes de implementar o construtor real, vamos ver como podemos abordar a adição de
módulos.

A função construtora Sandbox() também é um objeto, então você pode adicionar uma propriedade
estática chamada módulos a ela. Esta propriedade será outro objeto contendo pares chave-valor
onde as chaves são os nomes dos módulos e os valores são as funções que implementam cada
módulo: Sandbox.modules =
{};

Sandbox.modules.dom = função (caixa)


{ box.getElement = função () {};
box.getStyle = função () {};
caixa.foo = "barra";
};

Sandbox.modules.event = function (caixa) {


// acesso ao protótipo Sandbox, se necessário: //
box.constructor.prototype.m = "mmm";
box.attachEvent = função () {};
box.dettachEvent = função () {};
};

Sandbox.modules.ajax = função (caixa)


{ caixa.makeRequest = função () {};
caixa.getResponse = função () {};
};

Neste exemplo, adicionamos os módulos dom, event e ajax, que são peças comuns de funcionalidade
em todas as bibliotecas ou aplicativos da Web complexos.

As funções que implementam cada módulo aceitam a caixa de instância atual como um parâmetro
e podem adicionar propriedades e métodos adicionais a essa instância.

Padrão de caixa de areia | 103


Machine Translated by Google

Implementando o Construtor
Por fim, vamos implementar o construtor Sandbox() (naturalmente, você gostaria
de renomear esse tipo de construtor para algo que faça sentido para sua biblioteca
ou aplicativo):
function Sandbox() { //
transformando argumentos em um
array var args = Array.prototype.slice.call(arguments), //
o último argumento é o callback callback
= args.pop(), // os
módulos podem ser passados como um array ou como parâmetros
individuais módulos = (args[0] && typeof args[0] === "string") ? args : args[0],
i;

// certifique-se de que a função seja


chamada // como um
construtor if (!(esta instância do
Sandbox)) { return new Sandbox(modules, callback);
}

// adiciona propriedades a `this` conforme


necessário:
this.a = 1; este.b = 2;

// agora adiciona módulos ao objeto `this`


principal // nenhum módulo ou "*" ambos significam
"usar todos os módulos" if (!modules ||
modules ===
'*') { modules = []; for (i in
Sandbox.modules) { if
(Sandbox.modules.hasOwnProperty(i)) { modules.push(i);
}
}
}

// inicializa os módulos necessários


para (i = 0; i < modules.length; i += 1)
{ Sandbox.modules[modules[i]](this);
}

// chama o callback
callback(this);
}

// quaisquer propriedades de protótipo


conforme necessário
Sandbox.prototype =
{ name: "My
Application", versão: "1.0",
getName: function () { return this.name;
}
};

104 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Os principais itens na implementação são:

• Há uma verificação se esta é uma instância do Sandbox e se não (o que significa que o Sandbox()
foi chamado sem new), chamamos a função novamente como um construtor. • Você pode
adicionar propriedades a isso dentro do construtor. Você também pode adicionar propriedades ao
protótipo do construtor. • Os módulos
necessários podem ser passados como um array de nomes de módulos, ou como argumentos
individuais, ou com o curinga * (ou omitido), o que significa que devemos carregar todos os
módulos disponíveis. Observe que nesta implementação de exemplo não nos preocupamos
em carregar a funcionalidade necessária de arquivos adicionais, mas isso é definitivamente
uma opção. Isso é algo suportado pelo YUI3, por exemplo. Você pode carregar apenas o
módulo mais básico (também conhecido como “semente”) e quaisquer módulos necessários
serão carregados de arquivos externos usando uma convenção de nomenclatura em que os
nomes dos arquivos correspondem aos nomes dos módulos.
• Quando conhecemos os módulos necessários, os inicializamos, ou seja, chamamos a função
que implementa cada módulo. • O último
argumento para o construtor é o retorno de chamada. O retorno de chamada será invocado no
final usando a instância recém-criada. Esse retorno de chamada é, na verdade, a caixa de
proteção do usuário e obtém um objeto de caixa preenchido com todas as funcionalidades solicitadas.

Membros Estáticos
Propriedades e métodos estáticos são aqueles que não mudam de uma instância para outra.
Em linguagens baseadas em classe, os membros estáticos são criados usando uma sintaxe especial
e usados como se fossem membros da própria classe. Por exemplo, um método estático max() de
alguma classe MathUtils seria invocado como MathUtils.max(3, 5). Este é um exemplo de membro
estático público, que pode ser usado sem a necessidade de criar uma instância da classe. Também
pode haver membros estáticos privados - não visíveis para o consumidor da classe, mas ainda
compartilhados entre todas as instâncias da classe. Vamos ver como implementar membros
estáticos privados e públicos em JavaScript.

Membros Estáticos Públicos

Em JavaScript, não há sintaxe especial para denotar membros estáticos. Mas você pode ter a
mesma sintaxe de uma linguagem “elegante” usando uma função construtora e adicionando
propriedades a ela. Isso funciona porque os construtores, como todas as outras funções, são objetos
e podem ter propriedades. O padrão de memorização discutido no capítulo anterior empregava a
mesma ideia — adicionar propriedades a uma função.

O exemplo a seguir define um construtor Gadget com um método estático isShiny() e um método
de instância regular setPrice(). O método isShiny() é um método estático porque não precisa de um
objeto gadget específico para funcionar (assim como você não precisa de um

Membros Estáticos | 105


Machine Translated by Google

determinado gadget para descobrir que todos os gadgets são brilhantes). setPrice(), por outro
lado, precisa de um objeto, porque os gadgets podem ter preços
diferentes: //
construtor var Gadget = function () {};

// um método estático
Gadget.isShiny = function () { return
"pode apostar";
};

// um método normal adicionado ao protótipo


Gadget.prototype.setPrice = function (price) { this.price
= price;
};

Agora vamos chamar esses métodos. O estático isShiny() é invocado diretamente no construtor,
enquanto o método regular precisa de uma instância:
// chamando um método
estático Gadget.isShiny(); // "pode apostar"

// criando uma instância e chamando um método


var iphone = new Gadget();
iphone.setPrice(500);

Tentar chamar um método de instância estaticamente não funcionará; o mesmo para chamar um
método estático usando o objeto iphone da
instância: typeof Gadget.setPrice; // tipo
"indefinido" de iphone.isShiny; // "indefinido"

Às vezes, pode ser conveniente ter os métodos estáticos trabalhando com uma instância também.
Isso é fácil de conseguir simplesmente adicionando um novo método ao protótipo, que serve
como uma fachada apontando para o método estático original:
Gadget.prototype.isShiny = Gadget.isShiny;
iphone.isShiny(); // "pode apostar"

Nesses casos, você precisa ter cuidado ao usar isso dentro do método estático. Quando você
fizer Gadget.isShiny() , isso dentro de isShiny() se referirá à função do construtor Gadget . Se
você fizer iphone.isShiny() , isso apontará para o iphone.

Um último exemplo mostra como você pode ter o mesmo método sendo chamado estaticamente
e não estaticamente e se comportar ligeiramente diferente, dependendo do padrão de invocação.
Aqui, instanceof ajuda a determinar como o método foi
chamado: //
construtor var Gadget = function
(price) { this.price = price;
};

// um método estático
Gadget.isShiny = function () {

// isso sempre funciona

106 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

var msg = "pode apostar";

if (this instanceof Gadget) { // isso


só funciona se chamado não estaticamente msg
+= ", custa $" + this.price + '!';
}

msg de retorno;
};

// um método normal adicionado ao protótipo


Gadget.prototype.isShiny = function () { return
Gadget.isShiny.call(this);
};

Testando uma chamada de método estático:

Gadget.isShiny(); // "pode apostar"

Testando uma instância, chamada não


estática: var a = new
Gadget('499.99'); a.éBrilhante(); // "pode apostar, custa $ 499,99!"

Membros Estáticos Privados

A discussão até agora foi sobre métodos estáticos públicos; agora vamos dar uma olhada em como você
pode implementar membros estáticos privados . Por membros estáticos privados, queremos dizer membros
que são:

• Compartilhado por todos os objetos criados com a mesma função construtora • Não
acessível fora do construtor

Vejamos um exemplo em que counter é uma propriedade estática privada no construtor Gadget. Neste
capítulo já houve uma discussão sobre propriedades privadas, então esta parte ainda é a mesma - você
precisa de uma função para atuar como um encerramento e envolver os membros privados. Em seguida,
vamos fazer com que a mesma função wrapper seja executada imediatamente e retorne uma nova função.
O valor da função retornada é atribuído à variável Gadget e se torna o novo construtor:

var Dispositivo = (função () {

// variável estática/propriedade
var counter = 0;

// retornando a nova implementação //


do construtor return
function ()
{ console.log(counter += 1);
};

}()); // executa imediatamente

Membros Estáticos | 107


Machine Translated by Google

O novo construtor Gadget simplesmente incrementa e registra o contador privado. Testando com
várias instâncias, você pode ver que o contador é realmente compartilhado entre todas as instâncias:
var g1 = new Gadget(); // registra 1
var g2 = new Gadget(); // registra 2
var g3 = new Gadget(); // registra 3

Como estamos incrementando o contador com um para cada objeto, essa propriedade estática se
torna um ID que identifica exclusivamente cada objeto criado com o construtor Gadget .
O identificador exclusivo pode ser útil, então por que não expô-lo por meio de um método privilegiado?
Abaixo está um exemplo que se baseia no anterior e adiciona um método privilegiado getLastId() para
acessar a propriedade privada estática:
// construtor var
Gadget = (function () {

// variável estática/propriedade
var contador = 0,
Novo Gadget;

// isso se tornará a // nova


implementação do construtor
NewGadget = function ()
{ contador += 1;
};

// um método privilegiado
NewGadget.prototype.getLastId = function ()
{ return counter;
};

// substitui o construtor return


NewGadget;

}()); // executa imediatamente

Testando a nova implementação: var


iphone = new Gadget();
iphone.getLastId(); // 1 var
ipod = new Gadget();
ipod.getLastId(); // 2 var
ipad = new Gadget();
ipad.getLastId(); // 3

As propriedades estáticas (privadas e públicas) podem ser bastante úteis. Eles podem conter métodos
e dados que não são específicos da instância e não são recriados a cada instância.
No Capítulo 7, quando discutimos o padrão singleton, você pode ver um exemplo de implementação
que usa propriedades estáticas para implementar construtores singleton semelhantes a classes.

108 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Constantes do objeto
Não há constantes em JavaScript, embora muitos ambientes modernos possam oferecer a você a instrução
const para criar constantes.

Como solução alternativa, uma abordagem comum é usar uma convenção de nomenclatura e fazer com
que as variáveis que não devem ser alteradas se destaquem usando letras maiúsculas. Essa convenção é
realmente usada nos objetos JavaScript integrados:

Math.PI; // 3.141592653589793
Math.SQRT2; // 1.4142135623730951
Number.MAX_VALUE; // 1.7976931348623157e+308

Para suas próprias constantes, você pode adotar a mesma convenção de nomenclatura e adicioná-las como
propriedades estáticas à função do construtor: //
constructor var
Widget = function () { //
implementação...
};

// constantes
Widget.MAX_HEIGHT = 320;
Widget.MAX_WIDTH = 480;

A mesma convenção pode ser aplicada para objetos criados com um literal; as constantes podem ser
propriedades normais com nomes em maiúsculas.

Se você realmente deseja ter um valor imutável, pode criar uma propriedade privada e fornecer um método
getter, mas não setter. Isso provavelmente é um exagero em muitos casos, quando você pode sobreviver
com uma convenção simples, mas ainda é uma opção.

O exemplo a seguir é uma implementação de um objeto constante de propósito geral , que fornece estes
métodos:

set(nome, valor)
Para definir uma nova constante

isDefined(nome)
Para verificar se existe uma constante

obter (nome)
Para obter o valor de uma constante

Nesta implementação, apenas valores primitivos são permitidos como constantes. Além disso, alguns
cuidados extras são tomados para garantir que não haja problema em declarar constantes com nomes que
sejam nomes de propriedades internas, como toString ou hasOwnProperty usando uma verificação
hasOwnProperty() e, adicionalmente, precedendo todos os nomes de constantes com um gerado
aleatoriamente prefixo: var
constante = (função () { var
constantes = {},
ownProp = Object.prototype.hasOwnProperty,
permitido = {

Constantes de objeto | 109


Machine Translated by Google

string: 1,
número: 1,
booleano: 1
},
prefix = (Math.random() + "_").slice(2); return
{ conjunto:
função (nome, valor) {
if (this.isDefined(nome)) { return
false;

} if (!ownProp.call(permitido, tipo de valor)) { return false;

} constantes[prefixo + nome] = valor;


retornar verdadeiro;
},
isDefined: function (nome) { return
ownProp.call(constantes, prefixo + nome);
},
obtenha: função (nome) { if
(this.isDefined(nome)) { return
constants[prefix + name];

} retornar nulo;
}

}; }());

Testando a implementação:
// verifica se está
definido constant.isDefined("maxwidth"); // falso

// define
constant.set("maxwidth", 480); // verdadeiro

// verifique
novamente constant.isDefined("maxwidth"); // verdadeiro

// tenta redefinir
constant.set("maxwidth", 320); // falso

// o valor ainda está intacto?


constant.get("maxwidth"); // 480

padrão de encadeamento

O padrão de encadeamento permite chamar métodos em um objeto um após o outro,


sem atribuir os valores de retorno das operações anteriores às variáveis e sem ter
que dividir suas chamadas em várias linhas:
myobj.method1("olá").method2().method3("mundo").method4();

110 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Quando você cria métodos que não têm valor de retorno significativo, você pode fazer com que eles
retornem isso, a instância do objeto com o qual estão trabalhando. Isso permitirá que os consumidores
desse objeto chamem o próximo método encadeado ao anterior:
var obj =
{ valor: 1,
incremento: function ()
{ this.value += 1;
devolva isto;
},
adicione: function (v)
{ this.value += v;
devolva isto;
},
shout: function ()
{ alert(this.value);
}
};

// chamadas de método
em cadeia obj.increment().add(3).shout(); // 5

// ao invés de chamá-los um por um


obj.increment();
obj.add(3);
obj.shout(); // 5

Prós e contras do padrão de encadeamento Um

benefício de usar o padrão de encadeamento é que você pode economizar um pouco de digitação e
criar um código mais conciso que quase se lê como uma frase.

Outro benefício é que ele ajuda você a pensar em dividir suas funções e criar funções menores e mais
especializadas, em oposição a funções que tentam fazer muito.
Isso melhora a capacidade de manutenção a longo prazo.

Uma desvantagem é que fica mais difícil depurar o código escrito dessa maneira. Você pode saber que
ocorre um erro em uma linha específica, mas há muita coisa acontecendo nessa linha. Quando um dos
vários métodos que você encadeou falha silenciosamente, você não tem ideia de qual deles.
Robert Martin, autor do livro Clean Code, chega a chamar isso de padrão de “desastre”.

De qualquer forma, é bom reconhecer esse padrão e, quando um método que você escreve não tem
um valor de retorno óbvio e significativo, você sempre pode retornar isso. O padrão é amplamente
utilizado, por exemplo, na biblioteca jQuery. E se você observar a API DOM, poderá perceber que ela
também é propensa a encadeamento com construções como:
document.getElementsByTagName('head')[0].appendChild(newnode);

padrão de encadeamento | 111


Machine Translated by Google

método() Método
JavaScript pode ser confuso para programadores que estão acostumados a pensar em termos de
classes. É por isso que alguns desenvolvedores optam por tornar o JavaScript mais semelhante a
uma classe. Uma dessas tentativas é a ideia do método method() introduzida por Douglas
Crockford. Em retrospecto, ele admite que tornar o JavaScript semelhante a uma classe não é
uma abordagem recomendada, mas mesmo assim é um padrão interessante e você pode encontrá-
lo em alguns aplicativos.

O uso de funções construtoras se parece com o uso de classes em Java. Eles também permitem
que você adicione propriedades de instância a isso dentro do corpo do construtor. Porém,
adicionar métodos a isso é ineficiente, pois eles acabam sendo recriados a cada instância e isso
consome mais memória. É por isso que métodos reutilizáveis devem ser adicionados à propriedade
protótipo do construtor. O protótipo pode parecer estranho para muitos desenvolvedores, então
você pode escondê-lo atrás de um método.

A adição de funcionalidades convenientes a uma linguagem costuma ser chamada de açúcar


sintático ou simplesmente açúcar. Nesse caso, você pode chamar o método method() de
“método sugar”.

A forma de definir uma “classe” usando o método sugar() seria a seguinte:


var Pessoa = função (nome)
{ this.name = nome;
}.
method('getName', function ()
{ return this.name;
}).
method('setName', function (name)
{ this.name =
name; return
this; });

Observe como o construtor é encadeado à chamada para method(), que por sua vez é encadeado
para a próxima chamada de method() e assim por diante. Isso segue o padrão de encadeamento
descrito anteriormente e ajuda a definir toda a “classe” com uma única instrução.

O method() recebe dois parâmetros:


• O nome do novo método

• A implementação do método

Esse novo método é então adicionado à “classe” Person . A implementação é apenas uma outra
função e, dentro da função de implementação, aponta para o objeto criado por Person, como seria
de esperar.

112 | Capítulo 5: Padrões de Criação de Objetos


Machine Translated by Google

Veja como você pode usar Person() para criar e usar um novo objeto:

var a = new Pessoa('Adão');


a.getName(); // 'Adão'
a.setName('Eva').getName(); // 'Véspera'

Novamente, observe o padrão de encadeamento em ação, que se tornou possível simplesmente porque
setName() retornou isso.

E finalmente, veja como o método method() é implementado:

if (typeof Function.prototype.method !== "função") {


Function.prototype.method = function (nome, implementação) {
this.prototype[nome] = implementação;
devolva isto;
};
}

Na implementação de method(), primeiro fazemos a devida diligência para verificar se o método já não está
implementado. Caso contrário, continuamos adicionando a função passada como implementação de argumento
ao protótipo do construtor. Neste caso, refere- se à função construtora, cujo protótipo é aumentado.

Resumo
Neste capítulo você aprendeu sobre diferentes padrões para criar objetos que vão além dos padrões básicos
de uso de literais de objeto e funções construtoras.

Você aprendeu sobre o padrão de namespace que mantém o espaço global limpo e ajuda a organizar e
estruturar o código. Você também aprendeu sobre o padrão simples, mas surpreendentemente útil, de declarar
dependências. Em seguida, houve uma discussão detalhada sobre padrões de privacidade, incluindo membros
privados , métodos privilegiados , alguns casos extremos com privacidade, o uso de objetos literais com
membros privados e revelando métodos privados como públicos. Todos esses padrões são os blocos de
construção para o popular e poderoso padrão de módulo.

Então você aprendeu sobre o padrão sandbox como uma alternativa ao namespace longo, que também ajuda
a criar ambientes independentes para seu código e módulos.

Para encerrar a discussão, analisamos profundamente as constantes de objeto, métodos estáticos (públicos e
privados), encadeamento e um curioso método method() .

Resumo | 113
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 6

Padrões de reutilização de código

A reutilização de código é um tópico importante e interessante simplesmente porque é natural se


esforçar para escrever o mínimo e reutilizar o máximo possível do código existente, que você ou outra
pessoa já escreveu. Especialmente se for um código bom, testado, passível de manutenção, extensível
e documentado.

Quando se fala em reutilização de código, a primeira coisa que vem à mente é a herança, e grande
parte do capítulo é dedicada a esse tópico. Você vê várias maneiras de fazer herança “clássica” e não
clássica. Mas é importante ter em mente o objetivo final — queremos reutilizar o bacalhau; A herança é
uma maneira (meio) de atingirmos esse objetivo. E não é a única forma. Você vê como pode compor
objetos de outros objetos, como usar misturas de objetos e como pode emprestar e reutilizar apenas a
funcionalidade de que precisa, sem herdar tecnicamente nada permanentemente.

Ao abordar uma tarefa de reutilização de código, tenha em mente o conselho que o livro Gang of Four
tem a oferecer sobre a criação de objetos: “Prefira a composição de objetos à herança de classes”.

Padrões de herança clássicos versus modernos


Muitas vezes você ouve o termo “herança clássica” em discussões sobre o tópico de herança em
JavaScript, então vamos primeiro esclarecer o que clássico significa. O termo não é usado no sentido
de algo antigo, estabelecido ou amplamente aceito como a maneira adequada de fazer as coisas. O
termo é apenas um trocadilho com a palavra “classe”.

Muitas linguagens de programação têm a noção de classes como projetos para objetos. Nessas
linguagens todo objeto é uma instância de uma classe específica e (no caso de Java, por exemplo) um
objeto não pode ser criado se a classe para ele não existir. Em JavaScript, como não há classes, a
noção de instâncias de classes não faz muito sentido.
Objetos em JavaScript são simplesmente pares chave-valor, que você pode criar e alterar em tempo
real.

Mas o JavaScript tem funções de construtor, e a sintaxe do novo operador se parece muito com a
sintaxe do uso de classes.

115
Machine Translated by Google

Em Java você poderia fazer algo como:


Pessoa adam = new Pessoa();

Em JavaScript você faria: var


adam = new Person();

Além do fato de que Java é fortemente tipado e você precisa declarar que adam é do tipo
Person, a sintaxe parece a mesma. A invocação do construtor do JavaScript parece que Person
é uma classe, mas é importante ter em mente que Person ainda é apenas uma função.
A similaridade na sintaxe levou muitos desenvolvedores a pensar em JavaScript em termos de
classes e desenvolver ideias e padrões de herança que assumem classes. Tais implementações
podemos chamar de “clássicas”. Digamos também que “moderno” são quaisquer outros padrões
que não exijam que você pense em classes.

Quando se trata de adotar um padrão de herança para o seu projeto, você tem algumas opções.
Você deve sempre se esforçar para escolher um padrão moderno, a menos que a equipe fique
realmente desconfortável se não houver aulas envolvidas.

Este capítulo discute os padrões clássicos primeiro e depois passa para os modernos.

Resultado esperado ao usar a herança clássica


O objetivo da implementação da herança clássica é fazer com que os objetos criados por uma
função construtora Child() obtenham propriedades que vêm de outro construtor Parent().

Embora a discussão seja sobre padrões clássicos, vamos evitar usar a palavra “classe”.
Dizer “função do construtor” ou “construtor” é mais longo, mas é preciso e não ambíguo.
Em geral, esforce-se para eliminar a palavra “classe” ao se comunicar dentro de sua
equipe, porque quando se trata de JavaScript, a palavra pode significar coisas diferentes
para pessoas diferentes.

Aqui está um exemplo de definição dos dois construtores Parent() e Child():


// a função construtora pai
Parent(name) { this.name
= name || 'Adão';
}

// adicionando funcionalidade ao protótipo


Parent.prototype.say = function () { return
this.name;
};

// função construtor filho vazia


Child(name) {}

116 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

// a mágica da herança acontece


aqui Heritage(Child, Parent);

Aqui você tem os construtores pai e filho, um método say() adicionado ao protótipo do construtor
pai e uma chamada para uma função chamada inherit() que cuida da herança. A função Heritage()
não é fornecida pela linguagem, então você deve implementá-la você mesmo. Vejamos várias
abordagens para implementá-lo de maneira genérica.

Padrão clássico nº 1 - o padrão padrão


O método padrão mais comumente usado é criar um objeto usando o construtor Parent() e
atribuir esse objeto ao protótipo de Child() . Aqui está a primeira implementação da função
reutilizável herdada() :
função herdar (C, P) {
C.prototype = new P();
}

É importante lembrar que a propriedade protótipo deve apontar para um objeto, não para uma
função, portanto, deve apontar para uma instância (um objeto) criada com o construtor pai, não
para o próprio construtor. Ou seja, fique atento ao novo operador, pois você precisa dele para
que esse padrão funcione.

Posteriormente em seu aplicativo, quando você usar new Child() para criar um objeto, ele obterá
a funcionalidade da instância Parent() por meio do protótipo, conforme mostrado no exemplo a
seguir:
var kid = new Child();
garoto.dizer(); // "Adão"

Seguindo a Cadeia de Protótipos


Usando este padrão, você herda ambas as propriedades próprias (propriedades específicas da
instância adicionadas a isso, como nome) e propriedades e métodos de protótipo (como say()).

Vamos revisar como a cadeia de protótipos funciona nesse padrão de herança. Para os fins
desta discussão, vamos pensar nos objetos como blocos em algum lugar da memória, que
podem conter dados e referências a outros blocos. Ao criar um objeto usando new Parent(), você
cria um desses blocos (marcado como bloco nº 2 na Figura 6-1). Ele contém os dados para a
propriedade name . Porém , se você tentar acessar o método say() (por exemplo, usando (new
Parent).say()), o bloco 2 não contém esse método. Mas usando o link oculto __proto__ que
aponta para a propriedade protótipo da função construtora Parent(), você obtém acesso ao objeto
nº 1 (Parent.prototype), que conhece o método say() . Tudo isso acontece nos bastidores e você
não precisa se preocupar com isso, mas é importante saber como funciona e onde estão os
dados que você está acessando ou talvez modificando. Observe que __proto__ é usado aqui
para explicar a cadeia de protótipos; esta propriedade não está disponível no próprio idioma,
embora seja fornecida em alguns ambientes (por exemplo, Firefox).

Padrão Clássico Nº 1 — O Padrão Padrão | 117


Machine Translated by Google

Figura 6-1. Cadeia de protótipo para o construtor Parent()

Agora vamos ver o que acontece quando um novo objeto é criado usando var kid = new
Child() depois de usar a função inherit() . O diagrama é mostrado na Figura 6-2.

Figura 6-2. Cadeia de protótipo após herança

O construtor Child() estava vazio e nenhuma propriedade foi adicionada a Child.prototype;


portanto, usar new Child() cria objetos que estão praticamente vazios, exceto pelo link
oculto __proto__. Nesse caso, __proto__ aponta para o novo objeto Parent() criado na
função inherit() .
Agora, o que acontece quando você faz kid.say()? O objeto nº 3 não possui esse método,
portanto, ele procura o nº 2 por meio da cadeia de protótipos. O objeto nº 2 também não
o possui, então ele segue a cadeia até o nº 1, que por acaso o possui. Então, dentro de
say() há uma referência a this.name, que precisa ser resolvida. A pesquisa começa
novamente. Neste caso, aponta para o objeto #3, que não tem nome. O objeto #2 é
consultado e possui uma propriedade name , com o valor “Adam”.

118 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

Finalmente, vamos dar uma olhada em mais uma etapa. Digamos que temos este código:

var filho = new Filho();


kid.name = "Patrick";
garoto.dizer(); // "Patrick"

A Figura 6-3 mostra como a corrente ficará neste caso.

Figura 6-3. Cadeia de protótipos após herança e adição de uma propriedade ao objeto filho

Definir kid.name não altera a propriedade name do objeto #2, mas cria uma propriedade própria diretamente
no objeto kid #3. Quando você faz kid.say(), o método say é pesquisado no objeto nº 3, depois no nº 2 e
finalmente encontrado no nº 1, exatamente como antes. Mas desta vez procurar this.name (que é o mesmo
que kid.name) é rápido, porque a propriedade é encontrada imediatamente no objeto #3.

Se você remover a nova propriedade usando delete kid.name, então a propriedade name do objeto #2 irá
“brilhar” e será encontrada em pesquisas consecutivas.

Desvantagens ao usar o padrão nº 1 Uma

desvantagem desse padrão é que você herda as propriedades próprias adicionadas a este e as propriedades
do protótipo. Na maioria das vezes, você não deseja as próprias propriedades, porque elas provavelmente
são específicas de uma instância e não podem ser reutilizadas.

Uma regra geral com construtores é que os membros reutilizáveis devem ser
adicionados ao protótipo.

Padrão Clássico Nº 1 — O Padrão Padrão | 119


Machine Translated by Google

Outra coisa sobre o uso de uma função herdada() genérica é que ela não permite que você passe
parâmetros para o construtor filho, que o filho passa para o pai.
Considere este exemplo:
var s = new Filho('Seth');
s.dizer(); // "Adão"

Isso não é o que você esperaria. É possível que o filho passe parâmetros para o construtor do pai, mas
aí você tem que fazer a herança toda vez que precisar de um novo filho, o que é ineficiente, porque
você acaba recriando objetos pai repetidamente
sobre.

Padrão Clássico # 2 - Rent-a-Constructor


Este próximo padrão resolve o problema de passar argumentos do filho para o pai.
Ele pega emprestado o construtor pai, passando o objeto filho para ser vinculado a ele e também
encaminhando quaisquer argumentos:
função Filho(a, c, b, d) {
Parent.apply(this, argumentos);
}

Dessa forma, você só pode herdar as propriedades adicionadas a this dentro do construtor pai.
Você não herda membros que foram adicionados ao protótipo.

Usando o padrão de construtor emprestado, os objetos filhos obtêm cópias dos membros herdados, ao
contrário do padrão clássico nº 1, onde eles obtêm apenas referências. O exemplo a seguir ilustra a
diferença:
// uma função construtora
pai Article() { this.tags
= ['js', 'css'];

} var artigo = new Artigo();

// uma postagem de blog herda de um objeto de


artigo // por meio da função de
padrão clássico nº 1 BlogPost() {}
BlogPost.prototype = artigo; var
blog = new BlogPost(); // note
que acima você não precisou de `new Article()` // porque
você já tinha uma instância disponível

// uma página estática herda do artigo // por


meio da função padrão do construtor
alugado StaticPage()
{ Article.call(this);

} var página = new StaticPage();

alert(article.hasOwnProperty('tags')); // true
alert(blog.hasOwnProperty('tags')); // false
alert(page.hasOwnProperty('tags')); // verdadeiro

120 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

Neste snippet, o pai Article() é herdado de duas maneiras. O padrão padrão faz com que o
objeto blog obtenha acesso à propriedade tags por meio do protótipo, portanto, não a possui
como uma propriedade própria e hasOwnProperty() retorna false. O objeto de página tem uma
propriedade de tags própria porque, usando o construtor alugado, o novo objeto obtém uma
cópia (não uma referência) do membro de tags do pai.

Observe a diferença ao modificar a propriedade de tags herdadas:


blog.tags.push('html');
page.tags.push('php');
alert(article.tags.join(', ')); // "js, css, HTML"

Neste exemplo, o objeto blog filho modifica a propriedade tags e, dessa forma, também
modifica o pai porque, essencialmente, blog.tags e article.tags apontam para o mesmo array.
Alterações em page.tags não afetam o artigo pai porque page.tags é uma cópia separada
criada durante a herança.

A cadeia de protótipos
Vamos dar uma olhada em como a cadeia de protótipos se parece ao usar este padrão e os
familiares construtores Parent() e Child() . Child() será ligeiramente modificado para seguir o
novo padrão: // a
função construtora pai
Parent(name) { this.name
= name || 'Adão';
}

// adicionando funcionalidade ao protótipo


Parent.prototype.say = function ()
{ return this.name;
};

// função do construtor
filho Child(name)
{ Parent.apply(this, arguments);
}

var filho = new Filho("Patrick");


criança.nome; //
"Patrick" typeof kid.say; // "indefinido"

Se você der uma olhada na Figura 6-4, perceberá que não há mais um vínculo entre o novo
objeto Filho e o Pai. Isso ocorre porque Child.prototype não foi usado e simplesmente aponta
para um objeto em branco. Usando esse padrão, kid obteve seu próprio nome de propriedade,
mas o método say() nunca foi herdado e uma tentativa de chamá-lo resultará em um erro. A
herança era uma ação única que copiava as próprias propriedades do pai como propriedades
do filho e isso era tudo; nenhum link __proto__ foi mantido.

Padrão Clássico #2 — Rent-a-Constructor | 121


Machine Translated by Google

Figura 6-4. A cadeia quebrada ao usar o padrão construtor de empréstimo

Herança múltipla por empréstimo de construtores


Usando os padrões de empréstimo de construtores, é possível implementar herança
múltipla simplesmente tomando emprestado de mais de um
construtor:
function Cat()
{ this.legs = 4; this.say
= function () { return "meaowww";
}
}

função Bird()
{ this.wings =
2; this.fly = verdadeiro;
}

function CatWings()
{ Cat.apply(this);
Bird.apply(this);
}

var jane = new CatWings();


console.dir(jane);

O resultado é mostrado na Figura 6-5. Quaisquer propriedades duplicadas serão resolvidas com
a vitória da última.

122 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

Figura 6-5. Um objeto CatWings inspecionado no Firebug

Prós e Contras do Padrão Construtor de Empréstimo


A desvantagem deste padrão é obviamente que nada do protótipo é herdado e,
como mencionado antes, o protótipo é o local para adicionar métodos e
propriedades reutilizáveis, que não serão recriados para cada instância .
Um benefício é que você obtém cópias verdadeiras dos próprios membros dos pais e não há risco de
que uma criança substitua acidentalmente a propriedade de um dos pais.

Então, como os filhos podem herdar as propriedades do protótipo também, no caso anterior, e como o
garoto pode obter acesso ao método say() ? O próximo padrão aborda essa questão.

Padrão Clássico #3 - Alugar e Definir Protótipo


Combinando os dois padrões anteriores, você primeiro pega emprestado o construtor e então também
define o protótipo do filho para apontar para uma nova instância do construtor:
function Child(a, c, b, d) {
Parent.apply(this, argumentos);
}
Child.prototype = new Parent();

O benefício é que os objetos resultantes obtêm cópias dos próprios membros do pai e referências à
funcionalidade reutilizável do pai (implementada como membros do tipo protótipo). O filho também
pode passar quaisquer argumentos para o construtor pai. Esse comportamento é provavelmente o mais
próximo do que você esperaria em Java; você herda tudo o que existe no pai e, ao mesmo tempo, é
seguro modificar as próprias propriedades sem o risco de modificar o pai.

Uma desvantagem é que o construtor pai é chamado duas vezes, portanto, pode ser ineficiente. No
final, as próprias propriedades (como nome no nosso caso) são herdadas duas vezes.

Vamos dar uma olhada no código e fazer alguns testes:

// a função construtora pai


Parent(name) { this.name
= name || 'Adão';
}

// adicionando funcionalidade ao protótipo


Parent.prototype.say = function ()
{ return this.name;

Padrão Clássico Nº 3 — Protótipo de Aluguel e Conjunto | 123


Machine Translated by Google

};

// função do
construtor filho
Child(name) { Parent.apply(this, arguments);
}
Child.prototype = new Parent();

var filho = new Filho("Patrick");


criança.nome; //
"Patrick" kid.say(); //
"Patrick" delete
kid.name; garoto.dizer(); // "Adão"

Ao contrário do padrão anterior, agora say() é herdado corretamente. Você também pode notar
que o nome é herdado duas vezes e, depois de excluirmos a própria cópia, a que vier da cadeia
de protótipos brilhará.

A Figura 6-6 mostra como funcionam os relacionamentos entre os objetos. As relações são
semelhantes àquelas mostradas na Figura 6-3, mas a maneira como chegamos lá foi diferente.

Figura 6-6. A cadeia de protótipos é mantida além dos próprios membros herdados

Padrão Clássico Nº 4 - Compartilhe o Protótipo


Ao contrário do padrão de herança clássico anterior, que exigia duas chamadas para o construtor
pai, o próximo padrão não envolve a chamada do construtor pai.

A regra geral era que os membros reutilizáveis deveriam ir para o protótipo e não para este.
Portanto, para fins de herança, qualquer coisa que valha a pena herdar deve estar no

124 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

protótipo. Assim, você pode apenas definir o protótipo do filho para ser o mesmo que o
protótipo
do pai: function inherit(C,
P) { C.prototype = P.prototype;
}

Isso fornece pesquisas de cadeia de protótipo curtas e rápidas porque todos os objetos
realmente compartilham o mesmo protótipo. Mas isso também é uma desvantagem porque
se um filho ou neto em algum ponto abaixo da cadeia de herança modificar o protótipo, isso
afetará todos os pais e avós.
Como mostra a Figura 6.7 , os objetos pai e filho compartilham o mesmo protótipo e obtêm
acesso igual ao método say() . No entanto, os objetos filhos não herdam a propriedade name .

Figura 6-7. Relacionamentos ao compartilhar o mesmo protótipo

Padrão Clássico #5 - Um Construtor Temporário


O próximo padrão resolve o problema do mesmo protótipo quebrando o vínculo direto entre
o protótipo do pai e o do filho enquanto, ao mesmo tempo, se beneficia da cadeia de protótipos.

Abaixo está uma implementação deste padrão, onde você tem uma função vazia F(), que
serve como um proxy entre o filho e o pai. A propriedade protótipo de F() aponta para o
protótipo do pai. O protótipo do filho é uma instância da função em branco:

função herdar (C, P) {


var F = função () {};
F.protótipo = P.protótipo;
C.prototype = new F();
}

Esse padrão tem um comportamento ligeiramente diferente do padrão padrão (padrão


clássico nº 1) porque aqui o filho herda apenas as propriedades do protótipo (consulte a Figura 6-8).

Padrão Clássico #5—Um Construtor Temporário | 125


Machine Translated by Google

Figura 6-8. Herança clássica usando um construtor temporário (proxy) F()

E isso geralmente é bom, na verdade preferível, porque o protótipo é o lugar para a funcionalidade
reutilizável. Nesse padrão, quaisquer membros que o construtor pai adiciona a isso não são herdados.

Vamos criar um novo objeto filho e inspecionar seu comportamento:


var filho = new Filho();

Se você acessar kid.name ele ficará indefinido. Neste caso, name é uma propriedade própria do pai e,
enquanto herdamos, nunca chamamos new Parent(), portanto, essa propriedade nunca foi criada.
Quando você acessar kid.say(), ele não estará disponível no objeto #3, então a cadeia de protótipos é
procurada. O objeto #4 também não tem este método, mas o objeto #1 tem e este é o mesmo local na
memória, que será reutilizado por todos os diferentes construtores que herdam Parent() e todos os
objetos criados por todos os filhos.

Armazenando o Superclass

Building em cima do padrão anterior, você pode adicionar uma referência ao pai original.
É como ter acesso à superclasse em outros idiomas e pode ser útil ocasionalmente.

A propriedade é chamada de uber porque “super” é uma palavra reservada e “superclasse” pode levar o
desenvolvedor desavisado a pensar que JavaScript tem classes.
Aqui está uma implementação aprimorada desse padrão clássico:

126 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

função herdar (C, P) {


var F = função () {};
F.protótipo = P.protótipo;
C.prototype = new F();
C.uber = P.protótipo;
}

Redefinindo o ponteiro do construtor


Uma última coisa a ser adicionada a essa função de herança clássica quase perfeita é
redefinir o ponteiro para a função do construtor, caso você precise dela no futuro.

Se você não redefinir o ponteiro para o construtor, todos os objetos filhos relatarão que Parent() foi
seu construtor, o que não é útil. Portanto, usando a implementação anterior de inherit(), você pode
observar este comportamento:
// pai, filho, função de herança
Parent() {} function
Child() {} herda(Filho,
Pai);

// testando as águas
var kid = new Child();
garoto.construtor.nome; // "Pai"
kid.constructor === Pai; // verdadeiro

A propriedade constructor raramente é usada, mas pode ser conveniente para introspecção de
objetos em tempo de execução. Você pode redefini-lo para apontar para a função de construtor
esperada sem afetar a funcionalidade porque essa propriedade é principalmente informativa.

A versão final do Santo Graal desse padrão clássico de herança será assim:
função herdar (C, P) {
var F = função () {};
F.protótipo = P.protótipo;
C.prototype = new F();
C.uber = P.protótipo;
C.prototype.constructor = C;
}

Uma função semelhante a esta existe na biblioteca YUI (e provavelmente em outras bibliotecas) e
traz a herança clássica para uma linguagem sem classes, se você decidir que esta é a melhor
abordagem para o seu projeto.

Esse padrão também é conhecido como aquele que usa uma função de proxy ou um
construtor de proxy, em vez de um construtor temporário, porque o construtor
temporário é usado como um proxy para obter o protótipo do pai.

Uma otimização comum do padrão Holy Grail é evitar a criação do construtor temporário (proxy)
toda vez que você precisar de herança. É suficiente criá-lo uma vez e

Padrão Clássico #5—Um Construtor Temporário | 127


Machine Translated by Google

apenas mudar seu protótipo. Você pode usar uma função imediata e armazenar a função
proxy em seu encerramento:

var herdar = (função () { var


F = função () {}; retornar
função (C, P) { F.prototype
= P.prototype; C.prototype
= new F(); C.uber =
P.prototype;
C.prototype.constructor = C;

} }());

classe
Muitas bibliotecas JavaScript emulam classes, introduzindo uma nova sintaxe sugar. As
implementações diferem, mas muitas vezes há alguns pontos em comum, incluindo o seguinte:

• Existe uma convenção sobre como nomear um método, que deve ser considerado o
construtor da classe, por exemplo initialize, _init ou algo semelhante e que é chamado
automaticamente. • As classes
herdam de outras classes.

• Há acesso à classe pai (superclasse) de dentro da classe filha.

Vamos mudar de assunto aqui e, apenas nesta parte do capítulo, usar a palavra “classe” livremente porque o

tópico é emular classes.

Sem entrar em muitos detalhes, vamos ver um exemplo de implementação de classes


simuladas em JavaScript. Primeiro, como a solução será usada do ponto de vista do cliente?
var Man = klass(null,
{ __construct: function (what)
{ console.log("Construtor do
Man"); this.name = what;
},
getName: function ()
{ return this.name;

} });

A sintaxe sugar vem na forma de uma função chamada klass(). Em algumas implementações,
você pode vê-lo como construtor Klass() ou como Object.prototype aumentado, mas neste
exemplo, vamos mantê-lo como uma função simples.

A função recebe dois parâmetros: uma classe pai a ser herdada e a implementação da nova
classe fornecida por um objeto literal. Influenciado pelo PHP, vamos estabelecer a convenção
de que o construtor da classe deve ser um método chamado __construct. No

128 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

trecho anterior, uma nova classe chamada Man é criada e não herda de nada (o que significa herdar
de Object nos bastidores). A classe Man possui um nome de propriedade próprio criado dentro de
__construct e um método getName(). A classe é uma função construtora, então o seguinte ainda
funcionará (e se parecerá com uma instanciação de classe):

var primeiro = new Homem('Adão'); // registra o "construtor do


homem" first.getName(); // "Adão"

Agora vamos estender esta classe e criar uma classe SuperMan :

var SuperMan = klass(Man,


{ __construct: function (what)
{ console.log("Construtor do SuperMan");
},
getName: function () { var
name = SuperMan.uber.getName.call(this);
" return
"Eu sou + nome;
}
});

Aqui, o primeiro parâmetro para klass() é a classe Man a ser herdada. Observe também em getName()
que a função getName() da classe pai é chamada primeiro usando a propriedade estática uber (super)
de SuperMan. Vamos testar: var clark = new
SuperMan('Clark Kent'); clark.getName(); //
"Eu sou Clark Kent"

A primeira linha registra no console "Man's constructor" e depois "Superman's constructor". Em


algumas linguagens, o construtor do pai é chamado automaticamente toda vez que o construtor do
filho é chamado, então por que não emular isso também?

Testar esse operador instanceof retorna os resultados esperados:


exemplo claro do homem; // true
clark instanceof Superman; // verdadeiro

Finalmente, vamos ver como a função klass() pode ser implementada: var
klass = function (Parent, props) {

var Criança, F, i;

// 1. //
novo construtor Child
= function () { if
(Child.uber && Child.uber.hasOwnProperty("__construct"))
{ Child.uber.__construct.apply(this, arguments);

} if (Child.prototype.hasOwnProperty("__construct")) {
Child.prototype.__construct.apply(this, argumentos);
}
};

// 2. //
herdar
Parent = Parent || Objeto;

Classe | 129
Machine Translated by Google

F = função () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.uber = Parent.prototype;
Child.prototype.constructor = Filho;

//
3. // adicionar métodos de
implementação para
(i em props) { if (props.hasOwnProperty(i)) {
Child.prototype[i] = props[i];
}
}

// retorna a "classe"
return Child;
};

A implementação klass() tem três partes interessantes e distintas:

1. Uma função construtora Child() é criada. Esta é a função que será retornada no final e
será utilizada como uma classe. Nesta função, o método __construct é chamado se
existir. Também antes disso, o __construct do pai é chamado (novamente, se existir)
usando a propriedade uber estática. Pode haver casos em que uber não está definido -
quando você herda de Object , por exemplo, como foi o caso da definição da classe
Man .
2. A segunda parte cuida do bit de herança. É simplesmente usar o padrão do Santo Graal
da herança clássica discutido na seção anterior do capítulo.
Há apenas uma novidade: definir o Parent como Object se nenhum Parent foi passado
para herdar.
3. A seção final percorre todos os métodos de implementação (como __construct e
getName nos exemplos), que são a definição real da classe e adicioná-los ao protótipo
de Child.

Quando usar esse padrão? Bem, na verdade é melhor você evitar, porque traz toda a noção
confusa de classes, que tecnicamente não existem na linguagem. Ele adiciona nova sintaxe
e novas regras para aprender e lembrar. Dito isso, se você ou a equipe se sentirem à
vontade com as aulas e, ao mesmo tempo, se sentirem desconfortáveis com os protótipos,
isso pode ser algo a ser explorado. Esse padrão permite que você esqueça completamente
os tipos proto, e o bom é que você pode ajustar a sintaxe e as convenções para se parecer
com outra de suas linguagens favoritas.

Herança Prototípica
Vamos começar a discussão dos padrões sem classes “modernos” com um padrão chamado
herança prototípica. Nesse padrão não há classes envolvidas; aqui os objetos herdam de
outros objetos. Você pode pensar desta forma: você tem um objeto que gostaria de reutilizar
e deseja criar um segundo objeto que obtenha sua funcionalidade do primeiro.

130 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

Veja como você faria isso: // objeto a ser


herdado de var parent =
{ name: "Papa"

};

// o novo objeto
var child = object(parent);

// testando
alert(child.name); // "Papai"

No snippet anterior, você tem um objeto existente chamado pai criado com o objeto literal e
deseja criar outro objeto chamado filho que tenha as mesmas propriedades e métodos do pai.
O objeto filho foi criado com uma função chamada object(). Esta função não existe em
JavaScript (não confundir com a função construtora Object()), então vamos ver como você
pode defini-la.

Da mesma forma que o Santo Graal clássico, você usaria uma função construtora temporária
vazia F(). Em seguida, você define o protótipo de F() para ser o objeto pai. Finalmente, você
retorna uma nova instância do construtor temporário:
função objeto(o)
{ função F() {}
F.protótipo = o;
retornar novo F();
}

A Figura 6-9 mostra a cadeia de protótipos ao usar o padrão de herança de protótipos.


Aqui, o filho sempre começa como um objeto vazio, que não possui propriedades próprias,
mas ao mesmo tempo possui todas as funcionalidades de seu pai, beneficiando-se do __proto__
link.

Figura 6-9. Padrão de herança prototípico

Herança Prototípica | 131


Machine Translated by Google

Discussão
No padrão de herança prototípico, seu pai não precisa ser criado com a notação literal (embora
essa seja provavelmente a maneira mais comum). Você pode fazer com que as funções do
construtor criem o pai. Observe que, se você fizer isso, as propriedades "próprias" e as
propriedades do protótipo do construtor serão herdadas: //
função do construtor
pai Person() { //
uma propriedade
"própria" this.name = "Adam";

} // uma propriedade adicionada ao


protótipo Person.prototype.getName =
function () { return this.name;
};

// cria uma nova pessoa


var papa = new Person(); //
herdar var
kid = object(papa);

// testa se tanto a própria propriedade //


quanto a propriedade do protótipo foram herdadas
kid.getName(); // "Adão"

Em outra variação desse padrão, você tem a opção de herdar apenas o objeto protótipo de
um construtor existente. Lembre-se de que os objetos herdam de objetos, independentemente
de como os objetos pais foram criados. Aqui está uma ilustração usando o exemplo anterior,
ligeiramente modificado: //
função do construtor
pai Person() { //
uma propriedade
"própria" this.name = "Adam";

} // uma propriedade adicionada ao


protótipo Person.prototype.getName =
function () { return this.name;
};

// herdar
var kid = object(Person.prototype);

typeof kid.getName; // "função", porque estava no protótipo typeof


kid.name; // "indefinido", porque apenas o protótipo foi herdado

Adição ao ECMAScript 5 No
ECMAScript 5, o padrão de herança prototípico torna-se oficialmente parte da linguagem. Este
padrão é implementado através do método Object.create(). em outro

132 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

palavras, você não precisará rolar sua própria função semelhante a object(); ele será construído
na linguagem: var
child = Object.create(parent);

Object.create() aceita um parâmetro adicional, um objeto. As propriedades do objeto extra


serão adicionadas como propriedades próprias do novo objeto filho que está sendo retornado.
Esta é uma conveniência que permite que você herde e construa sobre o objeto filho com uma
chamada de método. Por
exemplo: var filho = Object.create(parent, {
idade: { valor: 2 } // descritor ECMA5 });

criança.hasOwnProperty("idade"); // verdadeiro

Você também pode descobrir que o padrão de herança prototípico é implementado em


bibliotecas JavaScript; por exemplo, em YUI3 é o método Y.Object() :
YUI().use('*', function (Y) {
var filho = Y.Object(pai); });

Herança por cópia de propriedades Vamos dar


uma olhada em outro padrão de herança - herança por cópia de propriedades. Nesse padrão,
um objeto obtém funcionalidade de outro objeto, simplesmente copiando-o.
Aqui está um exemplo de implementação de uma função de exemplo extend() que faz
isso: function extend(parent,
child)
{ var i; criança =
criança || {}; for (i in
parent) { if
(parent.hasOwnProperty(i)) { child[i] = parent[i];
}

} return filho;
}

É uma implementação simples, apenas percorrendo os membros do pai e copiando-os.


Nesta implementação, o filho é opcional; se você não passar um objeto existente para ser
aumentado, um novo objeto será criado e retornado: var dad = {name:
"Adam"}; var filho =
extend(pai);
criança.nome; // "Adão"

A implementação fornecida é a chamada “cópia rasa” do objeto. Uma cópia profunda, por outro
lado, significa verificar se a propriedade que você está prestes a copiar é um objeto ou uma
matriz e, em caso afirmativo, iterar recursivamente por meio de suas propriedades e copiá-las
também. Com a cópia rasa (porque os objetos são passados por referência em JavaScript), se
você alterar uma propriedade do filho e essa propriedade for um objeto, então

Herança por Copiar Propriedades | 133


Machine Translated by Google

você também modificará o pai. Na verdade, isso é preferível para métodos (já que as funções também
são objetos e são passadas por referência), mas pode levar a surpresas ao trabalhar com outros
objetos e arrays. Considere isto:
var pai =
{ conta: [1, 2, 3], lê:
{papel: verdadeiro}
};
var filho = extend(pai);
kid.counts.push(4);
pai.contagens.toString(); // "1,2,3,4"
dad.reads === kid.reads; // verdadeiro

Agora vamos modificar a função extend() para fazer cópias profundas. Tudo o que você precisa é
verificar se o tipo de uma propriedade é um objeto e, em caso afirmativo, copiar recursivamente suas
propriedades. Outra verificação necessária é se o objeto é um objeto verdadeiro ou se é um array.
Vamos usar a verificação de matriz discutida no Capítulo 3. Portanto, a versão de cópia profunda de
extend() ficaria assim: function

extendDeep(parent, child) { var i, toStr =


Object.prototype.toString, astr = "[object Variedade]";

criança = criança || {};

for (i in parent) { if
(parent.hasOwnProperty(i)) { if
(typeof parent[i] === "object") { child[i]
= (toStr.call(parent[i]) === astro) ? [] : {}; extendDeep(pai[i],
filho[i]); } else { filho[i] = pai[i];

}
}

} return filho;
}

Agora, testar a nova implementação nos dá cópias verdadeiras dos objetos, para que os objetos filhos
não modifiquem seus pais: var
dad =
{ counts: [1, 2, 3],
reads: {paper: true}
};
var filho = extendDeep(pai);

kid.counts.push(4);
kid.counts.toString(); // "1,2,3,4"
dad.counts.toString(); // "1,2,3"

pai.lês === filho.lês; // false


kid.reads.paper = false;

134 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

kid.reads.web =
verdadeiro; pai.lê.papel; // verdadeiro

Este padrão de cópia de propriedade é simples e amplamente utilizado; por exemplo, o Firebug (as
extensões do Firefox são escritas em JavaScript) tem um método chamado extend() que faz cópias
rasas e o extend() do jQuery cria uma cópia profunda. YUI3 oferece um método chamado Y.clone(),
que cria uma cópia profunda e também copia as funções vinculando-as ao objeto filho. (Haverá mais
informações sobre encadernação mais adiante neste capítulo.)

Vale a pena notar que não há nenhum protótipo envolvido nesse padrão; é apenas sobre objetos e
suas próprias propriedades.

Misturas
Levando a ideia de herança por cópia de propriedade um passo adiante, vamos considerar um
padrão de “mistura”. Em vez de copiar de um objeto, você pode copiar de qualquer número de
objetos e misturá-los em um novo objeto.

A implementação é simples; apenas percorra os argumentos e copie cada propriedade de cada objeto
passado para a função: function mix() { var arg,
prop, child = {}; for
(arg = 0; arg <
argumentos.comprimento; arg += 1) { for (prop em
argumentos[arg]) { if
(argumentos[arg].hasOwnProperty(prop))
{ filho[prop] = argumentos[arg] [suporte];
}
}

} return filho;
}

Agora que você tem uma função de mixagem genérica, pode passar qualquer número de objetos
para ela e o resultado será um novo objeto que possui as propriedades de todos os objetos de origem.
Aqui está um exemplo de uso:
var cake =
mix( {ovos: 2, grandes:
verdadeiro}, {manteiga: 1,
salgado: verdadeiro},
{farinha: "3 xícaras"},
{açúcar: "claro!"} );

A Figura 6-10 mostra o resultado da exibição das propriedades dos novos objetos de bolo misturados
executando console.dir(cake) no console do Firebug.

Misturas | 135
Machine Translated by Google

Figura 6-10. Examinando o objeto bolo no Firebug

Se você está acostumado com o conceito de mistura de idiomas nos quais é uma
parte oficial do idioma, pode esperar que a mudança de um ou mais dos pais afete
a criança, mas isso não é verdade na implementação fornecida. Aqui simplesmente
fazemos um loop, copiamos as próprias propriedades e quebramos o vínculo com
o(s) pai(s).

Métodos de Empréstimo
Às vezes pode acontecer que você goste apenas de um ou dois métodos de um objeto existente.
Você deseja reutilizá-los, mas não deseja realmente formar um relacionamento pai-filho com
esse objeto. Você deseja usar apenas os métodos de que gosta, sem herdar todos os outros
métodos de que nunca precisará. Isso é possível com o padrão de métodos de empréstimo, que
se beneficia dos métodos de função call() e apply(). Você já viu esse padrão no livro e até mesmo
neste capítulo na implementação de extendDeep(), por exemplo.

Como você sabe, as funções em JavaScript são objetos e vêm com alguns métodos interessantes
próprios, como call() e apply(). A única diferença entre os dois é que um pega um array de
parâmetros a serem passados para o método que está sendo chamado, e o outro pega os
parâmetros um a um. Você pode usar estes métodos para emprestar funcionalidade de objetos
existentes: // call() exemplo

notmyobj.doStuff.call(myobj, param1, p2, p3); //


exemplo apply()
notmyobj.doStuff.apply(myobj, [param1, p2, p3]);

Aqui você tem um objeto chamado myobj e sabe que algum outro objeto chamado notmyobj tem
esse método útil chamado doStuff(). Em vez de passar pelo incômodo da herança e herdar
vários métodos que seu myobj nunca precisará, você pode simplesmente pegar emprestado o
método doStuff() temporariamente.

Você passa seu objeto e quaisquer parâmetros, e o método emprestado vincula seu objeto como
seu próprio this. Basicamente, seu objeto finge ser o outro objeto um pouco para se beneficiar
do método que você gosta. É como obter uma herança, mas sem pagar o imposto de herança
(onde o “imposto” vem na forma de propriedades e métodos extras que você não precisa).

136 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

Exemplo: Empréstimo de Array Um uso

comum para este padrão é pegar emprestado métodos de array.

Arrays têm métodos úteis, que objetos semelhantes a arrays, como argumentos, não possuem.
Portanto, os argumentos podem usar métodos de array, como o método slice(). Aqui está um exemplo:
function f() { var
args = [].slice.call(argumentos, 1, 3); argumentos
de retorno;
}

// exemplo
f(1, 2, 3, 4, 5, 6); // retorna [2,3]

Neste exemplo, há uma matriz vazia criada apenas para usar seu método. Uma maneira um pouco
mais longa de fazer o mesmo é pegar emprestado o método diretamente do protótipo de Array ,
usando Array.prototype.slice.call(...). Dessa forma, é um pouco mais longo para digitar, mas você
economizará o trabalho de criar um array vazio.

Pegar emprestado e vincular

Ao pegar métodos emprestados por meio de call()/apply() ou por meio de atribuição simples, o
objeto para o qual isso aponta dentro do método emprestado é determinado com base na expressão
de chamada. Mas às vezes é melhor ter o valor disso “ bloqueado” ou vinculado a um objeto
específico e predeterminado com antecedência.

Vejamos um exemplo. Existe um objeto chamado one que possui um método say() :
var um =
{ nome: "objeto",
diga: função (saúde)
{ retorne saudação + ", " + este.nome;
}
};

// teste
um.say('oi'); // "oi, objeto"

Agora outro objeto dois não tem um método say() , mas pode pegá-lo emprestado de um:
var dois = {
nome: "outro objeto"
};

one.say.apply(dois, ['olá']); // "olá, outro objeto"

No caso anterior, this inside say() apontava para dois e this.name era, portanto, “outro objeto”. Mas
e os cenários em que você atribui o ponteiro de função a uma variável global ou passa a função
como um retorno de chamada? Na programação do lado do cliente, há muitos eventos e retornos
de chamada, então isso acontece muito: // atribuindo a uma variável //
`this` apontará para o objeto
global

Métodos de empréstimo | 137


Machine Translated by Google

var dizer = um.dizer;


diga('hoho'); // "hoho, indefinido"

// passando como callback var


yetanother = { name:
"Yet another object", method:
function (callback) { return
callback('Hola');
}
};
ainda outro.método(um.dizer); // "Holla, indefinido"

Em ambos os casos, this inside say () estava apontando para o objeto global e todo o trecho não
funcionou como esperado. Para fixar (em outras palavras, vincular) um objeto a um método,
podemos usar uma função simples como
esta: function bind(o,
m) { return function
() { return m.apply(o, [].slice.call (argumentos));
};
}

Essa função bind() aceita um objeto o e um método m, vincula os dois e retorna outra função. A
função retornada tem acesso a o e m por meio de um encerramento.
Portanto, mesmo após o retorno de bind() , a função interna terá acesso a o e m, que sempre
apontarão para o objeto e método originais. Vamos criar uma nova função usando bind(): var
twosay
= bind(two, one.say); twosay('yo'); //
"yo, outro objeto"

Como você pode ver, embora twosay() tenha sido criado como uma função global, isso não apontava
para o objeto global, mas apontava para o objeto dois, que foi passado para bind(). Independentemente
de como você chama twosay(), isso sempre será vinculado a dois.

O preço que você paga pelo luxo de ter um vínculo é o fechamento adicional.

Function.prototype.bind()
O ECMAScript 5 adiciona um método bind() a Function.prototype, tornando-o tão fácil de usar
quanto apply() e call(). Então você pode fazer expressões como:
var newFunc = obj.someFunc.bind(myobj, 1, 2, 3);

Isso significa vincular someFunc() e myobj e também preencher previamente os três primeiros
argumentos que someFunc() espera. Este também é um exemplo de aplicação de função
parcial discutida no Capítulo 4.

Vamos ver como você pode implementar Function.prototype.bind() quando seu programa rodar
em ambientes pré-ES5:
if (typeof Function.prototype.bind === "indefinido")
{ Function.prototype.bind = function (thisArg) { var fn = this,

138 | Capítulo 6: Padrões de reutilização de código


Machine Translated by Google

fatia = Array.prototype.slice,
args = fatia.call(argumentos, 1);

return function ()
{ return fn.apply(thisArg, args.concat(slice.call(argumentos)));
};
};
}

Essa implementação provavelmente parece um pouco familiar; está usando a aplicação parcial e concatenando a lista
de argumentos - aqueles passados para bind() (exceto o primeiro) e aqueles passados quando a nova função retornada
por bind() é chamada posteriormente. Aqui está um exemplo de uso:

var twosay2 = one.say.bind(dois);


twosay2('Bonjour'); // "Bonjour, outro objeto"

No exemplo anterior, não passamos nenhum parâmetro para bind() além do objeto a ser vinculado. No próximo exemplo,
vamos passar um argumento a ser aplicado parcialmente:

var twosay3 = one.say.bind(dois, 'Enchanté');


twosay3(); // "Enchanté, outro objeto"

Resumo
Existem muitas opções disponíveis quando se trata de herança em JavaScript. É bom estudar e entender os diferentes
padrões porque eles ajudam a melhorar sua compreensão do idioma. Neste capítulo, você aprendeu sobre vários
padrões clássicos e modernos para abordar a herança.

No entanto, a herança provavelmente não é um problema que você enfrenta frequentemente durante o desenvolvimento.
Isso se deve em parte ao fato de que esse problema provavelmente já foi resolvido de uma forma ou de outra pela
biblioteca que você usa — e em parte porque é raro precisar estabelecer cadeias de herança longas e complicadas em
JavaScript. Em linguagens estáticas fortemente tipadas, a herança pode ser a única maneira de reutilizar o código. Em
JavaScript, muitas vezes você pode ter maneiras muito mais simples e elegantes, incluindo métodos emprestados,
vinculando-os, copiando propriedades e misturando propriedades de vários objetos.

Lembre-se de que a reutilização de código é o objetivo e a herança é apenas uma das maneiras de atingir esse objetivo.

Resumo | 139
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 7

Padrões de design

Os padrões de projeto do livro Gang of Four oferecem soluções para problemas comuns relacionados ao
projeto de software orientado a objetos. Eles já existem há algum tempo e provaram ser úteis em muitas
situações. Por isso é bom se familiarizar com eles e falar sobre eles.

Embora esses padrões de projeto sejam independentes de linguagem e agnósticos de implementação, eles
têm sido estudados por muitos anos, principalmente da perspectiva de linguagens de classe estática fortemente
tipadas, como C++ e Java.

O JavaScript, sendo uma linguagem baseada em protótipo dinâmico não tipado, às vezes torna
surpreendentemente fácil, até mesmo trivial, implementar alguns desses padrões.

Vamos começar com o primeiro exemplo de como as coisas são diferentes em JavaScript em comparação
com uma linguagem baseada em classe estática — o padrão singleton.

solteiro
A ideia do padrão singleton é ter apenas uma instância de uma classe específica. Isso significa que na segunda
vez que você usar a mesma classe para criar um novo objeto, deverá obter o mesmo objeto que foi criado na
primeira vez.

E como isso se aplica ao JavaScript? Em JavaScript não há classes, apenas objetos.


Quando você cria um novo objeto, na verdade não há outro objeto como ele, e o novo objeto já é um singleton.
A criação de um objeto simples usando o objeto literal também é um exemplo de um singleton:

var obj =
{ myprop: 'meu valor'
};

141
Machine Translated by Google

Em JavaScript, os objetos nunca são iguais, a menos que sejam o mesmo objeto; portanto, mesmo que
você crie um objeto idêntico com exatamente os mesmos membros, não será o mesmo que o primeiro
um:

var obj2 =
{ myprop: 'meu valor'
};
obj === obj2; // false obj
== obj2; // falso

Portanto, você pode dizer que toda vez que cria um objeto usando o objeto literal, na verdade está
criando um singleton e não há nenhuma sintaxe especial envolvida.

Observe que, às vezes, quando as pessoas dizem “singleton” em um contexto


JavaScript, elas se referem ao padrão de módulo discutido no Capítulo 5.

Usar o novo

JavaScript não tem classes, portanto, a definição literal de singleton não faz sentido tecnicamente. Mas
o JavaScript tem a nova sintaxe para criar objetos usando funções construtoras e, às vezes, você pode
querer uma implementação singleton usando essa sintaxe. A ideia é que quando você usa new para
criar vários objetos usando o mesmo construtor, você deve obter apenas novos ponteiros para
exatamente o mesmo objeto.

Aviso de utilidade: A discussão a seguir não é tão útil como um padrão prático, mas
mais como um exercício teórico para imitar as soluções alternativas para problemas
relacionados aos designs de algumas linguagens baseadas em classes (estaticamente,
fortemente tipadas) nas quais as funções não são as primeiras -objetos de classe.

O trecho a seguir mostra o comportamento esperado (supondo que você descarte a ideia do Multiverso
e aceite que existe apenas um Universo lá fora):
var uni = new Universo();
var uni2 = new Universo();
uni === uni2; // verdadeiro

Neste exemplo, uni é criado apenas na primeira vez que o construtor é chamado. Na segunda vez (e na
terceira, quarta e assim por diante) o mesmo objeto uni é retornado. É por isso que uni === uni2 —
porque são essencialmente duas referências apontando exatamente para o mesmo objeto.
E como conseguir isso em JavaScript?

Você precisa que seu construtor Universe armazene em cache a instância do objeto this quando ela for
criada e, em seguida, retorne-a na segunda vez que o construtor for chamado. Você tem várias opções
para conseguir isso:

142 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

• Você pode usar uma variável global para armazenar a instância. Isso não é recomendado
devido ao princípio geral de que globais são ruins. Além disso, qualquer pessoa pode
sobrescrever essa variável global, mesmo por acidente. Portanto, não vamos discutir
mais essa opção. • Você pode armazenar em cache uma propriedade estática do construtor.
Funções em JavaScript são objetos, então elas podem ter propriedades. Você pode ter
algo como Universe.instance e armazenar o objeto lá. Esta é uma solução agradável e
limpa, com a única desvantagem de que a propriedade da instância é acessível
publicamente e o código do seu lado pode alterá-la, então
você perde a instância. • Você pode agrupar a instância em um encerramento. Isso mantém a
instância privada e não disponível para modificações fora de seu construtor à custa de um
fechamento extra.

Vamos dar uma olhada em um exemplo de implementação da segunda e terceira opções.

Instância em uma propriedade estática

Aqui está um exemplo de cache da instância singular em uma propriedade estática do


Construtor do universo :
função Universo() {

// temos uma instância existente? if


(typeof Universe.instance === "objeto") { return
Universe.instance;
}

// proceder normalmente
this.start_time = 0;
this.bang = "Grande";

// cache
Universe.instance = this;

// retorno implícito: //
retorna isso;
}

// testando
var uni = new Universe(); var
uni2 = new Universo(); uni
=== uni2; // verdadeiro

Como você pode ver, esta é uma solução direta com a única desvantagem de que a instância é
pública. É improvável que outro código o altere por engano (muito menos provável do que se a
instância fosse global), mas ainda é possível.

Singleton | 143
Machine Translated by Google

Instância em um Closure

Outra maneira de fazer o singleton semelhante a uma classe é usar um encerramento para
proteger a instância única. Você pode implementar isso usando o padrão de membro estático
privado discutido no Capítulo 5. O segredo aqui é reescrever o construtor:
função Universo() {

// a instância em cache
var instance = this;

// proceder normalmente
this.start_time = 0;
this.bang = "Grande";

// reescreve o construtor
Universo = função ()
{ return instância;
};
}

// testando
var uni = new Universe(); var
uni2 = new Universo(); uni
=== uni2; // verdadeiro

O construtor original é chamado pela primeira vez e retorna isso como de costume. Em seguida,
a segunda, terceira vez e assim por diante, o constritor reescrito é executado. O construtor
reescrito tem acesso à variável de instância privada por meio do encerramento e simplesmente a retorna.

Essa implementação é, na verdade, outro exemplo do padrão de função de autodefinição do


Capítulo 4. A desvantagem, como discutimos lá, é que a função reescrita (neste caso, o construtor
Universe()) perderá todas as propriedades adicionadas a ela entre o momento da definição inicial
e da redefinição. Em nosso caso específico, qualquer coisa que você adicionar ao protótipo de
Universe() não terá um link ativo para a instância criada com a implementação original.

Veja como você pode ver o problema com alguns testes:


// adicionando ao protótipo
Universe.prototype.nothing = true;

var uni = new Universo();

// adicionando novamente ao
protótipo // depois que o objeto inicial é
criado Universe.prototype.everything = true;

var uni2 = new Universo();

Teste: //
apenas o protótipo original foi //
vinculado aos objetos

144 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

uni.nada; // verdadeiro
uni2.nothing; //
verdadeiro uni.everything; //
undefined uni2.everything; // indefinido

// isso soa bem:


uni.constructor.name; // "Universo"

// mas isso é
estranho: uni.constructor === Universe; // falso

A razão pela qual uni.constructor não é mais o mesmo que o construtor Universe() é porque uni.constructor ainda
aponta para o construtor original, não para o redefinido.

Se obter o protótipo e o ponteiro do construtor funcionando como esperado é um requisito, é possível conseguir
isso com alguns ajustes:

função Universo() {

// a instância em cache
var instance;

// reescreve o construtor
Universo = função Universo()
{ return instância;
};

// transporta as propriedades do protótipo


Universe.prototype = this;

// a instância
instance = new Universe();

// redefine o ponteiro do construtor


instance.constructor = Universe;

// toda a funcionalidade
instance.start_time = 0;
instance.bang = "Grande";

instância de retorno;
}

Agora todos os casos de teste devem funcionar como esperado:

// atualiza o protótipo e cria a instância


Universe.prototype.nothing = true; // true var
uni = new Universe();
Universe.prototype.everything = true; // true var
uni2 = new Universe();

// é a mesma instância única uni


=== uni2; // verdadeiro

// todas as propriedades do protótipo


funcionam // não importa quando foram definidas

Singleton | 145
Machine Translated by Google

uni.nada && uni.tudo && uni2.nada && uni2.tudo; // verdadeiro // as


propriedades normais funcionam
uni.bang; // "Big" //
o construtor aponta corretamente
uni.constructor === Universe; // verdadeiro

Uma solução alternativa também seria agrupar o construtor e a instância em uma função imediata.
Na primeira vez que o construtor é invocado, ele cria um objeto e também aponta a instância privada
para ele. A partir da segunda chamada, o construtor simplesmente retorna a variável privada. Todos
os testes do snippet anterior também funcionarão como esperado com esta nova implementação:

var Universo;

(função () {

var instância;

Universo = function Universo() {

if (instância)
{ return instância;
}

instância = isso;

// toda a funcionalidade
this.start_time = 0;
this.bang = "Grande";

};

}());

Fábrica O

propósito da fábrica é criar objetos. Geralmente é implementado em uma classe ou método estático
de uma classe, que tem as seguintes finalidades:

• Executa operações repetidas ao configurar objetos semelhantes • Oferece


uma maneira para os clientes da fábrica criarem objetos sem conhecer o tipo específico (classe)
em tempo de compilação

O segundo ponto é mais importante em linguagens de classes estáticas nas quais pode não ser
trivial criar instâncias de classes que não são conhecidas antecipadamente (em tempo de compilação).
Em JavaScript, essa parte da implementação é bastante fácil.

Os objetos criados pelo método de fábrica (ou classe) são, por design, herdados do mesmo objeto
pai; são subclasses específicas que implementam funcionalidades especializadas.
Às vezes, o pai comum é a mesma classe que contém o método de fábrica.

146 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Vejamos um exemplo de implementação onde temos:

• Um construtor pai CarMaker comum . •


Um método estático do CarMaker chamado factory(), que cria objetos
carro. • Construtores especializados CarMaker.Compact, CarMaker.SUV e CarMaker.
Conversível que herda da CarMaker. Todos eles serão definidos como propriedades estáticas
do pai para que possamos manter o namespace global limpo e também saber onde encontrá-
los quando precisarmos deles.

Vamos primeiro ver como a implementação finalizada será usada:


var corolla = CarMaker.factory('Compact'); var
solstício = CarMaker.factory('Conversível'); var
cherokee = CarMaker.factory('SUV');
corolla.drive(); // "Vroom, eu tenho 4 portas"
solstice.drive(); // "Vroom, eu tenho 2 portas"
cherokee.drive(); // "Vroom, eu tenho 17 portas"

Esta parte:
var corolla = CarMaker.factory('Compact');

é provavelmente o mais reconhecível no padrão de fábrica. Você tem um método que aceita um
tipo fornecido como uma string em tempo de execução e, em seguida, cria e retorna objetos do
tipo solicitado. Não há construtores usados com novos ou quaisquer literais de objeto à vista,
apenas uma função que cria objetos com base em um tipo identificado por uma string.

Aqui está um exemplo de implementação do padrão de fábrica que faria o código no snippet
anterior funcionar:
// função construtora pai
CarMaker() {}

// um método do pai
CarMaker.prototype.drive = function
" () { return
"Vroom, eu tenho + this.doors + "portas";
};

// o método estático de fábrica


CarMaker.factory = function (type) { var
constr = type, newcar;

// erro se o construtor não existir if (typeof


CarMaker[constr] !== "function") { throw { name:
"Error",
message: constr
+ "não existe"
};
}

// neste ponto, sabe-se que o construtor existe // vamos


fazer com que ele herde o pai, mas apenas uma vez if
(typeof CarMaker[constr].prototype.drive !== "function") {

Fábrica | 147
Machine Translated by Google

Montadora[constr].prototype = new Montadora();

} // cria uma nova instância


newcar = new CarMaker[constr](); //
opcionalmente chama alguns métodos e então retorna...
return newcar;
};

// define montadoras específicas


CarMaker.Compact = function ()
{ this.doors = 4;
};
CarMaker.Convertible = function ()
{ this.doors = 2;
};
CarMaker.SUV = function ()
{ this.doors = 24;
};

Não há nada particularmente difícil na implementação do padrão de fábrica.


Tudo o que você precisa fazer é procurar a função construtora que cria um objeto do tipo
necessário. Nesse caso, uma convenção de nomenclatura simples foi usada para mapear os
tipos de objeto para os construtores que os criam. A parte da herança era apenas um exemplo de
um pedaço de código repetitivo comum que poderia ser colocado no método de fábrica em vez
de repetido para cada tipo de construtor.

Object Factory integrado E


para um exemplo de “fábrica em estado selvagem”, considere o construtor Object() global
integrado. Ele também se comporta como uma fábrica, pois cria diferentes objetos, dependendo
da entrada. Se você passar um número primitivo, ele pode criar um objeto com o construtor
Number() nos bastidores. O mesmo é verdadeiro para os valores de string e booleanos. Quaisquer
outros valores, incluindo nenhum valor de entrada, criarão um objeto normal.

Aqui estão alguns exemplos e testes do comportamento. Observe que Object pode ser chamado
com ou sem new:

var o = new Object(), n


= new Object(1), s
= Object('1'), b =
Object(true);

// teste
o.constructor === Object; // true
n.constructor === Number; // true
s.constructor === String; // true
b.constructor === Boolean; // verdadeiro

O fato de Object() também ser uma fábrica é de pouca utilidade prática, apenas algo que vale a
pena mencionar como exemplo de que o padrão fábrica está ao nosso redor.

148 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Iterador
No padrão do iterador, você tem um objeto contendo algum tipo de dados agregados. Esses dados podem ser
armazenados internamente em uma estrutura complexa e você deseja fornecer acesso fácil a cada elemento
dessa estrutura. O consumidor de seu objeto não precisa saber como você estrutura seus dados; tudo o que
eles precisam é trabalhar com os elementos individuais.

No padrão iterador, seu objeto precisa fornecer um método next() . Chamar next() em sequência deve retornar
o próximo elemento consecutivo, onde cabe a você decidir o que “next” significa em sua estrutura de dados
específica.

Supondo que seu objeto seja chamado agg, você pode acessar cada elemento de dados simplesmente
chamando next() em um loop da seguinte forma:

elemento var;
while (element = agg.next()) { // faça
algo com o elemento ... console.log(element);

No padrão do iterador, o objeto agregado geralmente também fornece um método Next() de conveniência , para
que os usuários do objeto possam determinar se atingiram o fim de seus dados. Outra maneira de acessar todos
os elementos sequencialmente, desta vez usando hasNext(), seria a seguinte:

while (agg.hasNext()) { // faz


algo com o próximo elemento...
console.log(agg.next());
}

Agora que temos os casos de uso, vamos ver como implementar tal objeto agregado.

Ao implementar o padrão do iterador, faz sentido armazenar de forma privada os dados e um ponteiro (índice)
para o próximo elemento disponível. Para demonstrar um exemplo de implementação, vamos assumir que os
dados são apenas um array comum e a lógica “especial” para recuperação do próximo elemento consecutivo
será retornar todos os outros elementos do array:

var agg = (função () {

var índice = 0,
dados = [1, 2, 3, 4, 5],
comprimento = dados.comprimento;

retornar {

próximo: função ()
{ elemento
var; if (!this.hasNext())
{ return null;

} elemento = dados[índice];
índice = índice + 2;
elemento de retorno;

Iterador | 149
Machine Translated by Google

},

hasNext: função () { return


index < comprimento;
}

}; }());

Para fornecer acesso mais fácil e capacidade de iterar várias vezes sobre os dados, seu objeto pode
fornecer métodos de conveniência adicionais: rewind()

Para redefinir o ponteiro de volta ao início current()

Para retornar o elemento atual, porque você não pode fazer isso com next() sem avançar o ponteiro

A implementação desses métodos não apresentará nenhuma dificuldade:

var agg = (função () {

// [recorte...]

retornar {

// [recorte...]

rebobinar: função ()
{ índice = 0;
},
atual: function () { return
dados[índice];
}

}; }());

Agora, testando o iterador: //


este loop registra 1, depois 3, depois 5
while (agg.hasNext())
{ console.log(agg.next());
}

// volta
agg.rewind();
console.log(agg.current()); // 1

O resultado será logado no console: 1, 3, 5 (do loop) e finalmente 1 (após o rewind).

150 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Decorador
No padrão decorador, funcionalidades adicionais podem ser adicionadas a um objeto dinamicamente, em
tempo de execução. Ao lidar com classes estáticas, isso pode ser um desafio. No Java Script, os objetos são
mutáveis, portanto, o processo de adicionar funcionalidade a um objeto não é uma
problema em si.

Um recurso conveniente do padrão decorator é a personalização e configuração


do comportamento esperado. Você começa com seu objeto simples, que tem alguma funcionalidade básica.
Em seguida, você escolhe entre um grupo disponível de decoradores quais
você deseja usar para aprimorar seu objeto simples e em qual ordem, se a ordem for
importante.

Uso
Vamos dar uma olhada em um exemplo de uso do padrão. Digamos que você esteja trabalhando em uma web
aplicativo que vende algo. Cada nova venda é um novo objeto de venda . A venda “sabe”
sobre o preço do item e pode devolvê-lo chamando o método sale.getPrice() .
Dependendo das circunstâncias, você pode começar a decorar este objeto com funcionalidade extra. Imagine
um cenário em que a venda para um cliente seja na província canadense
de Québec. Neste caso, o comprador precisa pagar um imposto federal e um provincial do Québec
imposto. Seguindo o padrão decorador, você dirá que “decora” o objeto com um
decorador tributário federal e decorador tributário quebequense. Você também pode decorar o objeto
com funcionalidade de formatação de preços. Este cenário pode ser semelhante ao seguinte:

var venda = new Venda(100); // o preço é de 100 dólares


venda = venda.decorate('fedtax'); // adicionar imposto federal
venda = venda.decorar('quebeque'); // adicionar imposto provincial
venda = venda.decorar('dinheiro'); // formata como dinheiro
venda.getPrice(); // "$ 112,88"

Em outro cenário, o comprador pode estar em uma província que não possui um imposto provincial,
e você também pode querer formatar o preço usando dólares canadenses, então você pode fazer:

var venda = new Venda(100); // o preço é de 100 dólares


venda = venda.decorate('fedtax'); // adicionar imposto federal
venda = venda.decorar('cdn'); // formata usando CDN
venda.getPrice(); // "CDN$ 105,00"

Como você pode ver, esta é uma maneira flexível de adicionar funcionalidade e ajustar um objeto em tempo de execução.
Vamos ver como abordar uma implementação do padrão.

Implementação
Uma maneira de implementar o padrão decorador é fazer com que cada decorador seja um objeto
contendo os métodos que devem ser sobrescritos. Cada decorador realmente herda
o objeto aprimorado até agora após o decorador anterior. Cada método decorado chama

Decorador | 151
Machine Translated by Google

o mesmo método no uber (o objeto herdado) e obtém o valor e continua fazendo algo adicional.

O efeito final é que, ao fazer sale.getPrice() no primeiro exemplo de uso, você está chamando o método
do decorador de dinheiro (consulte a Figura 7-1). Mas como cada método decorado primeiro chama o
método pai, getPrice() de money primeiro chama getPrice() de quebec , que por sua vez chama getPrice()
de fedtax e assim por diante. A cadeia vai até o getPrice() original não decorado implementado pelo
construtor Sale() .

Figura 7-1. Implementação do padrão decorador

A implementação começa com um construtor e um método de protótipo:


função Venda(preço)
{ this.price = preço || 100;
}
Sale.prototype.getPrice = function ()
{ return this.price;
};

Os objetos decoradores serão todos implementados como propriedades de uma propriedade construtora:
Venda.decoradores = {};

Vamos ver um decorador de exemplo. É um objeto que implementa o método get Price() customizado .
Observe que o método primeiro obtém o valor do método pai e depois modifica esse valor:

152 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Sale.decorators.fedtax =
{ getPrice: function ()
{ var price = this.uber.getPrice();
*
preço += preço 5 / 100;
preço de retorno;
}
};

Da mesma forma podemos implementar outros decoradores, quantos forem necessários. Eles podem ser
extensões para a funcionalidade principal do Sale() , implementadas como plug-ins. Eles podem até “viver”
em arquivos adicionais e serem desenvolvidos e compartilhados por desenvolvedores terceirizados:
Sale.decorators.quebec =
{ getPrice: function ()
{ var price = this.uber.getPrice();
*
preço += preço 7,5/100;
preço de retorno;
}
};

Sale.decorators.money = {
getPrice: function ()
{ return "$" + this.uber.getPrice().toFixed(2);
}
};

Sale.decorators.cdn =
{ getPrice: function ()
{ return "CDN$ "+ this.uber.getPrice().toFixed(2);
}
};

Finalmente, vamos ver o método “mágico” chamado decor() que une todas as peças.
Lembre-se que será chamado assim:

venda = venda.decorate('fedtax');

A string 'fedtax' corresponderá a um objeto implementado em Sale.decorators.fedtax. O objeto recém-decorado


newobj herdará o objeto que temos até agora (seja o original ou aquele após o último decorador ter sido
adicionado), que é o objeto this. Para fazer a parte da herança, vamos usar o padrão de construtor temporário
do capítulo anterior. Também definimos a propriedade uber de newobj para que os filhos tenham acesso ao
pai. Em seguida, copiamos todas as propriedades extras do decorador para o objeto recém-decorado newobj.
Ao final, newobj é retornado e, em nosso exemplo de uso concreto, torna-se o novo objeto de venda atualizado:

Sale.prototype.decorate = function (decorador) {


var F = function () {},
substitui = this.constructor.decorators[decorator], i,
newobj;
F.protótipo = este;
novoobj = novo
F(); newobj.uber =
F.protótipo; for (i em substituições) {

Decorador | 153
Machine Translated by Google

if (overrides.hasOwnProperty(i))
{ newobj[i] = overrides[i];
}

} return newobj;
};

Implementação usando uma


lista Vamos explorar uma implementação um pouco diferente, que se beneficia da
natureza dinâmica do JavaScript e não precisa usar herança. Além disso, em vez de
cada método decorado chamar o método anterior na cadeia, podemos simplesmente
passar o resultado do método anterior como parâmetro para o próximo método.

Essa implementação também pode permitir desdecorar ou desfazer uma decoração facilmente, o que
significa simplesmente remover um item da lista de decoradores.

O exemplo de uso será um pouco mais simples porque não atribuímos o valor de retorno de decora()
ao objeto. Nesta implementação, décor() não faz nada com o objeto, ele simplesmente anexa a uma
lista:
var venda = new Venda(100); // o preço é de 100 dólares
sale.decorate('fedtax'); // adiciona imposto federal
sale.decorate('quebec'); // adiciona o imposto
provincial sale.decorate('money'); // formato como
dinheiro sale.getPrice(); // "$ 112,88"

O construtor Sale() agora possui uma lista de decoradores como propriedade própria:
function Venda(preço)
{ this.price = (preço > 0) || 100;
this.decorators_list = [];
}

Os decoradores disponíveis são novamente implementados como propriedades de Sale.decorators.


Observe que os métodos getPrice() agora são mais simples porque não chamam o pai getPrice()
para obter o resultado intermediário; este resultado é passado para eles como um parâmetro:

Venda.decoradores = {};

Sale.decorators.fedtax =
{ getPrice: function (preço) { 5 /
*
preço de retorno + preço 100;
}
};

Sale.decorators.quebec =
{ getPrice: function (preço)
preço de retorno + preço{*7,5 / 100;
}
};

Sale.decorators.money = {

154 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

getPrice: function (preço) {


return "$" + preço.toFixed(2);
}
};

A parte interessante acontece nos métodos decore() e getPrice() do pai. Na implementação


anterior, decorate() era um tanto complexo e getPrice() era bem simples. Nesta implementação,
é o contrário: decorate() apenas acrescenta a uma lista enquanto getPrice() faz todo o trabalho.
O trabalho inclui percorrer a lista de decoradores adicionados atualmente e chamar cada um dos
métodos getPrice() , passando o resultado do anterior:

Sale.prototype.decorate = function (decorador)


{ this.decorators_list.push(decorador);
};

Sale.prototype.getPrice = function () { var


price = this.price, i, max
=
this.decorators_list.length,
nome;
for (i = 0; i < max; i += 1) {
nome = this.decorators_list[i];
preço = Venda.decoradores[nome].getPrice(preço);

} preço de retorno;
};

Essa segunda implementação do padrão decorador é mais simples e não há herança envolvida.
Os métodos de decoração também são mais simples. Todo o trabalho é feito pelo método que
“convém” ser decorado. Nesta implementação de exemplo, getPrice() é o único método que
permite decoração. Se você quiser ter mais métodos que possam ser decorados, a parte de
percorrer a lista de decoradores deve ser repetida por cada método adicional. No entanto, isso
pode ser facilmente abstraído em um método auxiliar que pega um método e o torna “decorado”.
Em tal implementação, a propriedade decorators_list se tornaria um objeto com propriedades
nomeadas após os métodos e valores serem arrays de objetos decorator.

Estratégia
O padrão de estratégia permite selecionar algoritmos em tempo de execução. Os clientes do seu
código podem trabalhar com a mesma interface, mas escolher entre vários algoritmos disponíveis
para lidar com suas tarefas específicas, dependendo do contexto do que estão tentando fazer.

Um exemplo de uso do padrão de estratégia seria resolver o problema de validação de formulário.


Você pode criar um objeto validador com um método validar() . Este é o método que será
chamado independentemente do tipo concreto de formulário e sempre retornará o mesmo
resultado - uma lista de dados que não foram validados e quaisquer mensagens de erro.

Estratégia | 155
Machine Translated by Google

Mas dependendo do formulário concreto e dos dados a serem validados, os clientes do seu validador
podem escolher diferentes tipos de verificações. Seu validador escolhe a melhor estratégia para lidar
com a tarefa e delega as verificações de dados concretos ao algoritmo apropriado.

Exemplo de validação de dados

Digamos que você tenha os seguintes dados, provavelmente provenientes de um formulário em uma
página, e deseja verificar se são válidos: var
data =
{ first_name: "Super",
last_name: "Man",
age: "unknown",
nome de usuário: "o_O"
};

Para que o validador saiba qual a melhor estratégia a ser utilizada neste exemplo concreto, você
precisa primeiro configurar o validador e definir as regras do que você considera válido e aceitável.

Digamos que você não exija um sobrenome e aceite qualquer coisa como primeiro nome, mas exija
que a idade seja um número e que o nome de usuário tenha apenas letras e números e nenhum
símbolo especial. A configuração será algo como:
validator.config =
{ first_name: 'isNonEmpty',
idade: 'isNumber',
nome de usuário: 'isAlphaNum'
};

Agora que o objeto validador está configurado para lidar com seus dados, chame seu método valid()
e imprima quaisquer erros de validação no console:
validator.validate(dados); if
(validator.hasErrors())
{ console.log(validator.messages.join("\n"));
}

Isso pode imprimir as seguintes mensagens de erro:


Valor inválido para *idade*, o valor só pode ser um número válido, por exemplo, 1, 3,14 ou 2010
Valor inválido para *nome de usuário*, o valor pode conter apenas caracteres e números, sem símbolos
especiais

Agora vamos ver como o validador é implementado. Os algoritmos disponíveis para as verificações
são objetos com uma interface pré-definida—eles fornecem um método valid() e uma linha de ajuda
para ser usado em mensagens de erro:
// verifica valores não vazios
validator.types.isNonEmpty = {
validar: função (valor) { valor de
retorno !== "";
},
instruções: "o valor não pode estar vazio"

156 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

};

// verifica se um valor é um número


validator.types.isNumber = {
validar: função (valor) { return !
isNaN(valor);
},
instruções: "o valor só pode ser um número válido, por exemplo, 1, 3,14 ou 2010"
};

// verifica se o valor contém apenas letras e números


validator.types.isAlphaNum = {
validar: função (valor) { return !/
[^a-z0-9]/i.test(valor);
},
instruções: "o valor pode conter apenas caracteres e números, sem símbolos especiais"
};

E, finalmente, o núcleo do objeto validador :


var validador = {

// todos os tipos de verificação


disponíveis: {},

// mensagens de erro nas //


mensagens de sessão
de validação atuais: [],

// configuração de validação atual //


nome: configuração do tipo de
validação: {},

// o método de interface //
`data` é a chave => pares de valores
validam: função (dados) {

var i, msg, tipo, verificador, result_ok;

// redefinir todas as
mensagens this.messages = [];

para (i em dados) {

if (data.hasOwnProperty(i)) {

digite = this.config[i];
verificador = this.types[tipo];

if (!tipo)
{ continue; // não precisa validar

} if (!checker) { // uh-oh
throw
{ name: "ValidationError",
"
message: "Nenhum manipulador para validar o+tipo
tipo

Estratégia | 157
Machine Translated by Google

};
}

result_ok = verificador.validate(data[i]);
if (!result_ok)
{ msg = "Valor inválido para *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}

} return this.hasErrors();
},

// helper
hasErrors: function ()
{ return this.messages.length !== 0;
}
};

Como você pode ver, o objeto validador é genérico e pode ser mantido assim para todos os
casos de uso de validação. A maneira de melhorá-lo seria adicionar mais tipos de verificações.
Se você usá-lo em várias páginas, logo terá uma bela coleção de cheques específicos. Então
tudo que você precisa fazer para cada novo caso de uso é configurar o validador e executar o
método valid() .

Fachada
A fachada é um padrão simples; ele fornece apenas uma interface alternativa para um objeto. É
uma boa prática de design manter seus métodos curtos e não deixá-los lidar com muito trabalho.
Seguindo esta prática, você terá um número maior de métodos do que se tivesse métodos uber
com muitos parâmetros. Às vezes, dois ou mais métodos podem ser comumente chamados
juntos. Nesses casos, faz sentido criar outro método que envolva as chamadas de método
repetidas.

Por exemplo, ao manipular eventos do navegador, você tem os seguintes métodos:

stopPropagation()
Captura o evento e não permite que ele chegue aos nós pais
preventDefault()
Não permite que o navegador execute a ação padrão (por exemplo, seguir um link ou enviar
um formulário)

Estes são dois métodos separados com propósitos diferentes e devem ser mantidos separados,
mas, ao mesmo tempo, eles são frequentemente chamados juntos. Portanto, em vez de duplicar
as duas chamadas de método em todo o aplicativo, você pode criar um método de fachada que
chame os dois:

var meuevento
=
{ // ... stop: function (e) {

158 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

e.preventDefault();
e.stopPropagation();

} // ...
};

O padrão de fachada também é adequado para scripts de navegador, onde as diferenças entre os
navegadores podem ser ocultadas por trás de uma fachada. Continuando com o exemplo anterior,
você pode adicionar o código que lida com as diferenças na API de evento do IE:
var meuevento =
{ // ...
stop: function (e) { //
outros if
(typeof e.preventDefault === "function")
{ e.preventDefault();

} if (typeof e.stopPropagation === "função")


{ e.stopPropagation();
}
// IE
if (typeof e.returnValue === "boolean")
{ e.returnValue = false;

} if (typeof e.cancelBubble === "boolean")


{ e.cancelBubble = true;
}

} // ...
};

O padrão de fachada também é útil nos esforços de redesenho e refatoração. Quando você deseja
substituir um objeto por uma implementação diferente, deve fazê-lo durante um período de tempo (é
um objeto complexo), enquanto ao mesmo tempo está sendo escrito um novo código que usa esse
objeto. Você pode começar pensando na API do novo objeto e, em seguida, criar uma fachada na
frente do objeto antigo que segue a nova API. Dessa forma, quando você substituir totalmente o
objeto antigo, terá menos código de cliente para modificar porque qualquer código de cliente recente
já usará a nova API.

Proxy
No padrão de design proxy, um objeto atua como uma interface para outro objeto. É diferente do
padrão de fachada, onde tudo o que você tem são métodos de conveniência que combinam várias
outras chamadas de método. O proxy fica entre o cliente de um objeto e o próprio objeto e protege
o acesso a esse objeto.

Esse padrão pode parecer sobrecarga, mas é útil para fins de desempenho. O proxy serve como
um guardião do objeto (também chamado de “sujeito real”) e tenta fazer com que o sujeito real faça
o mínimo de trabalho possível.

Procuração | 159
Machine Translated by Google

Um exemplo de uso seria algo que podemos chamar de inicialização preguiçosa. Imagine que inicializar o
objeto real é caro, e acontece que o cliente o inicializa, mas nunca o usa de fato. Nesse caso, o proxy pode
ajudar sendo a interface com o sujeito real. O proxy recebe a solicitação de inicialização, mas nunca a
repassa até que esteja claro que o assunto real é realmente usado.

A Figura 7-2 ilustra o cenário em que o cliente faz uma solicitação de inicialização e o proxy responde que
está tudo bem, mas não transmite a mensagem até que seja óbvio que o cliente precisa de algum trabalho
feito pelo assunto. Só então o proxy passa as duas mensagens juntas.

Figura 7-2. Relação entre cliente e sujeito real ao passar por um proxy

Um exemplo O

padrão proxy é útil quando o sujeito real faz algo caro. Em aplicativos da Web, uma das operações mais
caras que você pode fazer é uma solicitação de rede, portanto, faz sentido combinar as solicitações HTTP o
máximo possível. Vamos dar um exemplo que faz exatamente isso e demonstra o padrão de proxy em ação.

Uma expansão

de vídeo Vamos ter um pequeno aplicativo que reproduz vídeos de um artista selecionado (Figura 7-3). Você
pode brincar com o exemplo ao vivo e ver o código em http:// www.jspatterns.com/ book/ 7/ proxy.html .

Você tem uma lista de títulos de vídeo em uma página. Quando o usuário clica no título de um vídeo, a área
abaixo do título se expande para mostrar mais informações sobre o vídeo e também permite que o vídeo seja
reproduzido. As informações detalhadas do vídeo e a URL do vídeo não fazem parte da página; eles precisam
ser recuperados fazendo uma chamada de serviço da web. O serviço da Web pode aceitar vários IDs de
vídeo, para que possamos acelerar o aplicativo fazendo menos solicitações HTTP sempre que possível e
recuperando dados de vários vídeos ao mesmo tempo.

Nosso aplicativo permite que vários (ou todos) vídeos sejam expandidos ao mesmo tempo, portanto, esta é
uma oportunidade perfeita para combinar solicitações de serviços da web.

160 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Figura 7-3. Expansão de vídeo em ação

sem procuração

Os principais “atores” da aplicação são os dois objetos:


videos
Responsável pelas áreas de informação expand/collapse (método videos.getInfo()) e
reprodução de vídeos (método

videos.getPlayer()) http Responsável pela comunicação com o servidor através do método http.makeRequest()

Quando não há proxy, videos.getInfo() chamará http.makeRequest() uma vez para cada
vídeo. Quando adicionamos um proxy, ele se tornará um novo ator chamado proxy e ficará
sentado entre vídeos e http e delegará as chamadas para makeRequest(), combinando-os
quando possível.

Procuração | 161
Machine Translated by Google

Vamos examinar primeiro o código sem o proxy e, em seguida, adicionar o proxy para melhorar a
capacidade de resposta do aplicativo.

HTML

O código HTML é apenas uma lista de links:


<p><span id="toggle-all">Alternar marcado</p> <ol
id="vids">
<li><input type="checkbox" marcado><a
href="http://new.music.yahoo.com/videos/--2158073">Coveiro</a></li>
<li><input type="checkbox" marcada><a
href="http://new.music.yahoo.com/videos/--4472739">Salve-me</a></li>
<li>< input type="checkbox" marcado <a
href="http://new.music.yahoo.com/videos/--45286339">Crush</a></li>
<li><input type="checkbox " verificado <a
href="http://new.music.yahoo.com/videos/--2144530">Não beba água</a></li>
<li><input type="checkbox" marcado><a
href="http://new.music.yahoo.com/videos/--217241800">Engraçado do jeito que é</a></li>
<li><input type="checkbox" marcado><a
href="http://new.music.yahoo.com/videos/--2144532">O que você diria</a></li>
</ol>

manipuladores de eventos

Agora vamos dar uma olhada nos manipuladores de eventos. Primeiro, definimos a função abreviada $ de
conveniência:

var $ = function (id)


{ return document.getElementById(id);
};

Usando a delegação de eventos (mais sobre esse padrão no Capítulo 8), vamos manipular todos os
cliques que ocorrem na lista ordenada id="vids" com uma única função:
$('vídeos').onclick = function (e) { var
src, id;

e = e || janela.evento;
src = e.target || e.srcElement;

if (src.nodeName !== "A")


{ return;
}

if (typeof e.preventDefault === "função")


{ e.preventDefault();

} e.returnValue = false;

id = src.href.split('--')[1];

if (src.className === "play")


{ src.parentNode.innerHTML = videos.getPlayer(id);
retornar;

162 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

src.parentNode.id = "v" + id;


videos.getInfo(id);
};

No manipulador de cliques catch-all, estamos interessados em dois cliques: um para expandir/recolher a seção
de informações (chamando getInfo()) e outro para reproduzir o vídeo (quando o destino tem o nome de classe
“play”), o que significa que o A seção de informações já foi expandida e podemos chamar getPlayer(). Os IDs
dos vídeos são extraídos do link hrefs.

O outro manipulador de cliques reage aos cliques para alternar todas as seções de informações. É basicamente
chamar getInfo() novamente, mas em um loop:

$('toggle-all').onclick = function (e) {

var hrefs,
i,

max, id;

hrefs = $('vídeos').getElementsByTagName('a');
for (i = 0, max = hrefs.length; i < max; i += 1) { // pular
links de reprodução
if (hrefs[i].className === "play")
{ continue;

} // pular
desmarcado if (!hrefs[i].parentNode.firstChild.checked)
{ continue;
}

id = hrefs[i].href.split('--')[1];
hrefs[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};

objeto de vídeos

O objeto videos tem três métodos:

getPlayer()
Retorna o HTML necessário para reproduzir o vídeo Flash (não relevante para a discussão).

atualizarLista()
O callback que recebe todos os dados do serviço web e produz o código HTML para ser usado na seção
de informações expandidas. Também não há nada particularmente interessante acontecendo neste
método. obter informação()

O método que alterna a visibilidade das seções de informações e também faz chamadas para http
passando updateList() como retorno de chamada.

Procuração | 163
Machine Translated by Google

Aqui está um trecho do objeto:


var videos = {

getPlayer: função (id) {...},


updateList: função (dados) {...},

getInfo: função (id) {

var info = $('info' + id);

if (!info)
{ http.makeRequest([id], "videos.updateList");
retornar;
}

if (info.style.display === "nenhum")


{ info.style.display = ''; }
else
{ info.style.display = 'nenhum';
}

}
};

Objeto
http O objeto http possui apenas um método, que faz a solicitação JSONP para o serviço
da Web YQL do Yahoo!:
var http =
{ makeRequest: function (ids, callback)
{ var url = 'http://query.yahooapis.com/v1/public/yql?q=',
sql = 'selecione * de music.video.id onde ids IN ("%ID%")',
format = "format=json",
handler = "callback=" + callback,
script = document.createElement('script') ;

sql = sql.replace('%ID%', ids.join('","')); sql =


encodeURIComponent(sql);

url += sql + '&' + formato + '&' + manipulador;


script.src = url;

document.body.appendChild(script);
}
};

YQL (Yahoo! Query Language) é um meta serviço da Web que oferece a capacidade de
usar sintaxe semelhante a SQL para consumir vários outros serviços da Web sem ter
que estudar a API de cada serviço.

164 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Quando todos os seis vídeos forem alternados, seis solicitações individuais serão enviadas ao serviço da
Web, com consultas YQL semelhantes a:

selecione * de music.video.id onde ids IN ("2158073")

Digite o proxy

O código descrito anteriormente funciona bem, mas podemos fazer melhor. O objeto proxy entra em cena
e assume a comunicação entre http e vídeos. Ele tenta combinar as solicitações usando uma lógica
simples: um buffer de 50ms. O objeto videos não chama o serviço HTTP diretamente, mas chama o proxy.
O proxy então espera antes de encaminhar a solicitação. Se outras chamadas de vídeos vierem no período
de espera de 50ms, elas serão mescladas em uma. Um atraso de 50ms é praticamente imperceptível para
o usuário, mas pode ajudar a combinar solicitações e agilizar a experiência ao clicar em “alternar” e
expandir mais de um vídeo de uma só vez. Também reduz significativamente a carga do servidor, pois o
servidor da Web precisa lidar com um número menor de solicitações.

A consulta YQL combinada para dois vídeos será como:

selecione * em music.video.id onde ids IN ("2158073", "123456")

Na versão modificada do código, a única mudança é que videos.getInfo() agora chama proxy.makeRequest()
em vez de http.makeRequest(), conforme mostrado aqui: proxy.makeRequest(id,
videos.updateList, videos);

O proxy configura uma fila para coletar os IDs dos vídeos recebidos nos últimos 50 ms e, em seguida,
libera a fila chamando http e fornece sua própria função de retorno de chamada, porque o retorno de
chamada videos.updateList() pode manipular apenas um único registro de dados.

Aqui está o código para o proxy: var


proxy = { ids:
[], delay:
50, timeout:
null, callback:
null, context: null,
makeRequest:
function (id, callback, context) {

// adiciona à fila
this.ids.push(id);

this.callback = retorno de
chamada; this.context = contexto;

// configura o tempo
limite if (!this.timeout)
{ this.timeout = setTimeout(function ()
{ proxy.flush(); },
this.delay);

Procuração | 165
Machine Translated by Google

}
},
descarga: função () {

http.makeRequest(this.ids, "proxy.handler");

// limpa o tempo limite e


enfileira this.timeout =
null; this.ids = [];

},
manipulador: função (dados)
{ var i, max;

// único vídeo if
(parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video); retornar;

// vários vídeos para


(i = 0, max = data.query.results.Video.length; i < max; i += 1)
{ proxy.callback.call(proxy.context, data.query.results.Video[ eu]);
}
}
};

A introdução do proxy fornece a capacidade de combinar várias solicitações de serviço da Web


em uma com apenas uma simples alteração no código original.

As Figuras 7-4 e 7-5 ilustram os cenários de fazer três viagens de ida e volta ao servidor (sem
um proxy) versus uma viagem de ida e volta ao usar um proxy.

Figura 7-4. Três viagens de ida e volta ao servidor

166 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Figura 7-5. Usando um proxy para combinar e reduzir o número de viagens de ida e volta ao servidor

Proxy como cache

Neste exemplo, o objeto cliente (vídeos) foi inteligente o suficiente para não solicitar as informações
de vídeo para o mesmo vídeo novamente. Mas isso pode não ser sempre o caso. O proxy pode ir
além na proteção do sujeito real http armazenando em cache os resultados de solicitações
anteriores em uma nova propriedade de cache (consulte a Figura 7-6). Então, se o objeto de
vídeos solicitar informações sobre o mesmo ID de vídeo pela segunda vez, o proxy poderá retirá-
lo do cache e salvar a viagem de ida e volta da rede.

Figura 7-6. O cache do proxy

Mediador
Os aplicativos — grandes e pequenos — são compostos de objetos separados. Todos esses objetos
precisam de uma maneira de se comunicar entre si de uma maneira que não prejudique a
manutenção e sua capacidade de alterar com segurança uma parte do aplicativo sem interromper o
restante. À medida que o aplicativo cresce, você adiciona mais e mais objetos. Então, durante a
refatoração, os objetos são removidos e reorganizados. Quando os objetos sabem muito um sobre o
outro e se comunicam diretamente (chamam os métodos uns dos outros e alteram as propriedades),
isso leva a um acoplamento rígido indesejável. Quando os objetos estão intimamente acoplados, não é fácil mudar

Mediador | 167
Machine Translated by Google

um objeto sem afetar muitos outros. Assim, mesmo a alteração mais simples em um
aplicativo não é mais trivial e é virtualmente impossível estimar o tempo que uma
alteração pode levar.
O padrão mediador alivia essa situação, promovendo acoplamento fraco e ajudando a
melhorar a capacidade de manutenção (consulte a Figura 7-7). Neste padrão os objetos
independentes (colegas) não se comunicam diretamente, mas através de um objeto
mediador . Quando um dos colegas muda de estado, notifica o mediador, e este
comunica a mudança a quaisquer outros colegas que devam saber.

Figura 7-7. Participantes do padrão mediador

Exemplo do
mediador Vamos explorar um exemplo de uso do padrão mediador. O aplicativo será
um jogo em que dois jogadores terão meio minuto para disputar quem apertará um
botão mais vezes que o outro. O jogador 1 compete pressionando 1 e o jogador 2
pressiona 0 (assim eles ficam mais confortáveis e não precisam brigar pelo teclado).
Um placar é atualizado com a pontuação atual.

Os objetos participantes serão:


• Jogador
1 • Jogador
2 • Placar
• Mediador

168 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

O mediador conhece todos os outros objetos. Ele se comunica com o dispositivo de entrada
(teclado), manipula eventos de pressionamento de tecla, determina qual jogador tem a vez
e o notifica (consulte a Figura 7-8). O jogador joga (ou seja, apenas atualiza sua própria
pontuação com um ponto) e notifica o mediador que terminou. O mediador comunica a
pontuação atualizada com o placar, que por sua vez atualiza a exibição.
Além do mediador, nenhum dos outros objetos sabe nada sobre qualquer outro objeto. Isso
torna trivial atualizar o jogo, por exemplo, adicionando um novo jogador ou outro display
mostrando o tempo restante.
Você pode ver a versão ao vivo do jogo e espiar a fonte em http:// jspatterns.com/ book/ 7/
mediator.html .

Figura 7-8. Participantes do jogo de pressionamento de tecla

Os objetos do jogador são criados com um construtor Player() e possuem propriedades


próprias, pontos e nome. O método play() do protótipo incrementa os pontos com um e
então notifica o mediador:

função Player(nome)
{ this.points = 0;
this.name = nome;
}
Player.prototype.play = function ()
{ this.points += 1;
mediator.played();
};

Mediador | 169
Machine Translated by Google

O objeto placar tem um método update() , que é chamado pelo objeto mediador após a vez de
cada jogador. O placar não conhece nenhum jogador e não mantém a pontuação; exibe
apenas a pontuação dada pelo mediador:
var placar = {

// elemento HTML a ser atualizado


element: document.getElementById('results'),

// atualiza a exibição da
pontuação update: function (score) {

var i, msg = ''; for


(i in score) { if
(score.hasOwnProperty(i)) { msg
+= '<p><strong>' + i + '<\/strong>: '; msg +=
pontuação[i]; msg
+= '<\/p>';
}

} this.element.innerHTML = msg;
}
};

E agora vamos dar uma olhada no objeto mediador . Ele inicializa o jogo, criando objetos de
jogador em seu método setup() e mantém o registro dos jogadores em sua propriedade players .
O método play() é chamado por cada jogador após cada turno. Este método atualiza um hash
de pontuação e o envia ao placar para exibição. O último método, keypress(), manipula os
eventos do teclado, determina qual jogador tem a vez e o notifica:
var mediador = {

// todos os jogadores
jogadores: {},

// configuração de
inicialização: function ()
{ var players = this.players;
jogadores.casa = new Jogador('Casa');
jogadores.convidado = new Jogador('Convidado');

},

// alguém joga, atualiza a pontuação


jogada: function () { var
players = this.players, score =
{ Home:
players.home.points, Guest:
players.guest.points
};

scoreboard.update(pontuação);
},

170 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

// manipula as interações do
usuário ao pressionar a
tecla: function (e) { e = e ||
janela.evento; // IE if (e.which ===
49) { // tecla "1"
mediator.players.home.play(); retornar;

} if (e.which === 48) { // chave "0"


mediator.players.guest.play();
retornar;
}
}
};

E a última coisa é configurar e desmontar o jogo:


// ir!
mediator.setup();
window.onkeypress = mediador.keypress;

// Fim do jogo em 30 segundos


setTimeout(function ()
{ window.onkeypress = null;
alert('Fim do jogo!'); },
30000);

Observador

O padrão observador é amplamente usado na programação JavaScript do lado do cliente. Todos


os eventos do navegador (mouseover, keypress e assim por diante) são exemplos do padrão.
Outro nome para isso também é eventos personalizados, ou seja, eventos que você cria
programaticamente, em oposição aos que o navegador aciona. Ainda outro nome é padrão
assinante/ editor .

A principal motivação por trás desse padrão é promover um acoplamento fraco. Em vez de um
objeto chamar o método de outro objeto, um objeto se inscreve na atividade específica de outro
objeto e é notificado. O assinante também é chamado de observador, enquanto o objeto
observado é chamado de publicador ou sujeito. O publicador notifica (chama) todos os assinantes
quando um evento importante ocorre e muitas vezes pode passar uma mensagem na forma de
um objeto de evento.

Exemplo nº 1: Assinaturas de revistas Para

entender como implementar esse padrão, vamos dar um exemplo concreto. Digamos que você
tenha uma editora de jornal, que publica um jornal diário e uma revista mensal. Um assinante
Joe será notificado sempre que isso acontecer.

O objeto de papel precisa ter uma propriedade assinantes que seja um array armazenando todos
os assinantes. O ato de assinatura é apenas adicionar a esta matriz. Quando ocorre um evento,
o papel percorre a lista de assinantes e os notifica. A notificação significa

Observador | 171
Machine Translated by Google

chamando um método do objeto assinante. Portanto, ao assinar, o assinante fornece um de seus


métodos para o método Subscribe() do papel .

O jornal também pode fornecer unsubscribe(), o que significa remover da matriz de assinantes.
O último método importante do paper é publish(), que chamará os métodos dos assinantes.
Para resumir, um objeto publicador precisa ter estes membros:
assinantes
Um array
Subscribe()
Adicionar ao array de assinantes
unsubscribe()
Remover da matriz de assinantes
publish()
Percorrer os assinantes e chamar os métodos que eles forneceram quando se inscreveram

Todos os três métodos precisam de um parâmetro de tipo , porque um editor pode disparar
vários eventos (publicar uma revista e um jornal) e os assinantes podem escolher assinar um,
mas não o outro.

Como esses membros são genéricos para qualquer objeto publicador, faz sentido implementá-
los como parte de um objeto separado. Em seguida, podemos copiá-los (padrão de mixagem)
para qualquer objeto e transformar qualquer objeto em um editor.

Aqui está um exemplo de implementação da funcionalidade de editor genérico, que define todos
os membros obrigatórios listados anteriormente, além de um método auxiliar visitSubscribers() :
var editor =
{ assinantes:
{ qualquer: [] // tipo de evento: assinantes
},
inscreva-se: função (fn, tipo) { tipo
= tipo || 'qualquer'; if
(typeof this.subscribers[type] === "indefinido")
{ this.subscribers[type] = [];

} this.subscribers[tipo].push(fn);
},
cancela inscrição: function (fn,
digite) { this.visitSubscribers('unsubscribe', fn, digite);
},
publish: function (publicação, tipo)
{ this.visitSubscribers('publish', publicação, tipo);
},
visitSubscribers: function (action, arg, type) { var
pubtype = type || 'qualquer',
assinantes = this.subscribers[pubtype], i,

max = assinantes.comprimento;

for (i = 0; i < max; i += 1) {

172 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

if (ação === 'publicar')


{ assinantes[i](arg); }
else { if
(assinantes[i] === arg)
{ assinantes.splice(i, 1);
}
}
}
}
};

E aqui está uma função que pega um objeto e o transforma em um editor simplesmente copiando
os métodos genéricos do editor:
function makePublisher(o) { var
i; for (i
in publisher) { if
(publisher.hasOwnProperty(i) && typeof publisher[i] === "function") { o[i] = publisher[i];

} o.subscribers = {any: []};


}

Agora vamos implementar o objeto de papel . Tudo o que pode fazer é publicar diariamente e mensalmente:

var paper =
{ daily: function ()
{ this.publish("grandes notícias de hoje");
},
mensal: function ()
{ this.publish("análise interessante", "mensal");
}
};

Fazendo do papel um editor:


makePublisher(paper);

Agora que temos um publicador, vamos ver o objeto assinante joe, que possui dois métodos:

var joe =
{ drinkCoffee: function (paper) '
{ console.log('Apenas leia + paper);
},
sundayPreNap: function (mensal) '
{ console.log('Prestes a dormir lendo isso + mensal);
}
};

Agora o jornal assina joe (em outras palavras, joe assina o jornal ):
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap, 'mensalmente');

Observador | 173
Machine Translated by Google

Como você pode ver, joe fornece um método a ser chamado para o evento padrão “qualquer” e
outro método a ser chamado quando o tipo de evento “mensal” ocorre. Agora vamos disparar alguns
eventos:

paper.daily();
paper.daily();
paper.daily();
papel.mensal();

Todas essas publicações chamam os métodos apropriados de joe e o resultado no console é:


Basta ler as grandes notícias de hoje
Basta ler as grandes notícias de hoje
Basta ler as grandes notícias de hoje
Prestes a adormecer lendo esta interessante análise

A parte boa aqui é que o objeto de papel não codifica joe e joe não codifica papel. Também não
há objeto mediador que saiba tudo. Os objetos participantes são fracamente acoplados e, sem
modificá-los, podemos adicionar muito mais assinantes ao papel; também joe pode cancelar a
inscrição a qualquer momento.

Vamos dar um passo adiante neste exemplo e também fazer de joe um editor. (Afinal, com blogs
e microblogs qualquer um pode ser um editor.) Assim, Joe se torna um editor e pode postar
atualizações de status no Twitter:
makePublisher(joe);
joe.tweet = function (msg)
{ this.publish(msg);
};

Agora imagine que o departamento de relações públicas do jornal decida ler o que seus leitores
twittam e se inscreve no joe, fornecendo o método readTweets():
paper.readTweets = function (tweet) '
{ alert('Chamar grande reunião! Alguém + tweet);
};
joe.subscribe(paper.readTweets);

Agora, assim que Joe twitta, o jornal é alertado:


joe.tweet("odiei o jornal hoje");

O resultado é um alerta: “Convoque grande reunião! Alguém odiou o jornal hoje.

Você pode ver o código-fonte completo e jogar no console com uma demonstração ao vivo em
http:// jspatterns.com/ book/ 7/ observer.html .

174 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Exemplo #2: O jogo Keypress Vamos


dar outro exemplo. Implementaremos o mesmo jogo de pressionamento de tecla do exemplo do
padrão mediador, mas desta vez usando o padrão observador. Para torná-lo um pouco mais
avançado, vamos aceitar um número ilimitado de jogadores, não apenas dois. Ainda teremos o
construtor Player() que cria objetos jogador e o objeto placar . Apenas o mediador agora se
tornará um objeto do jogo .

No padrão mediador, o objeto mediador conhece todos os outros objetos participantes e chama
seus métodos. O objeto do jogo no padrão observador não fará isso; em vez disso, deixará que
os objetos assinem eventos interessantes. Por exemplo, o placar se inscreverá no evento
“scorechange” do jogo .

Vamos primeiro revisitar o objeto de editor genérico e ajustar um pouco sua interface para
aproximá-lo do mundo do navegador:

• Em vez de publish(), Subscribe() e unsubscribe(), teremos fire(), on() e


remover().
• O tipo de evento será usado o tempo todo, então ele se torna o primeiro argumento para o
três funções.

• Um contexto extra pode ser fornecido além da função do assinante para permitir que o método
de retorno de chamada use isso referindo-se ao seu próprio objeto.

O novo objeto publicador torna-se:


var editor =
{ assinantes:
{ qualquer: []
},
em: função (tipo, fn, contexto) { tipo
= tipo || 'qualquer'; fn
= typeof fn === "função" ? fn : contexto[fn];

if (typeof this.subscribers[type] === "indefinido")


{ this.subscribers[type] = [];

} this.subscribers[type].push({fn: fn, context: context || this});


},
remove: function (type, fn, context)
{ this.visitSubscribers('unsubscribe', type, fn, context);
},
fire: function (tipo, publicação)
{ this.visitSubscribers('publicar', tipo, publicação);
},
visitSubscribers: função (ação, tipo, argumento, contexto) {
var pubtype = tipo || 'qualquer',
assinantes = this.subscribers[pubtype], i,

max = assinantes ? assinantes.comprimento : 0;

for (i = 0; i < max; i += 1) { if


(ação === 'publicar') {

Observador | 175
Machine Translated by Google

assinantes[i].fn.call(assinantes[i].contexto, arg); } else


{ if
(assinantes[i].fn === arg && assinantes[i].contexto === contexto)
{ assinantes.splice(i, 1);
}
}
}
}
};

O novo construtor Player() se torna: function


Player(nome, chave) { this.points
= 0; this.name =
nome; this.key =
chave;
this.fire('newplayer', this);
}

Player.prototype.play = function ()
{ this.points += 1;
this.fire('play', this);
};

A novidade aqui é que o construtor aceita a tecla, a tecla do teclado que o jogador pressiona para marcar
pontos. (As chaves foram codificadas antes.) Além disso, toda vez que um novo objeto de jogador é
criado, um evento “newplayer” é acionado. Da mesma forma, toda vez que um jogador joga, o evento
“play” é disparado.

O objeto placar permanece o mesmo; ele simplesmente atualiza a exibição com o atual
pontuação.

O novo objeto do jogo pode acompanhar todos os jogadores, para que possa produzir uma pontuação e
disparar um evento de “mudança de pontuação”. Ele também se inscreverá em todos os eventos
“keypress” do navegador e saberá sobre as teclas que correspondem a
cada jogador: var game = {

chaves: {},

addPlayer: function (player)


{ var key = player.key.toString().charCodeAt(0);
this.keys[chave] = jogador;
},

handleKeypress: function (e) {


e = e || janela.evento; // IE if
(jogo.chaves[e.qual]) {
game.keys[e.which].play();
}
},

handlePlay: function (player)


{ var
i, players = this.keys,

176 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

pontuação = {};

for (i in players) { if
(players.hasOwnProperty(i))
{ score[players[i].name] = players[i].points;
}

} this.fire('scorechange', score);
}
};

A função makePublisher() que transforma qualquer objeto em uma editora ainda é a mesma do
exemplo do jornal. O objeto do jogo torna-se um publicador (para que possa disparar eventos de
“mudança de pontuação”) e o Player.protoype torna-se um publicador para que cada objeto de
jogador possa disparar eventos “play” e “newplayer” para quem decidir ouvir:
makePublisher(Player.prototype);
makePublisher(jogo);

O objeto do jogo se inscreve nos eventos “play” e “newplayer” (e também “keypress” do navegador),
enquanto o placar se inscreve em “scorechange”:
Player.prototype.on("newplayer", "addPlayer", game);
Player.prototype.on("play", "handlePlay", jogo);
game.on("scorechange", placar.atualizar, placar);
window.onkeypress = game.handleKeypress;

Como você vê aqui, o método on() permite que os assinantes especifiquem o retorno de chamada
como uma referência a uma função (scoreboard.update) ou como uma string ("addPlayer"). A string
funciona desde que o contexto (por exemplo, jogo) também seja fornecido.

A parte final da configuração é criar objetos de jogador dinamicamente (com suas teclas
correspondentes a serem pressionadas), quantos o usuário desejar:
var nome do jogador,
chave; while
(1) { playername = prompt("Adicionar jogador
(nome)"); if (!
playername) { break;

} while (1)
"
{ key = prompt("Chave + nome do jogador + "?");
para if
(chave) { break;
}

} new Player(nome do jogador, chave);


}

E isso conclui o jogo. Você pode ver o código completo e reproduzi-lo em http:// jspatterns.com/ book/
7/ observer-game.html .

Observe que na implementação do padrão mediador, o objeto mediador tinha que saber sobre todos
os outros objetos para chamar os métodos corretos no momento certo e coordenar todo o jogo. Aqui
o objeto do jogo é um pouco mais burro e depende dos objetos

Observador | 177
Machine Translated by Google

observar certos eventos e agir: por exemplo, o placar ouvindo o evento “scorechange”. Isso resulta em um
acoplamento ainda mais flexível (quanto menos um objeto souber, melhor) ao preço de tornar um pouco mais
difícil acompanhar quem ouve qual evento. Neste jogo de exemplo, todas as assinaturas aconteceram no
mesmo lugar no código, mas à medida que um aplicativo cresce, as chamadas on() podem estar em todo
lugar (por exemplo, no código de inicialização de cada objeto). Isso tornará mais difícil de depurar, pois não
haverá um único lugar para olhar o código e entender o que está acontecendo. No padrão observador, você
se afasta da execução de código sequencial processual, onde começa do início e pode seguir o programa até
o fim.

Resumo
Neste capítulo, você aprendeu sobre vários padrões de design populares e viu como poderia abordar sua
implementação em JavaScript. Os padrões de design que discutimos
eram:

Singleton
Criando apenas um objeto de uma “classe”. Vimos várias abordagens se você quiser substituir a ideia
de uma classe por uma função construtora e preservar a sintaxe semelhante ao Java. Caso contrário,
tecnicamente todos os objetos em JavaScript são singletons. E também, às vezes, os desenvolvedores
diriam “singleton”, significando objetos criados com o padrão de módulo.

Fábrica
Um método que cria objetos do tipo especificado como uma string em tempo de execução.
Iterador
Fornecer uma API para percorrer e navegar por dados personalizados complexos
estrutura.
Decorador
Ajustando objetos em tempo de execução, adicionando funcionalidade de objetos decoradores
predefinidos.

Estratégia
Mantendo a mesma interface enquanto seleciona a melhor estratégia para lidar com a tarefa específica
(contexto).

Façade
Fornecer uma API mais conveniente agrupando métodos comuns (ou mal projetados) em um novo.

Proxy
Wrapping de um objeto para controlar o acesso a ele, com o objetivo de evitar operações dispendiosas
seja agrupando-os ou realizando-os somente quando realmente necessário.

178 | Capítulo 7: Padrões de Projeto


Machine Translated by Google

Mediador
Promove acoplamento fraco fazendo com que seus objetos não “conversem” diretamente entre si,
mas apenas por meio de um objeto mediador.
Observer
Acoplamento livre criando objetos “observáveis” que notificam todos os seus observadores quando
um evento interessante ocorre (também chamado de assinante/editor ou “eventos personalizados”).

Resumo | 179
Machine Translated by Google
Machine Translated by Google

CAPÍTULO 8

DOM e padrões de navegador

Nos capítulos anteriores do livro, o foco principal foi o núcleo do JavaScript (ECMAScript) e não tanto os
padrões de uso do JavaScript no navegador. Por outro lado, este capítulo explora vários padrões específicos
do navegador, porque esse é o ambiente mais comum para programas JavaScript. O script do navegador
também é o que a maioria das pessoas tem em mente quando diz que não gosta de JavaScript. E isso é
compreensível, dados todos os objetos de host inconsistentes e implementações de DOM que existem nos
navegadores. É óbvio que podemos nos beneficiar de quaisquer boas práticas que ajudem a tornar o script
do cliente menos trabalhoso.

Neste capítulo, você verá alguns padrões divididos em várias áreas, incluindo script DOM, manipulação de
eventos, script remoto, estratégias para carregar JavaScript na página e etapas para implantar JavaScript em
sites de produção.

Mas primeiro, vamos começar com uma discussão breve e um tanto filosófica sobre como abordar o script do
lado do cliente.

Separação de preocupações
As três principais preocupações no desenvolvimento de aplicativos da web são:

Contente
O documento HTML

Apresentação
Os estilos CSS que especificam a aparência do documento
Comportamento

O JavaScript, que lida com a interação do usuário e quaisquer alterações dinâmicas no documento

Manter as três preocupações tão separadas quanto possível melhora a entrega do aplicativo a uma vasta
gama de agentes de usuário — navegadores gráficos, navegadores somente de texto, tecnologia assistiva
para usuários com deficiência, dispositivos móveis e assim por diante. A separação de preocupações também
anda de mãos dadas com a ideia de aprimoramento progressivo - você começa com o

181
Machine Translated by Google

experiência básica (somente HTML) para os agentes de usuário mais simples e adicione mais à
experiência à medida que os recursos do agente de usuário melhoram. Se o navegador suportar CSS,
o usuário obtém uma melhor apresentação do documento. Se o navegador oferecer suporte a
JavaScript, o documento se tornará mais um aplicativo que adiciona mais recursos para aprimorar a
experiência.

Na prática, a separação de interesses significa:

• Testar a página com CSS desativado para ver se a página ainda pode ser usada e o conteúdo está
presente e legível • Testar
com JavaScript desativado e garantir que a página ainda possa realizar seu objetivo principal, todos
os links funcionam (sem instâncias de href = "#"), e todos os formulários ainda funcionam e são
enviados corretamente
• Não usar manipuladores de eventos inline (como onclick) ou atributos de estilo inline , porque eles
não pertencem à camada de conteúdo
• Usando elementos HTML semanticamente significativos, como cabeçalhos e listas

A camada JavaScript (o comportamento) deve ser discreta, o que significa que não deve atrapalhar o
usuário, não deve tornar a página inutilizável em navegadores não suportados e não deve ser um
requisito para o funcionamento da página. Em vez disso, deve apenas aprimorar a página.

Uma técnica comum para lidar com as diferenças do navegador com elegância é a detecção de
capacidade. Ele sugere que você não deve usar o sniffing do agente do usuário para determinar um
caminho de código, mas deve verificar se um método ou uma propriedade que deseja usar existe no
ambiente atual. O sniffing do agente do usuário geralmente é considerado um antipadrão. Às vezes é
inevitável, mas deve ser o último recurso e apenas nos casos em que a detecção de capacidade não
pode fornecer um resultado conclusivo (ou causará um impacto significativo no desempenho):
// antipadrão if
(navigator.userAgent.indexOf('MSIE') !== ÿ1)
{ document.attachEvent('onclick', console.log);
}

// melhor
if (document.attachEvent)
{ document.attachEvent('onclick', console.log);
}

// ou ainda mais específico


if (typeof document.attachEvent !== "undefined")
{ document.attachEvent('onclick', console.log);
}

A separação de preocupações também ajuda no desenvolvimento, na manutenção e na facilidade de


atualizações de um aplicativo da Web existente, porque você sabe onde procurar quando algo quebra.
Quando há um erro de JavaScript, você não precisa consultar o HTML ou o CSS para
consertá-lo.

182 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

Script DOM
Trabalhar com a árvore DOM de uma página é uma das tarefas mais comuns no JavaScript do lado
do cliente. Essa também é uma das principais causas de dores de cabeça (e dá má fama ao
JavaScript), porque os métodos DOM são implementados de forma inconsistente nos navegadores.
É por isso que usar uma boa biblioteca JavaScript, que abstrai as diferenças do navegador, pode
acelerar significativamente o desenvolvimento.

Vejamos alguns padrões recomendados ao acessar e modificar a árvore DOM, abordando


principalmente questões de desempenho.

Acesso DOM

O acesso ao DOM é caro; é o gargalo mais comum quando se trata de desempenho do JavaScript.
Isso ocorre porque o DOM geralmente é implementado separadamente do mecanismo JavaScript.
Do ponto de vista de um navegador, faz sentido adotar essa abordagem, porque um aplicativo
JavaScript pode não precisar de DOM. E também linguagens diferentes do JavaScript (por exemplo,
VBScript no IE) podem ser usadas para trabalhar com o DOM da página.
O ponto principal é que o acesso DOM deve ser reduzido ao mínimo. Isso significa:

• Evitar o acesso DOM em loops •


Atribuir referências DOM a variáveis locais e trabalhar com os locais • Usar API de
seletores quando disponível • Armazenar
em cache o comprimento ao iterar sobre coleções HTML (consulte o Capítulo 2)

Considere o seguinte exemplo onde o segundo (melhor) loop, apesar de ser mais longo, será
dezenas a centenas de vezes mais rápido, dependendo do navegador: //
antipadrão for
(var i = 0; i < 100; i += 1)
{ document.getElementById("resultado").innerHTML += i + ", ";
}

// melhor - atualiza uma variável local var


i, content = ""; for (i =
0; i < 100; i += 1) { conteúdo += i +
",";

} document.getElementById("resultado").innerHTML += conteúdo;

No próximo trecho, o segundo exemplo (usando um estilo de variável local) é melhor, embora exija
mais uma linha de código e mais uma variável: // antipattern var
padding =
document.getElementById("result").style.padding, margin =
document.getElementById("resultado").style.margin;

// melhor
estilo var = document.getElementById("resultado").style,

Script DOM | 183


Machine Translated by Google

preenchimento = estilo.padding,
margem = estilo.margem;

Usar APIs do seletor significa usar os métodos:

document.querySelector("ul .selected");
document.querySelectorAll("#widget .class");

Esses métodos aceitam uma string de seletor CSS e retornam uma lista de nós DOM que
correspondem à seleção. Os métodos seletores estão disponíveis em navegadores modernos (e no
IE desde a versão 8) e sempre serão mais rápidos do que se você mesmo fizer a seleção usando
outros métodos DOM. Versões recentes de bibliotecas JavaScript populares aproveitam as APIs do
seletor, portanto, certifique-se de usar uma versão atualizada de sua biblioteca preferida.

Também ajudará se você adicionar atributos id="" aos elementos que acessará com frequência,
porque document.getElementById(myid) é a maneira mais fácil e rápida de encontrar um nó.

Manipulação de DOM

Além de acessar os elementos DOM, muitas vezes você precisa alterá-los, remover alguns deles ou
adicionar novos. A atualização do DOM pode fazer com que o navegador repinte a tela e também
reflua (recalcule a geometria dos elementos), o que pode ser caro.

Novamente, a regra geral é ter menos atualizações de DOM, o que significa fazer alterações em
lote e executá-las fora da árvore de documentos “ao vivo”.

Quando você precisar criar uma subárvore relativamente grande, deverá fazê-lo sem adicionar ao
documento ativo até o final. Para isso, você pode usar um fragmento de documento para conter
todos os seus nós.

Veja como não anexar nós:


// antipadrão //
anexando nós à medida que são criados

var p, t;

p = document.createElement('p'); t =
document.createTextNode('primeiro parágrafo');
p.appendChild(t);
document.body.appendChild(p);

p = document.createElement('p'); t =
document.createTextNode('segundo parágrafo');
p.appendChild(t);
document.body.appendChild(p);

Uma versão melhor será criar um fragmento de documento, atualizá-lo “offline” e adicioná-lo ao
DOM ativo quando estiver pronto. Quando você adiciona um fragmento de documento à árvore
DOM, o conteúdo do fragmento é adicionado, não o próprio fragmento. E isso é realmente

184 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

conveniente. Portanto, o fragmento de documento é uma boa maneira de agrupar vários nós, mesmo
quando você não os contém em um pai adequado (por exemplo, seus parágrafos não estão em um
elemento div ) .

Aqui está um exemplo de uso de um fragmento de documento:

var p, t, frag;

frag = document.createDocumentFragment();

p = document.createElement('p'); t
= document.createTextNode('primeiro parágrafo');
p.appendChild(t);
frag.appendChild(p);

p = document.createElement('p'); t
= document.createTextNode('segundo parágrafo');
p.appendChild(t);
frag.appendChild(p);

document.body.appendChild(frag);

Neste exemplo, o documento ativo é atualizado apenas uma vez, causando um único refluxo/repintura, em
vez de um para cada parágrafo, como no caso do fragmento de antipadrão anterior.

O fragmento de documento é útil quando você adiciona novos nós à árvore. Mas quando você atualiza uma
parte existente da árvore, ainda pode fazer alterações em lote. Você pode fazer um clone da raiz da
subárvore que está prestes a alterar, fazer todas as alterações no clone e, quando terminar, trocar o original
pelo clone: var oldnode = document.getElementById(' result '), clone =
oldnode.cloneNode(true);

// trabalhar com o clone...

// quando terminar:
oldnode.parentNode.replaceChild(clone, oldnode);

Eventos
Outra área de script do navegador que está cheia de inconsistências e uma fonte de frustração é trabalhar
com os eventos do navegador, como clicar, passar o mouse e assim por diante. Novamente, uma biblioteca
JavaScript pode tirar muito do trabalho duplo que precisa ser feito para suportar o IE (antes da versão 9) e
as implementações em conformidade com o W3C.

Vamos examinar os pontos principais, porque nem sempre você está usando uma biblioteca existente para
páginas simples e hacks rápidos, ou pode estar criando sua própria biblioteca.

Eventos | 185
Machine Translated by Google

Manipulação de

eventos Tudo começa com a anexação de ouvintes de eventos aos elementos. Digamos que você tenha um
botão que aumenta um contador cada vez que você clica nele. Você pode adicionar um atributo onclick
embutido e isso funcionará em todos os navegadores, mas estará violando a separação de preocupações e o
aprimoramento progressivo. Portanto, você deve se esforçar para anexar o ouvinte em Java Script, fora de
qualquer marcação.

Digamos que você tenha a seguinte marcação:

<button id="clickme">Clique em mim: 0</button>


Você pode atribuir uma função à propriedade onclick do nó, mas só pode fazer isso
uma vez:

// solução abaixo do ideal


var b = document.getElementById('clickme'), count
= 0;
b.onclick = função ()
{ contagem
"
+= 1; b.innerHTML = "Clique em+mim:
contar;
};
Se você deseja que várias funções sejam executadas ao clicar, não pode fazê-lo com esse padrão mantendo o
acoplamento solto. Tecnicamente, você pode verificar se onclick já contém uma função e, em caso afirmativo,
adicionar o existente à sua própria função e substituir o valor onclick por sua nova função. Mas uma solução
muito mais limpa é usar o método addEventListener() . Este método não existe no IE até e incluindo a versão
8, então você precisa de attachEvent() para esses navegadores.

Quando examinamos o padrão de ramificação de tempo de inicialização (Capítulo 4), você viu um exemplo de
implementação de uma boa solução para definir um utilitário de escuta de eventos entre navegadores.
Sem entrar em todos os detalhes agora, vamos apenas anexar um ouvinte ao nosso botão:

var b = document.getElementById('clickme'); if
(document.addEventListener) { // W3C
b.addEventListener('click', myHandler, false);
} else if (document.attachEvent) { // IE
b.attachEvent('onclick', myHandler); } else
{ // último recurso b.onclick
= myHandler;
}
Agora quando este botão for clicado, a função myHandler() será executada. Vamos fazer com que esta função
incremente o número no rótulo do botão “Click me: 0”. Para torná-lo um pouco mais interessante, vamos supor
que temos vários botões e um único myHandler() para todos eles. Manter uma referência para cada nó de
botão e um contador para o número será ineficiente, já que podemos obter essa informação do objeto de evento
que é criado a cada clique.

Vamos ver a solução primeiro e comentá-la depois:

186 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

função meuHandler(e) {

var src, partes;

// obtém evento e elemento fonte


e = e || janela.evento;
src = e.target || e.srcElement;

// trabalho real: atualização do


rótulo parts = src.innerHTML.split(":
"); partes[1] = parseInt(partes[1], 10) + 1;
src.innerHTML = partes[0] + ": " + partes[1];

// sem bolhas
if (typeof e.stopPropagation === "function")
{ e.stopPropagation();

} if (typeof e.cancelBubble !== "indefinido") {


e.cancelBubble = verdadeiro;
}

// previne a ação padrão if


(typeof e.preventDefault === "function")
{ e.preventDefault();

} if (typeof e.returnValue !== "indefinido")


{ e.returnValue = false;
}

Um exemplo ao vivo está disponível em http:// jspatterns.com/ book/ 8/ click.html.


Existem quatro partes na função do manipulador de eventos:

• Primeiro, precisamos obter acesso ao objeto de evento, que contém informações sobre o evento e
o elemento da página que acionou esse evento. Esse objeto de evento é passado para o
manipulador de eventos de retorno de chamada, mas não ao usar a propriedade onclick , que
pode ser acessada por meio da propriedade global window.event . • A
segunda parte é fazer o trabalho real de atualização do rótulo. • O próximo
passo é cancelar a propagação do evento. Isso não é necessário neste exemplo específico, mas, em
geral, se você não fizer isso, o evento borbulhará até a raiz do documento ou até mesmo o objeto
da janela. Novamente, precisamos fazer isso de duas maneiras: da maneira padrão do W3C
(stopPropagation()) e de maneira diferente para o IE (usando cancelBubble).

• Finalmente, impeça a ação padrão, se for necessário. Alguns eventos (clicar em um link, enviar um
formulário) têm ações padrão, mas você pode evitá-los usando preventDefault() (ou para IE,
definindo returnValue como false).

Como você pode ver, há bastante trabalho duplicado envolvido, então faz sentido criar seu utilitário de
evento com métodos de fachada, conforme discutido no Capítulo 7.

Eventos | 187
Machine Translated by Google

Delegação de eventos

O padrão de delegação de eventos se beneficia do borbulhamento de eventos e reduz o número de


ouvintes de eventos conectados a nós separados. Se houver 10 botões dentro de um elemento div ,
você poderá ter um ouvinte de evento anexado ao div em vez de 10 ouvintes anexados a cada botão.

Vamos dar um exemplo com três botões dentro de um div (veja a Figura 8-1). Uma demonstração ao
vivo do exemplo de delegação de evento está disponível em http:// jspatterns.com/ book/ 8/ click-
delegate .html.

Figura 8-1. Exemplo de delegação de evento: três botões que incrementam seus rótulos ao clicar

Então, estamos trabalhando com a seguinte marcação:

<div id="click-wrap">
<button>Clique em mim: 0</
button> <button>Clique em mim
também: 0</button> <button>Clique em
mim três: 0</button> </div>

Em vez de anexar ouvintes a cada botão, você anexa um ao div “click-wrap”.


Então você pode usar a mesma função myHandler() do exemplo anterior com apenas uma pequena
mudança: você tem que filtrar os cliques que não lhe interessam. o mesmo div pode ser ignorado.

A mudança para myHandler() seria para verificar se o nodeName da fonte do evento é um “botão”:

// ... //
obtém evento e elemento fonte e
= e || janela.evento; src
= e.target || e.srcElement;

if (src.nodeName.toLowerCase() !== "botão")


{ return;

} // ...

A desvantagem da delegação de eventos é um pouco mais de código para filtrar os eventos que
acontecem no contêiner que não são interessantes para você. Mas os benefícios — desempenho e
código mais limpo — superam significativamente as desvantagens, por isso é um padrão altamente
recomendado.

As bibliotecas JavaScript modernas facilitam o uso da delegação de eventos, fornecendo APIs


convenientes. Por exemplo, YUI3 tem o método Y.delegate(), que permite especificar um seletor CSS
para corresponder ao wrapper e outro seletor para corresponder aos nós nos quais você está
interessado.

188 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

na verdade, nunca seja chamado quando o evento acontecer fora dos nós de sua preferência. Neste
caso, o código para anexar um event listener seria simplesmente:
Y.delegate('click', myHandler, "#click-wrap", "button");
E graças à abstração de todas as diferenças do navegador feitas no YUI e ao fato de que a origem
do evento é determinada por nós, a função de retorno de chamada será muito mais simples: function
myHandler(e) {

var src = e.currentTarget,


partes;

partes = src.get('innerHTML').split(": ");


partes[1] = parseInt(partes[1], 10) + 1;
src.set('innerHTML', partes[0] + ": " + partes[1]);

e.halt();
}

Um exemplo ao vivo está disponível em: http:// jspatterns.com/ book/ 8/ click-y-delegate.html.

Scripts de longa duração


Você notou que às vezes o navegador reclama que um script está sendo executado por muito tempo
e pergunta ao usuário se o script deve ser interrompido. Você não quer que isso aconteça em seu
aplicativo, por mais complicada que seja sua tarefa.

Além disso, se o script trabalhar muito, a interface do usuário do navegador deixará de responder a
ponto de o usuário não conseguir clicar em nada. Isso é ruim para a experiência e deve ser evitado.

Em JavaScript não há threads, mas você pode simulá-los no navegador usando setTimeout() ou,
em navegadores mais modernos, web workers.

setTimeout()
A ideia é dividir uma grande quantidade de trabalho em partes menores e executar cada parte com
um tempo limite de 1 ms. O uso de blocos de tempo limite de 1 ms pode fazer com que a tarefa seja
concluída mais lentamente, mas a interface do usuário permanecerá responsiva e o usuário ficará
mais confortável e no controle do navegador.

O tempo limite de 1 (ou mesmo 0 milissegundos) será mais do que isso, dependendo do
navegador e do sistema operacional. Definir um tempo limite de 0 não significa
imediatamente, mas sim “o mais rápido possível”. No Internet Explorer, por exemplo, o
menor “tick” do relógio é de 15 milissegundos.

Scripts de longa duração | 189


Machine Translated by Google

Trabalhadores da Web

Navegadores recentes oferecem outra solução para scripts de execução longa: web workers. Web
workers fornecem suporte a threads em segundo plano no navegador. Você coloca seus cálculos
pesados em um arquivo separado, por exemplo, my_web_worker.js, e então o chama do programa
principal (página) assim:
var ww = new Worker('my_web_worker.js');
ww.onmessage = function (event)
{ document.body.innerHTML "
+= "<p>mensagem do thread em segundo plano:+ event.data + "</p>";
};

A fonte de um web worker que faz uma operação aritmética simples 1e8 vezes (1 com 8 zeros) é
mostrada aqui:

var final = 1e8, tmp = 1;

postMessage('olá');

while (fim) { fim


-= 1; tmp
+= fim; if
(end === 5e7) { // 5e7 é a metade de 1e8
'
postMessage('no meio do caminho, `tmp` é agora + tmp);
}
}

postMessage('tudo feito');

O trabalhador usa postMessage() para se comunicar com o chamador e o chamador se inscreve no


evento onmessage para receber atualizações. O callback onmessage recebe um objeto de evento
como argumento e esse objeto contém os dados da propriedade, que podem ser qualquer coisa que
o worker queira passar. Da mesma forma, o chamador pode passar dados para o trabalhador usando
(neste exemplo) ww.postMessage() e o trabalhador pode assinar essas mensagens usando um
retorno de chamada onmessage .

O exemplo anterior será impresso no navegador:


mensagem do thread em segundo plano: olá mensagem
do thread em segundo plano: no meio do caminho, `tmp` agora é 3749999975000001
mensagem do thread em segundo plano: tudo pronto

Scripts remotos Os

aplicativos da Web de hoje geralmente usam scripts remotos para se comunicar com o servidor sem
recarregar a página atual. Isso permite aplicativos da Web muito mais responsivos e semelhantes a
desktops. Vamos considerar algumas maneiras de se comunicar com o servidor do JavaScript.

190 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

XMLHttpRequest

XMLHttpRequest é um objeto especial (uma função construtora) disponível na maioria dos


navegadores atualmente, que permite fazer uma solicitação HTTP a partir do JavaScript. Existem
três etapas para fazer uma solicitação:

1. Configure um objeto XMLHttpRequest (chamado de XHR para abreviar).

2. Forneça uma função de retorno de chamada para ser notificado quando o objeto de solicitação mudar de estado.

3. Envie a solicitação.

O primeiro passo é tão fácil quanto:

var xhr = new XMLHttpRequest();

Mas no IE antes da versão 7, a funcionalidade XHR foi implementada como um objeto ActiveX, então um caso especial
é necessário lá.

A segunda etapa é fornecer um retorno de chamada para o evento readystatechange :

xhr.onreadystatechange = handleResponse;

A última etapa é disparar a solicitação — usando dois métodos open() e send(). O método open() configura o método
de solicitação HTTP (por exemplo, GET, POST) e a URL. O método send() passa qualquer dado POST ou apenas
uma string em branco no caso de GET. O último parâmetro para open() especifica se a solicitação é assíncrona.

Assíncrono significa que o navegador não bloqueará a espera pela resposta. Esta é
definitivamente a melhor experiência do usuário, portanto, a menos que haja uma forte razão
contra isso, o parâmetro assíncrono deve ser
sempre true: xhr.open("GET",
"page.html", true); xhr.enviar();

Abaixo está um exemplo funcional completo de busca do conteúdo de uma nova página e atualização da página atual
com o novo conteúdo (demo disponível em http:// jspatterns.com/ book/ 8/ xhr.html ):

var i, xhr, activeXids =


[ 'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];

if (typeof XMLHttpRequest === "função") { // XHR nativo


xhr = new XMLHttpRequest(); }
else { // IE antes de 7 for
(i = 0; i < activeXids.length; i += 1) {
tente
{ xhr = new ActiveXObject(activeXids[i]);

quebrar; } pegar (e) {}


}
}

Script Remoto | 191


Machine Translated by Google

xhr.onreadystatechange = função () { if
(xhr.readyState !== 4) { return
false;

} if (xhr.status !== 200) {


"
alert("Erro, código de status: + xhr.status);
return false;

} document.body.innerHTML += "<pre>" + xhr.responseText + "<\/pre>";


};

xhr.open("GET", "page.html", true);


xhr.enviar("");

Alguns comentários sobre o exemplo:

• Por causa das versões 6 e anteriores do IE, o processo de criação de um novo objeto XHR é um
pouco mais complicado. Percorremos uma lista de identificadores ActiveX da versão mais recente
para a mais antiga e tentamos criar um objeto, envolvendo-o em um bloco try-catch.

• A função callback verifica a propriedade readyState do objeto xhr . Existem cinco valores possíveis
dessa propriedade – de 0 a 4 – onde 4 significa “completo”. Se o estado ainda não estiver
completo, continuamos esperando pelo próximo evento readystatechange . • O retorno de
chamada também verifica a propriedade de status do objeto xhr . Esta propriedade corresponde ao
código de status HTTP, por exemplo, 200 (OK) ou 404 (Não encontrado).
Estamos interessados apenas nos 200 códigos de resposta e relatamos todos os outros como
erros (para simplificar; caso contrário, há outros códigos de status válidos que você pode
verificar). • O código, conforme listado, fará uma verificação da maneira suportada de criar um objeto
XHR toda vez que uma solicitação for feita. Tendo visto alguns padrões nos capítulos anteriores
(por exemplo, ramificação em tempo inicial), você pode reescrever isso para fazer a verificação apenas
uma vez.

JSONPGenericName

JSONP (JSON com preenchimento) é outra maneira de fazer solicitações remotas. Ao contrário do
XHR, ele não é restrito pela política de navegador de mesmo domínio, portanto, deve ser usado com
cuidado devido às implicações de segurança do carregamento de dados de sites de terceiros.

A resposta a uma solicitação XHR pode ser qualquer tipo de documento:

• Documentos XML (historicamente) •


Blocos HTML (bastante comuns) • Dados
JSON (leve e conveniente) • Arquivos de texto
simples e outros

Com JSONP, os dados geralmente são JSON agrupados em uma chamada de função, em que o nome
da função é fornecido com a solicitação.

192 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

Um exemplo de URL de solicitação JSONP normalmente seria assim:

http:// example.org/ getdata.php?callback=myHandler

getdata.php pode ser qualquer tipo de página ou script. O parâmetro callback especifica qual
função JavaScript irá lidar com a resposta.

A URL é então carregada em um elemento <script> dinâmico , assim:


var script = document.createElement("script");
script.src = url;
document.body.appendChild(script);

O servidor responde com alguns dados JSON passados como parâmetro para a função callback.
O resultado final é que você realmente incluiu um novo script na página, que é uma chamada de
função. Por exemplo:
meuHandler({"olá": "mundo"});

Exemplo de JSONP:

jogo da velha Vamos colocar o JSONP para funcionar com um exemplo—um jogo de jogo da
velha, onde os jogadores são o cliente (o navegador) e o servidor. Ambos gerarão números
aleatórios entre 1 e 9, e usaremos JSONP para obter o valor do turno do servidor (consulte a Figura 8-2).

Você pode jogar o jogo ao vivo em http:// jspatterns.com/ book/ 8/ ttt.html.

Figura 8-2. Desafio JSONP do jogo da velha

Existem dois botões: para iniciar um novo jogo e para obter a vez do servidor (a vez do cliente
será automática após um tempo limite):
<button id="new">Novo jogo</button>
<button id="server">Jogo no servidor</button>

Script Remoto | 193


Machine Translated by Google

A placa conterá nove células de tabela com atributos id correspondentes . Por exemplo:
<td id="cell-1">&nbsp;</td>
<td id="cell-2">&nbsp;</td>
<td id="cell-3">&nbsp;</td>
...

Todo o jogo é implementado em um objeto ttt global:


var ttt = { //
células jogadas até
agora jogadas: [],

// get
abreviado: function
(id) { return document.getElementById(id);
},

// configura a
configuração de
cliques: function () { this.get('new').onclick =
this.newGame; this.get('servidor').onclick = this.remoteRequest;
},

// limpa o tabuleiro
newGame: function ()
{ var tds = document.getElementsByTagName("td"),
max = tds.length, i;

for (i = 0; i < max; i += 1) {


tds[i].innerHTML = "&nbsp;";

} ttt.played = [];
},

// faz uma
solicitação remoteRequest:
function () { var script = document.createElement("script");
script.src = "server.php?callback=ttt.serverPlay&played=" + ttt.played.join(',');
document.body.appendChild(script);
},

// callback, vez do servidor jogar


serverPlay: function (data) { if
(data.error)
{ alert(data.error);
retornar;

} dados = parseInt(dados,
10); this.played.push(dados);

this.get('célula-' + dados).innerHTML = '<span class="servidor">X<\/span>';

setTimeout(function ()
{ ttt.clientPlay(); },
300); // como se estivesse pensando muito

194 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

},

// vez do cliente jogar


clientPlay: function () { var
data = 5;

if (this.played.length === 9)
{ alert("Fim do
jogo"); retornar;
}

// continue apresentando números aleatórios


de 1 a 9 // até que uma célula não tomada
seja encontrada while (this.get('cell-' + data).innerHTML !==
"&nbsp;") { data = Math.ceil( Math.random() * 9);

} this.get('célula-' + dados).innerHTML = 'O';


this.played.push(dados);

};

O objeto ttt mantém uma lista de células tocadas até o momento em ttt.played e as envia para o
servidor, para que o servidor retorne um novo número excluindo as que já foram tocadas. Se ocorrer
um erro, o servidor responderá com uma saída como: ttt.serverPlay({"error":
"Descrição do erro aqui"});

Como você pode ver, o callback em JSONP tem que ser uma função pública e globalmente
disponível, não necessariamente global, mas pode ser um método de um objeto global. Se não
houver erros, o servidor responderá com uma chamada de método como:
ttt.serverPlay(3);
Aqui 3 significa que a célula número 3 é a escolha aleatória do servidor. Neste caso os dados são
tão simples que nem precisamos do formato JSON; basta um único valor.

Quadros e beacons de imagem

Uma forma alternativa de fazer scripts remotos é usar quadros. Com JavaScript, você pode criar um
iframe e alterar sua URL src . A nova URL pode conter dados e chamadas de função que atualizam
o chamador — a página pai fora do iframe.

A forma mais simples de script remoto é quando tudo o que você precisa fazer é enviar dados para
o servidor e não espera uma resposta. Nesses casos, você pode criar uma nova imagem e apontar
seu src para o script no servidor:
new Image().src = "http://example.org/some/page.php";

Esse padrão é chamado de beacon de imagem e é útil quando você deseja enviar dados para serem
registrados pelo servidor, por exemplo, para coletar estatísticas de visitantes. Porque você não tem

Script Remoto | 195


Machine Translated by Google

usar para uma resposta de tal farol, uma prática comum (mas um antipadrão) é fazer com que o
servidor responda com uma imagem GIF 1 × 1. Uma opção melhor será responder com uma resposta
HTTP “204 No Content”. Isso significa que apenas um cabeçalho e nenhum corpo de resposta é
enviado de volta ao cliente.

Implantando JavaScript
Existem algumas considerações de desempenho quando se trata de servir JavaScript. Vamos discutir
os mais importantes em alto nível. Para todos os detalhes, consulte Sites de alto desempenho e sites
ainda mais rápidos, ambos publicados pela O'Reilly.

Combinando Scripts A

primeira regra ao criar páginas de carregamento rápido é ter o menor número possível de componentes
externos, porque as solicitações HTTP são caras. Quando se trata de JavaScript, isso significa que
você pode acelerar significativamente o tempo de carregamento da página combinando arquivos de
script externos.

Digamos que sua página esteja usando a biblioteca jQuery. Este é um arquivo .js . Então você também
está usando alguns plug-ins jQuery, que também vêm em um arquivo separado. Dessa forma, você
pode terminar rapidamente com 4 ou 5 arquivos antes de escrever uma única linha. Faz sentido
combinar esses arquivos em um, especialmente considerando que alguns deles serão pequenos (2 a
3 Kb) e a sobrecarga do HTTP custará mais tempo do que o download real. Combinar os scripts
significa simplesmente criar um novo arquivo e colar o conteúdo de cada um.

É claro que essa concatenação de arquivos deve acontecer apenas um pouco antes de o código ir
para produção e não no desenvolvimento, onde tornará a depuração mais dolorosa.

As desvantagens de combinar arquivos de script são:

• É mais um passo antes de ir ao ar, mas pode ser facilmente automatizado e feito em
a linha de comando, por exemplo, usando cat no Linux/Unix:
$ cat jquery.js jquery.quickselect.js jquery.limit.js > all.js • Perder alguns
dos benefícios do armazenamento em cache — quando você faz uma pequena alteração em um dos
arquivos, invalida todo o pacote. É por isso que é bom ter um cronograma de lançamento para
projetos maiores ou considerar ter dois pacotes: um que contém arquivos que devem ser alterados
e um pacote “principal” que quase não muda. • Você precisa criar algum padrão de
nomenclatura e versão para o pacote, como usar um carimbo de data/hora: all_20100426.js ou um
hash do conteúdo do arquivo.

As desvantagens podem ser resumidas principalmente como uma inconveniência, mas o benefício
vale a pena.

196 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

Minificação e compactação No Capítulo

2, falamos sobre minificação de código. É importante fazer com que o processo de minificação também faça parte
do processo de ativação da compilação.

Quando você pensa do ponto de vista do usuário, não há razão para que ele tenha que baixar todos os comentários
em seu código, que não servem para o modo como o aplicativo funciona.

O benefício da minificação pode ser diferente dependendo de quão generosamente você usa comentários e
espaços em branco, e também das ferramentas específicas de minificação que você usa. Mas, em média, você
observaria cerca de 50% de redução no tamanho do arquivo.

Servir o arquivo de script compactado também é algo que você sempre deve fazer. É uma configuração de servidor
única e simples para ativar a compactação gzip e oferece uma aceleração instantânea. Mesmo se você estiver
usando um provedor de hospedagem compartilhada que não lhe dá muita liberdade em termos de configuração do
servidor, a maioria dos provedores pelo menos permite que você use os arquivos de configuração .htaccess do
Apache. Portanto, em sua raiz da web, adicione isto ao arquivo .htaccess :

AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml


application/javascript application/json

A compactação fornecerá arquivos 70% menores, em média. Combinando compactação com minificação, você
pode esperar que seus usuários baixem apenas 15% do tamanho do arquivo do código-fonte não compactado e
não minificado que você escreveu.

Expires Header
Ao contrário da crença popular, os arquivos não ficam muito tempo no cache do navegador.
Você pode fazer sua devida diligência e aumentar as chances de ter seus arquivos no cache para visitas repetidas
usando um cabeçalho Expires .

Novamente, esta é uma configuração de servidor única que você pode fazer em .htaccess:

Expira Ativo Em
ExpiresByType application/x-javascript "acesso mais 10 anos"

A desvantagem é que, se você quiser alterar o arquivo, também precisará renomeá-lo, mas provavelmente já estará
fazendo isso se tiver estabelecido uma convenção de nomenclatura para seus pacotes de arquivos combinados.

Usando um
CDN CDN significa Content Delivery Network. Este é um serviço de hospedagem pago (às
vezes bastante caro) que permite distribuir cópias de seus arquivos em diferentes centros de
dados ao redor do mundo e disponibilizá-los mais rapidamente para seus usuários, mantendo
o mesmo URL em seu código.

Implantando JavaScript | 197


Machine Translated by Google

Mesmo que você não tenha um orçamento para CDN, ainda pode se beneficiar de algumas opções gratuitas:

• O Google hospeda várias bibliotecas populares de código aberto, às quais você pode vincular para
gratuitamente e se beneficie de seu CDN.

• A Microsoft hospeda jQuery e suas próprias bibliotecas Ajax. • Yahoo!

hospeda a biblioteca YUI em seu CDN.

Estratégias de Carregamento

À primeira vista, como você inclui um script em uma página da Web parece uma pergunta direta — você usa um
elemento <script> e inline o código JavaScript ou vincula a um arquivo separado no atributo src :

// opção 1
<script>
console.log("olá mundo");
</
script> //
opção 2 <script src="external.js"></script>

Mas há mais alguns padrões e considerações dos quais você deve estar ciente quando seu objetivo é criar aplicativos
da web de alto desempenho.

Como observação, existem alguns atributos comuns que os desenvolvedores tendem a usar com o elemento
<script> :

idioma="JavaScript"
Em muitas formas de capitalizar “JavaScript” e às vezes com um número de versão.
O atributo language não deve ser usado, porque está implícito que o idioma é JavaScript. O número da versão
não funciona tão bem e é considerado um erro em retrospecto. digite="texto/javascript"

Este atributo é exigido pelos padrões HTML4 e XHTML1, mas não deveria ser, porque os navegadores
assumem JavaScript de qualquer maneira. O HTML5 está tornando isso um tributo não obrigatório. Além de
satisfazer os validadores de marcação, não há outro motivo para usar o atributo type .

adiar
(E melhor ainda, o assíncrono do HTML5 ) também é uma maneira, embora não amplamente suportada, de
especificar que o download do arquivo de script externo não deve bloquear o restante da página. Mais sobre
bloqueio a seguir.

198 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

O lugar do elemento <script> Os elementos


do script bloqueiam downloads progressivos de páginas. Os navegadores baixam vários
componentes de uma vez, mas quando encontram um script externo, eles interrompem os
downloads até que o arquivo do script seja baixado, analisado e executado. Isso prejudica o
tempo geral da página, especialmente se acontecer várias vezes durante o carregamento da página.

Para minimizar o efeito de bloqueio, você pode colocar o elemento script no final da página, logo antes
da tag de fechamento </body> . Desta forma não haverá outros recursos para o script bloquear. O
restante dos componentes da página será baixado e já envolverá o usuário.

O pior antipadrão é usar arquivos separados no cabeçalho do documento:


<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> <!-- ANTIPATTERN
--> <script src="jquery.js"></script>
<script src="jquery.quickselect .js"></script> <script
src="jquery.lightbox.js"></script> <script
src="myapp.js"></script> </head>
<body>

...
</body>
</html>

Uma opção melhor é combinar todos os arquivos:


<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> <script src="all_20100426.js"></script>
</head>
<body>
...
</body>
</html>

E a melhor opção é colocar o script combinado bem no final da página:


<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> </
head> <body>
...
<script src="all_20100426.js"></script> </
body>
</html>

Estratégias de Carregamento | 199


Machine Translated by Google

HTTP Chunking O

protocolo HTTP suporta a chamada codificação em partes. Ele permite que você envie a página
em pedaços. Portanto, se você tiver uma página complicada, não precisará esperar que todo o
trabalho do lado do servidor seja concluído antes de começar a enviar o cabeçalho mais ou menos
estático da página.

Uma estratégia simples é enviar o conteúdo <head> da página com o primeiro bloco
enquanto o restante da página está sendo montado. Em outras palavras, você pode ter algo assim:
<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> </
head> <!-- fim da parte #1
--> <body>
...
<script src="all_20100426.js"></script>
</
body>
</html> <!-- fim da parte #2 -->

Uma melhoria simples seria também mover o JavaScript de volta para <head> e servi-lo
com o primeiro pedaço. Dessa forma, o navegador começa a baixar o arquivo de script
enquanto o restante da página ainda não está pronto no lado do servidor:
<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> <script src="all_20100426.js"></script>
</head>
<!-- fim da parte #1 -->
<corpo>
...
</
body>
</html> <!-- fim da parte #2 -->

Uma opção ainda melhor seria ter um terceiro bloco, que contém apenas o script na parte
inferior da página. Você também pode enviar uma parte do corpo com a primeira parte se
tiver um cabeçalho um tanto estático no topo de cada página:
<!doctype html>
<html>
<head>
<title>Meu aplicativo</
title> </
head>
<body> <div id="header">
<img src="logo.png" />
...
</div>
<!-- fim da parte #1 -->

200 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

... O corpo inteiro da página ...

<!-- fim da parte #2 -->


<script src="all_20100426.js"></script> </
body>
</html>
<!-- fim da parte #3 -->

Essa abordagem se encaixa bem no espírito de aprimoramento progressivo e Java Script discreto. Logo após
o final do segundo bloco de HTML, você deve ter uma página completamente carregada, exibida e utilizável,
como se o JavaScript estivesse desabilitado no navegador.
Então, quando o JavaScript chega com o terceiro bloco, ele aprimora a página, adicionando todos os sinos e
assobios.

Elemento dinâmico <script> para downloads sem bloqueio Como já mencionado,

o JavaScript bloqueia o download dos arquivos que o seguem. Mas vários padrões permitem que você evite
isso:

• Carregar o script com uma solicitação XHR e então eval() como uma string. Essa abordagem sofre de
restrições de mesmo domínio e também envolve eval(), que é um antipadrão de si mesmo. • Usando
atributos defer e async , mas

eles não funcionam em todos os navegadores. • Usando um elemento <script> dinâmico .

O último é um padrão bom e factível. Semelhante ao que você viu com JSONP, você cria um novo elemento
de script, define seu src e o anexa à página.

Este é um exemplo que carregará um arquivo JavaScript de forma assíncrona sem bloquear o restante dos
downloads:

var script = document.createElement("script");


script.src = "all_20100426.js";
document.documentElement.firstChild.appendChild(script);

A desvantagem desse padrão é que você não pode ter nenhum outro elemento de script que siga esse padrão
se eles dependerem do carregamento do .js principal. O .js principal é carregado de forma assíncrona, portanto
não há garantia de quando ele chegará, e os scripts que vêm depois podem assumir objetos que ainda não
foram definidos.

Para resolver essa desvantagem, você pode fazer com que todos os scripts embutidos não sejam executados
imediatamente, mas sejam coletados como funções em uma matriz. Então, quando o script principal chega, ele
pode executar todas as funções coletadas na matriz do buffer. Portanto, existem três etapas para alcançar isso.

Primeiro, crie um array para armazenar todo o código inline, o mais cedo possível na página:

var meunamespace
= { inline_scripts: []
};

Estratégias de Carregamento | 201


Machine Translated by Google

Em seguida, você precisa agrupar todos os scripts embutidos individuais em uma função e anexar
cada função ao array inline_scripts . Em outras palavras:
// era: //
<script>console.log("Estou embutido");</script>

// torna-se:
<script>
mynamespace.inline_scripts.push(function ()
{ console.log("Estou

embutido"); }); </script>

E a última etapa é fazer com que seu script principal percorra o buffer de scripts embutidos e execute
todos eles:

var i, scripts = mynamespace.inline_scripts, max = scripts.length; for (i


= 0; i < max; max += 1) { scripts[i]
();
}

Anexando o elemento <script>

Normalmente, os scripts são anexados ao <head> do documento, mas você pode anexá-los a
qualquer elemento, incluindo o corpo (como no exemplo JSONP).

No exemplo anterior usamos documentElement para anexar ao <head>, porque documentElement é


o <html> e seu primeiro filho é o <head>:

document.documentElement.firstChild.appendChild(script);

Isso também é comumente escrito


como: document.getElementsByTagName("head")[0].appendChild(script);

Isso é bom quando você controla a marcação, mas e se estiver criando um widget ou um anúncio e
não tiver ideia de que tipo de página irá hospedá-lo? Tecnicamente, você pode não ter <head> e
nenhum <body> na página; embora document.body certamente funcione mesmo sem uma tag
<body> :
document.body.appendChild(script);

Mas, na verdade, há uma tag que sempre existirá na página em que seu script é executado - uma tag
de script. Se não houvesse tag de script (para um arquivo interno ou externo), seu código não seria
executado. Para usar esse fato, você pode inserirBefore() o primeiro elemento de script disponível na
página:
var first_script = document.getElementsByTagName('script')[0];
first_script.parentNode.insertBefore(script, first_script);

Aqui, first_script é o elemento de script que com certeza estará na página e script é o novo elemento
de script que você cria.

202 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

Carregamento lento

A técnica conhecida como carregamento lento refere-se ao carregamento de um arquivo externo após o evento de
carregamento da página . Muitas vezes é benéfico dividir um grande pacote de código em duas partes:

• Uma parte necessária para inicializar a página e anexar manipuladores de eventos ao


elementos da interface do usuário

• Uma segunda parte que só é necessária após a interação do usuário ou outras condições

O objetivo é carregar a página progressivamente e dar ao usuário algo com que trabalhar o mais rápido possível.
Em seguida, o restante pode ser carregado em segundo plano enquanto o usuário está envolvido e olhando ao
redor da página.

A maneira de carregar a segunda parte do JavaScript é simplesmente usar um elemento de script dinâmico
anexado ao cabeçalho ou ao corpo:

... O corpo inteiro da página ...

<!-- fim do bloco #2 -->


<script src="all_20100426.js"></script>
<script>
window.onload = function ()
{ var script = document.createElement("script");
script.src = "all_lazy_20100426.js";
document.documentElement.firstChild.appendChild(script);
};
</script>
</body>
</html>
<!-- fim da parte #3 -->

Para muitos aplicativos, a parte preguiçosa do código geralmente será maior do que a parte principal, porque a
“ação” interessante (como arrastar e soltar, XHR e animações) ocorre somente depois que o usuário a inicia.

Carregamento sob demanda

O padrão anterior carregava JavaScript adicional incondicionalmente após o carregamento da página,


assumindo que o código provavelmente será necessário. Mas podemos fazer melhor e carregar apenas
partes do código e apenas as partes que são realmente necessárias?

Imagine que você tenha uma barra lateral na página com diferentes guias. Clicar em uma guia faz uma solicitação
XHR para obter conteúdo, atualiza o conteúdo da guia e anima a atualização esmaecendo a cor. E se esse for o
único lugar na página em que você precisa de suas bibliotecas XHR e de animação, e se o usuário nunca clicar
em uma guia?

Insira o padrão de carga sob demanda. Você pode criar uma função ou método require() que receba um nome de
arquivo de um script a ser carregado e uma função de retorno de chamada a ser executada quando o script
adicional for carregado.

Estratégias de Carregamento | 203


Machine Translated by Google

A função require() pode ser usada assim:


require("extra.js", function ()
{ functionDefinedInExtraJS(); });

Vamos ver como você pode implementar tal função. Solicitar o script adicional é simples — basta
seguir o padrão do elemento <script> dinâmico . Descobrir quando o script é carregado é um
pouco mais complicado devido às diferenças do navegador:
function require(arquivo, retorno de chamada) {

var script = document.getElementsByTagName('script')[0],


newjs = document.createElement('script');

// IE
newjs.onreadystatechange = function () { if
(newjs.readyState === 'carregado' || newjs.readyState === 'completo')
{ newjs.onreadystatechange = null;
ligar de volta();
}
};

// outros
newjs.onload = function ()
{ callback();
};

newjs.src =
arquivo; script.parentNode.insertBefore(newjs, script);
}

Alguns comentários sobre esta implementação:

• No IE, você se inscreve no evento readystatechange e procura um readyState “loaded” ou


“complete”. Todos os outros navegadores irão ignorar isso. • No
Firefox, Safari e Opera, você se inscreve no evento load por meio do onload
propriedade.

• Esta abordagem não funciona no Safari 2. Se este navegador for um requisito, para fazê-lo
funcionar você terá que configurar um intervalo para verificar periodicamente se uma variável
especificada (que você define no arquivo adicional) foi definiram. Quando for definido,
significa que o novo script foi carregado e executado.

Você pode testar essa implementação criando um script atrasado artificialmente (para simular a
latência da rede), chamado ondemand.js.php, por exemplo:
<?
php header('Tipo de conteúdo: aplicativo/javascript');
dormir(1); ?
>
function extraFunction(logthis) {
console.log('carregado e executado');
console.log(logthis);
}

204 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

Agora, testando a função require() :


require('ondemand.js.php', function () {
extraFunction('carregado da página pai');
document.body.appendChild(document.createTextNode('pronto!')); });

Este snippet gravará duas linhas no console e atualizará a página dizendo “pronto!”
Você pode ver o exemplo ao vivo em http:// jspatterns.com/ book/ 7/ ondemand.html.

Pré-carregamento do

JavaScript No padrão de carregamento lento e no padrão sob demanda, pós-carregamos os scripts exigidos
pela página atual. Além disso, você também pode carregar scripts que não são necessários na página atual,
mas na página que provavelmente virá a seguir. Dessa forma, quando o usuário chegar à segunda página,
ele já terá o script pré-carregado e a experiência geral se tornará muito mais rápida.

O pré-carregamento pode ser implementado simplesmente usando o padrão de script dinâmico. Mas isso
significa que o script será analisado e executado. Embora a análise apenas acrescente ao tempo total gasto
no pré-carregamento, a execução também pode causar erros de JavaScript quando o script pré-carregado
assume que está sendo executado na segunda página e, por exemplo, espera encontrar determinados nós
DOM.

É possível carregar scripts sem analisá-los e executá-los; isso também funciona para CSS e imagens.

No IE, você pode fazer uma solicitação com o conhecido padrão de beacon de imagem:
new Image().src = "preloadme.js";

Em todos os outros navegadores, você pode usar um <object> em vez de um elemento de script e definir
seu atributo data para apontar para a URL do script: var obj
= document.createElement('object'); obj.data =
"preloadme.js";
document.body.appendChild(obj);

Para evitar que o objeto fique visível, você também deve definir seus atributos de largura e altura como 0.

Você pode criar uma função ou método preload() de uso geral e também usar o padrão de ramificação de
tempo init (Capítulo 4) para lidar com as diferenças do navegador:
var pré-
carga; if (/*@cc_on!@*/false) { // IE sniffing com comentários
condicionais preload =
function (file) { new Image().src = file;

}; } else
{ preload = function (file)
{ var obj = document.createElement('object'),
body = document.body;

Estratégias de Carregamento | 205


Machine Translated by Google

obj.width = 0;
obj.height = 0;
obj.data =
arquivo; body.appendChild(obj);
};
}

Usando a nova função:

preload('my_web_worker.js');

A desvantagem desse padrão é a presença de sniffing do agente do usuário, mas isso não pode ser evitado
porque, nesse caso, a detecção de capacidade não nos diz o suficiente sobre o comportamento do navegador.
Nesse padrão, por exemplo, teoricamente você pode testar se typeof Image é uma “função” e usar isso ao invés
do sniffing. No entanto, isso não ajudará aqui, porque todos os navegadores suportam new Image(); apenas
alguns têm um cache separado para imagens, o que significa que o pré-carregamento de componentes como
uma imagem não será usado como um script do cache na segunda página, mas será baixado novamente.

O sniffing do navegador usando comentários condicionais é interessante por si só. É um


pouco mais seguro do que procurar strings em navigator.userAgent, porque essas strings
são fáceis de alterar pelo usuário.

Tendo isso:

var isIE = /*@cc_on!@*/false;

irá definir isIE como falso em todos os navegadores (porque eles ignoram o comentário),
mas será verdadeiro no Internet Explorer, por causa da negação ! no comentário
condicional. É como se o IE visse:

var isIE = !false; // verdadeiro

O padrão de pré-carregamento pode ser usado para todos os tipos de componentes, não apenas scripts. É útil,
por exemplo, em páginas de login. Quando o usuário começar a digitar seu nome de usuário, você pode usar
esse tempo de digitação para iniciar o pré-carregamento (nada sensível, é claro), porque é provável que o usuário
acabe na segunda página de login.

Resumo
Considerando que os capítulos anteriores do livro cobriram principalmente padrões básicos de JavaScript,
independente do ambiente, este focou em padrões aplicáveis apenas no
ambiente do navegador do lado do cliente.

Nós olhamos para:

• As ideias de separação de preocupações (HTML: conteúdo, CSS: apresentação, Java Script: comportamento),
JavaScript discreto e detecção de capacidade versus detecção de navegador. (Embora no final do capítulo
você tenha aprendido como quebrar esse padrão.)

206 | Capítulo 8: DOM e padrões de navegador


Machine Translated by Google

• Scripting de DOM — padrões para acelerar o acesso e a manipulação de DOM, principalmente agrupando
as operações de DOM porque tocar no DOM sempre resulta em
um custo.

• Eventos, tratamento de eventos entre navegadores e uso de delegação de eventos para reduzir o número
de ouvintes de eventos e melhorar o desempenho. • Dois padrões

para lidar com casos de cálculos pesados de execução longa — usando set Timeout() para dividir operações
longas em pedaços menores e usando web workers em navegadores modernos.

• Vários padrões para script remoto e comunicação entre servidor e


cliente—XHR, JSONP, frames e beacons de imagem.

• Etapas para implantar o JavaScript no ambiente de produção — certificando-se de que os scripts sejam
combinados em menos arquivos, minificados e compactados com gzip (economia total de 85%) e,
idealmente, hospedados em um CDN e enviados com o cabeçalho Expires para

melhorar o armazenamento em cache . • Padrões para incluir os scripts em uma página para melhor
desempenho, incluindo: vários locais para colocar o elemento <script> , enquanto também se beneficia do fragmento HTTP.
Além disso, para reduzir o "impacto" inicial de carregar um script grande, analisamos vários padrões,
como carregamento lento, pré-carregamento e carregamento sob demanda do JavaScript.

Resumo | 207
Machine Translated by Google
Machine Translated by Google

Índice

Um objeto de ativação, 3 call () método, 136, 137 funções


métodos addEventListener(), 186 funções de retorno de chamada

anônimas, 58 antipadrões ouvintes de eventos assíncronos e, 66


definidos, 2 definidos, 62
strings de bibliotecas e, 67
passagem, 67 sniffing parênteses e, 63 scripts
de agente do usuário, 182 remotos, 192 padrão de
documentação de API, 30, 34 padrão sandbox e, 105 considerações
de retorno de de escopo, 64–65 tempos limites e,
chamada de padrões de API, 66 exemplo de uso,
62–67 objetos de configuração, 77–78 63, 64 convenção camel
currying, 81–83 case, 28 detecção de capacidade,
funções de retorno, 67 método 182 instrução catch, 53
apply(), 136, 137
argumentos.propriedade callee, 46, 77 CDN (Content Delivery Network), padrão de
Sintaxe literal de array encadeamento 197, codificação
de construtor de matriz, 47 em partes 110–111, tag @class
método isArray(), 48 strings 200–201 (YUIDoc), 33 classes
de repetição e, 48 operador
typeof e, 48 exemplo de uso, imitando, 128-130
46 , 47 notações literais de JavaScript e, 4 novos
matrizes, 46 a 48 matrizes, operadores e, 115
emprestadas de, 137 ouvintes de construtores clássicos de
eventos assíncronos, 66 atributos empréstimo de herança, 120–123 padrão
padrão, 117–120 resultados
definido, 3 esperados, 116 modernos
elementos de script, 198, 201 versus, 115 aluguel e
protótipo definido, 123–124 protótipos
B de compartilhamento, 124
construtores temporários, 125–128
bookmarklets, 72
Closure Compiler (Google), 36, 74
Construtor booleano, 52
construtores
Tipo de valor booleano, 52
de empréstimo de reutilização de código, 120–123

Gostaríamos de ouvir suas sugestões para melhorar nossos índices. Envie um e-mail para [email protected].

209
Machine Translated by Google

métodos de empréstimo, 136–139 D


propriedades de cópia e, 133–135
Construtor de data, 41
padrão de herança padrão, 117–120
depuradores, propriedade de nome e, 59, 60
resultados de herança esperados, 116
padrão de decorador, 151–155
importância de, 115
operador de exclusão,
padrões de herança e, 115 função
12 dependências, declaração, 90–91, 97
klass(), 128–130 padrão de
implementando
mistura e, 135 prototípico
JavaScript sobre, 196
herança, 130–133 alugar e definir
CDN e, 197
protótipo, 123–124 compartilhar
combinando scripts, 196
protótipos, 124 construtores
Expira cabeçalho, 197
temporários, 125–128 convenções de
minificação e compactação, 197
codificação chaves,
padrões de projeto
24–26 recuo, 24
sobre, 2, 141
espaços em
decorador, 151–155
branco, 26, 27 padrões
fábrica, 146–148
de codificação, definidos, 2
comentários fachada, 158
iterador, 149–150
processo de minificação, 36
mediador, 167–171
escrita, 30
observador, 171–178
objetos de configuração, 77, 78
proxy, 159–167
objeto de console, 6, 66
singleton , 141–146
declaração const, 109
estratégia, 155–158
objeto constante, 109, 110
funções de construtor método dir(), 7
elemento div, 188
personalizadas,
fragmentos de documento, 184, 185
42–44 herança e, 116
coleção de
invocação, 28
formulários de objeto
privacidade e, 92
de documento, 16 método
propriedade do construtor,
getElementById(), 184 método
4 @constructor tag (YUIDoc), 34
getElementsByClassName(), 15 método
construtores, 53
getElementsByName(), 15
(consulte também construtores
getElementsByTagName
específicos) padrão de herança clássico, 120–123,
125– 128 () método, 15 coleção de imagens, 16 coleção de links, 16
API DOM, 111
criando objetos de, 41, 100
Script DOM, 183–185
implementando, 104
herança e, 117
convenções de nomenclatura, E
28, 45 redefinindo Padrão ECMAScript
ponteiros, 127 sobre, 5
valores de retorno, 43 padrão convenção camel case, 29
de sandbox, 101– objetos nativos, 3
103 auto-invocação, 46 esta palavra-chave e, 43, 95 herança prototípica, 132 modo
Rede de entrega de conteúdo (CDN), 197 estrito, 5, 46, 77
Crockford, Douglas, 6, 112 notificação por e-mail, 36
chaves (convenção de codificação), 24–26 enumerações, definidas, 17
Curry, Haskell, 81 ambientes, objetos de host e, 5
processo de curry, 81–83 Construtor de erro, 53

210 | Índice
Machine Translated by Google

evitação do como objetos, 57


método eval(), 21– propriedade de protótipo, 4, 19
23 falhas de privacidade e, 93 fornecendo escopo, 58
delegação de eventos, 188 valores de retorno e, 43, 67
padrão de fachada autodefinição, 68
de manipulação de eventos e, visão geral da terminologia, 58
158 padrão de proxy e, 162
scripts e, 186–187 padrão de G
retorno de
objetos globais
chamada de ouvintes de
acessando, 13
eventos e, 66 scripts e, 186
definidos, 10
Utilitário de eventos (Biblioteca YUI2), 36
padrão de namespace, 88
Expira cabeçalho, 197
esta palavra-chave e, 44
propriedade de janela e, 10, 13
variáveis globais
F padrão de fachada, acessando, 13
158 padrão de fábrica, 146–148 definidos, 10
loops for, 15–17 elevando e, 14
loops for-in, 17–19 padrão de função imediata e, 70 importando
barras, 51 quadros e em módulos, 101 minimizando, 10–
beacons de imagem, 195 aplicativos de 15 namespaces e, 11,
função, 79, 80 88 considerações de
Construtor de funções portabilidade, 12 problemas com, 11,
método bind(), 138 strings 12 considerações de escopo,
de passagem para, 22 58 padrão de var único, 13, 14
exemplos de uso, 58 declaração de var e, 12
declarações de função
definidas, 59 Google Closure Compiler, 36, 74
expressões de função versus, 59, 60 elevação compactação gzip, 197
e, 61 ponto e vírgula
e, 59 expressões de H
função definidas, 58
método hasOwnProperty(), 17 elemento
declarações
principal, 200
de função versus, 59, 60 padrão de função
Heilmann, Christian, 96 hoisting,
imediata , 69 ponto e vírgula e, 59 literais
14, 61, 62 objetos de
de função, 59 funções,
host, 4, 5 arquivo de
62 (ver também tipos
configuração htaccess, 197
específicos de
Objeto HTMLCollection, 15, 16
funções) construtores como, 28, 42–44
Fragmentação HTTP, 200–201
processo de currying, 81–83
variáveis de declaração, 10
documentação, 30 hoisting
e, 14, 61, 62 imediato, I iframes, 195
69–73, 97 escopo de beacons de imagem, 195
gerenciamento, 10 padrão funções imediatas
de memoização, 76 sintaxe alternativa, 70
propriedade de nome, 59, 60 benefícios e uso, 72
convenções de nomenclatura, bookmarklets e, 72 definidos,
28 69

Índice | 211
Machine Translated by Google

padrão de módulo e, 97 método parse(), 22, 49 método


parâmetros, 70 stringify(), 50
parênteses e, 71 valores JSON (JSONP com preenchimento), 192–195
de retorno, 71, 72
inicialização imediata do objeto, 73, 74 criação
global implícita, 12
Eventos de pressionamento de tecla K ,
definida, 11
169, 175–178 função klass(), 128–130
instrução var
e, 12 importação global em
módulos, 101 recuo (convenção de codificação), L

24 herança, 115 (veja também herança clássica) inicialização lenta, 160


reutilização de código técnicas de carregamento lento, 203
e, 115 construtores e, 117 copiando coleções de
propriedades, 133–135 propriedades de
comprimento, 16
objetos de string, 52 bibliotecas
Suporte a JavaScript, 4 padrão de retorno de chamada e
padrões de mixagem, 135 67 classes de emulação, 128–130
múltiplos, 122 listas, padrão decorador e 154–155 padrões literais
prototípicos, 130–133
ramificações de tempo de Construtor de matriz, 46–48
inicialização, 74–76 funções Construtor de objeto, 39–42
imediatas de padrões de inicialização, expressões regulares, 50, 51 evento
69–73 inicialização imediata de objeto, 73, 74 de carregamento,
ramificações de tempo de 66 ramificação de tempo de
inicialização, 74–76 lentos carregamento, 74–76 estratégias de
inicialização, 160 operador carregamento (script) sobre, 198
instanceof, HTTP chunking, 200–201 técnica
129 instâncias em de carregamento lento, 203
fechamentos, 144–146 em carregamento sob demanda, 203–205
propriedades estáticas, 143 padrão iterador, 149–150 pré-carregamento JavaScript, 205, 206
elemento de script, 199, 201–202
variáveis locais, 58
método log(), 7
J utilitário javadoc, 30
scripts de longa duração sobre,
Classes de linguagem
189 método
JavaScript e, 4
setTimeout(), 189 web workers e,
considerações de implantação, 196–198 orientado
190 padrão de mediador de
a objetos, 3 pré-
acoplamento fraco
carregamento, 205, 206
e, 168 padrão de observador e, 171
padrão
de encadeamento jQuery,
111 método parseJSON(), 50
Conjunto de ferramentas JSDoc, 30 M
Ferramenta Martin, Robert, 111 padrão
JSLint mediador, 167–171 padrão de
sobre, 6 padrão de função imediata, 70 memorização, 76, 105 @method tag
recomendações de uso, 37 (YUIDoc), 33 method() method, 112
JSON (JavaScript Object Notation) sobre, 49 métodos

212 | Índice
Machine Translated by Google

empréstimo, 136–139 padrão de fábrica e, 148


padrão de encadeamento, tipo de valor numérico, 52
110 definido, 3,
39 privado, 92– O
97 privilegiado, 93,
objetos constantes, 109, 110
97 propriedade de protótipo e,
Construtor de objetos
95 público, 96, 105–
criando objetos, 41
107 padrão de revelação e,
96 estático, 105– padrão de fábrica e, 148
método toString(), 48
108 objetos de
considerações de uso, 41, 42
string, 52 objetos
padrões de criação de
wrapper, 52 processos
de objetos padrão de
encadeamento, 110–111 declarando
minificação sobre, 36
dependências, 90–91
implantação e, 197
método method(), 112
ferramentas de
padrão de módulo,
suporte, 91 padrão de
mixagem, 97–101 namespaces, 87–90
constantes de objeto, 109, 110 propriedades
135 padrão
e métodos privados, 92–97
de módulo, 97–101 módulos
padrão de revelação, 96, 99
adicionados, 103 construtores de
padrão sandbox, 88, 101–
criação, 100
105 membros
declaradores de dependências,
estáticos, 105–
90, 91, 97 definindo, 97 importando globais em, 101 herança múltipla, 122
108 literais de objeto
sobre, 39–42 privacidade e, 94 ,
95
N propriedade de nome, linguagens orientadas
59, 60 expressões de função a objetos, 3 objetos, 4 (ver também
nomeada, 58 @namespace tag protótipos) métodos de
(YUIDoc), 33 empréstimo,
funcionalidade de 136–139 configuração, 77, 78 criação, 4
namespaces, 87 função de propósito criação a
geral, 89, 97 variáveis globais partir de
e, 11, 88 padrão de módulo construtores, 41,
e, 97 padrões de criação de objeto, 100 definidos, 3 erros,
87–90 desvantagens de 53 funções
padrão, 101 convenções como, 57 globais, 10 , 13, 44, 88
de nomenclatura padrões host, 4, 5 inicialização
adicionais, 29 para imediata, 73,
construtores, 28, 45 74 método
palavras de separação, init(), 73, 74 modificação, 3 nativo,
28 objetos 3, 39 padrão de criação
nativos, 3, 39 preferencial, 41 wrapper
novas classes de operador e, primitivo, 52 membros
115 padrões de imposição, privados,
44–46 padrão singleton 92, 97 cópias
e, 142 notificação, e-mail, 36 valor nulo tipo, 52 rasas , 133
Construtor numérico cordas, 52
criando objetos wrapper, 52 arremessos, 53 tipos de, 3 padrões de observação sobre, 171

Índice | 213
Machine Translated by Google

exemplo de jogo de pressionamento de propriedade de protótipo e, 95


tecla, 175–178 exemplo de assinatura de revista, 171–174 estáticos, 105–108
atributo onclick, 186 padrões objetos de string, 52
de otimização, 74–76 objetos wrapper, 52 tags
@property (YUIDoc), 34 herança
prototípica, 130–133 propriedade de
P
protótipo adicionando
@param tag (YUIDoc), 33
propriedades e métodos, 95 aumentando, 19
propriedade _parent_ (Mozilla Rhino), 93 funções
definidos, 4 padrão
de callback
de herança
entre parênteses e, 63 funções
e, 117–119 construtores temporários e, 125
imediatas e, 71 método parseInt(), 23
protótipos definidos, 4 propriedades de
aplicações parciais, 80–81
filtragem, 17
padrões, 62 (veja também padrões
privacidade
específicos)
e, 95 aluguel e padrão definido,
definidos , 1 imposição de novo,
123–124
44–46
compartilhamento, 124 construtores
identificação, 2 revisões por
proxy, 127
pares
funções proxy, 127 padrão
(desenvolvimento de software), 35 padrões de
proxy sobre, 159
desempenho
considerações de
memoização, 76, 105
cache, 167
funções de autodefinição, 68
uso exemplo, 160–166 padrão de
JavaScript de pré-carregamento, 205,
revelação de métodos públicos
206 objetos wrapper primitivos, 52
e, 96 estáticos, 105–
funções de construtor
107 membros estáticos públicos,
de padrões de privacidade e, 92 literais
105–107
de objeto e, 94, 95 falhas de
privacidade, 93, 94 membros
privados, 92 métodos
privilegiados, 93 protótipos e, R
95 membros privados Construtor RegExp, 50 notação
implementando, 92 literal de expressão regular, 51 scripts remotos
padrão de módulo e, sobre, 190 frames e
97 padrão de revelação e, 96 beacons de
estáticos, 107, 108 métodos imagem, 195
privilegiados JSONP e, 192–195
definidos, 93 padrão de Objeto XMLHttpRequest e, 191, 192 @return tag
módulo e, 97 (YUIDoc), 33 funções de construtor
aprimoramento progressivo, de valores de
181, 201 propriedades definidas, 3, 39 retorno, 43 funções como, 67
operador de funções imediatas,
exclusão e, 12 71, 72 padrão de função de retorno,
objetos de erro, 53 filtragem, 67 padrão de revelação, 96, 99
17 herança por cópia,
133–135
memorização de, 76 padrão de mixagem,
135 privado, 92–97
Padrão de caixa de areia S , 88, 101–105
Schönfinkel, Moisés, 81

214 | Índice
Machine Translated by Google

processo de schönfinkelisation, 81–83 membros estáticos


acréscimo de definido, 105
elemento de script, privado, 107, 108
202 atributos comuns, 198, 201 público, 105–107
dinâmico, 201–202 propriedades estáticas, instâncias em,
funcionalidade, 199 143 padrão de estratégia, 155–
exemplo de uso, 193 158 argumento
eventos de modo estrito.propriedade chamada,
de navegador de script, 185– 46, 77
189 combinação, definido, 5 Construtor de string,
196 considerações de implantação, 196–198 41, 52 tipo de valor de
DOM, 183–185 string, 52 instrução switch,
estratégias de carregamento, 198– 20 Construtor SyntaxError, 53
206 longa execução, 189–
190 remoto, 190–196 T
separação de interesses, 181, 182
construtores temporários, 127
segurança, método eval() e, 21
aquela variável, 45
funções de autodefinição, 68
expressões esta palavra-
chave métodos emprestados e, 137
de função de ponto e vírgula e, 59
métodos de retorno de chamada
Mecanismo de inserção de JavaScript, 26
e, 65 exemplo de
separação de interesses, 181, 182
codificação, 13 construtores
método setInterval(), 22
e, 43, 95 objetos vazios e,
método setTimeout(), 22, 189 padrão
42 objetos globais e, 44
singleton sobre, 141
instrução throw, 53 jogo
instâncias
da velha jogo, 193–195 tempos
em fechamentos, 144–146 instâncias
limite
em propriedades estáticas, 143 novo
padrão de retorno de chamada
operador e, 142
e, 66 scripts e, 189
sniffing, agente de usuário,
@type tag (YUIDoc), 34
182 desenvolvimento de
Construtor TypeError, 53
software aumentando a propriedade do
operador typeof, 48
protótipo, 19 convenções de
codificação, 23–27
loops for, 15–17 loops U
for-in, 17–19 processo de tipos de valores indefinidos, 52
minificação, 36 minimizando
globais, 10–15 convenções de
V
nomenclatura, 28–30
padrão de
parseInt () método, 23
estratégia de validação e,
revisões por pares e,
exemplo de uso 155, 156–158
35 executando JSLint, 37 separação de
instrução var
interesses, 181, 182
delete operador e, 12 for
instruções switch, 20 variáveis
loops e, 16 variáveis
typecasting, 21–23 escrevendo
globais e, 12 considerações
documentação da API, 30–
de elevação, 14 globais implícitos
34 escrevendo comentários, 30
e, 12 recomendações, 11
escrevendo código de
padrão único, 13, 14
manutenção, 9, 10 fonte sistema de controle, 36 notação de colchetes, 21

Índice | 215
Machine Translated by Google

variáveis, 101
(consulte também variáveis
globais)
declarando,
11 definidas,
10 definindo, 3
elevando,
14, 61 locais, 58 convenções
de nomenclatura, 28
considerações de escopo, 58 conversão de tipos, 21–23

web workers, 190


espaços em branco (convenção de codificação), 26, 27,
36 curingas, padrão sandbox e, 102, 105 propriedades
da janela, 10, 13 objetos
wrapper, 52

x
Objeto XMLHttpRequest, 191, 192

YAHOO variável global (YUI2), 90 Yahoo!


YUICompressor, 36 serviço da web
YQL, 164 biblioteca YUI
(Yahoo! User Interface), 31, 127 ferramenta YUIDoc, 30,
31–34

Z
Zakas, Nicolau, 16

216 | Índice
Machine Translated by Google

Sobre o autor
Stoyan Stefanov é um Yahoo! desenvolvedor web, autor de livros (Java Script orientado a objetos),
colaborador de livros (Sites ainda mais rápidos, JavaScript de alto desempenho) e revisor técnico
(JavaScript: The Good Parts, PHP Mashups). Ele fala regularmente sobre JavaScript, PHP e outros tópicos
de desenvolvimento web em conferências e em seu blog (http:// www.phpied.com). Stoyan é o criador da
ferramenta de otimização de imagens smush.it e arquiteto da ferramenta de otimização de desempenho
YSlow 2.0 do Yahoo.

Colofão O

animal na capa de JavaScript Patterns é uma perdiz européia (Perdix per dix), também chamada de
perdiz cinza, perdiz inglesa, perdiz húngara ou perdiz boêmia. Esta ave comum é nativa da Europa e da
Ásia Ocidental, mas foi introduzida na América do Norte e agora é comum em algumas partes do sul do
Canadá e norte dos Estados Unidos.

As perdizes são membros da família dos faisões, Phasianidae. Eles são ninhos terrestres não migratórios
que comem principalmente grãos e sementes. Originalmente residentes de pastagens, eles se adaptaram
e se espalharam com a agricultura humana; eles agora são encontrados com mais frequência perto de
campos cultivados.

As perdizes européias são aves rotundas, parecidas com galinhas (cerca de 30 centímetros de
comprimento), com pescoço e cauda curtos. Eles têm costas marrons, partes inferiores cinzas (com uma
mancha castanha escura na barriga), rostos enferrujados e bicos e pernas opacos. Suas garras, compostas
por 15 a 20 ovos, estão entre as maiores de qualquer ave. Amplamente introduzidas como aves de caça,
as perdizes foram extensivamente caçadas no final de 1800 e início de 1900.

O nome científico da ave vem de Perdix da mitologia grega, sobrinho do inventor Dédalo. Dédalo estava
com ciúmes de seu jovem aluno - creditado por ter inventado a serra, o cinzel, o compasso geométrico e a
roda de oleiro - e aproveitou a oportunidade para empurrá-lo para fora da Acrópole. Atena, simpatizante
do esperto menino, veio em seu socorro e o transformou em uma perdiz, ave que evita alturas e prefere
nidificar no chão.

A imagem da capa é da Johnson's Natural History. A fonte da capa é Adobe ITC Ga ramond. A fonte do
texto é Linotype Birka; a fonte do cabeçalho é Adobe Myriad Condensed; e a fonte do código é
TheSansMonoCondensed da LucasFont.
Machine Translated by Google

Você também pode gostar