[JavaScript] 자바스크립트에서 this란?

김진평·2023년 2월 1일
0

JavaScript

목록 보기
5/6
post-thumbnail

this는 여러 언어에서 자주 볼 수 있는 녀석이다.
타 언어와는 달리 자바스크립트에서는 this가 휙휙 바뀔 수 있는데,
이것에 대해 이해하기 위해 한 번 알아보았다.

먼저 자바스크립트의 this는 어떻게 동작할까?

MDN에서 인용하기를 자바스크립트의 this는 "함수를 호출한 방법에 의해 결정된다" 라고 나와있다.

이게 대체 무슨 말인지 글만 보고는 이해하기 어렵다.

Ex1)

const dog = {
  name: "puppy",
  age: "2",
  getName: function() {
    console.log("my name is", this.name)
  }
}

dog.getName();		//my name is puppy

위 코드를 살펴보자.

dog 객체를 선언하고 그 하위의 getName 메서드를 호출하면 "my name is puppy"가 출력된다.

여기서 this는 호출된 객체 즉, dog 객체 자체를 의미하며

dog의 name을 호출하였기 때문에 puppy가 출력되었다.


Ex2)

다음 예시를 보자.

const dog = {
  name: "puppy",
  age: "2",
  getName: function() {
    console.log("my name is", this.name)
  }
}

const globalDog = dog.getName;
globalDog();		//window 객체 호출

위 코드는 첫 번째 예시와 유사하지만 전혀 다른 결과가 나온다.

바로 window 객체를 호출하게 되는데, 이는 globalDog()를 호출한 주체가 window 객체이기 때문이다.

좀 쉽게 말하자면 dog.getName()은 dog 객체가 getName 메서드를 호출하는 것이나

globalDog()는 단순히 dog.getName으로 초기화된 변수를 별다른 주체가 지정되지 않은 상태로 호출되는 것이기 때문에

최상위 객체인 window 객체가 이를 호출한 것이다.


Ex3)

그 다음 예시를 보자

const dog = {
  name: "puppy",
  age: "2",
  getName: function() {
    console.log("my name is", this.name)
  }
}

const cat = {
  name: "miyao",
  age: "1",
  getName: dog.getName
}

cat.getName();		//my name is miyao

새로 cat 객체를 선언하고 dog.getName의 값을 getName 메서드로 선언하였다.

cat.getName();을 실행하면 결과는 "my name is miyao"가 호출된다.

dog.getName 메서드의 this는 호출한 객체 자신이므로

여기서는 cat 객체의 getName 메서드를 호출하였기 때문에 "miyao"가 출력된 것이다.


Ex4)

그렇다면 아래 소스에서는 누가 this일까?

index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="btn">this가 누굴까?</button>
  </body>
  <script src="index.js"></script>
</html>

index.js

const dog = {
  name: "puppy",
  age: "2",
  getName: function () {
    console.log(this);
  },
};

const cat = {
  name: "miyao",
  age: "1",
  getName: dog.getName,
};

const btn = document.getElementById("btn");
btn.addEventListener("click", dog.getName);		//button Element

먼저 dog.getName 메서드에서 this를 콘솔로 출력하도록 변경 후

button Element 클릭 이벤트를 통해 this를 호출해보니 button Element 자체가 호출되었다.

이 또한 cat 객체처럼 this를 호출한 객체가 button Element이기 때문이다.

html Element 또한 예외 없이 호출한 대상이 this가 되는 것을 확인할 수 있다.


this를 고정하고 싶을 때

위 예시들처럼 호출하는 객체에 따라 this가 자꾸 변하기 때문에

this를 고정할 필요가 종종 발생한다.

그런 상황에서는 bind를 이용하면 this를 고정할 수 있다.

const dog = {
  name: "puppy",
  age: "2",
  getName: function() {
    console.log("my name is", this.name)
  }
}

const cat = {
  name: "miyao",
  age: "1",
  getName: dog.getName
}

const bindGetName = cat.getName.bind(dog);		//my name is puppy

bind를 사용할 경우 해당 객체로 this 를 고정할 수 있다.

즉, bindGetName은 "my name is puppy"가 된다.


화살표 함수에서의 this

const cow = {
  name: "beaf",
  age: "5",
  getName: function() {
    console.log("my name is", this.name);
    const innerFunc = function () {
      console.log("innerFunc", this);
    }
    innerFunc();
  }
}

cow.getName();
//첫 번째 콘솔 : "my name is beaf" 출력
//두 번째 콘솔 : window객체 출력

이번에는 객체에 선언된 메서드 내 또 다른 함수가 있을 때 this가 어떻게 적용되는지 확인하기 위해 위 코드를 작성했다.

innerFunc()의 경우 함수 호출시 window 객체가 호출된다.

이유인즉 위에 설명했듯이 innerFunc 함수를 호출하는 객체가 마땅히 지정되지 않아서이다.

그렇다면 innerFunc()의 this가 cow 객체가 되게 하려면 어떻게 해야할까?

먼저 위에서처럼 bind 함수를 이용하는 것이다.

그러나 다른 방법도 존재한다.

바로 화살표 함수를 사용하는 것.

const cow = {
  name: "beaf",
  age: "5",
  getName: function() {
    console.log("my name is", this.name);
    const innerFunc = () => {
      console.log("innerFunc", this.name);
    }
    innerFunc();
  }
}

cow.getName();
//첫 번째 콘솔 : "my name is beaf" 출력
//두 번째 콘솔 : "innerFunc beaf" 출력

화살표 함수를 사용하면 상위 스코프의 this 객체를 그대로 사용할 수 있다.

그렇다면 왜 화살표 함수와 function은 차이가 나는 것일까?

그 이유는 function에는 this가 존재하지만, 화살표 함수에는 this가 아예 존재하지 않아서 그렇다고 한다.

화살표 함수에 this가 존재하지 않기 때문에 자연스럽게 상위 스코프의 객체가 this가 된다.


이럴 때는 화살표 함수를 쓰지 말자..!

1. 메소드

const cow = {
  name: 'beaf';
  callName: () => console.log(this.name);
}

cat.callName();	// undefined

=> 이 경우에는 화살표 함수가 cow 객체보다 상위인 전역 객체를 가리키므로 this.name은 undefined가 된다.

2. 생성자 함수

const Foo = () => {};
const foo = new Foo()	// TypeError: Foo is not a constructor

=> 생성자 함수에서는 사용할 수 없게 만들어졌다.

3. addEventListener()의 콜백함수

const button = document.getElementById('btn');

button.addEventListener('click', () => {
  console.log(this);	// Window
  this.innerHTML = 'clicked';
});

button.addEventListener('click', function() {
   console.log(this);	// button 엘리먼트
   this.innerHTML = 'clicked';
});

=> addEventListener의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있다.
처럼 이미 this의 값이 정해져있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있다.

마무리

this에 대해 공부하고 정리하다보니 여러 상황에서 어떻게 사용해야할지 감이 조금 잡힌 것 같다.
프로젝트를 진행하며 분명히 this 관련해 문제를 맞닥뜨릴 것인데, 그 때 좋은 참고자료가 되었으면 한다.

0개의 댓글