[JavaScript Deep Dive] 22. this

소정·2024년 2월 7일
0
post-thumbnail

이제 슬슬 모던 자스의 활약상이 나올 때다 !
앞으로 this, 실행 컨텍스트, 클로저, 클래스가 쭉쭉 나온다 .. (후)
그래도 this에 대해선 많은 지식을 쌓았으니 ! 정리하는 느낌으로 가보자고 !


1. this 키워드

메서드는 프로퍼티를 참조하고 변경하는 역할 수행을 위해, 자신이 속한 객체를 가리키는 식별자를 참조해야만 한다.

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

const circle(){
  radius: 5,
  getDiameter(){
  	return 2 * circle.radius; // 재귀적 사용
  }
}

📌 재귀적 사용이 가능한 이유

객체 리터럴은 circle 변수에 할당하기 이전에 평가된다. (객체가 먼저 평가되고 할당되니까)
따라서 getDiameter 메서드가 호출되는 시점에는 이미, 진즉에 객체 리터럴 평가 완료, 객체 생성, circle 식별자에 할당까지 모두 완료된 이후이다. getDiameter에서 circle 식별자를 참조할 수 있는 이유가 바로 이것이다.

하지만 자신이 속한 객체를 재귀적으로 참조하는 방식은 권장되지 않는다. (비추)
객체 리터럴이 아닌 생성자 함수 방식으로 인스턴스를 생성하는 경우를 살펴보자.

📌 생성자 함수에선 재귀적 사용이 불가능하다해 ~

생성자 함수로 만들어진 인스턴스(객체)를 재귀적으로 참조하는 방식은 불가능하다.
인스턴스를 만들기 이전에 생성자 함수를 정의하는 과정이 우선되어야 하기 때문이다.
인스턴스가 만들어지지 않은, 생성자 함수가 생성되는 시점에서 인스턴스를 가리키는 식별자를 알 방법은 없다.

📌 자바스크립트 : this 줄게

따라서 자바스크립트는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자, this를 제공한다.

  • this는 자신이 속한 객체나 생성할 인스턴스를 가리키는 자기 참조 변수
  • this는 자신이 속한 객체나 생성할 인스턴스의 프로퍼티/메서드 참조 가능
  • this는 암묵적으로 생성되고 어디서든 참조 가능

함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다.
arguments 객체처럼 this도 동일하게 함수 내부에서 지역 변수로 사용이 가능하다.
다만 this가 가리키는 값(this 바인딩)은 함수 호출 방식에 의해 동적으로 결정된다. (이것이 아주 키포인트!)

this 바인딩 식별자와 값을 연결하는 과정 (묶다)

📌 함수 호출 방식에 의한 동적 결정의 예시

객체 리터럴에서 메서드 내부의 this메서드를 호출한 circle을 가리킨다.

const circle = {
  radius: 5,
  getDiameter(){
    return 2 * this.radius; // this : 메서드를 호출한 객체 = circle 
  }
}
console.log(circle.getDiameter());

생성자 함수 내부의 this생성자 함수가 생성할 인스턴스를 가리킨다.

function Circle() {
  this.radius: radius;
}
Circle.prototype.getDiameter = function(){
  return 2 * this.radius; // this : 메서드를 호출한 객체 = circle 
}

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

📌 strict mode에서의 this

위의 예제를 통해, 자바스크립트의 this는 함수가 호출되는 방식에 따라 바인딩될 값이 동적으로 결정되는 것을 확인했다. 이때 window가 바인딩되는 경우는 strict mode에서 undefined가 바인딩 된다.

  • 전역, 일반함수 내에서 this 사용
    • 전역 객체(window) 바인딩
    • strict mode : undefined 바인딩
  • 메서드 내부에서 this 사용 : 메서드 호출한 객체 바인딩
  • 생성자 함수 내부에서 this 사용 : 생성할 인스턴스 바인딩

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

(반복하는 만큼 중요한) this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
이때 함수가 정의될 때 상위 스코프를 결정하는 렉시컬 스코프와 this가 바인딩되는 시점은 다르다.
this는 함수가 정의될 때가 아닌 호출될 때 결정되기 때문 !

