[JS]모자딥 CH 22 this 완벽정리

현우.·2024년 11월 10일
post-thumbnail

자바스크립트를 처음 배웠을 때 this가 나오면 this가 무엇을 바인딩하는지 항상 헷갈렸다.😂
그래서 모자딥을 읽으면서 헷갈릴때마다 볼 수 있게 정리해보도록 하겠다.


🚀 this 키워드

자바스크립트의 객체는 프로퍼티메서드로 구성된다.
메서드는 프로퍼티를 참조하고 변경하는 역할을 한다.
따라서 자신이 속한 객체의 프로퍼티를 참조하려면 우선 자신의 속한 객체를 가리킬 식별자가 필요하다.

객체 리터럴에서 객체 참조

물론 객체 리터럴의 경우 자신이 속한 객체를 재귀적으로 참조할 수도 있긴하다.

const circle = {
  radius: 5,
  getDiameter: function () {
    return 2 * circle.radius;
  },
};

console.log(circle.getDiameter());

그러나 이러한 방법은 일반적이지 않을 뿐더러 바람직하지도 않다.

생성자 함수에서 객체 참조

생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 전이기 때문에
생성자 함수가 생성할 인스턴스를 가리킬 식별자를 알 수 없다.😓

이러한 경우 때문에 JS는 자신이 속한 객체생성될 인스턴스를 가리킬 특수한 식별자인 this를 제공한다.

this는 arguments와 같이 함수 호출시 함수 내부에 암묵적으로 생성된다.
또한 this는 함수가 호출되는 시점에 호출 방식에 의해 동적으로 결정된다.
이 내용은 뒤에서 다뤄보도록 하자.

위 코드를 this 식별자를 이용해 수정해보자

// 객체 리터럴 
const circle = {
  radius: 5,
  getDiameter: function () {
    return 2 * this.radius;
  },
};

console.log(circle.getDiameter());
// 생성자 함수
function Circle(radius) {
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

const circle = new Circle(5);
console.log(circle.getDiameter());

🚀 함수 호출 방식과 this 바인딩

바인딩

식별자와 값을 연결하는 과정을 의미한다. 쉽게말해 this가 가리킬 객체에 연결된다고 생각하자.

this 바인딩은 함수가 어떻게 호출되었냐에 따라 동적으로 결정된다.


참고📌) 렉시컬 스코프 vs this 바인딩 결정 시기

렉시컬 스코프는 함수객체 생성시점에 상위스코프가 결정되지만,
this 바인딩은 함수객체 호출 시점에 결정된다.


1. 일반 함수 호출

일반함수 내부의 this는 전역객체(window)가 바인딩된다.
또한 전역 함수뿐만이 아니라 중첩 함수콜백 함수또한 일반함수로 호출된다면 마찬가지로 전역객체가 바인딩된다.

var value = 1;
function foo() {
  console.log("전역함수 this:", this); //  window
  console.log("value: ", this.value); // 전역 객체의 프로퍼티 value

  function boo() {
    console.log("중첩함수 this:", this); //  window

    setTimeout(() => {
      console.log("비동기 콜백함수 this:", this); // window
    }, 1000);
  }
  boo();
}

foo();

객체의 메서드 내에서의 중첩함수나 콜백함수또한 마찬가지로 내부의 this는 전역객체에 바인딩된다.
그러나 이경우 문제가 있다. 중첩함수나 콜백함수는 외부 함수를 돕는 헬퍼함수로서의 역할을 하는데 서로의 this가 일치하지 않는다면 헬퍼함수로서 동작하기 어렵게 만들것이다.
(메서드에서 this는 메서드를 호출한 객체를 참조함. 뒤에서 다룰예정)

메서드 내부의 중첩함수, 콜백함수 this바인딩 일치시키는 방법

1. 메서드의 this 변수에 할당시키기

const obj = {
  value: 100,
  foo() {
    const that = this;
    setTimeout(function() {
      console.log(that.value); // 100 
    }, 1000);
  },
};

obj.foo();

2. Function.prototype의 bind 메서드 이용해 this 명시적 바인딩
bind, call, apply 메서드에 대해서는 뒤에서 다루도록 하겠다.

간단히 설명하자면 bind함수의 인자로 들어온 this를 객체 obj로 고정시키고,
콜백함수의 this를 obj로 명시적 바인딩 시켜주는 것이다.

const obj = {
  value: 100,
  foo() {
    setTimeout(
      function () {
        console.log(this.value); // 100
      }.bind(this),
      1000
    );
  },
};

obj.foo();

3. 화살표 함수 사용

const obj = {
  value: 100,
  foo() {
    setTimeout(() => {
      console.log(this.value); // 100
    }, 1000);
  },
};

obj.foo();

