[자바스크립트 계산기 만들어서 티스토리 블로그에 올리기]
See the Pen SimpleCalculator by YYYSYYY (@SayYoung) on CodePen.
아래 사이트 참고하여, 티스토리 블로그에 자바스크립트를 이용하여 계산기 만드는 과정을 배웠음
(https://kanhi.tistory.com/2)
아래 내용은 위 블로그의 글과 거의 흡사하고, 중간중간 의문가는 사항이나 이해하는 과정에서의 코멘트를 파란 글씨로 추가했음.
1. 태그 배우기
– 태그를 통해 유저가 숫자나 연산자를 클릭할 수 있게 한다
– 을 격자로 표현하기 위해
를 사용한다.
– 사용자로부터 값을 입력받을
2. 뼈대 만들기
아래와 같이 폴더와 html파일을 만든다.
html 코드 작성
Calculator
* 여기까지 결과 화면
이걸 계산기 모양대로 바꿔줘야 한다. css파일을 이용한다.
3. css파일 만들기
*css 문법 이해
css의 문법은 선택자와 선언부로 구성됨.
선택자는 css 적용을 위한 html요소를 가리킨다. 선언부는 하나 이상의 세미콜론으로 구분하여 포함 가능하며
중괄호로 전체를 둘러싼다.
(출처: http://tcpschool.com/css/css_intro_syntax)
*css 선택자는 다음과 같다
– Html 요소 선택자
h2 { color: teal; text-decoration: underline; }
– Id 요소 선택자
#heading { color: teal; text-decoration: line-through; }
Class 선택자
.headings { color: lime; text-decoration: overline; }
html요소는 특수문자 없이 선택하면 되고, id요소는 #기호, Class요소는 .기호를 앞에 붙여서 선택하는 것을 알 수 있다.
Group 선택자(위의 여러 선택자를 같이 사용하고자 할 때)
h1 { color: navy; }
h1, h2 { text-align: center; }
h1, h2, p { background-color: lightgray; }
*css 코드
/* style.css */
/* main이라는 엘리먼트의 너비를 150으로 고정함 */
/*main 이라는 html요소를 선택자로 하고 넓이를 150픽셀로 설정*/
main {
width: 150px;
}
/*button-wrap이라는 class 요소를 선택자로 하고, grid 디스플레이 적용,
repeat(반복횟수, 반복값)
*/
.button-wrap {
display: grid;
/* 한 줄에 4개씩, 모두 동일한 비율 적용(1:1:1:1) */
grid-template-columns: repeat(4, 1fr);
}
* 여기까지 결과화면
4. 별로 예쁘지 않으니까 버튼 크기 등을 조절해 준다
아래처럼 바꿔주려 한다.
css에서 조정해 줘야 하는데, a버튼과 0버튼은 다른 버튼과 크기가 다르다.
Html 코드에서 class 를 추가하여 css에서 다룰 수 있게 해 준다.
– html 코드 수정
-css 코드 추가
/*ac버튼 크기 조정*/
.ac {
/*4등분하여 나온 5개의 선 중에서 첫번째 선부터 4번째 선까지 지정*/
grid-column: 1/4;
}
/*0버튼 크기 조정*/
.zero {
/*4등분하여 나온 5개의 선 중에서 첫번째 선부터 3번째 선까지 지정*/
grid-column: 1/3;
}
*여기까지 결과화면
5. input 태그 스타일 설정(꾸미기)
여긴 색칠하고 그런거니까.. 복붙 하고 넘어가고 나중에 공부하기로 하자. 우선 기능을 만들어 보자
– css파일에 아래와 같이 추가해 준다.
* {
box-sizing: border-box;
color: white;
}
input, button {
height: 35px;
outline: none;
}
input {
width: 100%;
text-align: right;
border: none;
background: #5B5B5D;
padding-right: 1rem;
font-size: 2rem;
}
6. 버튼 태그 스타일 설정(꾸미기)
여기도 마찬가지로 복붙하고 넘어간다.
– css 코드 수정
button {
background: #828284;
border: 1px solid #454448;
font-size: 1rem;
}
/* nth-child(4n+2): 4번째 요소마다 스타일을 적용하는데 처음에만 두번째에 적용 */
button:nth-child(4n+2), button:last-child {
background-color: orange;
}
button:hover {
opacity: .5;
}
/*ac버튼 크기 조정*/
.ac {
/*4등분하여 나온 5개의 선 중에서 첫번째 선부터 4번째 선까지 지정*/
grid-column: 1/4;
background: #6A6A6C;
}
/*0버튼 크기 조정*/
.zero {
/*4등분하여 나온 5개의 선 중에서 첫번째 선부터 3번째 선까지 지정*/
grid-column: 1/3;
}
*여기까지 결과화면
7. 버튼 클릭 시 input에 추가
버튼을 클릭하면 값이 입력되도록 하는 작업이다. data-type에 따라 동작을 다르게 처리하기 위해, 숫자 버튼을 제외하고 data-type 속성을 추가해 준다
– ac버튼 : data-type=“ac”
– 연산자 버튼 : data-type=“operator”
– = 버튼 : data-tyle =“equals”
– html 코드 수정
*자바스크립트 파일을 만든다(파일명:script.js)
8. 버튼과 input 태그를 가져와서 변수에 담는다
– javascript 코드
const buttons = document.querySelectorAll(‘button’)
//자바스크립트 코드에 ‘buttons’ 라는 변수를 선언한다. css 문서 내의 ‘button’이라는 선택자에 해당하는 모든 요소를 리스트 타입으로 반환한다. 계산기에서 각 버튼들을 의미한다.
const displayElement = document.querySelector(‘input’)
//자바스크립트 코드에 ‘displayElemnet’ 라는 변수를 선언한다. css 문서 내의 ‘input’이라는 선택자에 해당하는 모든 요소를 리스트 타입으로 반환한다. 계산기에서 입력된 내용을 보여주는 부분이다.
querySelector()
document.querySelector() 는 입력한 선택자와 일치하는 문서 내의 첫 번째 element를 반환한다. 일치하는 요소가 없다면 null을 반환한다. 괄호 안에 들어가는 매개변수는 유효한 CSS 선택자여야한다.
querySelectorAll()
querySelector()가 한 개의 요소를 반환했다면, querySelectorAll()은 주어진 CSS 선택자와 일치하는 모든 요소를 반환한다. 이때, 반환 타입은 리스트 타입이다. 따라서 인덱스를 통해 조작할 수 있다.
9. html 파일에 script.js 연결
10. Calculator 클래스 만들고 인스턴스 생성
무슨말인지 모르겠다. 클래스는 뭐고 인스턴스는 뭘까.
예를 들면 이런 거라고 한다.
붕어빵틀 = class
붕어빵 = object
각각의 붕어빵 = instance
붕어빵을 굽다 = 인스턴스(instance)화 하다
생성자(constructor) =
클래스를 생성할 때 생성자는 반드시 존재해야 한다
생성자를 선언할 때 클래스의 이름과 생성자의 이름은 반드시 같아야 한다.
constructor 매서드(작업)는 class 내에서 객체를 생성하고 초기화하기 위한 특별한 메서드다.
내가 하는 작업에 적용해 보자면, “계산기”는 “붕어빵틀, class”이다. 무언가를 만들기 위한 기본 뼈대다. 이 도구를 통해서 입력값을 받아서 “결과값”을 내놓을 것이다. 이 “결과값”이 “붕어빵,instance”이라고 이해하면 될 것 같다. instance는 구체적인 상태(variable, 변수)와 행위(method, 기능)의 집합이다.
생성자의 개념은 잘 와 닿지 않는다. 비유하자면 생성자(constructor)는 업무를 하기 전에 하는 책상정리와 비슷하다고 한다(초기화).
(참조: https://whatisthenext.tistory.com/1)
class Calculator {
//class를 선언한다
constructor(displayElement) {
//생성자 함수를 통해 displatElement의 초기상태를 지정하기 위한 책상정리를 한다.
this.displayElement = displayElement
//this는 인스턴스 자신을 가리키는 참조변수다.
//Calculator클래스의 안에 있는 인스턴스 변수 displayElement에 displayElement를 담는다
this.displayContent = ”
//Calculator클래스의 안에 있는 인스턴스 변수 displayContent에 빈 문자열을 담는다
}
}
const buttons = document.querySelectorAll(‘button’)
const displayElement = document.querySelector(‘input’)
const calculator = new Calculator(displayElement)
//new 함수를 통해 Calculator라는 객체를 만든다. 이 객체에는 위에서 정의한 것과 같이 displayElement와 displayContent가 세팅되어 있다.
11. addEventListener로 모든 버튼에 클릭 이벤트를 연결하고 switch문으로 data-type에 따라 버튼 구분하기
event란 ‘사용자가 어떤 결과를 유발시킬만한 행동을 하는 것’이다. 사용자가 클릭을 한다거나 페이지를 넘긴다거나 하는 것을 말한다.
자바스크립트에서는 특정 이벤트가 발생했을 시 특정 함수를 실행할 수 있게 해주는 addEventListener라는 기능이 존재한다.
▶ addEventListener 등으로 등록할 수 있는 이벤트 중 자주 쓰이는 이벤트 목록
(출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=qbxlvnf11&logNo=220877806711)
이벤트 명 설명 change 변동이 있을 때 발생 click 클릭시 발생 focus 포커스를 얻었을 때 발생 keydown 키를 눌렀을 때 발생 keyup 키에서 손을 땟을 때 발생 load 로드가 완료 되었을 때 발생 mousedown 마우스를 클릭 했을 때 발생 mouseout 마우스가 특정 객체 밖으로 나갔을 때 발생 mouseover 마우스가 특정 객체 위로 올려졌을 때 발생 mousemove 마우스가 움직였을 때 발생 mouseup 마우스에서 손을 땟을 때 발생 select option 태그 등에서 선택을 햇을 때 발생
// …
const buttons = document.querySelectorAll(‘button’)
const displayElement = document.querySelector(‘input’)
const calculator = new Calculator(displayElement)
//arrow함수 forEach문은 다음과 같다.
//리스트.forEach(원소=>함수(원소));
//리스트 내의 각 원소를 함소 안에 차례로 넣는다.
buttons.forEach(button => {
<
switch (button.dataset.type) {
case ‘operator’:
console.log(‘operator’)
break
case ‘ac’:
console.log(‘ac’)
break
case ‘equals’:
console.log(‘equals’)
break
default:
console.log(‘number’)
break
}
})
})
12. 숫자 버튼을 클릭할 때마다 displayContent 속성에 숫자가 추가되고 input에도 표시되도록 appendNumber, updateDisplay 메서드 추가 switch문의 default에서 추가한 메서드 호출
class Calculator {
constructor(displayElement) {
this.displayElement = displayElement
//this.displaycontent=” 라고 표시했다. 따옴표 표시했으므로 자료형은 문자열이다.
this.displayContent = ”
}
//appendNumber 메서드(작업) 추가.
appendNumber(number) {
//+= 는 복합대입연산자인데, this.displayContent = this.displayContent + number와 같은 의미를 가진다. 문자열(String)에 +기호를 쓴 경우 덧셈을 하라는 것이 아니라 문자열을 잇는다는 것이다. ex) 10 + 1 = 101
this.displayContent += number
}
//updateDisplay 메서드(작업)추가.
//this.displayElemnet의 값(value)에 this.displayContent를 담는다.
updateDisplay() {
this.displayElement.value = this.displayContent
}
}
const buttons = document.querySelectorAll(‘button’)
const displayElement = document.querySelector(‘input’)
const calculator = new Calculator(displayElement)
buttons.forEach(button => {
button.addEventListener(‘click’, () => {
switch (button.dataset.type) {
case ‘operator’:
console.log(‘operator’)
break
case ‘ac’:
console.log(‘ac’)
break
case ‘equals’:
console.log(‘equals’)
break
default:
calculator.appendNumber(button.innerText)
calculator.updateDisplay()
break
}
})
})
13. 마찬가지로 클래스에 appendOperator 메서드 추가 후 연산자 버튼 클릭 시 호출되도록 연결
class Calculator {
constructor(displayElement) {
this.displayElement = displayElement
this.displayContent = ”
}
appendNumber(number) {
this.displayContent += number
}
//this.displayContent = this.displayContent + operator 의 의미다.
appendOperator(operator) {
this.displayContent += operator
}
updateDisplay() {
this.displayElement.value = this.displayContent
}
}
const buttons = document.querySelectorAll(‘button’)
const displayElement = document.querySelector(‘input’)
const calculator = new Calculator(displayElement)
//클릭한 버튼이 연산자이면, calculator 클래스의 appendOperator 메서드를 추가한다.
//element.innerText;는 자바스크립트에 내장된 기능이다. HTML element 안의 텍스트를 가져온다.
buttons.forEach(button => {
button.addEventListener(‘click’, () => {
switch (button.dataset.type) {
case ‘operator’:
calculator.appendOperator(button.innerText)
calculator.updateDisplay()
break
//…
}
})
})
14. AC 기능 구현
* ac버튼을 누르면 모든 입력이 초기화되도록 클래스에 clear 메서드 추가 후 ac버튼과 연결
class Calculator {
// …
clear() {
this.displayContent = ”
this.displayElement.value = 0
}
}
const buttons = document.querySelectorAll(‘button’)
const displayElement = document.querySelector(‘input’)
const calculator = new Calculator(displayElement)
buttons.forEach(button => {
button.addEventListener(‘click’, () => {
switch (button.dataset.type) {
case ‘ac’:
calculator.clear()
break
//…
}
})
})
* constructor에서도 clear 메서드를 호출하도록 수정
class Calculator {
constructor(displayElement) {
this.displayElement = displayElement
this.displayContent = ”
this.clear()
}
* 여기까지 결과화면
15. 계산 기능 구현
*eval() 함수.
자바스크립트에서 사칙연산과 관련된 산술 연산자는 +, -, *, /이다.
자바스크립트의 eval 함수를 사용하면 계산 기능을 쉽게 구현할 수 있다.
eval()은 문자로 표현된 JavaScript 코드를 실행하는 함수이다.
간단한 예로 a 변수에 2 + 3 * 5 연산식을 저장하고 eval 함수에 인자로 a를 넣고 실행하면 식이 계산된다.
let a = ‘2 + 3 * 5’
eval(a) // 17
class Calculator {
//…
compute() {
this.displayContent = eval(this.displayContent)
}
}
* 계속해서 클래스에 compute 메서드를 추가하고 eval() 함수를 사용해서 계산 기능 구현
class Calculator {
//…
compute() {
this.displayContent = eval(this.displayContent)
}
}
* “=” 버튼 클릭 시 compute 메서드를 호출하도록 연결
buttons.forEach(button => {
button.addEventListener(‘click’, () => {
switch (button.dataset.type) {
// …
case ‘equals’:
calculator.compute()
calculator.updateDisplay()
break
default:
calculator.appendNumber(button.innerText)
calculator.updateDisplay()
break
}
})
})
더하기와 빼기는 잘 되지만 곱하기와 나누기는 에러가 발생. 자바스크립트에서는 곱하기와 나누기가 ×, ÷가 아닌 *, / 를 사용하므로
replace를 사용하여 × -> *로, ÷ -> / 로 변경해준 후 계산
class Calculator {
// …
compute() {
this.displayContent = eval(this.displayContent
//”\u00D7″ 는 ×, “\u00F7″는 ÷를 의미한다.
.replace(‘\u00D7’, ‘*’)
.replace(‘\u00F7’, ‘/’)
)
}
}
* 여기까지 결과화면. 기본적인 기능을 한다.
2022-04-20 · 5 min read · 1211
위와 같은 자바스크립트 계산기를 만들어 본다. 친절한 튜토리얼은 아니다. 어떤 흐름으로 내가 계산기를 구현했는지 되짚어보는 글이다.
기능 명세
구현된 계산기는 맥북의 계산기를 본떠 제작되었다. 단, 소수점 지원은 되지 않는다. 이 기능은 toFixed 를 적재적소에 쓰면 아마 구현되지 않을까 싶다. 또한 첫 시작에 음수를 지원하지 않는다.
어쨌거나 기본적으로 계산할 수 있는 기능은 다 구현 했으니 인터넷 검색을 하다 흘러 오셨다면 계산기 구현에 참고하시길 바란다. 🙏
잡소리가 많았는데 기능 명세는 다음과 같다.
리셋 기능(AC) 더하기, 곱하기, 제곱, 나누기, 빼기 연산 기능
기능 명세라기도 부끄럽네.. 이게 끝이다. 🤣
구현하기
html과 css는 스킵하고 자바스크립트에만 초점을 맞춰 진행하도록 한다. 자세한 코드는 코드 샌드박스 혹은 깃허브에서 확인할 수 있으니 확인 바랍니다. 🙏 스타 한 번만 눌러주세..요
DOM 선택
HTML 파일을 확인하면 알겠지만, 계산기 패드들을 calc-body 라는 class로 감싸고 있다. 패드들에 이벤트 핸들러를 걸어야하므로 calc-body 를 DOM API로 불러와야 한다. 그리고 패드를 눌렀거나 or 값이 계산 완료 됐을 때는 값이 나타나는 곳을 calc-value 라는 곳에 보여줄 것이므로 요 녀석도 불러온다.
코드는 다음과 같다.
const calcBody = document . querySelectorAll ( ‘.calc-body’ ); const calcValue = document . querySelector ( ‘.calc-value’ );
calcBody 를 querySelectorAll 로 불러왔다. 다들 알다시피 querySelectorAll 로 element를 불러오게 되면 배열을 갖고 온다. 그러니 반복문을 돌려서 핸들러를 달아주자.
function handleClickBtn ( e ) { } calcBody. forEach ( ( button ) => { button. addEventListener ( ‘click’ , handleClickBtn); });
초기 설정 함수
자바스크립트 코드를 작성하면 꼭 하는 것은 초기 진입점을 잡는 init() 함수를 생성하는 것이다. 자바스크립트에는 c++처럼 main() 함수를 진입점으로 삼지 않기 때문에 대충 따라한다고 생각하면 된다.
리액트에 빗대어 얘기 하면 초기 index.js 에서 실행하는 ReactDOM.render() 의 역할이랑 비슷한 셈이다.
function init () { }
아까 핸들러를 달아주는 반복문을 init() 내에 설정한다.
function renderNumber ( number ) { calcValue. textContent = number; } function init ( ) { calcBody. forEach ( ( button ) => { button. addEventListener ( ‘click’ , handleClickBtn); }); renderNumber ( 0 ); } init ();
init() 에는 아까 만든 핸들러 달아주는 반복문이 위치하고 계산기의 초기 값을 렌더링(화면에 그려줌)해주는 renderNumber() 도 위치했다.
요로코롬 해놓으면 계산기를 보기 위해 페이지를 열때 초기에 init() 가 실행되어 계산기에는 0이란 값이 보일 것이다.
계산기 패드 이벤트 만들기
계산기 패드를 눌렀을 때 이벤트를 생각해 보자. 사실 패드 이벤트만 걸면 계산기는 끝이다. 벌써 끝이.. 보인다.
어떤 식으로 구현하면 좋을까? 🤔
일단 나는 모든 패드들에 똑같은 이벤트 핸들러를 달아주었다. 패드는 숫자 입력 패드, 초기화 패드, 연산 패드(+, – 와 같은)로 나뉘어져있다.
그렇다. 이것을 분기 처리하고 각각에 맞는 함수를 구현하면 된다.
물론 나는 모든 패드에 동일하게 걸었으나 사람에 따라 천차만별로 구현할 수 있다. 숫자 따로, 연산 따로, 초기화 따로 등 다양한 방법들이 있다.
function handleClickBtn ( e ) { const { value } = e. target ; if ( /[0-9]/ . test (value)) { return setNumber (value); } if (value === ‘AC’ ) { return reset (); } if (value === ‘=’ ) { return finish (); } return setOperation (value); }
다양한 상황을 분기 처리해야 하므로 조건문을 사용한다. 👍
핸들러 내에서 조건문으로 분기를 나눴다면 헬퍼 함수들을 만들어 보자.
앞서 코드에서 선언한 setNumber , reset , finish , setOperation 함수를 정의할 것이다.
얘네 4개만 잘 만들면 끝인 것이다~ 🙂
계산기용 함수 생성
굳이 함수를 잘게 나눠 생성할 의무는 없지만 보다 깔끔한 코드 작성을 위함이라 생각하면 좋다. + 재사용성이나 유지보수면도 있고
1. setNumber
setNumber() 함수는 이름에서도 알 수 있듯이 숫자 값을 저장하는 함수다. 잠깐, 그러고보니 저장하기 위해서는 저장할 수 있는 그릇이 필요한데, 아직 선언을 안했다.
const calcValueObj = { currentValue : 0 , temp : 0 , operator : ” , };
간략하게 주석으로 설명을 달았으니 참고하시고. 저장할 그릇을 만들었으니 이제 본격적으로 함수를 만들어 보자.
function setNumber ( value ) { if (calcValueObj. operator ) { const result = String (calcValueObj. temp ) + value; calcValueObj. temp = Number (result); renderNumber (calcValueObj. temp ); } else { const result = String (calcValueObj. currentValue ) + value; calcValueObj. currentValue = Number (result); renderNumber (calcValueObj. currentValue ); } }
참고로 모든 코드는 객체의 참조 값을 변경시키는 식으로 진행된다. (즉 불변이 아니란 소리)
여기서도 분기 처리를 하게 된다.
calcValueObj.operator 에 값이 있으면, 계산을 할 것이므로 calcValueObj.temp 에 임시 저장 시키고 그 값을 계산기에 렌더링한다.
만약 값이 없으면 calcValueObj. currentValue 에 저장시키고 이 값을 계산기에 렌더링한다.
정리해 보자면 연산자가 입력된 상태일때는 항상 temp 에 숫자 패드로 누른 값을 저장시키고 그게 아니라면 항상 currentValue 에 저장시킨다.
2. reset
reset 함수는 쉽다. AC 를 누르면 모든 값을 초기화 시킬 것이다.
function reset ( ) { calcValueObj. currentValue = 0 ; calcValueObj. temp = 0 ; calcValueObj. operator = ” ; renderNumber ( 0 ); }
딱히 설명할 게 없다. 초기 값으로 다 되돌릴 뿐이다.
3. finish
finish 함수도 설명할 게 딱히 없다. = 를 누르게 되면 연산을 하고 그 연산값을 렌더링한다.
function finish ( ) { calculator (); renderNumber (calcValueObj. currentValue ); calcValueObj. operator = ” ; }
4. setOperation
+ , – , ^ 와 같은 연산자를 누르게 되면 호출되는 함수다.
function setOperation ( operator ) { if (calcValueObj. currentValue && calcValueObj. temp ) { calculator (); renderNumber (calcValueObj. currentValue ); } calcValueObj. operator = operator; }
여기서도 조건문 처리할 게 있다.
temp에 저장된 값이 있다면 계산될 준비가 되어있는 것이므로 계산을 마무리시킨다. 그리고 다음에 계산을 진행할 operator 값을 저장한다.
이러지 않으면 의도대로 계산 진행이 되지 않는 불상사가 생긴다.
계산 헬퍼 함수
위 코드를 자세히 보면 함수 내에 아까 finish() 를 설명했을 때 보인 calculator 함수가 있다. 이 녀석의 정체는 우리의 계산을 도와줄 헬퍼 함수이다.
이 계산 헬퍼 함수는 switch로 calcValueObj.operator 에 저장되어 있는 연산 기호에 맞춰 계산을 진행한다. 끝나면 계산 완료가 되었으므로 계산하기 위해 temp 에 임시로 저장한 값은 초기화시킨다.
function calculator ( ) { switch (calcValueObj. operator ) { case ‘+’ : { calcValueObj. currentValue += calcValueObj. temp ; break ; } case ‘*’ : { calcValueObj. currentValue *= calcValueObj. temp ; break ; } case ‘-‘ : { calcValueObj. currentValue -= calcValueObj. temp ; break ; } case ‘/’ : { calcValueObj. currentValue /= calcValueObj. temp ; break ; } case ‘%’ : { calcValueObj. currentValue %= calcValueObj. temp ; break ; } case ‘^’ : { calcValueObj. currentValue **= calcValueObj. temp ; break ; } default : { break ; } } calcValueObj. temp = 0 ; }
이렇게 계산기 구현은 모두 끝났다.
마무리
지금까지 계산기를 만들어 봤다. 아무래도 프로그래밍 초보 시절에 가장 많이 하는 과제(?)가 계산기일 것 같은데 구현해 보면서 옛날 생각도 나고 재밌었다.
나도 계산기를 만드는 과제를 진행한 적이 있었는데.. 참 그때는 뭐가 그리 어려웠는지~ 🤣
완벽한 계산기도 아니고, 모범 코드도 아니지만 계산기 구현을 위해 헤맬 모든 분들을 위해 공유해본다. 🙇♂️