Articolo originale: https://www.freecodecamp.org/news/how-to-build-an-html-calculator-app-from-scratch-using-javascript-4454b8714b98/
Questo è un articolo epico in cui impari come costruire una calcolatrice da zero. Ci concentreremo su cosa è necessario scrivere in JavaScript: come pensare alla creazione della calcolatrice, come scrivere il codice e, infine, come ripulire il tuo codice.
Alla fine dell'articolo, dovresti ottenere una calcolatrice che funziona esattamente come una calcolatrice per iPhone (senza +/-
e la funzione percentuale).
I prerequisiti
Prima di provare a seguire la lezione, assicurati di avere una discreta padronanza di JavaScript. Come minimo, devi sapere queste cose:
- Istruzioni if/else
- Cicli for
- Funzioni in JavaScript
- Funzioni arrow
- Opertori
&&
e||
- Come modificare il testo con la proprietà
textContent
- Come aggiungere il listener di eventi con il modello di delega di eventi
Prima di iniziare
Ti esorto a provare a costruire tu stesso la calcolatrice prima di seguire la lezione. È una buona pratica, perché ti allenerai a pensare come uno sviluppatore.
Torna a questa lezione dopo aver provato per un'ora (non importa se ci riesci o fallisci. Quando ci provi, pensi, e questo ti aiuterà ad assorbire la lezione il doppio più velocemente).
Detto questo, iniziamo col capire come funziona una calcolatrice.
Costruire la calcolatrice
Per prima cosa, vogliamo costruire la calcolatrice.
La calcolatrice è composta da due parti: il display e i tasti.
<div class="calculator">
<div class="calculator__display">0</div>
<div class="calculator__keys"> … </div>
</div>
Possiamo usare CSS Grid per creare i tasti, poiché sono disposti in un formato simile a una griglia. Questo è già stato fatto per te nel file di partenza. Puoi trovare il file di partenza qui .
.calculator__keys {
display: grid;
/* other necessary CSS */
}
Per aiutarci a identificare i tasti per le operazioni, per la virgola, per cancella e per uguale, forniremo un attributo chiamato data-action che descrive ciò che fanno.
<div class="calculator__keys">
<button class="key--operator" data-action="add">+</button>
<button class="key--operator" data-action="subtract">-</button
<button class="key--operator" data-action="multiply">×</button>
<button class="key--operator" data-action="divide">÷</button
<button>7</button>
<button>8</button>
<button>9</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>1</button>
<button>2</button>
<button>3</button>
<button>0</button>
<button data-action="decimal">.</button>
<button data-action="clear">AC</button>
<button class="key--equal" data-action="calculate">=</button>
</div>
Ascolto della pressione dei tasti
Cinque cose possono accadere quando una persona prende una calcolatrice. Può schiacciare:
- un tasto numerico (0–9)
- un tasto operatore (+, -, ×, ÷)
- il tasto virgola
- il tasto di uguale
- il tasto cancella
I primi passi per costruire questa calcolatrice sono: essere in grado di (1) ascoltare tutte le pressioni dei tasti e (2) determinare il tipo di tasto che viene premuto. In questo caso, possiamo utilizzare un modello di delega di eventi per l'ascolto, poiché i tasti sono tutti figli di .calculator__keys
.
const calculator = document.querySelector('.calculator')
const keys = calculator.querySelector('.calculator__keys')
keys.addEventListener('click', e => {
if (e.target.matches('button')) {
// Do something
}
})
Successivamente, possiamo utilizzare l'attributo data-action
per determinare il tipo di tasto su cui si fa clic.
const key = e.target
const action = key.dataset.action
Se il tasto non ha un attributo data-action
, allora deve essere un tasto numerico.
if (!action) {
console.log('number key!')
}
Se il tasto ha un attributo data-action
che è add
, subtract
, multiply
o divide
, allora capiamo che è un tasto operatore.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
console.log('operator key!')
}
Se l'attributo data-action
del tasto è decimal
, sappiamo che l'utente ha fatto clic sulla tasto decimale.
Seguendo lo stesso schema, se l'attributo data-action
del tasto è clear
, sappiamo che l'utente ha fatto clic sul tasto Cancella (quello con scritto AC). Se l'attributo data-action
del tasto è calculate
, sappiamo che l'utente ha cliccato sul tasto uguale.
if (action === 'decimal') {
console.log('decimal key!')
}
if (action === 'clear') {
console.log('clear key!')
}
if (action === 'calculate') {
console.log('equal key!')
}
A questo punto, dovresti ricevere una risposta console.log
da ogni tasto della calcolatrice.
Costruzione in uno scenario standard
Consideriamo cosa farebbe la persona media quando prende in mano una calcolatrice. Questo “ciò che farebbe la persona media” è chiamato lo scenario standard .
Chiamiamo la nostra persona media Mary.
Quando Mary prende in mano una calcolatrice, potrebbe premere uno qualsiasi di questi tasti:
- un tasto numerico (0–9)
- un tasto operatore (+, -, ×, ÷)
- il tasto virgola
- il tasto uguale
- il tasto cancella
Può essere pesante considerare cinque tipi di tasti contemporaneamente, quindi procediamo passo dopo passo.
Quando un utente preme un tasto numerico
A questo punto, se la calcolatrice mostra 0 (il numero di default), il numero selezionato dovrebbe sostituire lo zero.
Se la calcolatrice mostra un numero diverso da zero, il numero selezionato deve essere aggiunto al numero visualizzato.
Qui, è necessario sapere due cose:
- Il numero del tasto su cui è stato fatto clic
- Il numero attualmente visualizzato
Possiamo ottenere questi due valori rispettivamente attraverso la proprietà textContent
del tasto cliccato e di .calculator__display
.
const display = document.querySelector('.calculator__display')
keys.addEventListener('click', e => {
if (e.target.matches('button')) {
const key = e.target
const action = key.dataset.action
const keyContent = key.textContent
const displayedNum = display.textContent
// ...
}
})
Se la calcolatrice mostra 0, vogliamo sostituire il valore mostrato sul display della calcolatrice con quello del tasto cliccato. Possiamo farlo sostituendo la proprietà textContent del display.
if (!action) {
if (displayedNum === '0') {
display.textContent = keyContent
}
}
Se la calcolatrice mostra un numero diverso da zero, vogliamo aggiungere il tasto cliccato al numero visualizzato. Per aggiungere un numero, concateniamo una stringa.
if (!action) {
if (displayedNum === '0') {
display.textContent = keyContent
} else {
display.textContent = displayedNum + keyContent
}
}
A questo punto, Mary può fare clic su uno di questi tasti:
- Il tasto virgpla
- Un tasto operatore
Diciamo che Mary preme il tasto decimale.
Quando un utente preme il tasto virgola
Quando Mary preme il tasto virgola, sul display dovrebbe apparire una virgola. Se Mary preme un numero qualsiasi dopo aver premuto il tasto virgola, anche il numero dovrebbe essere aggiunto sul display.
Per creare questo effetto, possiamo concatenare .
al numero visualizzato.
if (action === 'decimal') {
display.textContent = displayedNum + '.'
}
Quindi, supponiamo che Mary continui il suo calcolo premendo un tasto operatore.
Quando un utente preme un tasto operatore
Se Mary preme un tasto operatore, l'operazione dovrebbe essere evidenziata in modo che Mary sappia che l'operazione è attiva.
Per fare ciò, possiamo aggiungere la classe is-depressed
al tasto dell'operatore.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
key.classList.add('is-depressed')
}
Una volta che Mary ha premuto un tasto operatore, premerà un altro tasto numerico.
Quando un utente preme un tasto numerico dopo un tasto operatore
Quando Mary preme di nuovo un tasto numerico, il display precedente dovrebbe essere sostituito con il nuovo numero. Il tasto operatore dovrebbe anche rilasciare il suo stato premuto.
Per rilasciare lo stato premuto, rimuoviamo la classe is-depressed
da tutti i tasti attraverso un ciclo forEach
:
keys.addEventListener('click', e => {
if (e.target.matches('button')) {
const key = e.target
// ...
// Remove .is-depressed class from all keys
Array.from(key.parentNode.children)
.forEach(k => k.classList.remove('is-depressed'))
}
})
Successivamente, vogliamo aggiornare il display col valore del tasto cliccato. Prima di farlo, abbiamo bisogno di un modo per sapere se il tasto precedente è un tasto operatore.
Un modo per farlo è attraverso un attributo personalizzato. Chiamiamo questo attributo personalizzato data-previous-key-type
.
const calculator = document.querySelector('.calculator')
// ...
keys.addEventListener('click', e => {
if (e.target.matches('button')) {
// ...
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
key.classList.add('is-depressed')
// Add custom attribute
calculator.dataset.previousKeyType = 'operator'
}
}
})
Se previousKeyType
è un operatore, vogliamo sostituire il numero visualizzato con il numero cliccato.
const previousKeyType = calculator.dataset.previousKeyType
if (!action) {
if (displayedNum === '0' || previousKeyType === 'operator') {
display.textContent = keyContent
} else {
display.textContent = displayedNum + keyContent
}
}
Quindi, supponiamo che Mary decida di completare il suo calcolo premendo il tasto uguale.
Quando un utente preme il tasto uguale
Quando Mary preme il tasto uguale, la calcolatrice dovrebbe calcolare un risultato che dipende da tre valori:
- Il primo numero inserito nella calcolatrice
- L' operatore
- Il secondo numero inserito nella calcolatrice
Dopo il calcolo, il risultato dovrebbe sostituire il valore visualizzato.
A questo punto, conosciamo solo il secondo numero , ovvero il numero attualmente visualizzato.
if (action === 'calculate') {
const secondValue = displayedNum
// ...
}
Per ottenere il primo numero , dobbiamo memorizzare il valore visualizzato della calcolatrice prima di ripulire il display. Un modo per salvare questo primo numero consiste nell'aggiungerlo a un attributo personalizzato quando si fa clic sul pulsante dell'operatore.
Per ottenere l'operatore, possiamo usare anche la stessa tecnica.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
// ...
calculator.dataset.firstValue = displayedNum
calculator.dataset.operator = action
}
Una volta che abbiamo i tre valori di cui abbiamo bisogno, possiamo eseguire un calcolo. Alla fine, vogliamo che il codice assomigli a questo:
if (action === 'calculate') {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
display.textContent = calculate(firstValue, operator, secondValue)
}
Ciò significa che dobbiamo creare una funzione calculate
. Dovrebbe avere tre parametri: il primo numero, l'operatore e il secondo numero.
const calculate = (n1, operator, n2) => {
// Perform calculation and return calculated value
}
Se l'operatore è add
, vogliamo sommare i valori. Se l'operatore è subtract
, vogliamo sottrarre i valori e così via.
const calculate = (n1, operator, n2) => {
let result = ''
if (operator === 'add') {
result = n1 + n2
} else if (operator === 'subtract') {
result = n1 - n2
} else if (operator === 'multiply') {
result = n1 * n2
} else if (operator === 'divide') {
result = n1 / n2
}
return result
}
Ricorda che firstValue
e secondValue
sono attualmente delle stringhe. Se addizioni due stringhe, le concatenerai ( 1 + 1 = 11
).
Quindi, prima di calcolare il risultato, vogliamo convertire le stringhe in numeri. Possiamo farlo con le due funzioni parseInt
e parseFloat
.
parseInt
converte una stringa in un intero .parseFloat
converte una stringa in un float (questo significa un numero con cifre decimali).
Per una calcolatrice, abbiamo bisogno di un float.
const calculate = (n1, operator, n2) => {
let result = ''
if (operator === 'add') {
result = parseFloat(n1) + parseFloat(n2)
} else if (operator === 'subtract') {
result = parseFloat(n1) - parseFloat(n2)
} else if (operator === 'multiply') {
result = parseFloat(n1) * parseFloat(n2)
} else if (operator === 'divide') {
result = parseFloat(n1) / parseFloat(n2)
}
return result
}
Questo è tutto per lo scenario standard!
Puoi ottenere il codice sorgente per lo scenario standard attraverso questo link (scorri verso il basso e inserisci il tuo indirizzo e-mail nella casella e invierò il codici sorgente direttamente alla tua casella di posta).
I casi limite
Lo scenario standard non è abbastanza. Per costruire una calcolatrice robusta, è necessario rendere la calcolatrice resiliente a schemi di input strani. Per farlo, devi immaginare un piantagrane che cerca di rompere la calcolatrice premendo i tasti nell'ordine sbagliato. Chiamiamo questo individuo Tim.
Tim può premere questi tasti in qualsiasi ordine:
- Un tasto numerico (0–9)
- Un tasto operatore (+, -, ×, ÷)
- Il tasto virgola
- Il tasto uguale
- Il tasto cancella
Cosa succede se Tim preme il tasto virgola
Se Tim preme il tasto virgola quando il display mostra già un punto decimale, non dovrebbe succedere nulla.
Qui possiamo verificare che il numero visualizzato contenga un .
con il metodo includes
.
includes
controlla le stringhe per una determinata corrispondenza. Se viene trovata una stringa, restituisce true
; in caso contrario, ritorna false
.
Nota : includes
fa distinzione tra maiuscole e minuscole.
// Example of how includes work.
const string = 'The hamburgers taste pretty good!'
const hasExclaimation = string.includes('!')
console.log(hasExclaimation) // true
Per verificare se la stringa ha già un punto, facciamo questo:
// Do nothing if string has a dot
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.'
}
Successivamente, se Tim preme il tasto virgola dopo aver premuto un tasto operatore, il display dovrebbe mostrare 0.
.
Qui dobbiamo sapere se il tasto precedente è un operatore. Possiamo dirlo controllando l'attributo personalizzato, data-previous-key-type
, che abbiamo impostato nel paragrafo precedente.
data-previous-key-type
non è ancora completo. Per identificare correttamente se previousKeyType
è un operatore, dobbiamo aggiornare previousKeyType
per ogni tasto cliccato.
if (!action) {
// ...
calculator.dataset.previousKey = 'number'
}
if (action === 'decimal') {
// ...
calculator.dataset.previousKey = 'decimal'
}
if (action === 'clear') {
// ...
calculator.dataset.previousKeyType = 'clear'
}
if (action === 'calculate') {
// ...
calculator.dataset.previousKeyType = 'calculate'
}
Una volta che abbiamo il corretto previousKeyType
, possiamo usarlo per verificare se il tasto precedente è un operatore.
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.'
} else if (previousKeyType === 'operator') {
display.textContent = '0.'
}
calculator.dataset.previousKeyType = 'decimal'
}
Cosa succede se Tim preme un tasto operatore
Se Tim preme prima un tasto operatore, il tasto operatore dovrebbe accendersi. (Abbiamo già trattato questo caso limite, ma come? Vedi se riesci a identificare cosa abbiamo fatto).
Secondo, non dovrebbe succedere nulla se Tim preme più volte lo stesso tasto operatore (Abbiamo già coperto anche questo caso limite).
Nota: se vuoi fornire una migliore UX, puoi mostrare il tasto operatore che viene cliccato ripetutamente con alcune modifiche CSS. Non l'abbiamo fatto qui, ma vedi se puoi farlo tu stesso come una sfida di codifica aggiuntiva.
Terzo, se Tim preme un altro tasto operatore dopo aver premuto il primo tasto operatore, il primo tasto operatore dovrebbe essere rilasciato. Quindi, il secondo tasto operatore dovrebbe essere premuto. (Abbiamo coperto anche questo caso limite, ma come?).
Quarto, se Tim clicca in sequenza un numero, un operatore, un numero e un altro operatore, il display dovrebbe essere aggiornato al valore calcolato.
Ciò significa che dobbiamo usare la funzione calculate
quando esistono firstValue
, operator
e secondValue
.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
// Nota: È sufficiente controllare per firstValue e operator perch{ secondValue esiste sempre
if (firstValue && operator) {
display.textContent = calculate(firstValue, operator, secondValue)
}
key.classList.add('is-depressed')
calculator.dataset.previousKeyType = 'operator'
calculator.dataset.firstValue = displayedNum
calculator.dataset.operator = action
}
Sebbene possiamo calcolare un valore quando si fa clic sul tasto operatore per la seconda volta, a questo punto abbiamo anche introdotto un bug: clic aggiuntivi sul tasto operatore calcolano un valore quando non dovrebbero.
Per evitare che la calcolatrice esegua un calcolo ai clic successivi sul tasto operatore, è necessario verificare se il previousKeyType
è un operatore. Se lo è, non eseguiamo il calcolo.
if (
firstValue &&
operator &&
previousKeyType !== 'operator'
) {
display.textContent = calculate(firstValue, operator, secondValue)
}
Quinto, dopo che il tasto operatore ha calcolato un numero, se Tim schiaccia un numero, seguito da un altro operatore, l'operatore dovrebbe continuare con il calcolo, in questo modo: 8 - 1 = 7
, 7 - 2 = 5
, 5 - 3 = 2
.
Al momento, la nostra calcolatrice non può eseguire calcoli consecutivi. Il secondo valore calcolato sarà errato. Ecco cosa abbiamo: 99 - 1 = 98
, 98 - 1 = 0
.
Il secondo valore è calcolato in modo errato, perché abbiamo inserito i valori sbagliati nella funzione calculate
. Esaminiamo alcune immagini per capire cosa fa il nostro codice.
Comprendere la nostra funzione di calcolo
Per prima cosa, supponiamo che un utente clicchi su un numero, 99. A questo punto, nella calcolatrice non è ancora registrato nulla.
In secondo luogo, supponiamo che l'utente faccia clic sull'operatore di sottrazione. Dopo aver fatto clic sull'operatore di sottrazione, impostiamo a 99 firstValue
. Impostiamo anche operator
a sottrazione.
Terzo, supponiamo che l'utente faccia clic su un secondo valore: questa volta è 1. A questo punto, il numero visualizzato viene aggiornato a 1, ma firstValue
, operator
e secondValue
rimangono invariati.
Quarto, l'utente fa nuovamente clic su sottrai. Subito dopo aver fatto clic su sottrai, prima di calcolare il risultato, impostiamo secondValue
come numero visualizzato.
Quinto, eseguiamo il calcolo con firstValue
a 99, operator
a sottrazione e secondValue
a 1. Il risultato è 98.
Una volta calcolato il risultato, impostiamo la visualizzazione sul risultato. Quindi, impostiamo operator
su sottrazione e firstValue
al numero visualizzato precedente.
Beh, è terribilmente sbagliato! Se vogliamo continuare con il calcolo, dobbiamo aggiornare firstValue
con il valore calcolato.
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (
firstValue &&
operator &&
previousKeyType !== 'operator'
) {
const calcValue = calculate(firstValue, operator, secondValue)
display.textContent = calcValue
// Aggiorna il valore calcolato come firstValue
calculator.dataset.firstValue = calcValue
} else {
// Se non ci sono calcoli, imposta displayedNum come firstValue
calculator.dataset.firstValue = displayedNum
}
key.classList.add('is-depressed')
calculator.dataset.previousKeyType = 'operator'
calculator.dataset.operator = action
Con questa correzione, i calcoli consecutivi eseguiti dai tasti operatori dovrebbero ora essere corretti.
Cosa succede se Tim preme il tasto uguale?
Innanzitutto, non dovrebbe succedere nulla se Tim preme il tasto uguale prima di qualsiasi tasto operatore.
Sappiamo che i tasti operatore non sono ancora stati cliccati se firstValue
non è impostato su un numero. Possiamo usare questa conoscenza per impedire al tasto uguale di eseguire il calcolo.
if (action === 'calculate') {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (firstValue) {
display.textContent = calculate(firstValue, operator, secondValue)
}
calculator.dataset.previousKeyType = 'calculate'
}
In secondo luogo, se Tim clicca un numero, seguito da un operatore, seguito da un uguale, la calcolatrice dovrebbe calcolare il risultato in modo tale che:
2 + =
—>2 + 2 = 4
2 - =
—>2 - 2 = 0
2 × =
—>2 × 2 = 4
2 ÷ =
—>2 ÷ 2 = 1
Abbiamo già preso in considerazione questo strano input. Riesci a capire perché? :)
Terzo, se Tim preme il tasto uguale dopo che un calcolo è stato completato, un altro calcolo dovrebbe essere eseguito di nuovo. Ecco come dovrebbe essere letto il calcolo:
- Tim preme i tasti 5–1
- Tim schiaccia uguale. Il valore calcolato è
5 - 1 = 4
- Tim schiaccia uguale. Il valore calcolato è
4 - 1 = 3
- Tim schiaccia uguale. Il valore calcolato è
3 - 1 = 2
- Tim schiaccia uguale. Il valore calcolato è
2 - 1 = 1
- Tim schiaccia uguale. Il valore calcolato è
1 - 1 = 0
Sfortunatamente, la nostra calcolatrice scompiglia questo calcolo. Ecco cosa mostra il nostro calcolatore:
- Tim preme il tasto 5–1
- Tim schiaccia uguale. Il valore calcolato è
4
- Tim schiaccia uguale. Il valore calcolato è
1
Correzione del calcolo
Per prima cosa, supponiamo che il nostro utente faccia clic su 5. A questo punto, nel calcolatore non è ancora registrato nulla.
In secondo luogo, supponiamo che l'utente faccia clic sull'operatore di sottrazione. Dopo aver fatto clic sull'operatore di sottrazione, impostiamo firstValue
su 5. Impostiamo anche operator
su sottrazione.
Terzo, l'utente fa clic su un secondo valore. Diciamo che è 1. A questo punto, il numero visualizzato viene aggiornato a 1, ma firstValue
, operator
e secondValue
rimangono invariati.
Quarto, l'utente fa clic sul tasto uguale. Subito dopo aver fatto clic su uguale, ma prima del calcolo, impostiamo secondValue
come displayedNum
Quinto, la calcolatrice calcola il risultato di 5 - 1
e restituisce 4
. Il risultato viene aggiornato sul display. firstValue
e operator
vengono riportati al calcolo successivo poiché non li abbiamo aggiornati.
Sesto, quando l'utente schiaccia di nuovo uguale, impostiamo secondValue
con displayedNum
priema del calcolo.
Puoi dire cosa c'è che non va qui.
Invece di secondValue
, vogliamo impostare firstValue
con il numero visualizzato.
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
}
display.textContent = calculate(firstValue, operator, secondValue)
}
calculator.dataset.previousKeyType = 'calculate'
}
Vogliamo anche portare avanti il valore precedente di secondValue
nel nuovo calcolo. Per mantenere secondValue
fino al calcolo successivo, dobbiamo memorizzarlo in un altro attributo personalizzato. Chiamiamo questo attributo personalizzato modValue
(sta per valore modificatore).
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
}
display.textContent = calculate(firstValue, operator, secondValue)
}
// Assegna l'attributo modValue
calculator.dataset.modValue = secondValue
calculator.dataset.previousKeyType = 'calculate'
}
Se previousKeyType
è calculate
, sappiamo che possiamo usare calculator.dataset.modValue
come secondValue
. Una volta che lo sappiamo, possiamo eseguire il calcolo.
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
secondValue = calculator.dataset.modValue
}
display.textContent = calculate(firstValue, operator, secondValue)
}
Con ciò, abbiamo il calcolo corretto quando si fa clic consecutivamente sul tasto uguale.
Torna al tasto di uguale
Quarto, se Tim preme un tasto decimale o un tasto numerico dopo il tasto della calcolatrice, il display dovrebbe essere sostituito con 0.
o con il nuovo numero.
Qui, invece di controllare solo se previousKeyType
è operator
, dobbiamo anche verificare se è calculate
.
if (!action) {
if (
displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
display.textContent = keyContent
} else {
display.textContent = displayedNum + keyContent
}
calculator.dataset.previousKeyType = 'number'
}
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.'
} else if (
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
display.textContent = '0.'
}
calculator.dataset.previousKeyType = 'decimal'
}
Quinto, se Tim preme un tasto operatore subito dopo il tasto uguale, la calcolatrice non dovrebbe calcolare.
Per fare ciò, controlliamo se previousKeyType
è calculate
prima di eseguire calcoli con i tasti operatore.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
// ...
if (
firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
) {
const calcValue = calculate(firstValue, operator, secondValue)
display.textContent = calcValue
calculator.dataset.firstValue = calcValue
} else {
calculator.dataset.firstValue = displayedNum
}
// ...
}
Il tasto cancella ha due usi:
- Cancella tutto (indicato da
AC
) cancella tutto e riporta la calcolatrice allo stato iniziale. - Cancella numero (indicata da
CE
) cancella il numero corrente. Mantiene in memoria i numeri precedenti.
Quando la calcolatrice è nel suo stato predefinito, AC
dovrebbe essere visualizzato.
Innanzitutto, se Tim preme un tasto (qualsiasi tasto tranne cancella), AC
dovrebbe essere cambiato in CE
.
Lo facciamo controllando se data-action
è clear
. Se non è clear
, cerchiamo il pulsante cancella e gli cambiamo il textContent
.
if (action !== 'clear') {
const clearButton = calculator.querySelector('[data-action=clear]')
clearButton.textContent = 'CE'
}
Secondo, se Tim preme CE
, il display dovrebbe visualizzare 0. Allo stesso tempo, CE
dovrebbe essere ripristinato in AC
in modo che Tim possa riportare la calcolatrice al suo stato iniziale.**
if (action === 'clear') {
display.textContent = 0
key.textContent = 'AC'
calculator.dataset.previousKeyType = 'clear'
}
Terzo, se Tim preme AC
, ripristina la calcolatrice allo stato iniziale.
Per riportare la calcolatrice al suo stato iniziale, dobbiamo cancellare tutti gli attributi personalizzati che abbiamo impostato.
if (action === 'clear') {
if (key.textContent === 'AC') {
calculator.dataset.firstValue = ''
calculator.dataset.modValue = ''
calculator.dataset.operator = ''
calculator.dataset.previousKeyType = ''
} else {
key.textContent = 'AC'
}
display.textContent = 0
calculator.dataset.previousKeyType = 'clear'
}
Questo è tutto, per la parte dei casi limite, comunque!
Puoi prendere il codice sorgente per la parte dei casi limite tramite questo link (scorri verso il basso e inserisci il tuo indirizzo e-mail nella casella e invierò i codici sorgente direttamente alla tua casella di posta).
A questo punto, il codice che abbiamo creato insieme è piuttosto confuso. Probabilmente ti perderai se provi a leggere il codice da solo. Rivediamolo per renderlo più pulito.
Refactoring del codice
Quando si esegue il refactoring, si inizia spesso con i miglioramenti più evidenti. In questo caso, iniziamo con calculate
.
Prima di continuare, assicurati di conoscere queste pratiche/funzionalità di JavaScript. Li useremo nel refactoring.
Con questo, iniziamo!
Refactoring della funzione di calcolo
Ecco cosa abbiamo finora.
const calculate = (n1, operator, n2) => {
let result = ''
if (operator === 'add') {
result = parseFloat(n1) + parseFloat(n2)
} else if (operator === 'subtract') {
result = parseFloat(n1) - parseFloat(n2)
} else if (operator === 'multiply') {
result = parseFloat(n1) * parseFloat(n2)
} else if (operator === 'divide') {
result = parseFloat(n1) / parseFloat(n2)
}
return result
}
Hai imparato che dovremmo ridurre il più possibile le riassegnazioni. Qui, possiamo rimuovere le assegnazioni se restituiamo il risultato del calcolo all'interno delle istruzioni if
e else if
:
const calculate = (n1, operator, n2) => {
if (operator === 'add') {
return parseFloat(n1) + parseFloat(n2)
} else if (operator === 'subtract') {
return parseFloat(n1) - parseFloat(n2)
} else if (operator === 'multiply') {
return parseFloat(n1) * parseFloat(n2)
} else if (operator === 'divide') {
return parseFloat(n1) / parseFloat(n2)
}
}
Dal momento che restituiamo tutti i valori, possiamo utilizzare i ritorni anticipati . Se lo facciamo, non c'è bisogno di alcuna condizione else if
.
const calculate = (n1, operator, n2) => {
if (operator === 'add') {
return parseFloat(n1) + parseFloat(n2)
}
if (operator === 'subtract') {
return parseFloat(n1) - parseFloat(n2)
}
if (operator === 'multiply') {
return parseFloat(n1) * parseFloat(n2)
}
if (operator === 'divide') {
return parseFloat(n1) / parseFloat(n2)
}
}
E poiché abbiamo una sola dichiarazione per ogni condizione if
, possiamo rimuovere le parentesi. (Nota: alcuni sviluppatori preferiscono mantenere le parentesi graffe, però). Ecco come sarebbe il codice:
const calculate = (n1, operator, n2) => {
if (operator === 'add') return parseFloat(n1) + parseFloat(n2)
if (operator === 'subtract') return parseFloat(n1) - parseFloat(n2)
if (operator === 'multiply') return parseFloat(n1) * parseFloat(n2)
if (operator === 'divide') return parseFloat(n1) / parseFloat(n2)
}
Infine, abbiamo chiamato parseFloat
otto volte nella funzione. Possiamo semplificarla creando due variabili per contenere valori float:
const calculate = (n1, operator, n2) => {
const firstNum = parseFloat(n1)
const secondNum = parseFloat(n2)
if (operator === 'add') return firstNum + secondNum
if (operator === 'subtract') return firstNum - secondNum
if (operator === 'multiply') return firstNum * secondNum
if (operator === 'divide') return firstNum / secondNum
}
Abbiamo finito calculate
ora. Non pensi che sia più facile da leggere rispetto a prima?
Refactoring dell'event listener
Il codice che abbiamo creato per l'event listener è enorme. Ecco cosa abbiamo al momento:
keys.addEventListener('click', e => {
if (e.target.matches('button')) {
if (!action) { /* ... */ }
if (action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide') {
/* ... */
}
if (action === 'clear') { /* ... */ }
if (action !== 'clear') { /* ... */ }
if (action === 'calculate') { /* ... */ }
}
})
Come si inizia il refactoring di questo pezzo di codice? Se non conosci le migliori pratiche di programmazione, potresti essere tentato di rifattorizzare suddividendo ogni tipo di azione in una funzione più piccola:
// Don't do this!
const handleNumberKeys = (/* ... */) => {/* ... */}
const handleOperatorKeys = (/* ... */) => {/* ... */}
const handleDecimalKey = (/* ... */) => {/* ... */}
const handleClearKey = (/* ... */) => {/* ... */}
const handleCalculateKey = (/* ... */) => {/* ... */}
Non farlo. Non aiuta, perché stai semplicemente dividendo blocchi di codice. Quando lo fai, la funzione diventa più difficile da leggere.
Un modo migliore è dividere il codice in funzioni pure e impure. Se lo fai, otterrai un codice simile a questo:
keys.addEventListener('click', e => {
// Pure function
const resultString = createResultString(/* ... */)
// Impure stuff
display.textContent = resultString
updateCalculatorState(/* ... */)
})
Qui createResultString
è una funzione pura che restituisce ciò che deve essere visualizzato sulla calcolatrice. updateCalculatorState
è una funzione impura che modifica l'aspetto visivo e gli attributi personalizzati della calcolatrice.
Creazione di createResultString
Come accennato in precedenza, createResultString
dovrebbe restituire il valore che deve essere visualizzato sulla calcolatrice.
Puoi ottenere questi valori attraverso le parti del codice dove dice display.textContent = 'some value'
.
display.textContent = 'some value'
Invece di display.textContent = 'some value'
, vogliamo restituire ogni valore in modo da poterlo utilizzare in seguito.
// replace the above with this
return 'some value'
Esaminiamolo insieme, passo passo, partendo dai tasti numerici.
Creazione della stringa del risultato per i tasti numerici
Ecco il codice che abbiamo per i tasti numerici:
if (!action) {
if (
displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
display.textContent = keyContent
} else {
display.textContent = displayedNum + keyContent
}
calculator.dataset.previousKeyType = 'number'
}
Il primo passo è copiare le parti che dicono display.textContent = 'some value'
in createResultString
. Quando lo fai, assicurati di cambiare display.textContent =
in return
.
const createResultString = () => {
if (!action) {
if (
displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
return keyContent
} else {
return displayedNum + keyContent
}
}
}
Successivamente, possiamo convertire l'istruzione if/else
in un operatore ternario:
const createResultString = () => {
if (action!) {
return displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
? keyContent
: displayedNum + keyContent
}
}
Quando esegui il refactoring, ricorda di annotare un elenco di variabili di cui hai bisogno. Torneremo più tardi sull'elenco.
const createResultString = () => {
// Variabili necessarie:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
if (action!) {
return displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
? keyContent
: displayedNum + keyContent
}
}
Creazione della stringa di risultato per il tasto virgola
Ecco il codice che abbiamo per il tasto virgola:
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.'
} else if (
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
display.textContent = '0.'
}
calculator.dataset.previousKeyType = 'decimal'
}
Come prima, vogliamo spostare tutto ciò che cambia display.textContent
in createResultString
.
const createResultString = () => {
// ...
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
return = displayedNum + '.'
} else if (previousKeyType === 'operator' || previousKeyType === 'calculate') {
return = '0.'
}
}
}
Dal momento che vogliamo restituire tutti i valori, possiamo convertire le istruzioni else if
in return anticipati.
const createResultString = () => {
// ...
if (action === 'decimal') {
if (!displayedNum.includes('.')) return displayedNum + '.'
if (previousKeyType === 'operator' || previousKeyType === 'calculate') return '0.'
}
}
Un errore comune qui è dimenticare di restituire il numero attualmente visualizzato quando nessuna delle due condizioni è soddisfatta. Ne abbiamo bisogno perché sostituiremo il display.textContent
con il valore restituito da createResultString
. Se ce lo siamo perso, createResultString
tornerà undefined
, che non è ciò che desideriamo.
const createResultString = () => {
// ...
if (action === 'decimal') {
if (!displayedNum.includes('.')) return displayedNum + '.'
if (previousKeyType === 'operator' || previousKeyType === 'calculate') return '0.'
return displayedNum
}
}
Come sempre, prendi nota delle variabili richieste. A questo punto, le variabili necessarie rimangono le stesse di prima:
const createResultString = () => {
// Variabili necessarie:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
}
Creazione della stringa di risultato per i tasti operatore
Ecco il codice che abbiamo scritto per i tasti operatore.
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (
firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
) {
const calcValue = calculate(firstValue, operator, secondValue)
display.textContent = calcValue
calculator.dataset.firstValue = calcValue
} else {
calculator.dataset.firstValue = displayedNum
}
key.classList.add('is-depressed')
calculator.dataset.previousKeyType = 'operator'
calculator.dataset.operator = action
}
Ormai conosci il gioco: vogliamo spostare tutto ciò che cambia display.textContent
in createResultString
. Ecco cosa deve essere spostato:
const createResultString = () => {
// ...
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (
firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
) {
return calculate(firstValue, operator, secondValue)
}
}
}
Ricorda, createResultString
deve restituire il valore da visualizzare sulla calcolatrice. Se la condizione if
non è soddisfatta, vogliamo comunque restituire il numero visualizzato.
const createResultString = () => {
// ...
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
if (
firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
) {
return calculate(firstValue, operator, secondValue)
} else {
return displayedNum
}
}
}
Possiamo quindi convertire l'istruzione if/else
in un operatore ternario:
const createResultString = () => {
// ...
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const secondValue = displayedNum
return firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
? calculate(firstValue, operator, secondValue)
: displayedNum
}
}
Se guardi da vicino, ti renderai conto che non è necessario memorizzare una variabile secondValue
. Possiamo usare displayedNum
direttamente nella funzione calculate
.
const createResultString = () => {
// ...
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
return firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
? calculate(firstValue, operator, displayedNum)
: displayedNum
}
}
Infine, prendiamo nota delle variabili e delle proprietà necessarie. Questa volta, abbiamo bisogno di calculator.dataset.firstValue
e calculator.dataset.operator
.
const createResultString = () => {
// Variabili & proprietà necessarie:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
// 5. calculator.dataset.firstValue
// 6. calculator.dataset.operator
}
Creare la stringa del risultato per il tasto cancella
Abbiamo scritto il codice seguente per gestire il tasto clear
.
if (action === 'clear') {
if (key.textContent === 'AC') {
calculator.dataset.firstValue = ''
calculator.dataset.modValue = ''
calculator.dataset.operator = ''
calculator.dataset.previousKeyType = ''
} else {
key.textContent = 'AC'
}
display.textContent = 0
calculator.dataset.previousKeyType = 'clear'
}
Come sopra, vuoi spostare tutto ciò che cambia display.textContent
in createResultString
.
const createResultString = () => {
// ...
if (action === 'clear') return 0
}
Creare la stringa del risultato per il tasto uguale
Ecco il codice che abbiamo scritto per il tasto uguale:
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
let secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
secondValue = calculator.dataset.modValue
}
display.textContent = calculate(firstValue, operator, secondValue)
}
calculator.dataset.modValue = secondValue
calculator.dataset.previousKeyType = 'calculate'
}
Come sopra, vogliamo copiare tutto ciò che cambia display.textContent
in createResultString
. Ecco cosa deve essere copiato:
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
let secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
secondValue = calculator.dataset.modValue
}
display.textContent = calculate(firstValue, operator, secondValue)
}
}
Quando copi il codice in createResultString
, assicurati di restituire i valori per ogni possibile scenario:
const createResultString = () => {
// ...
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
let secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
secondValue = calculator.dataset.modValue
}
return calculate(firstValue, operator, secondValue)
} else {
return displayedNum
}
}
}
Successivamente, vogliamo ridurre le riassegnazioni. Possiamo farlo passando i valori corretti a calculate
attraverso un operatore ternario.
const createResultString = () => {
// ...
if (action === 'calculate') {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const modValue = calculator.dataset.modValue
if (firstValue) {
return previousKeyType === 'calculate'
? calculate(displayedNum, operator, modValue)
: calculate(firstValue, operator, displayedNum)
} else {
return displayedNum
}
}
}
Puoi semplificare ulteriormente il codice sopra con un altro operatore ternario se ti senti a tuo agio con esso:
const createResultString = () => {
// ...
if (action === 'calculate') {
const firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
const modValue = calculator.dataset.modValue
return firstValue
? previousKeyType === 'calculate'
? calculate(displayedNum, operator, modValue)
: calculate(firstValue, operator, displayedNum)
: displayedNum
}
}
A questo punto, vogliamo prendere nuovamente nota delle proprietà e delle variabili necessarie:
const createResultString = () => {
// Variabili e proprietà necessarie:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
// 5. calculator.dataset.firstValue
// 6. calculator.dataset.operator
// 7. calculator.dataset.modValue
}
Passando alle variabili necessarie
Abbiamo bisogno di sette proprietà/variabili in createResultString
:
keyContent
displayedNum
previousKeyType
action
firstValue
modValue
operator
Possiamo ottenere keyContent
e action
da key
. Possiamo anche ottenere firstValue
, modValue
, operator
e previousKeyType
da calculator.dataset
.
Ciò significa che la funzione createResultString
necessita di tre variabili: key
, displayedNum
e calculator.dataset
. Poiché calculator.dataset
rappresenta lo stato della calcolatrice, utilizziamo invece una variabile chiamata state
.
const createResultString = (key, displayedNum, state) => {
const keyContent = key.textContent
const action = key.dataset.action
const firstValue = state.firstValue
const modValue = state.modValue
const operator = state.operator
const previousKeyType = state.previousKeyType
// ... Refactor as necessary
}
// Using createResultString
keys.addEventListener('click', e => {
if (e.target.matches('button')) return
const displayedNum = display.textContent
const resultString = createResultString(e.target, displayedNum, calculator.dataset)
// ...
})
Sentiti libero di destrutturare le variabili se lo desideri:
const createResultString = (key, displayedNum, state) => {
const keyContent = key.textContent
const { action } = key.dataset
const {
firstValue,
modValue,
operator,
previousKeyType
} = state
// ...
}
Compattezza all'interno delle istruzioni if
In createResultString
, abbiamo utilizzato le seguenti condizioni per verificare il tipo di tasti su cui è stato fatto clic:
// If key is number
if (!action) { /* ... */ }
// If key is decimal
if (action === 'decimal') { /* ... */ }
// If key is operator
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) { /* ... */}
// If key is clear
if (action === 'clear') { /* ... */ }
// If key is calculate
if (action === 'calculate') { /* ... */ }
Non sono compatte, quindi sono difficili da leggere. Se possibile, vogliamo renderle compatte in modo da poter scrivere qualcosa del genere:
if (keyType === 'number') { /* ... */ }
if (keyType === 'decimal') { /* ... */ }
if (keyType === 'operator') { /* ... */}
if (keyType === 'clear') { /* ... */ }
if (keyType === 'calculate') { /* ... */ }
Per fare ciò, possiamo creare una funzione chiamata getKeyType
. Questa funzione dovrebbe restituire il tipo di chiave su cui è stato fatto clic.
const getKeyType = (key) => {
const { action } = key.dataset
if (!action) return 'number'
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) return 'operator'
// For everything else, return the action
return action
}
Ecco come useresti la funzione:
const createResultString = (key, displayedNum, state) => {
const keyType = getKeyType(key)
if (keyType === 'number') { /* ... */ }
if (keyType === 'decimal') { /* ... */ }
if (keyType === 'operator') { /* ... */}
if (keyType === 'clear') { /* ... */ }
if (keyType === 'calculate') { /* ... */ }
}
Abbiamo finito createResultString
. Passiamo a updateCalculatorState
.
Creare updateCalculatorState
updateCalculatorState
è una funzione che modifica l'aspetto visivo e gli attributi personalizzati della calcolatrice.
Come con createResultString
, dobbiamo controllare il tipo di tasto su cui è stato fatto clic. Qui possiamo riutilizzare getKeyType
.
const updateCalculatorState = (key) => {
const keyType = getKeyType(key)
if (keyType === 'number') { /* ... */ }
if (keyType === 'decimal') { /* ... */ }
if (keyType === 'operator') { /* ... */}
if (keyType === 'clear') { /* ... */ }
if (keyType === 'calculate') { /* ... */ }
}
Se guardi il codice residuo, potresti notare che cambiamo data-previous-key-type
per ogni tipo di chiave. Ecco come appare il codice:
const updateCalculatorState = (key, calculator) => {
const keyType = getKeyType(key)
if (!action) {
// ...
calculator.dataset.previousKeyType = 'number'
}
if (action === 'decimal') {
// ...
calculator.dataset.previousKeyType = 'decimal'
}
if (
action === 'add' ||
action === 'subtract' ||
action === 'multiply' ||
action === 'divide'
) {
// ...
calculator.dataset.previousKeyType = 'operator'
}
if (action === 'clear') {
// ...
calculator.dataset.previousKeyType = 'clear'
}
if (action === 'calculate') {
calculator.dataset.previousKeyType = 'calculate'
}
}
Questo è ridondante perché conosciamo già il tipo di chiave con getKeyType
. Possiamo rifattorizzare quanto sopra con:
const updateCalculatorState = (key, calculator) => {
const keyType = getKeyType(key)
calculator.dataset.previousKeyType = keyType
if (keyType === 'number') { /* ... */ }
if (keyType === 'decimal') { /* ... */ }
if (keyType === 'operator') { /* ... */}
if (keyType === 'clear') { /* ... */ }
if (keyType === 'calculate') { /* ... */ }
}
Realizzare updateCalculatorState
per i taasti operatore
Visivamente, dobbiamo assicurarci che tutti i tasti rilascino il loro stato premuto. Qui possiamo copiare e incollare il codice che avevamo prima:
const updateCalculatorState = (key, calculator) => {
const keyType = getKeyType(key)
calculator.dataset.previousKeyType = keyType
Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed'))
}
Ecco cosa resta da ciò che abbiamo scritto per i tasti operatore, dopo aver spostato i pezzi relativi a display.textContent
in createResultString
.
if (keyType === 'operator') {
if (firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
) {
calculator.dataset.firstValue = calculatedValue
} else {
calculator.dataset.firstValue = displayedNum
}
key.classList.add('is-depressed')
calculator.dataset.operator = key.dataset.action
}
Potresti notare che possiamo abbreviare il codice con un operatore ternario:
if (keyType === 'operator') {
key.classList.add('is-depressed')
calculator.dataset.operator = key.dataset.action
calculator.dataset.firstValue = firstValue &&
operator &&
previousKeyType !== 'operator' &&
previousKeyType !== 'calculate'
? calculatedValue
: displayedNum
}
Come prima, prendi nota delle variabili e delle proprietà di cui hai bisogno. Qui, abbiamo bisogno di calculatedValue
e di displayedNum
.
const updateCalculatorState = (key, calculator) => {
// Variabili e prorpietà necessarie
// 1. key
// 2. calculator
// 3. calculatedValue
// 4. displayedNum
}
Realizzazione di updateCalculatorState
per il tasto cancella
Ecco il codice rimanente per il tasto cancella:
if (action === 'clear') {
if (key.textContent === 'AC') {
calculator.dataset.firstValue = ''
calculator.dataset.modValue = ''
calculator.dataset.operator = ''
calculator.dataset.previousKeyType = ''
} else {
key.textContent = 'AC'
}
}
if (action !== 'clear') {
const clearButton = calculator.querySelector('[data-action=clear]')
clearButton.textContent = 'CE'
}
Non c'è molto su cui fare refactoring qui. Sentiti libero di copiare/incollare tutto in updateCalculatorState
.
Creare updateCalculatorState
per il tasto uguale
Ecco il codice che abbiamo scritto per il tasto uguale:
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue
const operator = calculator.dataset.operator
let secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum
secondValue = calculator.dataset.modValue
}
display.textContent = calculate(firstValue, operator, secondValue)
}
calculator.dataset.modValue = secondValue
calculator.dataset.previousKeyType = 'calculate'
}
Ecco cosa ci resta se rimuoviamo tutto ciò che riguarda display.textContent
.
if (action === 'calculate') {
let secondValue = displayedNum
if (firstValue) {
if (previousKeyType === 'calculate') {
secondValue = calculator.dataset.modValue
}
}
calculator.dataset.modValue = secondValue
}
Possiamo rifattorizzare questo in quanto segue:
if (keyType === 'calculate') {
calculator.dataset.modValue = firstValue && previousKeyType === 'calculate'
? modValue
: displayedNum
}
Come sempre, prendi nota delle proprietà e delle variabili utilizzate:
const updateCalculatorState = (key, calculator) => {
// Variabili e proprietà necessarie
// 1. key
// 2. calculator
// 3. calculatedValue
// 4. displayedNum
// 5. modValue
}
Passando alle variabili necessarie
Sappiamo che abbiamo bisogno di cinque variabili/proprietà per updateCalculatorState
:
key
calculator
calculatedValue
displayedNum
modValue
Poiché modValue
può essere recuperata da calculator.dataset
, abbiamo solo bisogno di passare quattro valori:
const updateCalculatorState = (key, calculator, calculatedValue, displayedNum) => {
// ...
}
keys.addEventListener('click', e => {
if (e.target.matches('button')) return
const key = e.target
const displayedNum = display.textContent
const resultString = createResultString(key, displayedNum, calculator.dataset)
display.textContent = resultString
// Pass in necessary values
updateCalculatorState(key, calculator, resultString, displayedNum)
})
Refactoring nuovamente di updateCalculatorState
Abbiamo cambiato tre tipi di valori in updateCalculatorState
:
calculator.dataset
- La classe per la pressione/rilascio dei tasti operatore
- Il testo
AC
vsCE
Se vuoi renderlo più pulito, puoi dividere (2) e (3) in un'altra funzione - updateVisualState
. Ecco come può essere updateVisualState
:
const updateVisualState = (key, calculator) => {
const keyType = getKeyType(key)
Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed'))
if (keyType === 'operator') key.classList.add('is-depressed')
if (keyType === 'clear' && key.textContent !== 'AC') {
key.textContent = 'AC'
}
if (keyType !== 'clear') {
const clearButton = calculator.querySelector('[data-action=clear]')
clearButton.textContent = 'CE'
}
}
Ricapitolando
Il codice diventa molto più pulito dopo il refactor. Se guardi nell'event listener, saprai cosa fa ciascuna funzione. Ecco come appare l'event listener alla fine:
keys.addEventListener('click', e => {
if (e.target.matches('button')) return
const key = e.target
const displayedNum = display.textContent
// Pure functions
const resultString = createResultString(key, displayedNum, calculator.dataset)
// Update states
display.textContent = resultString
updateCalculatorState(key, calculator, resultString, displayedNum)
updateVisualState(key, calculator)
})
Puoi prendere il codice sorgente per la parte di refactoring tramite questo link (scorri verso il basso e inserisci il tuo indirizzo email nella casella e invierò i codici sorgente direttamente alla tua casella di posta).
Spero che questo articolo ti sia piaciuto. Se è così, potrebbe interessarti Learn JavaScript , un corso in cui ti mostro come costruire 20 componenti, passo dopo passo, come abbiamo costruito oggi questa calcolatrice.
Nota: possiamo migliorare ulteriormente la calcolatrice aggiungendo il supporto della tastiera e funzionalità di accessibilità come le Live Regions. Vuoi scoprire come? Vai a dare un'occhiata a Learn JavaScript :)