모던 자바스크립트 Deep Dive : 22장 this

EdLee·2022년 11월 10일

javascript

목록 보기
12/37

22장 this

1. this 키워드


this의 존재 이유?

  • 객체는 프로퍼티와 메서드를 하나의 논리적인 단위로 묶은 자료구조
  • 메서드는 프로퍼티를 참조하고 변경할 수 있어야 한다
  • 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면, 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다
const circle = new Circle(5); // 이렇게 인스턴스를 만드려면, 생성자 함수가 있어야 한다.

// 그래서 만든 생성자 함수
function Circle(radius) {
  // 그런데 생성자 함수 자신이 생성할 인스턴스는 알 수 없다.
  ????.radius = radius;
}
  • 생성자가 미래에 생성할 인스턴스를 가리킬 키워드가 필요하다.
  • 그래서 필요한게 this

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

// 근데... 이렇게는 안되나?
function Rect(self, diagonal) {
  self.diagonal = diagonal;
}

var rect = {}; // 이렇게 미리 생성되어 있는 인스턴스라면 되긴 되네..
new Rect(rect, 5);
console.log(rect.diagonal); // 5

2. 함수 호출 방식과 this 바인딩


  • 함수 생성이 아니다😲
  • this는 함수 호출 방식에 따라 동적으로 결정된다

다양한 함수 호출 방식
1. 일반 함수 호출
2. 메서드 호출
3. 생성자 함수 호출
4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출

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

// 1. 함수 호출
foo(); // window

// 2. 메서드 호출
var obj = { foo: foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
var instance = new foo(); // instance

// 4. apply/call/bind 호출
var bar = { name: 'bar' };
foo.call(bar);   // bar
foo.apply(bar);  // bar
foo.bind(bar)(); // bar

일반 함수 호출

  • 일반 함수 호출 시 this는 window를 가리킨다
  • 그러나 메서드가 호출하는 중첩함수, 콜백 함수의 this와 메서드가 서로 바라보는 this가 다르다면?? 굉장히 불편하겠지?🤮
var value = 1;

const obj1 = function() {
  console.log("obj1", this.value);
}

const obj2 = {
  value: 100,
  foo() {
    console.log("obj2", this.value);
    obj1();
  }
}

obj2.foo(); // obj2 안에서 obj1을 호출해봐야 그 this는 window다.
  • 그럴 땐 다음 방법들을 사용

this를 스코프로 전달

var value = 1;

const obj = {
  value: 100,
  foo() {
    const thisObj = this;

    setTimeout(function() {
      console.log(thisObj.value); // 100
    }, 50); // 50ms만큼 대기
  }
};

obj.foo();

Function.prototype.apply/call/bind 사용

var value = 1;

const obj = {
  value: 100,
  foo() {
    setTimeout(function() {
      console.log(this.value); // 100
    }.bind(this), 50); // this를 명시적으로 바인딩
  }
};

obj.foo();

화살표 함수 사용

var value = 1;

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

obj.foo();

메서드 호출

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

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

생성자 함수 호출

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

const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getDiameter); // 10
console.log(circle2.getDiameter); // 20

// new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않는다. 즉, 일반적인 함수의 호출이다.
const circle3 = Circle(15);

// 일반 함수로 호출된 Circle에는 반환문이 없으므로 암묵적으로 undefined를 반환한다.
console.log(circle3); // undefined

// 일반 함수로 호출된 Circle 내부의 this는 전역 객체를 가리킨다.
console.log(radius); // 15

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

apply / call

  • 주어진 this와 인자를 사용해 함수를 호출한다
// 주어진 this 바인딩과 인수 리스트 배열을 사용하여 함수를 호출한다
Function.prototype.apply(thisArg[, argsArray])

// 주어진 this 바인딩과 ,로 구분도니 인수 리스트를 사용하여 함수를 호출한다
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

// 이게 뭔 🐶소리야
function getThisBinding() {
  console.log(arguments); // 오 arguments 이렇게 쓸 수가 있네
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
// apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다.
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// {a: 1}

// call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달한다.
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// {a: 1}

bind

  • 주어진 this를 바인딩한 함수를 리턴한다
function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체되어 다시 태어난다
console.log(getThisBinding.bind(thisArg)); // getThisBinding

// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}

3. 정리


함수 호출 방식this바인딩
일반 함수 호출전역 객체
메서드 호출메서드를 호출한 객체
생성자 함수 호출생성자 함수가 생성할 인스턴스
Function.prototype.apply/call/bindapply/call/bind에 첫번째 인자로 전달한 객체

0개의 댓글