이야기 정리

eval() 없이 계산기 만들기 본문

Project/JavaScript project

eval() 없이 계산기 만들기

jinhistory 2023. 1. 28. 16:17
eval 함수는 왜 사용하면 안될까?

계산기를 만들기 위해 코드들을 참고하다보면 자주보는 코드가 있다. 바로 eval 함수다. eval 함수는 전달된 문자열을 JS코드로 해석해 해석된 코드를 실행한다. 얼핏 편리해보이는 이 함수를 어째서 사용해선 안될까? 이유는 크게 3가지로 나눌 수 있다.

 

  1. 성능 : 어떤 문자열이 함수에 전달될지 예측하기 어렵다.
  2. 보안 : 텍스트 필드에 악성 코드를 입력하면, eval 함수로 실행되어 보안에 위험이 생긴다.
  3. 디버깅 : eval 함수에 전달되는 값은 문자열이기 때문에 ‘,’를 제대로 명시 하지 않을 시 문제가 발생한다.

참고 : https://developer-talk.tistory.com/271

 

 

위와 같은 이유로 eval함수를 사용하지 않고, 간단한 계산기를 만들었다.

See the Pen calculator by beren-105 (@beren-105) on CodePen.

 

필요한 기능
  1. 기본 사칙연산 기능
  2. AC를 누를 시 초기화된다.
  3. 화면에 누른 숫자들이 보이고, =를 클릭 시 최종값을 보여준다.
  4. 연산자가 연속되어선 안된다.
  5. 키보드를 누를 시, 마우스로 클릭한 것과 동일한 이벤트가 발생해야한다.

- 발생한 문제

  1. eval 함수의 대체방안
  2. if문을 여러개 사용하며 코드가 복잡해진다.
  3. 맨 처음에 연산자가 올 수 없게 하면 -값을 입력할 수 없다.
  4. 이미 버튼 이벤트를 만들었는데, 키보드를 눌렀을 때의 이벤트를 처음부터 끝까지 아예 다시 만들어야하나? 버튼 이벤트를 활용하는 방법은 없을까?

 


- 해결방안

1. eval 함수의 대체방안

  • eval 함수의 경우 다음과 같은 우회 함수를 사용하였다.
let result = (new Function ('return '+ input.value))
answer.value = result()

 

2. if문 남용으로 인한 코드의 복잡함

  • if문 대신 switch문을 사용해 코드를 보기 쉽게 변경했다.
function btnClick(e) {
    const list = e.target.className

    switch (list) {
        case 'num' :
            numberClick(e.target)
            break;

        case 'operator' :
            if (state === true) {
                operatorClick(e.target)
                break;
            } else {break}

        case 'operator minus' :
            if (index === 0) {
                index = 1
                operatorClick(e.target)
                break;
            } else if (state === true) {
                operatorClick(e.target)
                break;
            } else {break}

        case 'equal' :
            if (state === true) {
                let result = (new Function ('return '+ input.value))
                answer.value = result()
                break;
            } else {break}

        case 'reset' : 
            state = true
            input.value = input.value.substring(0, input.value.length - 1)
            break;
        
        case 'all-reset' :
            allReset()
            break;
    }
}

 

3. 맨 처음 -값을 입력할 수 없는 문제

  • 기존 - 역시 다른 +, /, * 연산자와 함께 두었으나, -를 따로 나누고, 함수만 재사용했다.
// +, /, * 연산자
case 'operator' :
    if (state === true) {
        operatorClick(e.target)
        break;
    } else {break}

// - 연산자
case 'operator minus' :
    if (index === 0) {
        index = 1
        operatorClick(e.target)
        break;
    } else if (state === true) {
        operatorClick(e.target)
        break;
    } else {break}

 

4. 이미 버튼 클릭 이벤트를 만들었는데, 똑같은 내용을 키보드 이벤트에 또 넣어야하나?

  • 키보드 이벤트를 만들 때 가장 많은 고민을 한 부분이다. 버튼 이벤트와 키보드 이벤트는 별개로 만들어야한다. 하지만 버튼 이벤트의 내용을 그대로 또 키보드 이벤트에 복사해 붙여넣는 것은 코드만 길어질 뿐이다.
  • 때문에 키보드에 연산자를 계산하는 함수를 다시 만드는 것이 아닌, 키보드를 누르면 버튼이 클릭되게 만들었다.
window.addEventListener('keydown', keyclick)

function keyclick(e) {
    var keyNumber = e.which;

    if (keyNumber > 47 && keyNumber < 58 ||
        keyNumber > 96 && keyNumber < 105 ||
        keyNumber ===  189 ||
        keyNumber ===  187 ||
        keyNumber ===  191 ||
        keyNumber ===  111 ||
        keyNumber ===  107
        ) {
        buttons.forEach((btn) => {
            if (btn.dataset.value == e.key)
            btn.click()
        })
    } else if (keyNumber === 13) {
        buttons[17].click()
    } else if (keyNumber === 8) {
        buttons[2].click()
    } else if (keyNumber === 27) {
        buttons[1].click()
    }
}

 

위 문제점들을 해결하고 완성된 최종 코드다.

const buttons = document.querySelectorAll('button')
const input = document.querySelector('.input')
const answer = document.querySelector('.input-final')

let state = false
let index = 0

buttons.forEach((button) => {
    button.addEventListener('click', btnClick)
})

function btnClick(e) {
    const list = e.target.className

    switch (list) {
        case 'num' :
            numberClick(e.target)
            break;

        case 'operator' :
            if (state === true) {
                operatorClick(e.target)
                break;
            } else {break}

        case 'operator minus' :
            if (index === 0) {
                index = 1
                operatorClick(e.target)
                break;
            } else if (state === true) {
                operatorClick(e.target)
                break;
            } else {break}

        case 'equal' :
            if (state === true) {
                let result = (new Function ('return '+ input.value))
                answer.value = result()
                break;
            } else {break}

        case 'reset' : 
            state = true
            input.value = input.value.substring(0, input.value.length - 1)
            break;
        
        case 'all-reset' :
            allReset()
            break;
    }
}

function numberClick(e) {
    state = true
    index = 1
    input.value += e.dataset.value
}

function operatorClick(e) {
    state = false
    input.value += e.dataset.value
}

function allReset() {
    input.value = ''
    answer.value = '0'
    index = 0
}


window.addEventListener('keydown', keyclick)

function keyclick(e) {
    var keyNumber = e.which;

    if (keyNumber > 47 && keyNumber < 58 ||
        keyNumber > 96 && keyNumber < 105 ||
        keyNumber ===  189 ||
        keyNumber ===  187 ||
        keyNumber ===  191 ||
        keyNumber ===  111 ||
        keyNumber ===  107
        ) {
        buttons.forEach((btn) => {
            if (btn.dataset.value == e.key)
            btn.click()
        })
    } else if (keyNumber === 13) {
        buttons[17].click()
    } else if (keyNumber === 8) {
        buttons[2].click()
    } else if (keyNumber === 27) {
        buttons[1].click()
    }
}
Comments