[Javascript] 익명함수, 람다식, 클로저 그리고 callback (DOMContentLoadedd와 load이벤트 예제)


Study/Javascript, Jquery, CSS  2020. 3. 25. 18:42

안녕하세요. 명월입니다.


이 글은 Javascript에서 익명함수, 람다식, 클로저 그리고 callback (DOMContentLoadedd와 load이벤트 예제)에 대한 글입니다.


Javascipt는 함수 지향 프로그램이라 그런지 익명함수나 람다식을 통한 callback 함수를 정말 많이 사용하는 것 같습니다.

그리고 함수를 변수처럼 취급이 가능한데, 다른 언어에서는 이를 함수 포인터나 델리게이트 선언해야 하는 것들을 쉽게 처리가 가능합니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // 일반 적인 함수 선언
    function test1() {
      // 콘솔 출력!
      console.log(" call function - test1() ");
    }
    // 익명 함수를 선언해서 test2 변수에 넣었다.
    // 즉 function test2()와 같은 의미
    var test2 = function() {
      console.log(" call function - test2() ");
    }
    // 람다식으로 선언해서 test3 변수에 넣었다. (람다식은 C#과 비슷한다.)
    // 즉, function test3()과 같은 의미
    var test3 = ()=> {
      console.log(" call lambda function - test3() ");
    };
    // test1함수 실행
    test1();
    // test2함수 실행
    test2();
    // test3함수 실행
    test3();
  </script>
 </body>
</html>

test1은 일반적인 함수 선언법입니다. test2와 test3은 각각 익명 함수와 람다식 선언법입니다.

여기까지 작성하면 일반 함수 선언과 크게 달라보이지 않습니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // 일반적인 함수 선언
    function test1(func) {
      // func 파라미터가 함수일 경우.
      if(func !== null && typeof func === "function") {
        // 파라미터를 실행
        // 첫번째의 파라미터는 this포인터의 주소, null의 값을 넘기면 호출되는 함수에서의 this는 자신이 됩니다.
        // 두번째 파라미터부터는 호출되는 함수의 파라미터
        func.call(this, "from test1 was called. ");
      }
    }
    // 익명함수 선언
    var func1 = function(param) {
      // 콘솔 출력
      console.log(" call function - func1 : param - " + param);
    };
    // 람다식 선언
    var func2 = (param)=>{
      // 콘솔 출력
      console.log(" call function - func2 : param - " + param);
    };
    // test1함수 실행 - 파라미터르는 익명함수를 넣었다.
    test1(func1);
    // test1함수 실행 - 파라미터르는 람다식를 넣었다.
    test1(func2);
  </script>
 </body>
</html>

test1의 함수는 파라미터에서 함수를 받으면 함수를 실행하는 형식입니다. 즉, 위와 같은 로직을 callback함수라고 합니다.

함수내에서 다른 함수를 부르는 방식을 이야기합니다.


그렇다면 변수를 사용하지 않고 콜백함수를 만들겠습니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // 일반적인 함수 선언
    function test(func) {
      // func 파라미터가 함수일 경우.
      if(func !== null && typeof func === "function") {
        // 파라미터를 실행
        func.call(this, "from test1 was called. ");
      }
    }
    // 익명 함수를 파라미터로 넘겼다.
    test(function(param) {
      // 콘솔 출력
      console.log(" call function - func1 : param - " + param);
    });
    // 람다식으로 파라미터로 넘겼다.
    test((param)=>{
      // 콘솔 출력
      console.log(" call function - func2 : param - " + param);
    });
  </script>
 </body>
</html>

보통 콜백이라고 하면 이벤트에 많이 붙여서 사용합니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // DOMContentLoaded이벤트에 익명함수로 콜백을 넣었다.
    document.addEventListener( "DOMContentLoaded", function() {
      // 콘솔 출력
      console.log(" DOMContentLoaded event ");
    });
    // DOMContentLoaded이벤트에 람다식으로 콜백을 넣었다.
    window.addEventListener( "load", ()=>{
      // 콘솔 출력
      console.log(" load event ");
    });
  </script>
 </body>
</html>

여기서 document의 DOMContentLoaded와 window의 load 이벤트에 각각 콜백함수를 넣었습니다. 즉, 이벤트가 발생하면 호출되는 것입니다.

DOMContentLoaded와 load는 html이 로드가 되면 호출이 되는 이벤트입니다. 다만 차이는 DOMContentLoaded의 경우 html의 로드가 끝나면 호출이 되는 것이고 load의 경우는 html안의 link와 이미지등까지 모두 로드가 끝나면 호출되는 것입니다.

그래서 발생 시점은 DOMContentLoaded가 빠릅니다. 그러나 라이브러리나 이미지의 오브젝트를 참조 한다면 DOMContentLoaded의 경우, 에러가 발생할 수 있습니다. 그러나 특별히 script를 async나 defer를 설정하지 않는 이상 괜찮습니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // 변수 선언
    var data = "hello world";
    // 이벤트 추가
    document.addEventListener( "DOMContentLoaded", function() { 
      // data의 변수의 경우는 함수 안에서 선언된 변수가 아니고 외부에서 선언된 변수입니다.
      // 별도로 파라미터로 값을 넘기길 필요없이 그냥 가져다 쓰면 됩니다.
      // 이것이 클로저입니다.      
      console.log(" DOMContentLoaded event , " + data);
    });
  </script>
 </body>
</html>

위는 클로저에 대한 예제입니다.

원래의 프로그래밍에서는 값을 주고 받을 때 파라미터를 통해 주고 받아야 합니다.

그러나 자바스크립트에서는 함수의 상위 statck 영역의 데이터라면 그냥 사용할 수 있습니다. 반대로 하위 statck의 데이터는 사용할 수 없습니다.

<!doctype html>
<html>
 <head>
  <title>Document</title>
 </head>
 <body>
  <script>
    // 변수를 선언하기 위한 함수
    funcion alloc() {
      var data = "hello world";
    }
    // 함수 호출
    alloc();
    // data의 변수는 alloc 함수에서 선언한 것(하위 statck 영역)이기 때문에 클로저가 되지 않는다.
    // 그러므로 data는 undefine.
    console.log(data);
  </script>
 </body>
</html>

요즘에는 잘 모르겠으나 제가 취준생일 때(10년 전쯤) IT회사의 클로저 질문으로 단골로 나오던 예상 문제였습니다.

말 그대로 질문에는 클로저라고 해 놓고 data의 값이 뭐냐고 물어보는 것입니다.

간혹 클로저니깐 hello world라고 하면 답하면 땡!, 아.. 함정이네 하면서 null이라고 해도 땡입니다.

undefined이 정확한 답입니다. undefined은 자바 스크립트에서 선언조차 안된 변수이고 null은 선언은 되었으나 값이 null입니다. undefined과 null도 분명히 다릅니다.

생각해보면 엄청 간단하고 별거 아닌데... 물어보면 의외로 이거 틀리는 사람이 많다고 면접관이 이야기했습니다. 저도 당연하잖아 생각하지만 신입 시절에 면접 시에 질문이 오면 세마포어나 IPC는 줄줄이 이야기하면서 이런 단순한게 오히려 순간적으로 헷갈립니다.

그냥 예전 추억이 생각나네요.


여기까지 Javascript에서 익명함수, 람다식, 클로저 그리고 callback (DOMContentLoadedd와 load이벤트 예제)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.