ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수를 호출할 때의 this 바인딩
    개발 2020. 4. 19. 19:59

    함수를 호출하면, 해당 함수 내부에서 사용된 this는 전역 객체인 window 객체에 바인딩 됩니다.

    자바스크립트의 모든 전역 변수는 실제로 전역 객체의 프로퍼티들 입니다. 

     

    전역 변수 foo 를 정의하고 출력하는 예제를 통해 정말 전역 객체의 프로퍼티인지 확인해 보겠습니다.

     

    !!! 아래의 예시와 다음에 나온 예시에서 let을 쓰지 않고 var를 사용했는데 만약 foo 변수에 let 키워드를 사용했다면 window.foo 는 undefined가 나올 것입니다. 왜냐하면 let 키워드로 선언된 변수를 전역 변수로 사용하는 경우 let 전역 변수는 전역 객체의 프로퍼티가 아니기 때문입니다.  즉, let 전역 변수는 보이지 않는 개념적인 블록 내에 존재하기 때문에 window.foo와 같이 접근할 수 없습니다.
    이와 같은 이유로 window.foo 처럼 접근해야 하는 경우가 생길 시에는 var 키워드를 사용하도록 하겠습니다.
    var foo = "Hello, World";
    
    console.log(foo);  // "Hello, World"
    console.log(window.foo);  // "Hello, World"

    전역 변수 foo 를 정의하고 콘솔에 출력해 보았습니다. foo라고 출력하나 window.foo 라고 출력하나 같은 결과를 출력해 내는 것을 볼 수 있습니다. 따라서 전역 변수는 전역 객체( window )의 프로퍼티로도 접근할 수가 있습니다.

     

    그렇다면 이번에는 함수를 호출할 때 this 바인딩이 어떻게 되는지 간단한 예제 코드로 알아보겠습니다.

    var foo = 'Hello, World';
    console.log(window.foo);  // 'Hello, World'
    
    var bar = function(){
      console.log(this.foo); 
    }
    
    bar();  // 'Hello, World'

    우선 foo 라는 전역 변수를 선언했고 앞서 설명했듯이 전역 변수는 전역 객체 window의 프로퍼티로 접근이 가능합니다.

    bar 함수는 단순히 this.foo 를 출력하는 함수입니다. 함수를 호출할 때 this는 전역 객체에 바인딩 된다고 했으므로, bar() 함수가 호출된 시점에서 this는 전역 객체인 window에 바인딩 됩니다. 때문에 this.foo는 window.foo를 의미하므로, 결국 'Hello, World' 값이 출력되는 것을 볼 수 있었습니다.

     

    하지만 이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 경우에도 그대로 적용되므로, 내부 함수에서 this를 이용할 때에 주의해야 한다는 사실을 객체의 메서드를 호출할 때 this바인딩 편의 마지막 예제에서 알게 되었습니다.

    다음 예제를 통해서 이 전 컨텐츠에서 이해하기 어려웠던 이 부분을을 더 완벽히 이해해보도록 하겠습니다.

    // 전역변수 value 정의
    var value = 100;
    
    // obj 객체 생성
    var obj = {
      value: 1,
      func1: function(){
        this.value +=1;
        console.log(this.value);  // 2 출력
        
        // func2 내부 함수
        func2 = function(){
          this.value +=1;
          console.log(this.value);  // 101 출력
          
          // func3 내부 함수
          func3 = function(){
            this.value +=1;
            console.log(this.value);  // 102 출력
          }
          
          func3();  // func3() 내부 함수 호출
        }
        
        func2();  // func2() 내부 함수 호출
      }
    };
    
    obj.func1();  // func1() 메서드 호출

    obj 라는 객체 안에 fun1 메서드가 정의되어 있고 func1()의 내부함수로 fun2()가 있고 func2()의 내부함수로 func3()이 정의되어 있습니다.

    함수 호출을 따라가 보면 함수 호출 순서에 따라서 func1() 메서드가 obj로부터 호출되고, 이어서 func2() 내부함수와 func3() 내부 함수가 차레대로 호출됩니다. 이 과정을 순차적으로 다시한번 설명하자면 다음과 같습니다.

    1. func1()은 obj 객체의 메서드입니다. 따라서 객체의 메서드를 호출할 때 this바인딩 편에서 설명했듯이 obj.func1()과 같이 메서드로 호출할 때는 메서드 코드 내에서 사용된 this는 자신을 호출한 객체를 가리키므로, func1()에서 사용된 this는 이 메서드를 호출한 객체인 obj를 가리킵니다.
    2. func2()는 func1()을 부모 함수로 하여, func2() 내부 함수의 this는 부모 함수의 this와 같은 객체인 obj를 가리킨다고 생각하는게 자연스럽겠지만 이 전 컨텐츠에서 설명했듯이 내부 함수는 엄밀이 말해 메서드가 아니기 때문에 단순 함수 호출 규칙에 따라 window를 가리키게 되어 func2()와 func3() 내부 함수는 window 객체를 가리키고 있습니다.

    정리해 보자면 func1()은 obj객체로부터 직접적으로 호출이 되었지만 내부함수는 직접적으로 호출된 것이 아니기 때문에 window를 가리키는 것 입니다. 누가 this를 호출했는지가 중요한 것입니다!!

     

    좀 더 이해하기 쉽게 그림으로 설명해보자면 다음과 같습니다.

     

     

    이렇게 내부 함수가 this를 참조하는 이 현상을 극복하려면 부모 함수 (위 예제에서는 func1() 메서드)의 this를 내부 함수가 접근 가능한 변수에 저장하는 방법이 사용 됩니다. 보통 관례상 this 값을 저장하는 변수의 이름은 that이라고 짓습니다. 이렇게 되면 내부 함수에서는 that 변수로 부모 함수(func1() 메서드)의 this가 가리키는 객체에 접근할 수 있습니다.

     

    다음은 이 방법을 코드로 옮겨낸 예제 입니다.

    var value = 100;
    
    var obj = {
      value: 1,
      func1: function(){
        // 변수 that에 this 저장
        var that = this;
        this.value +=1;
        console.log(this.value);  // 2 출력
        
        // func2 내부 함수
        func2 = function(){
          that.value +=1;
          console.log(that.value);  // 3 출력
          
          // func3 내부 함수
          func3 = function(){
            that.value +=1;
            console.log(that.value); // 4 출력
          }
          
          func3();
        }
        
        func2(); 
      }
    };
    
    obj.func1();

    위의 방법대로 that 변수에 this를 저장하고 내부 함수에서 that.value로 접근하니 의도했던대로 잘 출력되는 것을 볼 수 있습니다.

    이 과정을 순차적으로 설명하자면 다음과 같습니다.

    1. 부모 함수인 func1()의 this 값을 that 변수에 저장했습니다. 앞서 내부 함수의 특징에서 설명했듯이 func2()와 func3() 내부 함수는 자신의 부모 함수인 func1()의 변수에 접근 가능하므로, func2()와 func3()도 that 변수로 func1()의 this가 바인딩된 객체인 obj에 접근 가능하게 됩니다.
    2. func1() 함수의 this는 obj를 가리키므로, obj.value 값이 1 증가합니다.
    3. 부모 함수 func1()의 that 변수에도 obj 객체의 참조값이 저장되어 있으므로, obj.value 값이 각각 1씩 증가합니다.

    이 과정도 이해하기 쉽게 그림으로 표현하자면 다음과 같습니다.

     

     

    위와 같은 방법말고도 this 바인딩을 명시적으로 할 수 있도록 call과 apply 등의 메서드를 제공하기도 합니다. 

    이 방법은 call과 apply, bind 메서드를 이용한 명시적인 this 바인딩 편에서 더 자세하게 다루고 있으니 참고해주시면 감사하겠습니다.

Designed by Tistory.