What is this? (this는 무엇입니까?)

hangkemiii·2022년 10월 2일
0

Javascript

목록 보기
7/11
post-thumbnail

What is this?

우리는 자바스크립트를 공부하면서 this라는 단어를 정말 지겹게 보고, 면접 준비를 하면서도 this에 관해서 지겹도록 외웠을 것이다. 그래서 this가 뭔데? 라고 물어보면 아마 대다수는 정해진 대답을 할 것이다.

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

맞다. this는 자바스크립트에서 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. 그러나, 이렇게만 알아둔다면 this에 대해 정확히 이해를 못하고 넘어갈 가능성이 높다. 왜냐? 내가 지금 그러고 있으니까. 그러니 이번 글을 통해서 this에 대해 한번 deep하게 파고 들어가 보자.

this

그럼 아까 했던 정답을 다시 하나씩 차근차근히 뜯어 보자. 자신이 속한 "객체"에서 객체란, 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조를 뜻한다.

const circle = {
  // 프로퍼티: 객체 고유의 상태 데이터
  radius: 10;
  // 메서드: 상태 데이터를 참조하고 조작하는 동작
  getDiameter() {
    return 2 * circle.radius;
  }
};

위의 코드에서 객체는 'circle'이 된다. 여기서는 객체 리터럴 방식으로 객체를 생성했기 때문에 getDiameter 메서드가 호출되기 전에 객체 리터럴의 평가가 완료되어 circle 객체가 생성되었고 메서드 내부에서 circle을 참조할 수 있다. 객체 리터럴 방식으로 생성한 객체의 경우 메서드 내부에서 메서드 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있다. 하지만 이는 바람직하지 않고 일반적이지도 않다. 그러면 이 코드를 this를 사용해 수정해 보자.

// 객체 리터럴
const Circle = {
  radius: 10,
  getDiameter() {
    // this는 메서드를 호출한 객체를 가리킨다.
    return 2 * this.radius;
  }
};

console.log(circle.getDiameter()); // 20

위 코드에서의 this는 메서드를 호출한 객체, 즉 자신이 속한 객체인 circle이다. 그렇다면 '자신이 생성할 인스턴스를 가리키는'에 해당하는 예시는 무엇일까?

만일 생성자 함수로 객체를 생성하게 된다면, 생성자 함수를 정의한 이후에 new 연산자와 함께 생성자 함수를 호출하는 단계가 추가적으로 필요하다. 생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리키는 식별자를 알 수 없다.

function Circle(radius) {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
  ????.radius = radius;
}

Circle.prototype.getDiameter = function () {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
  return 2 * ????.radius;
};

// 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 한다.
const circle = new Circle(5);

바로 이때, 자신이 생성할 인스턴스를 가리키기 위해 this가 사용된다.

// 생성자 함수
function Circle(radius) {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = radius;
}

Circle.prototype.getDiameter = function() {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  return 2 * this.radius;
};

// 인스턴스 생성
const circle1 = new Circle(5);
console.log(circle.getDiameter()); // 10

위의 코드에서의 this는 생성자 함수가 생성할 인스턴스인 circle1을 가리킨다. 즉, this는 객체의 프로퍼티나 메서드를 참조하기 위한 '자기 참조 변수' 인 것이다.

this 바인딩

위의 코드에서 볼 수 있듯이, this는 상황에 따라 가리키는 대상이 다르다. 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정된다.

바인딩 (Binding)

바인딩(Binding) 이란 프로그램의 어떤 기본 단위가 가질 수 있는 구성요소의 구체적인 값, 성격을 확정하는 것을 말한다. 바인딩은 "묶다"라는 사전적 의미로, 코딩에서의 바인딩은 두 데이터 혹은 정보의 소스를 일치시키는 기법을 의미한다.

함수의 호출 방식에는 다양한 종류가 있다.

  1. 일반 함수 호출

  2. 메서드 호출

  3. 생성자 함수 호출

  4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출

이 함수 호출 방식에 따라 this 바인딩 역시 동적으로 결정된다. 어떻게 this 바인딩이 되는지 알아보도록 하자.

일반 함수 호출

기본적으로 일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩된다.

