losing thi?s [JavaScript]

yoondengo·2022년 8월 24일
1

JavaScript

목록 보기
4/5
post-thumbnail

최근에 어김없이 자바스크립트를 공부하면서 this에 대해 이래저래 많이 알아봤는데 아직 이거에 대해서 100% 알고 있다고 자신할 순 없지만 중간 결산차 적어보려 합니다.

이번 글에서는 this가 처음에는 어떤 값을 가지고 시작하며 어떻게 결정되는지를 알아보고,
더 나아가서는 this를 잃어버리는 상황을 비롯한 제가 헷갈려 했던 부분들을 정리해보려 합니다.

this가 뭔가요

this란 함수를 호출한 객체입니다.
그렇다는 말은 우선 함수에 관련된 요소라는 거겠죠
일단 사실 this가 뭔지 물어보기 이전에 사실 대부분 이걸 어렴풋이 많이 사용을 해왔을 것 입니다.
하지만 내부적으로는 생소하다면 this가 어떻게 결정되는지를 조금은 알아야 합니다.

함수 실행 컨텍스트

자바스크립트는 Single Thread 언어로써 엔진 내에 있는 Call Stack에 이 함수 실행 컨텍스트가 push / pop 되며 코드를 실행합니다.
이 부분에 대해서는 좀 더 자세한 학습이 반드시 필요합니다만 이번엔 이 정도만 설명하고 넘어갈게요.

이 실행 컨텍스트는 생성 단계실행 단계로 구분이 되는데
우리가 알아보려는 this는 생성 단계에서 결정됩니다.

정확히 함수 실행 컨텍스트의 생성 단계에서 자바스크립트는 렉시컬 환경을 정의하게 되는데 여기에 함수내부의 선언된 변수와 함수를 비롯하여 this또한 정의가 됩니다.

여기서 일단 this가 기본적으로 가지고 있는 값이 있는데 이 부분이 정말 중요하다고 생각합니다.
this는 기본적으로 전역 객체(window 혹은 global)을 참조하고 있습니다.

하지만 strict mode 가 사용된 경우, 즉 최신 문법을 사용하는 경우에는
this는 기본적으로 undefined를 가지고 있습니다.

그래서 다음과 같이 누구나 헷갈려 할 수 있는 결과를 낳는 것이죠.

그리고 방금 말씀드린 생성 단계에서 this는 함수 호출 패턴에 따라서 원래 가지고 있던 전역 객체를 놓고 자신을 호출한 객체를 물게 됩니다.
이것을 this 바인딩 이라 표현합니다.

함수 호출 패턴?

대략 4가지의 패턴이 있을 것 입니다

  • 함수로써 호출하기
  • 메서드로써 호출하기
  • 생성자로써 호출하기 (new 연산자와 호출)
  • apply, call을 사용한 호출하기

함수로써 호출은 위의 코드 사진과 같이 호출이 되는 상황일 것 입니다. 즉 초기화된 this에서 달라지지 않겠죠.

메서드로써라는 말은 객체가 호출하고 있다는 뜻이겠죠? 따라서 this는 호출한 객체가 됩니다.

모두 아시다시피 함수의 실행을 new 연산자와 함께 실행하면 객체를 만들 수 있습니다.
저는 이 부분이 좀 생소 했는데 이 경우 this는 만들어지는 객체를 참조합니다.
가만 생각해보면 당연한 소린가 싶기도 한데 어쨌든 그렇습니다

마지막으로 apply, call을 사용해서 호출하는 것은 this를 바인딩 해주기 위함 입니다.
저는 아직 경험이 미숙한지라 apply와 call의 활용 범위를 두고 완벽한 글을 쓸 수는 없습니다만,
적어도 이 두개의 함수가 this를 잃어버리는 상황에서 쓰인다는 것을 알고 있습니다.
자매품으로 bind도 있는데 같은 역할을 하지만 bind는 함수를 실행하지 않고 그냥 this를 바인딩까지만 해주는 역할을 합니다.

어쨌든... this를 잃어버리게 되는 상황이 어떻게 오게 되는걸까요?

내 this 돌려줘요

사실 이 상황에 대해서도 당연히 모두 알고 있지 않습니다.
https://ko.javascript.info/bind
링크의 글을 참고해보면
객체 메서드가 객체 내부가 아닌 다른 곳에 호출되면 this가 사라집니다.
라고 나와있습니다.
저는 이것을 읽고 setTimeout 뿐만 아니라 모든 함수에서 메서드가 인수로 전달되면 저렇게 될까?가 궁금해졌습니다.

주목할만한 라인은 30번째와 31번째 라인이겠죠.
각각 global과 Timeout 객체를 보여주고 있습니다.
원래 브라우저에서 실행을 하면 둘다 window객체를 반환하는 것을 확인했습니다만
링크에 작성자님의 말씀대로 node.js에서 저는 실행을 하였기 때문에 Timeout 객체로 나오게 되었습니다.
즉 setTimeout 뿐 아니라 객체 메서드를 다른 곳에서 호출하면 this가 사라지는 것을 확인한 셈 입니다.

어쨌든 이런 상황이 왜 발생할까요?
생각보다 간단한 이유라는 것을 알게 되었는데요.

우선 이 간단한 예제를 먼저 보시면
메서드를 변수에 옮기고 출력을 했더니 정상적으로 메서드가 잘 들어가있는 것을 볼 수 있습니다.
하지만 오해하시면 안되는 부분이 이것은 단순히 객체가 참조하는 프로토타입 메서드 함수 주소를 참조해서 click에 쥐어준 바와 같습니다.
즉, 12번째 라인의 click은 말 그대로 그냥 저 함수를 들고 있는 것이지 click();을 한다고 치면 this가 button이 될 순 없습니다.

