Javascript 스코프와 클로저 이해하기

고광필·2022년 3월 22일
2

Front

목록 보기
7/33

클로저를 공부하다가 이해되지 않았던 점들을 의문점들을 정리하며 글을 작성하겠습니다.

의문의 순서
1. 클로저가 어떻게 외부 변수를 참조하는가?
2. 클로저를 왜 쓰는가?
3. 데이터 은닉을 왜 하는가?

클로저의 쉬운 정의

: mdn 등 여러 문서에는 '함수와 함수가 선언된 어휘적 환경의 조합'이라고 설명합니다.
기본적인 개념은 '외부 변수를 참조하는 함수를 클로저 함수'라고 합니다.

function say() {
  let name = '김철수';
  
  return function() {
  	console.log(name);
  }
}
const logName = say();
logName();

위 코드의 say()는 함수를 반환합니다.
반환된 함수는 외부의 name에 접근합니다.

logName은 say()의 반환값인 함수입니다.
위 코드의 say()는 외부의 name을 참조하기 때문에 클로저 함수입니다.
의문1. 외부 변수가 여러개일수도 있는데 어떻게 참조하는 걸까요?

스코프와 스코프 체이닝

스코프

function school() {
  let leader = '홍길동';
  
  function class1() {
    let leader = '김철수';
  }

  function class3() {
    let leader = '김영희';
  }
}

1반과 3반은 반이 다르기 때문에 당연히 반장도 다릅니다.
1반에 가서 반장을 물어보면 철수가 대답하고, 3반에 가서 물어보면 영희가 대답합니다.
이처럼 어떤 변수(학생)에 접근할 수 있는 범위(반)를 스코프라고 합니다.

전교회장 길동이가 5반이라고 하겠습니다.
어느 반을 가서든 전교회장을 물어보면 길동이라고 대답합니다.
이처럼 전역적인 범위를 가지는 스코프를 전역 스코프라고 하고, 특정 범위를 가지는 스코프를 지역 스코프라고 합니다.

자바스크립트의 스코프

자바스크립트는 함수를 선언시에 스코프를 생성합니다.

  • 블록 스코프
    if, for과 같은 중괄호 블록을 기준으로 스코프를 생성합니다 (const, let)
  • 함수 스코프
    함수를 기준으로 스코프를 생성합니다 (var)

스코프 체이닝

자바스크립트는 함수 선언시에 '이 변수는 여기 있는 변수고, 저 변수는 저기에 있는 변수구나'를 스코프 체이닝을 통해 찾습니다.

let someone = "대한민국 김철수";

function university() {
  let someone = "대학교 김철수";

  function computer() {
    let someone = "컴퓨터공학과 김철수";

    function software() {
      let someone = "sw전공 김철수";

      function guess() {
        console.log(`혹시 ${someone}꺼?`); // 혹시 sw전공 김철수꺼?
      }
      guess();
    }
    software();
  }
  computer();
}

university();

소프트웨어학부실 앞에서 '김철수 졸업프로젝트'라고 써있는 usb를 주웠습니다.
김철수의 졸업을 위해 주인을 찾아줘야 하는데 어떻게 찾을 수 있을까요?

저는 소프트웨어학부실 앞에서 주웠으니 학부실, 컴퓨터공학과, 대학교 이런식으로 범위를 넓혀서 주인을 찾을 것 같습니다.

위 코드는 소프트웨어학부실에 usb를 잃어버린 김철수가 아닐지 짐작하는 코드입니다.

그런데 만약 없다면요?
그럼 컴퓨터공학과로 범위를 넓힙니다.
software() 안의 someone을 주석처리 하면
혹시 컴퓨터공학과 김철수꺼? 로 바뀌게 됩니다.

컴퓨터공학과에도 없어서 computer() 안의 someone을 주석처리 하면
대학교 김철수로 짐작할 수 있습니다.

이처럼 스코프가 중첩된 것을 스코프 체이닝이라 하고, 스코프 체이닝을 따라 상위로 이동하며 참조할 변수를 찾습니다.

해답1. 스코프 체이닝을 통해 상위로 올라가며 참조할 변수를 찾습니다. 따라서 변수 이름이 같아도 문제 없습니다.

