자바스크립트 - this

Radin·2023년 9월 14일
3

Javascript

목록 보기
6/7
post-thumbnail

들어가기

this는 Javascript뿐만 아닌 다른 객체지향 프로그래밍에서 사용되는 키워드이다. Javascript에서의 this 다른 언어와 조금 다르게 동작한다. 그래서 유독 헷갈려 하는 키워드이다. 호출방식에 따라 바인딩이 달라지는 this에 대해 자세히 살펴보자

🐬 1. this의 정의

this는 '자신이 속한 객체 또는 자신이 생성할 인스턴스'를 가리키는 자기 참조 변수다.
this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메소드를 참조할 수 있다.

🐬 2. this는 어떻게 바인딩 되나요?

💡 바인딩이란?
바인딩은 식별자을 연결하는 과정을 의미한다.

자바나 C++같은 클래스 기반 언어에서 this는 언제나 클래스가 생성하는 인스턴스를 가리킨다. 다른 언어와 다르게 자바스크립트의 this는 실행컨텍스트가 생성될때 this binding이 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로 즉, 함수를 호출할때 함수 호출 방식에 의해 동적으로 this binding된다.

💡 함수의 호출 방식은 크게 4가지가 있다.
1. 일반 함수 호출
2. 메소드 호출
3. 생성자 함수 호출
4. apply / call / bind 호출

🦭 2-1 일반 함수 내부에서의 this (기본 바인딩)

기본적으로 this는 전역 객체(global object)가 바인딩 된다.
일반 함수 내부에서의 this는 호출한 매개체에 바인딩 된다(기본 바인딩)

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

function foo() {
  return this; // 기본 바인딩
}

foo() === window; // true

🦭 2-2 메서드 내부에서의 this (암시적 바인딩)

메서드 내부에서의 this는 메서드를 호출한 객체와 바인딩된다.

let radin = {
  firstName: "radin",
  lastName: "Lee",
  intro() {
    console.log(`My name is ${this.firstName}`)
  }
}

radin.intro(); // My name is radin

🦭 2-3 생성자 함수 내부에서 this (new 바인딩)

생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스와 바인딩된다.

function person(firstName, lastName) {
 this.firstName = firstName,
 this.lastName = lastName,
 this.intro = function() {
	console.log(`My name is ${this.firstName}`)
 };
};

let radin = new person('radin', 'Lee');
console.log(radin.intro()); // My name is radin

🦭 2-4 apply / call / bind 호출시 this (명시적 바인딩)

💡 Call, Apply, Bind 메서드 사용 시, 메서드에 첫 번째 인수로 전달하는 객체에 this가 바인딩 된다.

callapply 메서드는 기본적으로 함수를 호출하는 역할을 한다. 기존 함수 호출과 차이점은 해당 메서드를 사용해 함수를 '실행'하면, 함수의 첫 번째 인자로 전달하는 객체에 this를 '바인딩'할 수 있다.

이를 통해서 유사 배열 arguments 객체에 배열 메서드를 사용할 수 있다. 반면 bind첫 번째 인자this에 바인딩하지만 함수를 실행하진 않으며, 새로운 함수를 반환한다.

call

call 사용시 함수를 실행하고 함수의 첫 번째 인자로 전달하는 값에 this를 바인딩한다.

function logInfo(a, b, c) {
  console.log(this.name);
  console.log(this.language);
  console.log(a + b + c);
}

const person = {
  name: 'radin',
  language : 'korean'
}

logInfo.call(person, 1, 2, 3);
// 'radin'
// 'korean'
// 6

apply

apply 사용시 함수를 실행하고 함수의 첫 번째 인자로 전달하는 값에 this를 바인딩한다. call과의 차이점은 인자를 배열의 형태로 전달한다. 이 때, 인자로 배열 자체가 전달하는 것이 아니라 배열의 요소들이 값으로 전달된다.

function logInfo(a, b, c) {
  console.log(this.name);
  console.log(this.language);
  console.log(a + b + c);
}

const person = {
  name: 'radin',
  language : 'korean'
}

logInfo.apply(person, [1, 2, 3]);
// 'radin'
// 'korean'
// 6

bind

bind는 함수의 첫 번째 인자에 this를 바인딩한다는 점은 같지만, 함수를 실행하지 않고, 새로운 함수를 반환한다.

function logInfo(a, b, c) {
  console.log(this.name);
  console.log(this.language);
  console.log(a + b + c);
}

const person = {
  name: 'radin',
  language : 'korean'
}

const radin = logInfo.bind(person, 1); // 새로운 함수

radin(2, 3);
// 'radin'
// 'korean'
// 6

🐬 3. 콜백 함수 내부에서 this

💡 콜백 함수는 제어권을 다른 함수(또는 메서드)에 넘긴 함수이다.

