[JS] this 에 대한 고찰

UI SEOK YU·2023년 5월 28일
0

JavaScript

목록 보기
6/6
post-thumbnail

this는 도대체 뭘 가리키고 싶은걸까?

코어 자바스크립트에서 정리한 내용.

그냥 메서드 앞에 객체가 참조되어 있는 지 여부에 따라 크게 다르고

생성자로 호출시에만 생성되는 객체를 가리킨다 정도로만 이해하면 될 것 같다.


this를 염두하지 않은 채 코드를 짜면..

근데 이 포스트에서 말하고 싶은 내용은 이 기본적인 내용이 아니다.

this가 뭘 바인딩하는지 모르면 어떻게 될까??

그 문제를 직접 겪었기에 에러 해결한 내용을 풀어쓴다.


배경지식

  • 함수는 자신이 가지고 있지 않은 변수를 활용해야한다면, outerEnviroment를 참조한다.
  • outerEnviroment는 자신이 '선언된' 함수를 가리키는 주소이다.
  • 화살표 함수는 this 바인딩을 하지 않는다. 따라서 함수 내부에서 this를 사용할 경우 this가 뭔지 모른다.
  • 그래서 outerEnviroment를 참조하는데, 여기는 자신이 선언된 함수이다.
  • 자신이 선언된 부모함수가 화살표 함수가 아니라면, 부모함수가 실행컨텍스트를 생성할 때, this 바인딩이 이루어 질 것이다.
  • 부모함수가 어디서 호출되었는지에 따라 this가 달라지게 되고, 그 this가 결국 화살표 함수의 this가 되게 된다.
    ( 엄밀히 말해 this를 사용한다면 부모의 this를 사용하게 된다 )

문제

//code 1 --- error

  @UseGuards(DynamicAuthGuard)
  @Post('login/:method')
  async localLogin(
    @Body()
    userlocalLoginInboundPortInput: UserLoginInboundPortInputDto,
    @Res()
    res: Response,
  ): Promise<UserLoginInboundPortOutputDto> {
    
    pipe(
      userlocalLoginInboundPortInput,
      userLoginInboundPort.login,		// <----
      tap((accessToken) =>
        res.setHeader('Authorization', 'Bearer ' + accessToken),
      ),
      tap(res.json),		// <----
    );

    return;
  }
  
//code 2 --- success

  @UseGuards(DynamicAuthGuard)
  @Post('login/:method')
  async localLogin(
    @Body()
    userlocalLoginInboundPortInput: UserLoginInboundPortInputDto,
    @Res()
    res: Response,
  ): Promise<UserLoginInboundPortOutputDto> {
    
    pipe(
      userlocalLoginInboundPortInput,
      (input) => this.userLoginInboundPort.login(input),		// <----
      tap((accessToken) =>
        res.setHeader('Authorization', 'Bearer ' + accessToken),
      ),
      tap((accessToken) => res.json(accessToken)),		// <----
    );

    return;
  }

화살표로 표시해 둔 부분을 잘 보자.
(input) => doSomeThing(input) 인 경우, 간단하게 doSomeThing으로 표현하고자 하였다.
인자와 실행값이 동일할 경우 축약할 수 있는 JS의 문법이다.

1번코드는 에러가 나고 2번코드는 정상적으로 동작한다. 왜 그럴까?


화살표로 표시한 곳은 두군데지만, 원인은 같으므로 아래쪽 화살표인 res.json을 가지고 에러를 해결해 보자.

1번코드의 에러를 보자.

ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'app')

갑자기 app 을 걸고 넘어진다.
app을 걸고 넘어진 이유는 this 바인딩이 잘못 이루어졌기 때문이다.
json 메서드에 대한 코드를 찾아보자.

export type Send<ResBody = any, T = Response<ResBody>> = (body?: ResBody) => T;

export interface Response<
    ResBody = any,
    LocalsObj extends Record<string, any> = Record<string, any>,
    StatusCode extends number = number
> extends http.ServerResponse,
        Express.Response {
    ...
    /**
     * Send JSON response.
     *
     * Examples:
     *
     *     res.json(null);
     *     res.json({ user: 'tj' });
     *     res.status(500).json('oh noes!');
     *     res.status(404).json('I dont have that');
     */
    json: Send<ResBody, this>;
    
    ...
}

Response형태의 객체가 호출한 json 의 경우, Send라는 형태의 타입이며, 이는 Resbody라는 타입과 this에 대한 타입을 매개타입으로 받는다.


원인

첫번째 코드에서 에러가 난 이유는

함수를 변수에 할당하거나 다른 함수의 매개변수로 전달할 때, 해당 함수는 본문 내용만을 가지고 이동하기 때문이다.

따라서 객체에 포함된 함수인 메서드의 경우, 객체의 정보는 가져가지 않고 함수의 본문만을 전달하게 된다.

res.json에서 res없이 json의 본문 만 전달받은 tap() 함수는 json을 실행시키자 this가 필요하게 되었다.

이 this는 Response형태의 객체인 res를 상정해야 정상적으로 동작하지만, 여기서는 this가 undefined가 되어 버렸다.

( this는 json() 의 실행 컨텍스트가 생성 될 때 Lexcial Environment를 생성하며 thisBinding도 같이 이루어 지는데, 메서드가 아닌 함수로 호출되었으므로 엄격모드인 타입스크립트에서는 undefined 가 바인딩 된다. )

따라서 'undefined'인 객체의 'app' 프로퍼티를 읽으려고 시도하다가 에러가 발생한 것이다.

( json() 이 내부적으로는 어떻게 동작하는지 모르겠지만 this.app() 이런게 있는 듯 하다. )


해결

그렇다면 어떻게 해야 이 문제를 해결 할 수 있을까?

이 문제가 발생한 원인은 this가 적절한 객체를 가리키지 못한 것이므로, 적절한 this 바인딩을 해 주면 된다.

this가 바인딩해야 할 적절한 객체는 json()을 호출해야할 res 객체이다.

따라서 tap() 함수에 메서드를 전달할 때, res객체에 대한 정보를 같이 넘기면 된다

이를 위해 화살표 함수를 활용하여, 화살표 함수의 본문으로 res.json() 이 넘어가도록 하고,

tap()에서는 이 화살표함수를 받아 res.json()을 온전하게 실행시키게 하면 된다.

그 결과가 두번째 코드이고, 잘 작동하게 되었다.

0개의 댓글