
지금까지 var, let, const의 개념을 간단하게 생각하고 사용하였지만 "this"키워드에 대한 포스팅을 정리하면서 변수 타입에 관해 생각지도 못한 난관을 만나게 되었다.. var, let, const 키워드의 선언 범위와 부딪혔던 난관이 어떤 문제였는지 정리해 보자.
var, let, const은 JavaScript에서 변수를 선언할 때 사용하는 키워드이다.
var은 ES6이전까지 변수를 선언할 수 있었던 유일한 키워드였다.
var키워드는 다른언어의 변수 선언에서 보지못한 색다른 특징들이 있다.
1. 변수 중복 선언 가능
var키워드는 중복선언이 가능하다.
var number = 42;
var number = 29;
console.log(number);
위와 예시와 같이 똑같은 변수명을 가진 변수를 재선언하면 해당 값이 기존의 "number"값을 덮어씌워버리고 값이 바뀌게 된다.
2. var 키워드 생략 가능
number = 42;
number = 29;
console.log(number);

암묵적으로 var키워드를 생략해서 변수를 선언할 수 있다.
따지고보면 var키워드를 생략한거랑 var키워드를 선언한거는 다르지만 둘의 성격은 똑같다.
3. 변수 호이스팅(초기화)
var는 const, let과 마찬가지로 호이스팅(Hoisting)이 일어나지만 초기화까지 한번에 발생한다.
4. 함수레벨 스코프(Function Level Scope)
var는 함수레벨 스코프의 성격을 지닌 변수 타입이다.
해당 개념에 대해서는 아래에서 자세하게 설명할 것이다.
5. 전역객체속성
전역에서 var로 선언한 변수는 전역객체(window)의 속성으로 들어간다.
const키워드는 ES6에서 새롭게 등장한 변수 타입이다.
const는 constant의 약자이며 변하지 않는 상수를 뜻한다.
const에도 몇가지 특징이 있다.
1. 선언과 동시에 초기화
const number;
console.log(number);
선언과 동시에 초기화를 하지 않으면 위 처럼 에러가 난다.
2. 중복 선언 금지
const number = 42;
const number = 29;
console.log(number);
const로 선언한 변수를 재선언하면 위와같이 에러가 난다.
3. 중복 할당 금지
const number = 42;
number = 29;
console.log(number);
이미 할당한 const변수를 다른 값으로 재할당하려고 하면 위와같이 에러가 난다.
4. 변수 호이스팅(초기화 X)
변수의 초기화까지 발생하는 var의 호이스팅과는 별개로 실행컨텍스트의 Record에 선언만 할뿐 초기화는 하지 않는다.
5. 블록레벨 스코프(Block Level Scope)
const는 앞으로 나올 let과 마찬가지로 블록레벨 스코프의 성격을 지닌다.
6. 전역변수
var과는 달리 const로 선언한 전역변수는 전역객체(window)의 속성으로 들어가는게 아닌 블록레벨스코프의 전역변수로 정의된다.
let키워드는 ES6에서 새롭게 등장한 변수 타입이다.
let에도 몇가지 특징이 있다.
1. 변수 중복 선언 금지
let number = 42;
let number = 29;
console.log(number);
같은 변수명을 재선언하는 경우 위와같이 에러가 난다.
2. 변수 재할당 가능
let number = 42;
number = 29;
console.log(number);
const와는 다르게 변수 재할당이 가능하다.
3. 변수 호이스팅(초기화 X)
변수의 초기화까지 이뤄지는 var과는 달리 선언만 이뤄지고 초기화는 발생하지 않는다.
4. 블록레벨 스코프(Block Level Scope)
const와 마찬가지로 블록레벨 스코프의 성격을 지니고 있다.
5. 전역변수
const와 마찬가지로 let으로 선언한 전역변수는 전역객체(window)속성으로 들어가는게 아닌 블록레벨스코프의 전역변수로 정의된다.
그래서 블록레벨 스코프와 함수레벨 스코프는 뭘까?