다시 user.sayHi를 사용했던 예제로 돌아가보면 마찬가지로 메서드의 주소를 그냥 넘겨주기만 하고 있다는 것을 알 수 있습니다.
aa에서 실행된 callback()의 실행 컨텍스트는 this를 초기값인 global으로 들고 있었겠죠

참고
더 나아가서 setTimeout의 경우엔 좀 특이한데 인자로 들어가는 함수가 이벤트 루프를 타기 때문에 this를 이 과정에서 잃어버린다고 합니다.
setTimeout은 global과 window 둘 다 가지고 있는 함수인데 아마 둘이 동작하는 과정이 달라서
결과가 각각 다른 것으로 예상됩니다.
이 부분은 특히 잘 모르는 부분이라 말 그대로 참고만 부탁드리겠읍니다,, 🙇‍♂️

화살표 함수의 this

화살표 함수의 실행 컨텍스트는 신기하게도(?) this를 가지고 있지 않습니다
하지만 화살표 함수 내에서도 this를 사용할 수 있는데요.
이것은 사용가능 하다기 보다는
자바스크립트의 특성상 화살표 함수의 실행 컨텍스트에서 사용된 this를 찾는데 당연히 화살표 함수 내엔 this라는게 없으니까 외부 렉시컬 환경, 즉 상위 스코프의 this를 찾게 됩니다.

저는 여기서 다음과 같은 상황이 궁금했습니다.

이상하죠 방금과 같은 상황인데 이번엔 click에 button이 잘 바인딩 되어있는 것을 볼 수 있습니다.

우선 사전 지식으로 알아야 할 것은
클래스 내부에서 메서드를 정의할때
일반함수로 정의를 하면 프로토 타입에 메서드가 저장되고
화살표함수로 정의를 하면 그 메서드가 객체의 프로퍼티가 됩니다.

생각보다 엄청 중요한 개념인데 이 말은 즉, 클래스 내부에서 화살표 함수로 메서드가 정의되어 있을때
인스턴스를 만들때마다 그 메서드를 위한 공간이 계속 메모리를 차지하게 된다는 점 (말이 메서드지 프로퍼티니까) 입니다.
(지금 주제와는 상관 없는 이야기지만 이와 같은 이유로 클래스 내부에서 화살표 함수를 사용하는 것을 지양하는 이유로 꼽습니다)

결론적으로 이번 코드에서의 12번째 줄 button.click을 참조하는 것은
11번째 줄에서 만든 button 객체의 프로퍼티인 click을 참조하는 것과 같기 때문에 차이가 있다고 보시면 되겠습니다.

"13번째 줄에서 click()의 this는 근데 어떻게 결정되는거지?"
라고 궁금하실 것 같습니다 (저는 궁금했...)
왜냐면 제가 화살표 함수는 this를 안 갖고 있다고 했으니까요
결국 어쨌든 함수 실행 컨텍스트는 만들어질 것 이고 this를 결정하게 될텐데
그럼 클래스 내부의 화살표 함수의 상위 스코프는 무엇인가? 가 궁금했습니다.
결론적으로 말하면 사실 디테일한 과정은 아무리 뒤져도 결국 못 알아냈습니다만
위와 같이 클래스에서 함수선언이 아니라 클래스 필드로써 화살표 함수를 할당하게 된다면
this가 자동으로 바인딩이 된다고 합니다

역시 이곳을 참고하여 글을 작성했습니다 : https://ko.javascript.info/class

? 뭐여


위에서 봤던 코드(클래스 내부에서 함수 선언으로 메서드)와 완전히 똑같습니다.
다만 click(); 으로 이번엔 실행을 했죠.
위의 결과를 보고 '?? 뭐여' 라고 연신 생각했습니다,,,

좀 이상합니다...
왜냐면 this가 왜 global이 아닌가 해서요.
사실 이렇게 나온다는 것을 봤을때 제가 몰랐던 사실이 있습니다.

제가 맨 위에서
함수 실행 컨텍스트가 this의 초기값을 정할때 최신 문법을 사용하는 경우 (strict mode)
global(혹은 window)이 아니라 undefined로 나온다고 했죠,,, 이 사실을 몰라서 한참 헤맸습니다.

class는 자바스크립트에선 놀랍게도 최신 문법입니다.
따라서 이것을 사용할 경우 use strict가 기본적으로 적용된다고 합니다.
그래서 undefined로 나온 것을 확인할 수 있습니다.

참고 : https://ko.javascript.info/strict-mode
이곳을 보시면 이해가 될 것 같습니다
(모던 자바스크립트 짱)

마무리

지난 며칠간 this에 대해서 삽질을 해가며 여태 공부해왔던 내용도 어느정도 정리가 되었고
무엇보다도 처음 공부하는 사람의 입장에서 헷갈릴만한 상황이 굉장히 많았다고 생각합니다.
그래도 꽤 시간을 써서 알아낸 김에 한번 정리해서 언제 다시봐도 복기가 될 만한 글을 적어보고 싶었습니다.

사실 이번 내용에서는 함수 실행 컨텍스트 라는 개념을 필히 알아야 합니다.
따라서 다음번에 (언젠가) 이 내용에 대해서도 제가 이해한 바를 정리하여 포스팅을 해볼 계획입니다.

글이 너무 잡다한 내용까지 넣었나 싶긴하지만... 그래도 나름대로 많이 알아보고 직접 확인해봐가며 작성한 글이니 잘 봐주셨음..좋겠습니다
그리고 어? 이거 아닌데 ㅋ 이것도 모르네 싶은 부분이 있다면 코멘트 남겨주시면 넙죽 넙죽 감사히 읽겠으니 지적 부탁드리겠...드려보겠습니다.

감사합닏.

profile
Software Engineer (전산쟁이)

0개의 댓글