function foo() {
  console.log("foo's this: ", this); // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

하지만 메서드 내에서 정의한 중첩 함수 또는 메서드에게 전달한 콜백 함수(보조 함수)가 일반 함수로 호출될 때 메서드 내의 중첩 함수 또는 콜백 함수의 this가 전역 객체를 바인딩하는 것은 문제가 있다.

중첩 함수 또는 콜백 함수는 외부 함수를 돕는 헬퍼 함수의 역할을 하므로 외부 함수의 일부 로직을 대신하는 경우가 대부분이다. 하지만 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치하지 않는다는 것은 중첩 함수 또는 콜백 함수를 헬퍼 함수로 동작하기 어렵게 만든다.

그렇기 때문에 this 바인딩을 일치시키기 위해서는 화살표 함수를 사용할 수 있다.

var value = 1;

const obj = {
  value: 100,
  foo() {
    // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.
    setTimeout(() => console.log(this.value), 100); // 100
  }
};

obj.foo();

화살표 함수에서는 this가 아예 존재하지 않기 때문에, 그 상위 환경에서의 this를 참조하게 된다. 즉, 화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩된다. 그렇기 때문에 헬퍼 함수로서의 동작을 위해 this 바인딩을 일치시킬 때에 화살표 함수를 사용할 수 있는 것이다.

메서드 호출

메서드 내부의 this에는 메서드를 호출한 객체, 즉 메서드를 호출할 때 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체가 바인딩된다.

const person = {
  name: 'Kyeom',
  getName() {
    // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
    return this.name;
  }
};

// 메서드 getName을 호출한 객체는 person이다.
console.log(person.getName()); // Kyeom

주의할 것은 메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다는 것이다. 위 예제의 getName 메서드는 person 객체의 메서드로 정의되었다. 즉, getName 프로퍼티가 함수 객체를 가리키고 있을 뿐이지 person 객체의 getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체다.

따라서 getName 프로퍼티가 가리키는 함수 객체, 즉 getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.

따라서 메서드 내부의 this는 프로퍼티로 메서드를 가리키고 있는 객체와는 관계가 없고 메서드를 호출한 객체에 바인딩된다.

생성자 함수 호출

생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩된다.

생성자 함수는 이름 그대로 인스턴스를 생성하는 함수다. 일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다. 만약 new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수가 아니라 일반 함수로 동작한다.

apply(), call(), bind()

함수 호출 방법으로는 위의 3가지 방법 외에도 apply(), call(), bind()를 사용하는 방법이 있다. apply(), call(), bind()로 함수를 호출하면 this 바인딩이 전역 객체인 window가 아닌 다른 객체에 할당된다.

call()

func.call(thisArg[, arg1[, arg2[, ...]]])

Function.prototype.call() 메서드에서 매개변수는 thisArg로, func 호출에 제공되는 this의 값을 뜻한다. 그리고 두 번째부터는 호출할 함수들의 인수가 들어가게 된다.

let Human1 = {
    name: 'Kyeom'
};

let Human2 = {
    name: 'Kim',
    callName: function() {
        console.log('이 사람의 이름은' + this.name + ' 입니다.');
    }
};

Human2.callName(); // 이 사람의 이름은 Kim 입니다.

// call()
Human2.callName.call(Human1); // 이 사람의 이름은 Kyeom 입니다.

위 예제를 보게 되면, call() 메서드로 Human2의 function을 호출하고 있지만 매개변수로 Human1을 넣어주었기 때문에 this는 Human1을 가리키게 된다.

apply()

func.apply(thisArg, [argsArray])

Function.prototype.apply() 메서드는 call() 메서드와 유사하지만, call() 은 함수에 전달될 인수 리스트를 받는데 비해, apply() 는 인수들의 단일 배열을 받는다는 차이점이 있다. 즉, 첫 번째 매개변수 thisArg로 this를 지정해주는 점은 동일하지만, apply()는 두 번째 매개변수를 배열 형태로 넣게 된다.

bind()

func.bind(thisArg[, arg1[, arg2[, ...]]])

Function.prototype.bind() 메서드는 호출될 경우에 새로운 함수를 생성하게 된다. 받게되는 첫 인자의 value로는 this 키워드를 설정하고, 이어지는 인자들은 바인드된 함수의 인수에 제공된다.

let Human1 = {
    name: 'Kyeom'
};

let Human2 = {
    name: 'Kim',
    callName: function() {
        console.log('이 사람의 이름은' + this.name + ' 입니다.');
    }
};

Human2.callName(); // 이 사람의 이름은 Kim 입니다.

// bind()
let person = Human2.callName.bind(Human1);

person(); // 이 사람의 이름은 Kyeom 입니다.

bind()는 call(), apply()와 같이 함수가 가리키고 있는 this를 바꾸지만 호출되지는 않기 때문에, 변수를 할당하여 호출하는 형태로 사용된다.

마치며

이렇게 우리는 this에 대한 정의와 this 바인딩까지 거의 this에 대한 모든 것을 알아보았다. 박수 짝짝짝! 사실 항상 this를 어렴풋이만 알아오면서 매번 헷갈리기 일쑤였는데 이렇게 글로 정리해보니 이해가 조금 은 더 수월하게 되는 듯 하다. 역시 기록의 힘! 앞으로도 꾸준히 velog를 쓰도록 노력해보자.

참고 자료

https://www.zerocho.com/category/JavaScript/post/57433645a48729787807c3fd

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call

모던 자바스크립트 딥 다이브 책

profile
Front-End Developer

1개의 댓글

comment-user-thumbnail
2022년 10월 5일

왓이즈 디스~ 글 잘보고 갑니다! 역시 개발 선생님답게 이해가 아주 쏙쏙 되네요~ Good Job~!
this 퍼가요~

답글 달기