Рубрики
Без рубрики

Как создать приложение HTML калькулятора с нуля с помощью JavaScript

Это эпическая статья, в которой вы узнаете, как построить калькулятор с нуля. Мы сосредоточимся на JavaScript, вам нужно написать – как подумать о создании калькулятора, как написать код и в конечном итоге, как очистить свой код. К концу

Автор оригинала: Zell Liew.

Это эпическая статья, в которой вы узнаете, как построить калькулятор с нуля. Мы сосредоточимся на JavaScript, вам нужно написать – как подумать о создании калькулятора, как написать код и в конечном итоге, как очистить свой код.

К концу статьи вы должны получить калькулятор, который функционирует точно так же, как калькулятор iPhone (без функциональности +/- и процентных функций).

Предпосылки

Прежде чем пытаться пройти через урок, пожалуйста, убедитесь, что у вас есть приличный командование JavaScript. Минимально вам нужно знать эти вещи:

  1. Если/иначе заявления
  2. Для петлей
  3. Функции JavaScript
  4. Функции стрелки
  5. && и
  6. операторы Как изменить текст с TextContent
  7. имущество

Прежде чем вы начнете

Я призываю вас попробовать построить калькулятор сами, прежде чем следовать урок. Это хорошая практика, потому что вы тренируете себя думать, как разработчик.

Вернитесь к этому уроку, как только вы пробовали на один час (не имеет значения, добивается ли вы успешно или потерпеть неудачу. Когда вы пытаетесь, вы думаете, и это поможет вам поглощать урок в двойном быстром времени).

С этим давайте начнем с понимания, как работает калькулятор.

Создание калькулятора

Во-первых, мы хотим построить калькулятор.

Калькулятор состоит из двух частей: дисплей и клавиши.

0

Мы можем использовать GRID CSS, чтобы сделать ключи, поскольку они расположены в сетке в формате. Это уже было сделано для вас в стартовом файле. Вы можете найти файл стартера в Эта ручка Отказ

.calculator__keys { 
  display: grid; 
  /* other necessary CSS */ 
}

Чтобы помочь нам определить оператора, десятичные, четкие и равные ключевые ключи, мы собираемся предоставить атрибут Data-Action, который описывает то, что они делают.

× 7

Прослушивание ключевых прессов

Пять вещей могут случиться, когда человек получает калькулятор. Они могут ударить:

  1. Номер ключа (0-9)
  2. Ключ оператора (+, -, ×, ÷)
  3. Десятичный ключ
  4. ключевой ключ
  5. Очистить ключ

Первые шаги для создания этого калькулятора должны быть в состоянии (1) прослушать все клавиши и (2) определить тип нажатия клавиши. В этом случае мы можем использовать шаблон делегирования событий для прослушивания, поскольку ключи всех детей .calculator kekeys Отказ

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 это либо Добавить , вычесть , Умножьте или Разделить Мы знаем, что ключ является оператором.

if (
  action === 'add' ||
  action === 'subtract' ||
  action === 'multiply' ||
  action === 'divide'
) {
  console.log('operator key!')
}

Если ключ Data-Action это Десятичная Мы знаем, что пользователь нажал на десятичный ключ.

Следуя тот же процесс мысли, если ключ Data-Action это ясно Мы знаем, что пользователь нажал на четкий (тот, который говорит AC). Если ключ Data-Action это рассчитать Мы знаем, что пользователь нажал на ключ равных.

if (action === 'decimal') {
  console.log('decimal key!')
}

if (action === 'clear') {
  console.log('clear key!')
}

if (action === 'calculate') {
  console.log('equal key!')
}

На данный момент вы должны получить console.log Ответ от каждого ключа калькулятора.

Строительство счастливого пути

Давайте рассмотрим, какой средний человек будет делать, когда они заберут калькулятор. Это «какой средний человек будет делать», называется счастливым путем Отказ

Давайте назовем нашему среднему человеку Марию.

Когда Мэри поднимает калькулятор, она могла ударить любой из этих клавиш:

  1. Номер ключа (0-9)
  2. Ключ оператора (+, -, ×, ÷)
  3. Десятичный ключ
  4. равный ключ
  5. Очистить ключ

