[js] this

·2022년 10월 8일
0

공부한 것

목록 보기
31/58

this란?

const dog = {
  name: '모찌',
  print() {
    console.log(`${this.name}는 귀여워`);
  }
}

dog.print(); // 모찌는 귀여워

메서드 : 객체 안에 선언된 함수
프로퍼티 : 객체 안에 선언된 속성

dog 객체의 print 메서드가 동작하기 위해서는 dog 객체의 name 프로퍼티를 알아내야 한다.

print 메서드name 프로퍼티 모두 dog 객체에 속해있다. 그럼 print 메서드는 자신이 속한 dog 객체를 지목할 수 있으면 name 프로퍼티도 사용할 수 있게 된다. 이때 자신이 속한 객체를 가리키는 식별자로 사용되는 변수가 this이다.

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.

그래서 위 예제에서 this.name의 this는 print 메서드가 속한 dog 객체를 지목하게 되고${dog.name}는 귀여워가 출력되게 되는 것이다.

this 결정 방식

그럼 this가 결정되는 방식에 대해 알아보자. 보통 설명을 보면

this는 호출하는 방식에 의해 동적으로 결정된다.

고 되어있다. 여기서 호출하는 방식 = 어떤 객체가 나를 호출하느냐 인 것이다.
위에서 dog 객체는 객체 리터럴을 사용해 선언되었다. 그래서 dog.print()로 바로 사용할 수 있다.
그런데 클래스 문법을 사용한 경우를 생각해 보자

메서드 호출

class Dog {
  constructor(name) {
    this.name = name;
  }
  
  print() {
    console.log(`${this.name}는 귀여워`);
  }
}

const mozzi = new Dog('모찌');
mozzi.print();

const coco = new Dog('코코');
coco.print();

클래스를 선언했다고 Dog.print()로 사용할 수 없다. 인스턴스를 생성해야 한다. 위 예제에서는 mozzi라는 인스턴스를 생성하고 mozzi.print()로 메서드를 호출하고 있다.

이 경우 print 메서드가 속한 곳은 어디일까? Dog 클래스 안에 선언된 메서드라서 Dog 일 것 같지만 그렇지 않다.

흔히 클래스를 붕어빵 틀로 설명하고 인스턴스를 붕어빵으로 설명한다.
우리가 먹는 것을 생각해 보자 우리에게 붕어빵 틀이 필요한가? 우리는 붕어빵을 먹지 붕어빵 틀을 먹진 않는다.
실제로 사용하고 필요한 부분은 결국 붕어빵인 인스턴스란 말이다. 위 예제에서 인스턴스는 mozzi이고 메서드는 mozzi.print()코드에서 호출된다. 결국은 mozzi가 print 메서드를 호출하고있는 것이다.

  1. this는 호출하는 방식에 의해 동적으로 결정된다.
  2. 호출하는 방식 = 누가 메서드를 호출하고 있으냐
  3. mozziprint 메서드를 호출함
  4. this = mozzi
const coco = new Dog('코코');
coco.print();

부분을 보면 생성된 인스턴스는 coco이고 print 메서드를 호출하고 있는 객체도 coco이다. this는 coco가 된다.

생성자 함수 호출

자바스크립트 딥 다이브 책을 보면 메서드 호출과 생성자 함수 호출의 경우를 분리해뒀는데 내가 생각하기엔 같은 말이 아닌가 싶다. 자바스크립트의 경우 클래스 문법이 없었기에 생성자 함수의 경우를 따로 분리한 것 같긴 하지만 결국은 생성될 인스턴스 즉 메서드를 호출하게 될 인스턴스 객체가 this가 되는 것이니까.

위 예제에서 미래에 생성될 인스턴스는 mozzi였고, 메서드를 호출하고 있는 인스턴스 객체는 mozzi였기에 this는 mozzi가 되었다.

일반 함수 호출

add();

이렇게 호출되는 경우가 일반 함수 호출인데 이 경우는 누가 호출하고 있는 것일까?
전역 함수의 경우 사실상 전역 객체인 window 객체의 메서드로 등록이 된다.

window.add();

로 함수를 호출하는 것과 같다는 말이다. 그래서 일반 함수 호출의 this는 window 객체가 된다.

그럼 아래의 경우를 생각해 보자

const dog = {
  name: '모찌',
  print() {
    setTimeout(function () {
		console.log(`${this.name}는 귀여워`);
    }, 100)
  }
}

dog.print(); // 는 귀여워

print 메서드 내부에 함수가 중첩되어 있다. 이 경우도 크게 다르지 않다. 중첩 함수도 일반 함수로 선언되었기 때문에 this에 window가 바인딩 된다.

사실 중첩 함수의 경우에 내부적으로 어떻게 동작해서 this에 window가 바인딩 되는지까진 아직 모르겠다.
딥 다이브 책에 보면 일반 함수는 기본적으로 window가 this로 바인딩되고 객체를 생성하는 데 사용되지 않아서 this가 의미가 없기 때문에 strict 모드에서는 일반 함수의 this는 undefined가 된다고 한다.

화살표 함수

화살표 함수는 자체적으로 this를 가지지 않는다. 그래서 상위 스코프의 this를 찾아서 사용한다.

const counter = {
	num: 1,
	
	// 자체 this가 없다. 상위 스코프는 counter가 속한 곳 => window
	increase: () => ++this.num, // 에러
	
	// 메서드 방식의 this는 인스턴스, 인스턴스 => counter
	decrease() {
		--this.num // num: 1을 참조
	}
};

위 예제에서 increase가 화살표 함수로 선언되었다. 그래서 자체적으로 this를 가지고 있지 않아 상위 스코프의 this를 찾으러 간다.

  1. 상위 스코프는 increase가 속한 counter이다.
  2. counter는 전역 객체인 window에 속해있다.
  3. 그럼 window.counter니까 this = window이다.

decrease는 화살표 함수가 아닌 메서드 방식으로 선언되어 있다. (increase는 메서드 방식을 사용하지 않고 프로퍼티로 선언되었다. 프로퍼티에 화살표 함수가 들어있다.)

  1. 메서드 방식으로 선언된 decrease
  2. this는 decrease를 호출하는 인스턴스 객체
  3. counter.decrease(); 니까 this = counter이다.

increase, decrease 두 메서드의 this는 다르다!

이벤트 핸들러 내부의 this

이벤트는 DOM 요소에 바인딩 된다. DOM 요소는 객체이다.

const onClick = () => console.log('aaa');
button.addEventListener('click', onClick);

이라고 하면

  1. button은 DOM 요소이다. 그럼 button은 객체이다.
  2. 클릭 이벤트가 발생할 때마다 button이 이벤트 핸들러인 onClick을 호출한다.
  3. this는 호출하는 객체이다. this = button이다.

참고자료

0개의 댓글