如何使用JavaScript建立一个计算器

9,213 阅读17分钟

如何使用JavaScript构建一个计算器

构建一个网络计算器是一个很好的项目,尤其是当你刚刚开始学习JavaScript时。它对任何技能水平的人来说都很简单。这个项目涵盖了与UI的交互和关键的JavaScript方法。

在这篇文章中,我们将带领你了解各种HTML和CSS元素,以及在构建一个功能性和响应性强的计算器时使用的Vanilla JavaScript和现代ES6实践,如下面的图片所示。

calculator-image

先决条件

  • 任何好的文本编辑器。
  • 对JavaScript和HTML的基本理解。

计算器的设计

为了开始工作,你需要考虑计算器的基本功能。它们包括additionsubtractionmultiplicationdivisiondeleteall-clear ,当然还有在执行这些操作时使用decimal numbers 的能力。

在你的文本编辑器中,为你的HTML,CSSJavaScript 创建独立的文件夹three 。这只是基本上使你的代码更有条理。

在你的HTML 文件夹中,你可以使用下面的代码链接CSSJavaScript 文件。

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Calculator</title>
    <link href="Calculator with Js\style.css" rel="stylesheet">
    <script src="Calculator with JS\script.js" defer></script>

你需要做的下一件事是添加所有不同的HTML元素。我们将使用grid ,以获得一个漂亮的设计。因此,创建一个div ,其类名为calculator-grid

   <div class="calculator-grid">

你将把所有不同的HTML elementsbuttons 放在上面的calculator-grid div里面。

下面是包含所需组件的HTML代码。

<!DOCTYPE html>
<html lang="en" dir="ltr">

<head>
    <meta charset="utf-8">
    <title>Calculator</title>
    <link href="Calculator with Js\style.css" rel="stylesheet">
    <script src="Calculator with JS\script.js" defer></script>
    <div class="calculator-grid">
        <div class="output">
            <div class="previous-operand"></div>
            <div class="current-operand"></div>
        </div>
        <button class="span-two">AC</button>
        <button>DEL</button>
        <button>÷</button>
        <button>1</button>
        <button>2</button>
        <button>3</button>
        <button>*</button>
        <button>4</button>
        <button>5</button>
        <button>6</button>
        <button>+</button>
        <button>7</button>
        <button>8</button>
        <button>9</button>
        <button>-</button>
        <button>.</button>
        <button>0</button>
        <button class="span-two">=</button>
    </div>
</head>
<body>
</body>
</html>

上面的HTML代码包含几个div 类。output 类代表计算器屏幕。previous-operand 代表计算器中previous 操作的结果,而current-operand 类代表计算器上的current 操作。

span-two 类代表计算器上的按钮,这些按钮将占据two 列。你可以从google或其他地方copy paste 除法符号(÷),因为它在你的键盘上是不可用的。

这就是你的计算器在这一点上的样子。

calculator-buttons

计算器的样式

接下来,我们需要使用CSS ,为计算器设计样式。首先,选择所有的元素,包括beforeafter 元素。然后,我们可以应用box-sizing 属性并将其设置为border-box

你也可以使用下面的代码改变计算器的font-familyfont-weight

*, *::before, *::after {
  box-sizing: border-box;
  font-family: Gotham Rounded, sans-serif;
  font-weight: normal;
}

接下来,我们需要通过使用body 元素来样式背景,如下图所示。

