[Javascript] this에 관하여

박기영·2022년 12월 3일
0

Javascript

목록 보기
22/45

객체, 클래스를 공부하다보면 this라는 것이 많이 등장한다.
엉단어 뜻으로 어렴풋이 "현재 가리키는 요소"를 의미한다는 것은 알겠는데...
정확한 의미는 무엇이고, 어떻게 활용되는지 알아보고자한다.

아래에서 사용되는 코드들은 전부 크롬 개발자 도구, 즉 브라우저에서 실행되었습니다.

this란 무엇인가?

'점 앞’의 this는 객체를 나타냅니다. 정확히는 메서드를 호출할 때 사용된 객체를 나타내죠.
- Javascript Info -

this는 메서드를 호출할 때 사용된 객체를 나타낸다고 한다.
이게 무슨 의미일까? 예시를 살펴보자.

let user = {
  name: "John",
  age: 30,

  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  },
};

user.sayHi(); // John

sayHi() 내부에서 this가 사용되었다.
여기에서 메서드를 호출할 때 사용된 객체는 무엇일까?

음...메서드는 sayHi()이고...이 메서드를 호출한 곳은 user.sayHi()니까...
아! 메서드를 호출할 때 사용된 객체는 user구나!

그렇다. 그래서 thisuser 객체를 나타낸다.
이 경우 this.name은 어떤 값을 지내게 될까?
thisuser 객체를 나타내므로 user.name에 있는 값인 "John"이 된다.

this의 값은 런타임에 결정된다

이게 무슨 소릴까? 런타임에 결정된다??

동일한 함수라도 다른 객체에서 호출했다면 'this’가 참조하는 값이 달라집니다.
- Javascript Info -

자바스크립트에서는 다른 언어들과 다르게 호출되는 곳에 따라서 this가 나타내는게 달라진다는 뜻이다.
this라는 단어 자체의 뜻이 "이 것"이니까 더 이해하기 편한 것 같기도하다...
각설하고, 예시를 살펴보자.

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert(this.name);
}

// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;

// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

같은 함수 sayHi()를 각각 다른 객체에 할당해서 실행하는 예시이다.
sayHi()this를 활용하여 name을 보여주는 함수이다.

하나는 user, 하나는 admin에 할당되어 호출이 되는데,
각각 어떻게 동작을 하게 될까?

우선, 지금까지 this에 대한 개념을 종합해보면
호출되는 곳에 따라서 나타내는게 달라지며, 메서드를 호출할 때 사용된 객체를 나타낸다.

그렇다면 user.f()에서 사용되는 this는 어떤 의미일까?
f()를 호출할 때 사용된 user 객체를 나타낸다.

그렇다면 admin.f()에서 사용되는 this는 어떤 의미일까?
f()를 호출할 때 사용된 admin 객체를 나타낸다.

즉, this가 나타내는 값이 달라졌다!
호출되는 곳에 따라 this가 나타내는 것이 변하는걸 런타임에 결정된다고 하는 것이다.

다양한 상황에서의 this

몇 가지 예시를 통해 어떤 상황에 쓰이는 this가 어떤 의미를 가지는지 살펴보자.
그냥 사용할 때와 일반 함수 내에서 사용할 때를 살펴보겠다.
메서드 내에서의 this는 이전 예시에서 살펴봤기 때문에 생략한다.
더 다양한 예시는 nykim님 블로그를 참고해주시면 되겠습니다.

냅다 사용한 this

this

참고 이미지

냅다 this만 사용하는 경우에는 전역 객체 window를 나타낸다.(브라우저에서)

일반 함수 내에서 사용한 this

위에서 배운 것에 따르면 this는 메서드를 호출할 때 사용한 객체를 나타낸다.
일반 함수를 호출할 때 사용되는 객체는 무엇일까?

function sayHi() {
  return this;
}

그렇다. window 객체이다.

참고 이미지

이런 특성을 통해 다음 예제가 가능하다는 것을 알 수 있다.

var num = 0;
function addNum() {
  this.num = 100;
  num++;
  
  console.log(num); // 101
  console.log(window.num); // 101
  console.log(num === window.num); // true
}
 
addNum();

this.num에서 thiswindow를 나타내므로,
this.numvar num = 0을 말하고 있다는 것을 알 수 있다.

var num === this.num === window.num인 상황인 것이다.

그래서,
첫 번째 console.log()에서는 num에 100이 할당된 상태에서 1 증가한 101이 출력된다.
두 번째 console.log()에서도 101로 증가되어있는 num이 출력된다.
세 번째 console.log()에서는 var num === window.num이므로 true가 출력된다.

그런데, var는 함수 스코프를 가지기 때문에 어디서든 접근이 가능하다.
또한, window는 전역 영역을 담당하기도 한다.
var로 선언된 numwindow에서 접근할 수 있는 이유가 바로 이 것 때문이라고 생각된다.

그래서 약간 예제를 틀어보았다.
var 대신 let을 사용해보자. 어떤 변화가 있을까?

let num = 0;
function addNum() {
  this.num = 100;
  num++;
  
  console.log(num); // 1
  console.log(window.num); // 100
  console.log(num === window.num); // false
}
 
addNum();

let은 블록 스코프를 가진다. 어디서든 접근할 수는 없다.

즉, this.num === window.num이지만, let num과는 별개라는 것이다.

