코딩하는 고릴라

[FE] 렉시컬 환경, 클로저를 활용한 기능 구현 본문

Project/Bridgetalk

[FE] 렉시컬 환경, 클로저를 활용한 기능 구현

코릴라입니다 2024. 5. 5. 19:24

🐖 Why

프로젝트 진행 중 녹음 기능 관련하여 평균 음량을 도출해내야 하는 요구사항이 있었습니다. 이를 계산하기 위해 도출된 음량의 합(volumes)과 음량을 측정한 횟수(count)를 필요로 했는데 평균 음량 측정에만 필요한 해당 변수들을 컴포넌트에 남겨두고 싶지 않아 함수 작성 방식에 대해 고민해봤습니다.
 

🐄 What

위와 같은 내용들을 충족시키기 위해 최근에 학습한 렉시컬 환경과 클로저 개념을 활용했습니다.

변수의 유효범위와 클로저

ko.javascript.info

 

🐌 How

export function getAvgVolume() {
  let count = 0;
  let volumes = 0;

  function getAvg(volume: number) {
    volumes += volume;
    count++;
    return Math.round(volumes / count);
  }

  return getAvg;
};

작성한 getAvgVolume 함수입니다. 
내부에 변수로 volume을 측정한 횟수인 count와 측정된 volume들의 합을 구할 volumes를 두었습니다.
그리고 getAvg 중첩 함수를 클로저로 하여 외부 변수인 count, volumes 를 통해 평균 음량을 계산하도록 했습니다.
해당 중첩 함수 getAvg를 반환하여 해당 함수를 외부에서 사용할 수 있도록 했습니다.
 
또한 해당 함수를 외부에서 호출 할 때 마다 새로운 렉시컬 환경이 생성되기 때문에
녹음을 잠시 중단했다 다시 시작했을 때는 count, volumes 값이 0부터 시작해 다시 평균음량을 계산하도록 했습니다.
 
위 함수의 환경 레코드를 표현해봤습니다.

getAvg 함수에는 어떤 변수도 없기에 비어있는 환경 레코드를 가집니다. 그럼에도 불구하고 getAvg 함수는 volumes와 count에 접근할 수 있습니다. 이는 해당 값들이 환경 레코드에 존재하지 않을때 상위 환경 레코드로 이동해 해당 값들을 탐색하기 때문입니다. 이때문에  getAvgVolume의 환경 레코드에서 count와 volumes를 찾을 수 있고 이를 활용할 수 있는 것입니다. 그래서 getAvg는 외부 변수를 기억하고, 이를 활용할 수 있는 클로저라고 할 수 있습니다.
 

// getAvg 함수를 외부에서 호출하는 코드

  useEffect(() => {
    let volumeCheckInterval: any = null;

    if (isRecording && !volumeCheckInterval) {
      // 음량 체크
      // ...

      // 녹음 시작
      // ...
    }

    if (!isRecording) {
      console.log('함수 재선언');
      getAvgVolumeData.current = getAvgVolume();
    }

    return () => {
      // 음량 체크 및 녹음 종료
	  // ...
    };
  }, [isRecording]);

녹음의 진행 상태를 알려줄 isRecording state 값이 변할 때, 녹음이 새로 시작됐다면 getAvgVolumeData.current 에 getAvgVolume 함수의 호출 결과(반환된 중첩함수인 getAvg)를 저장하도록 했습니다.
녹음을 중단했다가 다시 시작하게 되면 getAvgVolume 함수를 다시 호출하게끔 하였고 이에 따라 아래 사진과 같은 새로운 렉시컬 환경이 형성되도록 유도했습니다. 이를 통해 새로운 녹음이 이뤄질 때 count, volumes 값이 0으로 초기화되는 효과를 가질 수 있게 했습니다.

  // 볼륨 체크
  useEffect(() => {
    if (isRecording && getAvgVolumeData.current) {
      if (volume >= Math.floor(getAvgVolumeData.current(volume) * 0.6)) { // 평균 음량을 계산하여 조건부 처리
        if (devounceTimerRef.current) {
          clearTimeout(devounceTimerRef.current);
        }
        devounceTimerRef.current = setTimeout(() => {
          setIsSend(true);
          setIsRecording(false);
        }, 2000);
      }
    }
  }, [volume]);

위 코드에서는 if문 안에서 getAvgVolumeData.current 를 활용해 평균 음량을 계산했습니다. 녹음이 중단됐다 다시 시작되지 않는 이상 getAvgVolume 함수 내 count, volumese 값은 0으로 초기화되지 않고 계속해서 사용할 수 있습니다.
 

🦍 Result

최근 모던 자바스크립트 튜토리얼 사이트를 통해 자바스크립트에 대해 보다 깊이있게 공부를 하고 있습니다. 보통 기차에서 왔다갔다 하는 동안 핸드폰으로 읽는 정도라 실습을 해 볼 기회는 없었는데 해당 기능을 직접 적용해보면서 렉시컬 환경과 클로저 개념에 대해 조금 더 확실히 이해할 수 있게 됐습니다.

반응형