프론트/javascript
클로저 패턴이란 무엇인가
ZestLee
2024. 4. 20. 15:46
들어가며
의도치 않게 너무 어그로 끄는 제목이 되었는데, 별 생각 없이 정한 제목입니다...
이 글은 그저 끄적이는 글이기 때문에 엄청난 의식의 흐름일 수 있습니다
갑자기 뿅 하고 끝날 수도 있으니, 이 점 양해 부탁드립니다 ㅎ;;
배경
최근 react 를 공부하면서, 클로저 패턴을 자주 쓰게 되었음
(예를 들어, click과 같은 이벤트 핸들러 함수에 특정 값을 넘기면서 이벤트도 함께 넘기고 싶을 때 이 패턴을 활용하곤 하더라)
예시
export default function App() {
const togglePopover = (index) => (e) => {
e.preventDefault();
e.stopPropagation();
setOpen(openedIndex === index ? null : index);
};
return (
<div className="wrapper">
{dummyData.map(({ text, context }, i) => (
<Detail
key={`detail${i}`}
text={text}
context={context}
open={openedIndex === i}
onToggle={togglePopover(i)}
/>
))}
</div>
);
}
// 생략된 부분 다수...
- 위에서 `togglePopover` 는 클로저 함수임
- index 를 매개변수로 받고, 새로운 실행 컨텍스트를 생성하고, 함수를 반환함
- 반환된 함수는 이벤트 객체를 매개변수로 받고, 그 이벤트 객체를 사용해서 특정 작업을 수행함
- 이 반환된 함수는 원래 받았던 index 에 계속해서 접근이 가능함
- 이것은 클로저로 index 가 함수 내부에서 기억되기 때문임
- 그렇다면 굳이 왜 클로저 함수를 사용할까?
- 성능 최적화와도 관련이 있다고 함
- 만약 togglePopover 함수를 클로저 함수가 아닌, index 와 e 두개의 인자를 받는 함수로 만든다면
onToggle={(e) => togglePopover(i, e)} 이런 식으로 호출해야 함
이렇게 되면 onToggle 이벤트가 발생할 때마다 새로운 (e) => togglePopover(i, e) 함수가 생성이 됨 - 하지만 클로저 패턴으로 하면 특정 i 값에 대한 함수가 한 번만 생성이 됨
이 함수는 i 값을 기억하고, i 에 대한 이벤트가 발생할 때 재사용 됨
- 이게 왜 중요하냐면 리액트는 컴포넌트의 prop 이나 state 가 변경될 때 다시 렌더링 함
- 이벤트 핸들러와 같은 함수가 새로운 참조를 가지게 되면,
리액트는 이것을 변경된 prop 으로 간주하고,
해당 컴포넌트를 불필요하게 다시 렌더링 하게 되는 것임
- 이벤트 핸들러와 같은 함수가 새로운 참조를 가지게 되면,
그렇다면 클로저가 뭔데?
드디어 다시 돌아오게 되었음..;;
클로저는 함수랑 함수가 선언될 때의 렉시컬 환경 (어휘적 환경) 에 대한 조합임
렉시컬 환경은 해당 함수가 생성될 때의 변수들을 포함하고 있으며, 이를 통해 함수가 실행될 때도 그 변수들에 접근할 수 있게 됨
주로 함수형 프로그래밍에서 사용되고, 함수 내부에서 생성된 내부 함수가, 외부 함수의 지역 변수에 접근할 수 있게 해 줌
클로저를 통해서 데이터를 은닉하고 모듈 패턴과 같은 구조에서 특정 데이터에 대한 접근을 제어할 수 있음
데이터를 은닉하면 좋은 점이 뭔데?
(옛날에) 학교에서도 배웠는데... 맨날 까먹는
- 유지보수가 용이해짐
- 직접 접근하고 수정하는 걸 제한해서 복잡성이 줄어듦
- 데이터를 변경하거나 로직을 수정할 때 다른 코드에 미치는 영향을 최소화 할 수 있게 됨
- 충돌 감소
- 여러 개발자가 동일한 코드 베이스에서 작업할 때, 변수 or 함수 or 네임스페이스 충돌을 방지해 줌
- 각 데이터와 함수는 인터페이스 뒤에 숨겨져있기 떄문임
- 보안이 향상됨
- 중요한 데이터를 외부에서 직접 접근할 수 없도록 함. 따라서 데이터의 무단 변경이 되지 않음
- 인터페이스와 구현의 분리
- 개발자는 객체의 인터페이스만을 통해서 상호작용을 하게 됨
- 따라서 내부 구현이 변경되더라도, 인터페이스가 동일하다면 타 개발자 측을 변경할 필요가 없게 됨
- 이것은 확장성을 개선할 수가 있다
- 재사용성 향상
- 외부에서 필요한 기능만을 노출시키고 내부 구현을 숨길 수가 있게 됨
- 이렇게 되면 코드나 컴포넌트를 재사용할 수 있게 됨
클로저와 클래스는 뭐가 다른건데?
내 입장에서는 클로저와 클래스가 데이터를 은닉한다는 점에서 넘 비슷하단 생각이 들었음
((((((((((나)))))))))
그래서 생각난 김에 정리해 보려고 함
예시
계산기로 예시를 만들어 보자
- 클래스
class Calculator {
constructor() {
this.currentValue = 0;
}
add(value) {
this.currentValue += value;
return this.currentValue;
}
subtract(value) {
this.currentValue -= value;
return this.currentValue;
}
getCurrentValue() {
return this.currentValue;
}
}
const calc = new Calculator();
console.log(calc.add(10)); // 10
console.log(calc.subtract(4)); // 6
console.log(calc.getCurrentValue()); // 6
- new 해서 새로운 인스턴스를 만들 수 있고, 각 인스턴스는 자신만의 상태를 유지함
- OOP 의 일환으로, 구조화된 방식을 제공함
- 상속과 같은 기능으로 복잡한 시스템을 구축할 때 유용함
function Calculator() {
let currentValue = 0;
return {
add: function(value) {
currentValue += value;
return currentValue;
},
subtract: function(value) {
currentValue -= value;
return currentValue;
},
getCurrentValue: function() {
return currentValue;
}
};
}
const calc = Calculator();
console.log(calc.add(5)); // 5
console.log(calc.subtract(2)); // 3
console.log(calc.getCurrentValue()); // 3
- 이것도 새로운 인스턴스를 갖게 됨
- 함수의 스코프를 이용해서 데이터 숨김에 초점을 둠
- 메모리 최적화가 필요하거나 함수형 프로그래밍 패턴을 선호할 때 유용함