탱구탱구 개발자 일기

흐.. ㅠㅠ 자바스크립트 프로그램 코드에서 f와 f()의 차이로 인해 어제 오늘 엄청 헤맸다.

 

예제

function f1() {
   console.log("A");
}

// Question f1; vs f1(); ??

위와 같이 함수 f1에 대해 정의 하였다. 이 때 f1;과 f1();을 실행했을 때 콘솔에 찍히는 결과는 무엇일까??

결과

f1; // => ƒ f1() { console.log("A");}

f1(); // => A undefiend

 

 

위와 같은 결과가 나오는 이유를 영어문장으로 표현하면 아래와 같다.

Keep in mind that in JavaScript a function is an object, passed around like any other variable. So this is a reference to the function. This, on the other hand, executes the function and evaluates to its result:

 

첫 번째 this가 바로 f1; 이고 두 번째 this가 f1();을 뜻한다.

 

위 문장을 해석했을 때 괄호 없이 f1; 만 부른다면 f1에 담긴 함수 객체의 참조를 부른 것이다.

 

말 그대로 자바스크립트에서 함수도 객체이기 때문에 위와 같이 함수 선언문을 통해 함수 선언을 하면 자바스크립트 내부적으로는 함수 이름을 변수 이름으로 한 변수함수 객체를 생성한다. 그리고 그 변수에 함수 객체의 참조가 저장되기 때문이다.

let a = 10;
// a ??

 이건 위와 같이 변수 a에 10이라는 값이 저장되고 a가 무엇이냐고 물은 것과 동일하다.


반면에 f1에 괄호를 붙이면 f1이라는 함수를 실행하고 그 결과를 평가한 것이다. 이것을 함수를 호출했다고 표현한다.

 

따라서 함수 f1 내부 코드를 읽고 코드를 평가하여 console.log("A"); 를 평가하여 A가 출력되었고 return 문이 실행되지 않은 상태로 마지막 문장이 실행되었기 때문에 호출 코드로 돌아가 undefined라는 반환값을 출력한 것이다.

만약 return문이 있었다면 그것을 반환값으로 출력했을 것이다.


그렇다면 아래 코드를 보자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        window.onload = function () {

            let button = document.getElementById("btn");

            button.addEventListener("click", changeBgColor(getColor()), false);

            function changeBgColor(color) {
                console.log("color:", color);
                return function (e) {
                    e.currentTarget.style.backgroundColor = color;
                };
            }
        };

        function getColor() {
            let selectedColor = document.getElementById("colorBox")
                .options[document.getElementById("colorBox")
                .selectedIndex].value;
            return selectedColor;
        }
    </script>
</head>
<body>
<select id="colorBox" onchange="getColor()">
    <option value="red">red</option>
    <option value="blue">blue</option>
</select>
<button id="btn">버튼</button>
</body>
</html>

 

내가 원했던 기능은 select 박스에서 색상을 선택하고 버튼을 클릭했을 때 addEventListener에 등록된 이벤트 리스너가 실행되어 선택한 색을 파라미터로 받아서 버튼 색상을 변경시키는 프로그램이었다.

 

addEventListener 메서드의 리스너 부분에 익명함수가 아닌 일반 함수를 사용하는 것 뿐만 아니라 이벤트 객체와 색상 정보가 담긴 파라미터 분리하고 싶다는 생각에 위와 같이 코드를 작성하였다.

 

결과는 버튼이 빨간색으로 변경된 후 아무리 파란색으로 옵션을 바꾼다음 클릭을 해도 값이 변경되지 않는 문제가 발생했다. 아래 코드펜에서도 확인할 수 있다.

See the Pen JjXrJwv by saintnun150 (@saintnun150) on CodePen.

 

 

 


이 문제가 바로 f1;을 실행한 것과 f1();으로 실행한 것과 연관되어 있다.

 

