this

WooSeong·2021년 4월 25일
0

학습 노트

목록 보기
15/22

'이것'을 뜻하는 this

this는 '이것'을 뜻하는 JS 키워드이다. 이건 무슨 뜬구름 잡는 소리야? MDN에 정의된 this는 다음과 같다.

대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정됩니다.

그렇다 this는 함수를 호출할때 값이 결정된다. 이걸 좀 더 알아들을수 있게 바꾼다면... this는 누가 나를 불렀니? 라 할 수 있을 것이다.

메서드가 어디서 정의되었는지에 상관없이 this는 ‘점 앞의’ 객체가 무엇인가에 따라 ‘자유롭게’ 결정됩니다.

함수 그 자체보다 실행되는 방법이 중요!

함수가 실행되는 방법은 다음과 같다.

  • Function 호출

    func();
  • Method 호출

    String.prototype.toString();
  • new 키워드를 이용한 호출(생성자 호출)

    const child = new Parents();

이때 this는 호출되는 함수안에 존재하고, 위와 같은 방법으로 호출될때 그 형태가 결정되는 것이다.

function 호출

단순한 함수 호출에서 this는 얼핏 생각해 보면 함수 그 자체를 의미할 것 같지만, 비 엄격 모드에선 전역 객체를 참조하게 되며(window or global), 엄격 모드에선 정의되지 않은 값으로 결정 된다(undefined). function 호출 방법을 통해 함수를 실행 할때는 this를 사용할 의미가 없다. 만일 전역 this를 호출하고 싶은 것이라면...

globalThis 프로퍼티는 코드가 실행중인 현재 컨텍스트와 관계 없이 항상 전역 객체를 얻을 수 있다.

Method 호출

함수를 어떤 객체의 메서드 호출하면 this의 값은 그 객체를 사용한다.(this는 그 객체에 바인딩 된다.) 코드를 통해 알아보자.

//객체 리터럴로 객체를 정의 객체는 프로퍼티로 age와 name을 가지고 있고 메서드로 getAge를 가지고 있다.
let obj = {
	age: 30,
	name: 'joe',
	getAge: function() {
		console.log(`${this.name} is ${this.age} years old`)
	}
}

obj.getAge(); // joe is 30 years old

obj.getAge();는 obj 객체의 getAge 메서드를 호출한다. getAge 메서드가 실행되는 순간 this는 obj로 바인딩 되며, this.name은 obj.name으로 this.age는 obj.age로 치환된다.

생성자 호출(new 키워드를 이용한 호출)

함수를 new 키워드와 함께 생성자로 사용하면 this는 새로 생긴 객체에 묶인다.(this는 새로 생긴 인스턴스에 바인딩 된다.) 코드를 통해 알아보자

class Person {
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}

	getAge() {
		return `${this.name} is ${this.age} years old`
	}
}

let joe = new Person('joe', 30);

joe.getAge(); // joe is 30 years old
joe.name // 'joe'
joe.age // 30

let joe = new Person('joe', 30)은 전달인자를 각각 매개변수 name과 age로 전달하여 new 키워드와 함께 생성자로 사용한 함수다. (class문법 도입전에는 동일한 결과를 얻기 위해 function으로 정의했었다. 다시 말해 class는 function이다.) 생성자를 통해 Person class의 새로운 인스턴스인 joe가 만들어졌고 joe의 name은 'joe', joe의 age는 30으로 할당 되었다. 여기서 생성자의 this와 메서드의 this 모두 joe로 바인딩 된다. 따라서 this.name은 joe.name으로 this.age는 joe.age로 치환된다.

getAge 메서드의 this는 getAge를 실행할때 joe로 바인딩 된다.

call, apply, bind

call과 apply

MDN에 정의된 call은 다음과 같다.

  • Function.prototype.call() : call은 Function의 메서드이다.
    • call도 메서드이기 때문에 call로 this를 호출하면 메서드의 호출 처럼 작동할까?
    • Function.prototype : Function 원형 객체의 메서드이기 때문에
    • 원형 객체를 상속한 대상 함수의 메서드로 작동한다.
  • call() 메소드는 주어진 this 값 및 각각 전달된 인수와 함께 함수를 호출 합니다.
    • call은 함수를 호출한다!
  • call()은 이미 할당되어있는 다른 객체의 함수/메소드를 호출하는 해당 객체에 재할당할때 사용된다.
    • call의 첫번째 인자에 'this'를 넣으면 '현재 객체'를 참조한다.

MDN의 예제를 따라가며 좀 더 상세히 이해해 보자.

1   function Product(name, price) {
2     this.name = name;
3     this.price = price;
4   }
5
6   function Food(name, price) {
7     Product.call(this, name, price);
8     this.category = 'food';
9    }
10
11  console.log(new Food('cheese', 5).name);
12  // expected output: "cheese"

this는 함수가 실행되는 순간에 값이 결정된다. call을 사용한 예제에서 this가 어떤 값을 갖게 될까?

  • 예제에서 함수가 실행되는 순간은 순서대로 다음과 같다.
    • 11번째줄 new 키워드와 함께 Food 함수가 실행된다.
    • 6번째줄 부터 시작되는 Food 함수를 살펴보면 내부에 콜백함수(Product)가 call 메소드에 의해 호출된다.
  • 11번째줄 new 키워드와 함께 실행되는 Food 함수는 생성자 호출에 해당된다.
    • 따라서 해당 함수의 this는 새로운 인스턴스에 바인딩 된다.
    • 8번째줄 this는 익명 인스턴스에 바인딩 된다. Food의 새로운 인스턴스인 'cheese'라고 임시로 부르자
  • 7번째줄 call 메서드 안의 this는 메서드 호출에 해당된다.
    • call 메서드는 첫번째 인자에 this를 바인딩 한다.
    • 첫번째 인자가 this 이므로 해당 this의 값을 생각해 봐야 한다.
    • 해당 this는 현재 객체를 참조한다. 현재 객체는 익명 인스턴스(그러나 우리가 임시로 cheese라고 부르는 인스턴스)를 의미한다. 해당 인스턴스를 this에 바인딩 하여 Product에 전달 및 호출 한다.
  • Product의 this는 cheese 인스턴스를 의미한다. 즉 cheese.name과 cheese.price에 cheese 와 5가 전달된다.
  • 최종적으로 cheese 인스턴스는 name과 price, category를 키로 가지고 있고 각각 'cheese', 5, 'food'를 값으로 가지고 있는 객체이다.

