Cllaude99Cllaude99

JavaScript의 this 바인딩

·8 min read·Cllaude99
JavaScriptthisArrow Function

JavaScript는 프로토타입 기반 객체지향 언어로, this 키워드는 함수가 호출되는 방식에 따라 다르게 바인딩됩니다. this라는 키워드는 JavaScript뿐만 아니라 Java, C# 등의 객체지향 프로그래밍 언어에서도 사용되지만, JavaScript의 this는 다른 언어들과 다르게 동작하는 특징이 있습니다.

JavaScript에서 this는 함수가 호출되는 방식에 따라 다르게 바인딩됩니다

JavaScript의 가장 큰 특징 중 하나는 함수가 호출되는 방식에 따라 this에 바인딩되는 객체가 달라진다는 것입니다.

함수 호출 방식은 아래와 같이 다양합니다.

  • 일반 함수 호출
  • 메서드 호출
  • 생성자 함수 호출
  • apply/call/bind 호출

위의 함수 호출방식에 대해 하나씩 알아보겠습니다.

일반 함수 호출

console.log(this === window); // true

function thisFunction1() {
  console.log(this);
}
thisFunction1(); // window 또는 global

function thisFunction2() {
  const a = 10;
  console.log(this.a);
}

thisFunction2(); // undefined

function thisFunction3() {
  console.log('thisFunction3: ', this); // window 또는 global
  function thisFunction4() {
    console.log('thisFunction4: ', this); // window 또는 global
  }
  thisFunction4();
}

thisFunction3();

위 코드의 실행결과를 보면 알 수 있듯이 일반 함수로 호출될 때 this는 전역 객체를 가리킵니다. 즉, 브라우저에서는 window, Node.js에서는 global을 가리키게 됩니다.

특히 위의 thisFunction3 안에 있는 thisFunction4 내부함수에서도 this가 전역객체에 바인딩됩니다.

메서드 호출

다음으로 함수가 객체의 메서드로 호출되는 상황에서 this가 바인딩되는 상황을 살펴보겠습니다.

const obj1 = {
  name: '메서드 호출',
  showThis: function () {
    console.log(this.name);
  },
};

obj1.showThis(); // 메서드 호출
const obj2 = {
  a: 'hello',
  f1: function () {
    console.log(this.a);
  },
};

obj2.f1(); // hello

위와 같이 객체의 메서드로 호출될 때, this는 해당 메서드를 소유한 객체를 가리킵니다. (. 왼쪽에 있는 값을 가리킨다고 보면 이해하기 쉽습니다.)

메서드 호출 시 주의사항

메서드 호출 시에는 함수를 매개변수(콜백)로 넘겨서 실행하는 것을 주의해야 합니다.

const obj = {
  a: 'hello',
  f1: function () {
    console.log(this.a);
  },
};

setTimeout(obj.f1, 1000); // undefined

위와 같이 객체에 정의되어있는 함수의 레퍼런스를 매개변수로 전달하는 상황에서는 this는 기본 바인딩이 적용되어 전역 객체에 바인딩됩니다.

그 이유는 setTimeout 함수 안에 전달한 콜백은 f1 함수의 레퍼런스일 뿐, obj의 컨텍스트를 가지고 있지 않기 때문입니다.

즉, setTimeout 내부에서 호출되는 콜백은 obj 객체의 컨텍스트에서 실행되는 것이 아니기 때문에, this는 기본 바인딩이 적용되어 전역 객체에 바인딩됩니다.

생성자 함수 호출

생성자 함수 호출과정에서 this 바인딩에 대해 확인해보겠습니다.

function Person(name) {
  this.name = name;
}
const person = new Person('JS');
console.log(person.name); // 'JS'

위와 같이 new 키워드와 함께 호출되는 생성자 함수에서, this는 새로 생성되는 인스턴스를 가리킵니다.

주의할 점은 아래와 같이 new를 붙이지 않는다면, 생성자 함수로 동작하지 않게 됩니다.

const person = Person('JS');
console.log(person.name); // undefined

그렇다면 호출에 따라 this가 전역으로 바인딩되기도 하고, 해당 메서드가 있는 객체, 인스턴스를 가리키기도 하는데 this를 명시적으로 지정할 수는 없을까요?

apply/call/bind 메서드를 사용하면 가능합니다.

apply/call/bind 호출 (명시적 바인딩)

JavaScript의 모든 함수는 call, apply, bind라는 프로토타입 메서드를 가지고 있습니다.

즉, 위의 메서드들은 Function.prototype 객체의 메서드입니다. (Function.prototype.apply, Function.prototype.call, Function.prototype.bind)

function fn() {
  console.log(this.name);
}
fn(); // undefined

const obj = {
  name: '자바스크립트',
};

fn.call(obj); // 자바스크립트

위의 결과를 보면, 함수가 실행되고 첫 번째 인자로 전달한 값에 this가 바인딩된 것을 확인할 수 있습니다.

화살표 함수

ES6에서 추가된 화살표 함수에서 this 바인딩은 어떻게 동작할까요?

화살표 함수의 경우 this 바인딩 시에 앞서 살펴본 규칙들이 적용되지 않고 this에 렉시컬 스코프가 적용됩니다.

즉 화살표 함수는 자신만의 this를 가지지 않습니다. 이러한 이유로 화살표 함수는 생성자 함수로 사용할 수 없습니다.

아래에서 함수 선언식과 화살표 함수에서 this 바인딩을 비교해보겠습니다.

const obj = {
  arrowFn: () => {
    console.log(this);
  },
  fn() {
    console.log(this);
  },
};

obj.arrowFn(); // window 또는 global
obj.fn(); // obj

함수 선언식의 경우 this는 함수를 호출한 객체를 가리키지만, 화살표 함수의 경우에는 객체 리터럴이 평가되는 스코프는 전역 스코프이기 때문에 전역 객체(window 또는 global)를 가리키게 됩니다.

화살표 함수의 콜백함수 예시도 살펴보겠습니다.

const obj = {
  a: 10,
  fn: function () {
    setTimeout(() => {
      console.log(this.a);
    }, 1);
  },
};

obj.fn(); // 10

위의 예시에서 setTimeout의 콜백함수로 화살표 함수를 주었을 경우 화살표 함수는 렉시컬 스코프를 따르므로 this는 obj 객체를 가리킵니다. 따라서 위의 obj.fn에 의해 콜백함수가 실행될 때 this는 obj 객체를 가리키게 됩니다.

참고 자료