먼저 스코프는 변수와 함수의 유효범위를 나타낸다.
즉, 어떤 변수와 함수가 어디에서 접근가능하지를 결정하는걸 스코프라고 한다.
c, c++ 등등 대부분의 프로그래밍 언어는 Block Level Scope를 따르지만 JavaScript에서는 함수 레벨 스코프(Function Level Scope)를 따른다.
함수 내에서 선언된 변수는 함수 내에서만 유효하고 함수 외부에서는 참조할 수 없다.
즉, 함수 내부에서 선언한 변수는 지역변수이고 함수 외부에서 선언한 변수는 전역변수이다.
var number = 42;
function foo() {
var number = 29;
}
foo();
console.log(number);
위와같이 전역에서 "number"변수의 값으로 42를 할당하였고 "foo"함수 내부에서 "number"변수를 29라고 재선언하였지만 console.log의 출력값에는 42가 나오게 된다.
"foo"함수 내부에 있는 "number"변수는 var로 선언하여서 함수레벨스코프 유효범위를 가지게 되어서 전역으로 선언한 42가 출력된 것이다.
그럼 위 코드를 함수 내부가 아닌 코드블록으로 감싸면 어떻게 될까?
var number = 42;
if (true) {
var number = 29;
}
console.log(number);
위와같이 var변수는 함수레벨스코프의 성격을 지니고 있어서 코드블록 내부에서 재선언한 변수 정의가 반영되는걸 확인할 수 있다.
블록레벨 스코프는 코드블록{}내에서 선언된 변수만 블록내부에서 유효하고 블록외부에서는 참조할 수 없다.
즉, 블록 내부에서 선언한 변수는 지역변수가 된다.
let number = 42;
if (true){
let number = 29;
console.log("inner block : ", number)
}
console.log("outer block : ", number);
위와같이 함수내부가 아니더라도 블록 {}으로 감싸진 코드블록 내부에서 선언한 변수(let, const)는 해당 블록 내부에서만 유효한 범위이고 외부에서는 접근을 못하게 된다.
"number"변수의 값이 42인 변수는 전역변수이고 if문 코드블록 내부에 있는 "number"변수는 지역변수인 것이다.
따라서 if문 안에 있는 console.log의 출력은 29가 나오고 if문 외부에 있는 console.log의 출력은 42가 나온 것이다.
var number = 42;
function foo() {
var number = 29;
console.log(window.number);
}
foo();
위 코드를 실행시키면 console.log에 어떤 값이 출력될까?
여기서 다시 한번 기억하고 가야할 점은 var변수는 전역객체(window)의 속성으로 정의된다는 것이다.
위와 같이 함수 레벨 스코프의 성격을 지닌 var는 함수 코드 블록에서 선언한 var을 지역변수로 정의하고 함수인에서 전역 객체(window) 속성에 정의되지 않는다.
그럼 전역 객체(window)속성으로 정의된 var number = 42를 const로 바꾸면 어떤 값이 출력 될까?
const number = 42;
function foo() {
console.log(window.number);
}
foo();
위와같이 "undefined"값이 나온다.
왜 이런걸까?
이유를 알아보기 전에 let으로도 한번 테스트 해보자
let number = 42;
function foo() {
console.log(window.number);
}
foo();
const와 마찬가지로 "undefined"값이 나온다.
여기서 알수있는 것은 const, let은 전역에서 정의한다고 해도 전역객체(window)에 정의하지 않는 점이다.
즉, 전역으로 지정한 const, let은 보이지 않는 코드블록 내에 정의되어서 전역변수가 된 것이다.
let number = 42;
console.log(window.number);
함수로 감싸진 형태가 아니여도 똑같이 "undefined"값이 출력된걸 확인할 수 있다.
해당 포스팅 내용을 더 알아보기 위해 여러 글을 구글링 하다 보니 "const로 선언한 객체일 경우 재할당을 방지해 줘서 안전하다."라고 애매모호하게 설명한 글을 봤는데 해당 문장은 반은 맞고 반은 틀렸다.
const로 선언한 객체의 경우 객체 자체의 재할당은 막아주지만 객체 안의 속성까지는 재할당을 막지 않는다.
const obj = {
name: "sangyeki",
};
obj.name = "kim";
console.log(obj.name);
위와같이 객체안의 속성을 재할당한 경우 해당 값으로 변경된 걸 확인할 수 있다.
이 글을 포스팅한 이유는 this키워드를 정리하다보니 아래 코드에서 아리송한 결과가 나와서 정리하게 되었다.
const name = "sangyeki";
function foo() {
console.log(this.name);
}
foo();
여기서 this키워드는 중첩된 함수가 아닌 단일 일반함수 내부에서 사용했으므로 window객체를 가르키게 된다.
내 예상으로는 "sangyeki"가 출력되어야 하는데 아무런 값도 출력되지 않는 걸 보고서 순간적으로 "뭐지?"라는 생각만 들었고 해당 출력에 이유가 뭔지 곰곰이 생각을 해봤다.
호스팅부터 클로저까지 별의별 개념을 접목해서 생각해 봤지만 결론이 나오지 않길래 여러 관련 자료와 변수의 선언부터 다시 살펴보니 변수마다 스코프의 차이가 있다는 걸 특정 글을 통해 알아냈다..
변수의 타입과 선언이라는 게 어떻게 보면 기초적인 부분이고 포스팅하기에도 부끄러운 글이지만 기억 속에 오래 남기 위해서는 정리하여 포스팅하는 게 맞는다고 생각하여 위 글을 정리하게 되었다.
이번 포스팅을 통해서 얻은 건 내가 알고 있다고 생각하는 개념도 다시 한번 깊게 알아보고 앞으로 마주칠 수많은 에러를 대비해서 아래부터 쌓아 올린 지식을 토대로 빠르게 고쳐나가는 개발자가 돼야 한다는 걸 깊게 뇌우 쳤다.
https://poiemaweb.com/es6-block-scope
https://velog.io/@rohkorea86/%EC%9E%91%EC%84%B1%EC%A4%91-%EC%A0%84%EC%97%AD%EB%B3%80%EC%88%98-const-var-%EB%91%98-%EC%A4%91-%EC%96%B4%EB%96%A4-%ED%82%A4%EC%9B%8C%EB%93%9C%EB%A5%BC-%EA%B3%A8%EB%9D%BC%EC%95%BC-%ED%95%A0%EA%B9%8C-feat-window-this