Escolar Documentos
Profissional Documentos
Cultura Documentos
Neste e nos próximos dois capítulos, estamos trabalhando com POO.Em alguns estilos Ja-
vaScript,OOP não é enfatizado em favor da programação funcional. Mas além da razão
prática de entender OOP em JavaScript para dar suporte a projetos legados, os recursos
de OOP do JavaScript ainda estão sendo expandidos por meio do comitê TC39 que decide
sobre recursos para JavaScript.
É razoável ter uma preferência pessoal por sua própria codificação, mas se seu objetivo
é entender o JavaScript moderno, no momento da redação deste artigo, tanto FP quanto
OOP ainda estão sendo desenvolvidos ativamente. Sem um “vencedor” claro neste mo-
mento, faz sentido aprender sobre ambos.
O Código (Aprimorado)
Caso você tenha passado o último capítuloou perdeu um passo em algum lugar,aqui está
a versão que acabamos com:
function fileName(){
return theError.stack.match(/\/(\w+\.js)\:/)[1];
};
console.log(`Welcome to ${fileName()}!`);
paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7',
toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7',
'g7'];
songs.push([label, chords]);
labels.push(label);
chords.forEach(chord => {
if(!allChords.includes(chord)){
allChords.push(chord);
}
});
if(Object.keys(labelCounts).includes(label)){
} else {
labelCounts[label] = 1;
}
};
function setLabelProbabilities(){
Object.keys(labelCounts).forEach(function(label){
});
};
function setChordCountsInLabels(){
songs.forEach(function(song){
}
song[1].forEach(function(chord){
chordCountsInLabels[song[0]][chord] += 1;
} else {
chordCountsInLabels[song[0]][chord] = 1;
}
});
});
function setProbabilityOfChordsInLabels(){
probabilityOfChordsInLabels = chordCountsInLabels;
Object.keys(probabilityOfChordsInLabels).forEach(
function(difficulty){
Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(
function(chord){
});
});
train(imagine, easy);
train(somewhereOverTheRainbow, easy);
train(tooManyCooks, easy);
train(iWillFollowYouIntoTheDark, medium);
train(babyOneMoreTime, medium);
train(creep, medium);
train(paperBag, hard);
train(toxic, hard);
train(bulletproof, hard);
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
function classify(chords){
console.log(labelProbabilities);
Object.keys(labelProbabilities).forEach(function(difficulty){
chords.forEach(function(chord){
probabilityOfChordsInLabels[difficulty][chord];
if(probabilityOfChordInLabel){
}
});
});
console.log(classified);
};
Como foi o caso antes, salve isso em um arquivo chamado nb.js e você pode executá-lo
com este comando:
node nb.js
Não teremos testes para mais algumas seções. Ainda estamos mancando verificando se
nossa saída não mudou. Você deve obter o seguinte:
Bem-vindo ao nb.js!
[fácil: 0,3333333333333333,
médio: 0,3333333333333333,
difícil: 0,3333333333333333 ]
{fácil: 2,023094827160494,
médio: 1,855758613168724,
difícil: 1.855758613168724 }
[fácil: 0.3333333333333333,
médio: 0,3333333333333333,
difícil: 0,3333333333333333 ]
{fácil: 1,3433333333333333,
médio: 1.5060259259259259,
difícil: 1.6884223991769547}
chords.forEach(chord => {
if(!allChords.includes(chord)){
allChords.push(chord);
}
});
neste código:
Ele salva quatro linhas e uma verificação condicional. Ganha-ganha. Faça essa mudança
agora.
C O N J U N TO S E M A PA S N Ã O P O S S U E M TO D A S A S F U N Ç Õ E S D E A R R AY Ú T E I S
Por exemplo, até o momento em que escrevo, nem Set nem Map tem uma map função.Isso seria
bom, mas há algumas razões teóricas que estão fora do escopo aqui (tendo a ver com as leis dos
functors , que discutiremos muito brevemente no Capítulo 11 ) para você ficar preso sem imple-
mentações nativas. Portanto, no que diz respeito às opções convenientes, você fica preso à conver-
são para uma matriz e vice-versa ou ao uso de forEach .
Nosso código já tem um caso como este. Apesar de termos inicializado labelCounts e
labelProbabilities como arrays, nós os usamos como objetos o tempo todo. Para ser
mais específico, devemos alterar estas linhas:
a estes:
A R R AY S N Ã O S Ã O TO TA L M E N T E D I S T I N TO S D E O B J E TO S
Como conseguimos usar uma matriz em vez de um objeto? Infelizmente, o JavaScript permite que
você declare um array e atribua elementos a chaves baseadas em string, como se fosse um objeto.
Temos duas matrizes restantes, songs e labels . Se observarmos como eles são usados,
ambos têm elementos enviados a eles e songs são iterados e têm seu comprimento refe-
renciado. Ambos são matrizes justificadas, mas há dois pontos de interesse aqui. Pri-
meiro, labels é apenas “usado” no sentido de que os elementos são empurrados para
ele, mas não é realmente referenciado de outra forma. É código morto, então essas li-
nhas podem ser removidas:
(Salvar/executar/verificar/confirmar.)
O segundo ponto a ser observado songs é que “arrays” são inseridos nele, mas esses
“arrays” têm apenas dois elementos, nenhum dos quais é do mesmo tipo. Um é um ró-
tulo (uma classificação de dificuldade como “fácil”) e o outro é uma matriz de acordes.
Isso soa como um ajuste melhor para um objeto do que uma matriz. Para ser claro,
songs continua sendo um array, mas as coisas colocadas nele devem ser objetos. Isso
envolve algumas mudanças.
songs.push([label, chords]);
Agora estamos usando um objeto em vez de um array. Isso vai quebrar muitas coisas.
Antes de corrigirmos isso, porém, podemos fazer uma pequena alteração usando a abre-
viação da propriedade object :
songs.push({label, chords});
- chordCountsInLabels[song[0]] = {}
+ chordCountsInLabels[song.label] = {}
}
- song[1].forEach(function(chord){
- chordCountsInLabels[song[0]][chord] += 1;
+ song.chords.forEach(function(chord){
+ chordCountsInLabels[song.label][chord] += 1;
} else {
- chordCountsInLabels[song[0]][chord] = 1;
+ chordCountsInLabels[song.label][chord] = 1;
Nós não usamos isso antes, mas é assim que git diff representa as mudanças.Se você
usa o Git, verá isso com frequência. Cada linha com a - é uma linha a ser excluída e
substituída pela seguinte linha prefixada com um + . Linhas sem mais ou menos são for-
necidas apenas para contexto.
Nesse ponto, você poderá salvar, executar o código e verificar se a saída não foi alterada.
Agora parece que todos os nossos arrays são arrays apropriados, em vez de conjuntos ou
objetos disfarçados.Mas e esses objetos? Nós realmente queremos objetos?
Se você ainda não ouviu falar Map (do objeto, não da função), você pode estar se pergun-
tando qual seria a alternativa para um objeto. Mas depois de ler a última frase, você
pode estar adivinhando que é um mapa.
Por que você usaria um mapa sobreum objeto? (Vamos nos referir a ambos como
“contêineres”.)
Em termos práticos, isso significa que se a maioria de suas interações com objetos con-
sistem em fazer um loop através deles, e eles tendem a armazenar valores do mesmo
tipo (ou pelo menos valores que podem ser usados da mesma maneira—por exemplo,
endereçados com funções semelhantes ), você provavelmente quer um mapa.
O que tudo isso significa para nós e nossos objetos dentro de nossa NBC?
C O N T R A P O N TO : O S M A PA S S Ã O T E R R Í V E I S . . . M A S S Ó P O R A G O R A ?
Enquanto os dados do nosso programa sugerem fortemente que devemos usar mapas em vez de
objetos, hátambém são razões pelas quais podemos querer evitá-los:
Por esses motivos (especialmente o primeiro), acabaremos deixando alguns objetos internos como
estão, mas ainda usaremos mapas para os contêineres externos.
- classified[difficulty] = first;
+ classified.set(difficulty, first);
Quando salvamos e executamos isso, a saída parece um pouco diferente, mas os núme-
ros são todos iguais. Comprometer-se.
Em seguida, vamos ver o que seria necessário para converter labelCounts de um ob-
jeto para um mapa:
- if(Object.keys(labelCounts).includes(label)){
+ if(Array.from(labelCounts.keys()).includes(label)){
- labelCounts[label] = 1;
- Object.keys(labelCounts).forEach(function(label){
+ labelCounts.forEach(function(_count, label){
Lembre-se de nossa git diff notação, onde + é uma linha adicionada e uma linha -
excluída.
Para obter o array de rótulos com nosso objeto antigo, usamos a Object.keys função,
que tem o labelCounts objeto como parâmetro. Para obter a mesma matriz do nosso
mapa,temos que primeiro obter um iterador com labelCounts.keys() . Ao contrário
de um array, este objeto iterador não tem uma includes função, então convertemos de
um iterador para um array por meio da Array.from função.
O label é a chave do nosso mapa, e o valor é o _count . O sublinhado está aí para sig-
nificar isso, embora devamos incluir algo no primeiro ponto da lista de parâmetros para
rotular e acessar o segundo; o primeiro não é usado. Alguns usariam apenas um _ , mas
não há uma boa razão para não nomear a variável como algo útil. Alguém mais tarde
usando isso precisa procurar a definição da função para perceber o que o primeiro parâ-
metro significa ou usar console.log para encontrar seu valor? Além disso, se o pri-
meiro parâmetro for útil, é conveniente apenas excluir o sublinhado que precede um
nome de variável perfeitamente útil e descritivo.
POR QUE ELES FIZERAM ISSO?
A ordenação doparâmetros na forEach função de Map está para trás em que as pessoasusual-
mentedescreva hashes/dicionários como “pares chave/valor”, e aqui o valor é listado primeiro.
Poderia ser melhor se fosse (key, value) , em vez de (value, key) , mas suponho que a supo-
sição é que as pessoas estarão mais interessadas em estritamente o value , o que significa que ten-
deriam a chamá-lo com um parâmetro: (value) .
- Object.keys(labelProbabilities).forEach(function(difficulty){
Neste ponto, você ainda deve ter um código que seja executado e forneça os números
esperados.
Podemos aplicar a mesma abordagem de antes, embora seja mais meticuloso desta vez.
Vamos precisar das seguintes alterações:
-var chordCountsInLabels = {};
- chordCountsInLabels[song.label] = {};
+ chordCountsInLabels.set(song.label, {});
- chordCountsInLabels[song.label][chord] += 1;
+ chordCountsInLabels.get(song.label)[chord] += 1;
-chordCountsInLabels[song.label][chord] = 1;
+chordCountsInLabels.get(song.label)[chord] = 1;
- Object.keys(probabilityOfChordsInLabels).forEach(
-function(difficulty){
- Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(
+probabilityOfChordsInLabels.forEach(function(_chords, difficulty){
+ Object.keys(probabilityOfChordsInLabels.get(difficulty)).forEach(
+ probabilityOfChordsInLabels.get(difficulty)[chord] /= songs.length;
-var probabilityOfChordInLabel =
-probabilityOfChordsInLabels[difficulty][chord];
+var probabilityOfChordInLabel =
+probabilityOfChordsInLabels.get(difficulty)[chord];
Salvar/executar/verificar/commit.
VERSÕES “FRACAS” DE SET E MAP
Antes de deixarmos nossa discussão sobre conjuntos e mapas, vocêdeve notarque tam-
bém existem WeakSet e WeakMap . As principais diferenças entre eles e suas contrapar-
tes “fortes” (força normal?) são:
Basicamente, com as formas fracas, você abre mão da capacidade de saber facilmente o
que está dentro ou de aplicar funções a todo o conjunto. E o que você ganha é o controle
sobre vazamentos de memória e privacidade.
Mais um candidato para substituir arrays merece uma menção: campos de bits.Se você
tiver uma matriz que armazena booleanos, talvez queira campos de bits em alguns ca-
sos. Não há implementação nativa de campos de bits em JavaScript. No entanto, você
tem acesso a números e aritmética bit a bit, e isso é tudo que você precisa.
states = [true,
true,
true,
true,
true,
true,
false,
true]
Se você tivesse uma condicional que fosse válida apenas nessas condições, você poderia
fazer algo assim:
// something something
Como esses estados têm pouco significado por si mesmos, uma refatoração em potencial
seria mover essas condições para uma função:
if(stateIsOk()){
// something something
...
stateIsOk = function(state){
return state[0] && state[1] && state[2] && state[3] && state[4] &&
Mas se você armazenou seu estado em um campo de bits, você pode fazer isso:
if(state===0b11111101){
// something something
if(stateIsOk()){
...
stateIsOk = function(state){
return state===0b11111101;
Isso tem mais potencial para otimização de desempenho do que refatoração, porque a
aritmética bit a bit é super rápida, mas é um pouco difícil de trabalhar em muitos aplica-
tivos. Se você estiver fazendo algo que é graficamente intensivo e/ou precisa ser rápido
(como um jogo), lembre-se dessa representação tipo array.
Extrair esta função é bom porque podemos descrever facilmente (através do nome da
função) o que 0b11111101 realmente significa. Falando em extrair funções, vamos fa-
zer um monte disso na próxima seção.
Por outro lado, há um caso para ficar com testes de alto nível (mesmo manuais como o
nosso) até que seu código comece a tomar um pouco mais de forma.Muitos testes que co-
brem refatorações de baixo nível podem fornecer trabalho extra sem confiança extra.
Por exemplo, se você sabe que tem código morto, deseja testá-lo antes de excluí-lo? Se
você tem uma função que sabe que deseja renomear, deseja realmente testá-la antes?
MAIS QUATRO FILOSOFIAS DE TESTE
É uma boa abordagem para se sentir confortável e tende a combinar bem com a maioria
das equipes. No entanto, existem algumas outras filosofias de teste que valem a pena
considerar:
Testes de unidade são uma perda de tempo (apenas testes/métricas de alto nível
importam)
Eu nunca ouvi isso de um engenheiro competente que atualmente está traba-
lhando em tarefas de codificação, em vez de um gerente ou consultor focado mais
em metas de alto nível.
Neste capítulo, nossas mudanças são muito mais agressivas. Para abordar essas mudan-
ças com confiança, precisaremos de mais testes. Antes de fazermos mais alterações, va-
mos testar o que temos agora.
Caso não seja óbvio o que queremos testar, a premissa é que queremos converter nosso
fluxo de trabalho manual em automatizado. Isso significa que qualquer coisa que esteja-
mos procurando como saída deve estar dentro de um teste.
Se você olhar para o código de uma certa maneira, poderá ver como ele quer ser melhor. Arquivos
longos querem ser mais curtos. Comentários explicando algumas linhas de código querem ser fun-
ções, nomeadas com o comentário, que encerram essas poucas linhas. Como se aplica a nós agora,
essas instruções de registro querem ser funções.
it('works', function(){
wish(true);
});
});
Observe que você também pode precisar executar esses comandos na linha de comando:
Agora, se você correr mocha nb.js , você deveveja um teste de aprovação, bem como
nossas informações de registro.
N Ã O S E E S Q U E Ç A D O O B S E RVA D O R
Não se esqueça de usar mocha -w nb.js para executar o watcher em um terminaljanela para
atualizar os resultados quando você fizer alterações.
Testes de caracterização para classificar
Em vez de usar console.log no final das funções, podemos retornar algo delas que po-
demos testar.Adicione isso dentro do describe bloco (a classify linha é a mesma de
antes):
it('classifies', function(){
});
Em seguida, no final da classify função, faça com que ela retorne além do log:
function classify(chords){
...
});
console.log(classified);
};
Voltando ao teste: sabemos que, por falta de cobertura, queremos escrever um teste de
caracterização. Vamos usar o modo de caracterização de desejo fornecendo um segundo
parâmetro de true , e deixar a saída de teste nos dizer qual é a saída.
it('classifies', function(){
wish(classified.get('easy'), true);
wish(classified.get('medium'), true);
wish(classified.get('hard'), true);
});
WishCharacterization: classified.get('easy')
evaluated to 1.3433333333333333
wish(classified.get('easy'), true);
// with this
WishCharacterization: classified.get('medium')
evaluated to 1.5060259259259259
WishCharacterization: classified.get('hard')
evaluated to 1.6884223991769547
it('classifies', function(){
});
Seguindo esse mesmo processo novamente, podemos escrever um teste semelhante para
a outra música que estamos classificando:
});
Testando a WelcomeMessage
console.log(`Welcome to ${fileName()}!`);
console.log(`Welcome to ${fileName()}!`);
});
Neste momento, isso passa, pois não há nenhuma afirmação feita. Mas ainda vemos a
saída da instrução de log, então sabemos que o código ainda funciona. Vamos adicionar
a afirmação agora:
console.log(`Welcome to ${fileName()}!`);
});
Observe que pretendemos adicionar uma função aqui. Este não é um teste de caracteri-
zação. Este é um teste de unidade para uma função que ainda não foi escrita.
Recebemos um erro:
function welcomeMessage(){
};
O teste agora passa. Isso significa que não precisamos mais confiar na declaração de re-
gistro de mensagens de boas-vindas em nenhum lugar, inclusive em nosso teste, para
que possamos ter apenas o seguinte:
});
console.log(labelProbabilities);
console.log(labelProbabilities);
});
wish(labelProbabilities, true);
});
Quando executamos o teste, nossa saída nos diz o que precisamos fazer a seguir:
WishCharacterization: labelProbabilities
evaluated to [["easy",0.3333333333333333],
["medium",0.3333333333333333],
["hard",0.3333333333333333]]
});
Agora que temos testes de alto nível, podemos refatorar agressivamente e com confi-
ança. Além disso, estamos completamente livres de console.log declarações.
Extraindo Funções
Aqui vamos nós!Esta seção é sobre o que, como dissemos no capítulo anterior, é prova-
velmente a técnica de refatoração mais útil e subutilizada: extrair funções.
Se examinarmos o código neste ponto, podemos notar que temos dados (músicas e rótu-
los) misturados com nossas funções.No geral, nosso programa parece ter estas etapas:
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
function setLabelsAndProbabilities(){
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
};
setLabelsAndProbabilities();
Observe que também podemos executar isso como umIIFE anônimo (expressão de fun-
ção imediatamente invocada; consulte “Chamadas de função e literais de função” ),
assim:
(function(){
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
})();
Mas ao fazer isso, perdemos algum controle. Se alguma vez decidíssemos chamar isso
mais de uma vez, teríamos que nomeá-lo ou encontrar uma maneira de executar o có-
digo que o contém a cada vez.
Por enquanto, não importa quando essa função é chamada, desde que seja chamada
apenas uma vez: depois que o classificador for treinado, mas antes de executarmos
classify .
function trainAll(){
train(imagine, easy);
train(somewhereOverTheRainbow, easy);
train(tooManyCooks, easy);
train(iWillFollowYouIntoTheDark, medium);
train(babyOneMoreTime, medium);
train(creep, medium);
train(paperBag, hard);
train(toxic, hard);
train(bulletproof, hard);
setLabelsAndProbabilities();
};
trainAll();
function setLabelsAndProbabilities(){
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
};
Queremos trainAll fazer partenossa interface pública (uma função que executamos,
em vez de parte de um arquivo que executamos) como qualquer outra instrução, por-
tanto, devemos movê-la para ser chamada dentro do describe bloco de nossos testes:
trainAll();
Em seguida, podemos extrair uma função para definir nossas músicas e (por enquanto)
chamá-la imediatamente:
function setSongs(){
'b'];
'gmaj7', 'g7'];
};
setSongs();
Sabemos que queremos que issoacontecer uma vez antestraining, para que possamos
mover nossa setSongs chamada de função (a última linha do snippet anterior) para
dentro de trainAll :
function trainAll(){
setSongs();
...
Como sempre, após cada uma dessas alterações, você deve observar o conjunto de testes
aprovado e confirmar os resultados.
Queremos fazer o mesmo com a configuração de variáveis de dificuldade e nossos con-
têineres (array, conjuntos e mapas) que usamos em todo o programa, mas se adotarmos
a mesma abordagem de extrair uma função, nosso conjunto de testes apresentará um
erro. Tente isto:
function setDifficulties(){
};
setDifficulties();
Se fizermos isso, nossas variáveis ficarão presas no escopo dessa função e não poderão
mais ser lidas por outras funções. Mais tarde, abordaremos como restringir os escopos
dessas variáveis, mas, por enquanto,podemos pegar o caminho mais fácil e deixá-los
como variáveis globaisomitindo a var palavra-chave:
function setDifficulties(){
easy = 'easy';
medium = 'medium';
hard = 'hard';
};
setDifficulties();
E podemos fazer o mesmo para nossas outras variáveis globais (movê-las para uma fun-
ção, remover a declaração de escopo e chamar a função):
function setup(){
songs = [];
};
setup();
function trainAll(){
setDifficulties();
setup();
setSongs();
...
E nós nem tínhamos var declarações para nossas músicas para começar! Código ruim!
Certo?
Falaremos sobre var e outras declarações de escopo mais tarde.Por enquanto, reco-
nheça que nosso programa dependia delas como variáveis globais antes. Não estamos
mais usando a var palavra-chave para fingir que não são globais.
Uma maneira de corrigir isso é definir o escopo e passá-los especificamente para cada
função que os exija. Feito ingenuamente, esse caminho exigiria muitas mudanças, in-
cluindo assinaturas de métodos mais complexas (mais parâmetros) e mais valores de
retorno.
Em algum momento, vamos querer anexar essas variáveis a algum objeto diferente do
global, mas, por enquanto, é uma vitória ter a interface bem definida pelos testes: eles
apenas executam trainAll e classify , o que significa que estamos mais preocupa-
dos com o conteúdo do arquivo em oposição ao pedido .
Excelente. Então agora tudo está dentro de uma função e nossa interface para os testes
está bem definida. Poderíamos extrair mais funções se quiséssemos ser mais específicos
em alguns lugares. Por exemplo, poderíamos mudar nossa setup função para fazer algo
assim (mas não faça isso):
function setSongsVariable(){
songs = [];
};
function setup(){
setSongsVariable();
};
E poderíamos fazer isso com o resto das linhas setup também. Ou podemos usá-lo para
agrupar arbitrariamente algumas linhas dentro (não faça isso também):
function setSome(){
songs = [];
};
function setOthers(){
};
function setup(){
setSome();
setOthers();
};
Podemos adicionar o máximo de indireção que quisermos extraindo funções e usar essa
técnica para agrupar coisas onde fizer sentido. Mas essas mudanças não ajudam nosso
código a ficar mais claro. Estamos apenas agrupando linhas arbitrárias neste momento.
O oposto de extrair funções é inlinhá -las.Quando uma função extraída não faz nada, é
sensato colocá-la embutida.Se você extraiu essas funções, insira-as agora e restaure a
setup função como era:
function setup(){
songs = [];
};
Se você está se perguntando qual era o objetivo disso, é que extrair e inserir funções de-
vem vir tão naturalmente para você quanto introduzir e inserir variáveis.
Além de extrair funções que ajudam a agrupar o comportamento,também é útil para no-
mear funções anônimas. Temos algumas funções anônimas em nosso programa. Para
obter exemplos, observe qualquer código que segue um forEach , ou o segundo parâ-
metro para as chamadas de função describe e no teste. it
$('.my-button').on('click', function(){
window.location = "http://refactoringjs.com";
});
$('.other-button').on('click', function(){
window.location = "http://refactoringjs.com";
});
$('.my-button').on('click', function(){
window.location = siteUrl;
});
$('.other-button').on('click', function(){
window.location = siteUrl;
});
Isso facilita a mudança siteUrl no futuro (supondo que seja usado em outros lugares),
mas podemos reduzir ainda mais a duplicação extraindo uma função:
var siteUrl = "http://refactoringjs.com";
function visitSite(){
window.location = siteUrl;
$('.my-button').on('click', function(){
visitSite();
});
$('.other-button').on('click', function(){
visitSite();
});
Agora nossa implementação será mais fácil de mudar no futuro, mas de que adianta en-
volver nossa chamada de função em uma função? Nada! Tente isso em vez disso:
function visitSite(){
window.location = siteUrl;
$('.my-button').on('click', visitSite);
$('.other-button').on('click', visitSite);
function(){};
function visitSite(){};
visitSite();
A única maneira de chamar uma função anônima (a primeira, quando não está atri-
buída a uma variável como no terceiro snippet) é executando o código que a contém:
(function(){})();
// or
(function(){}());
A confusão surge quando as pessoas não percebem que uma declaração de função anô-
nima, quandosubstituído por uma função nomeada para reutilização, pode ser usado da
mesma maneiracomo uma referência de função :
$('.my-button').on('click', visitSite);
$('.my-button').on('click', visitSite());
Você deve passar uma referência à visitSite função na on função, não uma chamada
de função.
Uma confusão adicional surge quando um parâmetro é usado pela função que está
sendo referenciada. Se nossa função ficou assim:
function visitSite(siteUrl){
window.location = siteUrl;
};
Mas isso não vai funcionar. Nesse caso, a solução mais óbvia seria usar uma função anô-
nima para envolver a chamada como antes:
$('.my-button').on('click', function(){
visitSite("http://refactoringjs.com");
});
Outra solução, que se aplica não apenas à on função do jQuery, mas também às funções
nativas do JavaScript, como forEach e map , é passar outro argumento junto com os ou-
tros. Em muitos casos, o que você passar vai acabar sendo o parâmetro “implícito” (o
this ) da funçãoque você está chamando, mas se você estiver definindo uma função
que recebe uma função como argumento, você também pode projetar a assinatura da
função para receber argumentos que se tornarão argumentos explícitos para a função
passada.
De qualquer forma, quando você está passando funções dentro de funções, você deve ter
duas coisas em mente. Primeiro, consulte a API da função que recebe uma função como
argumento para quaisquer argumentos opcionais que possam ser passados e saiba o que
acontece quando eles não são. Segundo, reconheça que as chamadas de função JavaS-
cript funcionarão bem com muitos ou poucos argumentos. Você receberá um erro dentro
da função se tentar chamá-la e usar um parâmetro que não foi definido pela assinatura.
Além disso, quaisquer parâmetros não atendidospela funçãochamada será definida
como undefined .
Neste ponto, temos tudo embrulhado em funções, mas ainda temos muitas variáveis glo-
bais flutuando (isso inclui nossas funções, bem como variáveis definidas sem
var ).Nesta seção, temos dois objetivos. A primeira é reduzir o escopo de todas as variá-
veis globais que temos. A segunda é projetar nossa API – em outras palavras, tomar deci-
sões sobre como nosso código será usado.
Para o estilo de código que estamos explorando aqui, queremos um objeto principal, que
chamaremos classifier , e ele conterá todas as funções e variáveis incidentais criadas
ao longo do caminho. Exploraremos sua criação por meio de vários mecanismos (função
de fábrica, função construtora e classe) e, em todos os casos, trabalharemos com ela de
maneira OOP. Isso significa que estaremos usando construções de linguagem como
this e permitindo que nosso classifier objeto mude durante a execução de nosso
programa.
Se você preferir trabalhar em direção a uma abordagem funcional, em vez de uma OOP,
parte do que se segue não seguirá o caminho que você deseja seguir. Para esse caminho,
eu recomendaria ainda ler o restante deste capítulo e depois examinar o Capítulo 11 .
Mas o tempo que gastamos “ this -ficando” para o estilo OOP aqui precisará de trabalho
extra para converter para o estilo FP.A razão é que a OOP nos levará a ter um objeto mu-
tável principal (embora possa ser razoavelmente dividido em objetos menores), en-
quanto em FP, queremos tratar nosso objeto delimitador ( classifier ) como mais um
namespace estúpido para funções.
Uma versão funcional do nosso NBC é fornecida no Capítulo 11 . Se você estiver interes-
sado em chegar a esse ponto, tente começar pelo código que temos aqui, bem como pelo
código que você tem no final deste capítulo. Suspeito que você encontrará código divi-
dido em funções (mas não tão fortemente investido em POO quanto o código no final
deste capítulo), como temos neste ponto, mais fácil de trabalhar. Por todos os meios,
tente ambas as abordagens se isso lhe interessar.
function setDifficulties(){
easy = 'easy';
medium = 'medium';
hard = 'hard';
};
function setSongs(){
'b'];
};
function setup(){
songs = [];
};
songs.push({label, chords});
if(Array.from(labelCounts.keys()).includes(label)){
} else {
}
};
function setLabelProbabilities(){
labelCounts.forEach(function(_count, label){
labelProbabilities.set(label,
labelCounts.get(label) / songs.length);
});
};
function setChordCountsInLabels(){
songs.forEach(function(song){
}
song.chords.forEach(function(chord){
chordCountsInLabels.get(song.label)[chord] += 1;
} else {
chordCountsInLabels.get(song.label)[chord] = 1;
}
});
});
function setProbabilityOfChordsInLabels(){
probabilityOfChordsInLabels = chordCountsInLabels;
probabilityOfChordsInLabels.forEach(function(_chords, difficulty){
Object.keys(probabilityOfChordsInLabels.get(difficulty)).forEach(
function(chord){
probabilityOfChordsInLabels.get(difficulty)[chord]
/= songs.length;
});
});
function trainAll(){
setDifficulties();
setup();
setSongs();
train(imagine, easy);
train(somewhereOverTheRainbow, easy);
train(tooManyCooks, easy);
train(iWillFollowYouIntoTheDark, medium);
train(babyOneMoreTime, medium);
train(creep, medium);
train(paperBag, hard);
train(toxic, hard);
train(bulletproof, hard);
setLabelsAndProbabilities();
};
function setLabelsAndProbabilities(){
setLabelProbabilities();
setChordCountsInLabels();
setProbabilityOfChordsInLabels();
};
function classify(chords){
chords.forEach(function(chord){
probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}
});
});
return classified;
};
trainAll();
it('classifies', function(){
});
});
});
});
Então, dentro de trainAll , em vez de setup ser executado como uma função do ob-
jeto global, chame-o como parte disso classifier :
function trainAll(){
setDifficulties();
...
Agora precisamos mover nossa setup função para nosso classifier objeto,no topo do
arquivo. Você também pode excluir a setup função antiga:
var classifier = {
setup: function(){
};
};
Queremos que essas variáveis façam parte de classifier , então precisamos adicionar
um this. na frente de cada uma delas.Observe que setup é uma propriedade com o
rótulo ( setup ) seguido por um literal de função que contém o corpo da função.
Seria ótimo se os testes passassem neste ponto, mas eles não passam. Felizmente, a exe-
cução dos testes relatará um erro útil:
E aqui temos algumas mudanças muito chatas e repetitivas, mas relativamente fáceis de
fazer. Precisamos adicionar a classifier. na frente de cada referência às seguintes
variáveis (que não começam com this. ): songs , allChords , labelCounts ,
labelProbabilities , chordCountsInLabels e probabilityOfChordsInLabels .
Faça essas alterações agora.
Agora que olhamos para nossa setup função anexada a classifier , no entanto, po-
demos notar que ela não está fazendo muito trabalho.Podemos atribuir essas variáveis
diretamente ao objeto sem usar uma função de encapsulamento:
var classifier = {
songs: [],
};
this.songs = [];
songs: [],
A vírgula versus ponto e vírgula é fácil de perder. E também podemos excluir a chamada
para setup in trainAll :
function trainAll(){
...
Isso é ótimo, não apenas porque temos uma linha a menos, mas também porque nosso
código de treinamento realmente não tem nada a ver com a configuração geral do nosso
classifier .
Em seguida, vamos considerar como estamos adicionando músicas para serem treina-
das. setSongs define variáveis globais para cada nome de música e, em seguida,
cadadesses é referenciado na trainAll função. Esse acoplamento é propenso a erros e
não é realmente escalável. Claramente, se alterarmos nossa lista de músicas, teremos
que fazê-lo em dois lugares. Mas pior do que isso, não estamos preparados para um caso
provável em que teríamos um banco de dados de músicas para treinar, em vez de um
punhado que codificamos em um arquivo .
Vamos fazer um songList objeto que contenha as músicas em um array e tenha uma
função para adicionar músicas a ele. Isso pode ir no topo do seu arquivo:
var songList = {
songs: [],
}
};
Agora vamos mudar nossa setSongs função para fazer uso deste songList objeto:
function setSongs(){
songList.addSong('imagine',
songList.addSong('somewhereOverTheRainbow',
songList.addSong('iWillFollowYouIntoTheDark',
songList.addSong('babyOneMoreTime',
songList.addSong('creep',
songList.addSong('paperBag',
'b'], hard);
songList.addSong('toxic',
songList.addSong('bulletproof',
};
Se executarmos os testes agora, obteremos erros porque nossas variáveis nomeadas pe-
los nomes das músicas não estão mais disponíveis.Precisamos alterar nossa
trainAll função para adicionar as músicas do songList :
function trainAll(){
setDifficulties();
setSongs();
songList.songs.forEach(function(song){
});
setLabelsAndProbabilities();
};
Como primeiro esforço, uma vez que eles são apenas referenciados dentro de
setSongs , podemos simplesmente inline as variáveis lá:
function setSongs(){
...
E agora podemos excluir a função setDifficulties e nossa chamada para ela dentro
de trainAll . Os testes devem estar passando neste momento.
Depois disso, pode parecer um desperdício complicar nossa setSongs função com essas
variáveis. E se nós apenas usássemos um array para difficulties , e deixássemos
songList lidar com as strings?
var songList = {
songs: [],
}
};
Aqui, criamos um difficulties atributo songList como um array com os rótulos de-
sejados. Então, quando adicionamos uma música, esperamos que o índice do array apa-
reça e atribua conforme necessário com this.difficulties[difficulty] .
Isso significa que podemos nos livrar do ruído da descrição do rótulo setSongs e usar
números em vez das variáveis que estávamos usando antes. Observe os números como o
terceiro parâmetro das addSong chamadas de função:
function setSongs(){
songList.addSong('imagine',
songList.addSong('somewhereOverTheRainbow',
songList.addSong('iWillFollowYouIntoTheDark',
songList.addSong('babyOneMoreTime',
songList.addSong('creep',
songList.addSong('paperBag',
'b'], 2);
songList.addSong('toxic',
['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2);
songList.addSong('bulletproof',
};
Salvar/testar/comprometer.
Vamos pensar sobre essa trainAll função por um minuto.O que a configuração de mú-
sicas tem a ver com o treinamento do nosso classificador? Absolutamente nada, real-
mente.E se movermos a chamada para setSongs em nosso teste?
trainAll();
E enquanto estamos nisso, definir as músicas tem alguma coisa a ver com a estrutura do
nosso programa? Não. São apenas dados que estamos configurando e usando. Isso signi-
fica que faz parte da execução de uma possibilidade do nosso programa, não do pro-
grama em si. Isso implica que a função em setSongs si pertence aos testes. Vamos em-
butir o corpo da função nos testes e remover completamente a função do programa:
songList.addSong('imagine',
songList.addSong('somewhereOverTheRainbow',
...
songList.addSong('bulletproof',
trainAll();
...
Agora estamos livres para executar o programa independentemente dos dados forneci-
dos. Este é um grande passo e abre nosso programa para mais testes e colocá-lo em pro-
dução como um módulo.
Temos nove instâncias de var em nosso programa agora. Devemos mudar algum deles?
Em caso afirmativo, eles devem mudar para let ou const ?
C O N S T N Ã O S I G N I F I C A I M U TA B I L I D A D E !
Se você está pensando que const fornece um caminho fácil para a imutabilidade, lamento infor-
mar algumas notícias tristes.Isso impede que uma variável seja reatribuída (ou seja, por meio do
= operador), mas o conteúdo da variável ainda pode ser alterado. Os índices de matriz podem ser
atualizados. O mesmo vale para atributos de objetos e membros de conjuntos e mapas.
Object.freeze para o resgate!?Quase. Se você congelar um objeto com mais de um nível de pro-
fundidade, ainda poderá atualizar essas propriedades internas. Além disso, mesmo se você conge-
lar um objeto, se ele foi declarado com var ou let , você ainda pode reatribuí-lo.
Para garantir a imutabilidade, você precisará ir além e pode achar mais fácil usar uma solução em
pacote como Immutable.js ou mori, que fornece versões imutáveis de matrizes, mapas, conjuntos e
assim por diante. Mesmo se você não estiver usando nada que o imponha, é melhor tentar criar
novas variáveis em vez de reaproveitá-las.
Em geral, é preferível usar const . Quanto menos suas variáveis forem reatribuídas,
melhor.
Uma vez que temos testes em vigor, podemos fazer a suposição ousada de que todas as
nossas var declarações deveriam ser const em vez disso. Pesquise e substitua-os e exe-
cute os testes. Você receberá um erro:
A descrição do erro “variável constante” vale a pena notar como um estranho paradoxo,
mas vamos seguir em frente. Sem surpresa, nossa suposição ousada está errada, mas
apenas em um caso. Nossa first variável dentro da classify função requer reatri-
buição por enquanto, então a linha a seguir deve ser modificada para usar let :
smoothing;
Deveria ser:
smoothing;
Todas as outras const declarações estão corretas, e estamos livres para seguir em
frente.
Com algumas alterações, a variável first não precisa ser reatribuída, no entanto. Inici-
almente, destina-se a refletir a probabilidade de uma dificuldade aparecer em relação a
outras dificuldades.Mais tarde, é multiplicado pela probabilidade de um acorde existir
em uma música de determinada dificuldade.
function classify(chords){
classifier.labelProbabilities.forEach(
function(_probabilities, difficulty){
+ smoothing];
chords.forEach(function(chord){
classifier.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}
})
});
});
return classified;
};
Mencionamos, mas não nos aprofundamos, a reduce função. Ele nos permite percorrer
um array e aplicar alguma função a ele, enquanto trabalhamos com um valor retornado,
junto com cada elemento.
Mas espere! Já temos um array pelo qual estamos percorrendo ( chords ). Por que cria-
ríamos um novo para percorrer? Nós realmente precisamos fazer um loop duas vezes
(uma vez para acumular e uma vez para multiplicar)? Vamos tentar usar reduce isso
em forEach vez disso:
function classify(chords){
classifier.labelProbabilities.forEach(
function(_probabilities, difficulty){
// reduce starts
classifier.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}else{
}
// reduce ends
});
return classified;
};
Isso é um pouco mais complexo, por dois motivos. Primeiro, nossa probabilidade inicial
está agora no final, logo acima desta linha: //reduce ends . Isso fornece o “valor ini-
cial” ao reduce total. Em segundo lugar, o else ramo da nossa if declaração está de
volta. A razão é que em cada passo da reduce função, se nada for retornado (o que seria
o caso se a if condição fosse false e não tivéssemos else ramificação), então
undefined seria retornado. Retornar o total tem o mesmo efeito que apenas passar
para o próximo elemento.
Se isso é confuso, pense dessa maneira. Digamos que você tenha uma reduce função
como esta:
Esta função simplesmente retornaria 10 . Se nenhum valor for fornecido como segundo
parâmetro (se o , 10 não estiver lá), ele retornará o primeiro elemento: 2 . Isso ocorre
porque result é realimentado na função para cada elemento. Compare isso com outro
uso simples de reduce , que soma os valores de uma matriz:
Isso vai voltar 9 . Se tivesse um elemento inicial de 10 , adicionaria isso também, retor-
nando 19 .
if(probabilityOfChordInLabel){
return total;
De qualquer forma, você verá frequentemente esse estilo usado paratratamento de erros
assim:
if(error){
Agora vamos escolher a classify função um pouco mais. Em primeiro lugar, não deve-
mos ter problemas em vinculá-lo mais de perto ao classifier objeto. Se há uma coisa
óbvia que um classificador faz, é classificar.
const classifier = {
songs: [],
classify: function(chords){
classifier.labelProbabilities.forEach(
function(_probabilities, difficulty){
classifier.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}else{
}
});
}
};
Nossos testes serão interrompidos neste ponto, porque precisamos substituir instâncias
de classify com classifier.classify nos testes. Altere estas linhas:
...
a estes:
'd', 'f#m']);
...
Agora que nossos testes estão funcionando novamente (save/test/commit), ainda temos
algum trabalho a fazer com esta função. Ele se refere desajeitadamente a si mesmo na
terceira pessoa, em certo sentido. Ou seja, em vez de usar this , ele diz seu próprio
nome: classifier .Há três instâncias disso. Vamos alterá-los, tornando a função o
seguinte:
classify: function(chords){
this.labelProbabilities.forEach(
function(_probabilities, difficulty){
this.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}else{
}
});
return classified;
const probabilityOfChordInLabel =
this.probabilityOfChordsInLabels.get(difficulty)[chord]
// and
}, this.labelProbabilities.get(difficulty) + smoothing)
Isso ocorre porque nosso this (parâmetro implícito) é definido no contexto da função
que contém essas instruções. A correção mais comum e oafish é esta:
classify: function(chords){
this.labelProbabilities.forEach(
function(_probabilities, difficulty){
self.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}else{
}
});
return classified;
Definindouma self variável (no Capítulo 5 , nósusado that em vez disso), você pode
garantir que tenha acesso ao objeto desejado, mesmo dentro das funções internas. No
Capítulo 5 , aprendemossobre usar call , apply , ou bind para realizar a mesma coisa
com um pouco mais de elegância. Nesse caso, como não estamos chamando essas fun-
ções anônimas diretamente, apenas declarando que são chamadas por forEach and
reduce , não podemos usar call ou apply para definir o argumento implícito, this .
Para este caso, devemos usar bind ou encontrar outra maneira.
Quanto a forEach , podemos confiarna capacidade dessa função de aceitar a
thisArg como parâmetro. Isso nos permite definir o que queremos this que seja na
função anônima:
this.labelProbabilities.forEach(
function(_probabilities, difficulty){
self.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
}, this);
Agora que adicionamos this como um segundo parâmetro forEach (na última linha),
estamos livres para usar this em vez self da terceira da última linha. Mas a chamada
restante para self ainda não pode ser alterada para this , porque seu valor é encapsu-
lado pela função anônima dentro de reduce .
...
Agora nossa função pode estar livre da variável classify estranha : self
classify: function(chords){
this.labelProbabilities.forEach(
function(_probabilities, difficulty){
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
}.bind(this),
this.labelProbabilities.get(difficulty) + smoothing);
}, this);
return classified;
Mas antes de deixarmos este exemplo, há outra ferramenta que podemos usar para lidar
com a passagem this : funções de seta.Através deles, nossa função pode ser simplifi-
cada assim:
classify: function(chords){
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
});
return classified;
Agora podemos nos livrar de nosso this como segundo parâmetro para forEach e
nosso bind(this) in reduce . As funções de seta estão nas seguintes linhas:
...
As partes com => são o que as tornam funções de seta . A sintaxe pode parecer um
pouco estranha (e tem muita variação), mas o que é ótimo é a this passagem da função
externa.
Para simplificar um pouco mais esta função, smoothing pode ser retiradoda função e
adicionado como um novo atributo do classifier :
smoothing: 1.01,
classify: function(chords){
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
});
return classified;
Por um lado, isso é bom porque smoothing sai da nossa função. No entanto, aumenta o
escopo de onde está disponível e precisa ser adicionado this. para precedê-lo dentro
da função. Extrair isso const para um atributo também o torna atribuível novamente.
Infelizmente, obter algo como um const dentro de nosso classifier literal de objeto
dá um pouco de trabalho.Existe uma defineProperty função que podemos usar para
definir atributos que não são apenas não graváveis, mas até imutáveis por padrão. Não
entraremos em detalhes aqui, mas defineProperty é uma boa ferramenta para anali-
sar se você quiser ter muito controle sobre como as propriedades são usadas.
classify: function(chords){
Mas isso devolve a complexidade à nossa função, que é o que estávamos tentando evitar,
tornando-a um objeto para começar. Mais tarde, discutiremos alternativas para criar ob-
jetos com literais de objeto, o que pode nos dar mais controle sobre nossas propriedades
à medida que os objetos são criados. Mas, por enquanto, vamos reverter essa mudança e
permitir smoothing que seja um atributo como era.
Outra mudança que podemos fazer na classify função é inline a variável classificada
usando a map função em vez de forEach .Sempre que você estiver configurando um
contêiner (geralmente um array, mas no nosso caso um Map objeto), usando um loop de
algum tipo para alterar essa variável e, em seguida, retornando a variável, você pode ser
mais bem servido usando uma map função em vez de um ciclo:
classify: function(chords){
Array.from(this.labelProbabilities.entries()).map(
(labelWithProbability) => {
this.probabilityOfChordsInLabels.get(labelWithProbability[0])[chord];
if(probabilityOfChordInLabel){
}else{
}
}, this.labelProbabilities.get(labelWithProbability[0]) +
this.smoothing);
});
return classified;
Infelizmente, o Map objeto não tem uma map função (sim, isso também me incomoda.)
Portanto, nosso primeiro passo envolve extrair um array das entradas no Map para que
possamos usá map -lo. A segunda mudança é que acabamos com um objeto um pouco
menos conveniente ( labelWithProbability em vez de difficulty ), que exige que
peguemos o primeiro índice de [0] . Em outras palavras, difficulty é substituído por
labelWithProbability[0] . Mas podemos inicializar difficulty como um novo
const :
classify: function(chords){
Array.from(this.labelProbabilities.entries()).map(
(labelWithProbability) => {
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
});
return classified;
Agora podemos quase nos livrar de nossa classified variável. Em vez de inicializá-lo,
atualizá-lo no loop e retorná-lo depois, podemos map retornar um array multidimensio-
nal para definir o Map :
classify: function(chords){
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
}));
return classified;
E agora estamos prontos para nos livrarmos da classified variável. Simplesmente ex-
clua a linha com a última return instrução e, em vez disso, retorne o resultado da cha-
mada para o new Map construtor :
classify: function(chords){
this.labelProbabilities.entries()).map((labelWithProbability) => {
...
}));
const probabilityOfChordInLabel =
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
}, this.labelProbabilities.get(difficulty) + this.smoothing);
classify: function(chords){
this.probabilityOfChordsInLabels.get(difficulty)[chord];
if(probabilityOfChordInLabel){
}else{
}
}));
Isso pode parecer confuso, mas ainda estamos retornando um array de dois elementos.
O primeiro elemento é difficulty e o segundo é o resultado da reduce chamada.
Se quisermos simplificar ainda mais, podemos extrair uma função para eliminar nossa
atribuição e nossa condicional dentro deste código:
const probabilityOfChordInLabel =
this.probabilityOfChordsInLabels.get(difficulty)[chord]
if(probabilityOfChordInLabel){
}else{
return total;
Temos que excluir essa atribuição e substituir as outras duas referências (uma no teste
da condicional e outra na if ramificação da condicional) pelo lado direito da atribuição.
Isso deve nos deixar com isso:
if(this.probabilityOfChordsInLabels.get(difficulty)[chord]){
return total *
(this.probabilityOfChordsInLabels.get(
difficulty)[chord] + this.smoothing);
}else{
return total;
Em seguida, extraímos uma função para substituir completamente essas linhas por isso:
valueForChordDifficulty(difficulty, chord){
if(this.probabilityOfChordsInLabels.get(difficulty)[chord]){
this.smoothing;
}else{
return 1;
}
},
Essa função produzirá um valor para multiplicar pelo total em execução. Se quisermos
uma sintaxe um pouco mais densa para essa função, podemos usar a sintaxe ternária:
valueForChordDifficulty(difficulty, chord){
const value =
this.probabilityOfChordsInLabels.get(difficulty)[chord];
},
Observe que isso também funcionaria como a primeira linha se você não gostar da sin-
taxe abreviada da função:
valueForChordDifficulty(difficulty, chord){
DENSIDADE E ABSTRAÇÃO
Neste ponto, você deve estar se perguntando se essa refatoração realmente vale a pena.
Ao inserir variáveis, tornamos o código mais denso.Há também alguma sobrecarga para
usar map com um Map objeto. Para algumas pessoas, embutir variáveis e usar funções
como bind vai tornar o código mais difícil de ler. Para outros, a sintaxe da seta, com to-
das as suas variações (que abordaremos mais adiante), pode ser esmagadora. É impor-
tante estar ciente de como seu estilo pode afetar outros membros de sua equipe.
Para variáveis e funções, a parte importante é estar ciente de inlining e extração como
escolhas.
O C Ó D I G O N Ã O T E M E S TA D O F I N A L , P E R F E I TO , “ R E FATO R A D O ”
Uma coisa importante a notar sobre o nosso trabalho na classify função éque não há como sa-
ber quando você “termina” a refatoração. Algumas refatorações (como embutir e extrair) são pro-
cessos inversos, então você pode acabar desfazendo o trabalho de refatoração anterior com esfor-
ços subsequentes. Você poderia criar um loop infinito de refatoração apenas devido a esse fato.
Misture as opiniões dos outros sobre o que é um código “bom”, e você realmente pode “melhorar”
o código para sempre. É por isso que é importante desenvolver seus próprios padrões de quali-
dade e calibrá-los com as pessoas com quem você trabalha.
Passando da nossa classify função,nóster um problemano código que precisa ser en-
dereçado. Você verá algo interessante se adicionar instruções de log aos testes (depois
trainAll(); de ) como o seguinte:
console.log(classifier.probabilityOfChordsInLabels);
console.log(classifier.chordCountsInLabels);
Têm os mesmos valores. Não só isso, eles realmente estão referenciando o mesmo
Set objeto.
function setProbabilityOfChordsInLabels(){
classifier.probabilityOfChordsInLabels =
classifier.chordCountsInLabels;
classifier.probabilityOfChordsInLabels.forEach(
function(_chords, difficulty){
Object.keys(
classifier.probabilityOfChordsInLabels.get(difficulty)).forEach(
function(chord){
classifier.probabilityOfChordsInLabels.get(difficulty)[chord]
/= classifier.songs.length;
});
});
x = {a: 2};
// returns { a: 2 }
y = x;
// returns { a: 2 }
x['b'] = 3;
// returns 3
y;
// returns { a: 2, b: 3 }
y['c'] = 5;
// returns 5
x;
// returns { a: 2, b: 3, c: 5 }
No entanto, tudo isso envolve o que acontece quando você atualiza um objeto. Se você
reatribuir o objeto novamente, ele não alterará os dois rótulos para apontar para o novo
objeto:
x = {a: 2};
// returns { a: 2 }
y = x;
// returns { a: 2 }
x = {b: 5};
console.log(y);
// prints { a: 2 }
function setProbabilityOfChordsInLabels(){
classifier.chordCountsInLabels = classifier.chordCountsInLabels;
classifier.chordCountsInLabels.forEach(function(_chords, difficulty){
Object.keys(classifier.chordCountsInLabels.get(difficulty))
.forEach(function(chord){
classifier.chordCountsInLabels.get(difficulty)[chord]
/= classifier.songs.length;
});
});
Agora a linha dois parece mais obviamente redundante, o que é ótimo, porque sabemos
que podemos excluí-la:
function setProbabilityOfChordsInLabels(){
classifier.chordCountsInLabels.forEach(function(_chords, difficulty){
Object.keys(classifier.chordCountsInLabels.get(difficulty))
.forEach(function(chord){
classifier.chordCountsInLabels.get(difficulty)[chord]
/= classifier.songs.length;
});
});
const classifier = {
...
const value =
this.probabilityOfChordsInLabels.get(difficulty)[chord];
const classifier = {
...
E os testes devem estar passando neste momento. Em alguns casos, a refatoração correta
aqui seria copiar verdadeiramente o Set objeto em um novo. Então, ambos seriam obje-
tos independentes. Na verdade, não precisamos de dois objetos, então tentaremos uma
abordagem diferente aqui.
OBJETOS “COPIANDO”
Se você estiver interessado em copiar objetos emJavaScript, procureos termos cópia pro-
funda versus cópia superficial , bem como clonagem e as Object funções freeze ,
assign , e seal . Eles podem fazer o que você quer. Ou talvez você queira criar um
novo objeto vinculado ao antigounsprotótipo com Object.create . Talvez você queira
um objeto totalmente novo com Object.assign . Talvez você queira fazer uma função
construtora ou uma classe? Talvez você queira apenas uma função de fábrica (veja o Ca-
pítulo 8 ). Talvez você queira um contêiner novo e imutável para seus dados.
A propósito, há duas coisas a serem observadas Object.create . Primeiro, ele não pos-
sui as propriedades do objeto protótipo (seu primeiro parâmetro), então elas podem ser
sobrescritas. Segundo, ele apenas “copia” propriedades enumeráveis, portanto, não es-
pere que ele copie tudo .
Como o último exemplo de código contém nossa única referência ao valor do novo ob-
jeto, tudo o que temos agora é um objeto que muda. Mas talvez não precisemos atualizar
o objeto. Vamos tentar usar uma função como atributo, em vez de um conjunto que ar-
mazena todos os valores.
const classifier = {
...
setProbabilityOfChordsInLabels: function(){
classifier.chordCountsInLabels
.forEach(function(_chords, difficulty){
Object.keys(classifier.chordCountsInLabels.get(difficulty))
.forEach(function(chord){
classifier.chordCountsInLabels.get(difficulty)[chord]
/= classifier.songs.length;
});
});
},
valueForChordDifficulty(difficulty, chord){
...
function setLabelsAndProbabilities(){
setLabelProbabilities();
setChordCountsInLabels();
classifier.setProbabilityOfChordsInLabels();
};
A execução do conjunto de testes mostra que tudo está funcionando corretamente. Agora
vamos substituir as referências a classifier por this :
setProbabilityOfChordsInLabels: function(){
Object.keys(this.chordCountsInLabels.get(difficulty))
.forEach(function(chord){
this.chordCountsInLabels.get(difficulty)[chord]
/= this.songs.length;
}, this);
}, this);
},
Agora chegamos ao cerne do problema: não queremos funções que definam valores por
meio de efeitos colaterais.Queremos funções que retornem valores. Eles são muito mais
fáceis de trabalhar e acompanhar. Não chegaremos até aqui neste capítulo e, sem dú-
vida, OOP luta contra esse ethos, enquanto o FP o encoraja (consulte o Capítulo 11 ).
const classifier = {
...
.get(difficulty)[chord] / this.songs.length;
},
valueForChordDifficulty(difficulty, chord){
},
...
function setLabelsAndProbabilities(){
setLabelProbabilities();
setChordCountsInLabels();
};
Neste ponto, todos os testes devem estar funcionando novamente. Apenas para recapitu-
lar o que aconteceu nesta seção, tivemos dois nomes de variáveis que foram atribuídos
ao mesmo objeto. Primeiro, alteramos todas as instâncias do nome da segunda variável
para o nome da primeira variável, deixando-nos com uma variável que foi atualizada.
Então, em vez de depender das atualizações dessa variável, alteramos as referências a
ela para usar um cálculo para obter as informações (em vez de apenas acessar onde as
informações foram armazenadas).
SOBRE A REATRIBUIÇÃO DE VARIÁVEIS
Se há uma coisa que todos poderiam parar de fazer para beneficiar seu código, seria rea-
tribuir variáveis.
Algo a ser considerado é a ideia de que a POO encoraja a reatribuição de variáveis por
meio de objetos que permanecem e permitem que as propriedades sejam alteradas.
Na última seção, tratamos de duas referências a um objeto que foi atualizado. Agora, va-
mos lidar com dois objetos independentes que são um pouco parecidos demais.
classifier.songs e songList.songs parecem ter dados quase idênticos. Realmente,
a única diferença é a propriedade extra ( name ) em songList.songs . Vamos nos li-
vrar da classifier propriedade songs de :
const classifier = {
...
return this.chordCountsInLabels
.get(difficulty)[chord] / songList.songs.length;
},
function setLabelProbabilities(){
classifier.labelCounts.forEach(function(_count, label){
classifier.labelProbabilities.set(label,
classifier.labelCounts.get(label) / songList.songs.length);
})
};
Por último, nossa setChordCountsInLabels função precisa ser alterada para usar
songList.songs em vez de classifier.songs , e também para usar
song.difficulty em vez de song.label :
function setChordCountsInLabels(){
songList.songs.forEach(function(song){
if(classifier.chordCountsInLabels.get(song.difficulty)
=== undefined){
}
song.chords.forEach(function(chord){
0){
classifier.chordCountsInLabels.get(song.difficulty)[chord] +=
1;
} else {
classifier.chordCountsInLabels.get(song.difficulty)[chord] =
1;
}
});
});
const classifier = {
...
setChordCountsInLabels = function(){
songList.songs.forEach(function(song){
if(classifier.chordCountsInLabels.get(song.difficulty)
=== undefined){
}
song.chords.forEach(function(chord){
if(classifier.chordCountsInLabels
.get(song.difficulty)[chord] += 1;
} else {
classifier.chordCountsInLabels
.get(song.difficulty)[chord] = 1;
}
});
});
},
...
function setLabelsAndProbabilities(){
setLabelProbabilities();
classifier.setChordCountsInLabels();
};
const classifier = {
...
setChordCountsInLabels: function(){
songList.songs.forEach(function(song){
}
song.chords.forEach(function(chord){
this.chordCountsInLabels.get(song.difficulty)[chord] += 1;
} else {
this.chordCountsInLabels.get(song.difficulty)[chord] = 1;
}
}, this);
}, this);
},
Se tudo o que temos a fazer écontar o número de vezes que um acorde aparece em uma
determinada dificuldade, podemos montar loops semelhantes e um contador, e basta so-
mar 1 quando houver correspondência. Adicione isso depois
setChordCountsInLabels no classify objeto:
let counter = 0;
songList.songs.forEach(function(song){
song.chords.forEach(function(chord){
}
});
}
});
return counter;
},
Agora podemos usar essa função em vez de definir e recuperar as contagens de acordes.
Observe que não precisamos thisArg de s para nossas forEach funções porque não
temos mais referências a this .
Precisamos de algumas mudanças adicionais. Primeiro, a likelihoodFromChord fun-
ção pode ser alterada para:
songList.songs.length;
},
function setLabelsAndProbabilities(){
setLabelProbabilities();
};
Esta função agora só existe para chamar outra função. Isso significa que podemos ex-
cluir setLabelsAndProbabilities e apenas chamar setLabelProbabilities , o
queacontece dentro da trainAll função. Mude isso:
function trainAll(){
songList.songs.forEach(function(song){
});
setLabelsAndProbabilities();
};
para isso:
function trainAll(){
songList.songs.forEach(function(song){
});
setLabelProbabilities();
};
Todos os testes ainda passam. Antes de prosseguir, vamosdeve ter outro olhar para a
chordCountForDifficulty função:
let counter = 0;
songList.songs.forEach(function(song){
song.chords.forEach(function(chord){
}
});
}
});
return counter;
},
Como fizemos na classify função, quando temos um código que usa um loop para
aplicar alguma função a uma coleção enquanto altera uma variávelpor toda parte, é um
bom candidato para reduce . Pelocaminho, observe queusamos let aqui, em vez de
const , porque temos uma variável que atualiza genuinamente. Vamos alterá-lo para
usar reduce :
song.chords.forEach(function(chord){
}
});
}
}, 0);
},
}).length;
}
}, 0);
},
Obtemos o length deste array e o adicionamos ao counter . Isso significa menos atua-
lizações para o counter . Também raspamos duas linhas da
chordCountForDifficulty função. Poderíamos ter usado um segundo reduce aqui
em vez de filter se quiséssemos focar mais na contagem do que na condicional, mas
isso significaria uma innerCount variável, que parece um pouco mais desajeitada.
IMPACTOS NO DESEMPENHO
As alterações que fizemos para consultar conforme a necessidade, em vez de criar estru-
turas mais específicas para necessidades posteriores, são para fins de refatoração, in-
cluindo a redução do tamanho do código.
Dependendo dos padrões de acesso a dados de seu programa, essa estratégia pode tornar
seu programa mais lento ou mais rápido. Transformar estruturas em outras que você
nunca usará seria um desperdício. Transformá-los em outros mais simples (mais rasos)
que serão acessados com frequência pode produzir algum benefício de desempenho.
Tudo isso vem com a ressalva de que uma determinada plataforma JavaScript pode oti-
mizar seu código de maneiras que você não espera.
De qualquer forma, a abordagem deste livro é escrever primeiro para humanos e se pre-
ocupar com o desempenho depois do fato.
Seguindo em frente, temos apenas três funções restantes no escopo global: train ,
trainAll , e setLabelProbabilities . Esses parecem que se encaixam perfeita-
mente como parte do classifier . Vamos movê-los agora:
const classifier = {
...
trainAll: function(){
songList.songs.forEach(function(song){
});
classifier.setLabelProbabilities();
},
chords.forEach(chord => {
classifier.allChords.add(chord);
});
if(Array.from(classifier.labelCounts.keys()).includes(label)){
classifier.labelCounts.set(
} else {
}
},
setLabelProbabilities: function(){
classifier.labelCounts.forEach(function(_count, label){
classifier.labelProbabilities.set(
label, classifier.labelCounts.get(label) /
songList.songs.length);
});
...
A maior mudança é que precisamos colocar essas funções na sintaxe do atributo do ob-
jeto. Além disso, trainAll precisa preceder suas chamadas para train e
setLabelProbabilities com classifier. .
classifier.trainAll();
trainAll: function(){
songList.songs.forEach(function(song){
}, this);
this.setLabelProbabilities();
},
if(Array.from(this.labelCounts.keys()).includes(label)){
} else {
}
},
setLabelProbabilities: function(){
this.labelCounts.forEach(function(_count, label){
songList.songs.length);
}, this);
Observe que nossa forEach função dentro de train não precisa de um thisArg , pois
usa a sintaxe de seta.
Agora, tudo faz parte de um classifier ou songList . Estamos reduzidos a duas va-
riáveis globais. Agora podemos começar a pensar um pouco mais sobre o que pertence a
onde. Um atributo de classifier se destaca como sendo mais apropriado como parte
de songList : allChords .
const songList = {
Em seguida, uma vez que queremos que o nosso classifier seja o únicoponto de
acesso global no programa, devemos passar songList para ele:
const classifier = {
songList: {
}
},
...
Em seguida, vamos resolver algumas inconsistências que temosem nossa sintaxe de fun-
ção. Usamos funções de seta anteriormente neste livro, mas vamos entrar em mais deta-
lhes aqui. Começaremos com uma instância em que já estamos usando a sintaxe de seta.
Em nossa train função temos o seguinte:
train: function(){
...
Já discutimos isso um pouco antes, mas o que é interessante sobre isso é, bem, this .
Nós referenciamos this dentro da função anônima aqui e somos livres para não passar
um thisArg ( this ) como o segundo parâmetro para forEach . Como prova de que
isso é algo especial com funções de seta e não código que funciona bem sem ela, tente
convertê-lo para a forma longa:
train: function(){
chords.forEach(function(chord){
this.songList.allChords.add(chord);
});
...
Vai quebrar os testes! Se você quiser usar esse formulário, precisará incluir o this ,
como fazemos atualmente em trainAll :
train: function(){
chords.forEach(function(chord){
this.songList.allChords.add(chord);
}, this);
...
Mas poderíamos usar bind na função em vez disso (fazemos isso com reduce uma vez
que não tem a opção de passar a thisArg como segundo parâmetro):
train: function(){
chords.forEach(function(chord){
this.songList.allChords.add(chord);
}.bind(this));
...
Em vez de fazer qualquer um desses, vamos usar a sintaxe de seta, que passa o
this contexto para a função interna e nos poupa de ter que digitar tanto a função da pa-
lavra . Incrível!
Você pode estar se perguntando por que nunca usaríamos a sintaxe de seta. Existem al-
guns casos. Mais importante, você não gostaria de usá-lo nos casos em que o this que
você se importa não deve ser aquele this que você tem acesso ao entrar na função. Por
exemplo, você pode esperar que this um manipulador de cliques no jQuery se refira ao
objeto clicado. Se você usar uma função de seta, esse não será o caso.
// form 1
chords.forEach(function myFunction(){
this.songList.allChords.add(chord);
}, this);
// form 2
chords.forEach(myFunction, this);
function myFunction(){
this.songList.allChords.add(chord);
};
É fácil converter entre essas duas formas. Se você começar com uma função anônima no
formulário 1 (com ou sem sintaxe de seta), será necessário um pouco de trabalho extra
para convertê-la em uma função nomeada independente. Mas para todas as nossas fun-
ções usadas com forEach , map , reduce e filter , podemos substituí-las facilmente
por funções de seta.Isso corta cerca de 10 linhas de código. A propósito, com nossa refa-
toração até agora, o que antes era cerca de 110 linhas agora está reduzido para 63. É pe-
queno o suficiente para ver tudo de uma vez (menos os testes):
const classifier = {
smoothing: 1.01,
songList: {
}
},
}).length;
}
}, 0);
},
this.songList.songs.length;
},
valueForChordDifficulty(difficulty, chord){
},
trainAll: function(){
});
this.setLabelProbabilities();
},
if(Array.from(this.labelCounts.keys()).includes(label)){
} else {
}
},
setLabelProbabilities: function(){
this.songList.songs.length);
});
},
classify: function(chords){
}));
}
};
Há algumas coisas a serem observadas com a sintaxe da seta. Primeiro, se houver ape-
nas um argumento, ele não precisa entrar entre parênteses:
this.labelProbabilities.entries()).map(labelWithProbability => {
Se houver dois ou mais argumentos, você precisará deles, como neste caso:
setLabelProbabilities: function(){
Uma coisa um tanto estranha é que também precisamos de parênteses se tivermos zero
argumentos. Neste ponto, podemos alterar todos os nossos testes para usar esta sintaxe:
...
it('classifies', () => {
...
...
...
Passando para a sintaxe abreviada de declaração de função para objetos, podemos usar
a sintaxe de seta e declarar nossa trainAll função assim:
trainAll: () => {
this.songList.songs.forEach(song => {
this.train(song.chords, song.difficulty);
});
this.setLabelProbabilities();
},
No entanto, não é isso que queremos. Na verdade, queremos o this que obtemos com a
palavra-chave function usada, porque esse this é o objeto ( classifier ), que é a que
nosso this interior da função se refere. Quando declaramos funções dessa maneira, ob-
temos o contexto fora do objeto (o objeto global, assumindo o modo não estrito). De qual-
quer forma, existe uma abreviação melhor para declarações de funções, que veremos
em breve. Vamos colocar de volta nisso:
trainAll: function(){
this.songList.songs.forEach(song => {
this.train(song.chords, song.difficulty);
});
this.setLabelProbabilities();
},
Uma outra coisa estranha sobre a sintaxe da seta é que algumas vezes usamos chaves
{} e outras não.No momento temos isso:
counter += song.chords.filter((chord) => {
}).length;
A falta de parênteses, chaves e o return pode parecer um pouco chocante porque torna
a declaração mais densa. No entanto, há uma consideração adicional com a versão sem
chaves: ela retorna implicitamente o resultado de sua execução. No caso do nosso último
trecho de código, isso significa que ele retorna true ou false .
Observe também que, como a sintaxe de chaves é usada como forma de agrupar o corpo
da função, se você quiser retornar um objeto como este, ficará desapontado:
Por uma questão de estilo pessoal, eu recomendaria usar menos sintaxe quando possí-
vel. Isso significa isso:
Durante a discussão das funções de seta, esbarramos na ideia de uma abreviação para
declarar funções como parte de um objeto. É assim que estamos declarando atualmente
a maioria das funções:
const classifier = {
...
...
...
valueForChordDifficulty(difficulty, chord){
// instead of
Usar esta sintaxe abreviada pode remover completamente a : function parte, fazendo
com que as funções fiquem assim:
const classifier = {
songList: {
...
...
chordCountForDifficulty(difficulty, testChord){
...
Depois de fazer isso com todas as nossas declarações de função, estamos completamente
livres da palavra- function chave neste arquivo!Uma fonte incrível de ruído visual e
digitação extra se foi. Obrigado, ES2015!
Observe, no entanto, que essa abreviação funciona apenas dentro de literais e classes de
objetos. Fora desses contextos (em uma função normal, escopo global ou escopo de fun-
ção construtora, por exemplo), o interpretador lançará um erro no arquivo { .
Mais uma vez, podemos ver que nossa sintaxe abreviada é menos portátil do que a ver-
são longa.
PROPRIEDADES COMPUTADAS
songs = {
['first' + 'Song']: {},
['third' + 'Song']: {}
}
Basicamente, você pode executar JavaScript dentro desses colchetes para gerar nomes
de propriedades. Este é um exemplo trivial (e você provavelmente gostaria de alguns da-
dos em seus objetos), mas você pode achar isso conveniente em algum momento.
this.songs.push({name: name,
Mas poderíamos encurtaro objeto dentro de push para usar a abreviação do objeto ,
assim:
this.songs.push({name, chords,
difficulty: this.difficulties[difficulty]});
this.songs.push({name,
chords,
};
};
this.smoothing = 1.01;
}
}, 0);
};
this.songList.songs.length;
};
};
this.trainAll = function(){
});
this.setLabelProbabilities();
};
if(Array.from(this.labelCounts.keys()).includes(label)){
} else {
}
};
this.setLabelProbabilities = function(){
this.songList.songs.length);
});
};
this.classify = function(chords){
}));
};
};
...
Os testes têm apenas uma pequena mudança, porque precisamos inicializar nosso
classifier com:
É assim que você instancia um objeto em JavaScript usando new uma função constru-
tora. Observe que também estamos instanciando uma songList propriedade de ma-
neira semelhante dentro do classifier :
Se você esquecer new ao chamar uma função construtora,sua função ainda pode funcionar bem,
mas há uma chance de que não. Isso porque this será vinculado ao objeto global (ou
undefined em modo estrito).
Às vezes, esse medo (e se as pessoas esquecerem a palavra- new chave!?) é citado como a principal
motivação por trás da preferência Object.create por new uma função construtora. O argumento
mais forte para Object.create tem mais a ver com a busca de consistência e não obscurecer a
natureza prototípica do JavaScript , em vez de usar o “estilo pseudoclássico” que as funções (e clas-
ses) do construtor new promovem.
No lado mais flexível das coisas, os parênteses após a chamada do construtor a seguir new são op-
cionais quando nenhum argumento é passado para a função. É um pouco estranho, mas estes são
os mesmos:
Há uma diferença importante entre criarobjetos como este e usando literais de objeto. Se
você usar um literal de objeto, ficará preso fazendo algumas clonagens/cópias profun-
das/ Object.create hijinks para obter uma nova versão. Como exploramos anterior-
mente, atribuir algo a uma nova variável usando = apenas cria uma nova referência
para o mesmo objeto: dois dedos apontando para a mesma lua. Se você precisar de mais
de uma lua, precisará new de , Object.create , class e/ou algum outro utilitário de
cópia/clonagem. Se você sabe que vai querer mais de um objeto, literais de objeto podem
não ser o melhor ponto de partida para criá-los.
Este trecho também demonstra outro resultado dessa mudança: temos que adicionar
this. a todas as nossas propriedades quando usamos uma função construtora. Observe
também que essa sintaxe nos força a adicionar nossas function palavras-chave de
volta com os dois pontos substituídos por sinais de igual. Por fim, as vírgulas no final das
declarações foram substituídas por ponto e vírgula (ou não, em casos especiais, se você
for uma dessas pessoas).
FA L A N D O E M P O N TO E V Í R G U L A . . .
Algumas pessoas realmente odeiam ponto e vírgula.Eles só os usarão quando for absolutamente
necessário, como prova de seu profundo conhecimentoda inserção automática de ponto e vírgula
do JavaScript (ASI).
Pessoalmente, acho os casos extremos para quando os pontos e vírgulas são necessários um pouco
difíceis de lembrar, e sinto que outros (incluindo pessoas com quem trabalho) também. Por essas
razões, eu os incluo na maioria das vezes.
“Para ponto e vírgula, ou não para ponto e vírgula?” não é a questão final para um humano: este é
um trabalho para um linter lidar.
Embora a sintaxe possa não ser a que preferimos, os testes ainda passam, então esta é
uma refatoração bem-sucedida até agora. O que nós ganhamos? Bem, como estamos
dentro da função construtora e não restringidos pela sintaxe JSON, somos livres para es-
crever qualquer instrução que desejarmos. Isso significa que poderíamos ter funções e
variáveis privadas (ou seja, não expostas) dentro do construtor, bem como declarar va-
riáveis globais não usando var , let ou const (também poderíamos fazer isso na sin-
taxe literal do objeto, mas não diretamente). Mais importante, como mencionado anteri-
ormente, podemos obter várias instâncias do mesmo objeto simplesmente com new , em
vez de passar por etapas um tanto não intuitivas para “clonar” ou “copiar” ou “criar”
um.
Vamos fazer uma pausa do nosso classifier por apenas um minuto.Você pode achar
este exemplo semelhante ao diary código do Capítulo 5 , pois também diz respeito à
privacidade. No entanto, esta discussão está mais focada em objetos e sua criação.
Estaremos definindo nossa API em uma seção posterior deste capítulo e, para fazer isso,
devemos estar familiarizados com o que podemos e não podemos acessar dentro das
funções do construtor. O exemplo de código a seguir é um pouco mais curto, então será
mais fácil entender isso antes de aplicar os conceitos ao nosso código NBC:
// constructor function
};
this.notSecret = function(){
};
};
console.log(s.secret); // undefined
console.log(s.secretFunction()); // error
console.log(s.notSecret()); // 'sekrit'
console.log(s.totallyNotSecret); // undefined
Uma alternativa para criar objetos com a new palavra-chave éusando Object.create .
Antes de mudarmos nosso classificador, vamos apenas observar as diferenças entre essa
construção e new , como no último trecho:
// factory function
};
obj.notSecret = function(){
};
return obj;
})();
const s = Object.create(secretTemplate);
console.log(s.secret); // undefined
console.log(s.secretFunction()); // error
console.log(s.notSecret()); // 'sekrit'
console.log(s.totallyNotSecret); // undefined
Então, quando usamos Object.create , precisamos fornecer um objeto para ser padro-
nizado. Para manter a flexibilidade de usar um construtor com new , acabamos retor-
nando um objeto dentro da função. Observe que esta função é um IIFE.No entanto, tam-
bém podemos usar uma expressão de função normal:
var secretTemplate = function(){
...
};
const s = Object.create(secretTemplate());
Isso nos dá o mesmo objeto, mas parece um pouco menos claro. Além disso, nesta cons-
trução, a função terá que ser executada toda vez que criarmos um novo objeto. Na ver-
são IIFE, a secretTemplate função só precisa ser executada uma vez, e para criar no-
vos objetos basta referenciar o objeto template que foi criado.
// module pattern
};
notSecret(){
return secret;
}};
})();
const s = Object.create(secretTemplate);
console.log(s.secret); // undefined
console.log(s.secretFunction()); // error
console.log(s.notSecret()); // 'sekrit'
console.log(s.totallyNotSecret); // undefined
Isso usa o padrão de módulo , não deve ser confundido com módulos para importar e ex-
portar pacotes.Uma variação popular no padrão de módulo é o padrão de módulo revela-
dor , mostrado aqui:
};
};
})();
const s = Object.create(secretTemplate);
console.log(s.secret); // undefined
console.log(s.secretFunction()); // error
console.log(s.notSecret()); // 'sekrit'
console.log(s.totallyNotSecret); // undefined
Isso é um pouco mais limpo, pois torna o objeto retornado muito conciso e legível.
Acabamos de usar um IIFE como o lado direito de nossa instrução de atribuição. Nesse
caso, a variável é provavelmente uma boa candidata para exportar como um módulo.
Em outros casos, onde um IIFE é usado especificamente para reduzir o escopo do código
interno, podemos usar um bloco. Então, é isso:
(function(){
})();
torna-se isto:
};
Mas se você tentar usar umblock, ou seja {} , no lado direito de uma atribuição (como
fizemos com um IIFE), ele será interpretado como um objeto e provavelmente dará um
erro. Nesses casos, você está preso ao IIFE.
Voltando ao nosso classifier , como usaríamos Object.create para uma função de
fábrica?Acaba sendo muito simples e depende muito mais da sintaxe literal do objeto do
que de como reorganizamos as coisas para usar new com as funções do construtor na úl-
tima seção:
const classifierTemplate = {
songList: {
...
Para fazer esse código funcionar, precisamos apenas de duas alterações em relação ao
que fizemos com o literal do objeto. Primeiro, precisamos considerar nosso objeto inicial
como um template , então o renomeamos classifierTemplate . Segundo, em nossos
testes (que se referem a classifier ), criamos o classifier objeto passando o objeto
de modelo para Object.create . Observe que estamos usando apenas um literal de ob-
jeto aqui, não uma função ou IIFE, porque não temos nada que nos importe em ser pri-
vado no momento.
Caso você tenha esquecido quais vantagens uma função de fábrica Object.create e
uma função de construtor new têmsobre apenas usar o literal do objeto diretamente, o
principal é que essas são maneiras de criar várias classifier variáveis. O songList é
uma propriedade de classifier , e como um objeto aninhado, uma nova versão dele
será criada para cada classifier , mesmo que use a sintaxe literal de objeto.
Object.create e new (junto com classes e módulos) também são suas portas de en-
trada para herança, que discutiremos com mais profundidade no próximo capítulo.
class Classifier {
constructor(){
this.songList = {
this.songs.push({name,
chords,
}
};
};
chordCountForDifficulty(difficulty, testChord){
).length;
}
}, 0);
};
...
A primeira linha pode parecer um pouco com uma definição de função, e sob o capô ela
é (embora isso esteja se tornando menos aparente à medida que as classes JS evoluem),
mas é um tipo especial de função.E como acontece com uma função, também podemos
atribuir uma expressão de classe a uma variável, assim:
likelihoodFromChord(difficulty, chord){
/ this.songList.songs.length;
};
nestes:
likelihoodFromChord(difficulty, chord){
return this.divide(this.chordCountForDifficulty(difficulty,
chord),
this.songList.songs.length);
};
divide(dividend, divisor){
};
E como divide claramente não tem nada a ver com a função em si (o que é aparente
porque não há referências a this ), podemos torná-la estática e alterar a chamada para
refletir o novo contêiner da função (a classe, em vez da instância) :
likelihoodFromChord(difficulty, chord){
};
};
Essa mudança é desnecessária, pois / faz o trabalho muito bem, mas as funções estáti-
cas são bastante fáceis de implementar. Sinta-se à vontade para reverter essas
alterações.
MAS AS AULAS NÃO SÃO RUINS?
“JavaScript não tem classes reais!Eles são apenas funções e protótipos e objetos por
baixo! É um truque. Não caia nessa! Sua verdadeira natureza é disfarçada por eles!”
Ou “JavaScript só foi construído em 10 dias ou algo assim, certo? É uma bagunça por
baixo, e agarrar-se à nova sintaxe é nossa única esperança de evitar pensar nas diferen-
ças entre .prototype , [[prototype]] , getPrototypeOf e __proto__ .”
Alternativamente, “as aulas são boas e úteis se forem executadas em sua plataforma e
sua equipe puder entendê-las”.
Além disso, à medida que mais recursos são adicionados às classes JavaScript (como atri-
butos privados reais, conforme discutido no Capítulo 5 ), eles começam a se parecer me-
nos com “açúcar sintático” e mais com uma construção única. Isso não invalida os argu-
mentos de preferência por composição de objetos ou programação funcional sobre OOP,
mas enfraquece o argumento “classes não são nada especiais”.
Agora que nossa classe pareceestar embonitoboa forma, é hora de considerar nossa pró-
pria API. Em outras palavras, se alguém estivesse importando nosso código como um
módulo,o quefunções seriam públicas (acessíveis a elas) e quais seriam privadas? Como
nósComo sabemos do Capítulo 5 , a distinção privado/público é um pouco complicada
em JavaScript (no momento da redação deste artigo, “privado” atualmente significa obs-
curo ou inacessível, embora isso provavelmente mude), mas mesmo assim, podemos de-
terminar o ideal agora, mesmo antes de realizar a distinção totalmente em um módulo.
Abordaremos isso na próxima seção. Por enquanto, estamos apenas escolhendo quais
funções definitivamente queremos que sejam acessíveis.
constructor
trainAll
classify
Além disso, nossa addSong função from songList precisa ser acessível. Por conveniên-
cia, vamos adicionar uma função ao classificador:
class Classifier {
constructor(){
...
};
};
...
E O TREM?
Atualmente, nossa API depende de seguir o padrão de adicionar músicas e treiná-las todas de uma
vez.Infelizmente, por causaos efeitos colaterais que temos, nosso código não é idempotente (um
conceito abordado no Capítulo 11 ). Em outras palavras, nossas funções não são “puras”, por causa
de seus efeitos colaterais, e executá-las em uma ordem diferente da especificada (ou várias vezes)
não é bem tratada.
Este é um problema que não resolveremos neste capítulo, mas há uma versão idempotente (e fun-
cional) desta NBC no Capítulo 11 .
classifier.songList.addSong('imagine',
classifier.songList.addSong('somewhereOverTheRainbow',
classifier.addSong('imagine',
classifier.addSong('somewhereOverTheRainbow',
Além disso, como nossa função no classificador se tornou pura delegação, não precisa-
mos mais ser tão específicos sobre os parâmetros:
class Classifier {
constructor(){
...
};
addSong(...songParams){ // rest
};
...
A sintaxe do parâmetro rest pega todos os argumentos que você fornece e os transforma
em uma matriz. O operador spread faz o oposto, dividindo o array que você fornece em
argumentos individuais para passar para a função.
A razão pela qual este é um bom padrão para aplicar nesta situação é que já temos uma
definição de função para addSong dentro de songList . Se decidirmos alterar a função
em termos de quais argumentos ela aceita, seria bom não ter que alterar essa função
também. Usar descanso e dispersão aqui nos dá essa flexibilidade.
Felizmente, seu editor tem uma maneira de renomear facilmente as coisas (encontrar e
substituir não apenas um arquivo, mas todo o projeto). Estes são os rótulos aos quais
você deve adicionar um sublinhado:
songList
labelCounts
labelProbabilities
smoothing
chordCountForDifficulty
likelihoodFromChord
valueForChordDifficulty
train
setLabelProbabilities
Agora temos um caminho extremamente simples para exportar nossa classe como um
módulo.Vamos dividir nossos testes e arquivo principal para provar isso. Primeiro, te-
mos algumas alterações a fazer em nb.js :
constructor(){
this._songList = {
this.songs.push({name,
chords,
}
};
};
addSong(...songParams){
this._songList.addSong(...songParams);
};
...
classify(chords){
this._labelProbabilities.entries()).map(
(labelWithProbability) => {
chord);
}));
}
};
Fizemos apenas duas alterações aqui. A primeira é que a primeira linha agora tem isso
no início:
module.exports =
A segunda é que estamos movendo todos os nossos testes para um arquivo separado (no
mesmo diretório) chamado nb_test.js :
classifier.addSong('imagine',
...
0.3333333333333333);
0.3333333333333333);
0.3333333333333333);
});
});
Fizemos apenas uma alteração em nossos testes, e está no topo. Se você executar mocha
nb_test.js , não deverá ter falhas. Incrível.
Mas você quer privacidade real? Por enquanto, você tem algumas opções. A primeira é
seguir a rota do “padrão de módulo revelador”, mudara classe de volta em uma função
construtora, e fazer algumas mudanças sérias.Ou, você pode tentar algumas coisas mais
complicadas e esotéricas com s do ES2015 Symbol (criando privacidade através da obs-
curidade) ou WeakMap s, ou usando uma função de inicialização (que é basicamente pro-
jetar sua própria forma do padrão de módulo revelador).
Se pensarmos honestamente sobre os trade-offs aqui, o que você está ganhando fazendo
algo complexo e fora do padrão? Você está fazendo mais trabalho para você e outros que
podem trabalhar em seu módulo. O próprio código e os testes se tornam mais comple-
xos. Trocar sua capacidade de chamar testes por funções verdadeiramente privadas e
não endereçáveis parece um absurdo.
Quais são os riscos de seguir a rota do sublinhado? Alguns caracteres extras aqui e ali
tornarão seu código feio? As pessoas vão pensar que você é burro demais para usar as
soluções alternativas mais recentes e confusas? As pessoas que usam seu módulo insisti-
rão em chamar essas funções, mesmo que para os não iniciados você tenha indicado em
sua documentação que não deveriam?
Se o seu programa já está vivendo na função construtora cidade, esta é uma chamada
mais difícil, mas se você estiver usando classes, adicionar sublinhados parece ser a solu-
ção muito mais fácil.
Dito tudo isso, parece que campos e métodos privados podem vir em breve. A especifica-
ção ainda está em andamento até o momento, mas a ideia básica é que as referências a
funções e atributos privados usariam um # em vez de um _ , e seriam acessíveis apenas
de dentro da definição de classe. Consulte o Capítulo 5 para obter um exemplo de como
pode ser.
E agora para o último tópico paraeste programa: e se, em vez de músicas com acordes,
estivermos trabalhando com aprendizado de vocabulário, e em vez de “fácil”, “médio” e
“difícil”, estivermos classificando um corpus de texto como “compreendido” ou “não
entendido”?
Algumas pessoas (tanto codificadores quanto não codificadores), ao pensar em uma abs-
tração potencial como essa, pularão para o caso geral imediatamente.Pense em abstrair
uma NBC geral com o código desde o início versus o código agora. Pessoalmente, reco-
mendo construir dois ou três tipos de algo (mesmo algo tão pequeno quanto uma fun-
ção) antes de insistir que são semelhantes e tentar abstraí-los.
Agora que nosso código está em melhor forma e nosso programa é relativamente pe-
queno, é fácil tentar adaptá-lo a um novo domínio.
constructor(){
this._textList = {
comprehension: this.understood[comprehension]});
}
};
};
addText(...textParams){
this._textList.addText(...textParams);
};
_wordCountForComprehension(comprehension, testWord){
).length;
}
}, 0);
};
_likelihoodFromWord(comprehension, word){
this._textList.texts.length;
};
_valueForWordComprehension(comprehension, word){
};
trainAll(){
});
this._setLabelProbabilities();
};
_train(words, label){
if(Array.from(this._labelCounts.keys()).includes(label)){
}
};
_setLabelProbabilities(){
this._labelProbabilities.set(label,
this._labelCounts.get(label) / this._textList.texts.length);
});
};
classify(words){
this._labelProbabilities.entries()).map(
(labelWithProbability) => {
word);
}, this._labelProbabilities.get(comprehension) +
this._smoothing)];
}));
}
};
Classifier = require('./nb_new_domain.js');
classifier.addText('english text',
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
0);
classifier.addText('japanese text',
あ い
[' ', ' ', 'う', 'え', 'お',
1);
classifier.trainAll();
it('classifies', () =>{
お は よ う
const classified = classifier.classify([' ', ' ', ' ', ' ', ' ご', 'ざ',
い ま す
' ', ' ', ' ']);
});
});
});
});
Com base na entrada anterior, os testes mostram que o japonês provavelmente será in-
compreensível para alguém que classificou os dados de treinamento originais. Se quisés-
semos ir mais longe com este classificador de texto, poderíamos estar interessados em
processar o texto de forma mais completa. Isso pode significar por caractere/letra, por
palavra, por frase ou aprofundar-se nas gramáticas dos idiomas de destino com técnicas
como stemming . Da mesma forma, com a versão musical, poderíamos ter considerado
sequências de acordes (as transições podem ser mais difíceis do que os próprios acor-
des). Também é possível que um leitor de ambas as línguas possa ter um limiar diferente
de compreensão para um vocabulário específico (por exemplo, partes de uma motoci-
cleta), em vez de classificar tudo em um idioma como incompreensível.
Novamente (como fizemos com “fácil”, “médio”, “difícil”), estamos parando um passo an-
tes que nosso classificador imprima “sim/não” ou “japonês/inglês” (podemos treinar
para qualquer um dos cenários), então apenas escolhemos a categoria com o maior nú-
mero associado. Sinta-se à vontade para adicionar um teste e implementar esse recurso
se estiver se sentindo ambicioso.
Embora esses sejam todos os recursos a serem adicionados,nosso objetivo neste capítulo
— melhorar a qualidade do código — foi alcançado, então estamos prontos.Confira o Ca-
pítulo 11 para obter uma versão funcional deste código.
Empacotando
Apoiar Sair
© 2022 O'REILLY MEDIA, INC. TERMOS DE SERVIÇO POLÍTICA DE PRIVACIDADE