자바스크립트 딥다이브 - This

ChoiYongHyeun·2023년 12월 14일
0

this 키워드

객체 지향 프로그래밍에서 살펴보았듯이

객체는 객체의 상태를 나타내는 프로퍼티 와 동작을 나타내는 메소드 가 존재한다.

메소드는 다양한 객체들을 인수로 받을 수 있는데 그 중 메소드가 포함되어 있는 객체의 프로퍼티도 인수로 받을 수 있다.

그러면 ! 메소드는 어떻게 본인을 포함하고 있는 객체의 프로퍼티에 접근할까 ?

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

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

다음처럼 접근 할 수 있을 것이다.

circle 객체를 선언하고 할당하는 동안 getDiameter 는 이미 선언된 circle 의 주소를 가리키면 된다.

하지만 본인 자신을 재귀적으로 참조하는 방법은 일반적이지 않으며 바람직하지 않다.

이번에는 생성자 함수를 이용해서 해보자

function Circle(radius) {
  ????.radius = radius;
  ????.getDiameter = function getDiameter() {
    return 2 * ????.radius;
  };
}

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

객체 리터럴 방식에서는 변수를 할당하는 동안 이미 가리킬 식별자가 정해졌지만

생성자 함수같은 경우엔 해당 생성자 함수를 이용해서 무한의 객체를 만들 수 있기 때문에 만들 인스턴스를 가리킬 무엇인가가 필요하다.

???? 에 만약 circle 과 같이 고정된 식별자를 사용하면 식별자 이름이 circle 인 객체의 경우에는 잘 작동하겠지만 circle1 , circle2 .. 등 식별자 이름이 다른 것에서는 작동을 하지 않을 것이다.

그러면 어떤 식별자 명으로 인스턴스를 만들어도 그 인스턴스를 가리킬 어떤 식별자가 필요한데 그것이 바로 this 이다.

this는 자신이 속한 객체 혹은 생성할 인스턴스를 가리키는 자기 참조 변수이다. this 를 통해 속한 객체나 생성할 인스턴스를 가리키고, 이를 이용해 프로퍼티나 메소드에 접근 할 수 있다.

식별자 this 가 객체를 가리키는 것을 this 바인딩 이라고 한다.

그러면 식별자 this 는 어떻게 바인딩을 시행할까 ?


함수 호출 방식과 this 바인딩

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

함수가 사용되는 4가지 경우가 있는데 4가지 경우에 따라서 확인해보자

일반 함수 호출

globalThis.x = 1;

function foo() {
  console.log(this.x);
}

foo(); // 1

일반 함수로 호출 될 때 this 는 전역 객체인 global or window 를 가리킨다.

함수가 선언된 위치와 상관 없이 일반 함수로 호출 될 때에는 무조건 전역 객체를 가리킨다.

globalThis.x = 1;

function foo() {
  let x = 2;

  function bar() {
    console.log(this.x);
  }

  bar();
}

foo(); // 1

중첩함수로 함수 내에서 함수를 선언해도 전역 객체인 global 을 가리킨다.

메소드 호출

어떤 객체의 메소드로서 호출 될 때에는 해당 객체와 바인딩된다.

const person = {
  name: 'lee',
  introduce() {
    console.log(`hi i am ${this.name}`);
  },
};

person.introduce(); // hi i am lee

메소드로 선언된 introduce 에서 thisintroduce가 호출된 객체에 바인딩 된다.

여기서 person 메소드로 설정한 introduce 가 마치 person에 포함 된 것처럼 생각하게 될 수 있는데

포함된 것이 아니라 단순히 독립적으로 존재하는 별도의 객체이다.

다만 함수 객체의 introduce 프로퍼티가 함수 객체를 가리키고 있을 뿐이다.

책에서 이부분을 보고 한 30분동안 머리를 싸맸다.
person 내에서 introduce() {.. } 이런식으로 프로퍼티로 설정한 것은 단순히
introduce : function() {... } 로 한 것과 같다. (메소드 축약 표현)
person.introduce가 가리키고 있는 함수 객체인 function(){... } 또한 독립적인 함수 객체들이다.
person.introduce 가 해당 함수 객체를 가리키고 있는 것일뿐

여기서 포인트는 메소드로 선언된 함수객체에서의 this는 자신을 가리키고 있는 프로퍼티를 소유한 객체를 의미한다는 것이다.

personintroduce 라는 프로퍼티가 함수 객체를 가리키고 있으니 , 함수 객체에서의 this 는 프로퍼티를 소유한 객체를 의미한다.