의문2. 외부 변수를 참조한다는 클로저는 알겠는데 이걸 왜쓰는거에요?

private으로 이해하는 클로저

자바스크립트는 클래스 기반이 아닌 프로토타입 기반 언어라서 private 키워드를 사용할 수 없습니다.
private 키워드를 사용하면 데이터를 은닉하여 외부에서 변수에 대한 접근이 불가능합니다.
이러한 데이터 은닉을 클로저가 가능하게 합니다.

해답2. 클로저는 데이터 은닉을 위해 사용한다.

의문3. 그럼 데이터 은닉은 왜 하나요?

해답3. 데이터를 은닉하게 되면 외부에서 내부 상태를 무분별하게 변경할 수 없습니다. 내가 외부에서 건드린 코드 때문에 동료가 작성한 내부 코드가 에러날 수 있습니다.

클로저의 사용 예시

function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name); // Mozilla
  }
  return displayName;
}

let myFunc = makeFunc();
myFunc();

클로저는 위 코드처럼 은닉 데이터를 사용하는 로직을 함수로 작성하고, 해당 함수를 반환하여 외부에서 데이터에 직접적으로 접근할 수 없고 허가된 방법으로만 접근하도록 합니다.

클로저의 장점

  • 데이터를 은닉할 수 있다.
  • 허가된 특정 방법(함수)으로만 내부 데이터 접근을 허용한다.
  • 현재 상태를 기억하고 바뀐 최신 상태를 유지할 수 있다.

클로저가 잘못 사용되는 경우

let arr = [];
let i;
for (i=0; i<5; i++){
     arr[i] = function(){
          return i;
     }
}
console.log(arr.map(element => element())) // [5, 5, 5, 5, 5]

스코프를 잘못 이용한 반복문이 대표적입니다.
위 코드는 변수 i를 전역 스코프로 생성했습니다.

이 경우 for문이 끝나고 i는 마지막 값 5로 생존하며, arr의 각 요소에 들어있는 함수(외부의 변수 i를 참조하여 반환하는 함수)가 5를 반환합니다.

해결 방법

  1. 블록 스코프로 수정
    • 위 코드를 for (let i = 0...) 과 같이 바꾸면 변수 i의 스코프가 전역에서 for문 안의 블록 스코프로 바뀌어, 매 for문 루프마다 다른 스코프를 생성하게 됩니다.
      따라서 [1, 2, 3, 4, 5]를 출력할 수 있습니다.
  2. 즉시실행함수
    • 선언과 동시에 실행되는 함수를 뜻합니다.
      => 코드가 실행될 때 딱 한번만 실행되어서 초기화 기능으로 사용할 수 있습니다.
    • 함수기 때문에 독립적인 스코프를 가집니다.
      => 전역에서 즉시실행함수를 사용하면 전역이 아니게 됩니다.

클로저의 깊은 정의

: 함수가 선언될 때의 스코프 환경 정보를 기억해서, 외부에서 호출되어도 내부 데이터에 접근할 수 있는 것

정리

의식의 흐름대로 클로저가 어떻게 외부 변수를 참조하는지,
왜 쓰는지 (장점이 무엇인지),
사용 예시와 잘못 사용되는 예시를 공부했습니다.

클로저를 한번에 이해하지 못한것은 스코프 관련 개념이 미흡했기 때문이었습니다.
관련 개념들을 함께 공부하면서 연관성이나 특징들을 공부해야겠습니다.

참고

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
https://poiemaweb.com/js-scope
https://poiemaweb.com/js-closure

수정

2022.03.31. 클로저 예제 코드 수정

profile
이해하는 개발자를 희망하는 고광필입니다.

3개의 댓글

comment-user-thumbnail
2022년 3월 23일

ㅋㅋㅋ 예시 재미있게 잘 봤습니다 ☺️

답글 달기
comment-user-thumbnail
2022년 3월 23일

비유가 찰떡이에요!! 😃

답글 달기
comment-user-thumbnail
2022년 3월 23일

ㅋㅋㅋㅋ 컴퓨터 공학과 sw 전공 김철수 덕분에 스코프체이닝 잘 알아갑니다.

답글 달기