this를 소환하는 법

Sally·2022년 2월 25일
1

이번에 겪었던 this 문제에 대해서 기록하고 정리해보고자 한다.

그저 어느 날 처럼 습관적으로 this를 사용하였을 뿐이였는데

undefined 가 여기서 왜 나와...?

내가 의도한 것은 this를 통해서 값에 접근 하였을 때에 undefined가 아닌 아래와 같은 같이 출력이 됐어야 한다.

코드를 통해서 자세히 상황을 설명해보자면, 당시 내가 썼던 코드에서는 클릭 이벤트의 함수로 handleToggleButtonClick을 넘겨주었는데

handleToggleButtonClick내에서 this를 통해서 constructor에 선언된 lottoPrice, lottoPriceValid, lottoList 값에 접근할 수가 없고 앞서 본 것과 같이 undefined만이 계속 호출 되었다.

그래서 handleToggleButtonClick을 화살표 함수로 고쳐보기도 하고 호출 순서를 고쳐보거나 하였지만 모두 undefined만이 출력될 뿐이였다.

어째서 이런 오류가 발생했던 것일까?


this를 아십니까?

this란 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. 그래서 우리는 this를 통해서 this가 속한 객체 또는 this가 가리키는 인스턴스의 프로퍼티나 매서드를 참조할 수 있다.

하지만 주의할 점은 this가 가리키는 값은 함수 호출 방식에 의해서 동적으로 결정된다!

즉 this를 호출를 제대로 하지 않게 되면 자신이 원하는 값이 아닌 엉뚱한 곳을 가리켜 혼란을 가져 올 수 있다.

모던 자바스크립트 Deep Dive에 있는 예제를 통해 함수 호출에 따라 this 바인딩이 어떻게 되는지 살펴보자

const foo = function () {
	console.dir(this);
}

// 1. 일반 함수 호출 방식 
// foo 함수 내부의 this는 전역 객체인 window를 가리키게 된다 
foo(); // window

// 2. 메서드 호출 방식 
// foo 함수를 프로퍼티 값으로 할당하여 호출 
// foo 함수 내부의 this는 메서드를 호출한 객체 obj를 가리키게 된다 
const obj = { foo };
obj.foo(); // obj

// 3. 생성자 함수 호출 방식 
// foo 함수를 new 연산자와 함계 생성자 함수로 호출 
// foo 함수 내부의 this는 생성자 함수가 생성한 인스턴스를 가리키게 된다
new foo(); // foo{}

// 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// foo 함수 내부의 this는 인수에 의해서 결정된다 
const bar = {name : 'bar'};

foo.call(bar); // bar 
foo.appy(bar); // bar 
foo.bind(bar)(); // bar 

const bar 
  1. 일반 함수 호출
    앞서 말한 바와 같이 this는 자신이 속한 객체를 가리키는 값이다. 그렇기 때문에 객체를 생성하지 않는 일반 함수의 경우 this는 의미가 없고 이때의 this는 window전역 객체를 가리키게 된다.
  • 만약 strict mode가 적용된 일반 함수 내부에서 this를 사용한다면 this에는 undefined가 바인딩 된다.
  • 매서드 내에서 정의한 중첩 함수의 경우도 this는 전역 객체에 바인딩 된다
  • 콜백 함수가 일반함수로 호출된다면 콜백 함수 내부의 this에도 전역객체가 할당이 된다 (어떠한 함수라도 일반 함수로 호출되면 this에 바인딩 된다!)
  1. 메서드 호출
    메서드 내부의 this에는 메서드를 호출한 객체에 바인딩이 된다. 주의할 점은 매서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩이 된다는 것이다.

  2. 생성자 함수 호출
    생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스에 바인딩 된다.

  3. Function.prototype.apply/call/bind메서드에 의한 간접 호출
    appy, call, bind 메서드는 Function.prototype의 메서드 이다. 이들 메서드는 모든 함수가 상속받아 사용이 가능한데 this로 사용할 객체와 인수 리스트를 인수로 전달 받아 함수를 호출한다.


