Padrões de JavaScript por Stoyan Stefanov
Padrões de JavaScript por Stoyan Stefanov
info
Machine Translated by Google [Link]
Machine Translated by Google [Link]
Padrões JavaScript
Machine Translated by Google [Link]
Machine Translated by Google [Link]
Padrões JavaScript
Stoyan Stefanov
Padrões JavaScript
por Stoyan Stefanov
Publicado pela 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. Edições online também estão
disponíveis para a maioria dos títulos (http:// [Link]). Para mais informações, entre em contato com
nosso departamento de vendas corporativas/institucionais: (800) 998-9938 ou corporate@[Link].
Histórico de
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. Padrões
JavaScript, a imagem de uma perdiz europeia e a imagem comercial relacionada são marcas registradas da O'Reilly Media, Inc.
Muitas das designações usadas por fabricantes e vendedores para distinguir seus produtos são reivindicadas como marcas registradas.
Nos casos em que essas designações aparecem neste livro, e a O'Reilly Media, Inc. tinha conhecimento de uma reivindicação de marca
registrada, as designações foram impressas em letras maiúsculas ou com as iniciais maiúsculas.
Embora todas as precauções tenham sido tomadas na preparação deste livro, a editora 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 [Link]
Índice
Prefácio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
1. Introdução . ... 1
Padrões 1
JavaScript: Conceitos 3
Orientado a Objetos 3
Sem aulas 4
Protótipos 4
Ambiente 5
ECMAScript 5 5
JSLint 6
O Console 6
2. Essenciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Escrevendo código sustentável 9
Minimizando Globais 10
O problema com os globais 11
Efeitos colaterais ao esquecer var 12
Acesso ao Objeto Global 13
Padrão de variável única 13
Içamento: Um problema com vars dispersos e 14
loops for-in 15
17
(Não) Aumentando Protótipos Integrados 19
Padrão de troca 20
Evitando a conversão implícita de tipos 21
Evitando eval() 21
Conversões de números com parseInt() 23
Convenções de codificação 23
Recuo 24
Aparelhos Curly 24
vii
Machine Translated by Google [Link]
Espaço em branco 26
Convenções de nomenclatura 28
Construtores de Capitalização 28
Separando Palavras 28
Escrevendo Comentários 30
Exemplo de YUIDoc 31
Minificar…Resumo do JSLint 36
em execução 37
de produção 37
Convenção de Nomenclatura 45
Usando isso 45
Construtor de auto-invocação 46
Literal de matriz 46
Envoltórios primitivos 52
Objetos de erro 53
Resumo 54
4. Funções . ...
Fundo 57
Desambiguação da Terminologia 58
viii | Índice
Machine Translated by Google [Link]
Função de elevação 61
Padrão de retorno de chamada 62
Um exemplo de retorno de chamada 63
Retornos de chamada e escopo 64
Ouvintes de eventos assíncronos 66
Tempos limite 66
Retornos de chamada em bibliotecas 67
Retornando funções 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 de Objetos 73
Ramificação em tempo de inicialização 74
Propriedades de Função — Um Padrão de Memorização 76
Objetos de configuração 77
Caril 79
Aplicação de Função 79
Aplicação parcial 80
Currying 81
Quando usar currying 83
Resumo 84
Índice | ix
Machine Translated by Google [Link]
x | Índice
Machine Translated by Google [Link]
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
Procuração 159
Um exemplo 160
Proxy como um cache 167
Mediador 167
Exemplo de Mediador 168
Observador 171
Exemplo nº 1: Assinaturas de revistas 171
Exemplo nº 2: O jogo do Keypress 175
Resumo 178
Índice | xi
Machine Translated by Google [Link]
Índice . ...
xii | Índice
Machine Translated by Google [Link]
Prefácio
Padrões são soluções para problemas comuns. Um passo adiante, padrões são modelos para resolver
categorias de problemas.
Os padrões ajudam você a dividir um problema em blocos semelhantes aos de Lego e a focar nas partes
específicas do problema, ao mesmo tempo em que abstrai muitos detalhes do tipo "já passei por isso, fiz
isso, tenho 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 é para iniciantes; ele é direcionado a desenvolvedores e programadores profissionais que
desejam levar suas habilidades em JavaScript para o próximo nível.
Alguns conceitos básicos (como laços, condicionais e fechamentos) não são abordados. Se você precisar
revisar alguns desses tópicos, consulte a lista de leituras sugeridas.
Ao mesmo tempo, alguns tópicos (como criação ou elevação de objetos) podem parecer básicos demais
para estar neste livro, mas são discutidos a partir de uma perspectiva de padrões e, na minha opinião, são
essenciais para aproveitar o poder da linguagem.
Se você está procurando práticas recomendadas e padrões poderosos para ajudá-lo a escrever um código
JavaScript melhor, mais sustentável e robusto, este livro é para você.
Itálico
indica novos termos, URLs, endereços de e-mail, nomes de arquivos e extensões de arquivo.
xiii
Machine Translated by Google [Link]
Largura constante
Usada para listagens de programas, bem como dentro de parágrafos para se referir a elementos do programa, 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.
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, a atribuição. Uma atribuição geralmente inclui o título, autor, editora e ISBN.
Por exemplo: “Padrões JavaScript, por Stoyan Stefanov (O'Reilly). Copyright 2010 Yahoo!, Inc., 9780596806750.”
Se você acha que o uso de exemplos de código não se enquadra no uso justo ou na permissão concedida aqui, sinta-se à
vontade para entrar em contato conosco pelo e-mail permissions@[Link].
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 sobre tecnologia e criatividade para
encontrar as respostas que precisa rapidamente.
xiv | Prefácio
Machine Translated by Google [Link]
Com uma assinatura, você pode ler qualquer página e assistir a qualquer vídeo da nossa biblioteca on-line.
Leia livros no seu celular e dispositivos móveis. Acesse novos títulos antes que eles sejam disponibilizados
para impressão, tenha acesso exclusivo aos manuscritos em desenvolvimento e publique feedback para
os autores. Copie e cole exemplos 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 disponibilizou este livro no serviço Safari Books Online. Para ter acesso digital completo
a este e outros livros sobre temas semelhantes da O'Reilly e de outras editoras, inscreva-se gratuitamente
em http:// [Link].
Por favor, envie comentários e perguntas sobre este livro à editora: O'Reilly Media, Inc.
Temos uma página web para este livro, onde listamos erratas, exemplos e qualquer informação adicional.
Você pode acessá-la em:
comentar ou fazer perguntas técnicas sobre este livro, envie um e-mail para:
bookquestions@[Link]
Para obter mais informações sobre nossos livros, conferências, Centros de Recursos e
O'Reilly Network, visite nosso site em:
http:// [Link]
Agradecimentos
Sou eternamente grato aos incríveis revisores que compartilharam sua energia e conhecimento
para criar um livro muito melhor para o bem da comunidade. Seus blogs e feeds do Twitter são uma
fonte constante de admiração, observações perspicazes, ótimas ideias e padrões.
Prefácio | xv
Machine Translated by Google [Link]
Créditos
Alguns dos padrões do livro foram identificados pelo autor, com base em sua experiência e em estudos
de bibliotecas JavaScript populares, 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 a história e os créditos, uma lista de referências
e sugestões de leitura adicional estão disponíveis no site que acompanha o livro, em http://
[Link]/ book/ reading/.
Se eu esqueci de algum 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 on-line em http:// [Link].
Leitura
Este não é um livro para iniciantes e alguns tópicos básicos, como laços e condições, foram ignorados.
Se você precisa aprender mais sobre a linguagem, aqui estão algumas sugestões de títulos:
xvi | Prefácio
Machine Translated by Google [Link]
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 web (como imagens e campos de formulário), mas cresceu enormemente.
Além da criação de scripts para navegadores do lado do cliente, hoje em dia é possível usar JavaScript
para programar para uma variedade cada vez maior de plataformas. Você pode escrever código do
lado do servidor (usando .NET ou [Link]), aplicativos desktop (que funcionam em todos os sistemas
operacionais) e extensões de aplicativos (por exemplo, para Firefox ou Photoshop), aplicativos mobile
e scripts de linha de comando.
JavaScript também é uma linguagem incomum. Ela não possui classes, e funções são objetos de
primeira classe usados para diversas tarefas. Inicialmente, a linguagem foi considerada deficiente por
muitos desenvolvedores, mas nos últimos anos essa percepção mudou. Curiosamente, linguagens
como Java e PHP começaram a adicionar recursos como closures e funções anônimas, que os
desenvolvedores JavaScript já apreciavam e consideravam garantidos há algum tempo.
JavaScript é dinâmico o suficiente para que você possa fazer com que ele se pareça com outra
linguagem com a qual já esteja familiarizado. Mas a melhor abordagem é aceitar 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:// [Link] .org/ wiki/ Pattern).
1
Machine Translated by Google [Link]
• Eles nos ajudam a escrever códigos melhores usando práticas comprovadas e não reinventar a roda. • Eles
fornecem um nível de abstração — o cérebro pode conter apenas uma certa quantidade de informações em
um determinado momento, então, quando você pensa em um problema mais complexo, ajuda se você
não se preocupar com os detalhes de baixo nível, mas contabilizá-los com blocos de construção
autocontidos (padrões).
• Eles melhoram a comunicação entre desenvolvedores e equipes, que muitas vezes estão em locais remotos
e não se comunicam pessoalmente. Simplesmente rotular alguma técnica ou abordagem de codificação
torna mais fácil garantir que estamos falando da 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ênteses para invocar a função exatamente onde você a definiu".
multou-o.”
• 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" (nome dado em homenagem
aos seus quatro autores), publicado originalmente em 1994 sob o título Design Patterns: Elements of Reusable
Object-Oriented Software. Exemplos de padrões de projeto são singleton, factory, decorator, observer e assim
por diante. A questão dos padrões de projeto em relação ao JavaScript é que, embora independentes de
linguagem, os padrões de projeto foram estudados principalmente sob a perspectiva de linguagens fortemente
tipadas, como C++ e Java.
Às vezes, não faz necessariamente sentido aplicá-los literalmente em uma linguagem dinâmica de tipagem
flexível, 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 classes.
Em JavaScript, pode haver alternativas mais simples. Este livro discute implementações em JavaScript de
vários padrões de design 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 diversos usos de funções. Os padrões
de codificação JavaScript são o tema principal do livro.
Você pode encontrar um antipadrão ocasionalmente no livro. Antipadrões têm um som um pouco negativo ou
até mesmo ofensivo em seu nome, mas não precisa ser assim. 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 identificados com um comentário no código.
2 | Capítulo 1: Introdução
Machine Translated by Google [Link]
JavaScript: Conceitos
Vamos revisar rapidamente alguns conceitos importantes que fornecem um contexto para os próximos
capítulos.
JavaScript Orientado
A coisa mais simples que se faz em qualquer linguagem é definir uma variável. Bem, em JavaScript,
ao definir uma variável, você já está lidando com objetos. Primeiro, a variável se torna 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). Segundo, essa variável também é, na verdade, semelhante
a um objeto, pois possui suas próprias propriedades (chamadas 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 de descrição especiais
para manipulá-los.
Então, o que são esses objetos? Como eles fazem tantas coisas, devem ser bem especiais.
Na verdade, 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), nesse caso 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 introduza APIs para evitar mutações.) Você pode pegar um objeto e
adicionar, remover e atualizar seus membros. Se você se preocupa 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 ser categorizados como integrados (por exemplo, Array, Date) ou definidos
pelo usuário (var o = {};).
JavaScript: Conceitos | 3
Machine Translated by Google [Link]
Objetos host são, por exemplo, window e todos os objetos DOM. Se você está se perguntando se
está usando objetos host, tente executar seu código em um ambiente diferente, sem navegador.
Se funcionar bem, você provavelmente está usando apenas objetos nativos.
Sem aulas
Você verá esta afirmação repetida em diversas ocasiões ao longo do livro: Não há classes em
JavaScript. Este é um conceito novo para programadores experientes em outras linguagens, e são
necessárias mais do que algumas repetições e um esforço considerável para "desaprender" classes
e aceitar que 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 à de Java:
// Criação de objetos Java
OláOO olá_oo = new OláOO();
Repetir a mesma coisa três vezes parece um peso extra quando se trata de criar objetos simples.
E, na maioria das vezes, queremos manter nossos objetos simples.
Uma das regras gerais do livro da Gangue dos Quatro diz: "Prefira a composição de objetos à
herança de classes". Isso significa que, se você puder criar objetos a partir de partes disponíveis
que você tem por aí, essa é uma abordagem muito melhor do que criar longas cadeias e
classificações de herança pai-filho. Em JavaScript, é fácil seguir esse conselho — simplesmente
porque não existem classes e a composição de objetos é o que você faz de qualquer maneira.
Protótipos
JavaScript possui herança, embora esta seja apenas uma maneira de reutilizar 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 fazem uso de protótipos. Um protótipo é um objeto (sem surpresas) e cada função que
você cria recebe 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 literal de objeto ou construtor
Object() , exceto que sua propriedade de construtor aponta para a função que você cria e não para
o Object() interno . Você pode adicionar membros a este objeto em branco e, posteriormente, fazer
com que outros objetos herdem deste objeto e usem suas propriedades como se fossem suas.
Discutiremos herança em detalhes, mas por enquanto tenha em mente que o protótipo é um objeto
(não uma classe ou algo especial) e cada função tem uma propriedade de protótipo .
4 | Capítulo 1: Introdução
Machine Translated by Google [Link]
Ambiente
Programas JavaScript precisam de um ambiente para rodar. O habitat natural de um programa JavaScript é
o navegador, mas este não é o único ambiente. Os padrões neste livro estão principalmente relacionados
ao núcleo do JavaScript (ECMAScript), portanto, são independentes de 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 principal do JavaScript (excluindo DOM, BOM e objetos extras do host) é
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 em todos os 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 integrados à 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 do ES5, ele gera um erro, embora não haja problema
se encontrado no modo não estrito. O modo estrito é acionado por uma string comum, que 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 compreendem.
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()
{ "use strict"; //
resto da função...
}
Isso significa que o código na função é executado no subconjunto estrito da linguagem. Em navegadores
mais antigos, trata-se apenas de uma string não atribuída a nenhuma variável, portanto, não é usada, mas
ainda assim 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 no modo estrito.
ECMAScript 5 |5
Machine Translated by Google [Link]
O livro não explora padrões relacionados às adições específicas do ES5, pois, 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 por meio de:
• Garantir que os exemplos de código oferecidos não gerarão erros no modo estrito • Evitar e
apontar construções obsoletas, como [Link] • Chamar padrões ES3 que têm equivalentes
JSLint
JavaScript é uma linguagem interpretada sem verificações estáticas em tempo de compilação. Portanto, é
possível implementar um programa quebrado com um simples erro de digitação sem perceber. É aqui que o
JSLint ajuda.
JSLint (http:// [Link]) é uma ferramenta de qualidade de código JavaScript criada por Douglas Crockford que
inspeciona seu código e alerta sobre possíveis problemas. É altamente recomendável que você execute seu
código com JSLint. A ferramenta "vai machucar seus sentimentos", como seu criador alerta, 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
nele, sabendo que não cometeu uma simples omissão ou erro de sintaxe às pressas.
A partir do próximo capítulo, você verá o JSLint sendo bastante mencionado. Todo o código do livro passa com
sucesso na verificação do JSLint (com as configurações padrão, vigentes no momento da escrita), exceto em
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 ao longo do livro. Este objeto não faz parte da linguagem, mas sim do ambiente, e
está presente na maioria dos navegadores atuais. No Firefox, por exemplo, ele vem com a extensão Firebug. O
console do Firebug possui uma interface de usuário que permite digitar e testar rapidamente pequenos trechos
de código JavaScript, além de interagir com a página carregada no momento (veja a Figura 1-1). Também é
altamente recomendado como uma ferramenta de aprendizado e exploração. Funcionalidades semelhantes
estão disponíveis nos 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 [Link]
Frequentemente usamos o método log(), que imprime todos os parâmetros passados a ele, e às
vezes dir(), que enumera o objeto passado a ele e imprime todas as propriedades. Aqui está um
exemplo de uso:
[Link]("test", 1, {}, [1,2,3]);
[Link]({one: 1, two: {three: 3}});
Ao digitar no console, você não precisa usar [Link](); 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:
[Link] === window['nome']; // verdadeiro
O Console | 7
Machine Translated by Google [Link]
Machine Translated by Google [Link]
CAPÍTULO 2
Essenciais
Este capítulo discute as melhores práticas, padrões e hábitos essenciais para escrever código
JavaScript de alta qualidade, como evitar globais, usar declarações de variáveis únicas, pré-
armazenar em cache o 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 documentação de API,
conduzir revisões por pares e executar JSLint. Esses hábitos e melhores práticas podem ajudá-lo
a escrever um código melhor, mais compreensível e sustentável — um código do qual se orgulhar
(e ser capaz de decifrar) ao revisitá-lo meses e anos depois.
Outro problema, específico de projetos ou empresas maiores, é que a pessoa que eventualmente
corrige o bug não é a mesma que o criou (e também não é a mesma que o encontrou). Portanto,
é fundamental reduzir o tempo necessário para entender o código, seja ele escrito por você há
algum tempo ou por outro desenvolvedor da equipe. Isso é crucial tanto para o resultado final
(receita da empresa) quanto para a satisfação do desenvolvedor, porque todos nós preferimos
desenvolver algo novo e empolgante em vez de gastar horas e dias mantendo código legado.
9
Machine Translated by Google [Link]
O código provavelmente funcionará naquele momento, mas, à medida que o aplicativo amadurece, muitas
outras coisas acontecem e 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 é
completamente reescrito do zero ou portado para outra arquitetura ou até mesmo outra linguagem.
Como resultado das mudanças, as poucas horas-homem gastas inicialmente escrevendo o código se
transformam em semanas-homem gastas lendo-o. É por isso que criar um código sustentável é fundamental
para o sucesso de uma aplicação.
Código sustentável significa código que:
• É legível
• É consistente
• É previsível • Parece
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 dela. Por outro lado, variáveis globais são aquelas declaradas fora
de qualquer função ou simplesmente usadas sem serem declaradas.
Todo ambiente JavaScript possui um objeto global acessível quando você usa this fora de qualquer função.
Cada variável global que você cria se torna uma propriedade do objeto global. Em navegadores, por
conveniência, há uma propriedade adicional do objeto global chamada window 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
[Link](myglobal); // "olá"
[Link]([Link]); // "olá"
[Link](window["myglobal"]); // "olá"
[Link]([Link]); // "olá"
10 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
Também é comum que páginas da web incluam código não escrito pelos desenvolvedores da página,
por exemplo: • Uma
Digamos que um dos scripts de terceiros defina uma variável global, chamada, por exemplo, result.
Posteriormente, em uma de suas funções, você define outra variável global chamada result. O
resultado disso é que a última variável result substitui as anteriores, e o script de terceiros pode
simplesmente parar de funcionar.
Portanto, é importante ser um bom vizinho para os outros scripts que podem estar na mesma página
e usar o mínimo possível de variáveis globais. Mais adiante neste livro, você aprenderá sobre
estratégias para minimizar o número de variáveis globais, como o padrão de namespace ou as
funções imediatas autoexecutáveis. No entanto, o padrão mais importante para ter menos variáveis
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 sequer declará-las. E segundo, JavaScript tem a noção de
globais implícitas, o que significa que qualquer variável que você não declarar se torna uma
propriedade do objeto global (e é acessível como uma variável global devidamente declarada).
Considere o seguinte exemplo:
função soma(x, y) { //
antipadrão: resultado global
implícito = x +
y; retornar resultado;
}
Neste código, result é usado sem ser declarado. O código funciona bem, mas depois de chamar a
função, você acaba com mais uma variável result no namespace global, o que pode ser uma fonte
de problemas.
A regra geral é sempre declarar variáveis com var, como demonstrado na versão aprimorada da função
sum() : function sum(x, y) { var result = x +
y; return result;
Minimizando Globais | 11
Machine Translated by Google [Link]
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:
// ...
}
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 é declarado. 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 é adequado e não cria variáveis globais inesperadas.
Exemplo: function foo() { var a, b; // ...
a = b = 0; // ambas
locais
Outro motivo para evitar globais é a portabilidade. Se você quer que seu código
rode em diferentes ambientes (hosts), é perigoso usar globais, pois você pode
acidentalmente sobrescrever um objeto host que não existe no seu ambiente
original (então você achou que o nome era seguro), mas que existe em alguns
dos outros.
• 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 de serem criados dentro de funções) podem ser
excluído.
Isso mostra que variáveis globais implícitas não são tecnicamente variáveis reais, mas são propriedades
do objeto global. Propriedades podem ser excluídas com o operador delete, enquanto variáveis não podem:
12 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
// tentativa de excluir
delete global_var; // falso delete
global_novar; // verdadeiro
delete global_fromfunc; // verdadeiro
// testa a exclusão
typeof global_var; // "número"
typeof global_novar; // "indefinido" typeof
global_fromfunc; // "indefinido"
No modo estrito do ES5, atribuições a variáveis não declaradas (como os dois antipadrões no
snippet anterior) gerarão um erro.
navegadores, o objeto global é acessível de qualquer parte do código por meio da propriedade
window (a menos que você tenha feito algo especial e inesperado, como declarar uma variável
local chamada window). Mas em outros ambientes, essa propriedade de conveniência pode ter
outro nome (ou até mesmo não estar disponível para o programador). Se você precisar acessar o
objeto global sem codificar o identificador window, poderá fazer o seguinte a partir de qualquer
nível do escopo da função aninhada:
var global = (função () { retornar
isto; }());
Dessa forma, você sempre pode obter o objeto global, porque dentro de funções que foram
invocadas como funções (ou seja, não como constritores com new), this deve sempre apontar
para o objeto global. Na verdade, isso não é mais o caso no ECMAScript 5 em modo estrito,
então você precisa adotar um padrão diferente quando seu código estiver em modo estrito. Por
exemplo, se você estiver desenvolvendo uma biblioteca, pode encapsular 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 this como parâmetro para sua função imediata.
Padrão de variável
única: usar uma única instrução var no topo das suas funções é um padrão útil a ser adotado. Ele
tem os seguintes benefícios:
• Fornece um único local para procurar todas as variáveis locais necessárias para a função •
Evita erros lógicos quando uma variável é usada antes de ser definida (consulte “Içamento: Uma
Problema com vars dispersos” 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 [Link]
// corpo da função...
Use uma instrução var e declare várias variáveis delimitadas por vírgulas. É uma boa prática também
inicializar a variável com um valor inicial no momento da declaração. 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 analisar o código posteriormente, você pode ter uma ideia do uso pretendido de
uma variável com base em seu valor inicial — por exemplo, ela deveria ser um objeto ou um inteiro?
Você também pode realizar algum trabalho real no momento da declaração, como no caso de sum = a + b
no código anterior. Outro exemplo é ao trabalhar com referências DOM (Document Object Model). Você
pode atribuir referências DOM a variáveis locais juntamente com a declaração única, como demonstra o
código a seguir:
JavaScript permite que você tenha várias instruções var em qualquer lugar de uma função, e todas elas agem
como se as variáveis estivessem declaradas no topo da função. Esse comportamento é conhecido como
elevação. Isso pode levar a erros lógicos quando você usa uma variável e a declara posteriormente na
função. Em JavaScript, desde que uma variável esteja no mesmo escopo (mesma função), ela é considerada
declarada, mesmo quando usada antes da declaração var . Veja este exemplo:
// antipadrão
meunome = "global"; // variável global function
func() { alert(meunome); //
"indefinido" var meunome = "local";
alerta(meunome); // "locais"
} função();
14 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
Neste exemplo, você pode esperar que o primeiro alert() indique "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á "undefined" porque myname é 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 elevadas ao 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 fosse implementado assim:
meunome = "global"; // variável global
function func()
{ var meunome; // igual a -> var meunome =
indefinido; alerta(meunome); //
"indefinido"
meunome = "local"; alerta(meunome); // "locais"
} função();
Laços for
Nos laços for, você itera sobre arrays ou objetos semelhantes a arrays, como argumentos e
objetos HTMLCollection . O padrão usual do laço for se parece com o seguinte:
// loop subótimo para
(var i = 0; i < [Link]; i++) { // faça
algo com myarray[i]
}
Um problema com esse padrão é que o comprimento do array é acessado a cada iteração do
loop. Isso pode tornar seu código lento, especialmente quando myarray não é um array, mas
um objeto HTMLCollection .
HTMLCollections são objetos retornados por métodos DOM, como:
• [Link]()
• [Link]()
• [Link]()
para Laços | 15
Machine Translated by Google [Link]
Há também uma série de outras HTMLCollections, que foram introduzidas antes do padrão DOM e ainda são
usadas hoje. Entre elas, estão (entre outras):
[Link]
Todos os elementos IMG na página
[Link]
Todos os elementos A
[Link] Todos
os formulários
[Link][0].elements Todos
os campos do primeiro formulário na página
O problema com coleções é que elas são consultas ativas no documento subjacente (a página HTML). Isso
significa que toda vez que você acessa o comprimento de qualquer coleção, você está consultando o DOM
ativo, e as operações no DOM são caras em geral.
É por isso que um padrão melhor para loops for é armazenar em cache o comprimento do array (ou coleção)
sobre o qual você está iterando, conforme mostrado no exemplo a seguir:
Dessa forma, você recupera o valor do comprimento 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), você provavelmente gostaria que o comprimento fosse atualizado e não
constante.
Seguindo o padrão de variável única, você também pode tirar a variável do loop e fazer o loop como: function looper() { var i = 0, max,
minhamatriz = [];
// ...
Esse padrão tem o benefício da consistência porque você segue o padrão de variável única .
Uma desvantagem é que isso 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: Essenciais
Machine Translated by Google [Link]
você tem que ter certeza de também transferir 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 uma destas expressões:
i=i+1i
+= 1
O JSLint solicita que você faça isso; o motivo é que ++ e -- promovem "truques excessivos". Se você
discordar disso, pode definir a opção plusplus do JSLint como false. (É true por padrão.) Mais adiante no
livro, o último padrão é usado: i += 1.
máximo) • Faça a contagem regressiva até 0, que geralmente é mais rápido porque é mais eficiente comparar
com 0 do que com o comprimento da matriz ou com qualquer coisa diferente de 0
enquanto (i--)
{ // faça algo com myarray[i]
}
Essas são microotimizações e só serão percebidas em operações críticas de desempenho. Além disso, o
JSLint reclamará do uso de i--.
Loops for-in
Laços for-in devem ser usados para iterar sobre objetos que não sejam matrizes. Laços com for-in também
são chamados de enumeração.
Tecnicamente, você também pode usar for-in para fazer um loop sobre arrays (porque em JavaScript arrays
são objetos), mas isso não é recomendado. Isso pode levar a erros lógicos se o objeto array já tiver sido
complementado com funcionalidades personalizadas. 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 propriedades de objetos para filtrar
propriedades que descem a cadeia de protótipos.
Loops for-in | 17
Machine Translated by Google [Link]
= { mãos: 2,
pernas: 2,
cabeças: 1
};
Neste exemplo, temos um objeto simples chamado man definido com um literal de objeto. Em
algum momento, antes ou depois da definição de man , o protótipo do objeto foi complementado
com um método útil chamado clone(). A cadeia de protótipos está ativa, o que significa que todos
os objetos têm acesso automático 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.
A falha na filtragem pode resultar no aparecimento da função clone() , o que é um comportamento
indesejado na maioria dos cenários:
// 1. //
laço for-in para (var
i em man) { se
([Link](i)) { // filtro [Link](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) { [Link](i, ":",
man[i]);
} /*
resultado no console mãos: 2
pernas: 2
cabeças: 1
clone: função() */
18 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
Outro padrão para usar hasOwnProperty() é chamar esse método do [Link], assim:
A vantagem é que você pode evitar colisões de nomenclatura caso o objeto principal tenha redefinido
hasOwnProperty. Além disso, para evitar longas pesquisas de propriedades até o objeto, você pode usar
uma variável local para "armazená-la em cache":
var i,
hasOwn = [Link];
para (i em homem)
{ se ([Link](homem, i)) { // filtrar
[Link](i, ":", homem[i]);
}
}
Uma variação de formatação (que não passa por JSLint) ignora uma chave e coloca o if na mesma linha. A
vantagem é que a instrução do loop se parece mais com um pensamento completo ("para cada elemento
que tem uma propriedade X, faça algo com X"). Além disso, há menos recuo antes de chegar ao objetivo
principal do loop:
É tentador aumentar protótipos de construtores integrados, como Object(), Array() ou Function(), mas isso
pode prejudicar seriamente a manutenibilidade, pois tornará seu código menos previsível. Outros
desenvolvedores que usam seu código provavelmente esperarão que os métodos JavaScript integrados
funcionem de forma consistente e não contarão com suas adições.
Além disso, as propriedades que você adiciona ao protótipo podem aparecer em loops que não
usam hasOwnProperty(), o que pode criar confusão.
Portanto, é melhor não aumentar protótipos integrados. Você pode abrir uma exceção à regra
somente quando todas estas condições forem atendidas:
Se essas três condições forem atendidas, você poderá prosseguir com a adição personalizada ao
protótipo, seguindo este padrão: if
(typeof [Link] !== "function")
{ [Link] = function ()
{ // implementação...
};
}
Padrão de troca
Você pode melhorar a legibilidade e a robustez de suas instruções switch seguindo este padrão:
var inspect_me = 0,
resultado = '';
alternar (inspecione-me) {
caso 0:
resultado = "zero"; quebra;
caso 1:
resultado = "um";
quebra;
padrão:
resultado = "desconhecido";
}
• Alinhar cada caso com switch (uma exceção à regra de recuo de chaves). • Recuar o código
dentro de cada caso. • Terminar cada caso
com um break claro;.
20 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
• Evitar falhas (quando você omite a interrupção intencionalmente). Se você estiver absolutamente convencido
de que uma falha é a melhor abordagem, certifique-se de documentar esses casos, pois eles podem parecer
erros para os leitores do seu código. • Finalizar a troca com um padrão: para garantir que sempre
Para evitar confusão causada pela conversão de tipos implícita, 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á sendo executado porque zero é 0, não falso
}
// antipadrão if
(zero == false) { // este
bloco é executado...
}
Há outra corrente de pensamento que defende a opinião de que é redundante usar === quando == é suficiente. Por
exemplo, ao usar typeof , você sabe que ele retorna uma string, então não há razão para usar igualdade estrita. No
entanto, JSLint requer igualdade estrita; isso faz com que o código pareça consistente e reduz o esforço mental ao
lê-lo. ("Este == é intencional ou uma omissão?")
Evitando eval()
Se você identificar o uso de eval() no seu código, lembre-se do mantra "eval() é maligno". Esta função recebe 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á motivo para usar eval(). Se o código for gerado dinamicamente em
tempo de execução, geralmente há uma maneira melhor de atingir o objetivo sem eval(). Por exemplo, usar apenas
a notação de colchetes para acessar propriedades dinâmicas é melhor e mais simples:
// antipadrão var
propriedade = "nome";
alerta(eval("obj." + propriedade));
// propriedade
var preferida = "nome";
alerta(obj[propriedade]);
Usar eval() também tem implicações de segurança, pois você pode estar executando código (por exemplo, vindo
da rede) que foi adulterado. Este é um problema comum.
antipadrão ao lidar com uma resposta JSON de uma requisição Ajax. Nesses casos, é melhor
usar os métodos integrados do navegador para analisar a resposta JSON e garantir que ela
seja segura e válida. Para navegadores que não suportam [Link]() nativamente, você
pode usar uma biblioteca do [Link].
Também é importante lembrar que passar strings para setInterval(), setTimeout() e o construtor
Function() é, em grande parte, semelhante a usar 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("myFunc(1, 2, 3)", 1000);
// preferido
setTimeout(myFunc, 1000);
setTimeout(function ()
{ myFunc(1, 2,
3); }, 1000);
O uso do construtor new Function() é semelhante ao eval() e deve ser abordado com cautela.
Pode ser um construtor poderoso, mas frequentemente é mal utilizado. Se você realmente
precisar usar eval(), considere usar new Function() . Há um pequeno benefício potencial, pois
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 é encapsular a chamada
eval() em uma função imediata (mais sobre funções imediatas no Capítulo 4).
Considere o exemplo a seguir. Aqui, apenas un permanece como variável global, poluindo o namespace:
[Link](typeof
un); // "indefinido" [Link](typeof deux); //
"indefinido" [Link](typeof trois); //
"indefinido"
Outra diferença entre eval() e o construtor Function é que eval() pode interferir na cadeia de
escopo, enquanto Function é muito mais restrita. Não importa onde você execute Function, ele
vê apenas o escopo global. Portanto, ele pode lidar com menos variáveis locais.
22 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
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; [Link](local)"); // logs 3
[Link](local); // logs 3 }());
(function () { var
local = 1;
Function("[Link](typeof local);")(); // logs indefinidos }());
Usando parseInt(), você pode obter um valor numérico de uma string. A função aceita um segundo
parâmetro radix, que frequentemente é omitido, mas não deveria ser. Os problemas ocorrem quando a
string a ser analisada começa com 0: por exemplo, 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) no ECMAScript 3;
no entanto, isso mudou no ES5. Para evitar inconsistências e resultados inesperados, sempre
especifique o parâmetro radix:
Neste exemplo, se você omitir o parâmetro radix como parseInt(year), o valor retornado será 0, porque
“09” assume um número octal (como se você fizesse parseInt(year, 8)) e 09 não é um dígito válido na
base 8.
Geralmente, elas são mais rápidas que parseInt(), porque parseInt(), como o nome sugere, analisa e
não apenas converte. Mas se você espera uma entrada como "08 hello", parseInt() retornará um
número, enquanto as outras 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 se junta à equipe pode ler
as convenções e se tornar produtivo muito mais rápido, entendendo o código escrito por qualquer
outro membro da equipe.
Convenções de codificação | 23
Machine Translated by Google [Link]
Muitas discussões têm sido travadas em reuniões e listas de discussão sobre aspectos
específicos de certas convenções de codificação (por exemplo, a indentação do código —
tabulações ou espaços?). Portanto, se você está sugerindo a adoção de convenções em sua
organização, esteja preparado para enfrentar resistência 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 os detalhes exatos dessa convenção.
Recuo
Código sem indentação é impossível de ler. Pior ainda é código com indentação inconsistente, pois parece seguir uma
convenção, mas pode trazer surpresas confusas ao longo do caminho. É importante padronizar o uso da indentação.
Alguns desenvolvedores preferem recuo com tabulações, pois qualquer um pode ajustar seu
editor para exibir as tabulações com o número de espaços desejado individualmente. Alguns
preferem espaços — geralmente quatro. Não importa, desde que todos na equipe sigam a
mesma convenção. Este livro, por exemplo, usa recuo de quatro espaços, que também é o
padrão no JSLint.
E o que você deve recuar? A regra é simples — qualquer coisa entre chaves. Isso significa os corpos de funções, laços (do, while,
for, for-in), ifs, switches e propriedades de objetos na notação literal de objeto. O código a seguir mostra alguns exemplos de uso de
recuo: function outer(a, b) { var c = 1, d = 2, inner; if (a > b) { inner = function () { return { r: c - d
};
}; } senão
{ interno = função () { retornar
{ r: c + d
};
};
} retornar interno;
}
Chaves: As
chaves devem ser sempre 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: Essenciais
Machine Translated by Google [Link]
De qualquer forma, você deve sempre usá-los. Isso torna o código mais consistente e fácil de
atualizar.
Imagine que você tem um loop for com apenas uma instrução. Você poderia omitir as chaves e
não haveria erro de sintaxe: //
má prática for
(var i = 0; i < 10; i += 1) alert(i);
Mas e se, mais tarde, você adicionar outra linha no corpo do loop?
// má prática para
(var i = 0; i < 10; i += 1) alert(i); alert(i +
" "
é + (i % 2 ? "ímpar" : "par"));
O segundo alerta está fora do loop, embora a indentação possa te enganar. A melhor coisa a
fazer a longo prazo é sempre usar as chaves, mesmo para blocos de uma linha:
// melhor
para (var i = 0; i < 10; i += 1) { alert(i);
ruim if
(true)
alert(1); else
alert(2);
// melhor
se
(verdadeiro)
{ alert(1); }
senão { alert(2);
}
abertura Os desenvolvedores também tendem a ter preferências sobre onde a chave de abertura deve ficar: na
mesma linha ou na linha seguinte? if (true) { alert("É
VERDADEIRO!");
}
Ou:
se (verdadeiro)
{
alert("É VERDADE!");
}
Convenções de codificação | 25
Machine Translated by Google [Link]
Neste exemplo específico, é uma questão de preferência, mas há casos em que o programa pode se
comportar de forma diferente dependendo de onde a chave está. Isso se deve ao mecanismo de inserção
de ponto e vírgula — o JavaScript não é exigente quando você opta por 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 literal de objeto e a chave de abertura está na linha seguinte:
nome: "Batman"
};
}
Se você espera que esta função retorne um objeto com uma propriedade de nome , você 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() {
return undefined; //
código inalcançável segue... {
nome: "Batman"
};
}
Concluindo, use sempre chaves e sempre coloque a primeira 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ê deve sempre
usar ponto e vírgula, mesmo quando eles são implícitos pelos analisadores de JavaScript.
Isso não apenas promove disciplina e uma abordagem mais rigorosa ao código, mas
também ajuda a resolver ambiguidades, como mostrou o exemplo anterior.
Espaço em
Branco: O uso de espaços em branco também pode contribuir para melhorar a legibilidade e a
consistência do código. Em frases escritas em inglês, use intervalos após vírgulas e pontos. Em
JavaScript, siga a mesma lógica e adicione intervalos após expressões do tipo lista (equivalente a
vírgulas) e no final de declarações (equivalente a completar um "pensamento").
26 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
• 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 itens de array: var a = [1, 2, 3]; • Após vírgulas em
propriedades de objeto e depois de dois pontos que dividem nomes de propriedades e seus valores: var o = {a:
1, b: 2}; • Delimitando argumentos de função:
function myFunc() {} • Após function em expressões de função anônima: var myFunc = function
() {};
Outro bom uso para espaços em branco é separar todos os operadores e seus operandos com espaços,
o que basicamente significa usar um espaço antes e depois de +, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=
e assim por diante: //
espaçamento generoso e consistente //
torna o código mais fácil de ler //
permitindo que ele "respire" var
d = 0, a =
b + 1; if (a
&& b && c) { d = a %
c; a += d;
// antipadrão //
espaços ausentes ou inconsistentes //
torna o código confuso var d=
0, a =b+1;
if (a&&
b&&c) { d=a %c;
a+= d;
E uma observação final sobre o 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 literais de objeto • Entre a chave de fechamento
Um argumento contra o uso liberal de espaços em branco pode ser que ele aumentaria o tamanho do arquivo,
mas a minimização (discutida mais adiante no capítulo) resolve esse problema.
Convenções de codificação | 27
Machine Translated by Google [Link]
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 forma consistente.
Abaixo estão algumas sugestões de convenções de nomenclatura que você pode adotar como estão
ou ajustar conforme sua preferência. Novamente, ter uma convenção e segui-la consistentemente é
muito mais importante do que a convenção propriamente dita.
Construtores de Capitalização
JavaScript não tem classes, mas tem funções construtoras invocadas com new:
var adam = nova Pessoa();
Como construtores ainda são apenas funções, ajuda se você puder dizer, apenas olhando para o
nome da função, se ela deveria se comportar como um construtor ou como uma função normal.
Nomear construtores com a primeira letra maiúscula fornece essa dica. Usar letras minúsculas para
funções e métodos indica que eles não devem ser chamados com new:
função MyConstructor() {...}
função myFunction() {...}
No próximo capítulo, há alguns padrões que permitem que você force programaticamente seus
construtores a se comportarem como construtores, mas simplesmente seguir a convenção de
nomenclatura já é útil por si só, pelo menos para programadores que leem o código-fonte.
Separando Palavras
Quando você tem várias palavras em uma variável ou nome de função, é uma boa ideia seguir uma
convenção sobre como elas serão separadas. Uma convenção comum é usar o chamado " camel
case". Seguindo a convenção, você digita as palavras em letras minúsculas, colocando apenas a
primeira letra de cada palavra em maiúscula.
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 quanto às variáveis que não são funções? Desenvolvedores costumam usar 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 a
distinguir visualmente entre funções e todos os outros identificadores — primitivos e objetos.
28 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
O ECMAScript usa camel case 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 integradas, como
Number.MAX_VALUE), então os desenvolvedores adotaram a convenção de usar todas as letras maiúsculas
para nomear variáveis que não devem mudar de valor durante a vida do programa, como:
Há outra convenção que concorre para o uso de letras maiúsculas: usar letras maiúsculas para nomes de
variáveis globais. Nomear variáveis globais com todas as letras maiúsculas pode reforçar a prática de
minimizar seu número e torná-las facilmente distinguíveis.
Outro caso de uso de uma convenção para imitar a funcionalidade é a convenção de membros privados.
Embora seja possível implementar privacidade verdadeira 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 =
{ getNome: função () ' '
{ return this._getFirst() + + this._getLast();
},
_getFirst: função () { // ...
},
_getLast: função () { // ...
}
};
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 o uso do prefixo sublinhado avisa os
usuários do objeto pessoa de que não há garantia de que esses métodos funcionem na próxima versão e não
devem ser usados diretamente. Observe que o JSLint reclamará sobre os prefixos sublinhados, a menos que
você defina a opção nomen: false.
• Usando um sublinhado final para significar privado, como em name_ e getElements_() • Usando
Convenções de nomenclatura | 29
Machine Translated by Google [Link]
• No Firefox, algumas propriedades internas que tecnicamente não fazem parte 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ê precisa comentar seu código, mesmo que seja improvável que alguém além de você o toque.
Muitas vezes, quando você está imerso em um problema, acha que é óbvio o que o código faz, mas
quando volta a ele depois de uma semana, tem dificuldade em lembrar exatamente como ele funcionava.
Você não deve exagerar comentando o óbvio: cada variável ou cada linha. Mas geralmente é necessário
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á lá. Não há
uma regra rígida ou proporção de comentários para código; alguns trechos de código (pense em
expressões regulares) podem, na verdade, exigir mais comentários do que código.
E como você verá na próxima seção, os comentários podem ajudar você a gerar documentação
automaticamente.
A maioria dos desenvolvedores considera escrever documentação uma tarefa tediosa e pouco
recompensadora. Mas isso não precisa ser verdade. 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 precisar escrevê-la. A maioria dos programadores acha essa ideia fascinante, porque gerar
automaticamente uma referência legível a partir de palavras-chave específicas e "comandos"
especialmente formatados se parece muito com programação real.
Tradicionalmente, a documentação de APIs vem do mundo Java, onde um utilitário chamado javadoc
é distribuído junto com o Java SDK (Software Developer Kit). Mas a mesma ideia foi adaptada para
muitas outras linguagens. E para JavaScript, existem duas ferramentas excelentes, gratuitas e de
código aberto: o JSDoc Toolkit (http:// [Link]/ p/ jsdoc-toolkit/). e YUIDoc (http:// [Link]/
projects/ yuidoc).
30 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
A sintaxe especial que você precisa aprender consiste em cerca de uma dúzia de tags, que se parecem com isto:
/**
*
@valor da tag
*/
Por exemplo, digamos que você tenha uma função chamada reverse() que inverte uma string. Ela
recebe um parâmetro string e, no final, retorna outra string. A documentação poderia ser assim:
/**
* Inverter 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.
Exemplo de YUIDoc: O
YUIDoc foi criado originalmente com o objetivo de documentar a biblioteca YUI (Yahoo! User Interface), mas pode
ser usado em qualquer projeto. Ele possui algumas convenções que você deve seguir para aproveitar ao máximo
a ferramenta, como, 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ê receberá no final.
Na verdade, você pode ajustar o modelo HTML para torná-lo ainda mais bonito e personalizado de acordo com as
necessidades, a aparência e o comportamento do seu projeto.
Para uma demonstração ao vivo do exemplo, visite http:// [Link]/ book/ 2/.
Neste exemplo, todo o aplicativo consiste em apenas um arquivo ([Link]) com apenas um módulo (myapp) . Você
aprenderá mais sobre módulos nos próximos capítulos, mas, por enquanto, pense no módulo como uma tag de
comentário necessária para que o YUIDoc funcione.
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: Essenciais
Machine Translated by Google [Link]
*
* @method
*
sum @param {Number} a Primeiro
*
número @param {Number} b O segundo
número * @return {Number} A soma das duas
/**
* Multiplica dois números
*
* @método multi
*
@param {Número} a Primeiro número @param
*
{Número} b O segundo número * @return {Número} As
duas entradas multiplicadas */ multi: function (a, b) { return a
* b;
}
};
E isso completa a primeira “classe”. Observe as tags destacadas:
@namespace
A referência global que contém seu objeto.
@class
Um nome impróprio (sem classes em JavaScript) que é usado para significar um objeto ou uma
função construtora.
@method
Define um método em um objeto e especifica o nome do método. @param
Lista os
argumentos que uma função aceita. Os tipos dos parâmetros estão entre chaves, seguidos do nome
do parâmetro e sua descrição.
@return
Como @param, só que descreve o valor retornado pelo método e não tem 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 maneiras de
criar objetos:
/**
* Constrói objetos Pessoa * @class
Pessoa *
@constructor
*
@namespace
*
MEUAPP @param {String} primeiro Nome
*
@param {String} último Sobrenome */
/**
* Nome da pessoa
*
@property first_name
*
@type String
*/
this.first_name = first; /** *
this.last_name = last;
};
/**
* Retorna o nome do objeto pessoa
*
* @method getName
* @return {String} O nome da pessoa */
• @constructor indica que esta "classe" é, na verdade, uma função construtora. • @property
Qualquer escritor ou editor dirá que a edição é importante: provavelmente o passo mais importante na
produção de um bom livro ou artigo. Colocar tudo 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. Ela produz o resultado desejado, mas será que o faz da melhor maneira possível?
É fácil de ler, entender, manter e atualizar? Ao revisitar seu código, o ideal é
34 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
Depois de algum tempo, você certamente verá pontos a serem melhorados — maneiras de tornar o código mais fácil
de acompanhar, remover algumas ineficiências e assim por diante. Isso é essencialmente edição e pode ajudar
enormemente a atingir 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 a documentação da API é uma oportunidade de edição.
Muitas vezes, ao escrever blocos de comentários em documentos, você revisita o problema. Às vezes, revisitar deixa
claro que, por exemplo, este terceiro parâmetro de um método é, na verdade, mais necessário do que o segundo, e o
segundo quase sempre será definido como verdadeiro por padrão, 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á. Só
esse fato já fará você editar e pensar em maneiras melhores de resolver o problema que você tem em mãos.
Falando em primeiros rascunhos, há também a ideia de "planejar jogar um fora". Pode parecer um pouco extremo no
início, 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 que você encontrar deve ser descartada e então você
começa 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ê pegue atalhos ou se contente com a solução imperfeita.
Outra maneira de melhorar seu código é revisá-lo por pares. As revisões por pares podem ser formais e padronizadas,
até mesmo com o auxílio de 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 ser um obstáculo. Você pode simplesmente pedir ao desenvolvedor ao seu lado para dar uma olhada no seu
código ou pode orientá-lo.
Novamente, assim como acontece ao 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 outra pessoa 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
tanto o revisor quanto o criador compartilham e trocam conhecimento e aprendem com a experiência e as abordagens
individuais de cada um.
Se você tem um escritório individual e não tem colegas para revisar seu código, isso também não deve ser um
obstáculo. Você sempre pode tornar público pelo menos parte do código ou simplesmente publicar um blog sobre um
trecho interessante dele e, assim, ter o mundo como 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 fizer check-in do código. A maioria
desses e-mails não será lida, mas de vez em quando pode ocorrer uma revisão espontânea por
pares quando alguém decide dar uma pausa no trabalho e dar uma olhada no código que você
acabou de fazer check-in.
Minificar…Em produção
Minificação é o processo de eliminar 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 o Closure Compiler do Google, e ajuda a acelerar
o tempo de carregamento da página. É importante minificar os scripts prontos para produção, pois
isso resulta em economias significativas, muitas vezes reduzindo o tamanho pela metade.
Veja como é um exemplo de código minificado (isso faz parte do utilitário Event da biblioteca YUI2):
[Link]=function(D,C,B,A){[Link]=D;[Link]=C||window;[Link]
=B;[Link]=A||[Link];[Link]=[];if(![Link]) {}var
E="_YUICEOnSubscribe";if(D!==E){[Link]=new
[Link](E,this,true);}...
Como observação lateral, o Google Closure Compiler também tentará minimizar variáveis globais
(no modo “avançado”), o que é arriscado e requer alguma atenção e disciplina da sua parte para
aproveitar essa minimização extra.
Minificar seu código de produção é importante porque melhora o desempenho da página, mas
você deve deixar essa tarefa para o minificador. É um erro tentar escrever código pré-minificado.
Você deve sempre usar nomes de variáveis descritivos, espaços em branco e recuos consistentes,
comentários e assim por diante. O código que você escrever será lido (por humanos), então facilite
a compreensão rápida para o mantenedor e deixe o minificador (a máquina) cuidar da redução do
tamanho dos arquivos.
36 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
Executar JSLint
O que o JSLint procura? Ele procura violações de alguns dos padrões discutidos neste capítulo
( padrão de variável única, base para parseInt(), sempre usando chaves) e muitas outras violações,
incluindo:
• Código inacessível
O 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 web e como código para download para
diversas 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 o JSLint e integrá-lo ao seu editor de texto para que você crie o hábito de
executá-lo sempre que salvar um arquivo. (Criar um atalho de teclado para ele também pode
ajudar.)
Resumo
Neste capítulo, analisamos 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 de todos ao seu redor. Em seguida, abordamos algumas das
melhores práticas e padrões essenciais, incluindo:
Resumo | 37
Machine Translated by Google [Link]
38 | Capítulo 2: Essenciais
Machine Translated by Google [Link]
CAPÍTULO 3
Literais e Construtores
Os padrões de notação literal disponíveis em JavaScript permitem definições de objetos mais concisas,
expressivas e menos propensas a erros. Este capítulo discute literais como objetos, arrays e expressões
regulares, e por que eles são preferíveis ao uso de funções construtoras integradas equivalentes, como
Object() e Array(). O formato JSON é apresentado para demonstrar como literais de arrays e objetos
são usados para definir um formato de transferência de dados. O capítulo também discute construtores
personalizados e maneiras de impor o 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), há uma
discussão sobre os construtores wrapper integrados Number(), String() e Boolean() e como eles se
comparam aos valores primitivos number, string e boolean. Por fim, há uma breve observação sobre o
uso dos diferentes construtores integrados Error() .
Objeto Literal
Ao pensar em objetos em JavaScript, pense simplesmente em tabelas hash de pares chave-valor
(semelhantes ao que chamamos de "matrizes associativas" em outras linguagens). Os valores podem
ser primitivos ou outros objetos; em ambos os casos, são chamados de propriedades. Os valores
também podem ser funções, e nesse caso são chamados de métodos.
Os objetos personalizados que você cria em JavaScript (ou seja, 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 funcionalidades a ele
à medida que avança. A notação literal de objeto é ideal para esse tipo de criação de objetos sob demanda.
// adicione uma
propriedade [Link] = "Benji";
39
Machine Translated by Google [Link]
// agora adicione um
método [Link] = function
() { return [Link];
};
No exemplo anterior, você começa com uma tela em branco — um objeto em branco. Em seguida, você
adiciona uma propriedade e um método a ele. A qualquer momento na vida do programa, você pode:
[Link] = function () { //
redefine o método para retornar //
um valor codificado
return "Fido";
};
• Remover propriedades/métodos completamente:
excluir [Link];
• Adicione mais propriedades e métodos:
[Link] = function ()
{ return "Au!";
};
[Link] = verdadeiro;
Não é necessário começar com um objeto vazio. O padrão literal de objeto permite adicionar
funcionalidade ao objeto no momento da criação, como demonstra o próximo exemplo.
var dog =
{ nome: "Benji",
getName: function ()
{ return [Link];
}
};
você não está acostumado com a notação literal de objeto, ela pode parecer um pouco estranha no começo,
mas quanto mais você a usar, mais vai gostar. Em essência, as regras de sintaxe são:
propriedades e métodos dentro do objeto por vírgula. Uma vírgula final após o par nome-valor é permitida,
mas causa erros no IE, portanto, não a utilize.
classes em JavaScript e isso permite grande flexibilidade, porque você não precisa saber nada sobre
seu objeto com antecedência; você não precisa de um "modelo" de classe.
Mas JavaScript também tem funções construtoras, que usam sintaxe semelhante à criação de objetos
baseada em classes 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:
Como você pode ver neste exemplo, um benefício óbvio da notação literal é que ela é mais curta de
digitar. Outro motivo pelo qual o literal é o padrão preferido para a criação de objetos é que ele enfatiza
que os objetos são simplesmente hashes mutáveis e não algo que precisa ser criado a partir de uma
"receita" (de uma classe).
Outro motivo para usar literais em vez do 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 desde o local onde você está chamando Object() até encontrar o
construtor global Object .
Você não tem motivo para usar o construtor new Object() quando pode usar um literal de objeto, mas
pode estar herdando código legado escrito por outros, portanto, você deve estar ciente de um "recurso"
deste construtor (ou ainda outro motivo para não usá-lo). O recurso em questão é que o construtor
Object() aceita um parâmetro e, dependendo do valor passado, pode decidir delegar a criação do objeto
a outro construtor interno 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 [Link]
// um objeto numérico
var o = new Object(1);
[Link]([Link] === Number); // true
[Link]([Link](2)); // "1.00"
// um objeto string
var o = new Object("Eu sou uma string");
[Link]([Link] === String); // true // objetos
normais não têm um método substring(), // mas
objetos string têm [Link](typeof
[Link]); // "função"
// um objeto booleano
var o = new Object(true);
[Link]([Link] === Boolean); // true
Esse comportamento do construtor Object() pode levar a resultados inesperados quando o valor
passado a ele é dinâmico e não é conhecido até o tempo de execução. Novamente, para concluir,
não use new Object(); em vez disso, use o literal de objeto mais simples e confiável.
Além do padrão literal de objeto e das funções construtoras integradas, você pode criar objetos
usando suas próprias funções construtoras personalizadas, como demonstra o exemplo a seguir:
Este 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 construtora Person pode ser definida.
Quando você invoca a função construtora com new, o seguinte acontece dentro da função:
// retornar isto;
};
Para simplificar, neste exemplo, o método say() foi adicionado . O resultado é que sempre
que você chama new Person(), uma nova função é criada na memória. Isso é obviamente
ineficiente, pois o método say() não muda de uma instância para a outra.
A melhor opção é adicionar o método ao protótipo de Person:
[Link]ó[Link] = função () { return "Eu sou
"
+ [Link];
};
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 no livro, mas vale a pena mencionar aqui
para garantir a completude. Dissemos que dentro do construtor algo assim acontece nos
bastidores:
// var isto = {};
Isso não é toda a verdade, porque o objeto "vazio" não está realmente vazio; ele herdou do
protótipo da Pessoa . Então é mais como:
// var this = [Link]([Link]);
Quando invocada com new, uma função construtora sempre retorna um objeto; por padrão, é
o objeto referenciado por this. Se você não adicionar nenhuma propriedade a this dentro do
seu construtor, um objeto "vazio" será retornado ("vazio", exceto por herdar do protótipo do
construtor).
Construtores retornam implicitamente this, mesmo quando você não tem uma instrução return
na função. Mas você pode retornar qualquer outro objeto de sua escolha. No próximo
exemplo, um novo objeto referenciado por that é criado e retornado.
};
// teste
var o = new Objectmaker();
[Link]([Link]); // "E é isso"
Como você pode ver, você tem a liberdade de retornar qualquer objeto em seus construtores,
desde que seja um objeto. Tentar retornar algo que não seja um objeto (como uma string ou
um booleano "false", por exemplo) não causará erro, mas será simplesmente ignorado, e o
objeto referenciado por "this" será retornado em seu lugar.
Quando seu construtor tem algo como [Link] e você o invoca sem new, você está, na
verdade, criando uma nova propriedade do objeto global chamada member , acessível por
meio de [Link] ou simplesmente member. Esse comportamento é altamente
indesejável, pois você sabe que deve sempre se esforçar para manter o namespace global
limpo.
// função
construtora Waffle()
{ [Link] = "yummy";
}
// um novo objeto
var bom_morning = new Waffle();
[Link](typeof bom_morning); // "objeto"
[Link](bom_morning.stastes); // "gostoso"
// antipadrão: //
esquecido `novo`
var bom_morning = Waffle();
[Link](typeof bom_morning); // "indefinido"
[Link]([Link]); // "gostoso"
Esse comportamento indesejado é corrigido no ECMAScript 5 e, no modo estrito, 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 tal, 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 nos nomes dos construtores (MyConstructor) e em minúscula
em funções e métodos “normais” (myFunction).
Usar that
Seguir uma convenção de nomenclatura pode certamente ajudar, mas apenas sugere e não impõe o
comportamento correto. Aqui está um padrão que ajuda você a garantir que seu construtor sempre se
comporte como um construtor. Em vez de adicionar todos os membros a this, você os adiciona a that e então
retorna that.
Para objetos mais simples, você nem precisa de uma variável local como essa; você pode simplesmente
retornar um objeto de um literal como este:
function Waffle()
{ return
{ tastes: "yummy"
};
}
Usando qualquer uma das implementações acima, Waffle() sempre retorna um objeto, independentemente
de como ele é chamado:
O problema com esse padrão é que o link para o protótipo é perdido, então quaisquer membros que você
adicionar ao protótipo Waffle() não estarão disponíveis para os objetos.
Observe que o nome da variável é apenas uma convenção; não faz parte da linguagem. Você pode
usar qualquer nome, enquanto outros nomes de variáveis comuns incluem self e me.
Construtor de Autoinvocação.
Para resolver 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, caso contrário, o construtor se autoinvoca novamente,
desta vez corretamente com new:
função Waffle() {
[Link] = "delicioso";
}
[Link] = verdadeiro;
[Link]([Link]); // "delícia"
[Link]([Link]); // "delícia"
[Link]([Link]); // verdadeiro
[Link]([Link]); // verdadeiro
Este padrão aproveita o fato de que, dentro de cada função, um objeto chamado arguments é
criado, contendo todos os parâmetros passados à função quando ela foi invocada. E arguments
tem uma propriedade chamada callee, que aponta para a função que foi chamada. Esteja ciente
de que [Link] não é permitido no modo estrito do ES5, portanto, é melhor limitar seu
uso futuro e também remover quaisquer instâncias que você encontrar no código existente.
Literal de matriz
Arrays em JavaScript, como a maioria das outras coisas na linguagem, são objetos. Eles podem
ser criados com a função construtora interna Array(), mas também possuem uma notação
literal e, assim como a literal de objeto, a notação literal de array é mais simples e preferível.
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 de Literal de
Matriz : A notação literal de matriz não é muito extensa: é apenas uma lista de elementos
delimitada por vírgulas, e toda a lista é delimitada por colchetes. Você pode atribuir qualquer
tipo de valor aos elementos da matriz, incluindo objetos ou outras matrizes.
A sintaxe literal de array é simples, direta e elegante. Afinal, um array é apenas uma lista de
valores indexados a 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 do array. Isso significa
que new Array(3) cria um array com comprimento 3, mas sem elementos reais. Se você tentar
acessar qualquer um dos elementos, obterá o valor undefined 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];
[Link]([Link]); // 1
[Link](a[0]); // 3
Embora esse comportamento possa ser um pouco inesperado, ele piora quando você passa
um número de ponto flutuante para new Array() em vez de um inteiro. Isso resulta em um erro
porque o ponto flutuante não é um valor válido para o comprimento do array:
// usando literal de array
var a = [3.14];
[Link](a[0]); // 3.14
Matriz Literal | 47
Machine Translated by Google [Link]
Para evitar possíveis erros ao criar matrizes dinâmicas em tempo de execução, é muito mais
seguro usar a notação literal de matriz.
Verifique se há array-
ness. Usar o operador typeof com operandos de array retorna “objeto”.
[Link](typeof [1, 2]); // "objeto"
Embora esse comportamento faça sentido (matrizes são objetos), não é muito útil. Muitas vezes, você precisa saber se um valor é
realmente uma matriz. Às vezes, você pode ver código verificando a presença da propriedade length ou de algum método de matriz,
como slice(), para determinar a "qualidade de uma matriz". Mas essas verificações não são robustas, pois não há razão para que
um objeto que não seja uma matriz não tenha propriedades e métodos com os mesmos nomes. Além disso, às vezes, as pessoas
usam a matriz instanceof, mas essa verificação funciona incorretamente quando usada entre quadros em algumas versões do IE.
Se este novo método não estiver disponível em seu ambiente, você pode fazer a verificação
chamando o método [Link]() . Se você invocar o método call() de toString
no contexto de um array, ele deverá retornar a string "[object Array]". Se o contexto for um
objeto, ele deverá retornar a string "[object Object]". Então, você pode fazer algo assim: if
(typeof
[Link] === "undefined") { [Link]
= function (arg) {
retornar [Link](arg) === "[matriz de objetos]";
};
}
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 prático para trabalhar em diversas linguagens, especialmente em JavaScript.
Na verdade, não há nada de novo para aprender sobre JSON. É apenas uma combinação da notação
de array e literal de 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 literal de objeto é que os nomes de propriedades
precisam estar entre aspas para serem JSON válidos. Em literais de objeto, as aspas são necessárias
apenas quando os nomes de propriedades não são identificadores válidos; por exemplo, quando
contêm espaços {"first name": "Dave"}.
Em strings JSON você não pode usar funções ou literais de expressões regulares.
Como mencionado no capítulo anterior, não é recomendado avaliar cegamente qualquer string JSON com eval() devido
às implicações de segurança. É melhor usar o método [Link]() , que faz parte da linguagem desde o ES5 e é
fornecido nativamente pelos mecanismos JavaScript em navegadores modernos. Para mecanismos JavaScript mais
antigos, você pode usar a biblioteca [Link] (http:// [Link]/ [Link]). para obter acesso ao objeto JSON e seus
métodos.
// antipadrão
var data = eval('(' + jstr + ')');
// var data
preferencial = [Link](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 [Link] adicional. Por exemplo, usando o YUI3,
você pode fazer:
// uma string JSON de
entrada var jstr = '{"mykey": "my value"}';
JSON | 49
Machine Translated by Google [Link]
O oposto do método [Link]() é [Link](). Ele recebe qualquer objeto ou array (ou um
primitivo) e o serializa em uma string JSON.
var dog =
{ nome: "Fido",
data de nascimento:
nova data(), 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 da expressão regular é mais curta e não força você 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 aspas e, frequentemente,
precisa escapar duas vezes as barras invertidas, como mostrado no trecho anterior, onde
precisamos de quatro barras invertidas para corresponder a uma única. Isso torna seus padrões de
expressões regulares mais longos e difíceis de ler e modificar. Expressões regulares já são bastante
complexas para começar, e qualquer chance de simplificá-las é bem-vinda, então é melhor usar a
notação literal.
• g—Correspondência global •
m—Multilinha
Usar o literal de expressão regular ajuda a escrever código mais conciso ao chamar métodos como
[Link]() que aceitam objetos de expressão regular como parâmetros.
Um motivo para usar new RegExp() é que o padrão não é conhecido antecipadamente, mas é criado como uma
string em tempo de execução.
Outra distinção entre o literal de expressão regular e o construtor é que o literal cria um objeto apenas uma vez
durante o tempo de análise. Se você criar a mesma expressão regular em um loop, o objeto criado anteriormente
será retornado com todas as suas propriedades (como lastIndex) já definidas desde a primeira vez. Considere o
exemplo a seguir como uma ilustração de como o mesmo objeto é retornado duas vezes.
E uma última observação: chamar RegExp() sem new (como uma função, não como um construtor) se comporta
da mesma forma que com new.
Envoltórios primitivos
JavaScript possui cinco tipos de valores primitivos: number, string, boolean, null e undefined.
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 internos 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;
[Link](typeof n); // "número"
// um objeto Number
var nobj = new Number(100);
[Link](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(). 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 com primitivos — assim que você invoca um método, o primitivo é temporariamente
convertido em um objeto em segundo plano e se comporta como se fosse um objeto. // uma string
primitiva pode ser usada como um objeto var s =
"hello"; [Link]([Link]()); // "HELLO"
Como os primitivos podem atuar como objetos assim que você precisar deles, muitas vezes
não há motivo para usar os construtores wrapper mais verbosos. Por exemplo, você não
precisa escrever new String("hi"); quando pode simplesmente usar "hi":
// evite estes: var s
= new String("minha string"); var n = new
Number(101); var b = new
Boolean(true);
Um motivo para usar objetos wrapper é quando você deseja aumentar o valor e persistir o
estado. Como primitivos não são objetos, eles não podem ser aumentados com propriedades.
No trecho anterior, greet foi convertido temporariamente em um objeto para que o acesso à
propriedade/método funcionasse sem erros. Por outro lado, se greet fosse definido como um objeto
usando new String(), a propriedade smile aumentada teria funcionado conforme o esperado.
Aumentar uma string, um número ou um valor booleano raramente é usado e, a menos que seja
isso que você precisa, provavelmente não precisa dos construtores wrapper.
Quando usados sem new, os construtores wrapper convertem o argumento passado a eles em um
valor primitivo: typeof
Number(1); // "número" typeof
Number("1"); // "número" typeof
Number(new Number()); // "número" typeof
String(1); // "string" typeof
Boolean(1); // "boolean"
Objetos de erro
JavaScript possui diversos 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 de nome da função construtora que criou o objeto; pode ser o “Error” geral ou
um construtor mais especializado, como “RangeError”
mensagem
A string passada ao 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 do navegador implementadas de forma
inconsistente entre 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. Esses 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 com seus objetos
de erro personalizados e usá-los para restaurar o estado normal do aplicativo.
Objetos de erro | 53
Machine Translated by Google [Link]
try
{ // algo ruim aconteceu, lance um erro throw
{ name:
"MyErrorType", // tipo de erro personalizado
message: "oops",
extra: "Isso foi bastante embaraçoso",
remedy: genericErrorHandler // quem deve lidar com isso
}; } catch (e)
{ // informar o usuário
alert([Link]); // "oops"
Os construtores de erro invocados como funções (sem new) se comportam da mesma forma que os
con-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 abordou:
• Notação literal de objeto — Uma maneira elegante de criar objetos como pares de chave-valor
delimitados por vírgulas, envoltos em chaves.
• Funções de construtor — Construtores integrados (que quase sempre têm uma notação literal
melhor e mais curta) e construtores personalizados
Em geral, com exceção do construtor Date() , raramente há necessidade de usar os outros construtores
integrados. A tabela a seguir resume esses construtores e seus padrões literais correspondentes e
preferenciais.
lançar erro("uh-oh");
Resumo | 55
Machine Translated by Google [Link]
Machine Translated by Google [Link]
CAPÍTULO 4
Funções
Dominar funções é uma habilidade essencial para o programador JavaScript, pois a linguagem oferece
muitas utilidades para elas. Elas realizam 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,
sobre expressões e declarações de função e verá como funcionam o escopo local e o içamento de
variáveis. Em seguida, você aprenderá sobre diversos padrões que auxiliam suas APIs (fornecendo
interfaces melhores para suas funções), inicializações de código (com menos variáveis globais) e
desempenho (em outras palavras, evitando trabalho).
Vamos nos aprofundar nas funções, começando pela revisão e esclarecimento dos conceitos básicos 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.
Portanto, 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. À primeira vista, são muitas funções para acompanhar. Mas quando
você se sente confortável com as várias aplicações das funções, passa a apreciar o poder, a
flexibilidade e a expressividade.
57
Machine Translated by Google [Link]
que as funções podem oferecer. Em geral, quando você pensa em uma função em JavaScript, pense em
um objeto, cuja única característica especial é que esse objeto é invocável, ou seja, pode ser executado.
O fato de que funções são objetos se torna óbvio quando você vê o construtor new Function() em ação:
// antipadrão //
apenas para fins de
demonstração var add = new Function('a, b', 'return
a + b'); add(1, 2); // retorna 3
Neste 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()) , pois o código é passado como uma
string e avaliado. Também é inconveniente de escrever (e ler), pois você precisa escapar aspas e tomar
cuidado extra se quiser recuar o código dentro da função corretamente para facilitar a leitura.
A segunda característica importante é que as funções fornecem escopo. Em JavaScript, não há escopo
local para chaves; em outras palavras, blocos não criam escopo. Há apenas escopo para funções.
Qualquer variável definida com var dentro de uma função é uma variável local, invisível fora da função.
Dizer que chaves não fornecem escopo local significa que, se você definir uma variável com var dentro
de uma condição if ou dentro de um loop for ou while , isso não significa que a variável seja local para
aquele if ou for. Ela é local apenas para a função de encapsulamento e, se não houver função de
encapsulamento, ela se torna uma variável global.
Conforme discutido no Capítulo 2, minimizar o número de globais é um bom hábito, portanto, funções
são indispensáveis quando se trata de manter o escopo da variável sob controle.
dedicar um momento para discutir a terminologia que envolve o código usado para definir uma função,
porque usar nomes precisos e acordados é tão importante quanto o código quando falamos sobre
padrões.
O código anterior mostra uma função que usa uma expressão de função nomeada.
Se você pular o nome (a segunda adiçã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 [Link]
Portanto, o termo mais amplo é “expressão de função” e a “expressão de função nomeada” é um caso
específico de uma expressão de função, que define o nome opcional.
Ao omitir o segundo add e terminar 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 está amplamente disponível em muitos ambientes. Se você mantiver o segundo add, a
propriedade [Link] conterá a string "add". A propriedade name é útil ao usar depuradores, como o
Firebug, ou ao chamar a mesma função recursivamente a partir dela mesma; caso contrário, você pode
simplesmente ignorá-la.
Por fim, temos as 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á uma diferença de sintaxe entre os dois no ponto e vírgula final. O ponto e vírgula não é necessário em
declarações de funções, mas é obrigatório em expressões de funções, e você deve sempre usá-lo, mesmo
que o mecanismo automático de inserção de ponto e vírgula possa fazer isso por você.
deve usar — declarações de função ou expressões de função? Nos casos em que sintaticamente não é
possível usar uma declaração, esse dilema está resolvido. Exemplos incluem passar um objeto de função
como parâmetro ou definir métodos em literais de objeto:
Contexto | 59
Machine Translated by Google [Link]
Declarações de função só podem aparecer em "código de programa", ou seja, dentro dos corpos
de outras funções ou no espaço global. Suas definições não podem ser atribuídas a variáveis
ou propriedades, nem aparecer em invocações de função como parâmetros. Aqui está um
exemplo do uso permitido de declarações de função, onde todas as funções foo(), bar() e local()
são definidas usando o padrão de declaração de função: // escopo
global função
foo() {}
função local() { //
escopo local
função bar() {}
return bar;
}
[Link]; // "foo"
[Link]; // ""
[Link]; // "baz"
60 | Capítulo 4: Funções
Machine Translated by Google [Link]
O argumento contra declarações de função e a razão para preferir expressões de função é que
as expressões destacam que funções são objetos como todos os outros objetos e não alguma
construção de linguagem especial.
Elevação de Funções:
Da discussão anterior, você pode concluir que o comportamento das declarações de funções é
praticamente equivalente ao de uma expressão de função nomeada . Isso não é exatamente
verdade, e a diferença reside no comportamento de elevação.
Como você sabe, todas as variáveis, independentemente de onde sejam declaradas no corpo da
função, são içadas para o topo da função nos bastidores. O mesmo se aplica às funções, pois
são apenas objetos atribuídos a variáveis. O único problema é que, ao usar uma declaração de
função, a definição da função também é içada, não apenas sua declaração. Considere este
trecho: // antipattern // apenas para
ilustração
// funções globais
function foo()
{ alert('global foo');
} função bar()
{ alert('barra global');
}
função hoistMe() {
// declaração de função: //
a variável 'foo' e sua implementação são içadas
Contexto | 61
Machine Translated by Google [Link]
função foo()
{ alerta('local foo');
}
// expressão de função: //
somente a variável 'bar' é içada // não a
implementação var bar =
function () { alert('local
bar');
};
} hoistMe();
Neste exemplo, você vê que, assim como acontece com variáveis normais, a mera presença de foo e
bar em qualquer lugar da função hoistMe() as move para o topo, sobrescrevendo as funções globais
foo e bar. A diferença é que a definição local de foo() é elevada para o topo e funciona bem; embora
seja definida posteriormente. A definição de bar() não é elevada, apenas sua declaração. É por isso
que, até que a execução do código alcance a definição de bar() , ela permanece indefinida e não pode
ser usada como função (embora ainda impeça que a função global bar() seja "vista" na cadeia de
escopo).
Agora que já abordamos o contexto e a terminologia necessários em torno das funções, vamos ver
alguns dos bons padrões relacionados a funções que o JavaScript oferece, começando pelo padrão
de retorno de chamada. Novamente, é importante lembrar as duas características especiais das
funções em JavaScript:
Funções são objetos, o que significa que podem ser passadas como argumentos para outras funções.
Quando você passa a função introduceBugs() como parâmetro para a função writeCode(), em algum
momento é provável que writeCode() execute (ou chame) introduceBugs(). Nesse caso, introduceBugs()
é chamada de função de retorno de chamada ou simplesmente uma função de retorno de chamada.
ligar de volta:
function writeCode(callback) { //
faça algo...
callback(); // ...
escreverCódigo(introduzirBugs);
62 | Capítulo 4: Funções
Machine Translated by Google [Link]
Observe como introduceBugs() é passado como argumento para writeCode() sem os parênteses.
Os parênteses executam uma função, enquanto neste caso queremos passar apenas uma
referência à função e deixar que writeCode() a execute (em outras palavras, a chame de volta)
quando apropriado.
Um Exemplo de
} retornar nós;
};
É uma boa ideia manter esta função genérica e fazer com que ela simplesmente retorne um
array de nós DOM, sem fazer nada com os elementos em si. A lógica de modificação de nós
poderia estar em uma função diferente, por exemplo, uma função chamada hide() que, como o
nome sugere, oculta os nós da página:
var hide = função (nós) {
var i = 0, max = nó[Link]; para (;
i < max; i += 1)
{ nós[i].[Link] = "nenhum";
}
};
// executando as funções
hide(findNodes());
Esta implementação é ineficiente, pois 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 deixará de ser uma função genérica devido ao acoplamento da lógica de
recuperação e modificação. Entre no padrão de retorno de chamada — você passa a lógica de
ocultação de nós como uma função de retorno de chamada e delega sua execução:
// findNodes() refatorado para aceitar um retorno de
chamada var findNodes = function (callback)
{ var i = 100000,
nodes = [],
found;
enquanto (i)
{ i -= 1;
if (retorno de chamada)
{ retorno de chamada(encontrado);
nó[Link](encontrado);
} retornar nós;
};
A implementação é simples; a única tarefa adicional que findNodes() executa é 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 danificará o código antigo
que depende da API antiga.
A implementação hide() será muito mais simples agora porque não precisa fazer um loop pelos nós: //
uma função de
retorno de chamada var
hide = function (node) {
[Link] = "nenhum";
};
O retorno de chamada pode ser uma função existente, como 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 retorno de
chamada anônimo findNodes(function
(node) { [Link] =
"block"; });
Nos exemplos anteriores, a parte onde o retorno de chamada é executado era assim:
callback(parâmetros);
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 é uma
64 | Capítulo 4: Funções
Machine Translated by Google [Link]
método de um objeto. Se o método de retorno de chamada usar this para se referir ao objeto ao qual pertence,
isso pode causar um comportamento inesperado.
Imagine que o retorno de chamada é a função paint(), que é um método do objeto chamado
meuaplicativo:
"function") { callback(found);
} // ...
};
Se você chamar findNodes([Link]), não funcionará como esperado, pois [Link] não estará definido.
O objeto this se referirá ao objeto global, pois findNodes() é uma função global. Se findNodes() fosse um
método de um objeto chamado dom (como [Link]()), então this dentro do retorno de chamada se
referiria a dom em vez do myapp esperado.
A solução para esse problema é passar a função de retorno de chamada e, além disso, passar o objeto ao
qual esse retorno de chamada pertence:
findNodes([Link], meuaplicativo);
Então você também precisa modificar findNodes() para vincular o objeto que você passou:
} // ...
};
Haverá mais sobre os tópicos de vinculação e uso de call() e apply() nos próximos capítulos.
Outra opção para passar um objeto e um método para ser usado como retorno de chamada é passar o método
como uma string, para não repetir o objeto duas vezes. Em outras palavras: findNodes([Link],
myapp);
pode se tornar:
findNodes("pintura", meuaplicativo);
//...
se (tipo de retorno de chamada === "função") {
[Link](callback_obj, encontrado);
} // ...
};
de retorno de chamada tem muitos usos cotidianos; por exemplo, ao anexar um ouvinte de eventos a um elemento
em uma página, você está, na verdade, 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 [Link]() é passado como
um retorno de chamada ao ouvir o evento de clique 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 de carregamento . Em seguida, o usuário interage com a página e aciona
vários eventos, como clique, pressionamento de tecla, passagem do mouse, movimentação do mouse e assim
por diante. 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 ligações de todos os
candidatos o tempo todo. No JavaScript assíncrono orientado a eventos, ocorre um fenômeno semelhante. Só
que, em vez de fornecer seu número de telefone, você fornece uma função de retorno de chamada para ser
chamada quando for a hora certa. Você pode até fornecer mais retornos de chamada do que o necessário, pois
certos eventos podem nunca acontecer.
Por exemplo, se o usuário nunca clicar em “Comprar agora!”, sua função que valida o formato do número do
cartão de crédito nunca será chamada de volta.
Tempos limite
Outro exemplo do padrão de retorno de chamada em ação é quando você usa os métodos de tempo limite
fornecidos pelo objeto de janela do navegador: setTimeout() e setInterval(). Esses métodos também aceitam e
executam retornos de chamada: var thePlotThickens =
};
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 quer apontar para ela
66 | Capítulo 4: Funções
Machine Translated by Google [Link]
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 compõe uma biblioteca de software deve ser o mais genérico e reutilizável possível, e os
retornos de chamada podem ajudar nessa generalização. Você não precisa prever e implementar todos
os recursos que imaginar, pois isso sobrecarregará a biblioteca e a maioria dos usuários nunca precisará
de uma grande parte desses recursos. Em vez disso, concentre-se na funcionalidade principal e forneça
"ganchos" na forma de retornos de chamada, o que permitirá que os métodos da biblioteca sejam
facilmente desenvolvidos, estendidos e personalizados.
Retornando funções
Funções são objetos, portanto, podem ser usadas como valores de retorno. Isso significa que uma função
não precisa retornar nenhum tipo de valor de dado ou matriz 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 realiza algum trabalho, provavelmente uma inicialização
única, e então trabalha em seu valor de retorno. O valor retornado é outra função, que também pode ser
executada:
// usando a função de
configuração var my = setup(); //
alertas 1 my(); // alertas 2
Como setup() encapsula a função retornada, ele cria um fechamento, e você pode usar esse fechamento
para armazenar alguns dados privados, que são acessíveis 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();
next(); // retorna 1
Retornando Funções | 67
Machine Translated by Google [Link]
próximo(); // 2
próximo(); // 3
Funções autodefinidas
Funções podem ser definidas dinamicamente e 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á sobrescrevendo a função
antiga pela nova. De certa forma, você estará reciclando o ponteiro de função antigo para apontar
para uma nova função. E tudo isso pode acontecer dentro do corpo da função antiga. Nesse caso,
a função se sobrescreve e se redefine com uma nova implementação. Isso provavelmente parece
mais complicado do que é; vejamos um exemplo simples: var scareMe = function () { alert("Boo!");
scareMe = function
() { alert("Double boo!");
};
};
Este padrão é útil quando sua função tem algum trabalho preparatório inicial a ser feito e precisa
fazê-lo apenas uma vez. Como não há motivo para repetir o trabalho quando ele pode ser evitado,
uma parte da função pode não ser mais necessária. Nesses casos, a função autodefinida pode
atualizar sua própria implementação.
Usar esse padrão pode obviamente ajudar no desempenho do seu aplicativo, porque sua função
redefinida simplesmente faz menos trabalho.
Vamos ver um exemplo em que a função scareMe() é usada da mesma forma que um objeto de
primeira classe seria usado:
68 | Capítulo 4: Funções
Machine Translated by Google [Link]
// chamando como um
método [Link](); //
"Boo!" [Link](); //
"Boo!" [Link]([Link]); // "corretamente"
Como você pode ver, a autodefinição não ocorreu como você provavelmente esperava quando a
função foi atribuída a uma nova variável. Toda vez que prank() era chamado, ele emitia o alerta "Boo!".
Ao mesmo tempo, ele sobrescreveu a função global scareMe() , mas a própria função prank()
continuou visualizando a definição antiga, incluindo a propriedade property. O mesmo aconteceu
quando a função foi usada como o método boo() do objeto assustador . Todas essas invocações
continuaram reescrevendo o ponteiro global scareMe(), de modo que, quando finalmente chamado,
ele tinha o corpo atualizado alertando "Double boo" desde a primeira vez. Também não era mais
possível visualizar [Link].
Funções Imediatas
O padrão de função imediata é uma sintaxe que permite executar uma função assim que ela é
definida. Veja um exemplo:
(function ()
{ alert('cuidado!'); }());
Este 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 [Link]
• 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 o posicionamento dos parênteses de fechamento), mas o JSLint
prefere a primeira: (function () { alert('watch out!'); })();
Esse padrão é útil porque fornece uma área restrita 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
carrega, 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á motivo para criar uma função nomeada reutilizável. Mas o código também
requer algumas variáveis temporárias, que você não precisará 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
encapsular 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 estivesse encapsulado 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, às 23h26min59s GMT-0800 (PST)
70 | Capítulo 4: Funções
Machine Translated by Google [Link]
Normalmente, o objeto global é passado como um argumento para a função imediata para que ele
seja acessível dentro da função sem precisar usar window: dessa forma, o código fica 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 se tornar um fardo ficar rolando constantemente para o topo e para o final da função para
entender como ela funciona.
{ return 2 + 2; }());
Outra maneira de conseguir o mesmo é omitir os parênteses que envolvem a função, pois eles não
são necessários ao atribuir o valor de retorno de uma função imediata a uma variável. Omitir o
primeiro conjunto de parênteses resulta no seguinte:
var resultado = função () { return 2
+ 2; }();
Esta sintaxe é mais simples, mas pode parecer um pouco enganosa. Se não notar o () no final da
função, alguém que esteja lendo o código pode pensar que "result" aponta para uma função. Na
verdade, "result" aponta para o valor retornado pela função imediata, neste caso, o número 4.
Os exemplos anteriores retornaram um valor inteiro primitivo como resultado da execução da função
imediata. Mas, em vez de um valor primitivo, uma função imediata pode retornar qualquer tipo de
valor, incluindo outra função. Você pode então usar o escopo da função im-mediate para armazenar
privadamente alguns dados, específicos da função interna retornada.
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, um valor que foi pré-calculado e
armazenado no fechamento da função imediata:
var getResult = (função () { var res = 2
+ 2;
Funções Imediatas | 71
Machine Translated by Google [Link]
retornar função ()
{ retornar res;
}; }());
Funções imediatas também podem ser usadas ao definir propriedades de objetos. Imagine que você
precisa definir uma propriedade que provavelmente nunca mudará durante a vida útil do objeto, mas
antes de defini-la, é preciso realizar um trabalho para descobrir o valor correto.
Você pode usar uma função imediata para encapsular esse trabalho, e o valor retornado pela função
imediata se tornará o valor da propriedade. O código a seguir mostra um exemplo:
var o =
{ mensagem: (função () { var
quem = "eu", o
que = "chamar";
""
retornar o que + + quem;
}()),
getMsg: função ()
{ retornar [Link];
}
}; // uso
[Link](); // "me ligue"
[Link]; // "me ligue"
Neste exemplo, [Link] é uma propriedade de string, não uma função, mas precisa de uma função,
que é executada enquanto o script está sendo carregado e que ajuda a definir a propriedade.
Benefícios e Uso O
padrão de função imediata é amplamente utilizado. Ele ajuda a encapsular uma quantidade de trabalho
que você 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 autoinvocadas 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 eles são executados em
qualquer página e manter o namespace global limpo (e seu código de bookmarklet discreto) é
essencial.
O padrão também permite que você agrupe recursos individuais em módulos autocontidos.
Imagine que sua página é estática e funciona bem sem JavaScript. Então, no espírito de aprimoramento
progressivo, você adiciona um trecho de código que aprimora a página de alguma forma.
Você pode encapsular esse código (também pode chamá-lo de “módulo” ou “recurso”) em um
72 | Capítulo 4: Funções
Machine Translated by Google [Link]
função e garantir que a página funcione bem com e sem ela. Em seguida, você pode adicionar mais
melhorias, removê-las, fazer testes de divisão, permitir que o usuário as desative e
breve.
Você pode usar o seguinte modelo 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
lançar o código no site, você decide quais recursos estão prontos para o lançamento e mescla os arquivos
correspondentes usando seu script de compilação.
({
// aqui você pode definir valores de configuração //
também conhecidos como constantes de
configuração
maxwidth: 600, maxheight: 400,
// inicializar init:
function ()
{ [Link]([Link]()); //
mais tarefas init...
} }).init();
Em termos de sintaxe, você aborda esse padrão como se estivesse criando um objeto normal usando o
literal de objeto. Você também envolve o literal entre parênteses (operador de agrupamento), o que instrui
o mecanismo JavaScript a tratar as chaves como literais de objeto, não como um bloco de código. (Não é
um loop if ou for .) Após fechar os parênteses, você invoca o método init() imediatamente.
Você também pode encapsular o objeto e a invocação init() em parênteses de agrupamento em vez de
encapsular apenas o objeto. Em outras palavras, ambos funcionam: ({...}).init(); ({...}.init());
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 complexo em
termos de sintaxe do que simplesmente encapsular um monte de código em uma função anônima, mas
se suas tarefas de inicialização forem mais complexas (como costumam ser), ele 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,
elas provavelmente são apenas funções espalhadas.
Uma desvantagem desse padrão é que a maioria dos minificadores JavaScript pode não minimizá-lo tão
eficientemente quanto o código simplesmente encapsulado 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 fazê-lo com segurança. No momento da escrita, o Compilador de Closure do Google em
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(){retornar isto.d+"x"+isto.c},b:function(){[Link](isto.a())}}).b();
Por exemplo, depois de verificar se XMLHttpRequest é suportado como um objeto nativo, não há chance
de o navegador subjacente mudar no meio da execução do seu 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
verificando (e chegando à mesma conclusão) toda vez que você precisar de outro objeto XHR.
Descobrir os estilos computados de um elemento DOM ou anexar manipuladores de eventos são outros
métodos que podem se beneficiar do padrão de ramificação em tempo de inicialização. A maioria dos
desenvolvedores já codificou — pelo menos uma vez na vida de programador 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 [Link]
// ANTES
var utils =
{ addListener: function (el, type, fn) { if
(typeof [Link] === 'function')
{ [Link](type, fn, false); }
else if (typeof [Link] === 'function') { // IE
[Link]('on' + type, fn); } else
{ // navegadores mais antigos
el['on' + type] = fn;
}
},
removeListener: function (el, type, fn) { //
praticamente o mesmo...
}
};
O problema com este código é que ele é um pouco ineficiente. Toda vez que você chama
[Link]() ou [Link](), as mesmas verificações são executadas repetidamente.
};
// a implementação if
(typeof [Link] === 'function')
{ [Link] = function (el, type, fn)
{ [Link](type, fn, false);
};
[Link] = função (el, tipo, fn)
{ [Link](tipo, fn, falso);
};
} senão se (tipo de [Link] === 'função') { // IE
[Link] = função (el, tipo, fn)
{ [Link]('on' + tipo, fn);
};
[Link] = função (el, tipo, fn)
{ [Link]('on' + tipo, fn);
Este é o momento de mencionar um alerta contra o sniffing de navegador. Ao usar esse padrão, não exagere nas
funcionalidades do navegador. Por exemplo, se você detectou que o navegador não suporta [Link],
não presuma que o navegador com o qual está lidando é o IE e que ele também não suporta XMLHttpRequest
nativamente, embora isso tenha sido verdade em algum momento na história do navegador. Pode haver casos em
que você pode presumir com segurança que as funcionalidades andam juntas, como .addEventListener
e .removeEventListener, mas, em geral, as funcionalidades do navegador mudam independentemente. A melhor
estratégia é detectar as funcionalidades separadamente e, em seguida, usar a ramificação em tempo de carregamento
para realizar 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, não seja necessário refazer cálculos potencialmente pesados. Armazenar em
cache os resultados de uma função também é conhecido como memorização.
No exemplo a seguir, a função myFunc cria um cache de propriedades, acessível normalmente via [Link]. A
propriedade cache é um objeto (um hash) onde o parâmetro param passado para a função é usado como chave e o
resultado do cálculo é o valor. O resultado pode ser qualquer estrutura de dados complexa que você possa precisar:
} retornar [Link][param];
};
// armazenamento em
cache [Link] = {};
O código anterior pressupõe que a função aceita apenas um parâmetro de argumento e que é um tipo de dado
primitivo (como uma string). Se você tiver mais parâmetros e parâmetros 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 no seu objeto de cache :
76 | Capítulo 4: Funções
Machine Translated by Google [Link]
resultado;
if (![Link][cachekey]) { result =
{}; // ... operação
cara ... [Link][cachekey] =
result;
// armazenamento em
cache [Link] = {};
Esteja ciente de que, na serialização, a "identidade" dos objetos é perdida. Se você tiver dois objetos
diferentes com as mesmas propriedades, ambos compartilharão a mesma entrada de cache.
Outra maneira de escrever a função anterior é usar [Link] para se referir à função, em vez de
codificar o nome da função. Embora isso seja possível atualmente, esteja ciente de que [Link]
não é permitido no modo estrito do ECMAScript 5: var myFunc = function (param) {
var f = [Link],
resultado;
if (![Link][param]) { result =
{}; // ... operação
cara ... [Link][param] = result;
} retornar [Link][parâmetro];
};
// armazenamento em
cache [Link] = {};
Objetos de configuração
O padrão de objeto de configuração é uma maneira de fornecer APIs mais limpas, especialmente se você
estiver criando uma biblioteca ou qualquer outro código que será consumido por outros programas.
É um fato que os requisitos de software mudam à medida que o software é desenvolvido e mantido. Muitas
vezes, 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 sobrenome
e adiciona uma pessoa a uma lista:
função addPerson(primeiro, último) {...}
Objetos de Configuração | 77
Machine Translated by Google [Link]
Mais tarde, você descobre que, na verdade, a data de nascimento também precisa ser armazenada,
assim como, opcionalmente, o gênero e o endereço. Então, você modifica a função adicionando os novos
parâmetros (colocando cuidadosamente os parâmetros opcionais no final da lista):
função addPerson(primeiro, último, data de nascimento, sexo, endereço) {...}
Neste ponto, a assinatura desta função já está ficando um pouco mais longa. E então você descobre que
precisa adicionar um nome de usuário, o que é absolutamente necessário, não opcional. Agora, quem
chama a função terá que passar até os parâmetros opcionais e tomar 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, de "configuração":
addPerson(conf);
Esse padrão pode ser útil quando sua função cria elementos DOM, por exemplo, ou na definição de
estilos CSS de um elemento, porque elementos e estilos podem ter um grande número de atributos e
propriedades, principalmente opcionais.
78 | Capítulo 4: Funções
Machine Translated by Google [Link]
Caril
O restante do capítulo discute o tópico de currying e aplicação de função parcial .
Mas antes de nos aprofundarmos neste tópico, vamos primeiro ver o que exatamente significa aplicação 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 como sendo aplicada. Em JavaScript, temos a mesma coisa — podemos aplicar uma função
usando o método [Link](), porque 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 têm o mesmo resultado. apply() recebe
dois parâmetros : o primeiro é um objeto a ser vinculado a this dentro da função; o segundo é um array ou
argumentos, que então se torna o objeto de argumentos , semelhante a um array, 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á referência nula passada (como no exemplo anterior). Aqui,
o objeto se torna o primeiro argumento de apply():
var alien =
{ sayHi: function (who)
{ return "Olá" + (quem ? ", " + quem : "") + "!";
}
};
No trecho anterior, this dentro de sayHi() aponta para alien. No exemplo anterior, this aponta para o objeto global.
Como os dois exemplos demonstram, verifica-se que o que pensamos sobre chamar uma função não é muito mais
do que um truque sintático, equivalente a uma aplicação de função.
Caril | 79
Machine Translated by Google [Link]
Observe que, além de apply(), há um método call() do objeto [Link] , mas ele ainda
é apenas um complemento de sintaxe sobre apply(). Às vezes, é melhor usar o complemento:
quando você tem uma função que recebe apenas um parâmetro, você pode economizar o
trabalho de criar arrays com apenas um elemento:
// o segundo é mais eficiente, salva um array
[Link](alien, ["humans"]); // "Olá, humanos!"
[Link](alien, "humans"); // "Olá, humanos!"
Aplicação Parcial:
Agora que sabemos que chamar uma função é, na verdade, aplicar um conjunto de argumentos
a ela, é possível passar apenas alguns argumentos, e não todos? 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 chegar a uma solução considerando que x é 5 e y é 4:
// para fins ilustrativos // JavaScript
inválido
// e sabemos os argumentos
add(5, 4);
Neste trecho, os passos 1 e 2 não são JavaScript válidos, mas é assim que você resolveria o
problema manualmente. Pegue o valor do primeiro argumento e substitua o valor desconhecido
x pelo valor conhecido 5 em toda a função. Em seguida, repita o mesmo procedimento até
esgotar os 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 sim outra função.
80 | Capítulo 4: Funções
Machine Translated by Google [Link]
};
// aplicação completa
[Link](null, [5, 4]); // 9
Como você pode ver, a aplicação parcial nos fornece outra função, que pode ser chamada com
os outros argumentos. Isso seria equivalente a algo como add(5)(4), pois add(5) retorna uma
função que pode ser chamada com (4). Novamente, o conhecido add(5, 4) pode ser considerado
pouco mais do que um adoçante sintático em vez de usar add(5)(4).
Agora, voltando à realidade: não existe o método partialApply() e funções em JavaScript não
se comportam assim por padrão. Mas você pode criá-las, porque JavaScript é dinâmico o
suficiente para permitir isso.
O processo de fazer uma função entender e lidar com aplicações parciais é chamado de
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 o nome dele.) 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 podemos schönfinkelificar (ou schönfinkelizar ou curry) uma função? Outras
linguagens funcionais podem ter isso incorporado na própria linguagem e todas as funções são
curried por padrão. Em JavaScript, podemos modificar a função add() para uma função curried
que lidará com a aplicação parcial.
Vejamos um exemplo: //
um add() curried //
aceita lista parcial de argumentos function
add(x, y) { var oldx =
x, oldy = y; if (typeof oldy
=== "undefined") { // retorno parcial function (newy)
{ return oldx + newy;
};
} // aplicação completa
return x + y;
}
// teste
Caril | 81
Machine Translated by Google [Link]
Neste trecho, na primeira vez que você chama add(), ele cria um fechamento em torno da função
interna que retorna. O fechamento armazena os valores originais x e y nas variáveis privadas oldx
e oldy. A primeira, oldx, é usada quando a função interna é executada. Se não houver aplicação
parcial e x e y forem passados, a função simplesmente os adiciona.
Esta implementação de add() é um pouco mais detalhada do que o necessário, apenas para fins
ilustrativos. Uma versão mais compacta é mostrada no próximo trecho, onde não há oldx e oldy,
simplesmente porque o x original é armazenado implicitamente na cláusula closure e reutilizamos
y como uma variável local em vez de criar uma nova variável newy como fizemos no exemplo
anterior:
// uma adição
curry // aceita lista parcial de argumentos
function add(x, y) { if
(typeof y === "undefined") { // retorno parcial
function (y) { return x +
y;
};
} // aplicação completa
return x + y;
}
Nestes exemplos, a própria função add() cuidou de aplicações parciais. Mas podemos fazer o
mesmo de 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 argumentos não são um array real em JavaScript. Pegar emprestado o método
slice() de [Link] nos ajuda a transformar argumentos em um array e trabalhar mais
82 | Capítulo 4: Funções
Machine Translated by Google [Link]
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, pois o primeiro
argumento é a função que está sendo processada. Então, schonfinkelize() retorna uma nova
função. Quando a nova função é chamada, ela tem acesso (por meio do fechamento) aos
argumentos já armazenados privadamente stored_args e à referência slice . A nova função
precisa mesclar apenas os argumentos antigos parcialmente aplicados (stored_args) com os
novos (new_args) e, em seguida, aplicá-los à função original fn (também disponível privadamente no fechamento).
Agora, armados com uma maneira geral de fazer qualquer função ser executada, 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); addOne(10, 10, 10, 10); // 41
var addSix = schonfinkelize(addOne, 2, 3);
addSix(5, 5); // 16
Caril | 83
Machine Translated by Google [Link]
Resumo
Em JavaScript, o conhecimento e o uso adequado de funções são essenciais. Este capítulo discutiu o
contexto e a terminologia relacionados a funções. Você aprendeu sobre dois recursos importantes das
funções em JavaScript, a saber:
1. Funções são objetos de primeira classe; elas podem ser passadas como valores e aumentadas com
propriedades e métodos.
2. Funções fornecem escopo local, o que outras chaves não oferecem. Também é importante lembrar
que as declarações de variáveis locais são elevadas ao topo da lista local.
escopo.
2. Expressões de função (as mesmas que as anteriores, mas sem nome), também conhecidas como
funções anônimas 3.
Depois de abordar o contexto e a sintaxe das funções, você aprendeu sobre uma série de padrões úteis,
que podem ser agrupados nas seguintes categorias:
1. Padrões de API, que ajudam você 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
argumento Objetos de
configuração Ajudar a manter o número de argumentos para uma função sob
controle Retornar
funções Quando o valor de retorno de uma função é outra função
Currying
Quando novas funções são criadas com base nas existentes, além de uma lista parcial de
argumentos
2. Padrões de inicialização, que ajudam a executar tarefas de inicialização e configuração (muito comuns
em páginas e aplicativos da web) de forma mais clara e estruturada, sem poluir o namespace global
com variáveis temporárias. Estes incluem: Funções imediatas: executadas assim que definidas.
Inicialização imediata de
objetos: tarefas de inicialização estruturadas
84 | Capítulo 4: Funções
Machine Translated by Google [Link]
Ramificação em tempo
de inicialização: ajuda a ramificar o código apenas uma vez durante a execução inicial, em vez de
muitas vezes posteriormente, durante a vida útil do
Funções autodefinidas
Sobrescrever-se com novos corpos para fazer menos trabalho a partir da segunda invocação e
depois
Resumo | 85
Machine Translated by Google [Link]
Machine Translated by Google [Link]
CAPÍTULO 5
Criar objetos em JavaScript é fácil — você usa o literal do objeto ou funções construtoras. Neste
capítulo, vamos além e vemos alguns padrões adicionais para a criação de objetos.
A linguagem JavaScript é simples e direta, e muitas vezes não há uma sintaxe específica 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 apresenta padrões
comuns para implementar, substituir ou simplesmente pensar de forma diferente sobre esses recursos.
Padrão de namespace
Os namespaces ajudam a reduzir o número de globais exigidos pelos nossos programas e, ao mesmo
tempo, também ajudam a evitar colisões de nomes ou prefixação excessiva de nomes.
JavaScript não possui namespaces incorporados à sintaxe da linguagem, mas esse é um recurso
bastante fácil de implementar. 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.
Assim, você pode adicionar todas as funcionalidades a esse objeto.
// ANTES: 5 globais
// Aviso: antipadrão
// construtores
função Parent() {}
função Child() {}
// uma variável
87
Machine Translated by Google [Link]
var alguma_var = 1;
// alguns objetos
var módulo1 = {};
módulo1. dados = {a: 1, b: 2}; var
módulo2 = {};
Você pode refatorar esse tipo de código criando um único objeto global para seu aplicativo, chamado, por
exemplo, MYAPP, e alterar todas as suas funções e variáveis para se tornarem propriedades do seu objeto
global:
// DEPOIS: 1 global
// construtores
[Link] = function () {};
[Link] = function () {};
// uma variável
MYAPP.some_var = 1;
// um contêiner de objetos
[Link] = {};
// objetos aninhados
[Link].module1 = {};
[Link] = {a: 1, b: 2};
[Link].module2 = {};
Para o nome do objeto de namespace global, você pode escolher, por exemplo, o nome do seu aplicativo
ou biblioteca, o nome do seu domínio ou o nome da sua empresa. Os desenvolvedores costumam usar a
convenção de escrever a variável global em LETRAS MAIÚSCULAS, para que ela se destaque para os
leitores do código. (Mas lembre-se de que letras maiúsculas também costumam ser usadas para constantes.)
Este padrão é uma boa maneira de criar namespaces para seu código e evitar conflitos de nomenclatura
em seu próprio código, bem como conflitos entre seu código e código de terceiros na mesma página, como
bibliotecas JavaScript ou widgets. Este padrão é altamente recomendado e perfeitamente aplicável a muitas
tarefas, mas apresenta 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 a instância 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 mais
curto var MYAPP = MYAPP || {};
Você pode ver como essas verificações adicionais podem rapidamente resultar em muita repetição de
código. Por exemplo, se você quiser definir [Link].module2, precisará 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 do namespace. Vamos chamar essa função de namespace() e usá-
la assim:
// usando uma função de namespace
[Link]('[Link].module2');
// equivalente a: //
var MYAPP = { //
módulos: { //
módulo2: {} // // };
}
Padrão de namespace | 89
Machine Translated by Google [Link]
pai = pai[partes[i]];
} retornar pai;
};
// namespace longo
[Link]('[Link]');
Declarando Dependências
Bibliotecas JavaScript são frequentemente modulares e com namespaces, o que permite
incluir apenas os módulos necessários. Por exemplo, no YUI2, há uma variável global
YAHOO, que serve como namespace, e módulos que são propriedades da variável
global, como [Link] (o módulo DOM) e [Link] (o módulo Events).
É uma boa ideia declarar os módulos dos quais seu código depende no topo da sua função ou módulo. A declaração
envolve a criação apenas de uma variável local e o apontamento para o módulo desejado:
Este é um padrão extremamente simples, mas ao mesmo tempo tem inúmeros benefícios:
• A declaração explícita de dependências sinaliza aos usuários do seu código as dependências específicas
arquivos de script que eles precisam ter certeza de que estão incluídos na página.
• 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 [Link]), 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, a variável local é usada, o que é muito mais rápido.
O trecho a seguir ilustra o 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 complexo por exigir mais linhas de código e uma variável
extra, na verdade resulta em menos código após a minificação, o que significa menos código para o usuário
baixar:
função test1()
{ alerta([Link].m1);
alerta([Link].m2);
alerta([Link].m51);
}
/*
corpo do teste1
minificado: alert([Link].m1);alert([Link].m2);alert([Link].m51)
*/
/*
corpo test2 minificado:
Declarando Dependências | 91
Machine Translated by Google [Link]
var a=[Link];alert(a.m1);alert(a.m2);alert(a.m51) */
var myobj =
{ minhaprop:
1, getProp: função ()
{ retornar [Link];
}
};
[Link]([Link]); // `myprop` é publicamente acessível
[Link]([Link]()); // 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()
{ [Link] = 'iPod';
[Link] = function () { return
'iPad';
};
Membros Privados
Embora a linguagem não tenha uma sintaxe especial para membros privados, você pode implementá-los
usando um fechamento. Suas funções construtoras criam um fechamento e quaisquer variáveis que façam 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 em que nome é um membro
privado, não acessível fora do construtor:
function Gadget() { //
membro privado var
name = 'iPod'; //
função pública
[Link] = function () { return
name;
};
Como você pode ver, é fácil alcançar privacidade em JavaScript. Tudo o que você precisa fazer é
encapsular os dados que deseja manter privados em uma função e garantir que eles sejam locais para
a função, o que significa não disponibilizá-los fora dela.
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 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ê acesse o escopo privado da função. Da
mesma forma, a propriedade __parent__ no Mozilla Rhino permite acesso ao escopo privado.
Esses casos extremos não se aplicam aos navegadores amplamente utilizados hoje em dia.
• 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 de Gadget parece
inocente:
função Gadget() { //
membro privado var
specs =
{ largura_da_tela: 320,
altura_da_tela: 480, cor:
"branco"
};
// função pública
[Link] = 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çõ[Link] = "preto";
especificaçõ[Link]ço = "grátis";
[Link]([Link]());
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 fazer 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 Autoridade
Mínima (POLA), que afirma que você nunca deve fornecer mais do que o necessário. Nesse caso,
se o consumidor de Gadget estiver interessado em saber se o gadget se encaixa em um
determinado padrão, ele precisa apenas das dimensões. Portanto, em vez de fornecer tudo, você
pode criar getDimensions(), que retorna um novo objeto contendo apenas largura e altura. Talvez
você não precise implementar getSpecs() .
Outra abordagem, quando você precisa passar todos os dados, é criar uma cópia do objeto specs
usando uma função de clonagem de objetos de uso geral. O próximo capítulo oferece duas dessas
funções — uma, chamada extend() , faz uma cópia superficial do objeto fornecido (copia apenas
os parâmetros de nível superior). A outra, chamada extendDeep(), faz uma cópia profunda,
copiando recursivamente todas as propriedades e suas propriedades aninhadas.
Como você viu antes, tudo o que você precisa é de uma função para encapsular os dados privados.
Portanto, no caso de literais de objeto, você pode usar o fechamento criado por uma função imediata
anônima adicional. Aqui está um
exemplo: var myobj; // este será o objeto
(function() { //
membros privados
var name = "my, oh my";
myobj =
{ // método privilegiado
getName: function ()
{ return name;
}
}; }());
A mesma ideia, mas com uma implementação ligeiramente diferente, é apresentada no exemplo a seguir: var
myobj =
(function () { // membros
privados var name =
"my, oh my";
}; }());
Este exemplo também é o esqueleto do que é conhecido como “padrão de módulo”, que examinaremos
em breve.
desvantagem dos membros privados quando usados com construtores é que eles são recriados toda
vez que o construtor é invocado para criar um novo objeto.
Na verdade, isso é um problema com qualquer membro que você adiciona dentro de construtores.
Para evitar a duplicação de esforços e economizar memória, você pode adicionar propriedades e
métodos comuns à propriedade de protótipo do construtor. Dessa 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 isso, você pode usar uma
combinação de dois padrões: propriedades privadas dentro de construtores e propriedades privadas em
literais de objeto. Como a propriedade de protótipo é apenas um objeto, ela pode ser criada com os
literais de objeto.
function Gadget() { //
membro privado var
name = 'iPod'; //
função pública
[Link] = function () { return
name;
};
[Link] = (function () { //
membro privado var
browser = "Mobile Webkit"; //
membros de protótipo público
return
{ getBrowser: function ()
{ return browser;
}
}; }());
(função () {
função isArray(a)
{ retornar [Link](a) === astr;
}
} retornar ÿ1;
}
myarray =
{ isArray: isArray,
indexOf: indexOf,
inArray: indexOf
};
}());
Aqui você tem duas variáveis privadas e duas funções privadas — isArray() e indexOf(). Próximo ao
final da função imediata, o objeto myarray é preenchido com a funcionalidade que você decidir tornar
pública. Neste caso, o mesmo indexOf() privado é exposto como indexOf no estilo ECMAScript 5 e
inArray inspirado em PHP . Testando o novo objeto myarray :
[Link]([1,2]); // verdadeiro
[Link]({0: 1}); // falso
[Link](["a", "b", "z"], "z"); // 2
[Link](["a", "b", "z"], "z"); // 2
Agora, se algo inesperado acontecer, por exemplo, com o indexOf() público, o indexOf() privado
ainda estará seguro e, portanto, inArray() continuará funcionando:
[Link] = nulo;
[Link](["a", "b", "z"], "z"); // 2
Padrão de módulo
O padrão de módulos é amplamente utilizado porque fornece estrutura e ajuda a organizar seu código
à medida que ele cresce. Ao contrário de outras linguagens, JavaScript não possui sintaxe específica
para pacotes, mas o padrão de módulos fornece as ferramentas para criar trechos de código
autocontidos e desacoplados, que podem ser tratados como caixas-pretas de funcionalidade e
adicionados, substituídos ou removidos 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:
• Espaços de
nomes • Funções imediatas
primeiro passo é configurar um namespace. Vamos usar a função namespace() descrita anteriormente
neste capítulo e iniciar um módulo utilitário de exemplo que fornece métodos de array úteis:
[Link]('[Link]');
O próximo passo é definir o módulo. O padrão usa uma função imediata que fornecerá escopo privado
caso privacidade seja necessária. A função imediata retorna um objeto—
Padrão de Módulo | 97
Machine Translated by Google [Link]
o módulo real com sua interface pública, que estará disponível aos consumidores do módulo:
}; }());
},
isArray: function (a) { // ...
}; }());
Usando o escopo privado fornecido pela função imediata, você pode declarar algumas propriedades e
métodos privados conforme necessário. Logo no topo da função imediata também estará o local para
declarar quaisquer dependências que seu módulo possa ter. Após as declarações de variáveis, você
pode, opcionalmente, inserir 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:
[Link]('[Link]');
[Link] = (função () {
// dependências
var uobj = [Link], ulang
= [Link],
// propriedades privadas
array_string = "[object Array]", ops =
[Link];
// métodos
privados // ...
// fim da variável
// API pública
return {
retornar verdadeiro;
}
}
},
}; }());
O padrão de módulo é uma maneira amplamente utilizada e altamente recomendada de organizar seu código,
especialmente à medida que ele cresce.
discutimos o padrão de revelação neste capítulo, ao analisar os padrões de privacidade. O padrão de módulo
pode ser organizado de forma semelhante, onde todos os métodos são mantidos privados e você expõe apenas
aqueles que decidir no final, ao configurar a API pública.
[Link] = (função () {
// propriedades privadas
var array_string = "[object Array]", ops =
[Link],
// métodos privados
inArray = função (palheiro, agulha) { para (var i
= 0, max = [Link]; i < max; i += 1) {
se (palheiro[i] === agulha) { retornar
i;
}
} retornar ÿ1;
},
isArray = function (a) { return
[Link](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 [Link]
[Link] = (função () {
// dependências
var uobj = [Link], ulang =
[Link],
// fim da variável
} retornar um;
}
};
}());
uma variação comum do padrão, você pode passar argumentos para a função imediata que
encapsula o módulo. Você pode passar qualquer valor, mas geralmente são referências a variáveis
globais e até mesmo ao próprio objeto global. Importar globais ajuda a acelerar a resolução de
símbolos globais dentro da função imediata, pois as variáveis importadas se tornam locais para a
função:
Padrão Sandbox
O padrão sandbox aborda as desvantagens do padrão de namespace, a saber:
• Dependência de uma única variável global para ser a global do aplicativo. No padrão de
espaçamento de nomes, não há como executar duas versões do mesmo aplicativo ou
biblioteca na mesma página, pois ambas precisam do mesmo nome de símbolo global, por
exemplo, MYAPP. •
Nomes longos e com pontos para digitar e resolver em tempo de execução, por exemplo,
[Link].
Como o nome sugere, o padrão sandbox fornece um ambiente para os módulos “brincarem”
sem afetar outros módulos e seus sandboxes pessoais.
O padrão é muito usado na versão 3 do YUI, por exemplo, mas tenha em mente que a discussão
a seguir é uma implementação de referência de exemplo e não tenta descrever como a
implementação sandbox do YUI3 funciona.
Um construtor global
No padrão de namespace, você tem um objeto global; no padrão sandbox, o único objeto 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
sandbox para o seu código.
A caixa de objeto será como MYAPP no exemplo de namespace: ela terá todas as
funcionalidades de biblioteca necessárias para fazer seu código funcionar.
• Com um pouco de mágica (impondo o padrão new do Capítulo 3), você pode assumir new e não
exigi-lo ao criar o objeto. • O construtor
Sandbox() pode aceitar um argumento (ou argumentos) de configuração adicional especificando
os nomes dos módulos necessários para esta instância do objeto. Queremos que o código seja
modular, portanto, a maior parte da funcionalidade que Sandbox() fornece estará contida em
módulos.
Com esses dois recursos adicionais, vamos ver alguns exemplos de como ficará o código para
instanciar objetos.
Você pode omitir new e criar um objeto que use alguns módulos fictícios “ajax” e “event” como:
Este exemplo é semelhante ao anterior, mas desta vez os nomes dos módulos são passados como
argumentos individuais:
Sandbox('ajax', 'dom', função (caixa) { //
[Link](caixa); });
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á
'*'. Portanto, duas maneiras de usar todos os módulos disponíveis seriam:
Sandbox('*', function (box) { //
[Link](box); });
Sandbox(função (caixa) { //
[Link](caixa); });
E mais um exemplo de uso do padrão ilustra como você pode instanciar objetos sandbox várias
vezes — e você pode até mesmo aninhá-los 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 encapsulado em funções de retorno de chamada.
Se necessário, 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 instâncias diferentes dependendo do tipo de módulo 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. Essa 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: [Link] =
{};
Neste exemplo, adicionamos os módulos dom, event e ajax, que são funcionalidades comuns em
todas as bibliotecas ou aplicativos 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ê deve
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 = [Link](arguments), // o último
argumento é o retorno de chamada callback
= [Link](), // módulos
podem ser passados como um array ou como parâmetros individuais modules
= (args[0] && typeof args[0] === "string") ? args : args[0], i;
}
}
}
// chamar o retorno de
chamada callback(this);
}
• Há uma verificação se esta é uma instância de Sandbox e, se não for (o que significa que
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 neste exemplo de implementação 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 "seed") e quaisquer módulos que você
precisar serão carregados de arquivos externos usando uma convenção de nomenclatura onde
os nomes dos arquivos correspondem aos nomes dos módulos.
• Quando conhecemos os módulos necessários, nós os inicializamos, o que significa que
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 sandbox do usuário e recebe 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 classes, membros estáticos são criados usando uma sintaxe especial
e, em seguida, usados como se fossem membros da própria classe. Por exemplo, um método
estático max() de alguma classe MathUtils seria invocado como [Link](3, 5). Este é um
exemplo de um 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 ao consumidor
da classe, mas ainda compartilhados entre todas as instâncias da classe. Vamos ver como
implementar membros estáticos públicos e privados em JavaScript.
Em JavaScript, não existe uma 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 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 empregou 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
gadget específico 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
[Link] = function () { return
"você aposta";
};
Agora, vamos chamar esses métodos. O método estático isShiny() é invocado diretamente no construtor,
enquanto o método regular precisa de uma instância:
// chamando um método
estático [Link](); // "pode apostar"
Tentar chamar um método de instância estaticamente não funcionará; o mesmo vale para chamar um
método estático usando o objeto de instância
iphone : typeof [Link]; // "indefinido"
typeof [Link]; // "indefinido"
Às vezes, pode ser conveniente ter os métodos estáticos funcionando também com uma instância. 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: [Link] = [Link];
Nesses casos, você precisa ter cuidado ao usar this dentro do método estático. Ao usar [Link]() ,
this dentro de isShiny() se referirá à função construtora Gadget . Se você usar [Link]() , this
apontará para iphone.
Um último exemplo mostra como você pode ter o mesmo método sendo chamado estaticamente e não
estaticamente e se comportando de forma ligeiramente diferente, dependendo do padrão de invocação.
Aqui, instanceof ajuda a determinar como o método foi chamado: //
construtor var
Gadget = function (preço) { [Link]ço
= preço;
};
// um método estático
[Link] = function () {
retornar mensagem;
};
A discussão até agora foi sobre métodos estáticos públicos; agora vamos dar uma olhada em como
implementar membros estáticos privados . Por membros estáticos privados, queremos dizer membros
que são:
Vejamos um exemplo em que counter é uma propriedade estática privada no construtor Gadget.
Neste capítulo, já discutimos sobre propriedades privadas, então esta parte continua a mesma —
você precisa de uma função para atuar como um fechamento e envolver os membros privados.
Então, vamos executar a mesma função wrapper imediatamente e retornar uma nova função. O valor
da função retornada é atribuído à variável Gadget e se torna o novo construtor:
// variável/propriedade estática
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 é de fato compartilhado entre todas as instâncias:
var g1 = novo gadget(); // registra 1 var
g2 = new Gadget(); // logs 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/propriedade estática
var counter = 0,
Novo Gadget;
// um método privilegiado
[Link] = function () { return
counter;
};
Propriedades estáticas (privadas e públicas) podem ser bastante úteis. Elas 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 de Objeto
Não há constantes em JavaScript, embora muitos ambientes modernos possam oferecer a instrução const
para criar constantes.
Como solução alternativa, uma abordagem comum é usar uma convenção de nomenclatura e destacar as
variáveis que não devem ser alteradas usando letras maiúsculas. Essa convenção é usada nos objetos
JavaScript integrados:
[Link]; // 3,141592653589793
Math.SQRT2; // 1,4142135623730951
Número.VALOR_MÁXIMO; // 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 construtora: //
construtor 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 letras maiúsculas.
Se você realmente deseja ter um valor imutável, pode criar uma propriedade privada e fornecer um método
getter, mas nenhum setter. Isso provavelmente é um exagero em muitos casos, quando você pode se virar
com uma convenção simples, mas ainda é uma opção.
O exemplo a seguir é uma implementação de um objeto constante de uso geral , que fornece estes métodos:
definir(nome, valor)
Para definir uma nova constante
isDefined(nome)
Para verificar se uma constante existe
obter(nome)
Para obter o valor de uma constante
Nesta implementação, apenas valores primitivos são permitidos como constantes. Além disso,
tomamos um cuidado extra para garantir que seja aceitável declarar constantes com nomes que
sejam nomes de propriedades internas, como toString ou hasOwnProperty , usando uma verificação
hasOwnProperty() e, adicionalmente, adicionando um prefixo gerado aleatoriamente a todos os
nomes de constantes: var
constant = (function () { var
constants = {},
ownProp = [Link],
allowed = {
string: 1,
número: 1,
booleano: 1
},
prefix = ([Link]() + "_").slice(2); return { set:
function
(nome, valor) {
se ([Link](nome)) { retornar
falso;
} retornar nulo;
}
}; }());
Testando a implementação:
// verifique se constante
[Link]("maxwidth"); // falso
// define
[Link]("maxwidth", 480); // verdadeiro
// verifique
novamente [Link]("maxwidth"); // true
// tentativa de redefinir
[Link]("maxwidth", 320); // falso
Padrão de encadeamento
Ao criar métodos sem valor de retorno significativo, você pode fazer com que eles retornem this, 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: função ()
{ [Link] += 1;
retornar isto;
},
adicione: função (v)
{ [Link] += v;
retorne isto;
},
shout: função ()
{ alerta([Link]);
}
};
// método de cadeia
chama [Link]().add(3).shout(); // 5
benefício de usar o padrão de encadeamento é que você pode economizar digitação e criar um
código mais conciso que quase parece 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 vez de funções que tentam fazer muita coisa.
Isso melhora a manutenibilidade a longo prazo.
Uma desvantagem é que fica mais difícil depurar código escrito dessa maneira. Você pode saber
que um erro ocorreu 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 sabe 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, notará
que ela também é propensa a encadeamentos com construções como:
[Link]('cabeça')[0].appendChild(novo nó);
método() Método
JavaScript pode ser confuso para programadores acostumados a pensar em termos de classes. É
por isso que alguns desenvolvedores optam por tornar o JavaScript mais semelhante a classes.
Uma dessas tentativas é a ideia do método method() , introduzida por Douglas Crockford. Em
retrospectiva, ele admite que tornar o JavaScript semelhante a classes não é uma abordagem
recomendada, mas, ainda assim, é um padrão interessante, e você pode encontrá-lo em algumas
aplicações.
Usar funções construtoras é semelhante ao uso de classes em Java. Elas também permitem
adicionar propriedades de instância a this dentro do corpo do construtor. No entanto, adicionar
métodos a this é ineficiente, pois eles acabam sendo recriados a cada instância, o que consome
mais memória. É por isso que métodos reutilizáveis devem ser adicionados à propriedade
prototype do construtor. O protótipo pode parecer estranho para muitos desenvolvedores, então
você pode escondê-lo atrás de um método.
A maneira de definir uma “classe” usando o método sugar() seria algo como o seguinte:
var Pessoa = função (nome)
{ [Link] = nome;
}.
método('getName', função () { return
[Link];
}).
método('setName', função (nome)
{ [Link] = nome;
retornar
isto; });
Observe como o construtor está encadeado à chamada de method(), que por sua vez está
encadeada à 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
Este novo método é então adicionado à "classe" Person . A implementação é apenas mais uma
função, e dentro da função de implementação this aponta para o objeto criado por Person, como
seria de se esperar.
Veja como você pode usar Person() para criar e usar um novo objeto:
var a = new Pessoa('Adam');
[Link](); // 'Adão'
[Link]('Eva').getName(); // 'Eva'
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, porém surpreendentemente
útil, de declaração de 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 literais de objeto com membros privados e a revelação de métodos privados
como públicos. Todos esses padrões são os blocos de construção do popular e poderoso padrão de
módulo.
Depois, você aprendeu sobre o padrão sandbox como uma alternativa ao namespace longo, o que
também ajuda a criar ambientes independentes para seu código e módulos.
Para encerrar a discussão, demos uma olhada mais aprofundada em constantes de objetos, métodos
estáticos (públicos e privados), encadeamento e um curioso método method() .
Resumo | 113
Machine Translated by Google [Link]
Machine Translated by Google [Link]
CAPÍTULO 6
Ao falar sobre reutilização de código, a primeira coisa que vem à mente é herança, e grande parte
do capítulo é dedicada a esse tópico. Você verá várias maneiras de fazer herança "clássica" e
não clássica. Mas é importante manter o objetivo final em mente — queremos reutilizar código.
Herança é uma maneira (meio) de atingirmos esse objetivo. E não é a única. Você verá como
compor objetos a partir de outros objetos, como usar mix-ins de objetos e como pode pegar
emprestado e reutilizar apenas a funcionalidade necessária sem, tecnicamente, herdar nada
permanentemente.
Ao abordar uma tarefa de reutilização de código, tenha em mente o conselho que o livro Gang of
Four oferece sobre criação de objetos: “Prefira composição de objetos à herança de classes”.
Muitas linguagens de programação têm a noção de classes como modelos para objetos. Nessas
linguagens, cada 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 correspondente não existir. Em JavaScript, como não
existem 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
rapidamente.
Mas JavaScript tem funções construtoras, e a sintaxe do operador new se assemelha muito à
sintaxe de usar classes.
115
Machine Translated by Google [Link]
Além do fato de que Java é fortemente tipado e você precisa declarar que adam é do tipo
Person, a sintaxe é a mesma. A invocação do construtor em JavaScript parece como se Person
fosse 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 a desenvolver ideias e padrões de herança que pressupõem classes. Podemos
chamar tais implementações de "clássicas". Digamos também que "modernos" 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 se
sinta realmente desconfortável com a ausência de classes.
Este capítulo discute primeiro os padrões clássicos e depois passa para os modernos.
Embora a discussão seja sobre padrões clássicos, vamos evitar o uso da palavra
"classe". Dizer "função construtora" ou "construtor" é mais longo, mas é preciso e não
ambíguo. Em geral, procure eliminar a palavra "classe" ao se comunicar com sua
equipe, pois, quando se trata de JavaScript, a palavra pode ter significados diferentes
para cada pessoa.
// função construtora de
criança vazia Child(nome) {}
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 inherit()
não é fornecida pela linguagem, então você precisa implementá-la. Vejamos várias abordagens
para implementá-la de forma genérica.
É importante lembrar que a propriedade prototype deve apontar para um objeto, não para uma
função, portanto, ela precisa apontar para uma instância (um objeto) criada com o construtor
pai, não para o construtor em si. Em outras palavras, preste atenção ao operador new , pois
você precisa dele para que este padrão funcione.
Mais tarde em seu aplicativo, quando você usa new Child() para criar um objeto, ele obtém
funcionalidade da instância Parent() por meio do protótipo, conforme mostrado no exemplo a
seguir:
var kid = new Child();
[Link](); // "Adam"
Vamos revisar como a cadeia de protótipos funciona neste padrão de herança. Para os
propósitos 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 bloco desse tipo (marcado como bloco nº 2 na Figura 6-1). Ele
contém os dados para a propriedade name . Se você tentar acessar o método say() (por
exemplo, usando (new Parent).say()), o bloco nº 2 não contém esse método. Mas, usando o
link oculto __proto__ que aponta para a propriedade prototype da função construtora Parent(),
você obtém acesso ao objeto nº 1 ([Link]), 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; essa propriedade não está
disponível no idioma em si, 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() após usar a função inherit() . O diagrama é mostrado na Figura 6-2.
Por fim, vamos dar uma olhada em mais um passo. Digamos que temos este código:
var criança = new Criança();
crianç[Link] = "Patrick";
crianç[Link](); // "Patrick"
Figura 6-3. Cadeia de protótipos após herança e adição de uma propriedade ao objeto filho
Definir [Link] não altera a propriedade name do objeto nº 2, mas cria uma propriedade própria
diretamente no objeto kid nº 3. Quando você executa [Link](), o método say é pesquisado no objeto nº
3, depois no nº 2 e, finalmente, encontrado no nº 1, como antes. Mas desta vez, a pesquisa por
[Link] (que é o mesmo que [Link]) é rápida, pois a propriedade é encontrada imediatamente no
objeto nº 3.
Se você remover a nova propriedade usando delete [Link], a propriedade name do objeto #2 “irá
brilhar” e será encontrada em pesquisas consecutivas.
desvantagem desse padrão é que você herda tanto as propriedades próprias adicionadas a ele quanto
as propriedades do protótipo. Na maioria das vezes, você não quer as propriedades próprias, pois elas
provavelmente serã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 usar uma função inherit() genérica é que ela não permite que você passe parâmetros
para o construtor filho, que então o filho passa para o pai.
Considere este exemplo:
var s = new Child('Seth');
[Link](); // "Adão"
Isso não é o que você esperaria. É possível que o filho passe parâmetros para o construtor do pai,
mas você terá que fazer a herança toda vez que precisar de um novo filho, o que é ineficiente, porque
você acaba recriando os objetos pais repetidamente.
sobre.
Dessa forma, você só pode herdar propriedades adicionadas a isso 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 recebem cópias dos membros herdados,
ao contrário do padrão clássico nº 1, em que recebem apenas referências. O exemplo a seguir ilustra
a diferença:
// uma função construtora
pai Article() { [Link]
= ['js', 'css'];
alert([Link]('tags')); // verdadeiro
alert([Link]('tags')); // falso
alert([Link]('tags')); // verdadeiro
Neste snippet, o objeto pai Article() é herdado de duas maneiras. O padrão padrão faz com que
o objeto do blog tenha acesso à propriedade tags por meio do protótipo, portanto, ele não a
possui como propriedade própria e hasOwnProperty() retorna falso. O objeto da página possui
uma propriedade tags própria porque, ao usar o construtor alugado, o novo objeto obteve uma
cópia (não uma referência) do membro tags do objeto pai .
Neste exemplo, o objeto blog filho modifica a propriedade tags e, dessa forma, também modifica
o objeto pai, pois essencialmente [Link] e [Link] apontam para o mesmo array. Alterações
em [Link] não afetam o objeto pai, pois [Link] é uma cópia separada criada durante a
herança.
A Cadeia de Protótipos
Vamos dar uma olhada na aparência da cadeia de protótipos usando este padrão e os conhecidos
construtores Parent() e Child() . Child() será ligeiramente modificado para seguir o novo padrão: //
o construtor pai
function Parent(name)
{ [Link] = name ||
'Adam';
}
// função construtora
infantil Child(nome)
{ [Link](this, argumentos);
}
Se você observar a Figura 6-4, notará que não há mais um link entre o novo objeto Child e o
Parent. Isso ocorre porque [Link] não foi usado e simplesmente aponta para um objeto
em branco. Usando esse padrão, kid recebeu 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 foi uma
ação única que copiou as propriedades do parent como propriedades do child e foi basicamente
isso; nenhum link __proto__ foi mantido.
}
}
função Bird()
{ [Link] = 2;
[Link] = true;
}
função CatWings()
{ [Link](isto);
Pá[Link](isto);
}
O resultado é mostrado na Figura 6-5. Quaisquer propriedades duplicadas serão resolvidas com a
vitória da última.
Então, como os filhos também podem herdar propriedades do protótipo, no caso anterior, e como a
criança pode ter acesso ao método say() ? O próximo padrão aborda essa questão.
A vantagem é que os objetos resultantes obtêm cópias dos próprios membros do objeto pai e
referências à funcionalidade reutilizável do objeto pai (implementada como membros do protótipo).
O objeto filho também pode passar quaisquer argumentos para o construtor do objeto pai. Esse
comportamento é provavelmente o mais próximo do que se esperaria em Java; você herda tudo o
que está no objeto pai e, ao mesmo tempo, é seguro modificar suas próprias propriedades sem o
risco de modificar o objeto pai.
Uma desvantagem é que o construtor pai é chamado duas vezes, o que pode ser ineficiente. No
final, as propriedades próprias (como o nome no nosso caso) são herdadas duas vezes.
};
// função construtora
infantil Child(nome)
{ [Link](this, argumentos);
}
[Link]ótipo = novo Pai();
Ao contrário do padrão anterior, agora say() é herdado corretamente. Você também pode notar que
o nome é herdado duas vezes e, após excluirmos a própria cópia, a que vem na cadeia de protótipos
se destacará.
A Figura 6-6 mostra como funcionam os relacionamentos entre os objetos. Os relacionamentos são
semelhantes aos mostrados na Figura 6-3, mas o caminho que percorremos até lá foi diferente.
Figura 6-6. A cadeia de protótipos é mantida além dos membros próprios 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, tudo o que valesse a pena herdar deveria estar no
protótipo. Então, você pode simplesmente definir o protótipo do filho para ser o mesmo que o protótipo
do pai:
function inherit(C, P)
{ [Link] = [Link];
}
Isso proporciona consultas curtas e rápidas na cadeia de protótipos, pois todos os objetos compartilham
o mesmo protótipo. Mas isso também é uma desvantagem, pois se um filho ou neto em algum ponto
da cadeia de herança modifica o protótipo, isso afeta todos os pais e avós.
Como mostra a Figura 6-7 , objetos filho e pai compartilham o mesmo protótipo e têm acesso igual ao
método say() . No entanto, objetos filhos não herdam a propriedade name .
Abaixo está uma implementação deste padrão, onde você tem uma função F() vazia, que serve como
um proxy entre o filho e o pai. A propriedade prototype de F() aponta para o protótipo do pai. O
protótipo do filho é uma instância da função em branco:
função herdar(C, P) {
var F = função () {};
[Link]ótipo = [Link]ótipo;
[Link]ótipo = novo F();
}
Este padrão tem um comportamento ligeiramente diferente do padrão padrão (padrão clássico nº 1)
porque aqui o filho herda apenas propriedades do protótipo (veja Figura 6-8).
E isso geralmente é aceitável, na verdade preferível, porque o protótipo é o lugar para funcionalidades
reutilizáveis. Nesse padrão, quaisquer membros que o construtor pai adiciona a este não são herdados.
Se você acessar [Link], ele será indefinido. Neste caso, name é uma propriedade própria do objeto
pai e, durante a herança, nunca chamamos new Parent(), então essa propriedade nunca foi criada. Ao
acessar [Link](), ele não estará disponível no objeto nº 3, então a cadeia de protótipos é consultada. O
objeto nº 4 também não possui esse método, mas o objeto nº 1 possui, e este é o mesmo local na
memória, que será reutilizado por todos os diferentes construtores que herdam Parent() e por 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.
Isso é como ter acesso à superclasse em outras linguagens e pode ser útil em algumas ocasiões.
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 melhorada deste padrão clássico:
função herdar(C, P) {
var F = função () {};
[Link]ótipo = [Link]ótipo;
[Link]ótipo = novo F();
[Link] = [Link]ótipo;
}
A propriedade construtora raramente é usada, mas pode ser conveniente para introspecção de
objetos em tempo de execução. Você pode redefini-la para apontar para a função construtora
esperada sem afetar a funcionalidade, pois essa propriedade é principalmente informativa.
A versão final do Santo Graal desse padrão clássico de herança será semelhante a esta:
função herdar(C, P) {
var F = função () {};
[Link]ótipo = [Link]ótipo;
[Link]ótipo = novo F();
[Link] = [Link]ótipo;
[Link]ó[Link] = 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 seu projeto.
Esse padrão também é conhecido como aquele que usa uma função proxy ou um
construtor proxy, em vez de um construtor temporário, porque o construtor temporário é
usado como um proxy para chegar ao protótipo do pai.
Uma otimização comum do padrão Santo Graal é evitar a criação do construtor temporário (proxy)
sempre que você precisar de herança. Basta criá-lo uma vez e
alterar apenas seu protótipo. Você pode usar uma função imediata e armazenar a função proxy em
seu fechamento:
} }());
Classe
Muitas bibliotecas JavaScript emulam classes, introduzindo uma nova sintaxe sugar. As
implementações diferem, mas geralmente há alguns pontos em comum, incluindo os seguintes:
• Há 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, somente 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ê-la como um construtor Klass() ou como um [Link] aumentado , mas neste
exemplo, vamos mantê-la 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 literal de objeto. Influenciados pelo PHP, vamos estabelecer a convenção de que
o construtor da classe deve ser um método chamado __construct.
No snippet anterior, uma nova classe chamada Man é criada e não herda de nada (o que significa que
herda de Object nos bastidores). A classe Man tem uma propriedade name criada dentro de __construct
e um método getName(). A classe é uma função construtora, então o seguinte ainda funcionará (e se
parecerá com uma instanciação de classe):
var first = new Man('Adam'); // registra o "construtor do Man"
[Link](); // "Adam"
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 uber (super) estática
de SuperMan. Vamos testar: var clark = new
SuperMan('Clark Kent'); [Link](); //
"Eu sou Clark Kent"
Por fim, 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
([Link] && [Link]("__construct"))
{ [Link].__construct.apply(this, argumentos);
} se (Crianç[Link]ó[Link]("__construct")) {
[Link].__construct.apply(isto, argumentos);
}
};
// 2. //
herdar Pai
= Pai || Objeto;
Classe | 129
Machine Translated by Google [Link]
F = função () {};
[Link]ótipo = [Link]ótipo;
[Link]ótipo = novo F();
[Link] = [Link]ótipo;
[Link]ó[Link] = Filho;
// 3. //
adicione métodos de
implementação para
(i em props) { if ([Link](i)) {
Crianç[Link]ótipo[i] = adereços[i];
}
}
// retorna a "classe"
return Child;
};
1. Uma função construtora Child() é criada. Esta é a função que será retornada no final e será
usada como uma classe. Nesta função, o método __construct é chamado, se existir. Além
disso, antes disso, o método __construct do método pai é chamado (novamente, se existir)
usando a propriedade estática uber . Pode haver casos em que uber não esteja definido —
quando você herda de Object , por exemplo, como foi o caso com a definição da classe Man .
2. A segunda parte cuida da parte da herança. Ela simplesmente usa o padrão do Santo Graal da
herança clássica, discutido na seção anterior do capítulo.
Há apenas uma coisa nova: definir o Pai como Objeto se nenhum Pai foi passado para herdar.
Quando usar esse padrão? Bem, na verdade é melhor evitá-lo, porque ele traz consigo toda a
noção confusa de classes, que tecnicamente não existem na linguagem. Ele adiciona nova sintaxe
e novas regras para aprender e memorizar. Dito isso, se você ou a equipe se sentem à vontade
com classes e, ao mesmo tempo, se sentem desconfortáveis com protótipos, então isso pode ser
algo a ser explorado. Esse padrão permite que você esqueça completamente os protótipos, e o
bom é que você pode ajustar a sintaxe e as convenções para se assemelharem a outra de suas
linguagens favoritas.
Herança Prototípica
Vamos começar a discussão sobre padrões sem classes "modernos" com um padrão chamado
herança prototípica. Nesse padrão, não há classes envolvidas; aqui, objetos herdam de outros
objetos. Você pode pensar da seguinte forma: você tem um objeto que gostaria de reutilizar e quer
criar um segundo objeto que obtenha sua funcionalidade do primeiro.
};
// alerta de
teste(crianç[Link]); // "Papai"
No snippet anterior, você tem um objeto existente chamado " parent" criado com o literal de objeto
e deseja criar outro objeto chamado "child" que possui as mesmas propriedades e métodos do
"parent". O objeto "child" foi criado com uma função chamada "object()". Essa função não existe
em JavaScript (não confunda com a função construtora " Object()"), então vamos ver como você
pode defini-la.
Similarmente ao Santo Graal clássico, você usaria uma função construtora temporária vazia, F().
Em seguida, você definiria o protótipo de F() como o objeto pai. Por fim, você retornaria uma nova
instância do construtor temporário:
função objeto(o)
{ função F() {}
[Link] = o;
retornar novo F();
}
Discussão
No padrão de herança prototípica, seu pai não precisa ser criado com a notação literal (embora essa
seja provavelmente a forma mais comum). Você pode fazer com que funções construtoras criem o pai.
Observe que, se você fizer isso, tanto as propriedades "próprias" quanto as propriedades do protótipo
do construtor serão herdadas: // construtor pai função Person() { // uma
propriedade "própria"
[Link] = "Adam";
Em outra variação deste padrão, você tem a opção de herdar apenas o objeto protótipo de um construtor
existente. Lembre-se: objetos herdam de objetos, independentemente de como os objetos pais foram
criados. Aqui está uma ilustração usando o exemplo anterior, ligeiramente modificado: // construtor pai
function Person() { // uma
propriedade "própria"
[Link] = "Adam";
// herdar
var kid = object([Link]);
Adição ao ECMAScript 5 No
ECMAScript 5, o padrão de herança prototípica torna-se oficialmente parte da linguagem. Este padrão é
implementado através do método [Link](). Em outras palavras,
palavras, você não precisará criar sua própria função semelhante a object(); ela será incorporada à
linguagem: var child
= [Link](parent);
[Link]("idade"); // verdadeiro
Você também pode descobrir que o padrão de herança prototípico é implementado em bibliotecas
JavaScript; por exemplo, no YUI3 é o método [Link]() :
YUI().use('*', função (Y) {
var criança = [Link](pai); });
olhada em outro padrão de herança: a herança por cópia de propriedades. Nesse padrão, um objeto
obtém funcionalidade de outro objeto simplesmente copiando-o.
Aqui está um exemplo de implementação de uma função de amostra extend() que faz isso: function
extend(parent, child) { var i; child =
child ||
{}; for (i in parent) { if
([Link](i)) { child[i] =
parent[i];
}
} retornar criança;
}
É uma implementação simples, apenas percorrendo os membros do pai e copiando-os. Nesta implementação, filho é
opcional; se você não passar um objeto existente para ser aumentado, um novo objeto será criado e retornado: var dad
= {name: "Adam"}; var kid = extend(dad); [Link]; // "Adam"
A implementação apresentada é uma chamada "cópia superficial" do objeto. Uma cópia profunda, por
outro lado, significaria verificar se a propriedade que você está prestes a copiar é um objeto ou um array
e, em caso afirmativo, iterar recursivamente por suas propriedades e copiá-las também. Com a cópia
superficial (porque os objetos são passados por referência em JavaScript), se você alterar uma propriedade
do objeto filho, e essa propriedade for um objeto, então
Você também modificará o pai. Isso é preferível para métodos (já que funções também são objetos e são
passadas por referência), mas pode causar surpresas ao trabalhar com outros objetos e arrays. Considere
o seguinte:
var dad =
{ contagens: [1, 2, 3],
leituras: {papel: verdadeiro}
};
var criança = estender(pai);
crianç[Link](4);
[Link](); // "1,2,3,4" pai.lê ===
criança.lê; // 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 array discutida no Capítulo 3. Portanto, a versão de cópia profunda de extend() ficaria assim:
function extendDeep(parent, child) { var i,
toStr
= [Link], astr =
"[object Array]";
para (i no pai) { se
([Link](i)) { se
(typeof parent[i] === "object") { filho[i] =
([Link](parent[i]) === astr) ? [] : {}; extendDeep(parent[i],
filho[i]); } senão { filho[i] = pai[i];
}
}
} retornar criança;
}
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 criança = extendDeep(pai);
crianç[Link](4);
crianç[Link](); // "1,2,3,4"
[Link](); // "1,2,3"
criança.lê.teia =
verdadeiro; pai.lê.papel; // verdadeiro
Este padrão de cópia de propriedades é 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 superficiais, e o extend() do jQuery cria uma cópia profunda. O YUI3 oferece um método
chamado [Link](), que cria uma cópia profunda e também copia funções, vinculando-as ao objeto
filho. (Mais informações sobre vinculação serão abordadas posteriormente neste capítulo.)
Vale ressaltar que não há protótipos envolvidos nesse padrão; trata-se apenas de 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 "mix-in". 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; basta percorrer os argumentos e copiar todas as propriedades de cada objeto
passado para a função: function mix() { var arg,
prop, child = {}; for
(arg = 0; arg < [Link];
arg += 1) { for (prop in arguments[arg]) { if
(arguments[arg].hasOwnProperty(prop))
{ child[prop] = arguments[arg][prop];
}
}
} retornar criança;
}
Agora que você tem uma função mix-in genérica, pode passar qualquer número de objetos para
ela, e o resultado será um novo objeto que terá as propriedades de todos os objetos de origem.
Aqui está um exemplo de
uso: var cake =
mix( {eggs: 2, large: true},
{butter: 1, salted: true}, {flour:
"3 cups"}, {sugar:
"sure!"} );
A Figura 6-10 mostra o resultado da exibição das propriedades dos novos objetos cake misturados
executando [Link](cake) no console do Firebug.
Mix-ins | 135
Machine Translated by Google [Link]
Se você está acostumado com o conceito de mix-in em linguagens nas quais é uma
parte oficial, pode esperar que a alteração de um ou mais dos pais afete o filho, mas
isso não é verdade na implementação em questão. 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 de você gostar apenas de um ou dois métodos de um objeto existente.
Você quer reutilizá-los, mas não quer realmente formar uma relação pai-filho com esse objeto. Você
quer usar apenas os métodos que gosta, sem herdar todos os outros métodos dos quais nunca
precisará. Isso é possível com o padrão de empréstimo de métodos, 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, funções em JavaScript são objetos e vêm com alguns métodos interessantes, como
call() e apply(). A única diferença entre os dois é que um recebe um array de parâmetros a serem
passados ao método que está sendo chamado, e o outro recebe parâmetros um por um. Você pode
usar esses métodos para pegar emprestada funcionalidades de objetos existentes: // call() exemplo
[Link](myobj, param1, p2,
p3); // apply()
exemplo [Link](myobj, [param1,
p2, p3]);
Aqui você tem um objeto chamado myobj e sabe que outro objeto chamado notmyobj possui este
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 por um tempo para se beneficiar do
método que você gosta. É como receber uma herança, mas sem pagar o imposto sobre herança
(onde o "imposto" vem na forma de propriedades e métodos extras dos quais você não precisa).
matriz Um uso comum para esse padrão é pegar emprestado métodos de matriz.
Arrays possuem métodos úteis, que objetos semelhantes a arrays, como argumentos, não possuem.
Portanto, argumentos podem tomar emprestado métodos de array, como o método slice(). Aqui está um exemplo:
função f() { var
args = [].[Link](argumentos, 1, 3); retornar
argumentos;
}
// exemplo
f(1, 2, 3, 4, 5, 6); // retorna [2,3]
Neste exemplo, um array vazio foi criado apenas para usar seu método. Uma maneira um pouco mais
demorada de fazer o mesmo é pegar emprestado o método diretamente do protótipo de Array , usando
[Link](...). Essa maneira é um pouco mais demorada para digitar, mas você economizará
o trabalho de criar um array vazio.
Emprestar e Vincular
Ao tomar métodos emprestados por meio de call()/apply() ou por atribuição simples, o objeto para o qual
this aponta dentro do método emprestado é determinado com base na expressão de chamada. Mas, às
vezes, é melhor ter o valor de this "bloqueado" ou vinculado a um objeto específico e predeterminado com
antecedência.
// teste
[Link]('hi'); // "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 dentro de say() apontava para dois e [Link] era, portanto, "outro objeto". Mas e
quanto aos 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 bastante: // atribuindo a uma variável // `this`
apontará para o objeto global
Em ambos os casos, o método say() interno apontava para o objeto global, e o trecho inteiro 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 [Link](o, [].[Link](arguments));
};
}
Esta 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 fechamento.
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, [Link]); twosay('yo'); //
"yo, another object"
Como você pode ver, embora twosay() tenha sido criada como uma função global, this não
apontava para o objeto global, mas sim para o objeto two, que foi passado para bind().
Independentemente de como você chamar twosay(), this sempre estará vinculado a two.
O preço que você paga pelo luxo de ter uma encadernação é o fechamento adicional.
Funçã[Link]()
O ECMAScript 5 adiciona um método bind() a [Link], tornando-o tão fácil de usar
quanto apply() e call(). Assim, você pode criar expressões como:
var newFunc = [Link](myobj, 1, 2, 3);
Isso significa vincular someFunc() e myobj e também preencher previamente os três primeiros
argumentos esperados por someFunc() . Este também é um exemplo de aplicação de função
parcial discutido no Capítulo 4.
Vamos ver como você pode implementar [Link]() quando seu programa é
executado em ambientes pré-ES5:
se (tipo de Funçã[Link] === "indefinido")
{ Funçã[Link] = função (thisArg) { var fn = isto,
fatia = [Link],
args = [Link](argumentos, 1);
retornar função ()
{ retornar [Link](thisArg, [Link]([Link](argumentos)));
};
};
}
Esta implementação provavelmente parece um pouco familiar; ela usa a aplicação parcial e concatena 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. É benéfico estudar e compreender os
diferentes padrões, pois eles ajudam a aprimorar seu domínio da linguagem. Neste capítulo, você aprendeu sobre
vários padrões clássicos e modernos para abordar a herança.
Entretanto, herança provavelmente não é um problema que você enfrenta com frequência durante o desenvolvimento.
Isso se deve, em parte, ao fato de que esse problema provavelmente já está resolvido de alguma forma pela biblioteca
que você usa — e, em parte, porque é raro precisar estabelecer cadeias de herança longas e complexas em
JavaScript. Em linguagens estáticas fortemente tipadas, a herança pode ser a única maneira de reutilizar código. Em
JavaScript, você pode frequentemente ter maneiras muito mais simples e elegantes, incluindo pegar métodos
emprestados, vinculá-los, copiar propriedades e misturar 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 [Link]
Machine Translated by Google [Link]
CAPÍTULO 7
Padrões de Design
Os padrões de design do livro Gang of Four oferecem soluções para problemas comuns relacionados ao
design de software orientado a objetos. Eles existem há bastante tempo e têm se mostrado úteis em diversas
situações. Por isso, é bom se familiarizar com eles e conversar sobre eles.
JavaScript, sendo uma linguagem baseada em protótipos dinâmicos e não tipados, à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 estática baseada em classes: o padrão singleton.
Solteiro
A ideia do padrão singleton é ter apenas uma instância de uma classe específica. Isso significa que, na
segunda vez que você usar a mesma classe para criar um novo objeto, deverá obter o mesmo objeto que foi
criado na primeira vez.
E como isso se aplica ao JavaScript? Em JavaScript não existem classes, apenas objetos.
Quando você cria um novo objeto, na verdade não há outro objeto igual a ele, e o novo objeto já é um
singleton. Criar um objeto simples usando o literal de objeto também é um exemplo de singleton:
var obj =
{ myprop: 'meu valor'
};
141
Machine Translated by Google [Link]
Em JavaScript, os objetos nunca são iguais, a menos que sejam o mesmo objeto, então mesmo se
você criar um objeto idêntico com exatamente os mesmos membros, ele não será igual ao primeiro
um:
var obj2 =
{ myprop: 'meu valor'
};
obj === obj2; // falso obj
== obj2; // falso
Então você pode dizer que toda vez que você cria um objeto usando o literal de objeto, você está
na verdade criando um singleton, e não há nenhuma sintaxe especial envolvida.
Usar o novo
JavaScript não possui classes, então a definição literal de singleton não faz sentido tecnicamente.
Mas o JavaScript possui a sintaxe new para criar objetos usando funções construtoras, e às vezes
você pode querer uma implementação singleton usando essa sintaxe. A ideia é que, ao usar new
para criar vários objetos usando o mesmo construtor, você obtenha apenas ponteiros new 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 objetos de primeira classe.
O trecho a seguir mostra o comportamento esperado (supondo que você descarte a ideia do
Multiverso e aceite que existe apenas um Universo):
var uni = novo Universo();
var uni2 = novo 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 fazer isso em JavaScript?
Você precisa que o construtor Universe armazene em cache a instância do objeto this quando ele
for criado e a retorne na segunda vez que o construtor for chamado. Você tem várias opções para
fazer isso:
• Você pode usar uma variável global para armazenar a instância. Isso não é recomendado devido ao
princípio geral de que variáveis globais são ruins. Além disso, qualquer um 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,
portanto, podem ter propriedades. Você pode ter algo como [Link] e armazenar em cache
o objeto lá. Esta é uma solução boa e limpa, com a única desvantagem de que a propriedade da
instância é publicamente acessível, e o código fora do seu pode alterá-la, então você perde a
instância. • Você pode envolver a instância em um fechamento. Isso
mantém a instância privada e não disponível para modificações fora do seu construtor à custa de um
fechamento extra.
Aqui está um exemplo de armazenamento em cache da instância singular em uma propriedade estática do
Construtor do universo :
função Universo() {
// prossiga normalmente
this.start_time = 0;
[Link] = "Big";
// cache
[Link]ância = isto;
// retorno implícito: //
retornar isto;
}
// testando
var uni = new Universe(); var
uni2 = novo Universo(); uni
=== uni2; // verdadeiro
Como você pode ver, esta é uma solução simples, com a única desvantagem de que a instância é pública.
É improvável que outro código a altere por engano (muito menos provável do que se a instância fosse
global), mas ainda é possível.
Solteiro | 143
Machine Translated by Google [Link]
Instância em um fechamento
Outra maneira de criar um singleton semelhante a uma classe é usar um fechamento 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;
// prossiga normalmente
this.start_time = 0;
[Link] = "Big";
// reescreva o construtor
Universo = função ()
{ return instância;
};
}
// testando
var uni = new Universe(); var
uni2 = novo Universo(); uni
=== uni2; // verdadeiro
O construtor original é chamado na primeira vez e retorna isso como de costume. Na segunda,
terceira vez e assim por diante, o construtor reescrito é executado. O construtor reescrito tem
acesso à variável de instância privada por meio do fechamento e simplesmente a retorna.
// adicionando novamente ao
protótipo // após o objeto inicial ser criado
[Link] = true;
Teste: //
apenas o protótipo original foi //
vinculado aos objetos
[Link]; // verdadeiro
[Link]; // verdadeiro
[Link]; // indefinido
[Link]; // indefinido
// mas isso é
estranho: [Link] === Universe; // false
O motivo pelo qual [Link] não é mais o mesmo que o construtor Universe() é porque
[Link] ainda aponta para o construtor original, não para o redefinido.
Se for necessário fazer com que o protótipo e o ponteiro do construtor funcionem conforme o
esperado, é possível conseguir isso com alguns ajustes:
função Universo() {
// a instância em cache
var instance;
// reescreva o construtor
Universo = função Universo() { return
instância;
};
// a instância
instância = novo Universo();
// todas as funcionalidades
instance.start_time = 0;
[Link] = "Big";
retornar instância;
}
Solteiro | 145
Machine Translated by Google [Link]
Uma solução alternativa também seria encapsular 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 invocação, o construtor simplesmente retorna a variável privada. Todos
os testes do snippet anterior também funcionarão conforme o esperado com esta nova implementação:
var Universo;
(função () {
var instância;
se (instância)
{ retornar instância;
}
instância = isto;
// todas as funcionalidades
this.start_time = 0;
[Link] = "Big";
};
}());
Fábrica: O
objetivo da fábrica é criar objetos. Geralmente, ela é implementada em uma classe ou em um método
estático de uma classe, que tem os seguintes propósitos:
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, esta parte da implementação é bem fácil.
Os objetos criados pelo método de fábrica (ou classe) são, por design, herdados do mesmo objeto
pai; eles 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 = [Link]('Compacto');
é provavelmente o mais reconhecível no padrão de fábrica. Você tem um método que aceita um
tipo fornecido como string em tempo de execução e, em seguida, cria e retorna objetos do tipo
solicitado. Não há construtores usados com new 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 do snippet
anterior funcionar:
// função construtora pai
CarMaker() {}
// um método do pai
[Link] = function
" () { return
"Vroom, eu tenho + [Link] + "portas";
};
Fábrica | 147
Machine Translated by Google [Link]
E para um exemplo de "fábrica em ação", considere o construtor global Object() integrado. Ele
também se comporta como uma fábrica, pois cria objetos diferentes dependendo da entrada. Se
você passar um número primitivo, ele pode criar um objeto com o construtor Number() em segundo
plano. O mesmo vale para os valores string e booleanos. Quaisquer outros valores, incluindo valores
sem entrada, criarão um objeto normal.
Aqui estão alguns exemplos e testes do comportamento. Observe que Object pode ser chamado com
ou sem new:
// teste
[Link] === Objeto; // verdadeiro
[Link] === Número; // verdadeiro
[Link] === String; // verdadeiro
[Link] === 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 um exemplo de que o padrão de fábrica está ao nosso redor.
Iterador
No padrão iterador, você tem um objeto contendo algum tipo de dado agregado. Esses dados podem ser
armazenados internamente em uma estrutura complexa, e você deseja fornecer acesso fácil a cada
elemento dessa estrutura. O consumidor do seu objeto não precisa saber como você estrutura seus dados;
tudo o que ele precisa é 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, cabendo a você decidir o que "next" significa na 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 como este:
var element;
while (element = [Link]()) { // faça
algo com o elemento ... [Link](element);
No padrão iterador, o objeto agregado geralmente também fornece um método prático chamado hasNext() ,
para que os usuários do objeto possam determinar se chegaram ao fim dos seus dados. Outra maneira de
acessar todos os elementos sequencialmente, desta vez usando hasNext(), seria algo como o seguinte:
while ([Link]()) { //
faça algo com o próximo elemento...
[Link]([Link]());
}
Agora que temos os casos de uso, vamos ver como implementar esse objeto agregado.
Ao implementar o padrão iterador, faz sentido armazenar privadamente os dados e um ponteiro (índice)
para o próximo elemento disponível. Para demonstrar um exemplo de implementação, vamos supor que
os dados sejam apenas um array comum e que a lógica "especial" para recuperar o próximo elemento
consecutivo seja retornar um elemento sim, outro não do array:
var índice = 0,
dados = [1, 2, 3, 4, 5],
comprimento = [Link];
retornar {
próximo: função ()
{ var elemento;
se (![Link]())
{ retornar nulo;
} elemento = dados[índice];
índice = índice + 2;
retornar elemento;
Iterador | 149
Machine Translated by Google [Link]
},
hasNext: função ()
{ retornar índice < comprimento;
}
}; }());
Para fornecer acesso mais fácil e capacidade de iterar várias vezes sobre os dados, seu objeto pode
fornecer métodos de conveniência adicionais: rewind()
Para retornar o elemento atual, porque você não pode fazer isso com next() sem avançar o ponteiro
// [recortar...]
retornar {
// [recortar...]
rebobinar: função ()
{ índice = 0;
},
atual: função () { retornar
dados[índice];
}
}; }());
// voltar
[Link]();
[Link]([Link]()); // 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. Em JavaScript, os objetos são
mutáveis, portanto, o processo de adicionar funcionalidade a um objeto não é um processo complexo.
problema em si.
Uso
Vamos dar uma olhada em um exemplo de uso do padrão. Digamos que você esteja trabalhando em um site
aplicativo que vende algo. Cada nova venda é um novo objeto de venda . A venda “sabe”
sobre o preço do item e pode retorná-lo chamando o método [Link]() .
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 imposto provincial de Québec
imposto. Seguindo o padrão do decorador, você dirá que “decora” o objeto com um
decorador de impostos federais e um decorador de impostos de Quebec. Você também pode decorar o objeto
com funcionalidade de formatação de preços. Este cenário poderia ser o seguinte:
Em outro cenário, o comprador poderia estar em uma província que não tem 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 funcionalidades 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 na verdade herda
o objeto aprimorado até agora após o decorador anterior. Cada método decorado chama
Decorador | 151
Machine Translated by Google [Link]
o mesmo método no uber (o objeto herdado) e obtém o valor e prossegue fazendo algo adicional.
O efeito final é que, ao executar [Link]() no primeiro exemplo de uso, você está chamando o
método do decorador money (veja a Figura 7-1). Mas, como cada método decorado chama primeiro o
método do pai, o método getPrice() de money chama primeiro o método getPrice() de quebec , que por
sua vez chama o método getPrice() de fedtax , e assim por diante. A cadeia vai até o método getPrice()
original, não decorado, implementado pelo construtor Sale() .
função Venda(preço)
{ [Link]ço = preço || 100;
}
[Link]ó[Link]ço = função ()
{ retornar [Link]ço;
};
Todos os objetos decoradores serão implementados como propriedades de uma propriedade construtora:
[Link] = {};
Vejamos um exemplo de decorador. É um objeto que implementa o método personalizado get Price() . Observe que o
método primeiro obtém o valor do método pai e, em seguida, modifica esse valor:
[Link] =
{ getPrice: function ()
{ var price = [Link]();
*
preço += preço 5 / 100;
retornar preço;
}
};
Da mesma forma, podemos implementar outros decoradores, quantos forem necessários. Eles podem ser
extensões da funcionalidade principal do Sale() , implementados como plugins. Eles podem até mesmo "residir"
em arquivos adicionais e ser desenvolvidos e compartilhados por desenvolvedores terceirizados:
[Link] =
{ getPrice: function ()
{ var price = [Link]();
* 7,5 / 100;
preço += preço
return preço;
}
};
[Link] = {
obterPreço: função ()
{ retornar "$" + [Link]().toFixed(2);
}
};
[Link] =
{ obterPreço: função ()
{ retornar "CDN$ " + [Link]().toFixed(2);
}
};
Por fim, vamos ver o método “mágico” chamado decorate() que une todas as peças.
Lembre-se que será chamado assim:
Decorador | 153
Machine Translated by Google [Link]
se ([Link](i))
{ newobj[i] = substitui[i];
}
} retornar novoobjeto;
};
Essa implementação também pode permitir desfazer ou desfazer facilmente uma decoração, 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 decorate()
ao objeto. Nesta implementação, decorate() não faz nada ao objeto, apenas o anexa a uma lista:
O construtor Sale() agora tem uma lista de decoradores como uma propriedade própria:
função Venda(preço)
{ [Link] = (preço > 0) || 100;
this.decorators_list = [];
}
[Link] = {};
[Link] =
{ getPrice: function (preço) { 5 /
*
retornar preço + preço 100;
}
};
[Link] =
{ getPrice: function (preço) { 7,5 /
*
retornar preço + preço 100;
}
};
[Link] = {
this.decorators_list.length,
nome;
para (i = 0; i < máx; i += 1) {
nome = this.decorators_list[i]; preço
= [Link][nome].getPrice(preço);
} preço de retorno;
};
Esta segunda implementação do padrão decorador é mais simples e não envolve herança. Os
métodos de decoração também são mais simples. Todo o trabalho é feito pelo método que
"concorda" em ser decorado. Nesta implementação de exemplo, getPrice() é o único método que
permite decoração. Se você quiser ter mais métodos que podem 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 recebe um método e o torna "decorável".
Em tal implementação, a propriedade deco rators_list se tornaria um objeto com propriedades
nomeadas após os métodos e valores sendo arrays de objetos decoradores.
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 [Link]
Mas, dependendo da forma concreta e dos dados a serem validados, os clientes do seu validador
podem escolher diferentes tipos de verificação. O 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 vindos de um formulário em uma
página, e queira verificar se eles são válidos:
var data =
{ first_name: "Super",
last_name: "Man",
age: "unknown",
username: "o_O"
};
Para que o validador saiba qual é a melhor estratégia a ser usada 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 sobrenome e aceite qualquer nome, mas exija que a idade seja um
número e que o nome de usuário contenha apenas letras e números, sem símbolos especiais. A
configuração será algo como:
[Link] =
{ first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};
Agora que o objeto validador está configurado para manipular seus dados, chame seu método
validate() e imprima quaisquer erros de validação no console:
[Link](dados); se
([Link]())
{ [Link]([Link]("\n"));
}
Agora, vamos ver como o validador é implementado. Os algoritmos disponíveis para as verificações
são objetos com uma interface predefinida — eles fornecem um método validate() e informações de
ajuda de uma linha para serem usadas em mensagens de erro:
// verifica se há valores não vazios
[Link] = {
validar: função (valor) { valor de
retorno !== "";
},
instruções: "o valor não pode estar vazio"
};
var validador = {
// o método de interface //
`data` é chave => pares de valores
validar: função (dados) {
// redefine todas as
mensagens [Link] = [];
para (i em dados) {
se ([Link](i)) {
tipo = [Link][i];
verificador = [Link][tipo];
if (!type)
{ continue; // não há necessidade de validar
Estratégia | 157
Machine Translated by Google [Link]
};
}
result_ok = [Link](data[i]); if (!
result_ok) { msg =
"Valor inválido para *" + i + "*, " + [Link];
[Link](msg);
}
}
} retornar [Link]();
},
// auxiliar
hasErrors: função () { return
[Link] !== 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, em breve terá uma boa coleção de verificações específicas. Então, tudo
o que você precisa fazer para cada novo caso de uso é configurar o validador e executar o método
validate() .
Fachada
A fachada é um padrão simples; ela 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 essa prática, você terá um número maior de métodos do que se tivesse métodos
superdimensionados com muitos parâmetros. Às vezes, dois ou mais métodos podem ser chamados
juntos. Nesses casos, faz sentido criar outro método que envolva as chamadas de método repetidas.
pararPropagação()
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)
Esses são dois métodos distintos com propósitos distintos e devem ser mantidos separados, mas,
ao mesmo tempo, costumam ser chamados juntos. Portanto, em vez de duplicar as duas chamadas
de método em toda a aplicação, você pode criar um método de fachada que chame ambos:
var myevent =
{ // ...
parar: função (e) {
[Link]();
[Link]();
} // ...
};
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 eventos do IE:
var myevent =
{ // ...
stop: function (e) { //
outros if
(typeof [Link] === "function")
{ [Link]();
} // ...
};
O padrão de fachada também é útil em esforços de redesign e refatoração. Quando você deseja
substituir um objeto por uma implementação diferente, é preciso fazê-lo ao longo de um período (é
um objeto complexo), enquanto, ao mesmo tempo, um novo código é escrito utilizando 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ê chegar à substituição completa do
objeto antigo, terá menos código cliente para modificar, pois qualquer código cliente recente já usará
a nova API.
Procuração
No padrão de projeto proxy, um objeto atua como uma interface para outro objeto. É diferente do
padrão 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, protegendo
o acesso a esse objeto.
Este padrão pode parecer um overhead, mas é útil para fins de desempenho. O proxy atua como
guardião do objeto (também chamado de "sujeito real") e tenta fazer com que o sujeito real trabalhe
o mínimo possível.
Procuração | 159
Machine Translated by Google [Link]
Um exemplo de uso seria algo que podemos chamar de inicialização preguiçosa. Imagine que inicializar
o assunto real seja caro e que o cliente o inicialize, mas nunca o utilize. Nesse caso, o proxy pode ajudar
sendo a interface para o assunto real. O proxy recebe a solicitação de inicialização, mas nunca a repassa
até que fique claro que o assunto real está sendo realmente utilizado.
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 repassa a mensagem até que fique óbvio que o cliente precisa de algum
trabalho do sujeito. Só então o proxy repassa as duas mensagens juntas.
Figura 7-2. Relação entre cliente e sujeito real ao passar por uma procuração
Um Exemplo:
O padrão proxy é útil quando o sujeito real realiza algo custoso. Em aplicações web, uma das operações
mais custosas que você pode realizar é uma solicitação de rede, portanto, faz sentido combinar
solicitações HTTP o máximo possível. Vejamos um exemplo que faz exatamente isso e demonstra o
padrão proxy em ação.
Uma expansão
de vídeo. Vamos usar um pequeno aplicativo que reproduz vídeos de um artista selecionado (Figura
7-3). Você pode experimentar o exemplo ao vivo e observar o código em http:// [Link]/ book/
7/ [Link] .
Você tem uma lista de títulos de vídeos 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 ele
seja reproduzido. As informações detalhadas do vídeo e a URL do vídeo não fazem parte da página; elas
precisam ser recuperadas por meio de uma chamada de serviço web. O serviço web pode aceitar vários
IDs de vídeo, portanto, podemos acelerar a aplicação fazendo menos solicitações HTTP sempre que
possível e recuperando dados de vários vídeos simultaneamente.
Nosso aplicativo permite que vários (ou todos) vídeos sejam expandidos ao mesmo tempo, então esta é
uma oportunidade perfeita para combinar solicitações de serviços web.
Sem um proxy
Os principais “atores” da aplicação são os dois objetos:
vídeos
pela
comunicação com o servidor através do método [Link]()
Quando não há proxy, [Link]() chamará [Link]() uma vez para cada
vídeo. Quando adicionamos um proxy, ele se tornará um novo ator chamado proxy e
ficará entre vídeos e http , delegando as chamadas para makeRequest(), combinando-as
sempre que possível.
Procuração | 161
Machine Translated by Google [Link]
Vamos examinar primeiro o código sem o proxy e depois 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 $ , que é uma abreviação
conveniente:
Usando a delegação de eventos (mais sobre esse padrão no Capítulo 8), vamos lidar com todos os
cliques que ocorrem na lista ordenada id="vids" com uma única função:
$('vids').onclick = função (e) { var src, id;
e = e || [Link]; src
= [Link] || [Link];
} [Link] = falso;
id = [Link]('--')[1];
se ([Link] ===
"reproduzir") {[Link] =
ví[Link](id); retornar;
No manipulador de cliques genérico, 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 alvo tem o nome de classe
"play"), o que significa que a seção de informações já foi expandida, e podemos então chamar getPlayer(). Os
IDs dos vídeos são extraídos dos links hrefs.
O outro manipulador de cliques reage aos cliques para alternar todas as seções de informações. Basicamente, é
só chamar getInfo() novamente, mas em um loop:
var hrefs, i,
max,
id;
} // pular desmarcado
if (!hrefs[i].[Link]) { continue;
id = hrefs[i].[Link]('--')[1];
hrefs[i].[Link] = "v" + id;
ví[Link](id);
}
};
objeto de vídeos
obterJogador()
Retorna o HTML necessário para reproduzir o vídeo em Flash (não relevante para a discussão).
updateList()
O retorno de chamada que recebe todos os dados do serviço web e produz o código HTML a ser usado na
seção de informações expandidas. Também não há nada particularmente interessante acontecendo neste
método. getInfo()
O método que alterna a visibilidade das seções de informações e também faz chamadas para http passando
updateList() como um retorno de chamada.
Procuração | 163
Machine Translated by Google [Link]
se (!info)
{ [Link]([id], "[Link]"); retornar;
}
};
Objeto
http O objeto http tem apenas um método, que faz a solicitação JSONP ao serviço web YQL do
Yahoo!:
var http =
{ makeRequest: função (ids, retorno de
chamada) { var url = '[Link]
sql = 'select * from [Link] onde ids IN ("%ID%")', format =
"format=json", handler =
"callback=" + callback, script =
[Link]('script');
[Link](script);
}
};
YQL (Yahoo! Query Language) é um meta serviço web que oferece a capacidade de usar
sintaxe semelhante a SQL para consumir vários outros serviços web sem precisar
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
web, com consultas YQL semelhantes a:
selecione * de [Link] onde ids IN ("2158073")
Insira 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 50 ms. O objeto de vídeos 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 50 ms, elas serão mescladas em uma. Um atraso de 50 ms é
praticamente imperceptível para o usuário, mas pode ajudar a combinar solicitações e acelerar a
experiência ao clicar em "alternar" e expandir mais de um vídeo de uma vez. Também reduz
significativamente a carga do servidor, pois o servidor web precisa lidar com um número menor de
solicitações.
O proxy configura uma fila para coletar os IDs dos vídeos recebidos nos últimos 50 ms e, em seguida,
limpa a fila chamando http e fornece sua própria função de retorno de chamada, porque o retorno de
chamada [Link]() pode manipular apenas um único registro de dados.
// adicionar à fila
[Link](id);
[Link] = retorno de
chamada; [Link] = contexto;
// configurar tempo
limite if (![Link])
{ [Link] = setTimeout(function ()
{ [Link](); },
[Link]);
Procuração | 165
Machine Translated by Google [Link]
}
},
flush: função () {
[Link]([Link], "[Link]");
},
manipulador: função (dados)
{ var i, max;
// vídeo único if
(parseInt([Link], 10) === 1) {
[Link]([Link], [Link].Vídeo);
retornar;
}
As Figuras 7-4 e 7-5 ilustram os cenários de 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
do mesmo vídeo novamente. Mas isso pode não ser sempre o caso. O proxy pode ir além na
proteção do objeto HTTP real , armazenando em cache os resultados de solicitações anteriores em
uma nova propriedade de cache (veja a Figura 7-6). Assim, se o objeto de vídeos solicitar informações
sobre o mesmo ID de vídeo pela segunda vez, o proxy pode extraí-las do cache e economizar o
tempo de ida e volta da rede.
Mediador
Aplicações — grandes e pequenas — são compostas por 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 da aplicação sem interromper o restante. À
medida que a aplicação 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 uns sobre os outros e se
comunicam diretamente (chamam os métodos uns dos outros e alteram propriedades), isso leva a
um acoplamento rígido indesejável. Quando os objetos estão intimamente acoplados, não é fácil alterá-los.
Mediador | 167
Machine Translated by Google [Link]
um objeto sem afetar muitos outros. Assim, mesmo a mais simples alteração em um aplicativo
deixa de ser trivial, e é praticamente impossível estimar o tempo que uma alteração pode levar.
O padrão mediador ameniza essa situação, promovendo acoplamento fraco e ajudando a melhorar
a manutenibilidade (ver Figura 7-7). Nesse padrão, os objetos independentes (colegas) não se
comunicam diretamente, mas por meio de um objeto mediador . Quando um dos colegas muda de
estado, ele notifica o mediador, que comunica a mudança a quaisquer outros colegas que devam
saber.
Exemplo de Mediador:
Vamos explorar um exemplo de uso do padrão mediador. A aplicação será um jogo em que dois
jogadores têm meio minuto para competir para ver quem pressiona um botão mais vezes do que o
outro. O jogador 1 compete pressionando 1 e o jogador 2 pressiona 0 (para que fiquem mais
confortáveis e não precisem brigar pelo teclado). Um placar é atualizado com a pontuação atual.
• Jogador 1
• Jogador 2
• Placar
• Mediador
O mediador tem conhecimento de todos os outros objetos. Ele se comunica com o dispositivo de
entrada (teclado), processa eventos de pressionamento de tecla, determina qual jogador tem a vez
e o notifica (veja a Figura 7-8). O jogador joga (ou seja, apenas atualiza sua própria pontuação
com um ponto) e notifica o mediador de que terminou. O mediador comunica a pontuação
atualizada ao placar, que por sua vez atualiza a tela.
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 outra tela mostrando o tempo
restante.
Você pode ver a versão ao vivo do jogo e dar uma olhada no código-fonte em http:// [Link]/
book/ 7/ [Link] .
Os objetos do jogador são criados com um construtor Player() e possuem suas próprias
propriedades, pontos e nome. O método play() do protótipo incrementa os pontos em um e então
notifica o mediador:
função Jogador(nome)
{ [Link] = 0;
[Link] = nome;
}
[Link]ó[Link] = função ()
{ [Link] += 1;
[Link]();
};
Mediador | 169
Machine Translated by Google [Link]
O objeto placar possui um método update() , que é chamado pelo objeto mediador após
a vez de cada jogador. O placar não sabe sobre nenhum jogador e não registra a
pontuação; ele exibe apenas a pontuação dada pelo mediador:
var placar = {
// atualiza a exibição da
pontuação update: function (score) {
} [Link] = msg;
}
};
Agora, vamos dar uma olhada no objeto mediador . Ele inicializa o jogo, criando objetos
de jogador em seu método setup() e monitora os jogadores em sua propriedade players .
O método played() é chamado por cada jogador após cada jogada. 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 sua vez e o notifica:
var mediador = {
// todos os jogadores
jogadores: {},
// configuração de
inicialização: function
() { var players = [Link];
[Link] = new Player('Home');
[Link] = new Player('Guest');
},
[Link](pontuação);
},
Observador
A principal motivação por trás desse padrão é promover o acoplamento flexível. 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 pode frequentemente passar uma mensagem na forma de um objeto de
evento.
entender como implementar esse padrão, vamos usar um exemplo concreto. Digamos que você
tenha uma editora que publica um jornal diário e uma revista mensal. O assinante Joe será
notificado sempre que isso acontecer.
O objeto de papel precisa ter uma propriedade subscribers , que é um array que armazena todos
os assinantes. O ato de assinar é simplesmente adicionar algo a esse array. Quando um evento
ocorre, o papel percorre a lista de assinantes e os notifica. A notificação significa
Observador | 171
Machine Translated by Google [Link]
O artigo também pode fornecer unsubscribe(), o que significa remover assinantes do array. O último
método importante do artigo é publish(), que chamará os métodos dos assinantes. Resumindo,
um objeto publicador precisa ter estes membros:
assinantes
Uma matriz
subscribe()
Adicionar ao conjunto de assinantes
unsubscribe()
Remover do array de assinantes publish()
Faça um loop pelos assinantes e chame 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. Assim, podemos copiá-los (padrão mix-in) para qualquer objeto
e transformá-lo em um publicador.
} [Link][tipo].push(fn);
},
cancelar inscrição: função (fn, tipo)
{ [Link]('cancelar inscrição', fn, tipo);
},
publicar: função (publicação, tipo)
{ [Link]('publicar', publicação, tipo);
},
visitSubscribers: função (ação, argumento, tipo) { var
pubtype = tipo || 'qualquer',
assinantes = [Link][pubtype], i, max
=
[Link];
E aqui está uma função que pega um objeto e o transforma em um publicador simplesmente
copiando os métodos do publicador genérico:
função makePublisher(o) { var
i; para
(i em publisher) { se
([Link](i) && typeof publisher[i] === "function") { o[i] = publisher[i];
Agora vamos implementar o objeto de papel . Tudo o que ele pode fazer é publicar diariamente e mensalmente:
var paper =
{ daily: function ()
{ [Link]("grandes notícias de hoje");
},
mensal: function ()
{ [Link]("análise interessante", "mensal");
}
};
Agora que temos um publicador, vamos ver o objeto assinante joe, que tem dois métodos:
var joe =
{ drinkCoffee: function (paper) '
{ [Link]('Apenas ler + papel);
},
sundayPreNap: função (mensal) '
{ [Link]('Estou prestes a adormecer lendo isso + mensal);
}
};
Observador | 173
Machine Translated by Google [Link]
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 evento do tipo "mensal" ocorre. Agora vamos disparar alguns
eventos:
[Link]ário();
[Link]ário();
[Link]ário();
[Link]();
A parte boa aqui é que o objeto de papel não codifica Joe, e Joe não codifica o papel. Também não
há um objeto mediador que saiba tudo. Os objetos participantes são fracamente acoplados e, sem
modificá-los, podemos adicionar muitos outros assinantes ao papel; Joe também pode cancelar a
assinatura a qualquer momento.
Vamos levar este exemplo um passo adiante e também tornar Joe um editor. (Afinal, com blogs e
microblogs, qualquer um pode ser um editor.) Então, Joe se torna um editor e pode postar atualizações
de status no Twitter:
makePublisher(joe);
[Link] = função (msg)
{ [Link](msg);
};
Agora imagine que o departamento de relações públicas do jornal decide ler o que seus leitores tuitam
e assina o Joe, fornecendo o método readTweets():
[Link] = function (tweet)
'
+ tweet);
{ alert('Ligue para uma reunião importante! Alguém
};
[Link]([Link]);
O resultado é um alerta: "Convoque uma reunião importante! Alguém odiou o jornal hoje."
Você pode ver o código-fonte completo e jogar no console com uma demonstração ao vivo em http://
[Link]/ book/ 7/ [Link] .
Teclas. Vamos dar outro exemplo. Implementaremos o mesmo jogo do padrão mediador, mas desta
vez usando o padrão observador. Para torná-lo um pouco mais avançado, vamos aceitar um número
ilimitado de jogadores, não apenas dois. Ainda teremos o construtor Player() que cria objetos de jogador
e o objeto placar . Apenas o mediador se tornará um objeto de jogo .
No padrão mediador, o objeto mediador conhece todos os outros objetos participantes e chama seus
métodos. O objeto do jogo no padrão observador não fará isso; em vez disso, deixará que os objetos
se inscrevam em eventos interessantes. Por exemplo, o placar se inscreverá no evento "scorechange"
do jogo .
Vamos primeiro revisitar o objeto genérico do editor e ajustar um pouco sua interface para torná-lo mais
próximo 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 this referindo-se ao seu próprio objeto.
Observador | 175
Machine Translated by Google [Link]
[Link]ó[Link] = função ()
{ [Link] += 1;
[Link]('jogar', isto);
};
A novidade aqui é que o construtor aceita a tecla key, a tecla do teclado que o jogador pressiona para
marcar pontos. (As teclas eram codificadas antes.) Além disso, toda vez que um novo objeto player é
criado, o evento "newplayer" é disparado. Da mesma forma, toda vez que um jogador joga, o evento
"play" é disparado.
O objeto do placar permanece o mesmo; ele simplesmente atualiza a exibição com o atual
pontuação.
O novo objeto de jogo pode rastrear todos os jogadores, produzindo uma pontuação e disparando um
evento "scorechange". Ele também se inscreverá em todos os eventos "keypress" do navegador e
saberá quais teclas correspondem a cada jogador: var game = {
chaves: {},
jogadores = [Link],
pontuação = {};
para (i em jogadores)
{ se ([Link](i))
{ pontuação[jogadores[i].nome] = jogadores[i].pontos;
}
} [Link]('scorechange', pontuação);
}
};
O objeto do jogo assina os eventos “play” e “newplayer” (e também “keypress” do navegador), enquanto
o placar assina “scorechange”: [Link]("newplayer", "addPlayer",
game); [Link]("play", "handlePlay", game);
[Link]("scorechange", [Link], scoreboard);
[Link] = [Link];
Como você pode ver aqui, o método on() permite que os assinantes especifiquem o retorno de chamada
como uma referência a uma função ([Link]) 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 dinamicamente objetos de jogador (com suas teclas correspondentes
a serem pressionadas), quantos o usuário desejar:
var playername, chave;
while (1)
{ playername = prompt("Adicionar jogador
(nome)"); if (!
playername) { break;
} enquanto (1)
"
{ chave = prompt("Chave + nome do jogador + "?");
para if
(chave) { break;
}
E isso conclui o jogo. Você pode ver o código fonte completo e jogá-lo em http:// [Link]/ book/ 7/
[Link] .
Observe que, na implementação do padrão mediador, o objeto mediador precisava conhecer 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 [Link]
observando certos eventos e tomando medidas: por exemplo, o placar ouvindo o evento "scorechange". Isso
resulta em um acoplamento ainda mais fraco (quanto menos um objeto sabe, melhor) ao preço de tornar um
pouco mais difícil acompanhar quem ouve qual evento. Neste jogo de exemplo, todas as inscrições ocorreram
no mesmo lugar no código, mas à medida que um aplicativo cresce, as chamadas on() podem estar em todos
os lugares (por exemplo, no código de inicialização de cada objeto). Isso tornará mais difícil a depuração, 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 sequencial procedural de código, 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 abordar sua
implementação em JavaScript. Os padrões de design que discutimos
eram:
Singleton:
Criando apenas um objeto de uma "classe". Analisamos diversas abordagens caso você queira substituir
a ideia de uma classe por uma função construtora e preservar a sintaxe semelhante à de Java. Caso
contrário, tecnicamente, todos os objetos em JavaScript são singletons. E, às vezes, os desenvolvedores
também usam o termo "singleton", referindo-se a 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 executar um loop e navegar em torno de dados personalizados complexos
estrutura.
Decorador
Ajustando objetos em tempo de execução adicionando funcionalidades de objetos decoradores
predefinidos.
Estratégia
Manter a mesma interface enquanto seleciona a melhor estratégia para lidar com a tarefa específica
(contexto).
Fachada
Fornecendo uma API mais conveniente ao encapsular métodos comuns (ou mal projetados) em uma
nova.
Encapsular um objeto para controlar o acesso a ele, com o objetivo de evitar operações caras, agrupando-as
ou executando-as somente quando realmente necessário.
Mediador
Promovendo o acoplamento flexível fazendo com que seus objetos não “conversem” entre si diretamente, mas
apenas por meio de um objeto mediador.
Observador
Acoplamento fraco por meio da criação de objetos “observáveis” que notificam todos os seus observadores
quando ocorre um evento interessante (também chamado de assinante/publicador ou “eventos personalizados”).
Resumo | 179
Machine Translated by Google [Link]
Machine Translated by Google [Link]
CAPÍTULO 8
Nos capítulos anteriores do livro, o foco principal foi no JavaScript básico (ECMAScript) e não tanto nos
padrões de uso do JavaScript no navegador. Em contraste, este capítulo explora uma série de padrões
específicos do navegador, pois este é o ambiente mais comum para programas JavaScript. Scripts de
navegador também são o que a maioria das pessoas tem em mente quando diz que não gosta de
JavaScript. E isso é compreensível, considerando todos os objetos host inconsistentes e implementações
de DOM existentes nos navegadores. É óbvio que podemos nos beneficiar de quaisquer boas práticas
que ajudem a tornar a criação de scripts de cliente menos trabalhosa.
Neste capítulo, você verá alguns padrões divididos em diversas áreas, incluindo script DOM, tratamento
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 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 o mais separadas possível melhora a entrega do aplicativo a uma ampla
gama de agentes de usuário — navegadores gráficos, navegadores somente 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 [Link]
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 terá uma melhor apresentação do documento. Se o navegador suportar JavaScript, o documento
se tornará mais um aplicativo que adiciona mais recursos para aprimorar a experiência.
• Testar a página com o CSS desativado para verificar se ela ainda pode ser usada e se o conteúdo
está presente e legível. •
Testar com o JavaScript desativado e verificar se a página ainda pode executar sua finalidade principal,
se todos os links funcionam (sem instâncias de href="#") e se todos os formulários ainda funcionam
e são enviados corretamente.
• Não usar manipuladores de eventos em linha (como onclick) ou atributos de estilo em linha , porque
estes não pertencem à camada de conteúdo
• Usando elementos HTML semanticamente significativos, como títulos e listas
A camada JavaScript (o comportamento) deve ser discreta, ou seja, 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 diferenças entre navegadores com elegância é a detecção de
capacidade. Ela sugere que você não deve usar a detecção de agente de usuário para determinar um
caminho de código, mas sim verificar se um método ou propriedade que deseja usar existe no ambiente
atual. A detecção de agente de usuário é geralmente considerada 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á uma queda significativa no desempenho):
// antipadrão se
([Link]('MSIE') !== ÿ1)
{ [Link]('onclick', [Link]);
}
// melhor
se ([Link])
{ [Link]('onclick', [Link]);
}
Scripting DOM
Trabalhar com a árvore DOM de uma página é uma das tarefas mais comuns em JavaScript do lado
do cliente. Isso também é uma grande causa de dores de cabeça (e dá má fama ao JavaScript), pois
os métodos do DOM são implementados de forma inconsistente em todos os navegadores. É por
isso que usar uma boa biblioteca JavaScript, que abstrai as diferenças entre navegadores, pode
acelerar significativamente o desenvolvimento.
Vamos ver alguns padrões recomendados ao acessar e modificar a árvore DOM, abordando
principalmente questões de desempenho.
Acesso DOM
O acesso ao DOM é caro; é o gargalo mais comum quando se trata de desempenho de JavaScript.
Isso ocorre porque o DOM geralmente é implementado separadamente do mecanismo JavaScript.
Da perspectiva de um navegador, faz sentido adotar essa abordagem, pois um aplicativo JavaScript
pode não precisar de DOM. Além disso, outras linguagens além de JavaScript (por exemplo, VBScript
no IE) podem ser usadas para trabalhar com o DOM da página.
O ponto principal é que o acesso ao DOM deve ser reduzido ao mínimo. Isso significa:
Considere o exemplo a seguir, onde o segundo (melhor) loop, apesar de ser mais longo, será
dezenas a centenas de vezes mais rápido, dependendo do navegador: //
antipattern for
(var i = 0; i < 100; i += 1)
{ [Link]("result").innerHTML += i + ", ";
}
} [Link]("resultado").innerHTML += conteúdo;
No próximo trecho, o segundo exemplo (usando uma variável local style) é melhor, embora exija mais
uma linha de código e mais uma variável: // antipattern var padding
=
[Link]("result").[Link], margin =
[Link]("result").[Link];
// melhor
var style = [Link]("result").style,
preenchimento =
[Link], margem = [Link];
Esses métodos aceitam uma string seletora 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 fizesse 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 da sua biblioteca preferida.
Também ajudará se você adicionar atributos id="" aos elementos que você acessará com frequência,
porque [Link](myid) é a maneira mais fácil e rápida de encontrar um nó.
Manipulação do DOM
Além de acessar os elementos do DOM, muitas vezes é necessário alterá-los, remover alguns deles
ou adicionar novos. Atualizar o DOM pode fazer com que o navegador redesenhe a tela e também,
com frequência, refaça o fluxo (recalcule a geometria dos elementos), o que pode ser caro.
Novamente, a regra geral é ter menos atualizações de DOM, o que significa agrupar alterações e
executá-las fora da árvore de documentos "ativa".
Quando precisar criar uma subárvore relativamente grande, você deve fazê-lo sem adicionar nada 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 = [Link]('p');
t = [Link]('primeiro parágrafo');
[Link](t);
[Link](p);
p = [Link]('p');
t = [Link]('segundo parágrafo');
[Link](t);
[Link](p);
Uma versão melhor seria 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 fragmento em si. E isso é realmente
conveniente. Portanto, o fragmento de documento é uma boa maneira de encapsular vários nós, mesmo
quando você não os contém em um elemento pai adequado (por exemplo, seus parágrafos não estão em
um elemento div ).
frag = [Link]();
p = [Link]('p'); t
= [Link]('primeiro parágrafo');
[Link](t);
[Link](p);
p = [Link]('p'); t
= [Link]('segundo parágrafo');
[Link](t);
[Link](p);
[Link](frag);
Neste exemplo, o documento ativo é atualizado apenas uma vez, causando um único refluxo/repintura, em
oposição a um para cada parágrafo, como era o caso no snippet antipadrão anterior.
O fragmento de documento é útil ao adicionar novos nós à árvore. Mas, ao atualizar uma parte existente da
árvore, você ainda pode fazer alterações em lote. Você pode clonar a 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 =
[Link]('result'), clone = [Link](true);
// quando terminar:
[Link](clone, oldnode);
Eventos
Outra área de scripts de navegador repleta de inconsistências e fonte de frustração é o trabalho com
eventos do navegador, como clique, mouseover e assim por diante. Novamente, uma biblioteca JavaScript
pode eliminar grande parte do trabalho duplo necessário para suportar o IE (anterior à versão 9) e as
implementações em conformidade com o W3C.
Vamos rever os pontos principais, porque você pode não estar sempre usando uma biblioteca existente
para páginas simples e truques rápidos, ou você pode estar criando sua própria biblioteca.
Eventos | 185
Machine Translated by Google [Link]
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 incrementa um contador cada vez que você clica nele. Você pode adicionar um atributo onclick
embutido , que funcionará em todos os navegadores, mas violará a separação de interesses e o aprimoramento
progressivo. Portanto, você deve se esforçar para anexar o ouvinte em JavaScript, fora de qualquer marcação.
Você pode atribuir uma função à propriedade onclick do nó, mas só pode fazer isso
uma vez:
Se você deseja que várias funções sejam executadas com um clique, não é possível fazê-lo com este padrão
mantendo o acoplamento fraco. Tecnicamente, você pode verificar se onclick já contém uma função e, em caso
afirmativo, adicionar a existente à sua própria função e substituir o valor onclick pela nova função. Mas uma
solução muito mais limpa é usar o método addEventListener() . Este método não existe no IE até a versão 8,
inclusive, então você precisa de attachEvent() para esses navegadores.
Quando analisamos o padrão de ramificação do 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 muitos detalhes agora, vamos anexar um ouvinte ao nosso botão:
var b = [Link]('clickme'); if
([Link]) { // W3C
[Link]('click', myHandler, false);
} else if ([Link]) { // IE
[Link]('onclick', myHandler); } else
{ // último recurso [Link]
= 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 tornar a operação um pouco mais interessante,
vamos supor que temos vários botões e um único myHandler() para todos eles. Manter uma referência a cada
nó de botão e um contador para o número será ineficiente, visto que podemos obter essa informação do objeto
de evento criado a cada clique.
função myHandler(e) {
// sem bolha
if (typeof [Link] === "function")
{ [Link]();
• Primeiro, precisamos obter acesso ao objeto de evento, que contém informações sobre o
evento e o elemento da página que o acionou. Este objeto de evento é passado para o
manipulador de eventos de retorno de chamada, mas não ao usar a propriedade onclick ,
onde é acessível através da propriedade global [Link] . • A
segunda parte é realizar o trabalho real de atualizar o rótulo. • Em seguida,
é 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 borbulha 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, em seguida, de forma diferente para o IE (usando
cancelBubble).
• Por fim, impeça a ação padrão, se necessário. Alguns eventos (clicar em um link, enviar um
formulário) têm ações padrão, mas você pode impedi-los usando preventDefault() (ou, no
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 [Link]
Delegação de Eventos:
Vejamos um exemplo com três botões dentro de uma div (veja a Figura 8-1). Uma demonstração ao
vivo do exemplo de delegação de eventos está disponível em http:// [Link]/ book/ 8/ click-
[Link] .
Figura 8-1. Exemplo de delegação de evento: três botões que incrementam seus rótulos ao clicar
// ... //
obter evento e elemento de origem
e = e || [Link]; src
= [Link] || [Link];
} // ...
Na verdade, nunca será chamado quando o evento ocorrer fora dos nós que você considera
importantes. Nesse caso, o código para anexar um ouvinte de evento
seria simplesmente: [Link]('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) {
[Link]();
}
Além disso, se o script exigir muito esforço, a interface do navegador fica sem resposta a ponto de o
usuário não conseguir clicar em nada. Isso prejudica a experiência e deve ser evitado.
Em JavaScript não há threads, mas você pode simulá-las no navegador usando setTimeout() ou, em
navegadores mais modernos, web workers.
definirTempoEsgotado()
A ideia é dividir uma grande quantidade de trabalho em partes menores e executar cada parte com
um tempo limite de 1 ms. Usar partes com 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 se
sentirá mais confortável e no controle do navegador.
Um tempo limite de 1 (ou mesmo 0 milissegundos) pode, na verdade, ser maior do que isso,
dependendo do navegador e do sistema operacional. Definir um tempo limite de 0 não significa
imediatamente, mas sim "o mais breve possível". No Internet Explorer, por exemplo, o menor
"tique" do relógio é de 15 milissegundos.
Trabalhadores da Web
Navegadores recentes oferecem outra solução para scripts de longa duração: 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 a partir do
programa principal (página) da seguinte forma:
var ww = new Worker('my_web_worker.js');
[Link] = function (event)
{ [Link] "
+= "<p>mensagem do thread em segundo plano:+ [Link] + "</p>";
};
A fonte para um web worker que realiza uma operação aritmética simples 1e8 vezes (1 com 8 zeros)
é mostrada aqui:
postMessage('olá');
enquanto (fim)
{ fim -= 1;
tmp += fim;
se (fim === 5e7) { // 5e7 é a metade de 1e8
'
postMessage('na metade do caminho, `tmp` agora +está
tmp);
}
}
postMessage('tudo pronto');
O trabalhador usa postMessage() para se comunicar com o chamador, e este assina o evento
onmessage para receber atualizações. O retorno de chamada onmessage recebe um objeto de evento
como argumento, e este objeto contém os dados da propriedade, que podem ser qualquer coisa que
o trabalhador queira passar. Da mesma forma, o chamador pode passar dados para o trabalhador
usando (neste exemplo) [Link]() , e o trabalhador pode assinar essas mensagens usando
um retorno de chamada onmessage .
Scripting Remoto: Os
aplicativos web atuais costumam usar scripting remoto para se comunicar com o servidor sem precisar
recarregar a página atual. Isso permite aplicativos web muito mais responsivos e com aparência de
desktop. Vamos considerar algumas maneiras de se comunicar com o servidor usando 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 anterior à versão 7, a funcionalidade XHR era implementada como um objeto ActiveX, então um caso
especial é necessário aqui.
O último passo é 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 quaisquer dados POST ou apenas
uma string em branco no caso de GET. O último parâmetro de open() especifica se a solicitação é assíncrona.
Assíncrono significa que o navegador não ficará bloqueado aguardando a resposta. Esta é
definitivamente a melhor experiência do usuário, portanto, a menos que haja um forte motivo
para isso, o parâmetro assíncrono deve ser sempre
verdadeiro: [Link]("GET",
"[Link]", true); [Link]();
Abaixo está um exemplo prático completo de como buscar o conteúdo de uma nova página e atualizar a página atual
com o novo conteúdo (demonstração disponível em http:// [Link]/ book/ 8/ [Link] ):
[Link] = função () { se
([Link] !== 4) { retornar
falso;
[Link]("OBTER", "[Link]",
verdadeiro); [Link]("");
• Devido às 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 de retorno de chamada verifica a propriedade readyState do objeto xhr . Há cinco valores
possíveis para essa propriedade — de 0 a 4 — onde 4 significa "concluído". Se o estado ainda não
estiver completo, continuamos aguardando o próximo evento readystatechange . • O retorno de
chamada também verifica a propriedade status do objeto xhr . Essa 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 reportamos todos os outros como
erros (para simplificar; caso contrário, existem outros códigos de status válidos que você pode
verificar). • O código, conforme listado, verificará a maneira suportada de criar um objeto XHR sempre
que uma solicitação for feita. Tendo visto alguns padrões em capítulos anteriores (por exemplo,
ramificação em tempo de inicialização), você pode reescrever isso para fazer a verificação apenas
uma vez.
JSONP
JSONP (JSON com preenchimento) é outra maneira de fazer solicitações remotas. Ao contrário do XHR,
não é restrito pela política de navegador de mesmo domínio, portanto, deve ser usado com cautela devido
às implicações de segurança do carregamento de dados de sites de terceiros.
Com JSONP, os dados geralmente são encapsulados em JSON em uma chamada de função, onde o
nome da função é fornecido com a solicitação.
[Link] pode ser qualquer tipo de página ou script. O parâmetro callback especifica qual função
JavaScript manipulará a resposta.
O servidor responde com alguns dados JSON passados como parâmetro para a função de retorno
de chamada. O resultado final é que você incluiu um novo script na página, que por acaso é uma
chamada de função. Por exemplo:
myHandler({"olá": "mundo"});
da velha. Vamos colocar o JSONP em prática com um exemplo: um jogo da velha, em que 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 da vez do servidor (veja 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):
O quadro conterá nove células de tabela com os atributos de id correspondentes . Por exemplo:
<td id="célula-1"> </td>
<td id="célula-2"> </td>
<td id="célula-3"> </td>
...
// atalho get:
função (id) { return
[Link](id);
},
// manipular cliques
setup: function ()
{ [Link]('new').onclick = [Link];
[Link]('server').onclick = [Link];
},
// limpa o tabuleiro
newGame: function () { var
tds = [Link]("td"), max =
[Link], i;
} [Link] = [];
},
// faça uma
solicitação remoteRequest:
function () { var script = [Link]("script");
[Link] = "[Link]?callback=[Link]&played=" + [Link](',');
[Link](script);
},
setTimeout(function ()
{ [Link](); },
300); // como se estivesse pensando bastante
},
se ([Link] === 9)
{ alerta("Fim de jogo");
retornar;
}
};
O objeto ttt mantém uma lista de células reproduzidas até o momento em [Link] e as envia ao servidor,
para que este possa retornar um novo número, excluindo as que já foram reproduzidas. Se ocorrer um erro,
o servidor responderá com uma saída como: [Link]({"error": "Descrição do
erro aqui"});
Como você pode ver, o retorno de chamada em JSONP precisa ser uma função pública e globalmente
disponível, não necessariamente uma função 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:
[Link](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.
maneira alternativa de executar scripts remotos é usar frames. Com JavaScript, você pode criar um iframe e
alterar sua URL de origem . A nova URL pode conter dados e chamadas de função que atualizam o autor da
chamada — 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 está esperando uma resposta. Nesses casos, você pode criar uma nova imagem e apontar sua origem
para o script no servidor:
Este 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. Como você não tem
Para usar a resposta de tal beacon, uma prática comum (mas um antipadrão) é fazer com que o
servidor responda com uma imagem GIF 1×1. Uma opção melhor seria responder com uma resposta
HTTP “204 Sem Conteúdo”. Isso significa que apenas um cabeçalho e nenhum corpo de resposta são
enviados de volta ao cliente.
Implementando JavaScript
Existem algumas considerações de desempenho quando se trata de servir JavaScript. Vamos discutir
as mais importantes em detalhes. Para mais 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 mínimo possível de componentes
externos, pois as solicitações HTTP são caras. No caso do 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. Trata-se de um arquivo .js . Você também
está usando alguns plugins jQuery, que também vêm em arquivos separados. Dessa forma, você pode
rapidamente terminar 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 HTTP levará mais tempo do que o download em si. 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 ocorrer somente antes do código ir para produção e
não durante o desenvolvimento, onde tornará a depuração mais difícil.
• É mais uma etapa antes de entrar no ar, mas pode ser facilmente automatizada e feita em
a linha de comando, por exemplo usando cat no Linux/Unix:
$ cat [Link] [Link] [Link] > [Link] • Perder alguns
dos benefícios do cache — ao fazer uma pequena alteração em um dos arquivos, você invalida todo
o pacote. Por isso, é bom ter um cronograma de lançamento para projetos maiores ou considerar
ter dois pacotes: um que contenha os arquivos que devem sofrer alterações e um pacote
"principal" que praticamente não sofre alterações. • Você precisa criar um padrão de
nomenclatura e controle de versão para o pacote, como usar um registro de data e 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 que o processo de minificação também faça parte do processo
de lançamento do seu build.
Quando você pensa da perspectiva do usuário, não há razão para que ele tenha que baixar todos os comentários
no seu código, que não servem para nada na maneira como o aplicativo funciona.
O benefício da minificação pode variar dependendo da abundância de comentários e espaços em branco usados,
e também das ferramentas de minificação específicas que você utiliza. Mas, em média, você teria uma redução de
cerca de 50% no tamanho do arquivo.
Servir o arquivo de script compactado também é algo que você deve sempre fazer. É uma configuração simples e
única do servidor para habilitar a compactação gzip, o que proporciona um aumento instantâneo na velocidade.
Mesmo se você estiver usando um provedor de hospedagem compartilhada que não oferece 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 . Então, na raiz do seu site, adicione isto ao arquivo .htaccess :
A compactação resultará em 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 minificado e não
compactado que você escreveu.
Cabeçalho Expira
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 em 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á fará
isso se tiver estabelecido uma convenção de nomenclatura para seus pacotes de arquivos combinados.
Usando uma
CDN CDN significa Rede de Distribuição de Conteúdo (Content Delivery Network). Trata-se de um
serviço de hospedagem pago (às vezes bastante caro) que permite distribuir cópias dos seus arquivos
em diferentes data centers ao redor do mundo e disponibilizá-las mais rapidamente aos seus usuários,
mantendo a mesma URL no seu código.
Mesmo que você não tenha orçamento para CDN, você ainda pode se beneficiar de algumas opções gratuitas:
• O Google hospeda uma série de bibliotecas populares de código aberto, às quais você pode se conectar para
grátis e aproveite seu CDN.
Estratégias de carregamento
Como incluir um script em uma página da web parece uma questão simples à primeira vista — você usa um
elemento <script> e incorpora o código JavaScript ou vincula a um arquivo separado no atributo src :
// opção 1
<script>
[Link]("olá mundo"); </
script> //
opção 2
<script src="[Link]"></script>
Mas há mais alguns padrões e considerações que você deve conhecer quando seu objetivo é criar aplicativos
web de alto desempenho.
Como observação lateral, há 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, pois implica que a linguagem é JavaScript. O número da versão
não funciona muito bem e, em retrospecto, é considerado um erro. type="text/javascript"
Este atributo é exigido pelos padrões HTML4 e XHTML1, mas não deveria ser, pois os navegadores
assumem JavaScript de qualquer forma. O HTML5 está tornando este atributo desnecessá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 async 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.
<!doctype html>
<html>
<head>
<title>Meu aplicativo</title> <!--
ANTIPATTERN --> <script
src="[Link]"></script> <script
src="[Link]"></script> <script src="[Link]"></
script> <script src="[Link]"></script> </head> <body>
...
</corpo>
</html>
<!doctype html>
<html>
<cabeçalho>
<title>Meu aplicativo</
title> <script src="all_20100426.js"></script>
</head>
<body>
...
</corpo>
</html>
<!doctype html>
<html>
<head>
<title>Meu aplicativo</title> </
head>
<body>
...
<script src="all_20100426.js"></script> </
corpo>
</html>
Fragmentação
HTTP: O protocolo HTTP suporta a chamada codificação em blocos. Ela permite o envio da
página em partes. Portanto, se você tiver uma página complexa, 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 do bloco #1 --> <body>
...
<script src="all_20100426.js"></script> </body> </html>
<!-- fim
do bloco
#2 -->
Uma melhoria simples seria mover o JavaScript de volta para <head> e servi-lo com o
primeiro bloco. Dessa forma, o navegador tem uma vantagem inicial no download do 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
do bloco
#1 --> <body>
...
</body>
</html>
<!-- fim do bloco #2 -->
Uma opção ainda melhor seria ter um terceiro bloco, que contenha apenas o script no final
da página. Você também pode enviar parte do corpo do texto com o primeiro bloco se tiver
um cabeçalho estático no topo de cada página:
<!doctype html>
<html>
<head>
<title>Meu aplicativo</title> </
head>
<body>
<div id="header">
<img src="[Link]" />
...
</div>
<!-- fim do bloco #1 -->
mencionado, o JavaScript bloqueia o download dos arquivos subsequentes. Mas vários padrões permitem evitar
isso:
• Carregar o script com uma requisição XHR e, em seguida, usar eval() como uma string. Essa abordagem
sofre com restrições de mesmo domínio e também envolve eval(), que é um antipadrão por si só. • Usar
os atributos defer e async ,
mas estes não funcionam em todos os navegadores. • Usar um elemento <script> dinâmico .
O último é um padrão bom e factível. Semelhante ao que você viu com JSONP, você cria um novo elemento de
script, define sua origem 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 outros elementos de script que sigam esse padrão se
eles dependerem do carregamento do .js principal. O .js principal é carregado de forma assíncrona, então não
há garantia de quando ele chegará, e os scripts que vêm depois dele podem assumir objetos que ainda não
foram definidos.
Para resolver essa desvantagem, você pode fazer com que todos os scripts inline não sejam executados
imediatamente, mas sim reunidos como funções em um array. Assim, quando o script principal chega, ele pode
executar todas as funções coletadas no array de buffers. Portanto, há três etapas para conseguir isso.
Primeiro, crie uma matriz para armazenar todo o código embutido, o mais cedo possível na página:
var meunamespace =
{ inline_scripts: []
};
Em seguida, você precisa encapsular todos os scripts inline individuais em uma função e anexar cada
função ao array inline_scripts . Em outras palavras:
// era: //
<script>[Link]("Estou inline");</script>
// torna-se:
<script>
mynamespace.inline_scripts.push(function ()
{ [Link]("Estou inline"); });
</
script>
E o último passo é fazer com que seu script principal faça um loop no buffer de scripts inline e execute
todos eles:
Acrescentando o elemento
<script> Normalmente, os scripts são acrescentados ao <head> do documento, mas você pode
acrescentá-los a qualquer elemento, incluindo o corpo (como no exemplo JSONP).
[Link](script);
Isso é aceitável quando você controla a marcação, mas e se você estiver criando um widget ou
anúncio e não tiver ideia de que tipo de página o hospedará? Tecnicamente, você pode não ter
<head> nem <body> na página; embora [Link] certamente funcione mesmo sem a tag
<body> :
[Link](script);
Mas, na verdade, há uma tag que sempre existirá na página onde seu script é executado: uma tag de
script. Se não houvesse uma tag de script (para um arquivo inline ou externo), seu código não seria
executado. Para usar esse fato, você pode usar insertBefore() para o primeiro elemento de script
disponível na página:
var primeiro_script = [Link]('script')[0];
primeiro_script.[Link](script, primeiro_script);
Aqui, first_script é o elemento de script que certamente estará na página e script é o novo elemento
de script que você cria.
Carregamento
Preguiçoso: A técnica conhecida como carregamento lento refere-se ao carregamento de um arquivo externo após o
evento de carregamento da página . Geralmente, é vantajoso 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. O
restante pode ser carregado em segundo plano enquanto o usuário está engajado e navegando pela 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:
};
</script> </
body> </
html> <!--
fim do bloco #3 -->
Para muitas aplicações, a parte preguiçosa do código geralmente será maior que a parte principal, porque a “ação”
interessante (como arrastar e soltar, XHR e animações) acontece somente depois que o usuário a inicia.
Carregamento sob
Imagine que você tenha uma barra lateral na página com diferentes abas. Clicar em uma aba faz uma solicitação XHR
para obter conteúdo, atualiza o conteúdo da aba e anima a atualização, esmaecendo a cor. E se este for o único lugar
na página em que você precisa do XHR e das bibliotecas de animação, e se o usuário nunca clicar em uma aba?
Entre no padrão de carregamento sob demanda. Você pode criar uma função ou método require() que recebe o 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.
require("[Link]", função ()
{ functionDefinedInExtraJS(); });
Vamos ver como você pode implementar essa função. Solicitar o script adicional é simples — basta seguir o padrão
dinâmico do elemento <script> . Descobrir quando o script é carregado é um pouco mais complicado devido às
diferenças entre navegadores:
// IE
[Link] = function () { if
([Link] === 'carregado' || [Link] === 'completo')
{ [Link] = nulo;
retorno de chamada();
}
};
// outros
[Link] = function ()
{ callback();
};
[Link] = arquivo;
[Link](newjs, script);
}
• No IE, você se inscreve no evento readystatechange e procura por um readyState “carregado” ou “completo”.
Todos os outros navegadores irão ignorar isso. • No Firefox, Safari e Opera,
• Esta abordagem não funciona no Safari 2. Se este navegador for um requisito, para que funcione, você terá que
configurar um intervalo para verificar periodicamente se uma variável especificada (que você define no arquivo
adicional) foi definida. Quando ela for definida, significa que o novo script foi carregado e executado.
Você pode testar essa implementação criando um script artificialmente atrasado (para simular a latência da rede),
chamado [Link], por exemplo:
<?php
header('Tipo de conteúdo: aplicativo/javascript'); sleep(1); ?>
função extraFunction(logthis) {
[Link]('carregado e executado');
[Link](logthis);
}
Este snippet escreverá duas linhas no console e atualizará a página dizendo “concluído!”
Você pode ver o exemplo ao vivo em http:// [Link]/ book/ 7/ [Link].
Pré-carregamento de
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 aumente o tempo
total gasto no pré-carregamento, a execução também pode causar erros de JavaScript quando o script
pré-carregado presume 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 image beacon:
nova Imagem().src = "[Link]";
Em todos os outros navegadores, você pode usar um <object> em vez de um elemento de script e
definir seu atributo de dados para apontar para a URL
do script: var obj = [Link]('object');
[Link] = "[Link]";
[Link](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
init-time (Capítulo 4) para lidar com as diferenças do navegador:
var preload; if
(/*@cc_on!@*/false) { // IE sniffing com comentários condicionais preload =
function (file) { new Image().src
= file;
}; } senão
{ pré-carregamento = função
(arquivo) { var obj = [Link]('object'),
corpo = [Link];
[Link] = 0;
[Link] = 0;
[Link] =
arquivo; [Link](obj);
};
}
pré-carregar('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, neste caso, a capacidade de detecção não nos informa o suficiente sobre o comportamento do navegador.
Neste padrão, por exemplo, teoricamente, você pode testar se typeof Image é uma "função" e usá-la em vez 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:
definirá isIE como falso em todos os navegadores (porque eles ignoram o comentário), mas será
verdadeiro no Internet Explorer, devido à 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ça a digitar seu nome de usuário, você pode usar
esse tempo de digitação para iniciar o pré-carregamento (nada confidencial, é claro), pois é provável que o
usuário acabe na segunda página de login.
Resumo
Enquanto os capítulos anteriores do livro cobriam principalmente padrões básicos do JavaScript, independentes
do ambiente, este se concentrou em padrões aplicáveis apenas no
ambiente de navegador do lado do cliente.
• 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 computações pesadas de longa duração — usando set
Timeout() para dividir operações longas em pedaços menores e usando web workers em
navegadores modernos.
Resumo | 207
Machine Translated by Google [Link]
Machine Translated by Google [Link]
Índice
Gostaríamos de ouvir suas sugestões para melhorar nossos índices. Envie um e-mail para index@[Link].
209
Machine Translated by Google [Link]
210 | Índice
Machine Translated by Google [Link]
Índice | 211
Machine Translated by Google [Link]
comprimento, 16 objetos
de string, 52 bibliotecas
212 | Índice
Machine Translated by Google [Link]
Índice | 213
Machine Translated by Google [Link]
214 | Índice
Machine Translated by Google [Link]
Índice | 215
Machine Translated by Google [Link]
variáveis, 101
(ver também variáveis globais)
declarando, 11
definidas, 10
definindo, 3
elevando, 14, 61
local, 58
convenções de nomenclatura,
28 considerações de escopo,
58 conversão de tipos, 21–23
Trabalhadores da
web W , 190 espaços em branco (convenção de
codificação), 26, 27, 36 curingas, padrão sandbox
e, 102, 105 propriedade de
janela, 10, 13 objetos wrapper, 52
X
Objeto XMLHttpRequest, 191, 192
Z
Zakas, Nicholas, 16
216 | Índice
Machine Translated by Google [Link]
Sobre o autor
Stoyan Stefanov é desenvolvedor web do Yahoo!, autor de livros (JavaScript Orientado a Objetos),
colaborador de livros (Sites Ainda Mais Rápidos, JavaScript de Alto Desempenho) e revisor técnico
(JavaScript: As Partes Boas, Mashups de PHP). Ele palestra regularmente sobre JavaScript, PHP e outros
tópicos de desenvolvimento web em conferências e em seu blog (http:// [Link]). Stoyan é o
criador da ferramenta de otimização de imagens [Link] e arquiteto da ferramenta de otimização de
desempenho do Yahoo, YSlow 2.0.
Colofão: O
animal na capa de JavaScript Patterns é uma perdiz europeia (Perdix per-dix), também chamada de perdiz cinzenta,
perdiz inglesa, perdiz húngara ou perdiz boêmia. Esta ave, de ampla distribuição, é 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 do norte
dos Estados Unidos.
As perdizes são membros da família dos faisões, Phasianidae. São nidificadores terrestres não migratórios
que se alimentam principalmente de grãos e sementes. Originalmente residentes em pastagens, adaptaram-
se e se espalharam com a agricultura humana; atualmente, são mais frequentemente encontradas perto de
campos cultivados.
As perdizes europeias são aves rotundas, semelhantes a galinhas (cerca de 30 centímetros de comprimento),
com pescoço e cauda curtos. Possuem dorso marrom, ventre cinza (com uma mancha castanho-escura na
barriga), focinho cor de ferrugem e bico e patas opacos. Suas ninhadas, compostas de 15 a 20 ovos, estão
entre as maiores de todas as aves. Amplamente introduzidas como aves de caça, as perdizes foram
extensivamente caçadas no final do século XIX e início do século XX.
O nome científico do pássaro vem de Pérdice, da mitologia grega, sobrinho do inventor Dédalo. Dédalo
tinha ciúmes de seu jovem aluno — a quem se atribui a invenção da serra, do cinzel, do compasso
geométrico e da roda de oleiro — e aproveitou a oportunidade para expulsá-lo da Acrópole. Atena,
compadecida com o menino inteligente, veio em seu socorro e o transformou em uma perdiz, uma ave que
evita alturas e prefere fazer ninhos 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 título é Adobe Myriad Condensed; e a fonte do código é
TheSansMonoCondensed, da LucasFont.
Machine Translated by Google [Link]
<[Link]>
eBook
Baixe
Wow!
do