- 클로저 함수의 정의와 특징에 대해서 이해할 수 있다.
- 클로저가 갖는 스코프 범위를 이해할 수 있다.
- 클로저를 이용해 유용하게 쓰이는 몇 가지 패턴을 이해할 수 있다.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). - mdn (2023)
➡ 클로저는 함수와 그 함수가 접근할 수 있는 변수의 조합
// 클로저 사용 패턴 1 function outerFn() { const outerFnVar = 'outer 함수 내의 변수'; const innerFn = function() { return 'innerFn은 ' + outerFnVar + '에 접근할 수 있습니다.'; } return innerFn; }
- 실제 클로저를 사용할 때는
outerFn
,innerFn
처럼 함수가 함수를 리턴하는 패턴을 자주 사용하고,outerFn
을 외부 함수,innerFn
을 내부 함수라고 부른다.
function outerFn() { const innerFn = function() { const message = 'outerFn은 message에 접근할 수 있습니다.'; } return message; } const message = outerFn();
- 이 경우에는
outerFn
은 ,message
를 리턴하려고 하지만,innerFn
의 스코프,
즉 내부 함수에 있기 때문에 접근할 수 없어,ReferenceError
가 난다.
클로저를 응용하면, 함수 내부에 선언한 변수에 접근할 수 있고, 매개변수에도 접근할 수 있다
function createFoodRecipe (foodName) { let ingredient1 = '탄산수'; let ingredient2 = '위스키'; const getFoodRecipe = function () { return `${ingredient1} + ${ingredient2} = ${foodName}!`; } return getFoodRecipe; } const recipe = createFoodRecipe('하이볼'); recipe(); // '탄산수 + 위스키 = 하이볼!'
이 때,
createFoodRecipe('하이볼')
으로 전달된 문자열 '하이볼' 은recipe
함수 호출 시 계속 재사용 할 수 있다.
➡ createFoodRecipe 가 문자열 ‘하이볼’ 을 “보존”하고 있기 때문
function createFoodRecipe (foodName) { const getFoodRecipe = function (ingredient1, ingredient2) { return `${ingredient1} + ${ingredient2} = ${foodName}!`; } return getFoodRecipe; } const highballRecipe = createFoodRecipe('하이볼'); highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!' highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!' highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
➡
highballRecipe
함수는 문자열 ‘하이볼’ 을 보존하고 있어서 전달인자를 추가로 전달할 필요가 없고, 다양한 하이볼 레시피를 하나의 함수로 제작할 수 있다
여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위
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
sum
과currySum
이 같은 값을 리턴하기 위해서는currySum
함수에서 리턴한 함수에 두 번째 전달인자20
을 전달하여 호출하면 된다.- 이렇게 커링을 활용한
currySum
과 같은 함수를 커링 함수라고 부르기도 한다.
function makePancake(powder) { return function (sugar) { return function (pan) { return `팬케이크 완성! 재료: ${powder}, ${sugar} 조리도구: ${pan}`; } } } const addSugar = makePancake('팬케이크가루'); const cookPancake = addSugar('백설탕'); const morningPancake = cookPancake('후라이팬'); // 잠깐 낮잠 자고 일어나서 ... const lunchPancake = cookPancake('후라이팬'); //아래의 일반 함수는 위 커링 함수와 달리 //잠깐 낮잠 자고 일어나서 만든 팬케이크를 표현할 방법이 없다. function makePancakeAtOnce (powder, sugar, pan) { return `팬케이크 완성! 재료: ${powder}, ${sugar} 조리도구: ${pan}`; } const morningPancake = makePancakeAtOnce('팬케이크가루', '백설탕', '후라이팬')
✨이와 같이 커링은 함수의 일부만 호출하거나,
일부 프로세스가 완료된 상태를 저장하기에 용이하다!!✨
JavaScript에 class 키워드가 없던 시절 모듈 패턴을 구현하기 위해서 클로저를 사용
모듈이란?
하나의 기능을 온전히 수행하기 위한 모든 코드를 가지고 있는 코드 모음으로, 하나의 단위로서 역할한다.
모듈은 다른 모듈에 의존적이지 않고 독립적이어야 한다.
즉, 기능 수행을 위한 모든 기능을 갖추고 있어야 하고, 외부 코드 실행을 통해서 모듈의 속성이 훼손 받지 않아야 한다는 것.
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
displayValue
는makeCalculator
의 코드 블록 외에 다른 곳에서는 접근이 불가능하지만,cal
의 메서드는 모두 클로저의 함수로서displayValue
에 접근 가능
- 이렇게 데이터를 다른 코드 실행으로부터 보호하는 개념을 정보 은닉(information hiding)이라고 한다.
- 이는 캡슐화(encapsulation)의 큰 특징!
- ✨이와 같이 클로저는 특정 데이터를 다른 코드의 실행으로부터 보호할 때 용이!✨
근데 일단 모든 함수는 다 클로저이다.
주변 환경에 참조할 걸 가지고 있다면 일단 클로저(즉, 모든 함수)
그치만 '클로저'라는 개념을 주로 언급할때는,
외부함수 안에 내부함수가 있는 패턴일 때 사용한다.
즉, 실질적으로 코딩할 때 클로저를 활용하는 패턴이 외부 & 내부 함수 패턴
클로저는 대학생 수업과정에서는 배운 적 없는 개념이라 꽤 생소했다. 하지만 이런 유용한 개념을 모른채로 졸업 프로젝트를 만들었다니! 너무 아쉬웠다. 아직까지는 쉽게 쉽게 활용할 만큼 익숙한 개념은 아니지만, 얼른 연습문제 같은 것들은 많이 접하면서 익숙하게끔 해야겠다! 복습도 꾸준히 철저히 하기...!