Он может быть подавлен, чтобы рассмотреть пять типов клавиш одновременно, поэтому давайте сделаем его шагом за шагом.

Когда пользователь ударяет номер ключа

На данный момент, если калькулятор показывает 0 (номер по умолчанию), целевой номер должен заменить ноль.

Если калькулятор показывает ненулевое число, целевой номер должен быть добавлен на отображаемый номер.

Здесь нам нужно знать две вещи:

  1. Количество ключа, который нажал
  2. Текущий отображаемый номер

Мы можем получить эти два значения через 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 Display.

if (!action) {
  if (displayedNum === '0') {
    display.textContent = keyContent
  }
}

Если калькулятор показывает ненулевое число, мы хотим добавить клавишу нажатия на отображаемый номер. Чтобы добавить номер, мы объединяем строку.

if (!action) {
  if (displayedNum === '0') {
    display.textContent = keyContent
  } else {
    display.textContent = displayedNum + keyContent
  }
}

На данный момент Мэри может щелкнуть любой из этих клавиш:

  1. Десятичный ключ
  2. Ключ оператора

Допустим, Мэри попадает в десятичный ключ.

Когда пользователь попадает в десятичный ключ

Когда Мэри попадает в десятичный ключ, на дисплее должен появиться десятичное десятичное число. Если Mary попадает в любое число после удара десятичного ключа, номер также должен быть добавлен на дисплее.

Чтобы создать этот эффект, мы можем объединить Отказ к отображению номера.

if (action === 'decimal') {
  display.textContent = displayedNum + '.'
}

Далее, скажем, Мэри продолжает свой расчет, ударяя ключ оператора.

Когда пользователь попадает в ключ оператора

Если Мэри попадает в ключ оператора, оператор должен быть выделен, чтобы Мэри знала, что оператор активен.

Для этого мы можем добавить депрессии класс к ключу оператора.

if (
  action === 'add' ||
  action === 'subtract' ||
  action === 'multiply' ||
  action === 'divide'
) {
  key.classList.add('is-depressed')
}

Однажды Мария ударила ключ оператора, она ударит другой ключ.

Когда пользователь попадает в номер ряд после ключа оператора

Когда Mary снова попадает в клавишу номер, предыдущий дисплей должен быть заменен новым номером. Ключ оператора также должен освободить его нажатое состояние.

Чтобы выпустить прессованный штат, мы удаляем депрессии Класс от всех клавиш через 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'))
  }
})

Далее мы хотим обновить дисплей на кнопку нажатия. Прежде чем мы сделаем это, нам нужен способ сказать, если предыдущий ключ является ключом оператора.

Один из способов сделать это через пользовательский атрибут. Давайте назовем этот пользовательский атрибут Данные - предыдущий ключ-тип Отказ

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'
    }
  }
})

Если previekeytype Является ли оператор, мы хотим заменить отображаемый номер на щелчке.

const previousKeyType = calculator.dataset.previousKeyType

if (!action) {
  if (displayedNum === '0' || previousKeyType === 'operator') {
    display.textContent = keyContent
  } else {
    display.textContent = displayedNum + keyContent
  }
}

Далее, давайте скажем, Мэри решает завершить свой расчет, ударив по ключу равных.

Когда пользователь попадает в ключ равных

Когда Мэри попадает в равную клавишу, калькулятор должен рассчитать результат, который зависит от трех значений:

  1. Первый номер вошел в калькулятор
  2. оператор
  3. Второй номер вошел в калькулятор

После расчета результат должен заменить отображаемое значение.

На данный момент мы только знаем Второй номер – То есть текущий номер.

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)
}

Это означает, что нам нужно создать рассчитать функция. Он должен принять три параметра: первый номер, оператор и второе число.

const calculate = (n1, operator, n2) => {
  // Perform calculation and return calculated value
}

Если оператор является Добавить Мы хотим добавлять значения вместе. Если оператор является вычесть Мы хотим вычесть ценности и так далее.

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 + ).

Итак, перед расчетом результата мы хотим преобразовать строки на цифры. Мы можем сделать это с двумя функциями Парсент и Партифлет Отказ

  • Парсент преобразует строку в Целое число Отказ
  • Партифлет преобразует строку в плавать (Это означает число с десятичными местами).

