- Lexical Environment(렉시컬 환경)
- Lexical Environment(렉시컬 환경)
- 변수와 함수가 선언된 시점의 문맥(환경)을 나타내는 객체
- 자바스크립트는 코드를 실행할 때 각 함수와 블록에 대해 렉시컬 환경을 만든다.
- Lexical Environment(렉시컬 환경)의 구성
- Environment Record(환경 레코드)
- 현재 실행중인 함수나 블록 내에 선언된 모든 변수와 함수 정보를 저장함.
- Outer Lexical Environment Reference(외부 렉시컬 환경 참조)
- 현재 렉시컬 환경의 바로 바깥 환경을 참조, 이 참조를 통해 함수는 자신의 스코프 테인을 따라 외부 변수에 접근할 수 있음
렉시컬 환경과 스코프는 같은 의미인가?
- 스코프 (개념)변수가 어디까지 접근이 가능한지를 정의하는 규칙
- 코드에서 변수를 사용할 수 있는 범위를 뜻함
- 논리적 개념으로 사람이 이해하는 규칙
- 렉시컬 환경 (실체)코드 실행 중에 생성되는 객체라고 생각하면 쉬움변수 이름(키)과 값(밸류)를 매핑하고 외부 참조를 포함.
- 실제로 변수를 저장하고, 스코프를 구현하기 위한 내부 매커니즘
- 스코프를 실제로 구현한 엔진 내부의 데이터 구조
스코프 vs 렉시컬 환경스코프 렉시컬 환경
| "어떤 변수가 어디까지 접근 가능한지"라는 규칙을 의미 | 스코프를 구현하기 위한 실제 데이터 구조 |
| 개발자가 느끼는 "논리적 범위" | 자바스크립트 엔진이 관리하는 "내부 구조" |
| 실행 시 보이지 않는 개념 | 실행 중 만들어지는 객체 |
01. 클로저의 의미 및 원리 이해
의미
함수가 자신이 선언된 렉시컬 환경을 기억하는 것을 의미.
즉, 함수가 실행될 때 선언 당시의 렉시컬 환경이 함께 저장되고 유지.
클로저는 언제 발생 되나요?
- 함수가 다른 함수 안에서 정의 될 때
- 함수가 중첩 구조를 가질 때 가능.
- 내부 함수가 외부 함수의 변수에 접근할 때
- 내부 함수가 외부 함수의 렉시컬 환경을 기억함.
- 외부 함수가 실행을 마친 후에도 내부 함수가 호출될 수 있는 상태여야만 함
- 이 상태가 가능한 이유는 내부 함수가 반환되거나, 다른 곳에서 참조 되고 있기 때문
여러개의 함수가 반환될 때 클로저 예시
function createCounter() {
let count = 0; // 외부 변수 (private 변수처럼 동작)
return {
increment: function () {
count++;
console.log(`현재 카운트: ${count}`);
},
decrement: function () {
count--;
console.log(`현재 카운트: ${count}`);
},
reset: function () {
count = 0;
console.log("카운트가 초기화되었습니다.");
},
};
}
const counter = createCounter();
counter.increment(); // 현재 카운트: 1
counter.increment(); // 현재 카운트: 2
counter.decrement(); // 현재 카운트: 1
counter.reset(); // 카운트가 초기화되었습니다.
어떻게 클로저가 발생하나?
- 외부 함수(createCounter) 실행:
- count라는 변수가 외부 함수의 렉시컬 환경에 저장됨.
- 이 count는 외부에선 직접 접근 불가.
- 내부 함수 반환:
- 반환된 객체 안의 increment와 decrement는 count를 사용하는 함수들임.
- 이 함수들은 외부 함수의 렉시컬 환경(여기선 count)을 기억함.
- createCounter 종료:
- createCounter가 종료되었지만, 반환된 함수들이 여전히 count를 사용할 수 있음.
반환값이 없을 때 클로저가 발생하는 경우 (이벤트 핸들러)
//이벤트 핸들러에서 클로저
function setupButton() {
let count = 0; // 외부 함수의 변수
// 내부 함수
document.getElementById("myButton")
.addEventListener("click", function () {
count++; // 외부 변수를 참조
console.log(`버튼 클릭 횟수: ${count}`);
});
}
// 함수 호출
setupButton(); // 1
어떻게 클로저가 발생하나?
- 외부 함수 실행 (setupButton):
- count 변수가 생성되고, 값은 0이야.
- setupButton은 실행을 끝내지만, 반환값은 없음.
- 내부 함수 정의:
- 이벤트 핸들러로 전달된 익명 함수(function () {})가 내부 함수로 동작.
- 이 함수는 외부 함수의 변수 count를 참조.
- 클로저 발생:
- 이벤트 핸들러가 실행될 때마다, 내부 함수는 외부 함수의 렉시컬 환경(count)을 참조.
- setupButton 함수는 이미 종료되었지만, 렉시컬 환경은 내부 함수가 유지하고 있다.
- 반환값이 없더라도, 외부 변수와의 연결 고리가 유지되면 클로저가 발생할 수 있다.
- 내부 함수가 외부 함수의 변수를 참조하고 있는 한, 자바스크립트는 그 렉시컬 환경을 가비지 컬렉터로부터 보호한다.
반환값이 없을 때 클로저가 발생하는 경우 (카운터 setInterval 사용시)
function startCountdown() {
let count = 5;
const timer = setInterval(function () {
if (count > 0) {
console.log(`남은 시간: ${count}`);
count--;
} else {
console.log("타이머 종료!");
clearInterval(timer); // 타이머 종료
}
}, 1000);
}
// 함수 호출
startCountdown();
어떻게 클로저가 발생하나?
- 외부 함수 실행 (startCountdown):
- count 변수가 생성되고, 초기값은 5.
- 반환값 없이, setInterval로 내부 함수가 실행될 준비를 함.
- 내부 함수 정의:
- setInterval로 전달된 익명 함수는 count를 참조.
- 외부 함수(startCountdown)가 종료된 후에도 이 내부 함수는 계속 호출됨.
- 클로저 발생:
- 내부 함수는 외부 변수 count를 계속 읽고 쓰며, 렉시컬 환경을 유지.
결론
- 반환값이 없어도, 내부 함수가 외부 변수를 참조하기만 하면 클로저가 발생
- 중요한 점은 렉시컬 환경(Lexical Environment)이 유지되고 있느냐의 여부가 중요함
02. 클로저와 메모리 관리
클로저는 렉시컬 환경을 계속 유지하기 때문에, 외부 함수가 종료된 후에도 변수들이 메모리에서 사라지지 않는다. 이게 잘못 관리 되면 불필요한 메모리 점유로 이어질 수 있다.
클로저의 메모리 관리 팁
- 참조를 제거
- 더 이상 필요 없는 변수나 데이터를 null로 설정해 참조를 끊어야 한다.
- 이벤트 핸들러를 반드시 제거
- 이벤트 핸들러를 설정하면 클로저가 생성되기 때문에, removeEventListener로 제거해야 함.
- 타이머나 비동기 함수 종료를 잊지 말라.
- setInterval이나 setTimeout으로 만든 함수는 사용 후 clearInterval 또는 clearTimeout으로 정리.
- 디버깅 툴로 메모리 누수 확인
- 브라우저 개발자 도구의 메모리 탭에서 객체가 예상보다 오래 유지되는지 확인 가능.
결론
- 클로저는 메모리를 적극적으로 사용하기 때문에 잘못 관리하면 메모리 누수나 성능 저하가 생길 수 있음.
- 하지만 적절히 사용하면 강력한 도구니까 참조를 끊거나 리소스를 관리하는 습관을 들이면 됨.
03. 클로저의 활용 사례
클로저에는 다양한 곳에서 광범위 하게 활용되어 여러가지 상황이 존재함.
5-3-1 콜백 함수 내부에서 외부 데이터를 사용
- printArrayElements()는 뭘 하는 함수야?배열의 각 요소를 순차적으로 출력하기 위해 forEach 메서드를 사용하고,
- setTimeout을 이용해서 출력이 1초마다 이루어지도록 설정하고 있다.
- 배열을 받아서 그 배열의 각 요소를 1초 간격으로 출력하는 함수
- setTimeout는 뭐지?예를 들어, 어떤 작업을 일정 시간이 지난 후에 하고 싶을 때 사용
- 지정된 시간(밀리초) 후에 주어진 콜백 함수를 한 번 실행하는 함수
function printArrayElements(array) {
array.forEach((element, index) => {
setTimeout(() => {
console.log(`Index ${index}: ${element}`);
}, index * 1000);
});
}
// 실행
printArrayElements(["a", "b", "c"]);
클로저가 어떻게 발생했나?
- 외부 변수 참조:
- 콜백 함수 setTimeout 내부에서 index와 element를 참조.
- 이 index와 element는 외부 함수 forEach의 스코프에 있는 변수.
- 렉시컬 환경 유지:
- setTimeout이 실행될 때마다 콜백 함수가 외부 스코프에 접근해서 변수 값을 가져와.
- 비동기로 실행되더라도, 각 콜백은 자신만의 렉시컬 환경을 유지하고 있음.
- 뭐가 비동기로 실행 되는데?setTimeout은 비동기적으로 실행 되는 함수로, 지정한 시간 이후에 콜백 함수를 실행하도록 예약한다.
- setTimeout 자체는 즉시 실행되지 않고, 지정한 시간이 지난 뒤에 실행될 콜백을 큐에 넣어두기 때문에 비동기적으로 동작한다.
- 이 코드에서 비동기로 실행되는 부분은 바로 setTimeout이다.
- 비동기적 실행이란?
- setTimeout은 비동기적이기 때문에 콜백 함수들이 각자의 타이밍에 맞춰 실행되지만 forEach 루프는 바로 끝나게 된다
- “a”, “b”, “c”는 setTimeout의 예약이 끝나면 비동기적으로 실행되고, 그 사이 다른 작업을 할 수 있는 여유가 생긴다.
- forEach는 순차적으로 실행되지만, 실제로 콜백 함수가 실행되는 시점은 비동기적!
중점적으로 봐야 할 점
- 클로저의 강점: 외부 데이터를 안전하게 참조
- 콜백 함수는 외부 변수(index, element)를 참조하면서도, 이 변수가 변경되더라도 각 함수 호출 시점의 값을 기억해.
- 타이밍 관리
- setTimeout에서 지연 시간이 다르기 때문에, 클로저가 제대로 작동하지 않으면 값이 꼬일 수 있어.
- 여기서는 클로저 덕분에 각 setTimeout이 독립적으로 작동함.
왜 이런 방식이 필요할까?
- 비동기 작업에서 변수 상태를 보존해야 할 때 유용함.
- 예를 들어, API 요청을 여러 개 보내고 각각의 결과를 처리할 때, 요청마다 고유한 데이터를 기억해야 할 때 클로저가 활약함
5-3-2 접근 권한 제어(정보 은닉)
접근 권한 제어?
객체의 내부 상태를 외부에서 직접 수정하거나 접근하지 못하도록 제한하는 개념
이를 통해 데이터의 무결성을 보호하고, 코드의 예측 가능성과 안정성을 높이는 것이 목적.
자바스크립트에서는 보통 클로저를 활용해서 접근 권한 제어를 구현함.
함수 차원에서 public한 값과 private한 값을 구별 가능하게 해주며, 내부 변수를 private 하게 만들고 외부에서는 이를 조작할 수 없도록 은닉 시킨다.
접근 권한 제어에서 클로저를 활용한 예시
function createBankAccount(password) {
// 함수 내부에서 선언된 private 변수
let balance = 0;
const accountPassword = password;
// 입금 함수
function deposit(amount) {
// 사용자가 입금 할 때마다 blance 값을 증가시킨다.
// 외부에서 함수를 호출할 수 있지만, balance값은 외부에서 직접 수정할 수 없어서 안전함
if (amount > 0) {
balance += amount;
console.log(`입금되었습니다: ${amount}원. 현재 잔액: ${balance}원`);
} else {
console.log("입금 금액은 0보다 커야 합니다.");
}
}
// 출금 함수
function withdraw(amount, passwordInput) {
// 사용자가 출금할 때 입력한 passwordInput이 accountPassword와
// 일치 해야만 출금을 할 수 있음.
if (passwordInput !== accountPassword) {
console.log("비밀번호가 잘못되었습니다.");
return;
}
if (amount <= balance && amount > 0) {
balance -= amount;
console.log(`출금되었습니다: ${amount}원. 현재 잔액: ${balance}원`);
} else if (amount > balance) {
// 잔액 부족 체크도 해줘서 출금 금액이 잔액보다 많으면 출금할 수 없음
console.log("잔액이 부족합니다.");
} else {
console.log("출금 금액은 0보다 커야 합니다.");
}
}
// 잔액 조회 함수
function getBalance(passwordInput) {
if (passwordInput !== accountPassword) {
console.log("비밀번호가 잘못되었습니다.");
return;
}
console.log(`현재 잔액: ${balance}원`);
}
// 반환된 객체에서 제공할 메서드만 노출하고, balance와 password는 은닉
return {
deposit,
withdraw,
getBalance
};
}
// 사용 예시
const myAccount = createBankAccount("1234");
myAccount.deposit(1000); // 1000원 입금
myAccount.withdraw(500, "1234"); // 500원 출금
myAccount.getBalance("1234"); // 잔액 조회
myAccount.withdraw(2000, "1234"); // 잔액 부족
myAccount.getBalance("wrongPassword"); // 잘못된 비밀번호로 잔액 조회
코드 설명
- createBankAccount(password):
- *balance*와 **accountPassword*는 함수 내부에서만 접근할 수 있는 private 변수 / 외부에서는 직접 접근할 수 없음.
- 입금 (deposit):
- 사용자가 입금할 때마다 balance 값을 증가시켜. 이 함수는 외부에서 호출할 수 있지만, balance 값은 외부에서 직접 수정할 수 없어서 안전함
- 출금 (withdraw):
- 사용자가 출금할 때, 입력한 **passwordInput*이 **accountPassword*와 일치해야만 출금이 이루어짐 / 이렇게 비밀번호로 접근 권한을 제어할 수 있음
- 잔액 부족 체크도 해줘서 출금 금액이 잔액보다 많으면 출금할 수 없음
- 잔액 조회 (getBalance):
- 잔액을 조회할 때도 비밀번호를 입력해야만 조회할 수 있어. 잘못된 비밀번호를 입력하면 잔액 조회가 불가능.
클로저가 어떻게 발생하나?
- balance와 accountPassword는 createBankAccount 함수의 스코프 내부에 존재하는 변수들로, 클로저 덕분에 반환된 객체 메서드들이 이 변수들에 접근할 수 있음
- deposit, withdraw, getBalance 함수는 클로저를 사용해 balance와 accountPassword를 기억하고, 외부에서 직접 접근할 수 없도록 은닉하고 있음.
- 이 객체들은 private 변수에 접근할 수 있는 방법을 제공하면서도, 직접적으로 수정할 수 없도록 제한하고 있음
중점적으로 봐야할 점
- 정보 은닉: balance와 accountPassword는 함수 내에서만 수정되고, 외부에서는 직접 접근할 수 없어서, 클로저를 통해 정보 보호가 가능
- 메서드를 통한 접근: 은닉된 변수에 접근하려면 반드시 제공된 메서드를 통해서만 가능하고, 이를 통해 캡슐화된 데이터에 안전하게 접근할 수 있음.
- 비밀번호 제어: withdraw와 getBalance에서 비밀번호를 입력해야만 접근할 수 있도록 하여, 접근 권한 제어를 구현하고 있음. 외부에서는 이 비밀번호를 알지 못하므로, 안전하게 보호됨
결론
- 클로저는 함수 내부에서 지역 변수와 내부 함수를 생성하고, 이를 외부에서 제어된 방식으로 접근할 수 있도록 하는 기술이다.
- 외부에서 접근할 수 없는 변수를 은닉하고, 필요한 메서드만을 반환하여 정보 은닉과 접근 권한 제어를 가능하게 한다.
- 클로저는 외부에서 접근할 수 없는 변수를 은닉하고, 필요한 메서드만을 반환하여 정보 은닉과 접근 권한 제어를 가능하게 한다.
5-3-3 부분 적용 함수
부분 적용 함수(Partial Application)?
함수의 일부 인자만 미리 고정해두고, 나중에 나머지 인자들을 제공하여 함수를 호출하는 방식.
즉, 주어진 함수의 일부 인자만 미리 제공하고, 나머지 인자는 나중에 받아서 실행할 수 있도록 함수를 반환하는 기법으로 반환된 함수는 미리 제공한 인자들을 기억하고, 나중에 제공된 인자와 함께 원래 함수를 실행한다.
이 방식은 클로저를 활용해서, 미리 제공된 인자를 기억할 수 있도록 하고 나중에 필요한 인자를 추가적으로 받아 실행할 수 있게 한다. 즉 부분 적용은 클로저가 있기에 가능하다.
부분 적용 함수 예시
자주 쓰는 덧셈 함수가 있다고 할 때, 이 덧셈 함수의 일부 인자만 미리 고정하고 나중에 나머지 인자들을 제공하는 방식으로 사용할 수 있다.
// 부분 적용 함수 구현
function add(a, b) {
return a + b;
}
// 부분 적용 함수
// a 인자만 받아서 내부적으로 새로운 함수를 반환 함.
function partialAdd(a) {
// 반환된 함수는 b 인자만 받음, a는 고정값
return function(b) {
return add(a, b);
};
}
// 사용 예시
// a 인자만을 받는 partialAdd를 사용해서 5 값은 고정이 되었다.
const add5 = partialAdd(5); // 5는 고정
// 10 또는 20이라는 b인자를 추가적으로 받아서 더하는 함수를 반환한다.
console.log(add5(10)); // 5 + 10 = 15
console.log(add5(20)); // 5 + 20 = 25
const add10 = partialAdd(10); // 10은 고정
console.log(add10(5)); // 10 + 5 = 15
코드 설명
- add(a, b): 기본 덧셈 함수로 두 숫자를 더하는 함수다.
- partialAdd(a): 부분 적용 함수반환된 함수는 b 인자를 받으며, a는 이미 고정된 값
- 이 함수는 하나의 인자만 받아서, 내부적으로 새로운 함수를 반환
- *add5*와 **add10*은 각각 a 값을 고정시킨 부분 적용된 함수
- 이들 함수는 b 인자만 받아서, 그 값을 고정된 a와 함께 원래 add 함수로 덧셈을 실 행해.
이 방식으로, partialAdd(5)는 5를 고정시키고, 나중에 그 값에 더할 b 값만을 받아서 덧셈을 수행하는 함수가 반환되는 것.
부분 적용 함수와 클로저의 관계
부분 적용 함수는 클로저를 활용해서 고정된 인자들을 기억하고, 나중에 미완성된 함수를 호출할 수 있게 한다.
- partialAdd 함수에 반환값으로 적용되어있는 내부 함수가 a라는 변수를 기억하고 있기 때문에 나중에 a를 다시 사용할 수 있게 한다.
- 내부함수가 a를 기억하고 있어서, 나중에 b가 주어졌을때 a+b로 덧셈이 가능하다.
결론
부분 적용 함수는 일부 인자만 미리 제공하고, 나중에 나머지 인자를 제공해서 함수를 호출하는 방식
이 방식은 클로저를 사용하여, 미리 고정된 값을 기억하게 하고 나중에 다른 값들을 추가로 받아서 함수를 실행할 수 있게 함.
이렇게 하면 코드가 더 유연하고 재사용이 가능해짐
- 코드가 유연하고 재사용이 가능해진다?
- 코드의 유연성부분 적용 함수를 사용하면, 함수의 구조가 변경되지 않으면서도 다른 값들로 쉽게 변형할 수 있게 해줄 수 있다
- 즉 함수의 호출 방식이 바뀌지 않으면서도 인자만 다르게 제공하는 방식으로 다양한 결과를 얻을 수 있다는 뜻
- 유연하다는 말은 코드가 다양한 상황에서 쉽게 적응 할 수 있다는 의미
- 코드 재사용성partialAdd(5) 처럼 미리 고정된 값을 갖는 부분 적용 함수가 있으면, 그 값을 바꿀 필요 없이 여러번 호출해서 다른 b값만 바꿔서 사용할 수 있음.하나의 함수로 여러 케이스를 다룰 수 있기 때문에 코드의 중복을 줄이고 유지보수도 쉬워진다는 장점이 있음
- 즉 새로운 함수를 만들지 않고 같은 부분 적용 함수를 계속 재사용할 수 있다는 의미
- 재사용 가능이라는 말은 하나의 코드를 여러 곳에서 반복적으로 사용할 수 있게 된다는 의미
5-3-3 커링 함수
커링(Currying)이란?
커링은 다중 인자를 받는 함수를 하나씩 인자를 받는 함수들의 연속으로 변환하는 기법이다.
다중 인자 함수를 여러 개로 나누어, 각 함수 한 개당 하나의 인자만 받도록 만든다.
- 다중 인자 함수? 단일 인자 함수?
- 다중 인자 함수
- 두 개 이상의 인자를 받는 함수로 여러 값을 동시에 처리할 수 있다.
- 단일 인자 함수
- 하나의 인자만을 받는 함수
- 커링을 적용한 함수
function add(a) {
return function(b) {
return a + b;
};
}
const add3 = add(3); // add(3)은 b 값을 받을 수 있는 함수 반환
console.log(add3(4)); // 3 + 4 = 7
- 예시위 코드에서 add(3)는 3을 고정하고, b값을 나중에 받을 수 있는 함수 (즉, add(3) 함수를 반환해주는 함수)를 반환
- 이처럼 하나의 함수로 여러 인자를 받을 수 있는 구조를 하나씩 받는 함수 체인으로 바꿔준다고 생각하면 쉽다.
** 커링과 부분 함수의 차이점
커링
하나의 함수를 여러 개의 함수로 분할 하는 방식
모든 인자를 하나씩 처리하는 방식으로, 각 인자를 차례대로 처리할 수 있도록 만듦
커링의 핵심은 모든 인자를 순차적으로 받는 것
단점
- 인자가 많아지면 가독성이 떨어진다는 단점이 있음
- 여러번의 중첩된 함수 호출을 통해 결과를 반환해서 호출 함수의 수가 많아짐에 따라 성능에 부담을 줄 수 있음
- 다중 인자를 처리 할 수 없어서 불편함
- 커링을 사용할 때 인자를 모두 받은 상태에서만 처리가 가능해서 중간에 인자가 없는 경우에 처리가 복잡할 수 있음 > 각각의 인자를 하나씩 나누어 받는 형태로 바뀌게 되는데 중간에 인자가 빠지면 복잡해지거나 값을 넣지 못하는 경우가 생길 수 있음
// 하나라도 빠질 수 없음
function curriedEx(a) {
return function(b) {
return function(c) {
return function(d) {
return a + b + c + d;
};
};
};
}
하나라도 빠졌을 때 처리 예시 (기본값 설정)
// 기본값을 설정하여 중간에 없는 경우 처리해줘야함
function curriedEx(a) {
return function(b = 0) {
return function(c = 0) {
return function(d = 0) {
return a + b + c + d;
};
};
};
}
console.log(curriedEx(1)(2)(3)(4)); // 10
console.log(curriedEx(1)(2)(3)()); // 6 (d는 기본값 0)
console.log(curriedEx(1)(2)()); // 3 (c, d는 기본값 0)
4-1. 인자가 없는 경우 처리하는 방법은 기본값을 설정하는 방법 외에 옵셔널 인자를 처리하는 방식으로 해결할 수 있음
부분 적용
특정 인자들을 미리 고정시키고, 나머지 인자들만 나중에 받는 방식
커링은 모든 인자를 하나씩만 받는 방식인 반면, 부분 적용은 일부 인자만 미리 고정해 놓고 나머지 값을 넣을 수 있음
부분 적용 함수는 고정하고 싶은 인자들을 원하는 만큼 고정할 수 있음.
단점
- 여러 개의 부분 적용 함수가 얽히면 코드 가독성이 떨어진다는 단점이 있음
- 불필요한 함수를 생성해서 메모리 사용에 비효율적일 수 있음 (메모리 오버헤드 발생)
- 인자 순서를 고정해놓고 사용하기 때문에 인자 순서를 변경할 수 없음
- 잘못된 인자 전달를 넘길 가능성이 있음. 인자 타입이나 개수가 맞지 않으면 버그 유발
- 함수 시그니처(인자 구조)가 변경될 경우 해당 부분의 부분 적용 함수를 재작성 해야할 수 있다. 함수의 인자 추가나 제거가 필요하면 기존에 작성된 코드가 다시 작성되어야할 필요성이 있음
클로저는 이 커링 함수의 인자가 계속해서 기억될 수 있도록 도와주며, 커링을 할 때 반환된 각 함수가 외부 함수의 변수에 접근할 수 있도록 해주기 때문에 클로저가 발생하게 됨
커링 함수에서 클로저 활용 예시
// 세 개의 숫자를 더하는 커링 함수
function add(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
const result = add(2)(3)(4); // 2 + 3 + 4 = 9
console.log(result);
코드 설명
- add 함수는 하나의 인자만 받는 함수로 커링이 되어 있음
- 첫 번째 함수 add(a)는 a를 받아서 반환값으로 또 다른 함수 function(b)를 리턴
- function(b)는 b를 받아서, 또 function(c)를 리턴하고, 마지막으로 c를 받아서 a + b + c를 계산하여 반환함
- 클로저 발생:
- add 함수에서 정의된 a 값은 내부 함수들(즉, function(b), function(c))에서 계속 참조할 수 있음
- a는 함수가 종료되었더라도 계속해서 기억되고, 이를 통해 각 인자들을 하나씩 전달하면서 결과를 계산할 수 있게 됨
클로저가 어떻게 발생하나?
클로저는 외부 함수의 변수나 상태가 내부 함수에서도 계속 참조될 때 발생
예시에서 a 값은 add 함수의 지역 변수이나, 커링을 통해 반환되는 내부 함수들에서 계속 접근이 가능
- add(2)가 호출 > a는 2로 설정 > a값은 내부 함수들에서 계속 기억 됨
- add(2)는 function(b) 반환 > function(b)는 b를 받는 함수가 됨
- function(b) 에서 b 전달 > function(c)를 반환하고 function(c)는 c를 받음
- function(c)에서 c가 전달되면 a, b, c 가 모두 합쳐져 최종 결과가 반환
중점적으로 봐야할 점
- 클로저는 외부 함수에서 정의된 변수가 내부 함수에서도 참조될 때 발생한다는 점을 이해해야 함 > 커링에서 핵심적으로 활용되는 부분
- 각 함수에서 상태를 유지하고, 그것을 이어서 사용할 수 있다는 점
- 커링에서는 하나의 인자씩 값을 전달할 수 있게 되므로, 인자들을 점차적으로 넣어가며 처리가 가능
결론
커링은 다중 인자를 받는 함수를 하나씩 받는 함수들로 나누는 기법.
클로저는 함수가 반환된 후에도 그 함수의 상태를 기억하게 해, 커링 함수에서 외부 함수의 변수를 내부 함수들에서 참조하도록 한다.
커링을 사용하면 코드를 더 유연하고 재사용 가능하게 만들 수 있고, 클로저는 이 과정에서 상태를 유지할 수 있게 도와준다.
'코딩이론 > Javascript' 카테고리의 다른 글
| chapter.14 Value 속성 (1) | 2024.12.04 |
|---|---|
| chapter.13 이벤트 핸들링 (1) | 2024.12.03 |
| chapter.12 인수(Argument) 와 반환 (retrun) (0) | 2024.12.03 |
| chapter.10 화살표 함수(Arrow Function) (0) | 2024.12.03 |
| chapter.09 함수 (0) | 2024.12.02 |