본문 바로가기
JavaScript

this

by oncerun 2022. 2. 4.
반응형

 

자바스크립트의 this 키워드에 대해서 공부할 예정이다. 

 

[겪은 상황]

하이브리드 앱 개발 중 Class의 메서드를 추가하는 도중 이벤트 리스너를 등록해야 하는 상황이 있었다. 

인앱브라우저를 종료한 후 callback에서 여러 작업을 진행하는 상황이었다. 

그래서 아무 생각 없이 이벤트 리스너 내부에서 this로 클래스 프로퍼티에 접근하기 위해 this.propertie를 사용했다가 동작이 안 되는 문제가 발생했다. 

try / catch로 잡아 문제를 디버깅하는 과정에서 this.propertie에서 문제가 발생한다는 것을 알았다. 

 

이벤트 리스너는 클래스 내부 메서드 안에서 플러그인을 통해 생성한 객체에 이벤트 리스너를 등록하는 상황이었다. 

 

 

 

this 키워드

 

기존 객체 리터럴 방식으로 생성한 경우 메서드 내부에서 자신이 속한 객체를 가리키는 식별자를 참조하기 위해 재귀적 참조를 이용할 수 있다.

 

 

이는 객체 리터럴이 obj변수에 할당되기 직전 평가되며, 이에 따라 getName()이 호출되는 시점에는 해당 객체의 리터럴 평가 완료 후 객체가 생성된 후이기 때문에 메서드 내부에서 obj 식별자를 참조할 수 있다.

 

다만 객체를 재귀 참조하는 방식은 일반적이지도 바람직하지도 않다. 보통 생성자 방식으로 인스턴스를 생성한다. 

 

* 생성자 함수를 이용하는 경우 내부적으로 빈 객체 {}를 만들고 생성자 함수 몸체를 생성한 빈객체에 할당한 후 리턴해주는 방식이다. 

 

그렇기 때문에 생성자 함수를 정의하는 시점에서는 인스턴스 생성 이전이기 때문에 생성할 인스턴스를 가리키는 식별자를 알 수 없다. 따라서 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자가 필요한데 이를 위해 자바스크립트는 this라는 특수한 식별자를 제공한다.

 

this는 자바스크립트 엔진에 의해 암묵적 생성이 이루어지며, 코드에서 호출할 수 있다. 

다만 this가 가리키는 값, this바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

 

 

생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다. 

 

객체 리터럴 방식에서 this는 메서드를 호출한 객체 obj를 가리킨다.

 

 

* 렉시컬 스코프는 함수가 평가되어 함수 객체가 생성되는 시점에 상위 스코프를 결정하는 반면 this바인딩은 함수 호출 시점에 결정된다.

 

 

 

일반 함수 호출

 

 - 일반함수 호출, 혹은 중첩 함수에서 호출 시 기본적으로 전역 객체가 바인딩된다. 

 

 - 만약 strict mode에서 일반 함수 내부에서 호출 시 this는 undefined값이 바인딩된다. 왜냐하면 this는 객체의 프로퍼티, 메서드를 참조하기 위한 자기 참조 변수이므로 일반 함수 내부에서 this사용은 의미가 없기 때문이다.

 

 - 메서드 내부 혹은 콜백 함수 등 모든 함수가 일반 함수로 호출된다면 중첩 함수 내부의 this 또한 전역 객체가 바인딩된다.

 

이 부분이 문제인데 만약 메서드 내부에서 정의한 중첩 함수 또는 콜백 함수가 일반 함수로 호출될 때 메서드 내의 호출된 함수들의 this가 전역 객체를 바인딩하기 때문에 문제가 발생한다.  이러한 함수는 사실 외부 함수를 도와주는 역할을 하기 때문이다. 

 

 

 

 

다음을 보면 getName()의 this는 Object객체를 가리키며 해당 내용을 펼치면 obj 식별자를 가리킨다.

반면 setTimeout의 첫 번째 인자로 콜백 함수를 지정한 수 this를 호출했는데 Window객체 (전역 객체)가 출력된 것을 확인할 수 있다.

 

