ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바스크립트의 this
    Web/자바스크립트 2021. 2. 13. 23:33
    반응형

    자바스크립트의 this는 다른 언어에서와 달리, 그다지 직관적이지가 않은 편이다.

    그래서 예전에 한 번 날 잡고 this에 관한 내용을 엄청 찾아보면서

    '이 정도면 한동안 this때문에 헤맬 일은 없겠다' 하고 정리를 마쳤었는데,

    시간이 지나면서 그 내용들이 흐리멍텅해졌다.

     

    한동안 this때문에 골치 아플만한 일이 딱히 없었다가

    최근에 this때문에 시간을 많이 잡아먹었던 적이 있어서,

    간단하게나마 정리를 해둬야겠다는 생각을 하게 됐다.

     

    자바에서의 this는 인스턴스 자기 자신을 가리키는 참조 변수이다.

    객체 자신에 대한 참조를 하고 있기 때문에, 보통 매개변수명과 멤버변수명이 같을 때

    이들을 구분하는 용도로 사용되곤 한다.

    public class Stock {
      private String ticker;
      public Stock(String ticker) {
        this.ticker = ticker;
        // this.ticker는 멤버변수, ticker는 매개변수
      }
    }

    하지만 자바스크립트는 상황에 따라 this에 바인딩되는 객체가 달라지기 때문에, 

    헷갈리지 않게 유의해서 사용해야 한다.

     

    1. 함수 호출 방식에 따른 this 바인딩

    함수 호출

    우선, 전역 함수(a)의 호출에서는 this가 전역 객체(window)를 가리킨다.

    내부 함수(b)의 경우에도 마찬가지로 this는 전역 객체(window)를 가리킨다.

    function a() {
      console.log(this); // window
      function b() {
        console.log(this); // window
      }
      b();
    }
    a();

    객체의 메소드(a)에서는 this가 그 메소드를 둘러싼 객체(obj)를 가리킨다.

    반면, 메소드의 내부함수(b)인 경우에 this는 전역 객체(window)를 가리킨다.

    내부 함수가 콜백 함수인 경우에도 this는 전역 객체(window)를 가리킨다.

    const obj = {
      a: function() {
        console.log(this); // obj
        function b() {
          console.log(this); // window
        }
        setTimeout(function() {
          console.log(this); // window
        }, 100);
        b();
      },
    };
    
    obj.a();

    정리하자면, 전역함수에서는 this가 전역 객체를 가리키며

    객체의 메소드에서는 this가 해당 객체를 가리킨다.

    그리고 내부 함수의 경우, 일반 함수, 메소드, 콜백 함수 어디에서든 상관없이 this는 전역 객체를 가리킨다.

    (즉, Method call 에서만 this가 해당 객체를 가리키고, 나머지 모든 경우에는 this가 전역 객체를 가리킨다)

     

    만약 내부 함수를 둘러싼 바깥 함수의 this를 내부 함수에서 사용하고 싶다면,

    별도의 변수에다가 해당 this를 담아서 내부 함수로 전달하는 방법을 사용할 수 있다.

    const obj = {
      a: function() {
        const that = this;
        function b() {
          console.log(this); // window
          console.log(that); // obj
        }
        b();
      }
    };
    
    obj.a();

    메소드 호출

    객체 메소드 내부의 this는 해당 메소드를 소유한 객체(해당 메소드를 호출한 객체)에 바인딩 된다.

    (프로토타입 객체의 경우에도 마찬가지)

    const obj = {
      a: function() {
        console.log(this); // obj
      }
    };
    
    obj.a();
    function Obj(name) {
      this.name = name;
    }
    
    Obj.prototype.a = function() {
      return this.name;
    }
    
    const obj = new Obj('abc');
    console.log(obj.a()); // 'abc'

    생성자 함수 호출

    자바스크립트의 생성자 함수는 '객체를 생성하는 역할'을 한다.

    하지만, 아무런 함수에다가 new를 붙여서 호출해도 해당 함수가 생성자 역할을 하기 때문에 각별한 주의가 필요하다.

    (그래서 생성자 함수는 보통 첫 문자를 대문자로 표기해서 혼란을 줄이려는 노력을 한다)

    function Person(name) {
      this.name = name;
    }
    
    const person1 = new Person('Jason');
    console.log(person1); // Person { name: 'Jason' }
    
    // 생성자 함수로 만들어도, new를 붙이지 않으면 생성자 함수로 동작하지 않음
    const person2 = Person('David');
    console.log(person2); // undefined

    간단히 말하면, 'new'를 붙여서 호출해야 생성자 함수라는 내용이다.

    이렇게 'new'를 붙여서 생성자 함수를 호출하는 경우에는 다음과 같은 순서로 동작이 발생한다.

     

    1) 빈 객체 생성 & 해당 빈 객체에 this 바인딩

    2) this를 이용해서 프로퍼티 생성

    3) this에 바인딩된 새로 생성한 객체가 return 된다.

     

    간단히 말해서, 생성자 함수에서는 this가 해당 생성자 함수로 인해 새로 생성되는 객체를 가리키며

    이 this를 이용해서 각종 프로퍼티를 생성하곤 한다.

     

    call/apply/bind를 통한 함수 호출

    call/apply/bind를 이용하면 해당 함수에서의 this를 명시적으로 지정해줄 수가 있다.

    call과 apply는 새로운 this가 바인딩된 함수를 바로 호출하는 메소드이며,

    (차이점은 매개변수들을 각각 집어넣느냐, 하나의 배열로 집어넣느냐 하는 차이)

    bind는 새로운 this가 바인딩된 함수를 return하는 메소드이다.

     

    이 경우, 당연하게도 바인딩해준 this가 해당 함수에서의 this로 작동하게 된다.

     

    2. 화살표 함수에서의 this 바인딩

    function 키워드로 생성한 일반 함수와 화살표 함수의 가장 큰 차이점이 바로 this이다.

     

    일반 함수의 경우, 위에서도 계속해서 언급했듯이 함수를 어떻게(어디서) 호출하냐에 따라

    this에 바인딩되는 객체가 동적으로 결정된다. 이를 동적 바인딩이라고 한다.

     

    반면, 화살표 함수의 경우에는 this가 언제나 상위 스코프의 this를 가리킨다.

    이를 두고 정적 바인딩, 'Lexical this' 라고 한다.

    '화살표 함수는 자신의 this를 가지지 않고, 자신이 포함된 함수의 this를 갖다쓴다' 라고 표현하기도 한다.

     

    참고로, 화살표 함수는 call/apply/bind를 이용해도 this를 변경할 수가 없다.

     

    화살표 함수의 경우 Lexical this이기 때문에 콜백함수로 사용하기 편리하지만, 

    (위 1번의 '함수 호출'에서, 객체 메소드의 내부함수가 콜백함수인 경우에

    해당 콜백함수를 화살표 함수로 써주면 거기서의 this는 해당 객체를 가리키게 된다.)

    적지 않은 경우에 일반 함수보다 화살표 함수를 사용하는 것이 더 헷갈리기도 한다.

    메소드

    보통 화살표 함수로 메소드를 선언하는 것은 피하라고들 얘기한다.

    const obj = {
      a: () => {
        console.log(this); // window
      }
    };
    
    obj.a();

    위의 경우, 메소드로 정의한 화살표 함수 내부의 this가 메소드를 소유한 객체,

    즉 메소드를 호출한 객체를 가리키는 게 아니라 해당 객체의 상위 컨텍스트인 전역 객체를 가리킨다.

    그래서 메소드를 선언할 때는 화살표 함수를 사용하지말고, ES6의 축약 메소드 표현을 사용하는 것이 좋다고 한다.

    const obj = {
      a() {
        console.log(this);
      }
    };
    
    obj.a();

    Prototype

    Prototype 메소드의 경우에도 동일한 문제가 발생한다.

    const obj = {
      a: 'abc'
    };
    
    Object.prototype.b = () => console.log(this.a);
    
    obj.b(); // undefined(this가 window를 가리킴)

    그래서, Prototype의 경우에는 그냥 일반 함수를 사용해주면 된다.

    const obj = {
      a: 'abc'
    };
    
    Object.prototype.b = function() {
      console.log(this.a);
    }
    
    obj.b(); // 'abc'

    생성자 함수

    화살표 함수는 생성자 함수로 사용할 수 없다.

    prototype 프로퍼티를 가지지 않기 때문.

     

    addEventListener의 콜백함수

    addEventListener 함수의 콜백함수를 화살표 함수로 정의하면 this가 상위 컨텍스트인 전역 객체를 가리킨다.

    btn.addEventListener('click', () => {
      console.log(this); // window
    });

    따라서 addEventListener 함수의 콜백함수 내에서 this를 사용하려면

    function 키워드로 정의한 일반 함수를 사용해야 한다.

    일반함수로 정의된 addEventListener 함수의 콜백 함수 내부의 this는 이벤트 리스너에 바인딩된 요소를 가리킨다.

    btn.addEventListener('click', function() {
      console.log(this); // btn
    });

     

     

     

    참고

    Poeimaweb - 6.3 Arrow Functions

    Poeimaweb - 5.17 this

    반응형

    댓글

Designed by Tistory.