JavaScript 변수 선언 및 특징
JavaScript ES6(ECMAScript6)를 기준으로 변수 선언과 그에 따른 특징에 대해 알아보겠습니다.
1. 변수 선언
JavaScript ES5에서의 변수 선언은 var 키워드를 사용하는 것이 유일한 방법이었습니다. var 키워드를 사용한 변수 선언은 다음과 같은 특징과 문제가 있습니다.
- 함수레벨 스코프로 동작 (전역 변수의 남발 및 코드 복잡도 증가)
- 변수 선언시 var 키워드 생략 허용 (변수의 의도하지 않은 전역화)
- 중복 선언 허용 (변수의 의도하지 않은 값 변경)
- 변수 호이스팅 (변수 선언 이전에 참조 가능)
이러한 특징은 대부분 전역 변수의 선언과 사용으로 인하여 발생하며 코드 복잡도를 높이는 원인이 됩니다. ES6에서는 var 키워드의 단점을 보완하기 위해 let과 const 키워드를 도입하였습니다.
2. let
ES6에 도입된 let 키워드에 대한 내용입니다.
2.1 스코프 (Scope)
대부분의 언어는 블록 레벨 스코프를 지원하지만 JavaScript는 함수 레벨 스코프를 지원합니다. 다음은 함수 레벨 / 블록 레벨 스코프의 특징입니다.
- 블록 레벨 스코프
let과 const 키워드
코드 블록 내부에서 선언한 변수는 코드 블록 내에서만 유효하며 외부에서 참조 불가. - 함수 레벨 스코프
var 키워드
함수 내부에서 선언한 변수는 지역 변수이고 함수 외부에서 선언한 변수는 전역 변수
함수 내부에서 선언한 변수는 함수 내에서만 유효하며 외부에서 참조 불가.
다음 예제를 통해 키워드별 스코프의 차이에 대해 확인해보겠습니다.
1
2
3
4
5
6
7
8
9
|
console.log(varKeyword); // undefined
var varKeyword = 'varKeyword';
console.log(varKeyword); // varKeyword
{
var varKeyword = 123;
}
console.log(varKeyword); // 123
|
cs |
var 키워드는 함수 레벨 스코프로 동작하기 때문에 예제에서 선언한 변수 varKeyword는 전역 변수로 동작합니다. var 키워드는 중복 선언이 허용되므로 코드 블록에서 다시 선언을 하여 값을 할당해도 문제없이 동작하는 것을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
|
let letKeyword1 = 'letKeyword';
{
let letKeyword1 = 123;
let letKeyword2 = 456;
}
console.log(letKeyword1); // letKeyword
console.log(letKeyword2); // Error
|
cs |
let 키워드로 선언된 변수는 블록 레벨 스코프로 동작합니다. 다른 코드 블록에 선언된 변수가 변수명이 같더라도 let 키워드로 선언되었다면 전역에 선언된 변수와는 다른 변수로 동작합니다. 예제에서는 코드 블록 내부에서 선언한 변수는 외부에서 참조가 불가능하기 때문에 에러가 발생하는 것을 확인할 수 있습니다.
2.2. 중복 선언 금지
let 키워드는 var 키워드와 다르게 중복 선언을 허용하지 않습니다. 다음 예제를 통해 let 키워드로 변수 중복 선언시 에러가 발생하는 것을 확인할 수 있습니다.
1
2
|
let letKeyword = 'letKeyword';
let letKeyword = 123; // Error
|
cs |
2.3. 호이스팅 (Hoisting)
JavaScript는 모든 선언(var, let, const, function, function*, class)에 대해 호이스팅합니다. 호이스팅은 선언문을 해당 스코프의 선두로 옮겨서 동작하는 것처럼 만드는 특성을 의미합니다.
하지만 let과 const 키워드의 경우엔 변수 선언 이전에 참조하면 에러(Reference Error)가 발생합니다. let과 const 키워드로 선언된 변수는 이러한 경우에 스코프의 시작에서 변수 선언 단계 이전까지 일시적 사각지대(Temporal Dead Zone)에 빠지게 됩니다.
호이스팅에 대한 자세한 설명에 앞서서 변수 생성 단계에 대해 알아보겠습니다.
변수 생성 단계 | 내용 |
선언 단계 (Declaration phase) | 변수를 실행 context의 변수객체(Variable Object)에 등록. (변수객체는 스코프가 참조하는 대상) 이 단계에서 변수를 참조할 경우 참조에러(Reference Error)가 발생. |
초기화 단계 (Initialization phase) | 변수객체에 등록된 변수의 공간을 메모리에 할당. 변수는 undefined로 초기화 됨. |
할당 단계 (Assignment phase) | undefined로 초기화된 변수에 값을 할당. |
위의 변수 생성 단계를 기준으로 var 키워드로 선언된 변수는 선언/초기화 단계가 한번에 이루어집니다. 따라서 변수객체에 변수를 등록하고 변수를 메모리에 할당하여 undefined로 초기화하는 과정이 한번에 이루어지므로 변수 선언 이전에 변수에 접근해도 에러가 발생하지 않습니다. 이러한 현상을 호이스팅이라고 합니다.
반면에 let과 const 키워드로 선언된 변수는 선언과 초기화 단계가 분리되어 진행됩니다. 따라서 let과 const 키워드의 초기화 단계는 변수 선언문에 도달했을 때 이루어지고, 초기화 이전에 변수에 접근할 경우 참조 에러(Reference Error)가 발생하게 됩니다.
1
2
3
4
5
6
|
let letVal = 123; // 전역 변수
{
console.log(letVal); // ReferenceError
let letVal = 456; // 지역 변수
}
|
cs |
let 키워드로 선언된 변수는 블록 레벨 스코프로 동작하므로 블록 레벨 스코프 단위로 호이스팅이 이루어집니다. 따라서 위의 예제에서 코드 블록 내의 변수는 참조 에러가 발생하는 것을 확인할 수 있습니다.
2.4. 클로저 (Closure)
클로저는 내부 함수가 외부 함수의 context에 접근할 수 있는 것을 의미합니다. 흔히 함수 내에 함수를 정의하여 리턴하는 방식으로 사용합니다. 내부 함수는 외부 함수의 파라미터, 지역변수 등을 외부 함수가 종료된 이후에도 사용할 수 있습니다. 이렇게 사용하는 변수를 자유 변수(free variable)라고 하며 자유 변수를 갖는 함수를 클로저라고 합니다.
클로저에 대한 보다 자세한 내용은 이후에 정리하고 여기서는 필요한 내용에 대해 예제를 통해 확인해보겠습니다.
다음 예제는 0부터 9까지 출력하는 반복문처럼 보이지만 실행해보면 10이 열번 출력되게 됩니다.
1
2
3
4
5
|
for (var i=0; i<10; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
|
cs |
setTimeout에 파라미터로 넘긴 function()은 0.1초 간격으로 실행되는데, 그 전에 반복문이 모두 순회하여 전역변수 i의 값이 10이 되기 때문입니다. var 키워드로 선언한 변수 i 는 함수 레벨 스코프로 동작하기 때문에 전역 스코프를 갖게되어 이러한 문제가 발생하게 됩니다.
이러한 문제를 해결하기 위해서는 다음과 같이 클로저를 사용해줍니다.
1
2
3
4
5
6
7
|
for (var i=0; i<10; i++) {
(function(index){ // index(자유변수)
setTimeout(function() {
console.log(index);
}, 100);
}(i));
}
|
cs |
이렇게 하면 내부 함수는 반복문의 동작에 따라 별도로 10개의 스코프를 갖게 되고, 외부 함수에서 파라미터로 받은 i 값도 마찬가지로 각각 다른 값을 갖게 됩니다.
이 경우에 다음과 같이 let 키워드를 사용하게 되면 클로저를 사용하지 않아도 동일한 결과를 확인할 수 있습니다.
1
2
3
4
5
|
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
|
cs |
for문에서 let 키워드로 선언한 변수 i 는 for문 내에서만 유효한 지역 변수이며 자유 변수로 동작합니다.
2.5. 전역 객체와의 비교
JavaScript의 전역 객체(Global Object)는 모든 객체의 최상위 객체를 의미합니다. 일반적으로 브라우저 사이드에서는 window 객체, 서버 사이드(Node.js)에서는 global 객체를 의미합니다.
다음과 같이 var 키워드로 선언된 변수를 전역 변수로 사용하면 전역 객체의 프로퍼티가 됩니다.
1
2
|
var varVal = 123; // 전역변수
console.log(window.varVal); // 123
|
cs |
반면에 let 키워드로 선언된 변수를 전역 변수로 사용하는 경우는 전역 객체의 프로퍼티가 아니게 됩니다.
1
2
|
let letVal = 123; // 전역변수
console.log(window.letVal); // undefined
|
cs |
3. const
ES6에 도입된 const 키워드에 대한 내용입니다.
3.1 선언과 초기화
const는 상수를 위해 사용하는 키워드이고 가독성과 유지보수의 편의를 위해 많이 사용합니다. 하지만 반드시 상수에만 사용하는 것은 아닙니다. 특징은 앞서 살펴본 let과 거의 동일하며 블록 레벨 스코프로 동작합니다.
const는 다음과 같이 선언과 동시에 할당이 이루어져야 하고 재할당이 금지됩니다.
1
|
const CONST_VAL; // SyntaxError
|
cs |
1
2
|
const CONST_VAL = 123;
CONST_VAL = 456; // TypeError
|
cs |
3.2 const와 객체
const로 선언한 변수에는 객체도 할당하여 사용할 수 있지만 재할당은 금지됩니다. 따라서 const로 선언한 객체 타입의 변수에 할당된 객체의 주소값은 변경되지 않습니다.
또한 객체의 재할당은 불가능하지만 객체의 프로퍼티는 변경이 가능합니다. 이러한 특징으로 인하여 객체 타입의 변수를 선언할 때는 const를 사용하는 것이 좋습니다.
1
2
3
4
5
6
7
8
9
10
11
|
const object = {
name: 'harry'
};
object.name = 'kim';
console.log(object.name); // kim
// TypeError
object = {
name: 'john'
};
|
cs |
4. var & let & const
다음은 var, let, const 각 키워드에 대해 사용이 권장되는 경우입니다.
- JavaScript ES6를 사용하는 경우 var 키워드는 사용하지 않을 것
- let 키워드는 재할당이 필요한 변수에 사용
- const 키워드는 재할당이 필요없는 변수(기본 자료형/객체형)에 사용
5. 템플릿 리터럴 (Template Literal)
템플릿 리터럴은 ES6에서 도입된 문자열 표기법입니다. 일반적인 따옴표 대신에 백틱(backtick) 문자(`)를 사용합니다.
백틱 문자(`)는 작은따옴표와 큰따옴표를 혼용할 수 있으며 줄바꿈과 공백(white-space)이 그대로 적용됩니다.
1
2
3
4
5
6
|
const backtickTemplate = `
This is
'backtick'
"template".
`;
console.log(backtickTemplate);
|
cs |
또한 + 연산자를 이용하지 않아도 간단하게 문자열을 삽입할 수 있는 기능을 제공합니다.
1
2
3
|
const firstName = 'John';
const lastName = 'Wick';
console.log(`My name is ${firstName} ${lastName}.`); // My name is John Wick.
|
cs |
템플릿 리터럴에 사용하는 ${expression} 형태의 문법은 템플릿 대입문이라고 하는데 문자열 뿐만 아니라 JavaScript 표현식도 사용할 수 있습니다.
1
2
3
4
|
console.log(`1 + 1 = ${1 + 1}`);
const text = 'world';
console.log(`Hello ${text.toUpperCase()}`); // Hello WORLD
|
cs |
이상으로 JavaScript의 변수 선언 및 특징에 대해 알아봤습니다.
※ Reference
- 이웅모 지음, 『Angular Essentials』, 루비페이퍼(2018), p21 ~ p31. 3.1 let, const와 블록 레벨 스코프, 3.2 템플릿 리터럴
- poiemaweb.com, let, const와 블록 레벨 스코프, https://poiemaweb.com/es6-block-scope
- hyunseob.github.io, JavaScript 클로저(Closure), https://hyunseob.github.io/2016/08/30/javascript-closure/
- offbyone.tistory.com, 자바스크립트 클로저(Javascript Closure) 사용법, https://offbyone.tistory.com/135