Scopes#
JavaScript의 스코프는 변수와 함수의 접근성과 생존 기간을 결정하는 중요한 개념으로, 변수나 함수가 유효하게 접근할 수 있는 범위를 의미한다.
이를 이해하면 코드의 예측 가능성을 높이고 버그를 줄일 수 있으며, 변수의 생명 주기와 중첩된 함수, 클로저 등의 동작을 파악하는 데 큰 도움이 된다.
스코프의 기본 개념#
스코프란 간단히 말해 ‘변수의 유효 범위’이다.
JavaScript에서 변수가 선언되면, 이 변수는 특정 범위 내에서만 접근 가능하다. 이 범위를 스코프라고 한다.
1
2
3
4
5
6
7
| function exampleFunction() {
let insideVariable = "이 변수는 함수 내부에서만 접근 가능합니다";
console.log(insideVariable); // 정상 작동
}
exampleFunction();
// console.log(insideVariable); // 오류: insideVariable은 정의되지 않았습니다
|
위 예시에서 insideVariable
은 exampleFunction
내부에서만 접근 가능한 지역 변수이다.
스코프의 중요성#
JavaScript에서 스코프를 제대로 이해하고 활용하는 것은 다음과 같은 이유로 중요하다:
- 변수 이름 충돌 방지: 다른 스코프에서 같은 이름의 변수를 안전하게 사용할 수 있다.
- 메모리 효율성: 변수의 생명주기가 제한되어 불필요한 메모리 점유를 줄인다.
- 보안: 중요한 데이터나 함수를 외부에서 접근할 수 없게 보호한다.
- 코드 가독성: 변수의 범위가 명확하면 코드를 이해하기 쉬워진다.
- 모듈화: 독립적인 코드 블록을 만들어 재사용성과 유지보수성을 높인다.
JavaScript의 스코프 유형#
JavaScript에는 주로 다음과 같은 스코프 유형이 있다:
전역 스코프 (Global Scope)#
코드 어디에서나 접근이 가능한 스코프이다. 전역에서 선언된 변수는 전체 프로그램에서 참조할 수 있다
브라우저 환경에서는 전역 변수들이 window
객체의 프로퍼티가 되며, 너무 많은 전역 변수 사용은 네임스페이스 오염으로 인한 충돌 가능성을 높인다.
1
2
3
4
5
6
7
8
9
| // 전역 스코프에 선언된 변수
let globalVariable = "누구나 접근 가능";
function someFunction() {
console.log(globalVariable); // "누구나 접근 가능" 출력
}
someFunction();
console.log(globalVariable); // "누구나 접근 가능" 출력
|
함수 스코프 (Function Scope)#
함수 내부에서 선언된 변수는 해당 함수 내에서만 유효한 스코프이다.
주로 var
키워드로 선언된 변수는 함수 내에서만 접근이 가능하다.
함수 외부에서는 해당 함수의 지역 변수를 참조할 수 없으므로, 데이터 은닉과 캡슐화에 유리하다.
동일한 이름의 변수를 전역과 지역에서 각각 선언할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function outerFunction() {
// 함수 스코프에 선언된 변수
var functionScopedVar = "함수 내부에서만 접근 가능";
console.log(functionScopedVar); // 정상 작동
function innerFunction() {
console.log(functionScopedVar); // 내부 함수에서도 접근 가능
}
innerFunction();
}
outerFunction();
// console.log(functionScopedVar); // 오류: functionScopedVar는 정의되지 않았습니다
|
블록 스코프 (Block Scope)#
ES6에서 도입된 let
과 const
키워드로 선언된 변수는 블록 스코프를 가진다.
블록은 중괄호 {}
로 둘러싸인 코드 영역이다.
조건문, 반복문 등의 블록 내에서 선언된 변수는 해당 블록을 벗어나면 접근할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function blockScopeExample() {
if (true) {
// 블록 스코프에 선언된 변수
let blockScopedVar = "블록 내부에서만 접근 가능";
const anotherBlockScopedVar = "이것도 블록 내부에서만 접근 가능";
console.log(blockScopedVar); // 정상 작동
console.log(anotherBlockScopedVar); // 정상 작동
}
// console.log(blockScopedVar); // 오류: blockScopedVar는 정의되지 않았습니다
// console.log(anotherBlockScopedVar); // 오류: anotherBlockScopedVar는 정의되지 않았습니다
}
blockScopeExample();
|
모듈 스코프 (Module Scope)#
ES6 모듈에서는 파일 단위로 스코프가 분리된다.
모듈 내에서 선언된 변수, 함수, 클래스 등은 기본적으로 모듈 내부에서만 유효하며, 외부로 공개하려면 export
를 사용해야 한다.
모듈 스코프는 전역 네임스페이스 오염을 방지하고, 보다 안전한 코드 조직화 및 재사용성을 제공한다.
1
2
3
4
5
6
7
8
| // module1.js
const privateVar = "비공개 변수";
export const publicVar = "공개 변수";
export function publicFunction() {
console.log(privateVar); // 모듈 내부에서는 접근 가능
return "공개 함수";
}
|
1
2
3
4
5
6
| // module2.js
import { publicVar, publicFunction } from './module1.js';
console.log(publicVar); // "공개 변수" 출력
console.log(publicFunction()); // "공개 함수" 출력
// console.log(privateVar); // 오류: privateVar는 정의되지 않았습니다
|
렉시컬 스코프 (Lexical Scope)#
JavaScript는 렉시컬 스코프(또는 정적 스코프)를 사용한다.
이는 함수가 선언된 위치에 따라 스코프가 결정된다는 의미이다.
함수 내에서 선언된 변수나 함수는, 그 함수가 선언된 환경(위치)에 따라 결정된 스코프에 접근할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| let outerVar = "외부 변수";
function outerFunction() {
let innerVar = "내부 변수";
function innerFunction() {
console.log(outerVar); // "외부 변수" 출력
console.log(innerVar); // "내부 변수" 출력
}
innerFunction();
}
outerFunction();
|
innerFunction
은 자신의 렉시컬 스코프 체인을 통해 outerFunction
의 변수와 전역 변수에 접근할 수 있다.
스코프 체인 (Scope Chain)#
JavaScript 엔진은 변수를 찾을 때 현재 스코프에서 시작하여, 변수를 찾지 못하면 바깥쪽 스코프로 이동하며 검색을 계속한다. 이러한 구조를 통해 내부 함수는 외부 함수나 전역에 선언된 변수에 접근할 수 있다.
이 과정을 스코프 체인이라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| var globalVar = 'global';
function outerFunction() {
var outerVar = 'outer';
function innerFunction() {
var innerVar = 'inner';
console.log(globalVar); // 출력: global
console.log(outerVar); // 출력: outer
console.log(innerVar); // 출력: inner
}
innerFunction();
}
outerFunction();
|
위 예시에서 innerFunction
은 자신의 스코프에서 변수를 찾지 못하면, 상위 스코프인 outerFunction
의 스코프를 확인하고, 그래도 없으면 전역 스코프를 확인하는 방식으로 변수를 검색한다.
클로저 (Closure)#
클로저는 함수와 함수가 선언된 렉시컬 스코프의 조합이다.
클로저를 통해 함수는 자신이 생성된 환경의 변수에 계속 접근할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| function createCounter() {
let count = 0; // 외부에서 직접 접근할 수 없는 private 변수
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.getCount()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
|
위 예시에서 increment
, decrement
, getCount
함수들은 모두 createCounter
함수의 렉시컬 환경에 대한 참조를 유지하고 있어 count
변수에 계속 접근할 수 있다.
실제 사용 사례와 모범 사례#
즉시 실행 함수 표현식 (IIFE)#
IIFE는 독립적인 스코프를 생성하여 전역 스코프 오염을 방지한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| (function() {
// 이 함수 내부의 변수는 외부에서 접근할 수 없음
let privateVar = "비공개 데이터";
function privateFunction() {
console.log(privateVar);
}
privateFunction(); // "비공개 데이터" 출력
})();
// console.log(privateVar); // 오류: privateVar는 정의되지 않았습니다
// privateFunction(); // 오류: privateFunction은 정의되지 않았습니다
|
스코프를 활용한 데이터 캡슐화#
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
| const createPerson = function(name, age) {
// 비공개 데이터
const privateData = { name, age };
return {
getName: function() {
return privateData.name;
},
getAge: function() {
return privateData.age;
},
setAge: function(newAge) {
if (newAge > 0 && newAge < 120) {
privateData.age = newAge;
}
}
};
};
const person = createPerson("홍길동", 30);
console.log(person.getName()); // "홍길동" 출력
console.log(person.getAge()); // 30 출력
person.setAge(31);
console.log(person.getAge()); // 31 출력
// privateData에 직접 접근 불가
// console.log(person.privateData); // undefined
|
스코프 관련 모범 사례#
가능한 한 변수의 스코프를 제한하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 좋지 않은 방법
let i, len;
function someFunction() {
for (i = 0, len = 10; i < len; i++) {
// 전역 변수 i와 len을 사용
}
}
// 더 좋은 방법
function betterFunction() {
for (let i = 0, len = 10; i < len; i++) {
// 블록 스코프 변수 i와 len을 사용
}
}
|
const 우선, let 차선, var 지양
1
2
3
4
5
6
| // 권장
const immutableValue = 42;
let mutableValue = 10;
// 권장하지 않음
var oldStyleVariable = 100;
|
함수 선언문 대신 함수 표현식 사용
1
2
3
4
5
| // 함수 선언문 (호이스팅됨)
function hoistedFunction() { /* … */ }
// 함수 표현식 (변수 호이스팅 규칙을 따름)
const functionExpression = function() { /* … */ };
|
스코프 관련 디버깅 팁#
Chrome 개발자 도구를 활용한 스코프 확인
- 브라우저 개발자 도구의 Sources 패널에서 디버거를 통해 현재 스코프에 있는 변수를 확인할 수 있다.
- 중단점(breakpoint)을 설정하면 Scope 패널에서 현재 스코프 체인의 모든 변수를 볼 수 있다.
‘스코프가 오염되었나?’ 확인하기
1
2
3
4
5
6
7
8
| "use strict"; // 엄격 모드 활성화
function findPotentialIssues() {
// 실수로 선언 없이 변수 할당 (전역 스코프 오염 방지)
// x = 10; // 엄격 모드에서는 오류 발생
let x = 10; // 올바른 선언
}
|
참고 및 출처#
Block JavaScript에서 블록 스코프(Block Scope) 는 중괄호({})로 감싸진 코드 블록 내에서 선언된 변수나 함수가 해당 블록 내부에서만 유효한 범위를 의미한다.
이는 코드의 구조와 가독성, 유지보수성에 큰 영향을 미치며, 변수의 생명 주기와 가시성을 결정짓는 중요한 개념이다.
블록 스코프는 JavaScript에서 변수의 가시성과 생명주기를 제어하는 강력한 개념이다:
ES6에서 let과 const 키워드를 통해 도입되었다. 중괄호({})로 둘러싸인 코드 블록 내에서만 변수에 접근할 수 있다. 메모리 효율성, 코드 구조화, 버그 방지에 도움이 된다. 일시적 사각지대(TDZ)를 통해 더 예측 가능한 변수 동작을 제공한다. 클로저와 결합하여 강력한 패턴을 구현할 수 있다. JavaScript에서 블록은 다음과 같은 상황에서 생성된다:
...
Function JavaScript의 함수 스코프(Function Scope)는 함수 내에서 선언된 변수의 가시성과 접근성을 정의하는 중요한 개념이다. 이 스코프는 JavaScript의 변수 관리 및 코드 구조에 큰 영향을 미친다.
함수 스코프의 정의 함수 스코프란 함수 내부에 선언된 변수와 함수가 해당 함수 내부에서만 접근 가능하다는 JavaScript의 특성을 의미한다.
이는 함수가 자신만의 독립적인 변수 환경을 가지고 있음을 뜻한다.
함수가 실행될 때 생성되고, 함수가 종료되면 메모리에서 사라진다.
1 2 3 4 5 6 7 8 9 function exampleFunction() { // 이 변수는 함수 내부에서만 접근 가능 var functionScopedVar = "함수 스코프 내부 변수"; console.log(functionScopedVar); // "함수 스코프 내부 변수" } exampleFunction(); // console.log(functionScopedVar); // ReferenceError: functionScopedVar is not defined 위 예시에서 functionScopedVar는 exampleFunction 내부에서만 존재하며, 함수 외부에서는 접근할 수 없다.
...
Global 자바스크립트에서 글로벌 스코프는 코드 전체에서 접근할 수 있는 가장 넓은 범위를 의미하며, 여기서 선언된 변수나 함수는 프로그램 전반에서 사용이 가능하다.
자바스크립트의 전역 스코프는 코드의 어느 위치에서나 접근할 수 있는 최상위 스코프이다.
전역 변수와 함수는 편리하게 사용할 수 있지만, 이름 충돌, 의도치 않은 수정, 메모리 관리 등의 문제를 일으킬 수 있다.
현대 자바스크립트 개발에서는 모듈 시스템, 클로저, 네임스페이스 패턴 등을 활용하여 전역 스코프의 사용을 최소화하고, 코드를 논리적인 단위로 구성하는 것이 좋은 관행으로 여겨진다.
...