클로저

function outer(param){
  // param을 넣고 말고는 중요한게 아니라 나중에 inner함수를 실행할 때 
  // outerParam을 불러올 수 있다는 점이 중요
  const outerParam = `This is how ${param} works!`; 
  function inner() { // 이 inner 함수가 생성된 환경의 스코프는 outer함수
    // outer함수의 스코프를 inner함수가 기억하고 있어서 outerParam을 호출가능
    console.log(outerParam); 
  }
  // outer 함수는 inner함수를 리턴해줌
  return inner;
}

// 이 부분을 이해하는게 중요하다고 생각 함
// outer함수 실행이 끝나면 outer함수 내부 변수 outerParam은 함수 내부에서 선언이 되었기 때문에
// 더 이상 호출할 방법이 없어야 정상이지만  (Javascript context를 공부하면 좋습니다.)
// outer함수 내부에 선언된 inner함수는 함수가 생성된 환경의 스코프
// 즉, outer함 수의 스코프를 기억하고 있기 때문에 
// outer 내부에서 생성된 비공개 변수 outerParam 호출이 가능하게 됩니다.
const closure = outer('CLOSURE'); // outer함수 실행결과로 inner함수를 받아 closure변수에 할당

// outer함수 실행이 완료되어 사라졌어야할 outerParam을 inner함수가 참조해 실행
// 이제서야 outer함수 컨텍스트가 종료
closure(); // This is how CLOSURE works!
  • 클로저란?
    • 클로저는 함수가 선언된 환경의 (렉시컬) 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기술
    • 함수 안의 함수
    • 특징
      • inner 함수는 outer 함수의 지역 변수 참고 가능
      • outer 함수가 실행되어 소멸되어도 inner 함수는 outer 함수의 지역변수 참고 가능
      • private 변수 선언 가능

유명한 Counter 예제


// counter는 outer 함수
// changeCount는 inner함수
// 객체를 리턴하고 있고 객체 안에는 increase, decrease, show와 같은 inner함수들을 저장
const counter = function() {
  let count = 0;
  function changeCount(number) {
    count += number;
  }
  return {
    increase: function() {
      changeCount(1);
    },
    decrease: function() {
      changeCount(-1);
    },
    show: function() {
      alert(count);
    }
  }
};
// outer함수 counter를 실행하면 outer함수 스코프를 기억하고 있는 클로저들이 담긴 객체를 반환
// counterClosure는 counter함수 내부에 정의된 count나 changeCount에 접근 가능
const counterClosure = counter(); 
counterClosure.increase(); // 
counterClosure.show(); // 1
counterClosure.decrease();
counterClosure.show(); // 0

클로저를 사용해 private한 모듈을 만들어 보자!


아래 예제보다 count 예제를 보면 좀 더 쉽게 이해하기 쉽습니다.
아래 예제는 제가 이런 식으로 사용하면 될 것 같다 생각하면서 작성해 놓은 예제 입니다. (정리겸...)

// 이 함수 안에서 선언된 변수들은 다 private(비공개) 변수들입니다.
function getPrivate(){
  const log = console.log;
  const token = 'AaddfdBF343DVDFD';
  let API = {};

  function check(reqToken) {
      return token === reqToken;
  }

  API.register = (id, pass) => {
      log(`register! id: ${id} pass: ${pass} with ${token}`);
  };

  API.login = (id, pass) => {
      log('login!');
  };

  // 위처럼 하나씩 적어도 되고 
  //아니면 이렇게 객체에 하나씩 담아주어도 됩니다.
  API = {
    // spread operater를 사용한 객체 복사
    ...API,
    update: (id, pass) => {
        log('update!');
    },
    delete: (id) => {
        log(`delete! ${id}`);
    }
  };

  // Object.assign()을 사용한 객체 복사, 이건 그냥 해봤습니다..
  API = Object.assign({}, API, { check: check });  
  return API;
}
// Error! API is not defined! 
// getPrivate함수 안에 선언된 API변수를 전역에서 호출 가능한지 확인
API.register('id', 'pass'); 
// API가 api 변수에 할당 된다. (정확히는 API가 가리키는 객체의 주소가)
const api = getPrivate();
// 아래 register함수(closure)는 getPrivate함수 내에서 정의된 token변수에 접근 가능
api.register('IDDDD', 'PASSS'); // register! id: IDDDD padd: PASSS with AaddfdBF343DVDFD

클로저 + 싱글톤 private한 모듈

  • 위 예제에서는 getPrivate함수를 선언하고 여러번 호출 가능한 방식
  • 위 예제와 다르게 이번에 싱글톤을 붙인 이유는 즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)을 사용해 global하게 getPrivate함수를 선언하지 않았기 때문이다.
// 즉시실행함수로 실행한 함수는 글로벌 네임스페이스에 변수(getPrivate)가 추가되지 않는다.
// 그리고 getPrivate라고 함수명을 붙일 필요는 없다.
// (function() { 라고 작성해도 무방
//
const api = (function getPrivate() {
  const log = console.log;
  const token = 'AaddfdBF343DVDFD';
  let API = {};

  function check(reqToken) {
      return token === reqToken;
  }

  API.register = (id, pass) => {
      log(`register! id: ${id} pass: ${pass} with ${token}`);
  };

  API.login = (id, pass) => {
      log('login!');
  };

  // spread operater를 사용한 객체 복사
  API = {
      ...API,
    update: (id, pass) => {
        log('update!');
    },
    delete: (id) => {
        log(`delete! ${id}`);
    }
  };

  // Object.assign()을 사용한 객체 복사
  API = Object.assign({}, API, { check: check });  
  return API;
})();

api.register('IDDDD', 'PASSS'); // register! id: IDDDD padd: PASSS

이렇게 모듈을 만들면서 closure 기술? 방식으로 만드는 이유는 전역에 같은 이름을 가진 변수나 함수가 선언되어 있거나 선언될 수 있는 걸 방지해 이상없이 코드가 동작하도록 만들기 위함이다.

아래는 공부하면서 같이보면 좋아 보여서 링크들 남겨 봅니다.

위 예제는 공부하면서 익힌 문법들을 사용하면서 참고용으로 작성한 예제여서 아래 다시 정리 합니다.

const api = (function() {
  // 내부 변수들: private 변수들
  const log = console.log;
  const token = 'AaddfdBF343DVDFD';
  let API = {};

  // 내부 함수: private함수
  function checkToken() {
      log(`check token: ${token}`);
  }

  API.check = function(reqToken) {
    checkToken(); // 내부에만 정의된 checkToken을 실행할 수 있다.
      return token === reqToken;
  }
  API.register = (id, pass) => {
    checkToken();
    // 내부에 정의된 token 변수에 접근 가능
    log(`register! id: ${id} pass: ${pass} with ${token}`);
  };
  API.login = (id, pass) => {
    checkToken();
      log('login!');
  };
  API.update = (id, pass) => {
    checkToken();
    log('update!');
  };
  API.delete = (id) => {
    checkToken();
    log(`delete! ${id}`);
  };

  return API; // inner 함수를 가진 객체 리턴
})();

api.register('IDDDD', 'PASSS'); // register! id: IDDDD padd: PASSS