본문 바로가기

[JS] 클로저 활용 (함수, 커링, 모듈)

[JS] 클로저 활용 (함수, 커링, 모듈)

📝 Summary

  • 클로저는 주로 데이터를 보존하는 함수, 커링, 모듈 패턴으로 활용한다.
  • 클로저를 이용하면 특정 함수가 데이터를 보존할 수 있다.
  • 커링을 이용하면 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장할 수 있다.
  • 모듈 패턴을 이용하면 특정 데이터를 다른 코드의 실행으로부터 보호할 수 있다.

 


 

클로저의 특징을 활용한 사례에는 대표적으로 함수, 커링, 모듈 패턴이 있다. 

 

1. 데이터를 보존하는 함수

 

클로저를 활용하면 클로저의 함수 내에 데이터를 보존해두고 사용할 수 있다.

일반적으로 함수 내부에 선언한 변수에는 접근할 수 없다. 매개변수도 마찬가지이다.

 

function getFoodRecipe (foodName) {
  let ingredient1, ingredient2;
  return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}

console.log(ingredient1); // ReferenceError: ingredient1 is not defined (함수 내부에 선언한 변수에 접근 불가)
console.log(foodName); // ReferenceError: foodName is not defined (매개변수에 접근 불가)

 

클로저를 응용하면, 함수 내부에 선언한 변수에 접근할 수 있고, 매개변수에도 접근할 수 있다. 기존 함수 내부에서 새로운 함수를 리턴하면 클로저로서 활용할 수 있다. 즉, 리턴한 새로운 함수의 클로저에 데이터가 보존된다.

 

데이터를 보존하는 함수인 레시피를 제작하는 createFoodRecipe 함수를 만들어보자.

 

아래 코드에서는 getFoodRecipe가 클로저로서 foodName, ingredient1, ingredient2에 접근할 수 있다.

이 때, createFoodRecipe('하이볼') 으로 전달된 문자열 '하이볼' 은 recipe 함수 호출 시 계속 재사용 할 수 있다. createFoodRecipe 가 문자열 ‘하이볼’ 을 “보존”하고 있기 때문이다.

 

function createFoodRecipe (foodName) {
  let ingredient1 = '탄산수';
  let ingredient2 = '위스키';
  const getFoodRecipe = function () {
    return `${ingredient1} + ${ingredient2} = ${foodName}!`;
  }
  return getFoodRecipe;
}

const recipe = createFoodRecipe('하이볼');
recipe(); // '탄산수 + 위스키 = 하이볼!'

 

이를 더 잘 응용하기 위해 getFoodRecipe의 매개변수도 활용할 수 있게 코드를 아래와 같이 변경해보자.

 

function createFoodRecipe (foodName) {
  const getFoodRecipe = function (ingredient1, ingredient2) {
    return `${ingredient1} + ${ingredient2} = ${foodName}!`;
  }
  return getFoodRecipe;
}

const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'

 

highballRecipe 함수는 문자열 ‘하이볼’ 을 보존하고 있어서 전달인자를 추가로 전달할 필요가 없고, 다양한 하이볼 레시피를 하나의 함수로 제작할 수 있다.

 


 

2. 커링

 

커링은 여러 전달인자를 가진 함수함수를 연속적으로 리턴하는 함수로 변경하는 행위이다. 

 

아래의 sum 함수는 두 전달인자(10, 20)를 덧셈하는 함수고, currySum은 첫 번째 전달인자 10을 리턴하는 함수로 전달해준다. sum과 currySum이 같은 값을 리턴하기 위해서는 currySum 함수에서 리턴한 함수에 두 번째 전달인자 20을 전달하여 호출하면 된다. 이렇게 커링을 활용한 currySum과 같은 함수를 커링 함수라고 부른다.

 

function sum(a, b) {
  return a + b;
}

function currySum(a) {
	return function(b) {
		return a + b;
	};
}

console.log(sum(10, 20) === currySum(10)(20)) // true

 

 