this바인딩이 제대로 되지 않았을 때의 해결 방법

  1. 화살표 함수를 이용하자!
    화살표 함수의 this는 콜백 함수 내부의 this가 외부 함수의 this와 다르기 때문에 발생하는 문제를 해결하기 위해 의도적으로 설계되었기 때문에 일반함수의 this와 다르게 동작한다. 일반 함수와 달리 화살표 함수 내부에서 this를 참조하게 되면 상위 스코프의 this를 그래도 참조한다.
    이러한 화살표 함수의 특징을 lexical this라고 부르는데 화살표 함수의 this가 함수가 정의된 위치에 의해 결정된다는 것을 의미하는 말이다.

  2. this 바인딩을 변수에 선언해보자!
    this를 미리 다른 변수에 할당을 한 뒤 매서드 내부의 중첩 함수나 콜백함수에서 할당한 변수를 사용한다

const obj = {
	value: 100,
    foo() {
    	const that = this; // that 이라는 변수에 this 바인딩을 할당한다 
        
        setTimeout(function(){
        	console.log(that.value); // 미리 할당한 that을 통해서 100이라는 값에 접근할 수 있게 된다
        },100);
     }
}
  1. apply, call, bind메서드를 활용하자!
  • apply, call 메서드의 기능은 함수를 호출하는 것인데 이 둘을 통해서 함수를 호출하면서 첫번째 인수로 전달한 객체를 호출한 함수의 this에 바인딩 한다.
예시 
function getThisBinding(){
	return this;
}

const thisArg = { a : 1};

console.log(getThisBinding()); // 일반 함수이기 때문에 window가 찍히게 된다.

// getThisBinding 함수를 호출하면서 인수로 전달된 객체인 getThisBinding함수의 this에 바인딩 된다. 
console.log(getThisBinding.apply(thisArg)); // {a : 1} 출력
console.log(getThisBinding.call(thisArg)); // {a : 1} 출력

만약 getThisBinding이라는 함수에 인수 값을 전달해 주어야 한다면 apply는 배열로 call은 쉼표로 구분한 리스트 형식으로 전달하면 된다.

  • bind
    bind의 경우 앞선 apply, call과 달리 함수를 호출하지 않고 this로 사용할 객체만을 전달한다. 그렇기 때문에 함수를 호출할 경우 명시적으로 호출하는 작업이 필요하다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding를 출력하게 된다 
console.log(getThisBinding.bind(thisArg)()); // 명시적으로 호출한 경우 getThisBinding에 this로 사용할 thisArg를 전달과 동시에 함수를 호출하므로 {a:1}이 출력이 된다. 

나의 문제의 원인은 무엇이였을까?

위의 케이스들을 통해 내가 가졌던 undefined에러 문제에 대한 원인을 알 수 있었다. 일반함수인 handleToggleButtonClick 함수를 어떠한 처리 없이 인수로써 addEventListener에 전해주었기 때문에
handleToggleButtonClick 함수 내의 this가 전역객체에 할당이 되었고 strict mode가 합쳐지면서(class 내부의 모든 코드에는 strict mode가 암묵적으로 적용된다) undefined만이 계속 출력이 되었던 것이다.
그래서 그런지 handleToggleButton만을 화살표 함수로 바꾸고 console을 다시 찍어 보았을때에는
undefined가 아닌 class 내부의 초기 값을 출력할 수 있었다. (초기값이 출력되는 이유는 앞서 값을 할당하는 함수들 또한 일반함수이기 때문에 값이 할당이 되지 않았다.)
그리고 앞선 해결 방법들 중 우선적으로 addEventListener에 인수로 전해지는 함수의 형태를 화살표 함수로 바꾸었을때에 원하는 결과 값을 출력할 수 있었다.

0개의 댓글