201206 TIL Javascript this, bind, apply, call

ToastEggsToast·2020년 12월 6일
0

TIL

목록 보기
5/15

여기에 작성된 모든 글은 얕고 얕은 지식을 바탕으로 작성되어있기 때문에..
오류와 실수 투성이일 수 있습니다.
고쳐야하거나 더 공부해야 하는 부분에 대해서 둥글게 지적해주신다면
더 공부하고 고쳐서 작성하겠습니다. 감사합니다 :D

Javascript This

자바스크립트의 this!!
왜인지는 모르겠지만 국비에서 퍼블리싱 과정 수업을 들을 때 부터 이상하게 this는 너무나 무서운 존재였다. 선생님이.. 어렵다고 했기 때문이었던 것 같다.
하지만 어렵지 않습니다. (물론.. 물론 저는 많이 헤맸지만..)

그래서 this가 뭔데?

The JavaScript this keyword refers to the object it belongs to.

자바스크립트에서의 this는 클래스 인스턴스의 레퍼런스 변수입니다.
말이 어렵습니다.. 간단하게 이야기하면 이 this가 찍히고있는 위치가 어느 환경(전역 객체.object)에서 실행되고 있는가를 의미합니다. 이 환경 obj를 두고 context 객체라고도 표현하기도 하는 것 같습니다.

console.log(this)

크롬 개발자도구 창의 콘솔이나 RunJS를 열어봅시다.
그러고 일단 console.log(this)를 찍어봅시다.

ㄴㅇㄱ 엄청나게 기네요! 하지만 우리가 볼 것은 맨 윗줄 하나입니다.
Window[global] 이라고 적혀있네요. 그렇습니다. this는 가장 기본적으로 전역 객체 window를 의미하게 됩니다.
근데.. 그러면 this가 어려울 필요가 없었겠죠 :)

제가 this를 공부하기 위해 작성했던 객체입니다.

const obj = {
  1:this,
  2:function(){
    console.log(this)
  },
  3: ()=>{
    console.log(this)
  },
  4: function(){
    function t () {
      console.log(this)
    }
    t()
  },
  5: function(){
    const t = () => {
      console.log(this)
    }
    t()
  },
  6: ()=>{
    function t() {
      console.log(this)
    }
    t()
  },
  7: ()=>{
    const t = () => {
      console.log(this)
    }
    t()
  }
};

위 코드에서 1번부터 7번까지 실행을 시켰을 경우, this는 다음과 같은 결과를 돌려줍니다.

obj[1] : this === window
obj[2](): this === window.obj
obj[3](): this === window
obj[4](): this===window
obj[5](): this === window.obj
obj[6](): this === window
obj[7](): this === window

헐. 대박 어렵죠. 완전 모르겠죠. 하지만 걱정 마시라.
제가 헤매고 헤맸던 모든 과정들을 같이 알려드릴게요.

실행 환경에 따라 달라지는 this

위의 예제에서 보면 전부 console.log(this)를 실행하고 있지만, 전부 다른 값을 보여주고있습니다. 왜일까요? 달라진 실행 환경 때문에 그렇습니다.
그래서 this가 어렵다고들 합니다. 아니! 같은 this인데.. 객체 안에서마저 왜 window를 가리키지?? 똑같은 함수 내부에서 arrow function을 사용했을 뿐인데 값이 왜 달라지지!?
wow. 엄청난 혼란의 카오스. 이걸 살펴보기 위해선 가장 먼저 실행된 함수가 method인지, function인지, arrow function으로 정의되었는지 살펴봐야합니다.

function vs method

method는 Array.forEach 등과 같이 어떤 객체에서 .으로 객체를 먼저 불러와야 사용이 가능한 지정되어있는 함수를 의미합니다.
this를 설명하다 갑자기 왜 이걸 설명하냐.. 이게 또 중요하거든요..
여러가지 블로그도 찾아보고, 여러가지 강의도 들었는데 저는 이 중요한 부분을 놓치고 있었습니다.
method로 선언되어진 함수에서는 this는 환경 객체를 의미하지만, 그렇지 않고 정의되어진 함수에서는 window를 의미합니다.
제가 수많은 테스트케이스를 만들어야했던 이유가 바로 이것 때문입니다ㅜ..
2번과 3번에서의 경우는 arrow function의 특징을 살펴보면 this가 무엇을 의미하는지 금방 캐치할 수 있겠지만.. 4번부터 7번까지의 케이스에서 살펴보면 아니 대체 이게 왜 이렇게 나오는지 알 수가 없었습니다.
Arrow Function으로 정의된 5번과 7번의 케이스를 제외한 4번과 6번을 살펴보겠습니다.
분명 서로 다른 형식으로 정의된 함수 안에서 실행시켰지만.. 전부 window를 가리키고 있습니다.
이 이유가 바로 "메소드가 아닌 정의된 함수"였기 때문이었습니다.
이걸 몰랐던 저는.. this를 이해하는데 일주일을 사용해야 했습니다.ㅠㅠ

( )=>{ } vs. function( ){ }