이러한 this바인딩을 외부, (내부, 콜백)에서 일치시키기 위한 방법은 다음과 같다.

 

  • 내부, 콜백 함수에서 this를 특정 변수에 할당한다. 이후 내부 메서드에서 사용한다.
  • function.prototype.apply,  function.prototype.bind, function.prototype.call
  •  Arrow Function Expression 사용


 

 

메서드 호출

 

메서드 내부의 중첩 함수, 콜백 함수의 this를 호출한 객체에 바인딩하는 방법을 알아보았다.  이 부분에서 기억해야 할 점이 있다. 바로 메서드 내부의 this는 메서드를 소유한 객체가 아닌  메서드를 호출한 객체 바인딩된다는 것이다. 

 

무슨 말이냐면 다음 object변수는 hi라는 메서드를 호출한다.

const object = {
   name : 'hi',
   hi(){
       console.dir(this);
    }
};
object.hi();

 

 

hi()라는 메서드는 object 객체의 메서드로 정의되어있다. 메서드는 객체의 프로퍼티에 바인딩된 함수이다. 이 말은 object객체의 hi라는 프로퍼티가 가리키는 함수 객체는 object 객체에 포함된 것이 아닌 독립적으로 존재하는 함수 객체이다.

따라서 object의 hi프로퍼티는 별도의 function hi를 가리키고 있을 뿐이다.

 

그렇기 때문에 object의 함수는 다른 객체의 메서드나 일반 변수에 할당할 수 있다.

 

* 전역 변수인 hi의 this는 전역 객체(Window)가 할당된다.

 

이는 obj객체의 hi() 메서드를 가리키는 함수 객체가 별도로 존재하여 값을 할당할 수 있는 모든 위치에 들어갈 수 있음을 뜻하며, 해당 this가 호출한 객체에 바인딩된다는 것을 알 수 있다. 

 

즉 메서드 내부의 this는 프로퍼티로 메서드를 가지고 있는 객체와는 상관없이 해당 함수 객체를 호출한 객체에 바인딩된다는 것을 기억하면 된다.

 

따라서 정리하면 다음과 같다.

일반 함수 호출 전역 객체 바인딩
메서드 호출 메서드 호출한 객체가 바인딩
생성자 함수 호출 생성할 인스턴스에 바인딩
apply, call, bind 메서드의 호출 인수로 전달한 객체에 바인딩

 

생성자 함수 호출과 function.prototype의 apply, call, bind는 따로 정리했기 때문에 생략했다.

 

 

----

 

[겪은 상황]

 

이벤트 리스너는 클래스 내부 메서드 안에서 플러그인을 통해 생성한 객체에 이벤트 리스너를 등록하는 상황에서 this의 문제가 발생한 이유

 

메서드 내부의 중첩 함수로 eventListner를 정의했고 그때 두 번째 인자로 콜백 함수를 작성했다. 

공부한 바에 따르면 this는 호출한 객체에 동적으로 바인딩된다고 했다. 

 

이벤트 리스너가 등록되고 해당 객체나 요소에 지정된 타입의 이벤트가 발생하면, 브라우저는 자동으로 등록된 이벤트 리스너를 호출합니다. --TCP School

 

호출하는 주체는 브라우저다. 브라우저는 Window객체인 전역 객체이며 이 전역 객체에 class 내부에 정의한 프로퍼티가 존재할 수 없다. 그래서 해당 프로퍼티가 존재하지 않는다는 에러를 뱉은 것이다. 

 

이 문제를 해결하기 위해선 여러 가지 방법이 있을 수 있다.  월요일에 시도해볼 것은 해당 콜백 함수에 function.prototype의 bind를 통해 처리, 혹은 메서드 내부에서 this를 특정 변수에 할당하여 콜백 함수의 인자로 넘긴 후 사용하는 방법 등등 이 상황을 해결할 수 있는 지식을 얻었다. ㅎㅎ

 

 

 

반응형

'JavaScript' 카테고리의 다른 글

함수 코드 실행 컨텍스트  (0) 2022.02.23
전역 코드 실행 컨텍스트  (0) 2022.02.22
함수, 1급 객체  (0) 2022.01.25
Iteration protocol  (0) 2022.01.17
Symbol  (0) 2022.01.16

댓글