JavaScript - Patterns - (2010) PTBR
JavaScript - Patterns - (2010) PTBR
Padrões JavaScript
Machine Translated by Google
Machine Translated by Google
Padrões JavaScript
Stoyan Stefanov
Padrões JavaScript
por Stoyan Stefanov
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].
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
Í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
vii
Machine Translated by Google
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
Índice | ix
Machine Translated by Google
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
Índice | XI
Machine Translated by Google
Í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.
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ê.
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.
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].
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.
Envie comentários e perguntas sobre este livro para a editora: O'Reilly Media, Inc.
Temos uma página web para este livro, onde listamos errata, exemplos e qualquer informação adicional.
Você pode acessar esta página em:
comentar ou fazer perguntas técnicas sobre este livro, envie um e-mail para:
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.
Prefácio | xv
Machine Translated by Google
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:
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 ).
1
Machine Translated by Google
• 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.”
• 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.
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
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.
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:
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
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
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:
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.
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:
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 •
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
Também é comum que as páginas da Web incluam código não escrito pelos desenvolvedores da
página, por exemplo:
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.
• 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:
12 | Capítulo 2: Fundamentos
Machine Translated by Google
// 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.
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.
• 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
// 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;
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:
} função();
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 .
• 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.
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 = [];
// ...
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.
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
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
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:
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]);
}
}
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]);
}
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:
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";
}
• 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.
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
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"
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 }());
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.
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?
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);
// 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:
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
• 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
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
// 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.
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).
À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:
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.
• Usando um sublinhado à direita para significar privado, como em name_ e getElements_() • Usando
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.
E como você verá na próxima seção, os comentários podem ajudá-lo a gerar automaticamente a
documentação.
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
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
};
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.
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.)
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.
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
/**
* 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
}
};
@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
/**
* Nome da pessoa
*
@property first_name
*
@type String
*/
this.first_name = first; /** *
};
/**
* 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
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.
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.
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.
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);}...
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
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:
Resumo | 37
Machine Translated by Google
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.
// 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:
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ê 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:
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:
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.
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:
// um objeto vazio
var o = new Object();
Objeto Literal | 41
Machine Translated by Google
// 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.
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:
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.
Quando você invoca a função construtora com new, acontece o seguinte dentro da função:
// 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:
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);
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.
// 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.
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"
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.
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:
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.
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() {
this.tastes = "gostoso";
}
Waffle.prototype.wantAnother = verdadeiro;
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.
// exatamente o mesmo
array var a = ["itsy", "bitsy", "spider"];
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 .
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
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
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.
Verifique a existência de
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) {
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.
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.
// antipadrão
var data = eval('(' + jstr + ')');
// dados var
preferidos = JSON.parse(jstr);
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"}';
JSON | 49
Machine Translated by Google
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]
};
// jsonstr é agora: //
{"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}
O código de exemplo a seguir demonstra duas maneiras de criar uma expressão regular que
corresponde a uma barra invertida:
// 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.
• g—Correspondência global •
m—Multilinha
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.
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;
}
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.
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 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);
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.
// string primitiva
var greet = "Olá";
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"
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
... 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.
• 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
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.
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.
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
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
}
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ê.
Fundo | 59
Machine Translated by Google
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"
60 | Capítulo 4: Funções
Machine Translated by Google
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.
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() {
// 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.
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
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;
enquanto (i)
{ i -= 1;
// 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";
};
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"; });
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.
var meuaplicativo
= {}; myapp.color =
"verde"; myapp.paint = function (nó) {
node.style.color = this.color;
};
"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:
} // ...
};
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);
//... if
(tipo de retorno de chamada === "função") {
callback.call(callback_obj, encontrado);
} // ...
};
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:
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().
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:
// 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:
// 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!");
};
};
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.
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:
68 | Capítulo 4: Funções
Machine Translated by Google
// chamando como um
método spooky.boo(); //
"Vaia!" spooky.boo(); //
"Vaia!" console.log(spooky.boo.property); // "apropriadamente"
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
• 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 () {
alerta(msg);
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.
// imprime:
// Conheci Joe Black na sexta-feira, 13 de agosto de 2010 23:26:59 GMT-0800 (PST)
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) {
}(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.
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.
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.
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.
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.
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:
}());
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.
({
// aqui você pode definir valores de configuração //
também conhecidos como constantes de
configuração
maxwidth: 600, maxheight: 400,
// 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.
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 ();
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;
};
}
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.
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:
} 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 :
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);
var conf =
{ nome de usuário:
"batman", primeiro:
"Bruce", último: "Wayne"
};
addPessoa(conf);
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.
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: "") + "!";
}
};
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
// e conhecemos os argumentos
add(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
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).
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.
} // retorno completo
da aplicação x + y;
}
// teste
caril | 81
Machine Translated by Google
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.
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;
}
// 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
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.
2. Expressões de funções (iguais às anteriores, mas sem nome), também conhecidas como
funções anônimas 3.
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
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
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
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.
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.
// 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
// 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)
} // 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: {} // // };
}
Padrão de namespace | 89
Machine Translated by Google
pai = pai[partes[i]];
} return pai;
};
// namespace
longo MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');
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:
Este é um padrão extremamente simples, mas ao mesmo tempo tem inúmeros benefícios:
• 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.
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)
*/
/*
corpo test2 minificado:
Declarando Dependências | 91
Machine Translated by Google
var a=MYAPP.modules;alert(a.m1);alert(a.m2);alert(a.m51) */
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';
};
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;
};
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:
especificações.cor = "preto";
especificações.preço = "grátis";
console.dir(toy.getSpecs());
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.
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";
myobj =
{ // método privilegiado
getName: function ()
{ return name;
}
}; }());
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";
}; }());
Este exemplo também é o esqueleto do que é conhecido como “padrão de módulo”, que examinaremos
daqui a pouco.
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.
function Gadget() { //
membro privado
var name = 'iPod'; //
public function
this.getName = function ()
{ return name;
};
Gadget.prototype = (function () { //
membro privado
var browser = "Mobile Webkit"; //
membros do protótipo público
return
{ getBrowser: function ()
{ return browser;
}
}; }());
(função () {
function isArray(a)
{ return toString.call(a) === astr;
}
} return ÿ1;
}
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
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...
}; }());
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
// retorno da
API pública {
retornar verdadeiro;
}
}
},
}; }());
O padrão de módulo é uma maneira amplamente usada e altamente recomendada para organizar seu código,
especialmente à medida que ele cresce.
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.
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
// dependências
var uobj = MYAPP.utilities.object, ulang
= MYAPP.utilities.lang,
// fim da variável
} retorna um;
}
};
// retorna o construtor // a
ser atribuído ao novo namespace return
Constr;
}());
}(MYAPP, este));
• 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.
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.
• 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:
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:
//...
});
});
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 =
{};
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.
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;
// chama o callback
callback(this);
}
• 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.
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
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";
};
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"
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 () {
msg de retorno;
};
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:
// variável estática/propriedade
var counter = 0;
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;
// um método privilegiado
NewGadget.prototype.getLastId = function ()
{ return counter;
};
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.
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 = {
string: 1,
número: 1,
booleano: 1
},
prefix = (Math.random() + "_").slice(2); return
{ conjunto:
função (nome, valor) {
if (this.isDefined(nome)) { return
false;
} 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
padrão de encadeamento
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
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);
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.
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.
• 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.
Veja como você pode usar Person() para criar e usar um novo objeto:
Novamente, observe o padrão de encadeamento em ação, que se tornou possível simplesmente porque
setName() retornou isso.
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
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”.
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
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.
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 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.
É 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"
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).
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.
Finalmente, vamos dar uma olhada em mais uma etapa. Digamos que temos este código:
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.
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.
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.
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'];
alert(article.hasOwnProperty('tags')); // true
alert(blog.hasOwnProperty('tags')); // false
alert(page.hasOwnProperty('tags')); // verdadeiro
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.
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';
}
// função do construtor
filho Child(name)
{ Parent.apply(this, arguments);
}
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.
função Bird()
{ this.wings =
2; this.fly = verdadeiro;
}
function CatWings()
{ Cat.apply(this);
Bird.apply(this);
}
O resultado é mostrado na Figura 6-5. Quaisquer propriedades duplicadas serão resolvidas com
a vitória da última.
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.
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.
};
// função do
construtor filho
Child(name) { Parent.apply(this, arguments);
}
Child.prototype = new Parent();
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
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
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 .
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:
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.
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:
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
apenas mudar seu protótipo. Você pode usar uma função imediata e armazenar a função
proxy em seu encerramento:
} }());
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.
Vamos mudar de assunto aqui e, apenas nesta parte do capítulo, usar a palavra “classe” livremente porque o
} });
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
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):
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"
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;
};
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.
};
// 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();
}
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";
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";
// herdar
var kid = object(Person.prototype);
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
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);
criança.hasOwnProperty("idade"); // verdadeiro
} return filho;
}
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
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
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"
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
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
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).
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.
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"
};
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
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,
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:
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:
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.
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.
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:
• 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.
// 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.
// adicionando novamente ao
protótipo // depois que o objeto inicial é
criado Universe.prototype.everything = true;
Teste: //
apenas o protótipo original foi //
vinculado aos objetos
uni.nada; // verdadeiro
uni2.nothing; //
verdadeiro uni.everything; //
undefined uni2.everything; // indefinido
// 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;
};
// a instância
instance = new Universe();
// toda a funcionalidade
instance.start_time = 0;
instance.bang = "Grande";
instância de retorno;
}
Singleton | 145
Machine Translated by Google
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;
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:
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.
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";
};
Fábrica | 147
Machine Translated by Google
Aqui estão alguns exemplos e testes do comportamento. Observe que Object pode ser chamado
com ou sem new:
// 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.
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:
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 í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
},
}; }());
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 retornar o elemento atual, porque você não pode fazer isso com next() sem avançar o ponteiro
// [recorte...]
retornar {
// [recorte...]
rebobinar: função ()
{ índice = 0;
},
atual: function () { return
dados[índice];
}
}; }());
// volta
agg.rewind();
console.log(agg.current()); // 1
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.
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:
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:
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() .
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:
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');
Decorador | 153
Machine Translated by Google
if (overrides.hasOwnProperty(i))
{ newobj[i] = overrides[i];
}
} return newobj;
};
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 = [];
}
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 = {
} 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.
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.
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"));
}
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"
};
// o método de interface //
`data` é a chave => pares de valores
validam: função (dados) {
// 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.
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) {
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();
} // ...
};
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.
sem procuração
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
manipuladores de eventos
Agora vamos dar uma olhada nos manipuladores de eventos. Primeiro, definimos a função abreviada $ de
conveniência:
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;
} e.returnValue = false;
id = src.href.split('--')[1];
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:
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
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
if (!info)
{ http.makeRequest([id], "videos.updateList");
retornar;
}
}
};
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') ;
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.
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:
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.
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.
// 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");
},
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;
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-5. Usando um proxy para combinar e reduzir o número de viagens de ida e volta ao servidor
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.
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.
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.
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 .
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 = {
// atualiza a exibição da
pontuação update: function (score) {
} 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');
},
scoreboard.update(pontuação);
},
// 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;
Observador
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.
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
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;
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];
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");
}
};
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();
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);
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 .
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:
• 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.
Observador | 175
Machine Translated by Google
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: {},
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;
}
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.
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
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.
• 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);
}
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.
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:
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 + ", ";
}
} 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,
preenchimento = estilo.padding,
margem = estilo.margem;
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.
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
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 ) .
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);
// 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.
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.
função meuHandler(e) {
// sem bolhas
if (typeof e.stopPropagation === "function")
{ e.stopPropagation();
• 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
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
<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>
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;
} // ...
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.
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) {
e.halt();
}
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.
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:
postMessage('olá');
postMessage('tudo feito');
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.
XMLHttpRequest
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.
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á.
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 ):
xhr.onreadystatechange = função () { if
(xhr.readyState !== 4) { return
false;
• 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.
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.
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.
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).
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>
A placa conterá nove células de tabela com atributos id correspondentes . Por exemplo:
<td id="cell-1"> </td>
<td id="cell-2"> </td>
<td id="cell-3"> </td>
...
// 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;
} 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);
},
} dados = parseInt(dados,
10); this.played.push(dados);
setTimeout(function ()
{ ttt.clientPlay(); },
300); // como se estivesse pensando muito
},
if (this.played.length === 9)
{ alert("Fim do
jogo"); retornar;
}
};
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.
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
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.
• É 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.
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 :
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.
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.
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.
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.
...
</body>
</html>
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 -->
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.
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
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:
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: []
};
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
E a última etapa é fazer com que seu script principal percorra o buffer de scripts embutidos e execute
todos eles:
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).
document.documentElement.firstChild.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.
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 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:
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.
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.
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) {
// 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);
}
• 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);
}
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;
obj.width = 0;
obj.height = 0;
obj.data =
arquivo; body.appendChild(obj);
};
}
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.
Tendo isso:
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:
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.
• 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.)
• 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.
• 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
Gostaríamos de ouvir suas sugestões para melhorar nossos índices. Envie um e-mail para [email protected].
209
Machine Translated by Google
210 | Índice
Machine Translated by Google
Índice | 211
Machine Translated by Google
212 | Índice
Machine Translated by Google
Índice | 213
Machine Translated by Google
214 | Índice
Machine Translated by Google
Í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
x
Objeto XMLHttpRequest, 191, 192
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