| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- MVMM
- Error Handling
- sql
- INSERT SELECT
- 파이썬
- Gemini
- 자바의 정석
- join
- 자바
- 스터디윗미
- react
- RESIGNAL
- javascript
- 유데미코리아
- 스레드
- 조건문
- AI
- mysql
- til
- 개념
- 백준
- Java
- GoogleCloudConsole
- 회고
- databse
- 유데미
- dangerouslySetInnerHTML
- Google Cloud Skills Boost
- 프로그래머스
- 알고리즘
- Today
- Total
휘적이는 기록공간
[Javascript] 클로저 (Closure) 본문
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.
클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.
몇몇 언어에선
클로저를 구현하는 게 불가능하거나 특수한 방식으로 함수를 작성해야 클로저를 만들 수 있습니다.
하지만 자바스크립트에서는 모든 함수가 자연스럽게 클로저가 됩니다.
물론 예외도 존재합니다.
JavaScript는 함수 지향 언어입니다.
이런 특징은
함수를 동적으로 생성하고,
생성한 함수를 다른 함수에 인수로 넘기고,
생성된 곳이 아닌 곳에서 함수를 호출할 수도 있게 해줍니다.
함수 내부에서 함수 외부에 있는 변수에 접근할 수 있다는 것은 다들 알고 계실 것이라고 생각합니다.
그런데 함수가 생성된 이후에 외부 변수가 변경되면 어떤 일이 발생할까요?
함수는 새로운 값을 가져올까요? 아니면 생성 시점 이전의 값을 가져올까요?
매개변수를 통해 함수를 넘기고 이 함수를 저 멀리 떨어진 코드에서 호출할 때는 어떤 일이 발생할까요?
함수는 호출되는 곳을 기준으로 외부 변수에 접근할까요?
이 모든 것을 이해하기 위해서는
자바스크립트에서의 변수의 유효범위, 클로저를 공부해야합니다!
변수의 유효 범위
function init() {
const name = "Hui"; // name은 init에 의해 생성된 지역 변수이다.
function displayName() { // displayName() 은 내부 함수이며, 클로저다.
alert(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init()은 지역 변수 name과 함수 displayName()을 생성합니다.
displayName()은 init()안에 정의된 내부 함수이며 init()함수 본문에서만 사용할 수 있다.
여기서 주의할 점은 displayName() 내부엔 자신만의 지역 변수가 없다는 점입니다.
하지만 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName() 역시 부모 함수 init()에서 선언된 변수 name에 접근할 수 있습니다.
만약 displayName()가 자신만의 name변수를 가지고 있었다면, name 대신 this.name을 사용했을 것입니다.
function makeFunc() {
var name = "Hui";
function displayName() {
alert(name);
}
return displayName;
}
const myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
이 코드는 바로 전의 예제와 완전히 동일한 결과가 실행됩니다.
차이점은 displayname()함수가 실행되기 전에 외부함수인 makeFunc()로부터 리턴되어 myFunc변수에 저장된다는 것입니다.
makeFunc()실행이 끝나면(display 함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적입니다.
하지만 자바스크립트는 다릅니다.
그 이유는 자바스크립트는 함수를 리턴하고 리턴하는 함수가 클로저를 형성하기 때문입니다.
다른 예제를 통해 클로저를 이해해 보겠습니다.
클로저
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5);
var 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를 받아서 새 함수를 반환하는 함수 makeAddr(x)를 정의했습니다.
반환되는 함수는 단일인자 z를 받아서 x와 y와 z의 합을 반환합니다.
makeAddr은 함수를 만들어내는 공장으로 보면됩니다.
이는 makeAddr함수가 특정한 값을 인자로 가질 수 있는 함수들을 리턴한다는 것을 의미합니다.
위의 예제에서 add5, add10 두 개의 새로운 함수들을 만들기 위해 makeAddr 함수 공장을 사용했습니다.
하나는 매개변수 x에 5를 더하고 다른 하나는 매개 변수 x에 10을 더합니다.
add5와 add10은 둘 다 클로저입니다. 이들은 같은 함수 본문 정의를 공유하지만 서로 다른 환경을 저장합니다. 함수 실행 시 add5의 환경에서 클로저 내부의 x는 5이지만 add10의 맥락적 환경에서 x는 10입니다. 또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한것을 볼 수 있습니다. (물론 x값도 동일하게 변경 가능합니다)
이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는 것이 아니라는 것을 의미합니다.
클로저의 활용
클로저는 어떤 데이터와 그 데이터를 조작하는 함수를 연관시켜주기 때문에 유용합니다.
1. 클로저를 통한 css의 변경
2. 클로저를 이용하여 프라이빗 메소드 흉내내기
자바스크립트는 태생적으로 프라이빗 메소드를 제공하지 않지만 클로저를 이용하여 흉내내는 것은 가능합니다.
프라이빗 메소드는 제한적인 접근만을 허용하고 불필요한 메소드가 공용 인터페이스를 혼란스럽게 만들지 않도록 합니다.
이전 예제에서 각 클로저들이 고유한 문법적 환경을 가졌지만 여기서 우리는
counter.increment, counter.decrement, counter.value 세 함수에 의해 공유되는 하나의 환경을 만듭니다.
공유되는 환경은 실행되는 익명 함수 안에서 만들어집니다. 이 익명 함수는 정의되는 즉시 실행됩니다. 이 환경은 두 개의 프라이빗 아이템을 포함합니다.
하나는 privateCounter라는 변수이고 나머지 하나는 changeBy라는 함수입니다.
둘 다 익명 함수 외부에서 접근될 수 없습니다. 대신에 익명 wrapper에서 반환된 세 개의 퍼블릭 함수를 통해서만 접근되어야만 합니다.
위의 세 가지 퍼브릭 함수는 같은 환경을 공유하는 클로저입니다. 이는 자바스크립트의 변수의 범위 덕분에 접근할 수 있는 것입니다.
counter를 생성하는 함수를 정의하는 동시에 호출하고 결과를 counter 변수에 할당하는 것을 깨달았을 것입니다. 이 함수를 별도의 변수 makeCounter에 저장하고 이 변수를 이용해 여러 개의 counter를 만들 수도 있습니다.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* 2 */
counter1.decrement();
alert(counter1.value()); /* 1 */
alert(counter2.value()); /* 0 */
참고: 이런 방식으로 클로저를 사용하여 객체지향 프로그래밍의 정보 은닉과 캡슐화 같은 이점들을 얻을 수 있습니다.
3. 반복문에서의 클로저 생성 (일반적인 실수)
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
helpText 배열은 세 개의 도움말을 정의합니다. 각 도움말은 input 필드의 ID와 연관됩니다. 루프를 돌면서 각 input ID에 해당하는 element의 onFoucs 이벤트에 관련된 도움말을 보여주는 메소드에 연결합니다.
이 코드를 사용하면 제대로 동작하지 않는 것을 알 수 있습니다. 어떤 필드에 포커스를 주든 나이에 관한 도움말이 표시됩니다.
onFocus 이베트에 연결된 함수가 클로저이기 때문입니다. 루프에서 세 개의 클로저가 만들어졌지만 각 클로저는 값이 변하는 변수가(item.help) 있는 동일한 환경을 공유합니다(세 개의 클로저가 같은 item 변수를 공유한다는 뜻). onFoucs 콜백이 실행될 때 콜백의 환경에서 item 변수는 helpText 리스트의 마지막 요소를 가리키고 있을 것입니다.
이 경우 한가지 해결책은 더 많은 클로저를 사용하는 것입니다. 앞서 말했던 함수공장을 가동하는 것입니다.
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
이 것은 예상대로 동작합니다.
모두 단일 환경을 공유하는 콜백인 대신, makeHelpCallback 함수는 각각의 콜백에 새로운 환경을 생성합니다. 여기서 help는 helpText배열의 해당 문자열을 나타냅니다.
결론
클로저: 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수
이 클로저를 잘 활용하기 위해서는 자바스크립트에서의 변수의 허용 범위에 대해 잘 알아야 합니다.
var를 사용할 경우 예상했던 결과가 안 나올 수 있기 때문에 키워드 사용에 주의할 것!
출처
'Tech Notes & Growth > Learning Notes' 카테고리의 다른 글
| [Start With Udemy] Python 부트캠프: 100개의 프로젝트로 Python 개발 완전 정복 Start (0) | 2022.05.01 |
|---|---|
| (TIL) 2FA: 2-factor authentication (0) | 2022.04.27 |
| [JavaScript] 자바스크립트 기본형과 참조형의 차이점 (데이터 구조의 이해, 데이터 타입에 따른 처리 과정) (0) | 2021.12.28 |
| [TIL] 애자일 방법론, 그리드 알고리즘, MVMM 패턴 (0) | 2021.12.15 |
| [TIL] 작성 규칙 (0) | 2021.12.14 |