본문 바로가기
Javascript

Javascript의 "this"

by jewook3617 2021. 1. 21.

javascript에서 가장 헷갈리는 부분 중 하나인 "this" 에 대해서 정리해보겠습니다.


TL;DR

  • 일반 함수 선언/표현식

    • 객체의 프로퍼티인 함수에서 this는 자신을 호출한 객체가 된다.

    • 일반 함수에서 this는 전역 객체가 된다. (strict mode에서는 undefined 가 된다.)

    • 브라우저 환경에서 전역 객체는 Window 객체이며 node 환경에서 전역 객체는 global 객체이다.

  • 화살표 함수

    • 함수가 생성된 scope의 this를 물려받는다.

    • 브라우저 환경에서 전역 스코프의 this는 Window 객체이며 node 환경에서 전역 스코프의 this는 module.exports 객체이다.


this의 정체

this는 일반적으로 객체의 프로퍼티인 함수에서 사용될 때 의미가 있습니다. 객체의 프로퍼티인 함수에서 this는 자신을 호출한 객체가 됩니다.

const obj = {
  name: 'Jony',
  hello: function() {
    return `Hello, my name is ${this.name}.`;
  }
};

console.log(obj.hello()) // "Hello, my name is Jony."

 

위 코드의 실행결과

위 코드처럼 obj라는 객체를 만들고 그 객체의 프로퍼티인 hello 함수에서 this를 사용해봤습니다. 여기서 hello 함수를 호출한 객체가 obj 객체이기 때문에 hello 함수 내부에서 this는 obj가 되는 것입니다.

그럼 아래 코드의 경우에는 결과가 어떻게 나올까요?

const obj = {
  name: "Jony",
  hello: function() {
    return `Hello, my name is ${this.name}.`;
  },
};

const obj2 = {
  name: "Jewook",
};

obj2.hello = obj.hello;

console.log(obj2.hello());

name이 "Jewook" 인 새로운 객체 obj2를 만들고 obj.hello 함수를 obj2.hello에 할당했습니다. 이번에는 hello 함수를 obj2가 호출했으므로 obj2.hello 함수의 this는 obj2가 됩니다.

따라서 결과는 "Hello, my name is Jewook." 입니다.

javascript에서 함수의 스코프 정적 스코프(lexical scope)를 따르지만 this는 어디에서 호출 됐느냐에 따라 동적으로 결정됩니다.

정적 스코프를 따른다는 뜻은 함수 선언시 스코프가 정해진다는 뜻입니다. 정적 스코프에 관해서는 이 글에 간략히 정리해놨습니다.

그럼 객체의 프로퍼티가 아닌 일반 함수에서는 this가 어떻게 결정될까요? 일반 함수에서는 this가 전역 객체가 됩니다. 전역 객체란 전역 스코프에 존재하는 값들을 프로퍼티로 가지고 있는 최상위 객체입니다. 브라우저에서의 전역 객체는 window 객체이고 node에서의 전역 객체는 global 객체입니다.

function f() {
  return this;
}

// 브라우저에서
f() === window // true

// node에서
f() === global // true

 

하지만 strict mode에서는 전역 객체가 아닌 undefined가 됩니다. 

"use strict"

function f() {
  return this;
}

// strict mode에서
f() === undefined // true

지금까지 this를 결정하는 두 가지 규칙을 알아봤습니다.

  • 객체의 프로퍼티인 함수에서는 this가 해당 함수를 호출한 객체가 된다.

  • 일반 함수에서는 this가 전역 객체가 된다. (strict mode에서는 undefined가 된다.)

그럼 아래 코드의 결과는 어떻게 나올까요?

name = "Jony"; // 전역변수

const obj = {
  name: "Jewook",
  greetBackwards: function() {
    function getReverseName() {
      let nameBackwards = "";
      for (let i = this.name.length - 1; i >= 0; i--) {
        nameBackwards += this.name[i];
      }
      return nameBackwards;
    }
    return `${getReverseName()} si eman ym ,olleH`;
  },
};

console.log(obj.greetBackwards());

바로 "ynoJ si eman ym ,olleH" 입니다.

이번엔 obj 객체를 만들고 위에서 만들었던 hello 함수의 내용을 거꾸로 return 해주는 함수를 greetBackwards 함수를 프로퍼티로 만들었습니다. 그리고 greetBackwards 함수 안에 this.name을 거꾸로 만들어주는 getReverseName을 선언하고 사용했습니다.

