클로저란? 함수와 함수가 선언된 어휘적 환경의 조합
function add() {
const a = 10;
function innerAdd() {
const b = 20;
console.log(a + b);
}
innerAdd(); // 30
}
add();
a
변수의 유효 범위는 add
함수 전체이고, b
의 유효 범위는 innerAdd
함수 전체입니다. innerAdd
는 add
내부에서 선언되었기 때문에 a
를 사용할 수 있습니다. 즉, "선언된 어휘적 환경"이란 변수가 코드 내부에서 어디서 선언되었는지를 의미합니다.
스코프란? 변수의 유효 범위로, 자바스크립트에는 다양한 스코프가 존재합니다.
전역 레벨에서 선언된 변수를 전역 스코프라고 합니다.
브라우저 환경에서는 전역 객체가 window
이고, Node.js 환경에서는 global
입니다. 이 객체에 전역 레벨에서 선언한 스코프가 반영됩니다.
var globalVar = 'wow';
function wow() {
console.log(globalVar);
}
console.log(globalVar); // wow
wow(); // wow
console.log(globalVar === window.globalVar); // true
다른 언어와 달리 자바스크립트는 기본적으로 함수 레벨 스코프를 따릅니다. 즉, {}
블록이 스코프 범위를 결정하지 않습니다.
if (true) {
var globalVar = 'global scope';
}
console.log(globalVar); // 'global scope'
console.log(globalVar === window.globalVar); // true
var globalVar
은 {}
내부에 선언되었지만, 외부에서도 접근이 가능합니다. 이는 자바스크립트가 {}
기준이 아닌 함수 레벨 기준으로 스코프를 가지고 있기 때문입니다.
function hello() {
var local = 'local variable';
console.log(local); // local variable
}
hello();
console.log(local); // Uncaught ReferenceError: local is not defined
if
문과는 달리 함수 블록 내부에서는 외부에서 접근이 불가능합니다. 자바스크립트는 함수 스코프를 따르기 때문입니다.
var x = 10;
function foo() {
var x = 100;
console.log(x); // 100
function bar() {
var x = 1000;
console.log(x); // 1000
}
bar();
}
console.log(x); // 10
foo();
자바스크립트에서는 가장 가까운 스코프에서 변수가 존재하는지 확인하므로 위와 같은 결과가 나타납니다.
var counter = 0;
function handleClick() {
counter++;
}
위 counter
는 전역 레벨에 선언되어 있어 어디서든 접근해 수정할 수 있습니다. 이는 잠재적인 오류를 발생시킬 수 있습니다.
function Counter() {
var counter = 0;
return {
increase: function () {
return ++counter;
},
decrease: function () {
return --counter;
},
getCounter: function () {
console.log('counter에 접근!');
return counter;
},
};
}
var c = Counter();
console.log(c.increase()); // 1
console.log(c.increase()); // 2
console.log(c.increase()); // 3
console.log(c.decrease()); // 2
console.log(c.getCounter()); // 2
counter
변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 방지했습니다.
import React, { useState } from 'react';
function Component() {
const [state, setState] = useState(0);
function handleClick() {
setState((prev) => prev + 1);
}
return <button onClick={handleClick}>Increment</button>;
}
useState
가 반환하는 상태 업데이트 함수(setState
)는 클로저를 사용하여 상태 값이 저장된 공간을 참조합니다. 따라서 컴포넌트가 여러 번 렌더링된 후에도 setState
함수는 이전 상태 값을 기억하고, 그 값을 바탕으로 업데이트 작업을 수행할 수 있습니다. 이는 함수의 스코프 내에서 선언된 변수를 기억하는 클로저의 특성 덕분입니다.
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
위 코드의 결과는 모두 5
가 출력됩니다. i
가 var
로 선언되었고, var
는 함수 레벨 스코프를 따르므로 전역 객체에 속하게 됩니다. 따라서 for
루프가 모두 실행된 후 setTimeout
이 호출될 때 i
의 값은 5
로 고정됩니다.
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
위 코드의 결과는 의도대로 0
, 1
, 2
, 3
, 4
가 순차적으로 출력됩니다. let
으로 선언된 i
는 블록 스코프를 가지므로 각 반복마다 새로운 i
가 생성되어 setTimeout
내부에서도 올바른 값을 참조하게 됩니다.