Для калькулятора нам нужен плавающий.

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
}

Вот это для счастливого пути!

Вы можете схватить исходный код для счастливого пути через эта ссылка (Прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на свой почтовый ящик).

Краевые чехлы

Хап-путь не достаточно. Чтобы построить калькулятор, который надежный, вам необходимо сделать свой калькулятор устойчивым к странным входным узорам. Для этого вам приходится представить нарушитель спокойствия, который пытается сломать калькулятор, ударяя ключи в неправильном порядке. Давайте назовем это нарушение удаления тима.

Тим может ударить эти ключи в любом порядке:

  1. Номер ключа (0-9)
  2. Ключ оператора (+, -, ×, ÷)
  3. Десятичный ключ
  4. Ключевой ключ
  5. Очистить ключ

Что произойдет, если Тим попадает в десятичный ключ

Если Тим попадает в десятичный ключ, когда дисплей уже показывает десятичную точку, ничего не должно произойти.

Здесь мы можем проверить, что отображаемый номер содержит A Отказ с включает в себя метод.

включает в себя Проверяет строки для данного матча. Если найден строка, она возвращает правда ; Если нет, он возвращает ложь Отказ

Примечание : включает в себя чувствителен к регистру.

// 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 + '.'
}

Далее, если TIM попадает в десятичную клавишу после удара ключа оператора, дисплей должен показать 0. Отказ

Здесь нам нужно знать, является ли предыдущий ключ оператором. Мы можем сказать, проверив пользовательский атрибут, Данные - предыдущий ключ-тип Мы установили на предыдущем уроке.

Данные - предыдущий ключ-тип еще не завершен. Правильно идентифицировать, если previekeytype Это оператор, нам нужно обновить previekeytype Для каждого нажатия клавиши.

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'
}

Как только у нас есть правильный previekeytype , мы можем использовать его, чтобы проверить, является ли предыдущий ключ оператором.

if (action === 'decimal') {
  if (!displayedNum.includes('.')) {
    display.textContent = displayedNum + '.'
  } else if (previousKeyType === 'operator') {
    display.textContent = '0.'
  }
  
calculator.dataset.previousKeyType = 'decimal'
}

Что произойдет, если Тим попадает в ключ оператора

Если TIM попадает в ключ оператора, первый ключ оператора должен загореться. (Мы уже были покрыты для этого края, но как? Посмотрите, можете ли вы определить, что мы сделали).

Во-вторых, ничего не должно произойти, если TIM бьет тот же операторский ключ несколько раз. (Мы уже охватывались для этого края, а также).

Примечание: Если вы хотите предоставить лучший UX, вы можете показать оператора, нажав на несколько раз, когда некоторые изменения CSS. Мы не делали этого здесь, но посмотрите, можете ли вы программировать, что сами в качестве дополнительной проблемы кодирования.

В-третьих, если TIM ударит другой ключ оператора после удара первой клавиши оператора, следует выпустить первый ключ оператора. Затем второй ключ оператора должен быть в депрессии. (Мы также покрывали для этого края – но как?).

В-четвертых, если Тим попадает в число, оператор, номер и другой оператор, в этом порядке дисплей должен быть обновлен до расчетного значения.

Это означает, что нам нужно использовать рассчитать Функция, когда FirstValue , Оператор и 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
}

Хотя мы можем рассчитать значение, когда ключ оператора нажат во второй раз, мы также представили ошибку в данный момент – дополнительные клики на клавише оператора рассчитывают значение, когда он не должен.

Чтобы предотвратить выполнение калькулятора вычисления на следующих кликах на клавише оператора, нам нужно проверить, если previekeytype это оператор. Если это так, мы не выполняем расчет.

if (
  firstValue &&
  operator &&
  previousKeyType !== 'operator'
) {
  display.textContent = calculate(firstValue, operator, secondValue)
}

Пятый, после того, как ключ оператора рассчитывает число, если Тим попадает в число, за которым следует другой оператор, оператор должен продолжаться с расчетом, как это: 8 - , 7 - , 5 - Отказ

Прямо сейчас наш калькулятор не может принимать последовательные расчеты. Второе расчетное значение неверно. Вот что у нас есть: 99 - , 98 - Отказ