위 코드에서 greetBackwards() 함수는 객체의 프로퍼티인 함수이고 getReverseName() 함수는 일반함수입니다.그리고 this일반함수인 getReverseName() 함수에서 사용되었기 때문에 this가 전역객체가 됩니다. 따라서 this.name은 obj의 name이 아닌 전역변수 name이 된 것입니다.

지금까지 일반 함수 표현식으로 생성한 함수 내부에서 this가 어떻게 결정되는지 알아봤습니다. 이제 화살표 함수를 사용했을 때 this가 어떻게 결정되는지 알아보겠습니다.

화살표 함수

화살표 함수로 생성한 함수에서 this는 정적(lexical)으로 결정됩니다. 즉, 함수 생성 시점에 this가 결정된다는 뜻입니다.

화살표 함수를 사용하면 함수가 생성된 스코프의 this를 물려받습니다. 이 규칙은 strict mode에서도 동일합니다. 아래 코드를 보면서 살펴보겠습니다.

const obj = {
  name: 'Jony',
  hello: () => {
    return `Hello, my name is ${this.name}.`;
  }
};

console.log(obj.hello()) // "Hello, my name is undefined."

함수 표현식으로 hello 함수가 생성된 코드에서는 "Hello, my name is Jony." 라는 결과가 출력되었습니다. 하지만 hello 함수를 화살표 함수로 생성하면 다른 "Hello, my name is undefined." 라는 결과가 출력됩니다.

왜 이런 결과가 나왔는지 알아보기 전에 전역 스코프에서 this가 어떻게 결정되는 지 알아보겠습니다.

// 브라우저에서
console.log(this)   // window 객체

// node에서
console.log(this)   // module.exports 객체

console.log(globalThis)   // global 객체

브라우저 환경에서는 전역 스코프에서의 this전역객체 모두 window 객체입니다. 하지만 node 환경에서는 전역 스코프에서의 this와 전역객체가 다릅니다. 전역 스코프에서의 thismodule.exports 객체이고 전역객체는 global 객체입니다.

그럼 위 코드의 결과가 "Hello, my name is undefined." 가 나온 이유에 대해 알아보겠습니다.

hello 함수가 생성된 스코프는 전역 스코프이므로 this가 window 객체 or module.exports 객체입니다. hello 함수가 그 this를 물려받아 hello 함수의 this도 window 객체 or module.exports 객체가 됩니다. window 객체 or module.exports 객체안에는 name 프로퍼티가 없기 때문에 결과값은 "Hello, my name is undefined." 가 됩니다.

아래 코드도 살펴보겠습니다.

const f = () => this;

// 브라우저에서
f() === window // true

// node에서
f() === global // false

f() === module.exports // true

f 함수 역시 전역 스코프에서 생성되었기 때문에 전역 스코프의 this를 물려받게 됩니다.

그럼 아래 코드의 결과는 어떻게 될까요?

name = "Jony"; // 전역변수

const obj = {
  name: "Jewook",
  greetBackwards: function() {
    const getReverseName = () => {
      let nameBackwards = "";
      for (let i = this.name.length - 1; i >= 0; i--) {
        nameBackwards += this.name[i];
      }
      return nameBackwards;
    }
    return `${getReverseName()} si eman ym ,olleH`;
  },
};

console.log(obj.greetBackwards());

이번엔 getReverseName() 함수화살표 함수로 생성되었습니다. getReverseName() 함수는 greetBackwards() 함수 내부에서 생성되었기 때문에 greetBackwards() 함수의 this를 물려받습니다. greetBackwards() 함수객체의 프로퍼티인 함수이므로 greetBackwards() 함수의 this는 obj 객체가 됩니다. 따라서 getReverseName() 함수의 this도 obj 객체가 되므로 결과값은 "kooweJ si eman ym ,olleH" 입니다.

위에서 살펴본 내용들을 정리해보면 다음과 같습니다.

  • 일반 함수 선언/표현식

    • 객체의 프로퍼티인 함수에서 this는 자신을 호출한 객체가 된다.

    • 일반 함수에서는 전역 객체가 된다. (strict mode에서는 undefined 가 된다.)

    • 브라우저 환경에서 전역 객체는 Window 객체이며 node 환경에서 전역 객체는 global 객체이다.

  • 화살표 함수

    • 함수가 생성된 scope의 this를 물려받는다.

    • 브라우저 환경에서 전역 스코프의 this는 Window 객체이며 node 환경에서 전역 스코프의 this는 module.exports 객체이다.

이 글에 쓰인 코드들은 O'REILLY의 '러닝 자바스크립트' 책에 있는 코드와 MDN 문서의 코드를 참고했습니다.