티스토리 뷰

728x90
SMALL

spread ref

지난 강의에서 참조값을 루틴(함수)으로 전달하면 생기는 문제들에 대해 이야기 했었다. 만약 메인 플로우에서 하나의 루틴에 전달해서 변경이 된다면 어느정도는 예측가능하지만 이 변경된 값을 다른 루틴에 전달하면 흐름을 예측할 수 없다. 머리좋은 사람들은 그렇게 해도 된다. 나는 아니니까 그렇게 할 생각이 없다.

sub routine chain

이번엔 루틴에서 다른 루틴을 호출한다고 생각해보자. 위의 그림과 같은 형태가 된다. 메인 프롤우에서 r1루틴을 호출하면 r1에서 r2를 호출하고 r2에서 r3를 호출하고 이런식으로 반복된다. 이때 콜스택이 쌓이게 된다. r1에서는 r2의 호출이 끝날때 까지 기다려야 되기 때문에 최종적으로는 r2,r3,r4~~모든 루틴이 실행되고 비로소 return이 된다. 보통 재귀에서 이런 현상이 발생한다. 그리고 일정 수치를 넘으면 stack overflow가 발생한다.  

tail recursion

tail recursion(꼬리 재귀)를 살펴보자. 위의 sub routin chain과 유사하다. 다른점은 return할때 루틴을 실행하는 것이다. 이게 무슨 의미가 있을까?? 의미가 있을수도 없을 수도 있다. 단, 우리는 자바스크립트 관점에서 OOP 강의를 듣고있다. 그게 중요하다.

 

자바스크립트에서는 꼬리물기최적화를 지원하라고 공식 문서에 적혀있다. 다른 언어는 지원하지 않는 언어도 있다. 그리고 사실 사파리를 제외한 브라우저에서는 현재 꼬리물기최적화를 지원하지 않는다. 강의가 올라온 년도는 2018년인데 현재 2022년에도 지원하지 않는다. 구글링을 해보면 이에 대해 꽤나 깊은 토론이 있었다는 알 수 있다. 이 방법이 크롬같은 브라우저에 영원히 도입되지 않을지도 모른다. 그렇다고 이 방법을 아는게 전혀 도움이 되지 않는다는 것은 아니다. 어쨌든 자바스크립트 공식 입장은 이를 도입하라는 것이고 브라우저가 여러 이슈들로 도입하지 않고 있을 뿐이다. 

 

return할때 루틴을 실행하면 현재 루틴이 콜스택에서 제거된다. 그리고 다음 루틴을 실행한다.(콜스택에 쌓인다.) 이렇게 되면 stack overflow가 발생하지 않는다. 엄청나다... 

 

그러면 이제 실제 함수로 예를 들어보자. 먼저 아래 간단한 함수를 보고 꼬리물기최적화를 적용시켜보자.

꼬리물기 최적화 이전 코드

const sum = (v) => v + (v > 1 ? sum(v - 1) : 0);
sum(3);

 

이전시간에 우리는 덧셈연산자가 있을 때 발생하는 일에 대해서 배웠다. 덧셈연산자가 있기 때문에 바로 return을 하고 스택에서 제거할 수 없다. 한번 생각해보자.

 

~

~

~

~

~

~

~

~

~

~

~

~

~

~

~

~

~

정답은  다음과 같다.

꼬리물기 최적화 적용 코드

const sum = (count, prev = 0) => {
  prev += count;
  if (count < 1) return prev;
  return sum(count - 1, prev);
};

sum(3)

 

강의코드를 배끼는게 싫어서 그냥 혼자 생각해봤는데 삼항연산자 처리빼고는 똑같아서 그냥 이렇게 썼다. 어려운 코드는 아니다. 다만 아까 쓴 코드를 보고 아 저거 꼬리물기 최적화 할 수 있는데 이렇게 바꿔야지 라고 생각하는건 쉽지 않을것이다. 애초에 그런 생각을 안하는(못하는) 개발자가 90프로이상이니까. 나역시 그랬으니 다른 개발자들을 비하하는것은 아니다. 

tail recursion to loop

이제 이걸 for문 혹은 while문으로 바꿔보자. 사실 애초에 위의 코드처럼 작성하는 사람은 아무도 없다. for문이나 while문을 사용한다. 저건 재귀공부할때나 쓰이는 코드다. 물론 재귀를 써야만 하는 코드는 꽤 난이도가 있기 때문에 처음에는 저런 코드로 연습을 한다.

 

최적화 while문, for문

const sum = (count) => {
  let prev = 0;
  while (count > 0) {
    prev += count;
    count--;
  }
  return prev;
};