Второе значение вычисляется неправильно, потому что мы кормили неправильные значения в рассчитать функция. Давайте пройдем несколько фотографий, чтобы понять, что делает наш код.

Понимание нашей расчетной функции

Во-первых, давайте скажем, пользователь нажимает на номер 99. На данный момент ничто не зарегистрировано в калькуляторе.

Во-вторых, скажем, пользователь нажимает подчитанный оператор. После того, как они нажимают на вычитанный оператор, мы устанавливаем FirstValue до 99. Мы также устанавливаем Оператор вычесть.

В-третьих, давайте скажем, пользователь нажимает на второе значение – на этот раз это 1. На данный момент отображаемый номер обновляется до 1, но наш FirstValue , Оператор и SecondValue оставаться без изменений.

В-четвертых, пользователь снова нажимает на вычесть. Сразу после того, как они нажимают вычитание, прежде чем мы рассчитаем результат, мы устанавливаем SecondValue как отображаемый номер.

В-пятых, мы выполняем расчет с FirstValue 99, Оператор вычесть, а SecondValue 1. Результат 98.

После того, как результат будет рассчитан, мы устанавливаем дисплей к результату. Тогда мы устанавливаем Оператор вычесть, а 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

С помощью этого исправления последовательные расчеты, выполняемые операторами ключей, теперь должны быть правильными.

Что произойдет, если TIM попадает в ключ равных?

Во-первых, ничего не должно произойти, если TIM попадает в ключ радости перед любыми ключами оператора.

Мы знаем, что ключи оператора еще не щелкнулись, если 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'
}

Во-вторых, если Тим попадает в число, за которым следует оператор, а затем равным, калькулятор должен рассчитать результат такой, что:

  1. 2 + = -> 2 +
  2. 2 - = -> 2 -
  3. 2 × = -> 2 ×
  4. 2 ÷ = -> 2 ÷

Мы уже сделали этот странный ввод во внимание. Можете ли вы понять почему?:)

В-третьих, если Тим попадает в равную клавишу после завершения расчета, другой расчет должен быть выполнен снова. Вот как следует читать расчет:

  1. Тим хиты клавиши 5-1
  2. Тим хиты равны. Расчетное значение – 5 -
  3. Тим хиты равны. Расчетное значение – 4 -
  4. Тим хиты равны. Расчетное значение – 3 -
  5. Тим хиты равны. Расчетное значение – 2 -
  6. Тим хиты равны. Расчетное значение – 1 -

К сожалению, наш калькулятор портит этот расчет. Вот что показывает наш калькулятор:

  1. Тим хиты ключ 5-1
  2. Тим хиты равны. Расчетное значение – 4.
  3. Тим хиты равны. Расчетное значение – 1.

Исправление расчета

Во-первых, скажем, наш пользователь нажимает 5. На данный момент ничто не зарегистрировано в калькуляторе.

Во-вторых, скажем, пользователь нажимает подчитанный оператор. После того, как они нажимают на вычитанный оператор, мы устанавливаем FirstValue до 5. Мы устанавливаем и Оператор вычесть.

В-третьих, пользователь нажимает на второе значение. Допустим, это 1. На данный момент отображаемое число обновляется до 1, но наш FirstValue , Оператор и SecondValue оставаться без изменений.

В-четвертых, пользователь нажимает ключевую клавишу равен. Сразу после того, как они нажимают на равных, но перед расчетом мы устанавливаем SecondValue как отображаемость

В-пятых, калькулятор рассчитывает результат 5 - 1 и дает 4 Отказ Результат обновляется на дисплей. FirstValue и Оператор Перенести на следующий расчет, поскольку мы не обновили их.

Шестой, когда пользователь снова попадает в равных случаях, мы устанавливаем SecondValue к Показатель значений до расчета.

Вы можете сказать, что здесь не так.

Вместо 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'
}

Если previekeytype это рассчитать , мы знаем, что можем использовать Calculator.dataset.modvalue как SecondValue Отказ Как только мы знаем это, мы можем выполнить расчет.

if (firstValue) {
  if (previousKeyType === 'calculate') {
    firstValue = displayedNum
    secondValue = calculator.dataset.modValue
  }
  
display.textContent = calculate(firstValue, operator, secondValue)
}