정리해 보면 call은 첫번째 인자에 this를 바인딩하는 역할을 하고 두번째 인자부터 그대로 대상 함수로 전달하는 역할을 한다.

apply는 call과 동일한 역할을 하는 메서드이다. call과의 차이는 두번째 인자에 무엇을 받는가이다. apply는 두번째 인자에 인수 배열 하나를 받는다.

func.apply(this, [5, 10]);

bind

얼핏 생각해 보면 bind 메서드 또한 call과 동일한 역할을 하는것 같다. 하지만 큰 차이점이 있는데 바로 bind 메서드는 새로운 함수를 생성한다는 점이다.

bind() 함수는 새로운 바인딩한 함수를 만듭니다. 바인딩한 함수는 원본 함수 객체를 감싸는 함수로, ECMAScript 2015에서 말하는 특이 함수 객체exotic function object입니다. 바인딩한 함수를 호출하면 일반적으로 래핑된 함수가 호출 됩니다.

MDN 예제로 더 자세히 이해해 보자

1   this.x = 9;
2   var module = {
3     x: 81,
4     getX: function() { return this.x; }
5   };
6
7   module.getX();
8
9   var retrieveX = module.getX;
10  retrieveX();
11
12  var boundGetX = retrieveX.bind(module);
13  boundGetX();
  • this.x 와 module 객체는 전역에 선언되어 있다.
    • this.x의 this는 전역에 바인딩 된다. 따라서 this 는 window(or glabal)을 의미한다.
  • 7번째줄은 메서드 호출이다.
    • 따라서 getX의 this는 그 this가 포함된 객체를 참조한다. 여기서는 module 객체이다. 따라서 7번째줄 실행의 결과는 81이다.
  • 그런데 10번째 줄을 봐보자 module.getX를 retrieveX에 할당하고 retrieveX를 함수 호출 하였다.
    • 결과는 9이다. 7번째 줄과 어떤 차이가 있기에 이런걸까?
    • 특정 메소드를 추출해서 새로운 변수에 담으면 원본 객체가 손실된다.
    • retirieveX는 분명 module의 getX 메서드를 담고 있지만, 해당 메서드가 정의된 객체는 자동적으로 할당되지 않는다.
    • 따라서 10번째줄의 실행은 메서드의 호출이 아니라 함수의 호출에 해당한다.
    • 전역에서 함수를 호출할 경우 this는 전역객체를 참조한다.
  • 위와 같은 문제를 해결하기 위해선 원본 객체를 바인딩 하는 함수를 생성하는 특별한 조치가 필요하다.
    • 그리고 여기서 bind가 등장한다.
    • 12번째줄은 retrieveX에 module 객체를 바인딩 한다.
    • boundGetX는 module 객체가 바인딩 되어 있기 때문에 13번째줄의 함수 호출은 this를 특정한 객체만 의미하도록 고정시킨다.
    • 따라서 this는 전역객체를 참조하지 않고 module을 참조하게 된다.
    • 결과는 81이다.

bind 메서드와 setTimeout

setTimeout 메서드(전역 객체의 메서드)에서 this는 기본적으로 window(or global)에 바인딩 된다. 따라서 클래스 인스턴를 참조하는 this를 필요로 하는 경우에는 bind를 사용하면 setTimeout의 this를 특정 객체를 참조할 수 있도록 고정시킬수 있다.

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};
  • bloom 메서드의 this는 메서드 호출의 this에 해당한다. 따라서 setTimeout(this...)의 this는 LateBloomer 클래스(객체)를 참조한다.
  • this.declare.bind(this) : 에서 this는 LateBloomer 클래스(객체)로 바인딩 되었다. bind 메서드 실행의 첫번째 인자 this 또한 LateBloomer 클래스(객체)로 바인딩 되며 bind메서드는 이를 그대로 declare 메서드로 전달한다.
  • declare 메서드의 this는 LateBloomer로 바인딩 된다!

화살표 함수와 this

화살표 함수엔 this가 없습니다. 화살표 함수 본문에서 this에 접근하면, 외부에서 값을 가져옵니다.

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach(
      student => alert(this.title + ': ' + student)
    );
  }
};

group.showList();
  • forEach 메서드의 콜백함수로 익명 함수가 호출되었고 익명 함수는 화살표 함수로 정의되어 있다.
  • 화살표 함수는 this가 없기 때문에 화살표 함수의 본문의 this는 외부 컨텍스트에서 값을 가져온다.
  • 외부에 this는 메서드의 this다.
  • group의 메서드 showList가 호출될때 this는 메서드 호출이기 때문에 group으로 바인딩 된다.
  • 화살표 함수 본문의 this는 showList 메서드의 this를 가져온다.
  • 따라서 화살표 함수 본문의 this는 group객체로 바인딩 된다.
profile
성장하는 개발자를 꿈꿉니다

0개의 댓글

관련 채용 정보