body {
  margin: 0;
  padding: 0;
  background: linear-gradient(to right, #CBCE91FF, #EA738DFF);
  }

下一步是对我们之前定义的calculator-grid div进行样式化。它包裹了我们所有不同的buttonselements 。我们可以把display 设为grid

我们还可以使用justify-content 属性将其设置为屏幕的中心位置。此外,align-content 属性可以帮助将项目对齐到屏幕中心。

在这一点上,你可能会注意到,calculator-grid 并不是垂直排列的。我们可以通过将min-height 设置为100vh 来解决这个问题。这意味着计算器的网格将一直填补100% 的高度。

另一点是,buttons 应该在你的屏幕中心对齐,并间隔开来。要制作一个普通的计算器,我们必须使用grid-template-columns ,然后将其设置为repeat ,每一列可以是100px wide 。我们也可以对grid-template-rows

这里是下面的代码。

.calculator-grid {
    display: grid;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    grid-template-columns: repeat(4, 100px);
    grid-template-rows: minmax(120px, auto) repeat(5, 100px);
    }

为了使output screen ,使它能按需要增长,并能适应任何数量的输入值,我们需要将minmax 的值设置为120px,将maximum value 设置为auto ,如上面的代码所示。

下面是你的计算器在这一点上的样子。

calculator-in-grid-form

为了正确定位按钮,我们应该选择calculator-grid 中的所有按钮并应用这些CSS 元素,如下图所示。

.calculator-grid > button {
      cursor: pointer;
      font-size: 2rem;
      border: 1px, solid #FFFFFF;
      outline: none;
      background-color: rbga(255, 255, 255, 0.75);
    }

      .calculator-grid > button:hover {
        background-color: #a9a9a9;
      }

align-content 在这一点上,我们可以通过改变align-items 属性来改进计算器的设计,并将其设置为center

然后,我们应该对span-two 类进行样式设计,这将影响到All-clearDelete 按钮。我们可以将grid-column 设置为跨越two 列。

输出窗口的样式

另一件重要的事情是为计算器上显示的output 样式。我们可以通过添加一个虚拟文本来实现,123 + ,代表你的前一个操作数,456 ,代表当前操作数。

下面是HTML代码的样子。

<div class="output">
<div class="previous-operand">123 +</div>
<div class="current-operand">456</div>

这样,你就有了一点虚拟文本,我们可以在样式设计时玩一玩。现在,我们可以开始对它进行样式设计。

width 我们可以做的第一件事是在整个计算器的output ,设置为span 。我们可以通过再次使用grid-column 属性来做到这一点,并将其设置为从列1 to -1 ,基本上只是最后一列的跨度。

接下来,我们将把background-color 改为black ,透明度为75% 。然后,我们将对齐容器内的所有元素。

最简单的方法是使用flex 。因此,将display 属性设置为flex ,将align-items 属性设置为flex-end 。输出元素将被定位在计算器的right 边。

为了使它们间隔开,我们可以使用justify-content 属性,并将其设置为space-around 。我们还可以改变flex-direction ,并将其设置为column ,使输出元素垂直对齐。

接下来,我们可以将padding 设置为任何需要的值。另外,为了在输出元素过长时使其wrap ,我们可以使用word-wrap 属性来选择单词的断裂位置。

此外,我们可以添加一个word-break ,并将其设置为break-all 。我们应该在输出类中对previouscurrent 操作数进行样式设置。

最后的CSS 代码是这样出来的。

*, *::before, *::after {
  box-sizing: border-box;
  font-family: Gotham Rounded, sans-serif;
  font-weight: normal;
}

body {
  margin: 0;
  padding: 0;
  background: linear-gradient(to right, #CBCE91FF, #EA738DFF);
  }

  .calculator-grid {
    display: grid;
    justify-content: center;
    align-content: center;
    min-height: 100vh;
    grid-template-columns: repeat(4, 100px);
    grid-template-rows: minmax(120px, auto) repeat(5, 100px);
    }

    .calculator-grid > button {
      cursor: pointer;
      font-size: 2rem;
      border: 1px, solid #FFFFFF;
      outline: none;
      background-color: rbga(255, 255, 255, 0.75);
    }

      .calculator-grid > button:hover {
        background-color: #a9a9a9;
      }

      .span-two {
        grid-column: span 2;
        color: #adf802;
        background-color: rgba(139, 0, 139, 0.8);
      }

      .output{
        grid-column: 1 / -1;
        background-color: rgba(0, 0, 0, 0.75);
        display: flex;
        align-items: flex-end;
        justify-content: space-around;
        flex-direction: column;
        padding: 10px;
        word-wrap: break-word;
        word-break: break-all;
      }

      .output .previous-operand{
        color: rgba(255,255, 255, 0.75);
        font-size: 1.5rem;
      }

      .output .current-operand{
        color: white;
        font-size: 2.5rem;
      }

在这一点上,计算器已经有了雏形。现在,是时候使用JavaScript使其发挥作用了。

实际的JavaScript

首先,我们应该选择我们的计算器的所有按钮和操作。我们可以通过在HTML文件中加入一些类来做到这一点。然而,由于我们不想把CSS 类和JavaScript 类混在一起,我们可以用data attributes 来代替选择它们。

data-operation 来表示你的操作按钮, 来表示 按钮, 来表示 按钮, 来表示 按钮。我们可以将这些类添加到 和 。data-numbers number data-all-clear All-Clear data-delete Delete previous-operand current-operand

下面是它在你的代码中的样子。

  <div data-previous-operand class="previous-operand"></div>
    <div data-current-operand class="current-operand"></div>
<button data-all-clear class="span-two">AC</button>
  <button data-delete>DEL</button>
  <button data-operation>÷</button>
  <button data-number>1</button>
  <button data-number>2</button>
  <button data-number>3</button>
  <button data-operation>*</button>
  <button data-number>4</button>
  <button data-number>5</button>
  <button data-number>6</button>
  <button data-operation>+</button>
  <button data-number>7</button>
  <button data-number>8</button>
  <button data-number>9</button>
  <button data-operation>-</button>
  <button data-number>.</button>
  <button data-number>0</button>
  <button data-equals class="span-two">=</button>

上面的添加是我们需要对HTML 文件进行的唯一改动,以便在JavaScript文件夹中选择这些元素。

在JavaScript文件中,定义一些constant variables ,它将代表number 的按钮。然后我们将使用document.querySelectorAll() 进行查询。这个函数将允许我们获得所有符合某个字符串的元素。

在这种情况下,我们选择一个必须在括号内的data 属性,我们选择'[data-number]',这将选择所有number 元素。

我们可以做同样的事情,但对于operation 按钮。这也适用于Equals,All-clearDelete 按钮,以及你的previousOperandTextElementcurrentOperandTextElement

代码会是这样的。

const numberButtons = document.querySelectorAll('[data-number]')
const operationButtons = document.querySelectorAll('[data-operation]')
const equalsButton = document.querySelector('[data-equals]')
const deleteButton = document.querySelector('[data-delete]')
const allClearButton = document.querySelector('[data-all-clear]')
const previousOperandTextElement = document.querySelector('[data-previous-operand]')
const currentOperandTextElement = document.querySelector('[data-current-operand]')

你可能注意到,document.querySelectorAll 只用于numbersoperation 按钮。嗯,这是因为这些按钮在计算器上出现过几次。

现在我们已经选择了所有的东西,我们可以开始编码,使计算器工作。我们应该考虑的第一件事是我们将如何存储输出。

我们可以通过在文件的顶部创建一个计算器类来做到这一点。在这个类中,我们将放置一个构造函数,它将接受所有的输入和所有的计算器功能。

这个构造函数将接受previousOperandTextElementcurrentOperandTextElement ,这样我们就可以确定将计算器的显示文本放在哪里。我们还需要在这个类中创建一些变量。

一旦我们创建了计算器,我们就应该调用this.clear 函数,因为我们必须重置输入。

下面是代码的模样。

class Calculator {
  constructor(previousOperandTextElement, currentOperandTextElement) {
    this.previousOperandTextElement = previousOperandTextElement
    this.currentOperandTextElement = currentOperandTextElement
    this.clear()
}

计算器的功能

接下来,我们必须定义计算器将执行的不同操作。第一个是clear() 函数,它将清除所有不同的变量。下一个方法是delete() ,用于清除一个single 数字。

我们还将创建一个函数,决定每次用户点击一个数字添加到显示屏上时将会发生什么,这个函数叫做appendNumber(number)

我们需要一个chooseOperation(operation) 函数来控制用户点击任何operation 按钮时将会发生什么。

另一个关键函数是compute() 。它接收你的计算器内的数值并显示结果。

最后,一个updateDisplay() 函数让我们更新输出里面的值。

这些函数在下面的代码片断中得到了说明。

clear() {
}

delete() {
}

appendNumber(number) {
}

chooseOperation(operation) {
}

compute() {
}

updateDisplay() {
}

现在我们已经定义了所有的操作,我们现在可以思考计算器需要存储的不同属性。我们应该确定用户输入的previous operand ,他们正在处理的current operand ,以及他们选择的operation

让我们开始着手处理这些函数。

clear()函数

clear() 函数将删除所有显示的值。如果输出上的值被删除,我们应该将this.currentOperand 设置为一个空字符串。我们也可以对previous operand 。我们必须将this.operation 改为undefined

下面是clear 函数应该是这样的。

clear() {
    this.currentOperand = ''
    this.previousOperand = ''
    this.operation = undefined
  }

接下来,让我们专注于钩住所有的variables ,并使它们对计算器对象进行操作。我们应该做的第一件事是创建一个calculator constant ,并将其设置为new calculator ,然后,我们将构造函数中的所有内容传递给它。然后我们传入previouscurrent operand 文本元素。

下面是它在代码中的样子。

const calculator = new Calculator(previousOperandTextElement, currentOperandTextElement)

appendNumber(number) 函数

现在我们已经传入了这些元素,然后我们可以使用这个计算器对象。

我们将选择一个number 按钮,然后用一个for.each 语句来循环计算所有这些不同的按钮。我们还可以使用button.addEventListener ,在按钮上添加一个EventListener 。只要按钮被点击,EventListener 就会调用一些东西。

在这种情况下,我们将只向计算器添加一个数字。这可以通过调用appendNumber 函数来完成,并使用button.innerText 来显示它。

一旦完成,我们需要调用calculator.updateDisplay 方法,从而确保每次我们点击计算器上的按钮时,displayed values 都会不断更新。

代码片段如下所示。

numberButtons.forEach(button => {
  button.addEventListener('click', () => {
    calculator.appendNumber(button.innerText)
    calculator.updateDisplay()
  })
})

为了确保我们所写的一切都在工作,在updateDisplay() 函数里面,添加this.currentOperandTextElement.innerText = this.currentOperand

appendNumber() 函数里面,我们也将改变current operand ,以匹配该数字,而不是appending 这个数字。

当我们在计算器上点击一个数字时,它应该显示在输出框中。然而,你可能会注意到,当你点击operation 按钮时,什么都没有显示出来。因此,附加的数字功能被正确地分配给所有的按钮。

接下来,让我们写一下appendNumber() 函数。你所需要做的就是更新current operand 的值并追加数字。我们可以使用this.currentOperand ,如果它是一个数字,就把它转换成一个字符串。这样,我们就可以通过使用 "+"轻松地在最后追加一些东西。

注意,我们应该将数字转换成字符串,以防止编译器进行实际操作。当你保存文件并点击数字时,你会发现它们不断被添加到列表中。但是,点击时,句号(.)符号也会被添加。

我们可以通过检查输出中的数字字符串是否包括句号(.),然后return ,来防止这种情况。这将阻止你的函数继续执行。现在,如果你试图添加多个句号,它将只添加一个。

下面是完整的appendNumber()

  appendNumber(number) {
    if (number === '.' && this.currentOperand.includes('.')) return
    this.currentOperand = this.currentOperand.toString() + number.toString()
  }

choiceOperation(operation)函数

我们需要在operation 按钮上使用我们应用于numbers 按钮的相同技术。然而,我们将使用appendNumber ,而不是chooseOperation(button.innerText) ,并使用calculator.updateDisplay 来更新显示。

代码将看起来像这样。

operationButtons.forEach(button => {
  button.addEventListener('click', () => {
    calculator.chooseOperation(button.innerText)
    calculator.updateDisplay()
  })
})

chooseOperation() 函数中,我们需要做一些计算。

当你点击一个数字,然后在计算器上进行操作时,你可能希望它移到上一个操作数部分,让你输入另一个数字来完成整个操作。

例如,如果你想计算2 + 60 ,你可能希望2 + 部分移动到显示的previous operand 部分,这样60 将被输入到current operand 部分。

这个操作可以在chooseOperation() 函数中实现。首先要做的是设置this.operation ,等于你传递的操作。这样,你的计算器就知道它在计算数值时希望使用什么操作。

然后,你设置this.previousOperand = this. currentOperand ,这样你基本上是在说你已经打完了当前的数字,所以你把它回收到前一个操作数。

新的current operand 也需要通过设置为空字符串来清空。你必须更新你的显示。在updateDisplay() 函数中,添加this.previousOperandTextElement = this.previousOperand

如果你去你的计算器,点击任何一个operation 按钮,你会发现即使不点击任何数字按钮,它们也会显示。

我们需要为此添加一个检入。你可以只说如果当前操作是空的,那么return ,这同样不会让你进一步执行到你的代码。

还有一件事,你可以添加到计算器中,就是在完成另一个操作的同时,自动计算一个操作。

例如,如果你输入54 + 50 ,然后点击÷ 按钮,计算器应该能够计算54 + 50 ,在自动除法之前使其成为104

我们可以通过检查previous 操作数是否不等于empty 字符串并调用this.compute() 方法来实现。

下面是代码的样子。

chooseOperation(operation) {
    if (this.currentOperand === '') return
    if (this.previousOperand !== '') {
      this.compute()
    }
    this.operation = operation
    this.previousOperand = this.currentOperand
    this.currentOperand = ''
  }

完成这两个函数后,你现在可以设置计算器内的所有数值。现在我们需要做的是如何计算东西和显示东西。

compute()函数

我们应该给Equals 按钮添加一个EventListener 。EventListener将调用compute 函数并返回结果。然后我们需要更新计算器的显示。当你点击Equals 按钮时,它将调用compute() 函数。

这里是链接到Equals按钮的代码。

equalsButton.addEventListener('click', button => {
  calculator.compute()
  calculator.updateDisplay()
})

我们现在可以进行计算了。你需要做的第一件事是创建一个变量,用来存储计算的结果。然后我们需要创建两个额外的变量。你会有一个前面的变量,这只是要成为你前面操作数的那个实际数字版本。

你只是把这个string 转换为number ,你将对current 操作数做同样的事情。例如,当用户点击Equals ,而没有点击它之前的任何按钮时,你不希望代码运行。你可以通过这种方式来解决这个问题。

你可以说如果它不是前一个数字,也就是说,如果它没有前一个或当前的值,就让它直接return ,这将立即取消这个函数。我们将使用switch 语句来确定或改变计算操作。

下面是compute 函数的代码。

compute() {
    let computation
    const prev = parseFloat(this.previousOperand)
    const current = parseFloat(this.currentOperand)
    if (isNaN(prev) || isNaN(current)) return
    switch (this.operation) {
      case '+':
        computation = prev + current
        break
      case '-':
        computation = prev - current
        break
      case '*':
        computation = prev * current
        break
      case '÷':
        computation = prev / current
        break
      default:
        return
    }
    this.currentOperand = computation
    this.operation = undefined
    this.previousOperand = ''
  }

现在,计算器可以进行计算了。然而,你会发现你不能清除东西,这是因为你还没有实现All-Clear 按钮。

下面是实现这一功能的代码。

allClearButton.addEventListener('click', button => {
  calculator.clear()
  calculator.updateDisplay()
})

现在你可以在计算器上使用All-Clear 按钮了。

delete()函数

最后一个要实现的函数是delete() 。你可以做的第一个方面是设置this.currentOperand = this.currentOperand 。然后我们将这个值转换为string ,通过使用slice 方法获得字符串的最后一个值,如下图所示。

  delete() {
    this.currentOperand = this.currentOperand.toString().slice(0, -1)
  }

现在你可以通过实现删除按钮来钩住这个变量。

下面是做这个的代码。

deleteButton.addEventListener('click', button => {
  calculator.delete()
  calculator.updateDisplay()
})

现在,当你输入一长串字符时,你可以使用Delete 按钮一个一个地删除它们。现在计算器的功能已经齐全了,但显示仍然需要一些工作。

updateDisplay函数

你可以做的第一件事是进入updateDisplay() 函数,添加一个if 语句。如果我们有一个非空的操作,那么我们将显示previous 操作数的文本元素。

然后我们同时显示previouscurrent 操作数,如下图所示。

updateDisplay() {
    if (this.operation != null) {
      this.previousOperandTextElement.innerText =
        `${this.getDisplayNumber(this.previousOperand)} ${this.operation}`

在你的计算器上,你可能会注意到,当你输入一串数字时,没有逗号来使数字更加明确。那么,我们可以通过使用一个辅助函数并调用它getDisplayNumber(number) ,来改变这种情况。然后我们将显示返回的值。

简而言之,我们要在调用updateDisplay() 函数时,将current 操作数添加到该函数中。现在,你在这个函数中所做的改变,将反映在previouscurrent 操作数的值中。

下面是整个updateDisplay() 函数代码的样子。

  updateDisplay() {
    this.currentOperandTextElement.innerText =
      this.getDisplayNumber(this.currentOperand)
    if (this.operation != null) {
      this.previousOperandTextElement.innerText =
        `${this.getDisplayNumber(this.previousOperand)} ${this.operation}`
    } else {
      this.previousOperandTextElement.innerText = ''
    }
  }
}

然而,你会注意到,数字的格式并不正确。这是因为你需要确保在getDisplayNumber() 函数中使用一个float 数字,而不是integer ,如下图所示。

getDisplayNumber(number) {
    const floatNumber = parseFloat(number)
    if (isNaN(floatNumber)) return ''
    return floatNumber.toLocaleString('en')
}

我们需要处理一个小错误。当你输入一个数值如0.0001 ,它将不会显示出来,除非你点击一个不同的数字如0.20.3

这是因为该值不能转换为float 。我们可以通过将你得到的数字分成两部分来解决这个问题:整数部分和小数部分。我们还可以通过将最大分数位数设置为零来消除不必要的小数点,如下图所示。

getDisplayNumber(number) {
    const stringNumber = number.toString()
    const integerDigits = parseFloat(stringNumber.split('.')[0])
    const decimalDigits = stringNumber.split('.')[1]
    let integerDisplay
    if (isNaN(integerDigits)) {
      integerDisplay = ''
    } else {
      integerDisplay = integerDigits.toLocaleString('en', { maximumFractionDigits: 0 })
    }
    if (decimalDigits != null) {
      return `${integerDisplay}.${decimalDigits}`
    } else {
      return integerDisplay
    }
  }

最后,我们需要清除previous 操作数的值。这可以通过创建一个if-else 语句并检查this.previousOperandTextElement.innerText 是否为空字符串来实现。

getDisplayNumber(number) {
if (decimalDigits != null) {
      return `${integerDisplay}.${decimalDigits}`
    } else {
      return integerDisplay
    }
  }

结论

在构建计算器时,我们使用ES6类来组织我们的代码。我们还利用了vanilla JavaScript、CSS Grid和Flexbox。