const sum = (count) => {
  let prev = 0;
  for (; count > 0; count--) {
    prev += count;
  }
  return prev;
};

참고로 강의에서는  while(v>1) 이라고 적혀있다. 강사님 실수 ㅎㅎ

while문을 사용하면 매우 간단하게 표현할 수 있다. 근데 위의 꼬리물기 최적화 코드와 while문의 코드는 당연하지만 별로 다를게없다. 컴퓨터에 한쪽의 코드를 넣으면 다른 한쪽의 코드를 뱉을 수 있는 정도다. 이걸 코드를 적지 않아도 바로바로 떠올릴 수 있는 수준까지 올려야한다. 이런 간단한 함수가 아니라 좀 더 복잡한 함수의 경우에도 말이다. 

closure

클로저... close의 명사형이다. 

실행컨텍스트나 이벤트루프, 리액트 훅스 동작방식등을 공부하면서 이미 closure는 알고 있다. 근데 조금 충격받았던 것은 지금 자바스크립트 엔진은 예전에 우리가 알고있던 스코프체인이나 프로토타입 체인 이런 방식으로 동작안한다는 점이다. 매년 엔진은 변화하고 우리는 그 모든 변경사항을 알 수 없다. 그래서 기저에 깔린 기본기를 공부해야한다. 라는게 강사님 생각이다.

 

어쨌든... 계속 다른 이야기를 하는데

자바스크립트에서는 자유변수가 있고 런타임에 루틴을 만든다. 이때 자유변수는 루틴안에 존재하지 않고 그 위의 플로우에 존재하는 변수를 말한다. 밑이 플로우에서 자유변수를 참조할 수 있다. 이렇게 되면 자유변수를 가두게 된다. 가두게된다에서 close, closure라는 용어가 나온 것 같다. 가두게되었다는 것을 생각해보자. 외부의 플로우가 종료되고 내부의 플로우에서 자유변수를 참조하면 어떻게 될까?? closure. 가두게된다. 즉 메모리 해제가 되지 않는다. 자바스크립트에서는 클로저를 꽤 많이 사용한다. 리액트 훅스만 봐도 그렇다. 이게 경우에 따라서는 메모리 해제가 안되기 때문에 문제가 생길 수도 있다. 단, 그런건 알고만 있자. 실무에 적용하는 건 한참 나중 이야기다.(내기준)

 

이 강의를 듣는사람은 클로저에 대한 개념이 있기 때문에 자세한 설명은 생략한다.

nested closure

근데 사실 es6부터는 블록으로만 감싸도 블록이 만들어진다. 루틴이 없어도 말이다. 함수, 실행컨텍스트를 생각하면 이해할 수 있다. 이해가 안된다면 다음 영상을 보고오자.

https://www.youtube.com/watch?v=vuEYJRXSIdk 

shadowing

shadowing. 이게 뭐지?? 할 수도 있고 eslint를 빡세게 사용해본 사람 혹은 클래스의 상속 등을 공부해본 사람이라면 들어봤을 수도 있다. 변수명이 겹칠때 가장 가까이 있는 클로저를 사용하는 것이 shadowing이다. 그래서 상위 플로우에 a라는 변수가 있을 때 하위 플로우에서 a라는 변수를 다시 선언하면 위의 플로우의 a가 가려진다. 이걸사용해서 권한을 제약하기 위해서 사용하기도 한다. 그런데 잘못사용하면 상위 플로우에서 가져와야하는 변수를 가릴수도 있다. 그래서 에어비엔비 린트에서는 쉐도잉을 막는 것 같다. 이런 상황을 방지하기 위해서 말이다. 만약 급여, 이자율, 세금같은 중요한 업무에서 잘못된 쉐도윙이 발생하면 문제가 크다. 그런 관점에서 봤을 때 린트에서 막는것도 이해가 간다. 

우리는 나는 똑똑하지 않고 회사에서 돌아가는 모든 코드가 머리에 들어있지 않기 때문에 그런것같다. 

co routine

루틴이라는 건 플로우를 다 마치고 return하는 것이다. 즉 호출할때마다 루틴은 반드시 끝까지 실행된다. 그런데 그렇지 않은 루틴이 있다. 아마 예측했을 것이다. 제너레이터다. 제너레이터에서는 제어권을 넘길 수 있다. 이를 이용해서 함수를 여러개 만들필요 없이 하나의 제너레이터 함수로 처리할 수 있다. 물론 이 방법이 무조건 좋다고는 볼 수 없다. 리덕스 사가정도를 제외하고는 프론트 실무에서 제너레이터를 다룰일은 많이 없다. 또한 무한순열같은것도 구현할 수 있다.

728x90
LIST
댓글
공지사항