화살표 함수는 자신만의 this를 가지고 있지 않기 때문에 함수 선언시 상위 스코프에서 this를 가져와 자신의 this에 바인딩해준다.


2. 메서드 호출

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

const person = {
  name: "lee",
  getName: function () {
    return this.name;
  },
};

console.log(person.getName()); // lee

getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함되는 것이 아니라
별도의 함수 객체로 존재한다.
getName 프로퍼티는 단지 함수객체를 참조하고 있을 뿐이다.

따라서 getName 프로퍼티가 참조하는 함수객체는 다양한 방식으로 활용할 수 있다.

활용 방식

  1. 다른 객체의 프로퍼티에 할당

  2. 일반 변수에 함수객체 할당

결론적으로 메서드 내부의 this는 메서드를 가지고 있는 객체와는 관계가 없고,
메서드를 호출한 객체에 바인딩 된다!

프로토타입 객체에 정의된 메서드

프로토타입 객체에 정의된 메서드 또한 this는 해당 메서드를 호출한 인스턴스에 바인딩된다.

function Person(name) {
  this.name = name;
}
Person.prototype.getName = function () {
  return this.name; // this는 getName을 호출한 인스턴스에 바인딩
};

const me = new Person("짱구");

console.log(me.getName()); 

3. 생성자 함수 호출

생성자 함수의 this는 생성될 인스턴스에 바인딩된다.

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.getName = function () {
    return this.name;
  };
}

const me = new Person("짱구", 5);
console.log(me.getName());

만약 new 연산자없이 함수를 호출하면 이 함수는 생성자 함수가 아니라 함수로서 동작한다.
this는 객체의 프로퍼티,메서드를 참조하기위한 변수이기 때문에
일반적으로 객체의 메서드 내부생성자함수 내부에서만 의미가 있다.


4. Function.prototype.apply/call/bind

apply, call, bind는 Function 프로토타입 객체의 메서드이며 모든 함수가 상속받아 사용 가능하다.

1. apply, call 메서드

apply call 메서드는 인수로 this로 사용할 객체(this 자체일수도있음), 인수 리스트를 받아 함수를 호출한다.

그러나 전달 받는 형식에 차이가 있다.

  • apply- this로 사용할 객체, 인수 리스트 배열 or 유사배열객체
  • call- this로 사용할 객체, 인수 리스트

이 외에 둘의 기능은 동일하다.

두 메서드의 본질적인 기능은 함수 호출이다.
즉 함수 호출시 첫번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩 해준다.

사용 예시를 보자

// 1. apply 적용
onst person = {
  fullName: function(city, country) {
    console.log(`${this.firstName} ${this.lastName} from ${city}, ${country}`);
  }
};

const user = {
  firstName: "John",
  lastName: "Doe"
};

// `this`를 `user`로 설정하고, 인자를 배열로 전달
person.fullName.apply(user, ["New York", "USA"]); 
// 출력: "John Doe from New York, USA"

// 2. call 적용
const person = {
  fullName: function(city, country) {
    console.log(`${this.firstName} ${this.lastName} from ${city}, ${country}`);
  }
};

const user = {
  firstName: "Alice",
  lastName: "Smith"
};

// `this`를 `user`로 설정하고, 인자를 개별적으로 전달
person.fullName.call(user, "London", "UK"); 
// 출력: "Alice Smith from London, UK"

활용에제: 유사배열 객체에 배열 메서드 사용하기

slice함수의 this를 arguments 객체로 설정하면, arguments 객체를 배열처럼 다룰 수 있게 되어
유사 배열 객체인 arguments를 실제 배열로 변환할 수 있다.

function example() {
  // `slice`의 `this`를 `arguments`로 설정하여 유사 배열 객체를 배열로 변환
  const argsArray = Array.prototype.slice.call(arguments);
  console.log(argsArray); // [1, 2, 3]
}

example(1, 2, 3);

2. bind 메서드

bind 메서드는 apply나 call 메서드와 달리 함수를 호출하지 않고 단지 반환한다.
다만 첫번째 인수로 전달한 값으로 this 바인딩이 교체된 새로운 함수를 반한환다.

const person = {
  name: "kim",
  foo() {
    setTimeout(
      function () {
        console.log(`Hi my name is ${this.name}`);
      }.bind(this),
      1000
    );
  },
};

person.foo();
profile
학습 기록.

2개의 댓글

comment-user-thumbnail
2024년 11월 12일

안녕하세요! 어차피 저도 책 표지 따와서 만든거라 원본 그대로 쓰셔도 됩니다..! 화질이 깨지니까 마음이 아파서요 ㅠ
https://media.vlpt.us/images/dnr6054/post/003fb185-6fe9-4bc2-bf79-ddf0779d7ccb/DeepDiveThumbnail.png

1개의 답글