ES6(2015) 이전에는 자바스크립트에서 변수를 선언할 수 있는 방법은 var
뿐이었습니다.
이것은 많은 문제를 발생시켰습니다.
- 전역 변수로 인한 스코프 충돌
- ES6이전에 스코프는
전역 스코프(Global Scope)
와함수 스코프(Function Scope)
만 있었습니다. 자바스크립트는 함수 스코프 외에는 모두 전역 스코프로 적용되기 때문에 전역 변수를 남발하여 스코프 충돌의 문제를 발생시켰습니다.
- ES6이전에 스코프는
- 변수 중복 선언
- 자바스크립트의 코드양이 많으면 많을 수록 스코프 충돌의 문제를 발생시킵니다. 이미 선언된 변수를 또
재선언
을 하거나다른 값을 넣거나
하는 문제를 발생할 수 있습니다.
- 자바스크립트의 코드양이 많으면 많을 수록 스코프 충돌의 문제를 발생시킵니다. 이미 선언된 변수를 또
보통 전역변수
로 인해 발생하는 문제로, 이를 해결하고자 ES6(2015)부터는 let
과 const
Keyword를 도입했습니다.
let
ES6(2015)
이전의 자바스크립트는 함수 내에서 선언된 변수 외 모든 코드 블록 내에서 선언된 변수는 전역 스코프(Global Scope)
를 갖습니다.
Block Scope
ES6(2015) 이전
var x = 10;
{
var x = 'change';
}
console.log(x);
change
x
에 10을 넣었지만, { } 블록 내부에 x
를 재선언 되었기 때문에 change
로 변경되어 출력되었습니다.
ES6(2015) let Keyword
let x = 10;
{
let x = 'change';
}
console.log(x);
10
{ }
코드 내부에 선언된 x
는 블록 스코프
를 따르기 때문에 외부에서 접근할 수 없습니다. 즉 재선언이 안된다는 말이죠.
따라서 전역 변수로 선언한 x=10
이 출력되는 것을 확인할 수 있습니다.
let x = 'Global'; // 전역 변수
{
let x = 'x Local'; // 지역 변수
let y = 'y Local'; // 지역 변수
}
console.log(x); // Global
console.log(y); // ReferenceError: bar is not defined
Global
Uncaught ReferenceError: y is not defined
at <anonymous>:9:13
전역 스코프를 갖는 let x='Global'
는 { }
코드 내부에 선언한 let x = 'x Local'
로 재선언되지 않으며
y
는 블록 스코프
를 따르기 때문에 외부에서 접근할 수 없기 때문에 console.log
시 ReferenceError
오류를 확인할 수 있습니다.
재선언 금지
var
키워드는 동일한 이름을 갖는 변수를 몇 번이고 재선언
을 할 수 있습니다. 하지만 let
키워드는 동일한 이름으로 재선언
을 할 수 없습니다.
중복 선언하게 되면 문법 에러(SyntaxError)
를 발생시킵니다.
var x = 10; // 최초 선언
var x = 20; // 재 선언
let y = 10; // 최초 선언
let y = 20; // 재 선언
console.log(x);
console.log(y);
Uncaught SyntaxError: Identifier 'y' has already been declared
호이스팅
자바스크립트에서 선언된 모든 것(var
, let
, const
, function
, class
)을 호이스팅이라고 합니다.
호이스팅(Hoisting)이란, var
선언문이나 function
선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말한다.
예시
를 먼저 보겠습니다.
console.log(x);
var x = 30;
undefined
변수 x
를 선언하기 전에 console.log
로 출력을 했는데 x is not defined
오류가 발생하지 않았습니다. 왜 그런지 알아봅시다.
변수는 3단계를 걸쳐 생성됩니다.
1단계: 선언 단계(Declaration phase)
- 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.
2단계: 초기화 단계(Initialization phase)
- 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다. 이 단계에서 변수는 undefined로 초기화된다.
3단계: 할당 단계(Assignment phase)
- undefined로 초기화된 변수에 실제 값을 할당한다.
var
는 선언 단계
와 초기화 단계
가 한 번에 실행됩니다. 스코프에 변수를 등록(선언 단계)
하여 메모리에 공간을 확보한 뒤,
undefined
로 초기화(초기화 단계)
합니다. 이러한 이유로 변수선언문 이전에 변수에 접근하여도 에러가 발생하지 않고 undefined
를 반환한 것입니다.
이후 변수에 값을 할당(할당 단계)
해야 비로소 값이 할당 되는 것입니다. 이런 현상을 변수 호이스팅(Variable Hoisting)이라 합니다.
// 스코프의 선두에서 선언 단계와 초기화 단게가 한 번에 이루어 집니다.
// 따라서, 변수 선언 이전에 변수에 참조할 수 있습니다.
console.log(x); // undefined
var x; // 선언 단계
console.log(x); // undefined
x = 10; // 할당 단계
console.log(x); // 10
undefined
undefined
10
let은 선언 단계와 초기화 단계가 분리되어 진행합니다.
let은 스코프에 변수를 등록(선언단계)은 하지만 초기화 단계는 변수 선언문에 도달했을 때 실행합니다. 따라서 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)
가 발생합니다.
그 이유는 초기화 단게가 이루어지지 않았기 때문입니다. 즉, 변수를 담을 메모리 공간이 확보되지 않았기 때문입니다.
정리해서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없습니다. 이 구간을 일시적 사각지대(Temporal Dead Zone; TDZ)
라고 부릅니다.
// 스코프의 선두에서 선언 단계가 이루어집니다.
// 변수 초기화 단계는 이루어지지 않았습니다.
// 따라서, 변수 선언 이전에 변수에 참조할 수 없습니다.
console.log(x); // Uncaught ReferenceError: x is not defined
let x; // 선언 단계
console.log(x); // undefined
x = 10; // 할당 단계
console.log(x); // 10
Uncaught ReferenceError: x is not defined
at <anonymous>:3:15
그럼 let은 호이스팅이 안되는게 아닐까?
let x = 10;
{
console.log(x);
}
10
결과를 보면 선언단계
와 초기화단계
가 먼저 이루어졌기 때문에 정상적으로 결과값이 출력됩니다.
아래 코드에 let
선언을 추가해봅니다.
let x = 10;
{
console.log(x);
let x = 20;
}
Uncaught ReferenceError: Cannot access 'x' before initialization
전역변수 x
가 console.log
에 출력될 것 처럼 보일 수 있다. 하지만, { }
블록안에서 호이스팅이 발생하기 때문에 참조 에러(ReferenceError)
가 발생하게 됩니다.
ES6
의 let
과 const
키워드는 코드블록 내에서는 블록 스코프를 따르므로 { }
블록 내에 선언 된 let x
는 지역 변수로 적용됩니다. 따라서 { }
블록 안 스코프에서 호이스팅이 적용되고 코드 블록의 선두에서 초기화가 이루어 지는 지점까지는 일시적 사각지대(TDZ)
에 빠지게 됩니다. 그렇기 때문에 참조 에러(ReferenceError)
가 발생하게 됩니다.
let은 전역 객체가 아니다
전역 객체(Global Object)
란 모든 객체의 최상위 객체를 의미합니다.
Browser-side에서는 window 객체
, Server-side(Node.js)에서는 global 객체
를 의미합니다.
여기서 var
로 선언된 변수를 전역 변수로 사용하게 된다면 전역 객체
의 프로퍼티가 됩니다.
하지만, let
로 선언된 변수를 전역 변수로 사용하게 되더라도 전역 객체
의 프로퍼티가 아닙니다.
다시말해, window 객체
로 접근할 수 없으며, global 객체
에만 접근할 수 있습니다.
var x = 'Global Object'; // var 키워드 선언
console.log(window.x); // var 키워드는 전역 객체(Global Object)의 프로퍼티가 되므로 window 객체 접근 가능
Global Object
let x = 'Global Object'; // let 키워드 선언
console.log(window.x); // let 키워드는 전역 객체(Global Object)의 프로퍼티가 되므로 window 객체 접근 불가능
undefined
const
const
는 상수(변하지 않는 값)을 위해 사용하는 키워드입니다.
const
는 let
과 대부분 비슷하지만 다른점에 대해서만 알아보겠습니다.
선언과 초기화
let
의 특징
- 중복선언 불가능
let x=20; // 최초 선언 let x=50; // 중복 선언
Uncaught SyntaxError: Identifier 'x' has already been declared
- 재할당 가능
let x=30; // 최초 선언 x =20; // 재할당 console.log(x); // 20 출력
20
const
의 특징
- 중복선언 불가능
const x=20; // 최초 선언 const x=50; // 중복 선언
Uncaught SyntaxError: Identifier 'x' has already been declared
- 재할당 불가능
const x=30; // 최초 선언 x =20; // 재할당시 문법 에러 발생
Uncaught TypeError: Assignment to constant variable.
-
const의 키워드는 반드시 선언과 동시에 할당이 이루어져야 합니다.
그렇지 않으면 이미 선언된 변수에 값을 할당할 수 없습니다. 그 이유는 재할당이 안되기 때문입니다.const x; // 초기화 x =20; // 할당
Uncaught SyntaxError: Missing initializer in const declaration
const의 객체
-
const
는 재할당이 되지 않는다고 말씀드렸습니다. 하지만, 객체의 프로퍼티는 변경할 수 있습니다.const obj = {'name' : 'seongsik'}; obj.name = 'good'; console.log(obj);
{name: 'good'}
const 키워드는 객체의 내용이 변경(추가, 변경, 삭제)되더라도 객체 타입 변수에 할당된 메모리 주소값은 변경되지 않습니다.
따라서, 객체 타입 변수 선언에는 const
를 사용하는 것이 좋습니다. 만약 객체 타입 변수의 주소값을 변경(재할당)해야 한다면 let
을 사용합니다.
끝으로
-
ES6(2015)+
를 사용한다면var
를 사용하지 않는 것이 좋습니다. - 자바스크립트 코드양이 많아질 수록 의도치 않은 재할당 실수를 하게되는데 이를 방지하기 위해
const
를 사용합니다. - 재할당이 필요한 경우에는
let
을 사용합니다.