arrow function은 this를 바인딩 하고 있습니다. 즉, 풀어 작성하게 되면
function( ){ }.bind(this) 이렇게 된다는 뜻이죠.
아니! 그럼! 얘가 대체 왜 this에 영향을 주느냐!! 라고 묻는다면! 2번과 3번의 경우를 보러 가시면 됩니다.
obj[2]의 경우에서 this는 객체 자신을 가리키지만, obj[3]에서는 window를 가리키고 있습니다.
obj[1]에서 확인할 수 있는 것처럼 obj의 this는 window를 가리키기 때문에,
이 window 객체를 this로 바인딩해 obj[3]의 실행 결과도 window를 가리키게 되는 것이죠.

다시 말해, arrow function에서의 this는 그 환경의 this가 어디를 가리키는지에 따라 결정되어집니다.

new operator

new 생성자를 사용해 클래스를 생성해줄 때에도 마찬가지입니다.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

다음과 같은 Rectangle class가 존재한다고 가정해봅시다.
여기서 this가 의미하는 것은 무엇일까요?
그렇습니다. Rectangle 자체를 의미하게 됩니다.
Rectangle의 constructor method에서 가리키는 this는 Rectangle이기 때문에,
Rectangle에서 정의하는 height가 this.height로 작성되어진 것입니다.

This, 이제 어렵지 않죠!?

이제 This를 파악하기 위해 어떤 걸 봐야할지 알겠죠! 완전 알겠죠!
모르겠다면.. 제 능력의 부족함이네요.
다른 분들의 동영상 강의, 혹은 블로그를 읽다보면 빡!! 하고 이해가 오는 순간이 있을 거랍니다.

그럼 bind, call, apply는 뭐지?

This에 대해 어느정도 찾아보고 읽어보고 했다면 세트로 따라붙는 bind, call, apply method를 한 번쯤은 읽어보셨을 것이라고 생각합니다.
못 읽어보셨더라 해도 상관없습니다. 지금 여기서 읽어보게 될테니까요 :)

bind, call, apply 모두 Function Method입니다.
인터넷에 검색해보면 Function.prototype.bind 라고 나와있는걸 알 수 있습니다.
그 말인즉슨! 함수에만 사용이 가능하다는 점이죠.
다음과 같은 makeProfile 함수가 있다고 가정해봅시다.

function makeProfile(name,age){
  this.name = name
  this.age = age
  this.sayName = function(hobby){
    return `my name is ${this.name} and ${age} years old. My hobby is ${hobby}.`
  }
}

new operator를 사용해 객체 인스턴스를 생성시켜보자.

const minsoo = new makeProfile("minsoo",27);

minsoo.name //minsoo
minsoo.age //27
minsoo.sayName('playing basketball') //my name is minsoo and 27 years old. My hobby is playing basketball.

우리가 가지고 있는 저 sayName이라는 메소드 함수에 bind, call, apply를 사용하면 이름과 나이를 바꿔줄 수 있다.

셋 모두 첫 인자로 this를 대체할 object, 그 뒤로는 sayName과 같은 메서드에 넘겨줄 인자들을 차례로 작성해준다. 이렇게!

minsoo.sayName.apply(minseo,["playing basketball"])
minsoo.sayName.call(minseo,'playingbasketball')
minsoo.sayName.bind(minseo,'playing basketball')

결국 쓰는 모양새와 해주는 일은 비슷한데..
세 개 씩이나 있으니.. 하늘 아래 같은 기능, 같은 메소드는 없다.
분명, 차이점은 존재한다.

bind vs. call, apply

bind와 call,apply에는 큰 차이점이 존재한다.
bind를 실행시켰을 경우 함수를 리턴하지만, call과 apply는 결과값을 리턴한다.

minsoo.sayName.apply(minseo,["playing basketball"])
minsoo.sayName.call(minseo,'playingbasketball')
// 'my name is minseo and 26 years old. My hobby is playingbasketball.'

minsoo.sayName.bind(minseo,'playing basketball')
// ƒ bound ()

따라서, bind에서 call, apply 와 같은 결과값을 받기 위해선 함수를 한 번 실행시켜줘야한다.
혹은 minsoo.sayName.bind(minseo)('playing basketball')처럼 실행시켜주는 것도 가능하다.

call vs. apply

관찰력이 좋은 사람이라면 알겠지만, call과 apply에도 차이점은 존재한다.
실행시킬 함수에 인자를 넘겨주는 방식이다.
call의 경우 ,로 구분해서 넣어주지만, apply는 [ ] 안에, 즉 배열 안에 담아서 넘겨준다.

Summary

결국 this를 파악하려면 this가 어느 context에서 요청되었는지 확인해야한다.

method => obj
function => global object, window

method라면 object를 가리킬 것이고, function이라면 window를 가리키게 될 것이다.
그리고 동시에 arrow method로 불렸는지, 아닌지를 판단해야 진짜 this가 가리키고 있는게 뭔지 알아볼 수 있게 될 것입니다 :)

profile
개발하는 반숙계란 / 하고싶은 공부를 합니다. 목적은 흥미입니다.

0개의 댓글