如何使用JavaScript构建一个计算器
构建一个网络计算器是一个很好的项目,尤其是当你刚刚开始学习JavaScript时。它对任何技能水平的人来说都很简单。这个项目涵盖了与UI的交互和关键的JavaScript方法。
在这篇文章中,我们将带领你了解各种HTML和CSS元素,以及在构建一个功能性和响应性强的计算器时使用的Vanilla JavaScript和现代ES6实践,如下面的图片所示。

先决条件
- 任何好的文本编辑器。
- 对JavaScript和HTML的基本理解。
计算器的设计
为了开始工作,你需要考虑计算器的基本功能。它们包括addition 、subtraction 、multiplication 、division 、delete 、all-clear ,当然还有在执行这些操作时使用decimal numbers 的能力。
在你的文本编辑器中,为你的HTML,CSS 和JavaScript 创建独立的文件夹three 。这只是基本上使你的代码更有条理。
在你的HTML 文件夹中,你可以使用下面的代码链接CSS 和JavaScript 文件。
<!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 elements 和buttons 放在上面的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 除法符号(÷),因为它在你的键盘上是不可用的。
这就是你的计算器在这一点上的样子。

计算器的样式
接下来,我们需要使用CSS ,为计算器设计样式。首先,选择所有的元素,包括before 和after 元素。然后,我们可以应用box-sizing 属性并将其设置为border-box 。
你也可以使用下面的代码改变计算器的font-family 和font-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进行样式化。它包裹了我们所有不同的buttons 和elements 。我们可以把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-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-clear 和Delete 按钮。我们可以将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 。我们应该在输出类中对previous 和current 操作数进行样式设置。
最后的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-clear 和Delete 按钮,以及你的previousOperandTextElement 和currentOperandTextElement 。
代码会是这样的。
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 只用于numbers 和operation 按钮。嗯,这是因为这些按钮在计算器上出现过几次。
现在我们已经选择了所有的东西,我们可以开始编码,使计算器工作。我们应该考虑的第一件事是我们将如何存储输出。
我们可以通过在文件的顶部创建一个计算器类来做到这一点。在这个类中,我们将放置一个构造函数,它将接受所有的输入和所有的计算器功能。
这个构造函数将接受previousOperandTextElement 和currentOperandTextElement ,这样我们就可以确定将计算器的显示文本放在哪里。我们还需要在这个类中创建一些变量。
一旦我们创建了计算器,我们就应该调用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 ,然后,我们将构造函数中的所有内容传递给它。然后我们传入previous 和current 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 操作数的文本元素。
然后我们同时显示previous 和current 操作数,如下图所示。
updateDisplay() {
if (this.operation != null) {
this.previousOperandTextElement.innerText =
`${this.getDisplayNumber(this.previousOperand)} ${this.operation}`
在你的计算器上,你可能会注意到,当你输入一串数字时,没有逗号来使数字更加明确。那么,我们可以通过使用一个辅助函数并调用它getDisplayNumber(number) ,来改变这种情况。然后我们将显示返回的值。
简而言之,我们要在调用updateDisplay() 函数时,将current 操作数添加到该函数中。现在,你在这个函数中所做的改变,将反映在previous 和current 操作数的值中。
下面是整个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.2 或0.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。