[TIL] Day 49 : 화살표 함수와 this binding

Q·2024년 6월 25일

TIL

목록 보기
50/59

Global Context 에서의 this

  • Global context에서 this는 기본적으로 브라우저 환경에서는 window 객체, Node.js 환경에서는 global 객체, strict mode에서는 undefined를 가리킨다.

함수 내의 this

  • 일반 함수 내의 this는 그 함수를 호출한 주체(객체)가 없다면 Global Context 에서의 this와 동일하고, 호출한 주체가 있다면 그 주체를 가리키게 된다. 즉, 호출한 주체와 this binding이 된다.
    예) obj.regularFunc()regularFunc 내의 this === obj

  • 화살표 함수 내의 this는 호출한 주체와 this binding이 되지 않는다. 즉, 화살표 함수를 호출한 주체와 상관이 없다. 대신 그 함수의 상위 scope의 this를 상속받는다. 이를 lexcial scope라고 한다.

일반 객체에서의 this binding

  • 일반 함수 선언문 형태의 method 안에 일반 함수와 화살표 함수

const obj = {
	regularMethod() {
    	console.log(this);			// (1)
        
        function regularFunc() {
        	console.log(this);		// (2)
	    }
        
        const arrowFunc = () => {
        	console.log(this);		// (3)
	    }
        
        regularFunc();
        arrowFunc();
    }
}

obj.regularMethod(); 

// (1) { regularMethod: [Function: regularMethod] }
// (2) undefined
// (3) { regularMethod: [Function: regularMethod] }

(1) regularMethod가 일반 함수이기 때문에 그 안에서 this는 regularMethod를 호출한 주체인 obj을 가리킨다.
(2) regularFunc이 일반 함수이기 때문에 그 안에서 this는 regularFunc을 호출한 주체를 가리키지만, 그 주체가 없기 때문에 undefined가 나온다.
(3) arrowFunc이 화살표 함수이기 때문에 그 안에서 this는 arrowFunc의 상위 scope의 this를 상속받는다. 즉, (1)의 this와 같다. 따라서 obj를 가리킨다.

  • 화살표 함수 사용은 아래와 같은 효과: 상위 scope의 this를 참조

const obj = {
	regularMethod() {
    	console.log(this);
        const that = this;
        function regularFunc() {
        	console.log(that);
	    }
        regularFunc();
    }
}
  • 화살표 함수 형태의 method 안에 일반 함수와 화살표 함수

const obj = {
	arrowMethod: () => {
		console.log(this);			// (1)

		function regularFunc() {
			console.log(this);		// (2)
		}

    	const arrowFunc = () => {
      		console.log(this);		// (3)
    	};
    
    	regularFunc();
    	arrowFunc();
	},
};

obj.arrowMethod();

// (1) undefined
// (2) undefined
// (3) undefined

(1) arrowMethod가 화살표 함수이기 때문에 그 안에서 this는 상위 scope의 this를 상속받는다. 그런데 상위 scope의 this가 존재하지 않으므로 undefined이 된다.
(2) regularFunc이 일반 함수이기 때문에 그 안에서 this는 regularFunc을 호출한 주체를 가리키지만, 그 주체가 없기 때문에 undefined가 나온다.
(3) arrowFunc이 화살표 함수이기 때문에 그 안에서 this는 arrowFunc의 상위 scope의 this를 상속받는다. 즉 (1)의 this와 같기 때문에 undefined이 된다.

class 객체에서의 this binding

class Obj {
	method () {
    	console.log(this);
    }
    
	regularFunc = function() {
		console.log(this);
	}

	arrowFunc = () => {
		console.log(this);
	};	
}

const obj = new Obj();

obj.method();					// (1)
obj.regularFunc();				// (2)
obj.arrowFunc();				// (3)

const method = obj.mothod;
const regular = obj.regularFunc;
const arrow = obj.arrowFunc;

method();						// (4)
regular();						// (5)
arrow();						// (6)
  • instance 생성 후 instance의 method로서 함수 실행

// (1) Obj {
  		regularFunc: [Function: regularFunc],
  		arrowFunc: [Function: arrowFunc]
	   }
// (2) Obj {
  		regularFunc: [Function: regularFunc],
  		arrowFunc: [Function: arrowFunc]
	   }
// (3) Obj {
  		regularFunc: [Function: regularFunc],
  		arrowFunc: [Function: arrowFunc]
	   }
  • instance의 method를 변수에 할당 후 함수 실행

// (4) undefined
// (5) undefined
// (6) Obj {
  		regularFunc: [Function: regularFunc],
  		arrowFunc: [Function: arrowFunc]
	   }

