Block#
JavaScript에서 블록 스코프(Block Scope) 는 중괄호({}
)로 감싸진 코드 블록 내에서 선언된 변수나 함수가 해당 블록 내부에서만 유효한 범위를 의미한다.
이는 코드의 구조와 가독성, 유지보수성에 큰 영향을 미치며, 변수의 생명 주기와 가시성을 결정짓는 중요한 개념이다.
블록 스코프는 JavaScript에서 변수의 가시성과 생명주기를 제어하는 강력한 개념이다:
- ES6에서
let
과 const
키워드를 통해 도입되었다. - 중괄호(
{}
)로 둘러싸인 코드 블록 내에서만 변수에 접근할 수 있다. - 메모리 효율성, 코드 구조화, 버그 방지에 도움이 된다.
- 일시적 사각지대(TDZ)를 통해 더 예측 가능한 변수 동작을 제공한다.
- 클로저와 결합하여 강력한 패턴을 구현할 수 있다.
JavaScript에서 블록은 다음과 같은 상황에서 생성된다:
if
문for
, while
등의 반복문switch
문- 단독 블록
{}
try/catch/finally
문
예를 들어, 다음과 같은 코드에서 중괄호 안에 있는 영역이 블록이다:
1
2
3
4
5
6
7
8
9
10
11
| if (true) {
// 이것이 블록입니다
}
for (let i = 0; i < 5; i++) {
// 이것도 블록입니다
}
{
// 독립적인 블록도 가능합니다
}
|
블록 스코프의 역사적 배경#
JavaScript의 블록 스코프는 비교적 새로운 개념이다.
ES6(ECMAScript 2015) 이전에는 JavaScript에서 진정한 의미의 블록 스코프가 존재하지 않았으며, ES6(ECMAScript 2015)에서 let과 const 키워드와 함께 도입되었다.
ES5 이전: var와 함수 스코프
ES5까지 JavaScript에서는 var
키워드로 선언된 변수가 함수 스코프 또는 전역 스코프를 가졌다.
블록은 스코프를 생성하지 않았다:
1
2
3
4
5
6
7
8
9
10
| function oldExample() {
var x = 1;
if (true) {
var x = 2; // 같은 함수 스코프의 x를 재할당
console.log(x); // 2
}
console.log(x); // 2 (블록 외부에서도 값이 변경됨)
}
|
이러한 동작은 다른 프로그래밍 언어와 달랐고, 예기치 않은 버그의 원인이 되었다.
ES6의 도입: Let과 Const
ES6에서는 let
과 const
키워드가 도입되어 진정한 블록 스코프를 지원하게 되었다:
1
2
3
4
5
6
7
8
9
10
| function newExample() {
let x = 1;
if (true) {
let x = 2; // 새로운 블록 스코프에 x 생성
console.log(x); // 2
}
console.log(x); // 1 (블록 외부의 x는 변경되지 않음)
}
|
Let과 Const의 블록 스코프 특성#
Let 키워드
let
으로 선언된 변수는 블록 스코프를 가진다:
1
2
3
4
5
6
| {
let blockScoped = '블록 내부에서만 접근 가능';
console.log(blockScoped); // '블록 내부에서만 접근 가능'
}
// console.log(blockScoped); // ReferenceError: blockScoped is not defined
|
let
은 다음과 같은 특성을 갖는다:
- 선언된 블록 내에서만 접근 가능
- 같은 스코프 내에서 중복 선언 불가
- 값 재할당 가능
- 호이스팅은 되지만, 초기화 전에 접근하면 에러 발생 (일시적 사각지대)
Const 키워드
const
도 let
과 동일한 블록 스코프를 가지지만, 값을 재할당할 수 없다:
1
2
3
4
5
6
7
8
| {
const PI = 3.14159;
console.log(PI); // 3.14159
// PI = 3; // TypeError: Assignment to constant variable
}
// console.log(PI); // ReferenceError: PI is not defined
|
const
의 주요 특성:
- let
과 동일한 블록 스코프 규칙
- 선언과 동시에 초기화 필수
- 값 재할당 불가
- 객체나 배열이 할당된 경우, 내부 속성은 변경 가능
1
2
3
4
5
6
7
| {
const user = { name: '홍길동' };
user.name = '김철수'; // 객체 속성 변경 가능
console.log(user.name); // '김철수'
// user = {}; // TypeError: Assignment to constant variable
}
|
블록 스코프의 중첩#
블록은 중첩될 수 있으며, 내부 블록에서는 외부 블록의 변수에 접근할 수 있지만 외부 블록에서는 내부 블록의 변수에 접근할 수 없다:
1
2
3
4
5
6
7
8
9
10
11
12
| {
let outer = '외부 블록';
{
let inner = '내부 블록';
console.log(outer); // '외부 블록' (접근 가능)
console.log(inner); // '내부 블록'
}
console.log(outer); // '외부 블록'
// console.log(inner); // ReferenceError: inner is not defined
}
|
블록 스코프와 일시적 사각지대(TDZ)#
블록 스코프에서 중요한 개념 중 하나가 ‘일시적 사각지대(Temporal Dead Zone, TDZ)‘이다.
이는 변수가 선언되었지만 아직 초기화되지 않은 스코프 영역이다:
1
2
3
4
5
6
7
8
| {
// 여기서부터 TDZ 시작
// console.log(blockVar); // ReferenceError: Cannot access 'blockVar' before initialization
let blockVar = '초기화 완료'; // TDZ 종료
console.log(blockVar); // '초기화 완료'
}
|
TDZ는 다음과 같은 특성을 갖는다:
- 블록의 시작부터 변수 선언문까지의 영역
- 이 영역에서 변수에 접근하면 ReferenceError 발생
let
, const
, class
선언에 적용됨var
에는 적용되지 않음
반복문에서의 블록 스코프#
반복문의 블록 스코프는 특히 유용하다.
각 반복마다 새로운 스코프가 생성된다:
For 반복문
1
2
3
4
5
6
7
8
9
10
| for (let i = 0; i < 3; i++) {
// 각 반복마다 새로운 i가 생성됨
setTimeout(() => console.log(i), 100); // 0, 1, 2가 올바르게 출력
}
// 비교: var를 사용한 경우
for (var j = 0; j < 3; j++) {
// 모든 반복에서 같은 j를 사용
setTimeout(() => console.log(j), 100); // 3, 3, 3 출력 (반복 종료 후의 값)
}
|
for…of, for…in 반복문
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| const fruits = ['사과', '바나나', '오렌지'];
for (let fruit of fruits) {
// 각 반복마다 새로운 fruit 변수 스코프
console.log(fruit);
}
// console.log(fruit); // ReferenceError: fruit is not defined
const user = { name: '홍길동', age: 30, job: '개발자' };
for (let key in user) {
// 각 반복마다 새로운 key 변수 스코프
console.log(`${key}: ${user[key]}`);
}
// console.log(key); // ReferenceError: key is not defined
|
블록 스코프와 클로저의 관계#
블록 스코프는 클로저 생성에 중요한 역할을 한다.
클로저는 함수가 자신이 생성된 렉시컬 환경을 기억하는 특성을 말한다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| function createFunctions() {
const functions = [];
for (let i = 0; i < 3; i++) {
// 각 반복마다 새로운 i가 생성되어 각 클로저에 독립적으로 캡처됨
functions.push(function() {
console.log(i);
});
}
return functions;
}
const [f0, f1, f2] = createFunctions();
f0(); // 0
f1(); // 1
f2(); // 2
// var를 사용했다면 모두 3을 출력했을 것
|
변수 섀도잉#
블록 내에서 외부 스코프와 동일한 이름의 변수를 선언할 수 있으며, 이는 외부 변수를 가리게 된다.
1
2
3
4
5
6
| let x = "outer";
if (true) {
let x = "inner";
console.log(x); // "inner"
}
console.log(x); // "outer"
|
블록 스코프를 활용한 실용적인 패턴#
임시 변수 격리
블록 스코프를 사용하여 임시 변수를 격리할 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| function processData(data) {
// 데이터 처리 로직
{
// 임시 변수를 위한 별도 블록
let temp = data.slice();
temp.sort();
temp = temp.filter(item => item > 0);
// 필요한 계산 수행
const result = calculateAverage(temp);
data.average = result;
}
// temp는 여기서 접근 불가
return data;
}
|
단독 블록을 이용한 변수 스코프 제한
파일 최상위 레벨에서도 블록을 사용하여 변수의 스코프를 제한할 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 전역 변수를 최소화하기 위한 패턴
{
const API_KEY = 'secret_key';
const API_URL = 'https://api.example.com';
function fetchData() {
// API_KEY와 API_URL을 사용
}
// 필요한 함수나 변수만 전역으로 노출
window.fetchData = fetchData;
}
// API_KEY와 API_URL은 여기서 접근 불가
|
Switch문에서의 변수 선언
switch
문에서도 블록 스코프가 유용하하다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| switch (status) {
case 'success': {
let message = '성공적으로 처리되었습니다';
console.log(message);
break;
}
case 'error': {
let message = '오류가 발생했습니다';
console.log(message);
break;
}
default: {
let message = '처리 중입니다';
console.log(message);
}
}
// 각 case에서 동일한 변수명 message를 사용할 수 있음
// console.log(message); // ReferenceError: message is not defined
|
블록 스코프와 성능#
블록 스코프는 성능에도 영향을 미칠 수 있다:
메모리 사용 최적화
변수가 필요한 범위에서만 살아있도록 하여 메모리 사용을 최적화할 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function processManyItems(items) {
// 처리 로직
for (let i = 0; i < items.length; i++) {
// 각 항목에 대한 대규모 처리
{
// 대용량 데이터를 위한 임시 변수
let tempData = new Array(10000).fill(items[i]);
let processed = heavyProcessing(tempData);
items[i] = processed.result;
}
// tempData와 processed는 여기서 가비지 컬렉션의 대상이 됨
}
return items;
}
|
엔진 최적화 기회 제공
블록 스코프를 사용하면 JavaScript 엔진이 변수의 생명주기를 더 정확히 파악하여 최적화할 수 있다.
블록 스코프 관련 주의사항과 모범 사례#
try/catch 블록#
try/catch
문에서 catch
블록은 오류 객체에 대한 별도의 스코프를 생성한다:
1
2
3
4
5
6
7
8
9
| try {
// 시도하는 코드
throw new Error('오류 발생');
} catch (error) {
// error는 catch 블록 내에서만 접근 가능
console.log(error.message); // '오류 발생'
}
// console.log(error); // ReferenceError: error is not defined
|
블록 스코프 모범 사례#
변수의 스코프를 최소화하기
1
2
3
4
5
6
7
8
9
10
11
| // 좋은 예: 변수를 필요한 스코프로 제한
function processUserData(userId) {
// 사용자 데이터 가져오기
if (userId > 0) {
let userData = fetchUserData(userId);
processData(userData);
}
// userData는 여기서 접근 불가 (메모리 효율성)
}
|
const를 기본으로 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
| // 값이 변경되지 않는 변수는 const 사용
{
const CONFIG = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// 변경이 필요한 경우에만 let 사용
let retryCount = 3;
// 로직 수행...
}
|
블록을 활용하여 코드 구조화하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| function complexFunction() {
// 초기화 관련 로직
{
// 1단계: 데이터 준비
const data = prepareData();
validateData(data);
}
{
// 2단계: 비즈니스 로직 처리
const result = processBusinessLogic();
verifyResult(result);
}
{
// 3단계: 마무리 작업
const summary = createSummary();
return summary;
}
}
|
명시적인 블록 주석 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function largeFunction() {
// --- 초기화 섹션 ---
{
// 초기화 관련 코드...
}
// --- 데이터 처리 섹션 ---
{
// 데이터 처리 관련 코드...
}
// --- 결과 반환 섹션 ---
{
// 결과 반환 관련 코드...
}
}
|
블록 스코프의 실제 사용 예제#
웹 애플리케이션에서의 이벤트 핸들러#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| function setupEventHandlers() {
const buttons = document.querySelectorAll('.action-button');
buttons.forEach(button => {
// 각 버튼에 대한 데이터 설정
const buttonId = button.getAttribute('data-id');
// 이벤트 리스너 등록
button.addEventListener('click', () => {
// 블록 스코프 변수를 캡처하는 클로저
console.log(`Button ${buttonId} clicked`);
{
// 클릭 시 임시 상태 관리
let isProcessing = true;
button.classList.add('processing');
processButtonAction(buttonId)
.then(result => {
console.log(result);
})
.finally(() => {
button.classList.remove('processing');
// isProcessing은 이 블록에서만 필요함
});
}
});
});
}
|
API 호출에서의 블록 스코프 활용#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| async function fetchUserData(userId) {
// API URL 및 옵션 설정
{
const API_KEY = getApiKey(); // 민감한 키는 가능한 작은 스코프에 유지
const url = `https://api.example.com/users/${userId}`;
const options = {
method: 'GET',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
};
try {
const response = await fetch(url, options);
// API_KEY는 이 블록 외부에서 접근 불가
if (!response.ok) {
throw new Error('API 호출 실패');
}
return await response.json();
} catch (error) {
console.error('사용자 데이터 가져오기 오류:', error);
throw error;
}
}
}
|
모듈 패턴 구현#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| const userModule = (function() {
// 모듈 내부 상태 (비공개)
let userState = null;
// 내부 유틸리티 함수
function validateUser(user) {
return user && user.id && user.name;
}
// 공개 API
return {
setUser: function(user) {
// 블록 스코프를 사용한 검증 로직 격리
{
const isValid = validateUser(user);
if (!isValid) {
throw new Error('유효하지 않은 사용자 데이터');
}
// 추가 검증 로직
let permissionsValid = checkPermissions(user);
if (!permissionsValid) {
throw new Error('권한이 유효하지 않음');
}
}
// 검증 통과 후 상태 업데이트
userState = { ...user, lastUpdated: new Date() };
return true;
},
getUser: function() {
return userState ? { ...userState } : null;
}
};
})();
|
var
와 블록 스코프의 차이#
var
와 let
/const
의 블록 스코프 차이를 명확히 이해하는 것이 중요하다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| function varvs.Let() {
var varArray = [];
let letArray = [];
// var의 함수 스코프 vs. let의 블록 스코프
for (var i = 0; i < 3; i++) {
varArray.push(function() { return i; });
}
for (let j = 0; j < 3; j++) {
letArray.push(function() { return j; });
}
console.log(varArray.map(f => f())); // [3, 3, 3]
console.log(letArray.map(f => f())); // [0, 1, 2]
// 블록 내 var 선언은 함수 스코프에 적용됨
{
var varInBlock = 'var in block';
let letInBlock = 'let in block';
}
console.log(varInBlock); // 'var in block'
// console.log(letInBlock); // ReferenceError: letInBlock is not defined
// if 문에서도 동일
if (true) {
var varInIf = 'var in if';
let letInIf = 'let in if';
}
console.log(varInIf); // 'var in if'
// console.log(letInIf); // ReferenceError: letInIf is not defined
}
|
블록 스코프와 브라우저 호환성#
블록 스코프(let
과 const
)는 비교적 최신 기능이므로 호환성을 고려해야 한다:
- IE11 이하: 지원하지 않음
- Edge 12+, Firefox 44+, Chrome 49+, Safari 10+: 완벽 지원
- 구형 브라우저 지원을 위해서는 Babel과 같은 트랜스파일러 사용 필요
블록 스코프의 디버깅과 도구 활용#
크롬 개발자 도구에서의 블록 스코프 확인
크롬 개발자 도구의 Sources 패널에서 디버거를 사용하면 블록 스코프를 시각적으로 확인할 수 있다:
- 중단점(breakpoint)을 설정한다.
- 코드 실행 시 중단점에서 일시 중지된다.
- Scope 패널에서 현재 스코프의 변수를 확인한다:
- Block Scope: 현재 블록의 변수
- Closure: 상위 블록의 변수
- Script: 스크립트 스코프 변수
- Global: 전역 변수
ESLint를 활용한 스코프 관련 문제 방지
ESLint 설정을 통해 블록 스코프 관련 모범 사례를 강제할 수 있다:
1
2
3
4
5
6
7
8
| {
"rules": {
"no-var": "error", // var 사용 금지
"prefer-const": "error", // 재할당되지 않는 변수는 const 사용
"block-scoped-var": "error", // 블록 스코프 변수처럼 var 사용
"no-shadow": "error" // 상위 스코프 변수명 재사용 금지
}
}
|
참고 및 출처#