Closure)
클로저
는 함수와 함수가 선언된 어휘적 환경의 조합Lexical scoping
)function init() {
let name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
function displayName() { // displayName() 은 내부 함수이며, 클로저다.
alert(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init()
은 지역 변수 name
과 함수 displayName()
을 생성
displayName()
은 init()
안에 정의된 내부 함수이며 init()
함수 본문에서만 사용할 수 있다.
여기서 주의할 점은 displayName()
내부엔 자신만의 지역 변수가 없다는 점이다.
displayName()
함수 내에서 선언된 변수가 없다.그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName()
역시 부모 함수 init()
에서 선언된 변수 name
에 접근할 수 있다.
만약 displayName()
가 자신만의 name
변수를 가지고 있었다면, name
대신 this.name
을 사용했을 것이다.
displayName()
함수 내의 alert()
문이 부모 함수에서 정의한 변수 name
의 값을 성공적으로 출력한다.여기서 "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다.
스코프
)에서 선언한 변수에도 접근할 수 있다.클로저를 어떻게 구분할까?
const globalVar = '전역 변수';
function outerFn() {
const outerFnVar = 'outer 함수 내의 변수';
const innerFn = function() {
return 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.';
}
return innerFn;
}
함수 outerFn에서는 변수 globalVar에 접근할 수 있다.
함수 innerFn에서는 변수 globalVar와 함수 outerFn 내부의 outerFnVar에 접근할 수 있다.
클로저는 왜 중요할까?
클로저의 함수는 어디에서 호출되느냐와 무관하게 선언된 함수 주변 환경에 따라 접근할 수 있는 변수가 정해지기 때문.
const globalVar = '전역 변수';
function outerFn() {
const outerFnVar = 'outer 함수 내의 변수';
const innerFn = function() {
return 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.';
}
return innerFn;
}
const innerFnOnGlobal = outerFn();
const message = innerFnOnGlobal();
console.log(message); // innerFn에서는 변수 globalVar와 함수 outerFn 내부의 outerFnVar에 접근할 수 있습니다.
innerFnOnGlobal
은 outerFn
내부의 innerFn
의 주소값을 가진다.
그다음 줄에서 innerFnOnGlobal
을 호출.
이때, innerFnOnGlobal
은 innerFn
밖에 있기 때문에 outerFnVar
에는 접근하지 못한다고 생각할 수 있는데, 실제 접근할 수 있다.
innerFnOnGlobal
은outerFnVar
에 접근 못하지 않나요?
내부 스코프에 접근 할 수 없으니깐?
왜 접근할 수 있을까?
innerFn
함수가 최초 선언되었던 환경에서는 outerFnVar
에 접근할 수 있기 때문innerFnOnGlobal
은 innerFn
의 주소값을 가지고 있고, innerFn
은 클로저로서 outerFnVar
에 접근할 수 있기 때문.위에서 설명한 어휘적 환경(Lexical Environment)
을 뜻한다.
function makeAdder(x) {
let y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
let add5 = makeAdder(5);
let add10 = makeAdder(10);
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
이 예제에서 단일 인자 x
를 받아서 새 함수를 반환하는 함수 makeAdder(x)
를 정의했다.
반환되는 함수는 단일인자 z
를 받아서 x
와 y
와 z
의 합을 반환한다.
클로저를 활용하면 클로저의 함수 내에 데이터를 보존해 두고 사용할 수 있다
일반적으로 함수 내부에 선언한 변수에는 접근할 수 없다.
매개변수도 마찬가지다.
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
함수
function createFoodRecipe (foodName) {
const getFoodRecipe = function (ingredient1, ingredient2) {
return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}
return getFoodRecipe;
}
const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
코드에서는 getFoodRecipe
가 클로저로서 foodName
, ingredient1
, ingredient2
에 접근할 수 있다.
이때, createFoodRecipe('하이볼')
으로 전달된 문자열 '하이볼'
은 recipe
함수 호출 시 계속 재사용할 수 있다.
createFoodRecipe 가 문자열 ‘하이볼’을 “보존”하고 있기 때문.
커링은 여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위
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
함수는 두 전달인자(10, 20)를 덧셈하는 함수
currySum
은 첫 번째 전달인자 10을 리턴하는 함수로 전달
sum
과 currySum
이 같은 값을 리턴하기 위해서는 currySum
함수에서 리턴한 함수에 두 번째 전달인자 20을 전달하여 호출
이렇게 커링을 활용한 currySum
과 같은 함수를 커링 함수
- 커링은 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이
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
코드는 계산기의 최소한의 기능을 모듈 패턴으로 구현
- 클로저는 특정 데이터를 다른 코드의 실행으로부터 보호해야 할 때 용이