언뜻 봐서는 일반 함수와 커링 함수의 차이가 느껴지지 않지만, 커링은 전체 프로세스의 일정 부분까지만 실행하는 경우 유용하다.

 

아래 makePancake 함수는 팬케이크 제작 과정을 커링 함수로 만들었다. 팬케이크는 팬케이크 믹스를 만들어두었다가, 나중에 다시 만들 수도 있다. 반면, 커링이 적용되지 않은 makePancakeAtOnce 함수는 일부 조리 과정이 생략된 모습을 표현할 수 없다.

 

function makePancake(powder) {
  return function (sugar) {
		return function (pan) {
			return `팬케이크 완성! 재료: ${powder}, ${suger} 조리도구: ${pan}`;
		}
	}
}

const addSugar = makePancake('팬케이크가루');
const cookPancake = addSugar('백설탕');
const morningPancake = cookPancake('후라이팬');

// 잠깐 낮잠 자고 일어나서 ...
const lunchPancake = cookPancake('후라이팬');

 

function makePancakeAtOnce (powder, sugar, pan) {
  return `팬케이크 완성! 재료: ${powder}, ${suger} 조리도구: ${pan}`;
}

const morningPancake = makePancakeAtOnce('팬케이크가루', '백설탕', '후라이팬')
// 잠깐 낮잠 자고 일어나서 만든 팬케이크를 표현할 방법이 없다.

 

 

이와 같이 커링은 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이하다.

 


 

3. 모듈 패턴

 

JavaScript에 class 키워드가 없던 시절 모듈 패턴을 구현하기 위해서 클로저를 사용했다. 모듈은 하나의 기능을 온전히 수행하기 위한 모든 코드를 가지고 있는 코드 모음으로, 하나의 단위로서 역할을 한다. 모듈은 다른 모듈에 의존적이지 않고 독립적이어야 한다.

 

다른 모듈에 의존적이지 않고 독립적이라면 기능 수행을 위한 모든 기능을 갖추고 있어야 하고, 또한 외부 코드 실행을 통해서 모듈의 속성이 훼손 받지 않아야 한다. 모듈의 속성을 꼭 변경해야 할 필요가 있는 경우에는 제한적으로 노출된 인터페이스에 의해 변경되어야한다. 이 특징은 클로저와 유사하다.

 

아래 코드는 계산기의 최소한의 기능을 모듈 패턴으로 구현했다. displayValue는 makeCalculator의 코드 블록 외에 다른 곳에서는 접근이 불가능하지만, cal의 메서드는 모두 클로저의 함수로서 displayValue에 접근할 수 있다. 이렇게 데이터를 다른 코드 실행으로부터 보호하는 개념을 정보 은닉(information hiding)이라고 한다. 이는 캡슐화(encapsulation)의 큰 특징이기도 하다.

 

function makeCalculator() {
  let displayValue = 0;

  return {
    add: function(num) {
      displayValue = displayValue + num;
    },
    subtract: function(num) {
      displayValue = displayValue - num;
    },
    multiply: function(num) {
      displayValue = displayValue * num;
    },
    divide: function(num) {
      displayValue = displayValue / num;
    },
    reset: function() {
      displayValue = 0;
    },
    display: function() {
      return displayValue
    }
  }
}

const cal = makeCalculator();
cal.display(); // 0
cal.add(1);
cal.display(); // 1
console.log(displayValue) // ReferenceError: displayValue is not defined

 

 

이와 같이 클로저는 특정 데이터를 다른 코드의 실행으로부터 보호해야 할 때 용이하다.

728x90

'FE > JavaScript' 카테고리의 다른 글

[JS] 구조분해할당  (0) 2023.03.03
[JS] Scope 문제 풀이 - 다양한 패턴  (0) 2023.03.03
[JS] 클로저  (0) 2023.03.03
[JS] 변수 선언 시 주의할 점 (let, const 쓰기) ... Var bye!!!!  (0) 2023.03.03
[JS] 스코프(Scope)  (0) 2023.03.02
⬆︎