Javascript Closure? 클로저? 그리고 클로저 사용해 module을 만들기!

임택·2019년 4월 14일
8

Javascript

목록 보기
3/9
post-thumbnail

클로저

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
profile
캬-!

5개의 댓글

comment-user-thumbnail
2019년 11월 7일

예제의 의도를 파악하기 너무 어려워요 ㅠㅠ
뭐가 클로저 함수인지 프라이빗 파라메터 인지 예제에서 구분할수가 없네요. 설명이나 보면 이해가 될만한 문서 추천좀 부탁드려도 될까요

4개의 답글

관련 채용 정보