Q14. 아래 코드는 3개의 버튼을 생성하고, 클릭 시 각각 0, 1, 2의 인덱스를 출력하려 합니다. 하지만 코드를 실행하고 첫 번째 버튼을 클릭했을 때, 콘솔에는 예상과 다른 값이 출력됩니다. 출력 결과와 그 이유를 설명해주세요.
// 버튼이 3개 있다고 가정 (index: 0, 1, 2)
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button Index:', i);
});
}
// 사용자 액션: [첫 번째 버튼] 클릭!
- 출력 결과: 모든 버튼에서 Button Index: 3이 출력됩니다.
[이유]
- var의 스코프 특성 (Function Scope): var i는 블록 스코프({})를 무시하므로, 반복문이 돌 때마다 새로운 i가 생성되는 것이 아니라 단 하나의 변수 i를 공유합니다.
- 클로저의 참조 (Reference): 이벤트 리스너(콜백 함수)는 클로저로서 상위 스코프의 변수 i를 참조합니다. 하지만 3개의 함수 모두 똑같은 메모리 주소의 i를 바라보고 있습니다.
- 비동기 실행 시점 (Event Loop): for 반복문은 동기적으로 순식간에 실행되어 i는 0, 1, 2를 거쳐 3이 된 상태로 종료됩니다. 사용자가 버튼을 클릭하는 시점은 루프가 다 끝난(이미 i가 3이 된) 먼 훗날입니다.
- 결과: 클릭 이벤트가 발생하여 콜백 함수가 실행될 때, i의 값을 참조하러 가보면 이미 3으로 변해있기 때문에 3이 출력됩니다.
Q15. 다음은 React 클래스 컴포넌트를 바닐라 JS 클래스로 흉내 낸 코드입니다. render 메서드 안에서 onClick을 실행했을 때의 결과와, 그 이유에 대해서 설명해주세요.
class Counter {
constructor() {
this.value = 100;
}
handleClick() {
console.log(this.value);
}
render() {
const onClick = this.handleClick; // 1. 메서드를 변수에 할당 (참조 전달)
// 2. 사용자가 클릭했다고 가정하고 함수 실행
onClick();
}
}
const app = new Counter();
app.render();
- 결과(Strict Mode 기준) : TypeError: Cannot read properties of undefined (reading 'value')
이것이 바로 React 초창기에 .bind(this)를 지겹도록 썼던 이유 입니다.
1. 참조 할당(할당 시점) : const onClick = this.handleClick; 여기서 onClick 변수는 handleClick 이라는 함수 자체의 주소만 가져옵니다. this(Counter 인스턴스)와의 연결고리는 여기서 끊어 집니다. (this와의 연결고리가 끊아지고 일반 함수가 되는 것입니다. 이것을 메서드가 객체로 부터 분리 되었다고 합니다. )
2. 함수 실행(호출시점): onClick(); 이코드를 잘 보면 앞에 (점, .)이 없습니다. 이것은 "일반 함수 호출" 입니다.
- 일반 함수 호출에서 this는 기본적으로 전역 객체, window 입니다.
- 하지만! ES6 class 내부의 코드는 자동으로 Strict Mode(엄격모드)가 적용됩니다.
- Strict Mode에서 일반 함수 호출 시 this는 undefined가 됩니다.
따라서 handleClick 내부의 this는 undefined가 되고, undefined.value에 접근하려 했기 때문에 TypeError가 발생합니다. (이것이 React에서 이벤트 핸들러에 .bind(this)를 하거나 화살표 함수를 써야 하는 이유 입니다.
왜 갑자기 ES6로 넘어가요?
class에서 생성자를 선언하는 방식이 ES6 문법을 사용했기 때문이에요. ES6에서는 기본적으로 Strict Mode가 활성화 되는 것이 기본 조건 입니다.
ES6아니고, Strict Mode도 제거하면 어떻게 되나요?
이렇게 하려면 생성자를 선언하는 방식을 다음과 같이 변경해야 합니다.
Counter.prototype.handleClick = function() { console.log(this.value); };
하지만, 이렇게 되면 undefined를 반환할 뿐 100이라는 값은 절대 나오지 않습니다.
[시물레이션]
1. Counter의 render 함수를 실행
2. onClick에 Counter의 handleClick 함수를 일반 함수 형태로 담음
3. onClick()이 호출됨 (일반함수 호출)
4. Strict Mode가 아니므로, this는 전역객체 window 객체로 바인딩 됩니다.
=> 왜 window 객체로 바인딩 돼요?? onClick 함수에서 this는 현재 Counter인데 Counter로 바인딩 되어야 하는거 아니에요?
다이나믹 스코프에서는 "호출되는 그 코드의 형태(문법)"만 봅니다. 코드를 자세히 보면 onClick은 그냥 일반 함수입니다. '자기 명함(this)"을 건네주지 않고 그냥 "야, 너 실행해"라고 명령만 내린 꼴' 입니다. 그래서 window에 바인딩 되는 것입니다.
5. onClick()함수가 실행되고 코드는 window.value를 찾아야 합니다.
- window 객체에는 value라는 속성이 (보통)없습니다. 그래서 undefined 입니다.
TIP)
자바스크립트 this의 다이나믹은 일반적인 다이나믹 스코프하고 조금 다릅니다
- 일반적인 다이나믹 스코프 : "나를 호출한 함수 (Caller)의 변수 환경을 뒤진다."
- 자바스크립트 this의 다이나믹 : "호출될 때의 문법적 형태에 따라 그때그때 결정된다."
즉, 자바스크립트에서 "동적으로 결정된다"는 말은 호출 코드를 작성한 방식(점 유무)에 따라 바뀐다는 뜻이지, "부모 함수의 this를 자동으로 물려받는다"는 뜻이 아닙니다. ⭐️
'CS 질문' 카테고리의 다른 글
| [Deep Dive CS - Q17,Q18] Hoisting & Execution Context (0) | 2025.12.17 |
|---|---|
| [Deep Dive CS - Q16] 화살표 함수를 지양해야 되는 상황 (1) | 2025.12.16 |
| [Deep Dive CS- Q12, Q13] Scope (0) | 2025.12.16 |
| [Deep Dive CS - Q11] - Closure, Memory (0) | 2025.12.16 |
| [Deep Dive CS- Q10] React.useEffect vs React.useLayoutEffect (feat. EventLoop) (0) | 2025.12.15 |