콜백 함수는 제어권이 넘겨진 다른 함수의 내부 로직에 따라 실행되며, this역시 다른 함수 내부 로직에서 정한 규칙에 따라 값이 제어된다.

// (1)
setTimeout(function () {
  console.log(this);
}, 3000);

// (2)
[1, 2, 3].forEach(function (num) {
  console.log(this, num);
});

// (3)
document.body.querySelector("#id").addEventListener("click", function (e) {
  console.log(this, e);
});

1️⃣ setTimeout 함수는 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않는다. 따라서 3초 뒤 전역 객체가 출력된다.

2️⃣ forEach 메서드 또한 대상이 될 this를 지정하지 않으므로 1, 2, 3과 함께 전역 객체가 출력된다.

3️⃣ addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의되어 있다. 따라서 document.body.querySelector('#id')의 대상이 this로 출력된다.

  • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따른다.
  • 특별히 정의되지 않은 경우, 일반 함수와 동일하게 전역 객체를 바라본다.

🐬4. 화살표 함수 내부에서 this

콜백 함수는 내부 this특별히 정의되지 않은 경우 일반 함수와 동일하게 전역객체를 바인딩 한다고 하였다. 객체안의 콜백함 내부 this가 객체에 binding을 하고 싶다면 어떻게 해야할까?

const obj = {
    name: 'name',
    showName() {
        setTimeout(function() {
            console.log(this.name) 
        },1000)
    }
}
obj.showName() // 전역객체에서 name을 찾아 '' 빈공백으로 나타난다.

obj라는 객체 안의 showName을 호출하였더니 this는 암시적으로 전역객체를 바인딩 하였다.

const obj = {
  name: 'name',
  showName() {
	const target = this;
    setTimeout(function() {
     console.log(target.name)
    },1000)
  }
}
obj.showName() // 'name'

obj.showName은 this는 호출한 매개체 (obj)를 바인딩하여 변수 target에 바인딩 된 this를 할당한다 그후 콜백함수 내에서 사용하여 name을 출력한다.

하지만 코드가 길어질 뿐만아니라 신경써야 하는 요소가 많다. 예시로 콜백 안의 콜백안의 콜백.... 처럼 길어진다면? 🤔

화살표 함수는 이러한 불편함을 해소할 수 있다.
화살표 함수는 thisbinding이 일어나지 않는다 즉, 화살표 함수 내의 this는 스코프 체인상 가장 가까운 this를 참조한다.

const obj = {
  name: 'name',
  showName() {
    setTimeout(() => {
     console.log(this.name)
    },1000)
  }
}
obj.showName() // 'name'

정리

  • this는 '자신이 속한 객체 또는 자신이 생성할 인스턴스'를 가리키는 자기 참조 변수다.
  • this 바인딩의 종류는 크게 4가지가 있다.
    1. 기본 바인딩 - 일반함수 호출
    2. 암시적 바인딩 - 메서드 호출
    1. new 바인딩 - 생성자 함수 호출
    2. 명시적 바인딩 - apply / call / bind 호출
  • this 바인딩 우선순위
    new 바인딩 > 암시적 바인딩 > 명시적 바인딩 > 기본 바인딩
  • 화살표 함수의 this binding이 일어나지 않는 특징을 활용하여 간결한 코드로 내부 this를 상위 스코프에 bind할 수 있다.

마무리

이처럼 자바스크립트 this는 함수 호출 방식에 따라서 동적으로 결정된다.
다른 언어와 달리 this는 "상황"에 따라 다른 참조방식을 채택한 이유가 같은 단어라 할지라도 누가 어떤 상황(context)에서 접했나에 따라 의미가 달라지는 의미사용이론 처럼 자바스크립트가 클래스기반 객체지향이 아닌 프로토타입 기반 객체지향을 선택한 이유와 일맥상통 하다고 생각한다.
개인적으로 this를 왜이렇게 어렵게 설계했을까란 생각에 자바스크립트는 왜 프로토타입을 선택했을까 글을 참고하여 생각을 정리 하였지만 다른 사람의 의견도 다를 수 있다고 생각한다.
위 아티클을 읽어보고 각자 생각을 공유해보는 것도 좋을 것 같다.

Reference

참고 도서
모던 자바스크립트 Deep Dive
코어 자바스크립트
참고 영상
10분 테크톡 - 크리스의 Prototype
참고 자료
자바스크립트는 왜 프로토타입을 선택했을까 - 임성묵
this - JavaScript | MDN
JavaScript: 이것(this)이 의미하는 바는 무엇입니까? - web.dev
[JavaScript] this 개념 정리 및 연습 문제 - TimeGamBit

profile
Front-end developer

0개의 댓글