class body 안에서 그냥 directly method로 정의하든, 변수에 할당하는 식으로 일반 함수를 정의하든, 화살표 함수로 정의하든
(1), (2), (3) 결과는 동일하다.

따라서 여기서 핵심은 (4), (5), (6) 이다.

화살표 함수를 제외하면
instance를 생성instance의 method 함수를 변수에 할당함수 실행
했을 때, this는 undefined이 된다.

그 이유는 method를 변수에 할당하여 호출하면, method가 아니라 새로운 '함수'로 간주하게 된다.

따라서 method가 일반 함수로 정의되었다면, method 내부의 this는 일반 함수로 호출되었을 때의 this와 마찬가지로 호출한 주체를 가리키게 되는데, 호출한 주체가 없으니 undefined이 되는 것이다.

이렇게 함수 내의 this 값이 유실되는 문제가 3-layered-architecture pattern으로 코드를 분리를 할 때 발생할 수 있다.

예시

  • auth.router.js 내부
authRouter.post('/sign-up', signUpValidator, authController.signUp);

여기서 authController.signUp 메소드를 하나의 함수로서 가져오게 된다. 굳이 변수에 할당을 하지 않았을 뿐, 저 자체가 새로운 함수로서 인식이 된다.

  • auth.controller.js 내부 - 화살표 함수로 정의된 signUp 메소드
class AuthController {
  constructor(authService) {
    this.authService = authService;
  }

  // 회원가입
  signUp = async (req, res, next) => {
    try {
      // 작성 정보 받아오기
      const { email, password, nickname, role, contactNumber, address, image } = req.body;

      // user 생성하기
      const createdUser = await this.authService.signUp({
        email,
        password,
        nickname,
        address,
        role,
        image,
        contactNumber,
      });

      // 성공 메세지 반환
      res.status(HTTP_STATUS.CREATED).json({
        status: HTTP_STATUS.CREATED,
        message: MESSAGES.AUTH.SIGN_UP.SUCCEED,
        data: createdUser,
      });
    } catch (error) {
      next(error);
    }
    return;
  };
  
  (그 외 다른 메소드 중략)
  
}

따라서 authController의 signUp 메소드가 위의 코드와 같이 화살표 함수로 정의된다면,
signUp 내의 this는 상위 scope의 this인 AuthController 클래스의 instance를 가리키게 된다.

하지만, 만약 signUp 메소드가 화살표 함수가 아닌 일반 함수로 정의된다면, signUp 메소드 내부에서 처음으로 등장하는 this 부분(await this.authService.signUp)에서 에러가 발생하게 된다.

  • auth.controller.js 내부 - 일반 함수로 정의된 signUp 메소드
signUp = async function (req, res, next) {
    try {
      // 작성 정보 받아오기
      const { email, password, nickname, role, contactNumber, address, image } = req.body;

      // user 생성하기
      const createdUser = await this.authService.signUp({
        email,
        password,
        nickname,
        address,
        role,
        image,
        contactNumber,
      });

      // 성공 메세지 반환
      res.status(HTTP_STATUS.CREATED).json({
        status: HTTP_STATUS.CREATED,
        message: MESSAGES.AUTH.SIGN_UP.SUCCEED,
        data: createdUser,
      });
    } catch (error) {
      next(error);
    }
    return;
};
  • 에러 발생
TypeError: Cannot read properties of undefined (reading 'authService')
    at signUp (file:///Users/changjun/Documents/sparta/project-team/delivery-platform/src/controllers/auth.controller.js:16:38)
    at Layer.handle [as handle_request] (/Users/changjun/Documents/sparta/project-team/delivery-platform/node_modules/express/lib/router/layer.js:95:5)
    at next (/Users/changjun/Documents/sparta/project-team/delivery-platform/node_modules/express/lib/router/route.js:149:13)
    at signUpValidator (file:///Users/changjun/Documents/sparta/project-team/delivery-platform/src/middlewares/validators/sign-up-validator.middleware.js:37:5)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

예상한대로 this 값이 유실되어 undefined가 되어버렸기 때문에
this.authService, 즉 undefined에 'authService' 라는 property가 존재할 수 없으므로 에러가 발생하는 것이다.

결론

3-layered-architecture pattern으로 코드를 분리하고 class를 사용하고, 각 class의 method를 전달할 때, 화살표 함수를 사용하지 않으면 this binding 문제 때문에 this 값이 유실되는 문제가 발생할 수 있다.

따라서 이런 문제를 피하려면 화살표 함수를 사용하는 것이 좋다. 특히 위의 예시와 같이 메서드를 콜백으로 전달할 때 유용하다.

0개의 댓글