JavaScript에서 반복문과 addEventListener, 그리고 클로저
by yoora · 2025-08-17
JavaScript에서 반복문 내에서 addEventListener를 사용할 때 발생하는 일반적인 문제와 해결 방법, 그리고 클로저(Closure)의 개념과 활용에 대해 자세히 설명합니다.
반복문과 addEventListener
HTML 요소에 이벤트 리스너를 추가할 때, 반복문을 사용하여 여러 요소에 대해 이벤트를 등록하는 경우가 많습니다. 하지만 이 과정에서 변수 스코프와 클로저의 동작 방식에 따라 예기치 않은 결과가 발생할 수 있습니다.
1.1 문제 상황: var
키워드 사용 시 문제
아래는 여러 버튼에 클릭 이벤트 리스너를 추가하는 예제입니다.
HTML<button class="button-element">Button 1</button>
<button class="button-element">Button 2</button>
<button class="button-element">Button 3</button>
Javascriptconst buttons = document.querySelectorAll('.button-element');
// var로 선언한 경우
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + (i + 1) + ' clicked');
});
}
위 코드를 실행하면 모든 버튼을 클릭했을 때 항상 Button 4 clicked
가 출력됩니다. 이는 var
키워드가 함수 스코프를 가지기 때문에, for
문이 종료된 후 i
의 값이 buttons.length
(여기서는 3)으로 고정되기 때문입니다. 이벤트 핸들러가 실행될 때, 모든 핸들러가 동일한 전역 스코프의 i
를 참조하므로 최종 값인 3을 사용하게 됩니다.
1.2 해결 방법 1: let
키워드 사용
ES6에서 도입된 let
키워드는 블록 스코프를 제공합니다. 이를 사용하면 각 반복마다 새로운 i
변수가 생성되므로, 각 이벤트 핸들러가 올바른 i
값을 참조할 수 있습니다.
Javascriptconst buttons = document.querySelectorAll('.button-element');
// let으로 선언한 경우
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + (i + 1) + ' clicked');
});
}
버튼을 차례로 클릭하면 Button 1 clicked, Button 2 clicked, Button 3 clicked
가 출력됩니다. 이는 let
이 각 반복마다 새로운 스코프를 생성하여 i
값을 캡처하기 때문입니다.
1.3 해결 방법 2: 즉시 실행함수(IIFE) 사용
var
를 사용해야 하는 환경(예: ES5 이하)에서는 즉시 실행함수(IIFE; Immediately Invoked Function Expression)를 사용하여 각 반복마다 새로운 함수 스코프를 생성할 수 있습니다.
Javascriptconst buttons = document.querySelectorAll('.button-element');
for (var i = 0; i < buttons.length; i++) {
(function(i) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + (i + 1) + ' clicked');
});
})(i);
}
IIFE는 각 반복마다 새로운 함수 스코프를 생성하고,i
의 현재 값을 매개변수로 전달하여 캡처합니다. 따라서 각 이벤트 핸들러는 고유한 i
값을 참조하게 됩니다.
클로저(Closure)란?
클로저는 함수가 선언될 때의 환경(즉, 변수와 스코프)을 기억하고, 이후 함수가 다른 스코프에서 호출되더라도 그 환경에 접근할 수 있게 하는 JavaScript의 기능입니다.
2.1. 클로저 기본 예제
클로저의 기본적인 동작Javascriptfunction outerFunction() {
let outerVariable = "I am from the outer function";
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closureExample = outerFunction();
closureExample(); // 출력: "I am from the outer function"
outerFunction
은outerVariable
을 정의하고,innerFunction
을 반환합니다.innerFunction
은 외부 함수의outerVariable
을 참조합니다.outerFunction
이 실행을 마친 후에도innerFunction
은 클로저를 통해outerFunction
에 접근할 수 있습니다.
2.2. 클로저의 활용
- 정보 은닉 :
- private 변수를 구현하여 데이터에 대한 직접 접근을 제한합니다.
- 비동기 프로그래밍 :
- 비동기 콜백에서 특정 상태를 유지합니다.
- 함수 팩토리 :
- 특정 설정을 기반으로 함수를 생성합니다.
함수 팩토리 예제Javascriptfunction createCounter() {
let count = 0;
return {
increment: function() {
count++;
console.log(count);
},
decrement: function() {
count--;
console.log(count);
}
};
}
const counter = createCounter();
counter.increment(); // 출력: 1
counter.increment(); // 출력: 2
counter.decrement(); // 출력: 1
createCounter
는 클로저를 사용하여count
변수를 내부에 유지하며, 외부에서 increment
와 decrement
메서드를 통해 간접적으로만 접근할 수 있게 합니다.