introduce() {... } 는 독립적인 함수 객체라고 하였기 때문에 다른 인스턴스의 프로퍼티로 설정하는 것도 가능하다.

const tom = {
  name: 'tom',
  introduce() {
    console.log(`hi i am ${this.name}`);
  },
};

const jerry = {
  name: 'jerry',
};

jerry.IntoduceMyself = tom.introduce;
jerry.IntoduceMyself(); // ????

이번에는 jerryIntoduceMyself 라는 프로퍼티에 tom.introduce 를 할당시켰다.

그렇게 되면 결과는 어떻게 될까 ?

동일한 프로퍼티 명을 사용하면 마치 함수로 호출되는 것처럼 혼동이 있을 것 같아 일부러 jerry 의 프로퍼티명을 바꿔보았다.

결과는 hi i am jerrythis 가 가리키는 인스턴스 값이 변경되었다.

이처럼 메소드로 사용된 함수는 호출될 때 자신을 가리키고 있는 프로퍼티의 객체를 바인딩한다.

좀 더 극적인 예시로 가져와보자

globalThis.firstName = '전역객체입니다요';

function introduce() {
  console.log(`hi i am ${this.firstName}`);
}

const tom = {
  firstName: 'tom',
};

const jerry = {
  firstName: 'jerry',
};

tom.IntroduceMyself = introduce;
jerry.IntroduceMyself = introduce;

introduce(); // hi i am 전역객체입니다요
tom.IntroduceMyself(); // hi i am tom
jerry.IntroduceMyself(); // hi i am tom

다음처럼 전역 객체에 firstName 이란 프로퍼티가 존재하고 일반 함수로서 introduce 라는 함수를 선언했다.

해당 함수를 일반 함수 호출 방식으로 사용 했을 때의 this

객체의 프로퍼티가 함수를 가리키고 했을 때의 this 바인딩의 차이를 살펴보면 좀 더 이해가 쉽다.

정리

메소드로서 호출 될 때는 자신을 가리키고 있는 프로퍼티를 소유하고 있는 객체를 가리킨다.

그러면 더 극단적으로 가보자

function Person(name) {
  this.name = name;
}

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

// tom 생성
const tom = new Person('tom');
console.log(tom.getName()); // tom

Person.prototype.name = 'jerry';
console.log(tom.getName()); // tom
console.log(Person.prototype.getName()); // jerry

tom 에게 Person.prototype 을 상속 시켜주었고 this.namereturn 하게 하였다.

그럴 경우 tom 객체에서 getName() 을 실행시키면 tom 의 name 이 나온다.

호출한 주체가 tom 인스턴스이기 때문이다.

tom 의 getName 프로퍼티가 Person.prototype.getName 을 가리키고 있기 때문이다.

그 다음 Person.prototype.name 에 다른 이름을 추가해주고 시행해도

여전히 tom.getName()tom 을 부른다.

하지만 Person.prototype.getName() 같은 경우엔 다른 이름을 부른다.

메소드에서의 this 는 호출한 주체를 기준으로 동적으로 결정된다.

생성자 함수

생성자 함수 또한 생성되는 인스턴스와 함께 바인딩 된다.

설명은 위와 같다.

Fucntion.prototype.apply / call / bind 에 의한 간첩 호출

메소드설명특징예시
apply(thisArg, [argsArray])함수를 호출하면서 this를 특정 객체로 지정하고, 인수는 배열 형태로 전달합니다.- 함수 호출 시 인수를 배열로 전달 가능
- 주로 가변 인수를 사용하는 함수에 유용
js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } introduce.apply(person, ['Hello']);
call(thisArg, arg1, arg2, ...)함수를 호출하면서 this를 특정 객체로 지정하고, 인수를 일반적인 형태로 전달합니다.- 함수 호출 시 인수를 개별적인 값으로 전달
- 주로 가변 인수를 사용하는 함수에 유용
js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } introduce.call(person, 'Hello');
bind(thisArg, ...args)함수에 특정 객체를 바인딩하고, 새로운 함수를 반환합니다.- 함수를 호출하는 것이 아니라 새로운 함수를 반환
- 반환된 함수는 나중에 호출 가능
js const person = { name: 'Tom' }; function introduce(greeting) { console.log(`${greeting} ${this.name}`); } const boundIntroduce = introduce.bind(person); boundIntroduce('Hello');

간접 호출에 관한 내용은 나중에 자세히 보겠지만 결국에는 인수로 전달받은 객체와 바인딩 한다.

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글