그래서,
첫 번째 console.log()에서는 let num = 0에서의 num에 접근했다.
두 번째 console.log()에서는 this.num = 100에서의 num에 접근했다.
세 번째 console.log()에서는 첫 번째, 두 번째가 각각 다른 것을 가르키므로 false이다.

그런데, 코드를 엄격 모드(strict mode)에서 실행하면 결과가 또 다르다.
아래 예시를 살펴보자.

"use strict";

function sayHi() {
  alert(this);
}

sayHi(); // undefined

엄격 모드에서 실행하면, this엔 undefined가 할당됩니다.
- Javascript Info -

즉, thiswindow를 의미하지 않게 된다는 것이다.

따라서, 방금 본 예제 코드들을 엄격 모드에서 실행하면

"use strict";
 
var num = 0;
function addNum() {
  this.num = 100; //ERROR! Cannot set property 'num' of undefined
  num++;
}
 
addNum();

이렇게 에러가 발생한다. undefined.num이라는 것은 접근 할 수 없는 것이기 때문이다.

생성자 내에서 사용한 this

function Person(name) {
  this.name = name;
}

const park = new Person("park");

console.log(park.name);

new를 통해 생성한 함수에서 사용되는 thisnew가 생성한 함수에 바인딩된다.

단, new를 통하지 않는다면 일반 함수이기 때문에

function Person(name) {
  this.name = name;
}

const park = Person("park");

console.log(park.name);  // TypeError: Cannot read property 'name' of undefined

전역 객체인 window에 바인딩된다.
따라서, window로부터 접근해야된다.

function Person(name) {
  this.name = name;
}

const park = Person("park");

console.log(window.name);  // "park"

this가 없는 화살표 함수

위에서 this의 특징을 살펴봤는데, 화살표 함수(Arrow Function)에서는 이게 좀..다르다!
예시를 살펴보자.

let user = {
  firstName: "보라",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  },
};

user.sayHi(); // 보라

sayHi() 내부에 arrow()라는 화살표 함수가 선언되어있다. 그리고 바로 실행된다.
즉, user.sayHi()를 실행하면 sayHi()arrow()를 생성하고 실행한다.

뭔가..위에서 계속 배운대로라면 this는 메서드를 호출할 때 사용한 객체니까...
thisarrow()를 나타내는거 아니야?? 라고 생각할 수 있다.

그치만, 그렇지않다! 왜??

화살표 함수는 일반 함수와는 달리 ‘고유한’ this를 가지지 않습니다. 화살표 함수에서 this를 참조하면, 화살표 함수가 아닌 ‘평범한’ 외부 함수에서 this 값을 가져옵니다.
- Javascript Info -

그렇다. 화살표 함수에서의 this는 화살표 함수가 아니라 외부 함수를 나타내기때문이다.
즉, arrow() 내에 있는 this는 외부 함수 user.sayHi()this가 되는 것이다.
user.sayHi()this가 되었기때문에, thisuser를 나타내고 "보라"가 출력되는 것이다.

와닿지않는다면 아래 예시를 추가로 살펴보자.

let user = {
  firstName: "보라",
  sayHi() {
    let user2 = {
      firstName: "파랑",
      sayBye() {
        let arrow = () => {
          alert(this.firstName);
        };

        arrow();
      },
    };

    user2.sayBye();
  },
};

user.sayHi(); // 파랑

직전 예시랑 달리진 것은 객체를 한번 더 사용해서 깊어졌다는 것 뿐이다.
여기서의 this는 어떨까?

자, 순서대로 살펴보자.
user.sayHi()를 통해 sayHi()를 실행하고, sayHi()user2 객체를 생성한다.
직후에 user2 객체에 있는 sayBye()를 실행하며, sayBye()arrow()를 선언 및 실행한다.

arrow()는 여전히 화살표 함수이다.
그렇다면...arrow()에서 사용된 this는 "외부" 함수에서 값을 가져온다.
따라서 user2firstName에 접근하여 값을 가져오는 것을 볼 수 있다.

이 예시까지 보고나니, "외부"라는 것은 가장 바깥이 아니라 바로 맞닿아있는 바깥이라는 것도 알았다.

이제 이해는 되었다. 그런데 이걸 언제 써먹는지를 모르겠다.
왜 굳이 this가 외부 함수를 나타내게 하는걸까?

언제 화살표 함수의 this를 활용하는가?

별개의 this가 만들어지는 건 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수가 유용합니다.
- Javascript Info -

그렇다고한다. 예시를 살펴보자.

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach((student) => alert(this.title + ": " + student));
  },
};

group.showList();
// 1모둠: 보라
// 1모둠: 호진
// 1모둠: 지민

forEach()에서 화살표 함수를 사용했기 때문에 this.title은 화살표 함수 바깥에 있는 메서드인 showList()가 가리키는 대상과 같다.
위에서 공부했던 내용에 따라 group 객체를 나타낸다는 것을 알 수 있다.
따라서, 화살표 함수의 this.titlegroup.title과 같다고 할 수 있다.

만약, 화살표 함수가 아닌 일반 함수를 사용했다면 에러가 발생하게 된다.

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach(function (student) {
      // TypeError: Cannot read property 'title' of undefined
      alert(this.title + ": " + student);
    });
  },
};

group.showList();

에러는 forEach()에 전달되는 함수의 thisundefined이기 때문에 발생한다.

참고 자료

proshy님 블로그
nykim님 블로그
Javascript Info
하나몬님 블로그
nesoy님 블로그

profile
나를 믿는 사람들을, 실망시키지 않도록

0개의 댓글