함수를 호출하는데는 총 4가지의 방법이 있다. 하나씩 살펴보자!

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

1. 일반 함수 호출

  • this에는 전역 객체(window)가 바인딩 됨

전역함수도, 중첩함수도, 아무튼 일반 함수로 호출하면 함수 내부의 this에는 전역 객체가 바인딩된다.
바인딩된 전역 객체는 객체의 프로퍼티/메서드를 참조하기 위한 자기 참조 변수로서 의미가 없다.
strict mode가 적용된 일반 함수에서는 undefined가 바인딩 된다.

메서드 내에서 정의된 중첩 함수여도, 일반 함수로 호출되면 역시 this에는 전역 객체가 바인딩된다.
콜백 함수도 일반 함수로 호출되면 역시 this에는 전역 객체가 바인딩된다.
즉, 어떤 함수라도 일반 함수로 호출되면 ! this에는 전역 객체가 바인딩된다.

📌 일반 함수로 정의된 함수의 this 활용법

메서드 내부나 중첩 함수, 콜백 함수에서는 자기 자신을 참조해야 할 때가 자주 찾아온다.
이때 this 바인딩을 메서드의 this 바인딩과 일치하기 위해선 this를 변수로 할당하여 사용할 수 있다.

var value = 1;

const obj = {
  value: 100,
  foo(){
    const that = this; // obj로 바인딩된 `this`를 변수로 할당
    
    setTimeout(function(){
      console.log(that.value); // 100
    }, 100}
  }
}
obj.foo()

위의 변수를 할당하는 방법 외에도, Function.prototype.apply/call/bind 메서드를 활용할 수 있다.
하지만 요건 조금 이따가 ! 살펴보자 ~ (밀당쓰)
추가로 화살표 함수를 사용하는 방법도 존재한다. 하지만 이것은 26장에서 살펴볼 예정이다 ! (밀기만 하는중)

2. 메서드 호출

  • this에는 메서드를 호출한 객체가 바인딩

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

메서드는 다른 객체의 프로퍼티에 할당하여, 다른 객체의 메서드가 될 수도 있고
메서드는 일반 변수에 할당하여, 일반 함수로 호출될 수도 있다.

const person = {
  name: 'sozzang',
  getName(){ return this.name }
}
const anotherPerson(){ name: 'kim' }

// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName

console.log(person.getName) // sozzang
console.log(anotherPerson.getName) // kim

const getName = person.getName;
console.log(getName()) // '' : 일반 함수로 호출되어 window.name 값 반환됨

결국 메서드 내부의 this는 프로퍼티로 메서드를 가리키고 있는 객체와 관계 없이! 메서드를 호출한 객체에 바인딩된다.

📌 프로토타입 메서드 내부에서 this 바인딩 값은?

프로토타입 메서드 내부에서 사용된 this도 일반 메서드와 동일하다. 해당 메서드를 호출한 객체에 바인딩 된다.
Person.prototype도 객체이므로, 직접 메서드 호출이 가능하다.

function Person(name){ this.name = name; }
Person.prototype.name = 'kim'
Person.prototype.getName = function(){
  return this.name;
}
const me = new Person('sozzang')

console.log(me.getName) // sozzang
console.log(Person.prototype.getName()) // kim

3. 생성자 함수 호출

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

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

apply, call, bind 메서드는 Function.prototype의 메서드다.
이 메서드는 함수의 프로토타입에 바인딩되어 있기 때문에 모든 함수가 상속받아 사용이 가능하다.

  • apply : 인수로 전달한 배열이 this에 바인딩되어 함수 실행
  • call : 인수로 전달한 인수 리스트가 this에 바인딩되어 함수 실행
  • bind : 인수로 전달한 인수 리스트가 this에 바인딩되어 함수 본문 전달

📌 apply, call 메서드

  • this로 사용할 객체/인수 리스트를 인수로 전달받아 함수를 호출
  • Function.prototype.apply(thisArg, [arg1, arg2]) : 배열을 전달 받음
  • Function.prototype.call(thisArg, arg1, arg2, ...) : 인수 리스트를 전달 받음

