[Study] JS DeepDive : 22. this

sjoleee·2022년 11월 19일
0
post-thumbnail
post-custom-banner

모던 자바스크립트 Deep Dive 스터디

22. this

22.1 this 키워드

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

객체에서 메서드는 자신이 속한 객체의 상태, 즉 프로퍼티를 참조하고 변경하는 역할을 한다.
이때, 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자가 필요할 것이다.

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

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

객체 리터럴 방식으로 생성한 객체의 경우, 메서드 내부에서 메서드 사진이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있다.

getDiameter 메서드에서 circle를 참조하는 표현식이 평가되는 것은 getDiameter 메서드가 호출되고 함수 몸체가 실행되는 시점이다.
위 객체 리터럴은 circle 변수에 할당되기 직전에 평가된다. 따라서 getDiameter 메서드가 호출되는 시점에는 이미 객체 리터럴의 평가가 완료되어 circle에 할당되어 있다. 그러므로 getDiameter 메서드는 circle 식별자를 참조할 수 있다.

하지만, 이렇게 재귀적 참조 방식은 일반적이지 않으며 바람직하지 않다.
또한, 객체 리터럴이 아닌 생성자 함수 방식이라면 어떨까?

function Circle(radius) {
  ???.radius = radius;
};

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

const circle = new Circle(5);

생성자 함수 내부에서 프로퍼티 또는 메서드를 추가하기 위해서 자신이 생성할 인스턴스를 참조할 수 있어야 한다.
하지만 인스턴스를 만들기 위해서는 생성자 함수가 먼저 존재할 필요가 있다.
따라서, 생성자 함수가 프로퍼티, 메서드를 만들기 위해서는 존재하기 이전인 인스턴스를 미리 참조할 수 있는 특수한 식별자가 필요하다.

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다.
단, this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

// 전역에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window

// 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
function square(number) {
  console.log(this); // window
  return number*number;
}

//메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
const person = {
  name: "sangjo",
  getName() {
    console.log(this); // {name: "sangjo", getName: ƒ}
    return this.name;
  }
};
console.log(person.getName()); // sangjo

// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
function Person(name) {
  this.name = name;
  console.log(this); // Person {name: "sangjo"}
};
const me = new Person("sangjo")

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

💡 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.

함수를 호출하는 방식은 다양하다.

  • 일반 함수 호출
  • 메서드 호출
  • 생성자 함수 호출
  • Function.prototype.apply/call/bind 메서드에 의한 간접 호출
const foo = function() {
  console.dir(this);
};

// 1. 일반 함수 호출
// 일반 함수 내부의 this는 전역 객체를 가리킨다.
foo(); // window

// 2. 메서드 호출
// 메서드를 호출한 객체를 가리킨다.
const obj = { foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
// 생성자 함수가 생성한 인스턴스를 가리킨다.
new foo(); // foo {}

// 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// 메서드의 인수에 따라 결정된다.
const bar = { name: "bar" };

foo.apply(bar); // bar
foo.call(bar); // bar
foo.bind(bar)(); // bar

22.2.1 일반 함수 호출

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

전역 함수는 물론이고 중첩 함수를 일반 함수로 호출하면 함수 내부의 this에는 전역 객체가 바인딩된다.
메서드 내에서 정의한 중첩 함수 역시 일반 함수라면 this에는 전역 객체가 바인딩된다.

하지만 this는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서 this는 의미가 없다.
그리고 strict mode가 적용된 일반 함수 내부의 this에는 undefined가 바인딩된다.

콜백 함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩 된다.
이 경우 곤란한 상황이 발생할 수 있다.

var value = 1;

const obj = {
  value: 100,
  foo() {
    console.log("foo's this", this); // {value: 100, foo: ƒ}
    setTimeout(function() {
      console.log("callback's this", this); // window
      console.log("callback's this.value", this.value); // 1
    }, 1000)
  }
}

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

이 경우 사용할 수 있는 방법은 다음과 같다.

var value = 1;

const obj = {
  value: 100,
  foo() {
    // 메서드의 this를 that에 할당한다.
    const that = this;
    
    setTimeout(function() {
      // this대신 that을 참조한다.
      console.log(that.value); // 100
    }, 1000)
  }
}
var value = 1;

const obj = {
  value: 100,
  foo() {
    // 콜백 함수에 명시적으로 this를 바인딩한다.
    setTimeout(function() {
      console.log(this.value); // 100
    }.bind(this), 1000)
  }
}
var value = 1;

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

22.2.2 메서드 호출

💡 메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.

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

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

const me = new Person("sangjo");
console.log(me.getName()); // 1. sangjo

Person.prototype.name = "LEE"
console.log(Person.prototype.getName()); // 2. LEE

1의 경우, getName 메서드를 호출한 객체는 me이므로 thisme를 가리킨다.
2의 경우, getName 메서드를 호출한 객체는 Person.prototype이므로 thisPerson.prototype을 가리킨다.

22.2.3 생성자 함수 호출

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

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

💡 Function.prototype.apply/call/bind 메서드는 this로 사용할 객체를 전달받는다.

const foo = function() {
  console.dir(this);
};

const bar = { name: "bar" };

foo(); // window
foo.apply(bar); // bar
foo.call(bar); // bar
foo.bind(bar)(); // bar
profile
상조의 개발일지
post-custom-banner

0개의 댓글