Это эпическая статья, в которой вы узнаете, как построить калькулятор с нуля. Мы сосредоточимся на JavaScript, который вам нужно написать: как подумать о создании калькулятора, как написать код и, в конечном итоге, как очистить ваш код.
К концу статьи вы должны получить калькулятор, который работает точно так же, как калькулятор iPhone (без функций +/-
и процентов).
Предпосылки
Прежде чем пытаться продолжить урок, убедитесь, что вы хорошо владеете JavaScript. Как минимум, вам нужно знать следующее:
- Если / еще утверждения
- Для петель
- Функции JavaScript
- Стрелочные функции
&&
и||
операторы- Как изменить текст с помощью свойства
textContent
- Как добавить слушателей событий с шаблоном делегирования событий
Прежде чем вы начнете
Я настоятельно рекомендую вам попробовать собрать калькулятор самостоятельно, прежде чем продолжить урок. Это хорошая практика, потому что вы научитесь думать как разработчик.
Вернитесь к этому уроку после того, как вы попробовали в течение одного часа (неважно, удастся ли вы или нет. Когда вы пытаетесь, вы думаете, и это поможет вам усвоить урок в два раза быстрее).
Итак, давайте начнем с понимания того, как работает калькулятор.
Создание калькулятора
Во-первых, мы хотим построить калькулятор.
Калькулятор состоит из двух частей: дисплея и клавиш.
<div class=”calculator”> <div class=”calculator__display”>0</div> <div class=”calculator__keys”> … </div> </div>
Мы можем использовать CSS Grid для создания ключей, поскольку они расположены в виде сетки. Это уже было сделано за вас в стартовом файле. Вы можете найти стартовый файл на этой ручке.
.calculator__keys {
display: grid;
/* other necessary CSS */
}
Чтобы помочь нам идентифицировать операторы, десятичные, четкие и равные ключи, мы собираемся предоставить атрибут data-action, который описывает, что они делают.
<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>
Прослушивание нажатия клавиш
Когда человек достает калькулятор, может произойти пять вещей. Они могут поразить:
- цифровая клавиша (0–9)
- клавиша оператора (+, -, ×, ÷)
- десятичный ключ
- ключ равенства
- чистый ключ
Первые шаги к созданию этого калькулятора - это возможность (1) прослушивать все нажатия клавиш и (2) определять тип нажатой клавиши. В этом случае мы можем использовать шаблон делегирования событий для прослушивания, поскольку все ключи являются дочерними для .calculator__keys
.
const calculator = document.querySelector(‘.calculator’) const keys = calculator.querySelector(‘.calculator__keys’) keys.addEventListener(‘click’, e => { if (e.target.matches(‘button’)) { // Do something } })
Затем мы можем использовать атрибут data-action
, чтобы определить тип нажатой клавиши.
const key = e.target const action = key.dataset.action
Если ключ не имеет атрибута data-action
, это должен быть цифровой ключ.
if (!action) { console.log('number key!') }
Если ключ имеет data-action
, который равен либо add
, subtract
, multiply
или divide
, мы знаем, что ключ является оператором.
if ( action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide' ) { console.log('operator key!') }
Если data-action
ключа - decimal
, мы знаем, что пользователь нажал на десятичный ключ.
Следуя тому же процессу мышления, если ключ data-action
равен clear
, мы знаем, что пользователь нажал кнопку очистки (ту, которая говорит AC). Если data-action
ключа - calculate
, мы знаем, что пользователь нажал кнопку равенства.
if (action === 'decimal') { console.log('decimal key!') } if (action === 'clear') { console.log('clear key!') } if (action === 'calculate') { console.log('equal key!') }
На этом этапе вы должны получить console.log
ответ от каждой клавиши калькулятора.
Строим счастливый путь
Давайте посмотрим, что сделает средний человек, взяв в руки калькулятор. Это то, «что сделал бы средний человек», называется счастливым путем.
Назовем обычного человека Мэри.
Когда Мэри берет калькулятор, она может нажать любую из этих клавиш:
- цифровая клавиша (0–9)
- клавиша оператора (+, -, ×, ÷)
- десятичный ключ
- равный ключ
- чистый ключ
Одновременное рассмотрение пяти типов ключей может оказаться непосильной задачей, поэтому давайте рассмотрим это шаг за шагом.
Когда пользователь нажимает цифровую клавишу
На этом этапе, если калькулятор показывает 0 (число по умолчанию), целевое число должно заменить ноль.
Если калькулятор показывает ненулевое число, целевое число должно быть добавлено к отображаемому числу.
Здесь нам нужно знать две вещи:
- Номер нажатой клавиши
- Текущее отображаемое число
Мы можем получить эти два значения через свойство textContent
нажатой клавиши и .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 // ... } })
Если калькулятор показывает 0, мы хотим заменить дисплей калькулятора нажатой клавишей. Мы можем сделать это, заменив свойство textContent дисплея.
if (!action) { if (displayedNum === '0') { display.textContent = keyContent } }
Если калькулятор показывает ненулевое число, мы хотим добавить нажатую клавишу к отображаемому числу. Чтобы добавить число, мы объединяем строку.
if (!action) { if (displayedNum === '0') { display.textContent = keyContent } else { display.textContent = displayedNum + keyContent } }
На этом этапе Мэри может нажать любую из этих клавиш:
- Десятичный ключ
- Ключ оператора
Допустим, Мэри нажимает десятичную клавишу.
Когда пользователь нажимает десятичную клавишу
Когда Мэри нажимает десятичную клавишу, на дисплее должно появиться десятичное число. Если Мэри наберет любое число после нажатия десятичной клавиши, это число также должно появиться на дисплее.
Чтобы создать этот эффект, мы можем объединить .
с отображаемым числом.
if (action === 'decimal') { display.textContent = displayedNum + '.' }
Далее, допустим, Мэри продолжает свои вычисления, нажимая клавишу оператора.
Когда пользователь нажимает клавишу оператора
Если Мэри нажимает клавишу оператора, оператор должен быть выделен, чтобы Мэри знала, что оператор активен.
Для этого мы можем добавить класс is-depressed
к клавише оператора.
if ( action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide' ) { key.classList.add('is-depressed') }
Как только Мэри нажмет клавишу оператора, она нажмет еще одну цифровую клавишу.
Когда пользователь нажимает цифровую клавишу после клавиши оператора
Когда Мэри снова нажимает цифровую клавишу, предыдущий дисплей должен быть заменен новым номером. Клавиша оператора также должна выйти из своего нажатого состояния.
Чтобы освободить нажатое состояние, мы удаляем класс is-depressed
со всех клавиш через цикл 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')) } })
Затем мы хотим обновить отображение нажатой клавиши. Прежде чем мы это сделаем, нам нужен способ узнать, является ли предыдущий ключ клавишей оператора.
Один из способов сделать это - использовать настраиваемый атрибут. Назовем этот настраиваемый атрибут 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' } } })
Если previousKeyType
является оператором, мы хотим заменить отображаемое число на число, по которому щелкнули.
const previousKeyType = calculator.dataset.previousKeyType if (!action) { if (displayedNum === '0' || previousKeyType === 'operator') { display.textContent = keyContent } else { display.textContent = displayedNum + keyContent } }
Далее, допустим, Мэри решает завершить вычисление нажатием клавиши равенства.
Когда пользователь нажимает клавишу равенства
Когда Мэри нажимает клавишу равенства, калькулятор должен вычислить результат, который зависит от трех значений:
- первое число, введенное в калькулятор
- Оператор
- второе число, введенное в калькулятор.
После расчета результат должен заменить отображаемое значение.
На данный момент нам известно только второе число, то есть текущее отображаемое число.
if (action === 'calculate') { const secondValue = displayedNum // ... }
Чтобы получить первое число, нам нужно сохранить отображаемое значение калькулятора, прежде чем мы его очистим. Один из способов сохранить это первое число - добавить его в настраиваемый атрибут при нажатии кнопки оператора.
Чтобы получить оператор, мы также можем использовать ту же технику.
if ( action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide' ) { // ... calculator.dataset.firstValue = displayedNum calculator.dataset.operator = action }
Когда у нас есть три нужных нам значения, мы можем выполнить расчет. В конце концов, мы хотим, чтобы код выглядел примерно так:
if (action === 'calculate') { const firstValue = calculator.dataset.firstValue const operator = calculator.dataset.operator const secondValue = displayedNum display.textContent = calculate(firstValue, operator, secondValue) }
Это означает, что нам нужно создать calculate
функцию. Он должен принимать три параметра: первое число, оператор и второе число.
const calculate = (n1, operator, n2) => { // Perform calculation and return calculated value }
Если оператор add
, мы хотим сложить значения. Если оператор subtract
, мы хотим вычесть значения и так далее.
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 }
Помните, что firstValue
и secondValue
на этом этапе являются строками. Если вы сложите строки вместе, вы объедините их (1 + 1 = 11
).
Итак, перед вычислением результата мы хотим преобразовать строки в числа. Мы можем сделать это с помощью двух функций parseInt
и parseFloat
.
parseInt
преобразует строку в целое число.parseFloat
преобразует строку в число с плавающей запятой (это означает число с десятичными знаками).
Для калькулятора нам понадобится поплавок.
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 }
Вот и все для счастливого пути!
Вы можете получить исходный код для счастливого пути через эту ссылку (прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на ваш почтовый ящик).
Крайние случаи
Счастливого пути недостаточно. Чтобы создать надежный калькулятор, вам нужно сделать его устойчивым к странным шаблонам ввода. Для этого вы должны представить себе нарушителя спокойствия, который пытается сломать ваш калькулятор, нажимая клавиши в неправильном порядке. Назовем этого возмутителя спокойствия Тимом.
Тим может нажимать эти клавиши в любом порядке:
- Цифровая клавиша (0–9)
- Клавиша оператора (+, -, ×, ÷)
- Десятичный ключ
- Ключ равенства
- Ясный ключ
Что произойдет, если Тим нажмет десятичную клавишу
Если Тим нажимает десятичную клавишу, когда на дисплее уже отображается десятичная точка, ничего не должно происходить.
Здесь мы можем проверить, что отображаемое число содержит .
с помощью метода includes
.
includes
проверяет строки на соответствие. Если строка найдена, она возвращает true
; в противном случае возвращается false
.
Примечание. includes
чувствителен к регистру.
// Example of how includes work. const string = 'The hamburgers taste pretty good!' const hasExclaimation = string.includes('!') console.log(hasExclaimation) // true
Чтобы проверить, есть ли в строке уже точка, мы делаем это:
// Do nothing if string has a dot if (!displayedNum.includes('.')) { display.textContent = displayedNum + '.' }
Затем, если Тим нажимает десятичную клавишу после нажатия клавиши оператора, на дисплее должно отображаться 0.
.
Здесь нам нужно знать, является ли предыдущий ключ оператором. Мы можем это сказать, проверив настраиваемый атрибут data-previous-key-type
, который мы установили в предыдущем уроке.
data-previous-key-type
еще не завершен. Чтобы правильно определить, является ли previousKeyType
оператором, нам нужно обновить previousKeyType
для каждой нажатой клавиши.
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' }
Получив правильный previousKeyType
, мы можем использовать его, чтобы проверить, является ли предыдущий ключ оператором.
if (action === 'decimal') { if (!displayedNum.includes('.')) { display.textContent = displayedNum + '.' } else if (previousKeyType === 'operator') { display.textContent = '0.' } calculator.dataset.previousKeyType = 'decimal' }
Что произойдет, если Тим нажмет клавишу оператора
Если Тим сначала нажимает клавишу оператора, клавиша оператора должна загореться. (Мы уже рассмотрели этот крайний случай, но как? Посмотрим, сможете ли вы определить, что мы сделали).
Во-вторых, ничего не должно произойти, если Тим несколько раз нажмет одну и ту же клавишу оператора. (Мы уже рассмотрели этот крайний случай).
Примечание. если вы хотите улучшить UX, вы можете показать, что оператор постоянно нажимается, с некоторыми изменениями CSS. Мы не делали этого здесь, но посмотрим, сможете ли вы запрограммировать это самостоятельно в качестве дополнительной задачи кодирования.
В-третьих, если Тим нажимает клавишу другого оператора после нажатия клавиши первого оператора, первая клавиша оператора должна быть отпущена. Затем следует нажать вторую клавишу оператора. (Мы рассмотрели и этот крайний случай - но как?).
В-четвертых, если Тим нажимает число, оператор, число и другой оператор в указанном порядке, на дисплее должно появиться вычисленное значение.
Это означает, что нам нужно использовать функцию calculate
, когда существуют firstValue
, operator
и secondValue
.
if ( action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide' ) { const firstValue = calculator.dataset.firstValue const operator = calculator.dataset.operator const secondValue = displayedNum // Note: It's sufficient to check for firstValue and operator because secondValue always exists 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 }
Хотя мы можем вычислить значение при повторном нажатии клавиши оператора, мы также ввели ошибку на этом этапе - при дополнительном нажатии клавиши оператора вычисляется значение, хотя этого не должно быть.
Чтобы калькулятор не выполнял вычисления при последующих нажатиях клавиши оператора, нам нужно проверить, является ли previousKeyType
оператором. Если это так, мы не выполняем расчет.
if ( firstValue && operator && previousKeyType !== 'operator' ) { display.textContent = calculate(firstValue, operator, secondValue) }
В-пятых, после того, как клавиша оператора вычисляет число, если Тим нажимает на число, за которым следует другой оператор, оператор должен продолжить вычисление, например: 8 - 1 = 7
, 7 - 2 = 5
, 5 - 3 = 2
.
В настоящий момент наш калькулятор не может производить последовательные вычисления. Второе рассчитанное значение неверно. Вот что у нас есть: 99 - 1 = 98
, 98 - 1 = 0
.
Второе значение вычислено неправильно, потому что мы загрузили неправильные значения в функцию calculate
. Давайте рассмотрим несколько изображений, чтобы понять, что делает наш код.
Понимание нашей функции вычисления
Во-первых, предположим, что пользователь нажимает на число 99. На данный момент в калькуляторе еще ничего не зарегистрировано.
Во-вторых, допустим, пользователь нажимает оператор вычитания. После того, как они щелкают по оператору вычитания, мы устанавливаем firstValue
на 99. Мы также устанавливаем operator
на вычитание.
В-третьих, допустим, пользователь нажимает второе значение - на этот раз это 1. На этом этапе отображаемое число обновляется до 1, но наши firstValue
, operator
и secondValue
остаются неизменными.
В-четвертых, пользователь снова нажимает кнопку вычитания. Сразу после того, как они нажали кнопку «Вычесть», перед вычислением результата мы устанавливаем secondValue
в качестве отображаемого числа.
В-пятых, мы выполняем расчет с firstValue
99, operator
вычитанием и secondValue
1. Результат - 98.
Как только результат вычислен, мы устанавливаем отображение на результат. Затем мы устанавливаем operator
на вычитание и firstValue
на предыдущее отображаемое число.
Что ж, это ужасно неправильно! Если мы хотим продолжить вычисление, нам нужно обновить firstValue
вычисленным значением.
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 // Update calculated value as firstValue calculator.dataset.firstValue = calcValue } else { // If there are no calculations, set displayedNum as the firstValue calculator.dataset.firstValue = displayedNum } key.classList.add('is-depressed') calculator.dataset.previousKeyType = 'operator' calculator.dataset.operator = action
С этим исправлением последовательные вычисления, выполняемые клавишами оператора, теперь должны быть правильными.
Что произойдет, если Тим нажмет клавишу равенства?
Во-первых, ничего не должно произойти, если Тим нажмет клавишу равенства перед любой клавишей оператора.
Мы знаем, что клавиши оператора еще не нажимались, если для firstValue
не задано число. Мы можем использовать это знание, чтобы предотвратить вычисление равных.
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' }
Во-вторых, если Тим набирает число, за которым следует оператор, за которым следует равенство, калькулятор должен вычислить результат таким образом, чтобы:
2 + =
—>2 + 2 = 4
2 - =
—>2 - 2 = 0
2 × =
—>2 × 2 = 4
2 ÷ =
—>2 ÷ 2 = 1
Мы уже приняли во внимание этот странный ввод. Вы понимаете почему? :)
В-третьих, если Тим нажимает клавишу «равно» после завершения вычислений, необходимо выполнить еще одно вычисление. Вот как должен выглядеть расчет:
- Тим нажимает клавиши 5–1
- Тим бьет с равным. Расчетное значение
5 - 1 = 4
- Тим бьет с равным. Расчетное значение
4 - 1 = 3
- Тим бьет с равным. Расчетное значение
3 - 1 = 2
- Тим бьет с равным. Расчетное значение
2 - 1 = 1
- Тим бьет с равным. Расчетное значение
1 - 1 = 0
К сожалению, наш калькулятор не дает точных расчетов. Вот что показывает наш калькулятор:
- Тим нажимает клавишу 5–1
- Тим бьет с равным. Расчетное значение
4
- Тим бьет с равным. Расчетное значение
1
Исправление расчета
Во-первых, предположим, что наш пользователь нажимает 5. На данный момент в калькуляторе еще ничего не зарегистрировано.
Во-вторых, допустим, пользователь нажимает оператор вычитания. После того, как они щелкают по оператору вычитания, мы устанавливаем firstValue
на 5. Мы также устанавливаем operator
на вычитание.
В-третьих, пользователь нажимает на второе значение. Допустим, это 1. На этом этапе отображаемое число обновляется до 1, но наши firstValue
, operator
и secondValue
остаются без изменений.
В-четвертых, пользователь нажимает клавишу равенства. Сразу после нажатия кнопки равно, но перед вычислением мы устанавливаем secondValue
как displayedNum
.
В-пятых, калькулятор вычисляет результат 5 - 1
и дает 4
. Результат обновляется на дисплее. firstValue
и operator
переносятся на следующий расчет, поскольку мы не обновляли их.
В-шестых, когда пользователь снова нажимает "равно", мы устанавливаем secondValue
на displayedNum
перед вычислением.
Вы можете сказать, что здесь не так.
Вместо secondValue
мы хотим установить firstValue
на отображаемое число.
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' }
Мы также хотим перенести предыдущий secondValue
в новый расчет. Чтобы secondValue
сохранялся до следующего вычисления, нам нужно сохранить его в другом настраиваемом атрибуте. Назовем этот настраиваемый атрибут modValue
(обозначает значение модификатора).
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) } // Set modValue attribute calculator.dataset.modValue = secondValue calculator.dataset.previousKeyType = 'calculate' }
Если previousKeyType
равно calculate
, мы знаем, что можем использовать calculator.dataset.modValue
как secondValue
. Как только мы это узнаем, мы сможем выполнить расчет.
if (firstValue) { if (previousKeyType === 'calculate') { firstValue = displayedNum secondValue = calculator.dataset.modValue } display.textContent = calculate(firstValue, operator, secondValue) }
При этом у нас есть правильный расчет при последовательном нажатии клавиши равенства.
Вернуться к ключу равенства
В-четвертых, если Тим нажимает десятичную клавишу или цифровую клавишу после клавиши калькулятора, дисплей должен быть заменен на 0.
или новое число соответственно.
Здесь вместо того, чтобы просто проверять, является ли previousKeyType
operator
, нам также нужно проверить, является ли оно 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' }
В-пятых, если Тим нажимает клавишу оператора сразу после клавиши равенства, калькулятор не вычисляет.
Для этого мы проверяем, является ли previousKeyType
calculate
перед выполнением вычислений с клавишами оператора.
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 } // ... }
Ключ очистки имеет два применения:
- All Clear (обозначается
AC
) очищает все и сбрасывает калькулятор в исходное состояние. - Очистить запись (обозначается
CE
) очищает текущую запись. Он сохраняет в памяти предыдущие числа.
Когда калькулятор находится в состоянии по умолчанию, должно отображаться AC
.
Во-первых, если Тим нажимает клавишу (любую клавишу, кроме Clear), AC
следует изменить на CE
.
Мы делаем это, проверяя, является ли data-action
clear
. Если это не clear
, ищем кнопку очистки и меняем ее textContent
.
if (action !== 'clear') { const clearButton = calculator.querySelector('[data-action=clear]') clearButton.textContent = 'CE' }
Во-вторых, если Тим нажимает CE
, на дисплее должно отображаться 0. В то же время CE
следует вернуться к AC
, чтобы Тим мог сбросить калькулятор в исходное состояние. **
if (action === 'clear') { display.textContent = 0 key.textContent = 'AC' calculator.dataset.previousKeyType = 'clear' }
В-третьих, если Тим ударит AC
, сбросьте калькулятор в исходное состояние.
Чтобы вернуть калькулятор в исходное состояние, нам нужно очистить все установленные нами настраиваемые атрибуты.
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' }
Вот и все - по крайней мере, для части крайних случаев!
Вы можете получить исходный код для части крайних случаев через эту ссылку (прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на ваш почтовый ящик).
На данный момент код, который мы создали вместе, довольно запутан. Вы, вероятно, заблудитесь, если попытаетесь прочитать код самостоятельно. Давайте проведем рефакторинг, чтобы он стал чище.
Рефакторинг кода
Когда вы проводите рефакторинг, вы часто начинаете с наиболее очевидных улучшений. В этом случае давайте начнем с calculate
.
Прежде чем продолжить, убедитесь, что вы знаете эти методы / функции JavaScript. Мы будем использовать их в рефакторинге.
Итак, приступим!
Рефакторинг функции вычисления
Вот что у нас есть на данный момент.
const calculate = (n1, operator, n2) => { let result = '' if (operator === 'add') { result = firstNum + 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 }
Вы узнали, что нам следует максимально сократить количество перераспределений. Здесь мы можем удалить присвоения, если вернем результат вычислений в операторах if
и else if
:
const calculate = (n1, operator, n2) => {
if (operator === 'add') {
return firstNum + 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)
}
}
Поскольку мы возвращаем все значения, мы можем использовать ранний возврат. Если мы это сделаем, никаких else if
условий не потребуется.
const calculate = (n1, operator, n2) => { if (operator === 'add') { return firstNum + 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) } }
А поскольку у нас есть один оператор на 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)
}
Наконец, мы восемь раз вызывали parseFloat
функцию. Мы можем упростить его, создав две переменные, содержащие значения с плавающей запятой:
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
}
Мы закончили с calculate
. Вам не кажется, что это легче читать по сравнению с тем, что было раньше?
Рефакторинг прослушивателя событий
Код, который мы создали для прослушивателя событий, огромен. Вот что у нас есть на данный момент:
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') { /* ... */ } } })
Как начать рефакторинг этого фрагмента кода? Если вы не знакомы с лучшими практиками программирования, у вас может возникнуть соблазн провести рефакторинг, разделив каждый вид действий на более мелкие функции:
// Don't do this!
const handleNumberKeys = (/* ... */) => {/* ... */}
const handleOperatorKeys = (/* ... */) => {/* ... */}
const handleDecimalKey = (/* ... */) => {/* ... */}
const handleClearKey = (/* ... */) => {/* ... */}
const handleCalculateKey = (/* ... */) => {/* ... */}
Не делай этого. Это не помогает, потому что вы просто разбиваете блоки кода. Когда вы это сделаете, функцию становится труднее читать.
Лучше всего разделить код на чистые и нечистые функции. Если вы это сделаете, вы получите следующий код:
keys.addEventListener('click', e => { // Pure function const resultString = createResultString(/* ... */)
// Impure stuff display.textContent = resultString updateCalculatorState(/* ... */) })
Здесь createResultString
- это чистая функция, которая возвращает то, что нужно отобразить на калькуляторе. updateCalculatorState
- это нечистая функция, которая изменяет внешний вид калькулятора и настраиваемые атрибуты.
Создание createResultString
Как упоминалось ранее, createResultString
должен возвращать значение, которое необходимо отобразить на калькуляторе.
Эти значения можно получить с помощью частей кода, в которых указано display.textContent = 'some value
.
display.textContent = 'some value'
Вместо display.textContent = 'some value'
мы хотим вернуть каждое значение, чтобы использовать его позже.
// replace the above with this
return 'some value'
Давайте рассмотрим это вместе, шаг за шагом, начиная с цифровых клавиш.
Создание строки результата для цифровых клавиш
Вот код для цифровых клавиш:
if (!action) {
if (
displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
display.textContent = keyContent
} else {
display.textContent = displayedNum + keyContent
}
calculator.dataset.previousKeyType = 'number'
}
Первый шаг - скопировать части с надписью display.textContent = 'some value'
в createResultString
. Когда вы это сделаете, убедитесь, что вы изменили display.textContent =
на return
.
const createResultString = () => {
if (!action) {
if (
displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
) {
return keyContent
} else {
return displayedNum + keyContent
}
}
}
Затем мы можем преобразовать оператор if/else
в тернарный оператор:
const createResultString = () => {
if (action!) {
return displayedNum === '0' ||
previousKeyType === 'operator' ||
previousKeyType === 'calculate'
? keyContent
: displayedNum + keyContent
}
}
При рефакторинге не забудьте записать список необходимых вам переменных. Мы вернемся к списку позже.
const createResultString = () => { // Variables required are: // 1. keyContent // 2. displayedNum // 3. previousKeyType // 4. action
if (action!) { return displayedNum === '0' || previousKeyType === 'operator' || previousKeyType === 'calculate' ? keyContent : displayedNum + keyContent } }
Создание строки результата для десятичного ключа
Вот код десятичного ключа:
if (action === 'decimal') { if (!displayedNum.includes('.')) { display.textContent = displayedNum + '.' } else if ( previousKeyType === 'operator' || previousKeyType === 'calculate' ) { display.textContent = '0.' }
calculator.dataset.previousKeyType = 'decimal' }
Как и раньше, мы хотим переместить все, что изменяет display.textContent
на createResultString
.
const createResultString = () => { // ...
if (action === 'decimal') { if (!displayedNum.includes('.')) { return = displayedNum + '.' } else if (previousKeyType === 'operator' || previousKeyType === 'calculate') { return = '0.' } } }
Поскольку мы хотим вернуть все значения, мы можем преобразовать else if
операторы в ранние возвраты.
const createResultString = () => { // ...
if (action === 'decimal') { if (!displayedNum.includes('.')) return displayedNum + '.' if (previousKeyType === 'operator' || previousKeyType === 'calculate') return '0.' } }
Распространенная ошибка здесь - забыть вернуть отображаемое в настоящее время число, если ни одно из условий не соответствует. Нам это нужно, потому что мы заменим display.textContent
значением, возвращенным из createResultString
. Если мы его пропустили, createResultString
вернет undefined
, чего мы не желаем.
const createResultString = () => { // ...
if (action === 'decimal') { if (!displayedNum.includes('.')) return displayedNum + '.' if (previousKeyType === 'operator' || previousKeyType === 'calculate') return '0.' return displayedNum } }
Как всегда, обратите внимание на необходимые переменные. На этом этапе требуемые переменные остаются такими же, как и раньше:
const createResultString = () => {
// Variables required are:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
}
Создание строки результата для клавиш оператора
Вот код, который мы написали для клавиш оператора.
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 }
Вы уже знаете, что такое упражнение: мы хотим переместить все, что изменяет display.textContent
, в createResultString
. Вот что нужно переместить:
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) } } }
Помните, createResultString
необходимо вернуть значение, которое будет отображаться на калькуляторе. Если условие if
не совпало, мы все равно хотим вернуть отображаемое число.
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 } } }
Затем мы можем преобразовать оператор if/else
в тернарный оператор:
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 } }
Если вы присмотритесь, то поймете, что нет необходимости хранить secondValue
переменную. Мы можем использовать displayedNum
непосредственно в функции 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 } }
Наконец, обратите внимание на необходимые переменные и свойства. На этот раз нам нужны calculator.dataset.firstValue
и calculator.dataset.operator
.
const createResultString = () => {
// Variables & properties required are:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
// 5. calculator.dataset.firstValue
// 6. calculator.dataset.operator
}
Создание строки результата для ключа очистки
Мы написали следующий код для обработки ключа 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' }
Как и выше, хочу переместить все, что меняет display.textContent
на createResultString
.
const createResultString = () => {
// ...
if (action === 'clear') return 0
}
Создание строки результата для ключа равенства
Вот код, который мы написали для ключа равенства:
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' }
Как и выше, мы хотим скопировать все, что изменяет display.textContent
на 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 } display.textContent = calculate(firstValue, operator, secondValue) } }
При копировании кода в createResultString
убедитесь, что вы возвращаете значения для всех возможных сценариев:
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 } } }
Далее мы хотим уменьшить количество переназначений. Мы можем сделать это, передав правильные значения в calculate
через тернарный оператор.
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 } } }
Вы можете еще больше упростить приведенный выше код с помощью другого тернарного оператора, если вам это удобно:
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 } }
На этом этапе мы снова хотим обратить внимание на требуемые свойства и переменные:
const createResultString = () => {
// Variables & properties required are:
// 1. keyContent
// 2. displayedNum
// 3. previousKeyType
// 4. action
// 5. calculator.dataset.firstValue
// 6. calculator.dataset.operator
// 7. calculator.dataset.modValue
}
Передача необходимых переменных
Нам нужно семь свойств / переменных в createResultString
:
keyContent
displayedNum
previousKeyType
action
firstValue
modValue
operator
Мы можем получить keyContent
и action
от key
. Мы также можем получить firstValue
, modValue
, operator
и previousKeyType
от calculator.dataset
.
Это означает, что функции createResultString
требуются три переменные: key
, displayedNum
и calculator.dataset
. Поскольку calculator.dataset
представляет состояние калькулятора, давайте вместо этого воспользуемся переменной с именем 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)
// ... })
Не стесняйтесь деструктурировать переменные, если хотите:
const createResultString = (key, displayedNum, state) => { const keyContent = key.textContent const { action } = key.dataset const { firstValue, modValue, operator, previousKeyType } = state
// ... }
Согласованность внутри операторов if
В createResultString
мы использовали следующие условия для проверки типа нажатых клавиш:
// 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') { /* ... */ }
Они непостоянны, поэтому их трудно читать. Если возможно, мы хотим сделать их согласованными, чтобы мы могли написать что-то вроде этого:
if (keyType === 'number') { /* ... */ }
if (keyType === 'decimal') { /* ... */ }
if (keyType === 'operator') { /* ... */}
if (keyType === 'clear') { /* ... */ }
if (keyType === 'calculate') { /* ... */ }
Для этого мы можем создать функцию с именем getKeyType
. Эта функция должна возвращать тип нажатой клавиши.
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
}
Вот как вы будете использовать эту функцию:
const createResultString = (key, displayedNum, state) => { const keyType = getKeyType(key)
if (keyType === 'number') { /* ... */ } if (keyType === 'decimal') { /* ... */ } if (keyType === 'operator') { /* ... */} if (keyType === 'clear') { /* ... */ } if (keyType === 'calculate') { /* ... */ } }
Мы закончили с createResultString
. Перейдем к updateCalculatorState
.
Делаем updateCalculatorState
updateCalculatorState
- это функция, которая изменяет внешний вид калькулятора и настраиваемые атрибуты.
Как и в случае с createResultString
, нам нужно проверить тип нажатой клавиши. Здесь мы можем повторно использовать getKeyType
.
const updateCalculatorState = (key) => { const keyType = getKeyType(key)
if (keyType === 'number') { /* ... */ } if (keyType === 'decimal') { /* ... */ } if (keyType === 'operator') { /* ... */} if (keyType === 'clear') { /* ... */ } if (keyType === 'calculate') { /* ... */ } }
Если вы посмотрите на оставшийся код, вы можете заметить, что мы меняем data-previous-key-type
для каждого типа ключа. Вот как выглядит код:
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' } }
Это избыточно, потому что мы уже знаем тип ключа с getKeyType
. Мы можем реорганизовать приведенное выше, чтобы:
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') { /* ... */ } }
Изготовление updateCalculatorState
для клавиш оператора
Визуально нам нужно убедиться, что все клавиши выходят из своего нажатого состояния. Здесь мы можем скопировать и вставить код, который у нас был раньше:
const updateCalculatorState = (key, calculator) => { const keyType = getKeyType(key) calculator.dataset.previousKeyType = keyType
Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed')) }
Вот что осталось от того, что мы написали для клавиш оператора после перемещения частей, связанных с display.textContent
, в 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 }
Вы можете заметить, что мы можем сократить код с помощью тернарного оператора:
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
}
Как и раньше, обратите внимание на необходимые вам переменные и свойства. Здесь нам нужны calculatedValue
и displayedNum
.
const updateCalculatorState = (key, calculator) => {
// Variables and properties needed
// 1. key
// 2. calculator
// 3. calculatedValue
// 4. displayedNum
}
Делаем updateCalculatorState
для чистого ключа
Вот оставшийся код для ключа очистки:
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' }
Здесь нет ничего особенного, что можно было бы реорганизовать. Не стесняйтесь копировать / вставлять все в updateCalculatorState
.
Делаем updateCalculatorState
для ключа равенства
Вот код, который мы написали для ключа равенства:
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' }
Вот что у нас останется, если мы удалим все, что касается display.textContent
.
if (action === 'calculate') { let secondValue = displayedNum
if (firstValue) { if (previousKeyType === 'calculate') { secondValue = calculator.dataset.modValue } }
calculator.dataset.modValue = secondValue }
Мы можем преобразовать это в следующее:
if (keyType === 'calculate') {
calculator.dataset.modValue = firstValue && previousKeyType === 'calculate'
? modValue
: displayedNum
}
Как всегда, обратите внимание на используемые свойства и переменные:
const updateCalculatorState = (key, calculator) => {
// Variables and properties needed
// 1. key
// 2. calculator
// 3. calculatedValue
// 4. displayedNum
// 5. modValue
}
Передача необходимых переменных
Мы знаем, что для updateCalculatorState
нам нужно пять переменных / свойств:
key
calculator
calculatedValue
displayedNum
modValue
Поскольку modValue
можно получить из calculator.dataset
, нам нужно передать только четыре значения:
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) })
Повторный рефакторинг updateCalculatorState
В updateCalculatorState
мы изменили три вида значений:
calculator.dataset
- Класс для нажатия / депрессии операторов
AC
противCE
текста
Если вы хотите сделать его чище, вы можете разделить (2) и (3) на другую функцию - updateVisualState
. Вот как может выглядеть 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' } }
Подведение итогов
После рефакторинга код стал намного чище. Если вы посмотрите в прослушиватель событий, вы узнаете, что делает каждая функция. Вот как выглядит прослушиватель событий в конце:
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) })
Вы можете получить исходный код для части рефакторинга через эту ссылку (прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на ваш почтовый ящик).
Надеюсь, вам понравилась эта статья. Если да, то вам, возможно, понравится Learn JavaScript - курс, в котором я покажу вам, как построить 20 компонентов, шаг за шагом, подобно тому, как мы построили этот калькулятор сегодня.
Примечание: мы можем улучшить калькулятор, добавив поддержку клавиатуры и специальные возможности, такие как живые регионы. Хотите узнать как? Пойдите, проверьте Learn JavaScript :)