applycall 메서드의 본질적인 기능은 함수를 호출하는 것이다.
이때 중요한 차이점은, this로 사용될 애를 첫번째 인수로 전달한다.
즉 첫번째 인수로 전달한 값이 실행된 함수에서 this로 사용된다.

📌 applycall 사용 예시 : arguments 유사 배열 객체를 배열로

대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하기 위해서이다.

function convertArgsToArr(){
  console.log(arguments); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  
  const arr = Array.prototype.slice.call(arguments)
  console.log(arr) 
  
  return arr;
}
convertArgsToArr(1, 2, 3) // [1, 2, 3]

// 이런것도 가능하다 ! (문자열에 배열 전용 메서드 사용하기)
'1,2,3'.slice('') // '1,2,3'
Array.prototype.slice.call('123') // (3) ['1', '2', '3']

위의 예시를 보면, 배열 메서드인 slice를 사용하지 못하는 유사 배열 객체 argumentscall 메서드로 간접 호출하여 slice 메서드를 사용했다. 물론 apply도 가능하다.

📌 bind 메서드

bindapply, call과 다르게 함수를 호출하지 않는다. 첫번째 인수로 교체된 함수 본문을 반환한다.
따라서 bind 메서드는, 1️⃣ 메서드의 this와 2️⃣ 메서드 내부의 중첩함수/콜백함수의 this가 불일치하는 문제를 해결하는데 유용하다.

const person = {
  name: 'sozzang',
  foo(callback){
    console.log(this); // this = person : 메서드 축약형은 해당 객체를 참조함
  	setTimeout(callback.bind(this), 100)
  }
}

person.foo(function(){ console.log(this.name) }) // sozzang

콜백 함수 내부의 this를 외부 함수 foo의 this와 일치시켜야 한다.
위와 같이 foo 함수의 this를 전달하여 변경된 callback 함수 본문을 호출하면 된다.


3. 요약 가보자고

내가 보려고 기록하는 정리 !

this는 함수가 호출되는 대상이 바인딩됨
일반 함수는 호출하는 대상이 전역 객체임 왜냐?
사실 모든 일반 함수는 window.함수()인데 전역 객체가 생략된 것이기 때문 !
그래서 일반 함수로 작성된 함수, 중첩함수, 콜백함수 모~두 this는 전역 객체, 즉 window가 바인딩됨

메서드로 사용되었다면 this는 무조건 무언가의 객체를 바인딩
왜냐면 메서드를 사용하려면 무조건 객체.메서드() 해야하니까 ~

근데 이때 화살표 함수를 사용하면 화살표 함수는 this가 없음
그래서 this가 뭔지 몰라서 상위 컨텍스트한테 물어봄 "this가 모야?"
상위 컨텍스트는 전역/함수로만 이루어짐 (블록문/객체는 컨텍스트 환경 아님)
그래서 상위의 함수가 있다면 타고 타고 물어보다가 결국 아무도 안 말해주면 전역 객체 바인딩

this를 활용하는 방법이 3가지 더 있음 바로 apply, call, bind
우선 call이랑 apply는 함수를 대신 호출하는거 ! 간접 호출 !
call, apply의 첫번째 인수로 전달한 값을 this로 바인딩해서 함수를 실행함
원하는 애를 this로 사용하기 딱 좋음

bind는 첫번째 인수로 전달된 값이 this로 바인딩되긴 하는데 ..
함수가 호출되진 않음 = 실행되진 않음. 함수 본문이 전달됨 (중요)
그래서 setTimeout에서 사용하기 좋음 굿


오랜만에 2일 연속으로 모던 자스 딥다이브를 성공했다 !
아무래도 수업 시간에 this에 대해 많이 배우고 사용해봐서 빠르게 정리할 수 있었던 것 같다.
(하지만 화살표 함수가 상위 컨텍스트의 this를 받아온다는 퀴즈는 틀렸죠?)

낼부터 휴강 + 설 연휴다 ... 조금만 놀고 열심히 공부해야쥐 ~
설 연휴때도 공부하는 멋진 나.. 가보자고 !

profile
" 퍼블리셔에서 프론트엔드로 Level up 중 =3 "

0개의 댓글