티스토리 뷰
오늘 페어님이랑 같이 구현한 계산기입니다. 일단 코드 전문부터
const calculator = document.querySelector(".calculator"); // calculator 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const buttons = calculator.querySelector(".calculator__buttons"); // calculator__keys 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const firstOperend = document.querySelector(".calculator__operend--left"); // calculator__operend--left 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const operator = document.querySelector(".calculator__operator"); // calculator__operator 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const secondOperend = document.querySelector(".calculator__operend--right"); // calculator__operend--right 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const calculatedResult = document.querySelector(".calculator__result"); // calculator__result 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
const operatorBtnAll = document.querySelectorAll(".operator");
function calculate(n1, operator, n2) {
let result = 0;
console.log(n1, operator, n2);
if (operator === undefined) return n2;
if (operator === "+") result = parseFloat(n1) + parseFloat(n2);
if (operator === "-") result = parseFloat(n1) - parseFloat(n2);
if (operator === "*") result = parseFloat(n1) * parseFloat(n2);
if (operator === "/") {
result = n1 / n2;
console.log(n1, n2, result);
}
return String(result);
}
buttons.addEventListener("click", function (event) {
// 버튼을 눌렀을 때 작동하는 함수입니다.
const target = event.target; // 클릭된 HTML 엘리먼트의 정보가 저장되어 있습니다.
const action = target.classList[0]; // 클릭된 HTML 엘리먼트에 클레스 정보를 가져옵니다.
const buttonContent = target.textContent; // 클릭된 HTML 엘리먼트의 텍스트 정보를 가져옵니다.
// ! 위 코드(Line 19 - 21)는 수정하지 마세요.
if (target.matches("button")) {
// TODO : 계산기가 작동할 수 있도록 아래 코드를 수정하세요. 작성되어 있는 조건문과 console.log를 활용하시면 쉽게 문제를 풀 수 있습니다.
// 클릭된 HTML 엘리먼트가 button이면
if (action === "number") {
// 그리고 버튼의 클레스가 number이면
// 아래 코드가 작동됩니다.
console.log("숫자 " + buttonContent + " 버튼");
if (
secondOperend.textContent === "0" &&
firstOperend.textContent === "0"
) {
firstOperend.textContent = buttonContent;
} else if (
firstOperend.textContent !== "0" &&
secondOperend.textContent === "0"
) {
secondOperend.textContent = buttonContent;
}
}
if (action === "operator") {
console.log("연산자 " + buttonContent + " 버튼");
operator.textContent = buttonContent;
}
if (action === "decimal") {
// console.log('소수점 버튼');
}
if (action === "clear") {
console.log("초기화 버튼");
firstOperend.textContent = 0;
secondOperend.textContent = 0;
operator.textContent = "+";
calculatedResult.textContent = 0;
}
if (action === "calculate") {
console.log("계산 버튼");
calculatedResult.textContent = calculate(
firstOperend.textContent,
operator.textContent,
secondOperend.textContent
);
}
}
});
// ! Advanced Challenge test와 Nightmare test를 위해서는 아래 주석을 해제하세요.
const display = document.querySelector(".calculator__display--for-advanced"); // calculator__display 엘리먼트와, 그 자식 엘리먼트의 정보를 모두 담고 있습니다.
let firstNum, operatorForAdvanced, previousKey, previousNum;
let count = 0;
let lastCalculate = 0;
let decimalCount = 0;
buttons.addEventListener("click", function (event) {
// 버튼을 눌렀을 때 작동하는 함수입니다.
const target = event.target; // 클릭된 HTML 엘리먼트의 정보가 저장되어 있습니다.
const action = target.classList[0]; // 클릭된 HTML 엘리먼트에 클레스 정보를 가져옵니다.
const buttonContent = target.textContent; // 클릭된 HTML 엘리먼트의 텍스트 정보를 가져옵니다.
// ! 위 코드는 수정하지 마세요.
// ! 여기서부터 Advanced Challenge & Nightmare 과제룰 풀어주세요.
if (target.matches("button")) {
if (action === "number") {
if (
display.textContent === "0" ||
previousKey === "operator" ||
previousKey === "calculate"
) {
display.textContent = buttonContent;
} else {
display.textContent += buttonContent;
}
previousKey = "number";
}
if (action === "operator") {
console.log("연산자 " + buttonContent + " 버튼");
for (let i = 0; i < operatorBtnAll.length; i++) {
let item = operatorBtnAll.item(i);
if (item.textContent === buttonContent) {
item.style.backgroundColor = "#00dac1";
} else {
item.style.backgroundColor = "#313132";
}
}
if (previousKey === "operator") operatorForAdvanced = buttonContent;
if (previousKey === "number" || previousKey === "calculate") {
lastCalculate = display.textContent;
if (count >= 1 && previousKey === "number") {
lastCalculate = calculate(
firstNum,
operatorForAdvanced,
display.textContent
);
}
firstNum = lastCalculate;
operatorForAdvanced = buttonContent;
previousKey = "operator";
count++;
decimalCount = 0;
}
}
if (action === "decimal") {
if (decimalCount === 0) {
if (previousKey === "operator") {
display.textContent = `0${buttonContent}`;
previousKey = "decimal";
} else if (previousKey !== "decimal") {
display.textContent += buttonContent;
previousKey = "decimal";
}
decimalCount++;
}
}
if (action === "clear") {
firstNum = 0;
operatorForAdvanced = undefined;
display.textContent = 0;
previousKey = "clear";
decimalCount = 0;
for (let i = 0; i < operatorBtnAll.length; i++) {
let item = operatorBtnAll.item(i);
item.style.backgroundColor = "#313132";
}
}
if (action === "calculate") {
if (previousKey !== "calculate") {
previousNum = display.textContent;
display.textContent = calculate(
firstNum, // 3
operatorForAdvanced, // undifind
display.textContent // 3
);
}
if (previousKey === "calculate") {
display.textContent = calculate(
firstNum,
operatorForAdvanced,
previousNum
);
}
count = 0;
firstNum = display.textContent;
previousKey = "calculate";
decimalCount = 0;
for (let i = 0; i < operatorBtnAll.length; i++) {
let item = operatorBtnAll.item(i);
item.style.backgroundColor = "#313132";
}
}
console.log("fisrtNum : " + firstNum);
console.log("previousKey : " + previousKey);
console.log("previousNum : " + previousNum);
}
});
오늘 nightmare 난이도 까지 클리어 했습니다. advanced 까지는 쉽게 풀었는데 그 이후 Edge Case 대비 기능 개선에서 많이 힘들었습니다. 그럼, 코드의 주요부분에 대한 설명을 시작하겠습니다.
숫자판 부분
if (action === "number") {
if (
display.textContent === "0" ||
previousKey === "operator" ||
previousKey === "calculate"
) {
display.textContent = buttonContent;
} else {
display.textContent += buttonContent;
}
previousKey = "number";
}
우선 if문으로 number 클래스를 가진 숫자판들을 입력하면 작동되게 하였습니다. 이는 다른 버튼들에서도 마찬가지입니다. 숫자 입력에서 나올 수 있는 경우의 수는 2가지인데
1.초기값0이 있거나 바로 이전에 연산자를 입력하여 숫자를 입력하면 현재값이 초기화되고 입력한 값이 들어오는 상황
2. 1이후에 display에 숫자가 있고, 입력한 값이 그 뒤에 붙어서 나오는 상황이 2가지이다. 이를 구현하기위해
- 우선 if문으로 초기화되는 경우를 나누었다. display에 0이 들어 있거나 이전에 입력한 값의 종류를 기억하는 previousKey이 사칙연산과 = 인 경우에 초기화가 작동하게 만들었다.
- 나머지는 else문에 넣어서 display뒤에 붙이도록 했다. 이때 display는 문자열 타입이기에 하면 사칙연산이 아니라 문자열이 합해지는 결과가 나온다.
- 이후 공통적으로 숫자를 입력했다는 것을 알리기 위해 previousKey에 number을 넣었다.
연산자 부분
if (action === "operator")
{
if (previousKey === "operator") {
operatorForAdvanced = buttonContent;
}
if (previousKey === "number" || previousKey === "calculate")
{
if (count >= 1 && previousKey === "number") {
display.textContent = calculate(
firstNum,
operatorForAdvanced,
display.textContent
);
}
firstNum = display.textContent;
operatorForAdvanced = buttonContent;
previousKey = "operator";
count++;
decimalCount = 0;
}
}
연산자부분은 가장 복잡하고 시간이 많이 든 파트이다. 우선 나올 수 있는 경우의 수로는
1..previousKey가 연산자일 경우, 새로운 연산자를 넣고 종료.
2.previousKey가 숫자나 =일 때에 현재 display에 있는 숫자를 firstNum에 저장하고, 입력한 연산자를 operatorForAdvanced에 저장하고 previousKey에 연산자를 저장한다. 이때 count 변수를 1 증가시키고 .의 개수를 의미하는 decimanlCount를 0으로 초기화한다.
3.count는 =를 누를 때까지 입력한 연산자의 수를 의미한다. 즉, 다중 연산을 구분하기 위한 변수로 count가 1이상이면 =이 아니라도 calculate 함수로 연산을 한다.
소수점 부분
if (action === "decimal") {
if (decimalCount === 0) {
if (previousKey === "operator") {
display.textContent = `0${buttonContent}`;
previousKey = "decimal";
} else if (previousKey !== "decimal") {
display.textContent += buttonContent;
previousKey = "decimal";
}
decimalCount++;
}
소수점 부분에서 유의할 점은 .이 여러개가 되는 것을 막는 것이다. 이를 위해
1.decimalCount라는 변수를 만들어 0일 때만 작동하고 1을 증가시켜 중복을 막았다.
2.연산자를 입력 후 .을 입력하면 자동으로 0.으로 출력하게 만들었다.
3.다른 숫자가 있는 경우에는 그냥 뒤에 붙여주었다.
초기화 부분
if (action === "clear") {
firstNum = 0;
operatorForAdvanced = undefined;
display.textContent = 0;
previousKey = "clear";
decimalCount = 0;
}
엽력 값들을 초기화 시켜주는 부분으로 주요 포인트는 operatorForAdvanced에 undefind를 주는 것이다.
=부분
if (action === "calculate") {
if (previousKey !== "calculate") {
previousNum = display.textContent;
display.textContent = calculate(
firstNum,
operatorForAdvanced,
display.textContent
);
}
if (previousKey === "calculate") {
display.textContent = calculate(
firstNum,
operatorForAdvanced,
previousNum
);
}
count = 0;
firstNum = display.textContent;
previousKey = "calculate";
decimalCount = 0;
}
두번째 난관 =이다. 그냥 함수로 넘기면 되지 않냐? 라고 물을 수 있지만 =을 연속적으로 누르면 이전 연산을 반복시킨다는 조건이 달려서 힘들었다. 간단히 설명하면
1.일반적인 경우는 firstNum과 display로 calculate함수를 돌린다
2.previousNum에 이전 연산 결과를 저장해 =가 연속으로 누르면 display 대신 previousNum으로 calculate함수를 돌린다.
3.이후 count들의 초기화를 진행한다.
느낀점
- 이번 실습에서는 머리로는 상상할 수 있는데 입과 손으로는 구현하지 못한다는 답답함을 느낄수 있었다.
- 이는 페어분도 마찬가지로 능력부족보다는 경험부족이 주된 원인이다.
- 오히려 이 부분이 더 마음에 드는데 현장에서 의사소통하는 연습을 할 수 있어 시행착오를 줄이는데 도움이 된다.
- 다만 다 끝나고 레퍼런스를 보니 내 코드가 먼가 안 좋아 보인다. "방금까지 아름다워다고 생각하던 나의 생산라인~ 무슨 원숭이 새끼들 파파야 까먹으로고 줄서는 것처럼 보인다 "라는 어떤 대머리의 명언이 떠오르는데 딱 이 기분이다.
'문제풀이' 카테고리의 다른 글
[코플릿]이진트리 후위순회(postorder) (0) | 2023.05.11 |
---|---|
[코플릿]박스 포장 (0) | 2023.05.10 |
[코플릿] 유효한 괄호쌍 (0) | 2023.05.10 |
프로그래머스 LV.0 (Javascript) 옹알이 (1) (0) | 2023.04.10 |
fe-sprint-react-twittler-intro-reference 의사코드 (0) | 2023.03.27 |