С этим у нас есть правильный расчет, когда qualtal ключ нажимается последовательно.

Вернуться к ключу равных

В-четвертых, если TIM попадает в десятичный ключ или номер числа после клавиши калькулятора, дисплей должен быть заменен на 0. или новый номер соответственно.

Здесь вместо того, чтобы просто проверять, если previekeytype это Оператор , нам также нужно проверить, это рассчитать Отказ

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'
}

В-пятых, если Тим попадает в ключ оператора сразу после равного ключа, калькулятор должен не рассчитать.

Для этого мы проверяем, если previekeytype это рассчитать Перед выполнением расчетов с операторами ключей.

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
  }
  
// ...
}

Clear Key имеет два использования:

  1. Все ясно (обозначено AC ) Очищает все и сбрасывает калькулятор к своему первоначальному состоянию.
  2. Очистить запись (обозначается CE ) Очищает текущую запись. Он сохраняет предыдущие числа в памяти.

Когда калькулятор находится в состоянии по умолчанию, AC должно быть показано.

Во-первых, если Тим попадает в ключ (любой ключ, кроме четкого), AC следует изменить на CE Отказ

Мы делаем это, проверив, если Data-Action это ясно Отказ Если это не ясно , мы ищем четкую кнопку и измените ее 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'
}

Вот и все – для части краевых чехлов, в любом случае!

Вы можете схватить исходный код для деталей по краям через эта ссылка (Прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на свой почтовый ящик).

На данный момент код, который мы создали вместе, вполне запутанно. Вы, вероятно, потеряете, если вы попытаетесь прочитать код самостоятельно. Давайте рефикторуем его, чтобы сделать это уборщиком.

Рефакторинг кода

Когда вы рефакторируете, вы часто начинаете с наиболее очевидных улучшений. В этом случае давайте начнем с рассчитать Отказ

Перед продолжением, убедитесь, что вы знаете эти практики/функции JavaScript. Мы будем использовать их в рефакторе.

  1. Ранние возвращения
  2. Тровые операторы
  3. Чистые функции
  4. Разрушение ES6

С этим давайте начнем!

Рефакторинг вычисления функции

Вот что мы имеем до сих пор.

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
}

Вы узнали, что мы должны максимально уменьшить переназначение. Здесь мы можем удалить задания, если мы вернем результат расчета в Если и еще если заявления:

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)
  }
}

Так как мы возвращаем все значения, мы можем использовать Раннее возвращение Отказ Если мы этого сделаем, нет необходимости в любом еще если условия.

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)
  }
}

И поскольку у нас есть одно утверждение на Если Условие, мы можем удалить кронштейны. (Примечание: некоторые разработчики клянутся вьющимися скобками, хотя). Вот что будет выглядеть код:

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)
}

Наконец, мы позвонили Партифлет восемь раз в функции. Мы можем упростить его, создавая две переменные, чтобы содержать значения поплавка:

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
}

Мы закончили с рассчитать сейчас. Не думаете ли вы, что проще читать по сравнению с ранее?

Рефакторинг слушателя события

Код, который мы создали для слушателя события, огромно. Вот что у нас на данный момент:

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 Это чистая функция, которая возвращает то, что необходимо отобразить на калькуляторе. UpdateCalcoutousState Является ли непростой функцией, которая изменяет визуальный внешний вид калькулятора и пользовательские атрибуты.

Создание CreateResultString.

Как упоминалось ранее, CreateResultstring следует вернуть значение, которое необходимо отобразить на калькуляторе. Вы можете получить эти значения через части кода, который говорит display.textcontent = 'Некоторые значения Отказ

display.textContent = 'some value'

Вместо Display.textcontent Мы хотим вернуть каждое значение, чтобы мы могли использовать его позже.

// 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 в CreateResultstring Отказ Когда вы делаете это, убедитесь, что вы меняете display.textcontent = в Возвращение Отказ

const createResultString = () => {
  if (!action) {
    if (
      displayedNum === '0' ||
      previousKeyType === 'operator' ||
      previousKeyType === 'calculate'
    ) {
      return keyContent
    } else {
      return displayedNum + keyContent
    }
  }
}

