언어·프레임워크/JavaScript
[JavaScript] 클로저(Closure) 정리!
DandyNow
2023. 9. 26. 00:38
728x90
반응형
1. 클로저(Closure) 란?
클로저는 함수와 함수가 선언된 렉시컬 스코프(Lexical Scope) 사이의 특별한 관계를 나타내는 개념이다. 함수 내부에서 선언된 함수가 외부 함수의 변수에 접근할 수 있고, 그러한 함수를 클로저라고 한다. 클로저는 외부 함수의 변수에 대한 참조를 "닫아"(Capture)서 나중에 사용할 수 있도록 저장한다. 이로 인해 외부 함수가 종료된 이후에도 해당 변수에 접근할 수 있다.
2. 클로저와 관련한 몇 가지 개념
1) 렉시컬 스코프(Lexical Scope): JavaScript는 함수를 정의할 때 함수 내부에서 외부 스코프에 접근할 수 있도록 스코프를 정의한다. 이 스코프는 함수를 선언할 때 결정되며, 함수가 어떤 변수에 접근할 수 있는지 결정한다. 이러한 스코프 규칙을 렉시컬 스코프라고 한다. 렉시컬 스코프는 코드를 작성할 때 정적으로 결정된다.
2) 실행 컨텍스트(Execution Context): 실행 컨텍스트는 코드가 실행될 때 생성되며 현재 실행 중인 코드 블록에 대한 정보를 담고 있다. 실행 컨텍스트에는 변수, 함수, 스코프 체인 등의 정보가 포함된다.
3) 스코프 체인(Scope Chain): 실행 컨텍스트마다 해당 컨텍스트의 변수와 외부 컨텍스트(상위 스코프)에 대한 참조를 가지고 있다. 이것이 스코프 체인이며, 변수를 검색할 때 사용된다. 스코프 체인을 통해 렉시컬 스코프가 구현된다.
3. 실행 컨텍스트와 스코프 체인에 의한 동적 스코프 조작과 클로저
- 렉시컬 스코프는 함수를 정의할 때 결정된다. 이것은 함수가 어떤 스코프에 접근할 수 있는지를 정의하는 데 사용된다.
- 실행 컨텍스트와 스코프 체인은 런타임 중에 생성된다. 코드가 실행될 때마다 실행 컨텍스트가 생성되고, 해당 컨텍스트의 스코프 체인이 형성된다.
- 이러한 동적 스코프 체인은 함수가 런타임 중에 동적으로 다른 스코프에 접근할 수 있는 유연성을 제공한다.
- 예를 들어, 함수가 다른 함수 내부에서 실행될 때 해당 함수의 스코프에 접근할 수 있게 된다.
- 이러한 동적 스코프 체인은 함수가 다른 함수 내에서 동작할 때 필요한 변수 및 데이터에 접근할 수 있도록 한다. 이것은 클로저가 가능한 이유 중 하나이다.
- 따라서 JavaScript는 함수의 스코프를 정적으로 결정하지만, 실행 컨텍스트와 스코프 체인을 통해 런타임 중에 동적으로 스코프를 조작할 수 있다.
- 이것은 렉시컬 스코프와 동적 스코프 체인을 혼합하여 언어의 유연성을 제공하며, 클로저와 같은 고급 기능을 가능하게 한다.
4. 클로저 예제
4.1. 예제 1
function outer() {
let i = 0;
function inner(x) {
i += x;
return i;
}
return inner;
}
// a와 b는 독립적으로 존재한다!
const a = outer();
const b = outer();
console.log("a: ", a(1)); // a: 1
console.log("b: ", b(10)); // b: 10
console.log("a: ", a(1)); // a: 2
console.log("b: ", b(10)); // b: 20
4.2. 예제 2 : setTimeout()
// 1초 후 10 출력
var i;
for (i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000);
}
// 1초 후 0~9까지 출력
var i;
for (i = 0; i < 10; i++) {
((j) => setTimeout(() => console.log(j), 1000))(i);
}
// 1초 후 0~9까지 출력
// let 키워드 사용시 즉시 실행함수를 적용하지 않아도 0~9 출력하고 있음
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 1초 후 0~9까지 출력
var i;
for (i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}
4.3. 예제 3 : 클로저로 인해 정상 작동하지 않는 코드와 개선된 코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
<script>
// 참고: https://jake-seo-dev.tistory.com/182
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)' }
];
// 1. 클로저로 인해 정상 작동하지 않는 코드
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
}; // 익명 함수 자체가 할당 되고 있음
}
// 2. 정상 작동하는 개선된 코드
// 1) 즉시 실행 함수
// for (var i = 0; i < helpText.length; i++) {
// var item = helpText[i];
// document.getElementById(item.id).onfocus = (
// function (txt) {
// return function () {
// return showHelp(txt);
// }
// })(item.help) // 즉시 실행 함수로 "리턴 값 할당"
// }
// 2) 즉시 실행 함수(화살표 함수)
// for (var i = 0; i < helpText.length; i++) {
// var item = helpText[i];
// document.getElementById(item.id).onfocus = ((txt) => () => {
// showHelp(txt);
// })(item.help); // 즉시 실행 함수로 "리턴 값 할당"
// }
// 3) result 함수 호출
// for (var i = 0; i < helpText.length; i++) {
// var item = helpText[i];
// document.getElementById(item.id).onfocus = result(item.help) // result함수 리턴 값을 할당
// }
// function result(txt) {
// return function () {
// return showHelp(txt);
// }
// }
}
setupHelp();
</script>
</body>
</html>
참고 자료
https://jake-seo-dev.tistory.com/182
728x90
반응형