HTML이 그려지고 window.onload에 적힌 코드가 실행된다. 이 때 addEventListener 메서드의 두 번째 파라미터에 적힌 이벤트 리스너부분에 changeBgColor(getColor())라고 적었다. 이는 해당 함수를 호출한 것이다. 정확하게는 getColor(), changeBgColor() 이렇게 각각의 함수가 호출된 것이다.

getColor(); // red;

changeBgColor(color); // 파라미터 color에 getColor() 함수의 반환값인 red가 들어감

// changeBgColor(color)의 반환값
function (e) {
  e.currentTarget.style.backgroundColor = red;
};

 

결국 코드가 아래와 같이 변경된 셈이다.

// button.addEventListener("click", changeBgColor(getColor()), false);

button.addEventListener("click", function (e) {
    e.currentTarget.style.backgroundColor = "red";
}, false);

 

따라서, 아무리 select option으로 파란색으로 옵션을 변경하고 버튼을 클릭해도 addEventListener의 changeBgColor(getColor()) 가 새로 호출되는 것이 아니라 처음 실행한 결과의 리턴값인 위 익명함수를 호출하기 때문에 에 계속 빨간색인 셈이다.

 

addEventListener 메서드는 해당 이벤트가 발생할 때 실행할 함수를 등록하는 것이기 때문에 함수만 등록해두면 호출은 이벤트가 발생했을 때 해당 메서드가 수행한다.

 

결국 위 코드의 문제점은 이미 함수가 실행되고 평가되어 그 값이 addEventListener에 전달되었다는 것이다. 또한 onchange 이벤트 핸들러로 색을 변경했다고 해서 그게 addEventListener에 영향을 줄 것이라는 것도 너무 해당 메서드 기능에 집중하고 있었기 때문에 보질 못했던 것이다.

 

따라서 위 코드가 정상적으로 동작하게 하기 위해선 아래와 같이 수정이 필요하다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>eventListenerDynamicParam(separated)</title>
    <script>
        window.onload = function () {

            let button = document.getElementById("btn");

            button.addEventListener("click", changeBgColor, false);

            function changeBgColor(e) {
                let selectedColor = document.getElementById("colorBox")
                    .options[document.getElementById("colorBox")
                    .selectedIndex].value;
                e.currentTarget.style.backgroundColor = selectedColor;
            }
        };
    </script>
</head>
<body>
<select id="colorBox">
    <option value="red" selected>red</option>
    <option value="blue">blue</option>
</select>
<button id="btn">버튼</button>
</body>
</html>

changeBgColor를 받아서 HTML 요소에 직접 onchange 핸들러를 등록할 필요없었고 굳이 getColor함수로 분리하지 않는게 가독성 측면에서 더 좋은 것 같아서 없애버렸다. 그러니 코드도 훨씬 간결해졌다. 동작도 잘한다.

 

See the Pen JjXrJwv by saintnun150 (@saintnun150) on CodePen.


사실 이 문제는 타이머 함수인 setInterval이나 setTimeout에도 동일하다.

function f1() {
    console.log("D");
}

setInterval(f1, 1000); // ??

setInterval(f1(), 1000); // ??

첫 번째 setInterval 함수는 의도한대로 1초 마다 D를 출력할 것이다. 그러나 두 번째는 호출 즉시 D가 출력되고 아무 일도 발생하지 않는다.

 

f1함수가 호출되어 반환값인 undefined를 setInterval 함수가 1초마다 실행하라는 의미이기 때문이다. 당연히 undefined이기 때문에 실행할 것이 없으니까 아무 실행도 안하는 것이다.

 

거의 어제 저녁부터 오늘 오후까지 간단한 건데 코드의 한부분에만 집중하다보니 거기에 꽂혀서 정작 기본적인 원리를 생각하지 않고 무작정 문제를 해결하려고 했다. 특히 위에 코드는 코드 상 에러가 발생하는 부분은 없었기 때문에 기본 원리를 다시 생각하기 전까지 디버깅도 어려웠다. 다행히 원인을 파악하고 나니 금방 해결할 수 있었다.

이 글을 공유합시다

facebook twitter kakaoTalk kakaostory naver band
loading