
자바스크립트에서 스코프를 이해하는 것은 매우 중요합니다. 이유를 7가지로 정리하여 설명하겠습니다.
1. 변수와 함수의 접근 범위 이해
- 자바스크립트에서 변수와 함수는 특정 스코프 내에서만 접근할 수 있습니다. 스코프를 이해하지 않으면 변수나 함수가 어디서 정의되었고, 어디서 사용할 수 있는지, 혹은 오류가 발생할 가능성이 있는지 알 수 없습니다.
- 예를 들어, 전역 변수와 지역 변수의 차이를 모르고 코드 작성 시 예기치 않은 결과가 발생할 수 있습니다.
2. 변수 충돌 방지
- 동일한 이름의 변수를 여러 번 사용하거나, 다른 스코프 내에서 동일한 이름의 변수가 있을 경우 충돌이 발생할 수 있습니다. 자바스크립트에서는 이를 변수 호이스팅(hoisting)과 스코프 체인(scope chain) 개념을 통해 다루지만, 스코프를 잘 이해하면 변수 충돌을 피할 수 있습니다.
- 예를 들어, 함수 내에서만 사용되는 변수는 함수 스코프 안에서만 유효하고, 전역 스코프에 영향을 미치지 않기 때문에 변수 이름 충돌을 방지할 수 있습니다.
3. 클로저(Closure)와 함수형 프로그래밍 이해
- 자바스크립트에서는 클로저라는 개념이 중요한데, 이는 함수가 정의될 때 그 함수가 참조하는 외부 변수들의 스코프에 대한 접근 권한을 갖는 특성입니다. 이 특성을 이해하면 상태 관리나 비동기 처리를 더 쉽게 다룰 수 있습니다.
- 예를 들어, 비동기 코드나 콜백 함수에서 스코프를 잘못 이해하면 예상치 못한 결과를 초래할 수 있습니다.
4. 메모리 관리
- 스코프는 메모리 관리에도 중요한 역할을 합니다. 변수가 스코프를 벗어나면 그 변수는 더 이상 접근할 수 없고, 이로 인해 메모리에서 해제됩니다. 스코프를 잘 이해하면 메모리 누수를 방지할 수 있습니다.
5. 코드 유지보수 및 디버깅
- 코드가 커지거나 여러 명이 작업할 때, 각 변수와 함수가 어디에서 유효한지 명확히 이해하고 있어야 디버깅이나 유지보수가 용이해집니다. 스코프를 이해하면 코드 흐름을 쉽게 파악할 수 있고, 문제를 빠르게 해결할 수 있습니다.
6. ES6+의 새로운 기능들 이해
- 자바스크립트 ES6부터 도입된 let, const와 같은 변수 선언 방식, 블록 스코프의 개념은 자바스크립트의 기존 var와 비교해 중요한 차이를 가지고 있습니다. 스코프를 잘 이해하면 이들 변수를 적절히 활용하여 더 안전하고 효율적인 코드를 작성할 수 있습니다.
7. 코드의 예측 가능성 증가
- 스코프를 정확히 이해하고 활용하면, 코드의 예측 가능성이 높아집니다. 코드가 어떤 스코프에서 실행되는지 정확히 알면, 변수나 함수가 예상대로 작동할지 여부를 확실히 할 수 있습니다.
8. React Hook. useState의 스코프와 클로져의 구성
- hook중 useState의 함수기반으로 심층분석해보며 클로저와 스코프의 사용에대해 파악해봅시다.
1. 변수와 함수의 접근 범위 이해
변수 스코프에대해서 이해해봅시다.
어떤 함수의 바깥에 변수를 선언하면, 현재 문서의 다른 코드에 해당 변수를 사용할 수 있기에 전역 변수라고 합니다.
만약 함수 내부에 변수를 선언하면, 오직 그 함수 내에서만 사용할 수 있기에 지역 변수라고 부릅니다.
ECMAScript 2015 이전의 JavaScript는 블록 문 스코프가 없습니다. 그래서 오히려, 블록 내에 선언된 변수는
그 블록 내에 존재하는 함수(혹은 전역 스코프)에 지역적입니다.
if (true) {
var x = 5;
}
console.log(x); // 5
5라는 로그가 남는데. x의 스코프가 전역 맥락 (혹은 코드가 함수의 일부분이라면 함수 맥락)이기 때문입니다. x의 스코프는 if문 블록에 제한되지 않습니다.
이 동작은 let 선언을 사용하면 바뀝니다 (ECMAScript 2015에 도입됨).
if (true) {
let y = 5;
}
console.log(y); // ReferenceError: y is not define
WHY? let 키워가 도입된이유는
var 키워드의 문제점을 해결하려는 의도로 탄생했습니다.
1. var의 스코프 문제
- var는 함수 스코프(function scope)만 지원하며, 블록 스코프(if문, for문 등)에서는 예상치 못한 동작을 할 수 있습니다.
- 예를 들어, for문에서 var로 선언한 변수를 사용할 때, 해당 변수가 블록을 벗어나서 전역 변수처럼 동작하는 문제가 발생합니다.위 코드에서 i는 for문 내부에서만 사용될 변수로 보이지만, 사실 var로 선언되었기 때문에 전역으로 선언된 것처럼 동작하고 있습니다. 이로 인해 변수의 의도치 않은 접근과 오류가 발생할 수 있습니다.
for (var i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // 3 (블록 바깥에서도 접근 가능)
2. 블록 스코프의 도입
- let 키워드는 블록 스코프(block scope)를 제공합니다. 즉, if문이나 for문 내에서 선언한 변수는 그 블록 안에서만 유효하게 됩니다.이렇게 let을 사용하면, 변수의 범위가 명확해지고, 불필요한 전역 변수 오염을 방지할 수 있습니다.
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ReferenceError: i is not defined (블록 밖에서는 접근 불가)
3. 변수 재선언 방지
- var를 사용하면 같은 스코프 내에서 같은 이름의 변수를 재선언할 수 있습니다. 이로 인해 버그나 예기치 않은 동작을 유발할 수 있습니다.하지만 let을 사용하면 같은 스코프 내에서 변수의 재선언을 방지할 수 있습니다.이로 인해 변수 재선언으로 인한 실수를 방지할 수 있습니다.
var x = 10;
var x = 20; // 문제 없이 재선언 가능
console.log(x); // 20
4. 호이스팅(Hoisting) 문제
- var는 호이스팅(hoisting)이라고 해서, 변수 선언이 해당 스코프의 최상단으로 끌어올려지는 특성을 가집니다. 이로 인해 코드가 예상치 못한 결과를 초래할 수 있습니다.하지만 let은 호이스팅이 되더라도 초기화되지 않은 상태로 끌어올려지기 때문에, 선언되기 전에 접근하면 ReferenceError가 발생합니다.이런 특성은 코드에서 오류를 더 빨리 발견할 수 있도록 도와줍니다.
console.log(x); // undefined (호이스팅으로 변수 선언이 끌어올려짐)
var x = 5;
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
5. const와 함께 사용하여 불변성 보장
- let은 const와 함께 사용되어, 불변성(immutable)을 보장하는 코드 스타일을 사용할 수 있게 해줍니다. 예를 들어, 값이 변경되지 않기를 원하는 변수에는 const를 사용하고, 변경 가능한 변수에는 let을 사용하여 코드의 의도를 명확하게 할 수 있습니다.
2. 클로저(Closure)와 함수형 프로그래밍 이해 - React기반으로 설명되어있습니다.
React에서 클로저(Closure*가 사용되는 이유는 주로 상태(state) 관리와 비동기 처리에 관련된 문제를 해결하기 위해서입니다. React는 컴포넌트 기반이고, 각 컴포넌트는 상태를 가질 수 있으며, 이 상태를 업데이트하거나 이벤트를 처리할 때 클로저가 매우 유용하게 활용됩니다. React에서 클로저가 어떻게 사용되는지, 그리고 그 이유를 자세히 설명해 드리겠습니다.
1. 상태(state)와 클로저
React에서 useState와 같은 훅을 사용할 때, 클로저가 중요한 역할을 합니다. 주로 상태의 캡슐화와 이전 상태를 참조하는 데 사용됩니다. React 컴포넌트 내에서 상태를 업데이트할 때, 상태의 최신 값을 클로저가 기억하면서 문제를 해결할 수 있습니다.
예시: setState와 클로저
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1); // 클로저가 prevCount를 기억
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
prevCount는 setCount가 호출될 때의 이전 상태를 참조하고 있으며, 이 값은 현재 함수의 스코프를 떠나서도 기억됩니다.
이 방식은 상태 업데이트가 비동기적으로 이루어질 때, 최신 상태를 기반으로 새로운 상태를 계산할 수 있게 해줍니다.
왜 클로저가 필요한지 설명하자면, React에서 상태는 비동기적으로 업데이트되므로, 상태 업데이트가 한 번에 이루어지지 않고 다수의 이벤트가 처리될 수 있습니다. 이때 상태를 안전하게 업데이트하려면 이전 상태에 의존하는 함수를 사용해야 하며, 그 이전 상태는 클로저가 제공하는 기능을 통해 관리됩니다.
2. 비동기 처리와 클로저
React에서 이벤트 핸들러나 비동기 작업을 처리할 때도 클로저는 유용하게 사용됩니다. 예를 들어, setTimeout이나 fetch와 같은 비동기 함수에서 변수를 사용할 때, 클로저가 변수를 "기억"하면서 원하는 동작을 처리할 수 있게 해줍니다.
예시: 비동기 함수에서 클로저 사용
import React, { useState, useEffect } from 'react';
function FetchDataComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const result = await response.json();
setData(result[0].title);
};
fetchData();
}, []);
return (
<div>
<p>{data ? `First post title: ${data}` : 'Loading...'}</p>
</div>
);
}
이 예시에서 fetchData 함수는 useEffect 훅 내에서 비동기적으로 데이터를 가져옵니다. 비동기 작업이 끝난 후, setData를 통해 상태를 업데이트합니다. 클로저가 중요한 이유는 비동기 함수 내에서 상태와 그 외 변수들이 스코프를 벗어나도 계속 기억되기 때문입니다. 클로저 덕분에 비동기 함수는 상태 업데이트를 안전하게 처리할 수 있습니다.
3. 이벤트 핸들러와 클로저
React에서 컴포넌트 내에 여러 이벤트 핸들러가 있을 때, 클로저를 사용하여 컴포넌트의 상태나 props에 접근하는 방식은 매우 유용합니다. 이벤트 핸들러가 함수 내부에서 상태를 기억하면서도, 이후에 실행될 때도 여전히 최신 상태를 참조할 수 있게 합니다.
예시: 이벤트 핸들러에서 클로저 사용
import React, { useState } from 'react';
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
const toggle = () => {
setIsOn(prevIsOn => !prevIsOn); // 클로저로 이전 상태를 참조
};
return (
<div>
<button onClick={toggle}>
{isOn ? 'Turn Off' : 'Turn On'}
</button>
</div>
);
}
위 코드에서 toggle 함수는 isOn 상태를 반전시키는 역할을 합니다. 이때 클로저는 setIsOn을 호출할 때 이전 상태값을 기억하고, 그 값을 바탕으로 새로운 상태를 계산합니다. 이는 **setState**와 유사하게 상태 변경이 비동기적인 React에서 매우 중요한 방식입니다.
4. 클로저의 주요 장점
React에서 클로저가 유용한 이유는 다음과 같습니다:
- 상태와 변수를 안전하게 관리: 클로저는 변수를 외부로부터 숨기고, 함수 내에서만 해당 변수를 사용할 수 있게 만들어 상태 캡슐화를 도와줍니다. 이는 불필요한 변수 충돌을 방지하고, 의도된 동작을 확실하게 구현할 수 있게 합니다.
- 상태 의존성 관리: React에서 상태를 업데이트할 때 이전 상태에 의존하는 연산이 필요합니다. 클로저는 이전 상태를 "기억"할 수 있기 때문에, 상태가 비동기적으로 변경될 때, 정확한 최신 상태를 사용하여 업데이트를 할 수 있습니다.
- 비동기 처리: 비동기 코드에서 스코프 외부의 상태나 변수를 참조할 때 클로저를 사용하면, 비동기 함수가 결과가 나오기 전에도 필요한 값에 접근할 수 있습니다. 이는 비동기 코드가 상태나 변수를 올바르게 참조하도록 보장해줍니다.
4. 메모리 관리 - React기반으로 설명되어있습니다.
리액트에서 메모리 관리와 스코프(Scope)는 자바스크립트와 매우 유사하게 작동하지만, 리액트의 특성상 컴포넌트 기반으로 동작하며, 컴포넌트가 생성되고 업데이트될 때마다 상태(state)와 렌더링이 관리됩니다. 이와 관련하여 클로저, 컴포넌트의 생애주기, 이벤트 핸들러 등에서 메모리 관리와 스코프가 어떻게 작용하는지에 대해 설명하겠습니다.
1. 컴포넌트의 상태와 메모리 관리
리액트 컴포넌트에서 상태(state)는 메모리를 차지합니다. 상태는 컴포넌트가 렌더링될 때마다 메모리에 할당되며, 컴포넌트가 언마운트(삭제)될 때 메모리에서 해제됩니다.
예시: 상태와 메모리
import React, { useState, useEffect } from 'react';
function Timer() {
const [time, setTime] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
// Cleanup function to stop the interval when the component is unmounted
return () => clearInterval(timer);
}, []); // 빈 배열로 마운트될 때만 실행
return (
<div>
<p>Time: {time}</p>
</div>
);
}
- 상태 time은 useState를 통해 컴포넌트에 할당된 메모리입니다. 컴포넌트가 렌더링될 때마다 이 상태가 메모리에서 관리됩니다.
- setInterval로 1초마다 상태를 업데이트하고 있는데, 이때 상태를 업데이트할 때 prevTime을 참조하는 방식으로 이전 상태를 안전하게 참조합니다.
- 메모리 관리: 컴포넌트가 언마운트될 때 clearInterval로 타이머를 정리해주어 메모리 누수를 방지합니다. 컴포넌트가 언마운트되면 setInterval로 생성된 타이머가 여전히 메모리를 차지하고 있기 때문에, 이를 해제하지 않으면 메모리 누수가 발생할 수 있습니다.
2. 클로저와 메모리 관리
리액트에서 클로저(Closure)는 상태(state)나 props를 컴포넌트 외부에서 참조할 수 있게 합니다. 이는 클로저가 메모리에서 참조를 유지하는 원리와 비슷하게 동작하며, 때로는 메모리 누수를 초래할 수 있습니다.
예시: 클로저가 메모리 누수를 유발하는 경우
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // count를 참조
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
- 위 예시에서 increment 함수는 count 값을 참조하는 클로저입니다. 이 코드에서 count는 컴포넌트의 상태이기 때문에, increment 함수가 count에 대한 참조를 계속 유지하게 됩니다.
- 만약 컴포넌트가 언마운트되기 전에 increment 함수가 여러 번 호출되면, count와 관련된 메모리가 계속해서 참조될 수 있습니다. 이로 인해 불필요한 메모리 유지가 발생할 수 있습니다.
이를 해결하기 위해 상태를 안전하게 업데이트하려면 prevState를 이용한 상태 업데이트 방식을 사용해야 하며, 이렇게 하면 최신 상태를 기반으로 상태를 업데이트하게 됩니다.
해결 방법: 상태 업데이트 시 prevState 사용
const increment = () => {
setCount(prevCount => prevCount + 1); // 이전 상태(prevCount)를 참조
};
- prevCount를 사용하면 상태가 안전하게 업데이트되고, 메모리 누수를 방지할 수 있습니다.
3. 이벤트 핸들러와 메모리 관리
리액트에서는 컴포넌트가 이벤트 핸들러를 사용하여 상태를 변경하거나 외부 작업을 처리합니다. 하지만 이벤트 핸들러가 컴포넌트의 상태나 props를 참조하는 경우, 이들이 클로저로 캡처되어 컴포넌트가 언마운트되었을 때도 메모리에서 해제되지 않을 수 있습니다.
예시: 이벤트 핸들러와 메모리 누수
import React, { useState, useEffect } from 'react';
function ButtonClick() {
const [clicks, setClicks] = useState(0);
const handleClick = () => {
setClicks(clicks + 1); // 상태를 참조하는 클로저
};
return <button onClick={handleClick}>Click Me</button>;
}
- 위 코드에서는 handleClick 함수가 clicks 상태를 참조하는 클로저를 만들기 때문에, clicks의 값이 변경되어도 handleClick이 계속해서 이전 상태를 참조할 수 있습니다.
- 메모리 누수를 방지하려면, 위에서 설명한 것처럼 prevState를 사용하여 상태 업데이트를 수행해야 합니다.
해결 방법: prevState 사용
const handleClick = () => {
setClicks(prevClicks => prevClicks + 1); // prevClicks로 최신 상태를 참조
};
4. 컴포넌트 언마운트 시 메모리 해제
리액트에서는 컴포넌트가 언마운트될 때 메모리 해제를 위해 useEffect의 정리 함수를 사용해야 합니다. 이 함수는 컴포넌트가 제거될 때 실행되며, 이벤트 리스너나 타이머 같은 비동기 작업을 정리(clean up)하는 데 사용됩니다.
예시: 타이머와 메모리 해제
import React, { useState, useEffect } from 'react';
function Timer() {
const [time, setTime] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
// 컴포넌트가 언마운트될 때 타이머를 정리
return () => clearInterval(timer);
}, []); // 마운트 시 한 번만 실행
return <p>Time: {time}</p>;
}
- useEffect 내에서 clearInterval을 사용하여 컴포넌트가 언마운트되었을 때 타이머가 정리되도록 합니다.
- 이벤트 리스너나 타이머 같은 비동기 작업은 컴포넌트가 언마운트될 때 **명시적으로 정리(clean up)**해주어야 메모리 누수를 방지할 수 있습니다.
5. 메모리 누수 방지
리액트에서 메모리 누수를 방지하려면 다음과 같은 방법을 고려해야 합니다:
- useEffect의 정리 함수 사용: 컴포넌트가 언마운트될 때 타이머나 이벤트 리스너를 정리합니다.
- prevState를 이용한 안전한 상태 업데이트: setState 호출 시 이전 상태를 참조하여 안전하게 업데이트합니다.
- 불필요한 참조를 끊어주기: 클로저를 사용할 때, 더 이상 사용되지 않는 참조를 명시적으로 끊어주어야 메모리 누수를 방지할 수 있습니다.
8. React Hook. useState의 스코프와 클로져의 구성
작은 useState의 버젼입니다.
참고URL : https://hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/
const MyReact = (function () {
let _val // 모듈 스코프 안에 state를 잡아놓습니다.
return {
render(Component) {
const Comp = Component()
Comp.render()
return Comp
},
useState(initialValue) {
_val = _val || initialValue // 매 실행마다 새로 할당됩니다.
function setState(newVal) {
_val = newVal
}
return [_val, setState]
},
}
})()
- useState에서 사용되는 _val은 모듈 스코프에 존재하므로, MyReact 모듈 내에서만 상태를 관리합니다.
- setState는 클로저를 사용하여 _val에 접근하고, 상태를 변경합니다.
- render 함수는 상태가 변경된 후 최신 상태를 반영하여 컴포넌트를 다시 렌더링합니다.
- 리액트에서는 컴포넌트가 화면에서 사라질 때(즉, 언마운트될 때), 해당 컴포넌트에 관련된 상태와 이벤트 핸들러가 메모리에서 자동으로 해제됩니다
결론적으로, 모듈 스코프와 클로저는 MyReact 내에서 상태를 유지하고 관리하는 핵심적인 역할을 합니다. 이 구조 덕분에 상태값은 컴포넌트가 렌더링될 때마다 일관되게 관리되며, 상태 업데이트와 렌더링 간에 올바른 값을 전달할 수 있습니다.
스코프는 코드 내에서 변수나 함수의 유효 범위를 정의하여, 변수 충돌을 방지하고 코드의 예측 가능성을 높여주는 중요한 개념입니다. 이를 통해 리액트와 같은 프레임워크에서는 상태 관리, 메모리 관리, 클로저와 같은 기능을 효율적으로 활용할 수 있습니다. 특히, 스코프는 코드의 안전성을 높이고, 변수의 캡슐화와 상태 유지가 가능하게 하여, 유지보수와 디버깅을 용이하게 만듭니다. 결국, 스코프는 안전하고 깔끔한 코드 작성의 기반을 마련해 주는 핵심 요소입니다.
이상입니다.
'언어 > JAVASCRIPT' 카테고리의 다른 글
[클로저] 외부 상태 기억과 데이터 캡슐화 제공 (0) | 2025.04.01 |
---|---|
9장 타입변환과 단축평가 (0) | 2024.09.27 |
실행 콘텍스트 (Execution context) (1) | 2023.12.25 |
[반복문] for() forEach() map() filter() 어떨때 사용?? (0) | 2023.10.04 |
자바스크립트의 핵심개념 (0) | 2022.07.04 |