Далее мы можем конвертировать Если/else Заявление о тройном операторе:

const createResultString = () => {
  if (action!) {
    return displayedNum === '0' ||
      previousKeyType === 'operator' ||
      previousKeyType === 'calculate'
      ? keyContent
      : displayedNum + keyContent
  }
}

Когда вы Refactor, не забудьте отметить список необходимых вариантов переменных. Мы вернемся к списку позже.

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.'
    }
  }
}

Поскольку мы хотим вернуть все значения, мы можем конвертировать еще если заявления в ранние доходы.

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 необходимо вернуть значение, которое будет отображаться на калькуляторе. Если Если Состояние не совпадает, мы все еще хотим вернуть отображаемый номер.

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
    }
  }
}

Затем мы можем ревертировать Если/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 Переменная. Мы можем использовать Показатель значений прямо в рассчитать функция.

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
}

Делать строку результатов для чистого ключа

Мы написали следующий код для обработки ясно ключ.

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
    }
  }
}

Далее мы хотим сократить переназначение. Мы можем сделать это, передавая правильные значения в рассчитать через тройной оператор.

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 :

  1. keycontent.
  2. отображаемость
  3. previekeytype.
  4. действие
  5. FirstValue.
  6. ModValue.
  7. оператор

Мы можем получить Keycontent и Действие от ключ Отказ Мы также можем получить FirstValue , ModValue , Оператор и previekeytype от Calculator.Dataset Отказ

Это означает CreateResultstring Функция нуждается в трех переменных- ключ , Показатель значений и Calculator.Dataset Отказ С Calculator.Dataset Представляет состояние калькулятора, давайте использовать переменную под названием Государство вместо.

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
  
  // ...
}

Согласованность внутри, если утверждения

В 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 Отказ Давайте перейдем к UpdateCalcoutousState Отказ

Создание UpdateCalculatorState

UpdateCalcoutousState это функция, которая меняет визуальный внешний вид калькулятора и пользовательские атрибуты.

Как с CreateResultstring Нам нужно проверить тип ключа, который был нажал. Здесь мы можем повторно использовать GetKeyType Отказ

const updateCalculatorState = (key) => {
  const keyType = getKeyType(key)
  
  if (keyType === 'number') { /* ... */ }
  if (keyType === 'decimal') { /* ... */ }
  if (keyType === 'operator') { /* ... */}
  if (keyType === 'clear') { /* ... */ }
  if (keyType === 'calculate') { /* ... */ }
}

Если вы посмотрите на левый код, вы можете заметить, что мы изменим Данные - предыдущий ключ-тип для каждого типа ключа. Вот что выглядит код:

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
}

Как и прежде, примите к сведению переменные и свойства, которые вам нужны. Вот, нам нужно Расчетное название и Показатель значений Отказ

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'
}

Там нет ничего особенного, мы можем рефакторировать здесь. Не стесняйтесь скопировать/вставить все в UpdateCalcoutousState Отказ

Создание UpdateCalculatorState для Caleal

Вот код, который мы написали для равных ключей:

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
}

Прохождение в необходимых переменных

Мы знаем, что нам нужно пять переменных/свойств для UpdateCalcoutousState :

  1. ключ
  2. калькулятор
  3. Расчетное значение
  4. отображаемость
  5. 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)
})

Рефакторинг UpdateCalcoutousState снова

Мы изменили три вида ценностей в UpdateCalcoutousState :

  1. Calculator.dataset.
  2. Класс для прессования/удручающих операторов
  3. 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)
})

Вы можете взять исходный код для части рефакторов через эта ссылка (Прокрутите вниз и введите свой адрес электронной почты в поле, и я отправлю исходные коды прямо на свой почтовый ящик).

Я надеюсь, что вам понравилось эту статью. Если бы вы сделали, вы можете любить Учите JavaScript – Конечно, где я покажу вам, как построить 20 компонентов, шаг за шагом, например, как мы создали этот калькулятор сегодня.

ПРИМЕЧАНИЕ. Мы можем дополнительно улучшить калькулятор, добавив поддержку клавиатуры и доступности, такие как живые области. Хотите узнать, как? Поехать проверить JavaScript:)