2015년 2월 19일 목요일

[난해한 Javascript 개념] 'this' 키워드의 의미

C/C++/Java에 익숙한 나에게 낯선 것들이 Javascript에는 너무나 많다. 지금까지 다루어 본 언어들과는 너무나 다른 functional 프로그래밍 언어인 Javascript의 핵심 요소를 살펴보고 '내 나름대로의 이해'를 시도해 본다.

낯선 것들:
  • first-class function

  • 변수의 scope

  • this 키워드의 의미

  • closure의 개념

  • 비동기적 프로그래밍과 callback





  • this 키워드의 의미
    C++/Java와 Javascript의 this의 의미는 엄밀히 말하면 똑같다(C에는 this 키워드가 없다). 'Owner'(또는 'Context'라고 생각해도 된다)를 가리킨다는 의미적인 측면에서는 같다. 하지만, 통상적인 Owner가 누구냐는 면에서는 조금 다르다. C++/Java의 Owner는 변수나 함수를 가지고 있는 instance이다.

    Java의 경우:

    class Foo {
      private int i;
      public Foo(int input){
       this.setValue(input); //instance f의 setValue() method
      }
      public void setValue(int input){
       this.i = input;       //instance f의 i 변수
      }
      public static void main (String[] args) {
       Foo f = new Foo(10); 
      }
    }
    

    Javascript의 this는 C++/Java에 비해 Owner로 누구를 가리키느냐가 조금 더 복잡하다.
    root scope에서의 this는 (Javascript를 구동하는) window를 Owner로 가리킨다.
    console.log(this); //출력: window
    

    아래 코드는 좀 더 이를 설명한 것이다.

    var a = 100;
      console.log(this == window) //출력: true
      console.log(this.a); //출력: 100
      console.log(window.a); //출력: 100
    

    root scope에서의 function의 Owner는 어떨까? 이때도 Owner는 window이다.
    function foo(){
       console.log(this); //출력: window
     }
     foo();  // window.foo();로 대체가능 - foo() 함수의 owner가 foo라는 것이 명백함
    

    Object내에 정의된 function은 어떨까? 이런 function의 Owner는 그 Object이다.
    var goo = {
      foo: function(){
        console.log(this); //출력: goo 
        console.log(this == goo); //출력: true
        console.log(this == window); //출력: false
      }
     }
     goo.foo();
    

    그런데, Javascript에서는 그 Owner를 명시적으로 바꾸어 줄 수 있다. 즉, this가 가리키는 대상을 명시적으로 교체 가능하다. 이와 관련된 함수가 call(), apply(), bind() 함수이다.
    call()과 apply() 함수는 거의 동일한 기능을 하며, 아래와 같이 원래 function호출의 맨 뒤에 call(대상)이나 apply(대상)을 붙임으로써 this가 가리키는 대상을 바꾼다.
    var goo = {
      foo: function(){
        console.log(this); //출력: window 
        console.log(this == goo); //출력: false
        console.log(this == window); //출력: true
      }
     }
     goo.foo.call(window);
     
    call()과 apply의 차이는 추가적인 argument를 passing해야 할 경우 방식이 다르다는 것이다. call은 바꾸려는 대상외의 argument를 나열하는 방식이고, apply는 바꾸려는 대상외의 argument를 array로 넘기는 방식이다. 아래의 예를 보면 이해가 쉽다.
    var goo = {
      foo: function(one, two, three){ //apply() 경우 array를 받는 변수 1개만 필요
        console.log(this); //출력: window 
        console.log(one+two+three); 
      }
     }
     goo.foo.call(window, 1, 2, 3);  // == goo.foo.apply(window, [1,2,3]);
     

    bind()는 Owner를 바꾼 함수를 return하는 것이다. 아래 예를 보자.
    var goo = {
      foo: function(){
        console.log(this); //출력: goo.foo() 호출 시 goo, fixed()호출 시 window
      }
     }
     var fixed = goo.foo.bind(window);  
     goo.foo(); 
     fixed(); 
     

    callback의 경우에는 this가 생각한 경우와 달라서 오류가 많이 발생하게 된다.
    var goo = {
     foo: function(){
       console.log(this);    //Owner는 goo
       setTimeout(function(err){
         console.log(this);  //Owner는?
       }, 2000);
     }
    };
    goo.foo();
    
    출력은 아래와 같다.

    setTimeout의 callback으로 등록된 function의 owner는 goo가 아니라 window이다. 객체 정의와 실행간 불일치로 인해 Owner가 바뀐 것이다. 이런 문제를 해결하기 위해 closure라는 개념이 Javascript에 있으므로 이를 반드시 확실히 이해해야만 한다. 그것은 다음 글에서 좀 더 자세히 다루어 보자.


    정리

    1. Javascript의 this 키워드는 Owner를 가리키는데 Object 내에 있으면 Object, 그렇지 않으면 window(root)를 가리킨다.
    2. call(), apply(), bind()를 통해 this가 가리키는 Owner를 바꿀 수 있다.
    3. callback의 경우에는 this를 착각하기 쉽다. 주의해야 한다.




    댓글 없음: