자바스크립트에서는 var라는 변수를 사용하는 코드들을 많이 접하였다. 그래서 자바스크립트 변수는 var만 있는 줄 알았다. 하지만 let이라는 변수가 등장하면서 많은 사람들이 var 보다는 let 사용을 권장하였다. var, let, const의 기능과 범위 등에 대해 살펴보고 그 이유를 알아보도록 하겠다.
1. 변수의 선언과 (재)할당이란?
- 변수를 선언한다는 것은 데이터를 저장할 공간을 생성하는 것과 같다.
- 할당이란 선언한 변수 공간에 데이터를 집어 넣는 것이다. 재할당은 기존에 할당된 데이터를 바꾸는 것이다.
변수 종류 | 재선언 | 재할당 |
var | 가능 | 가능 |
let | 불가능 | 가능 |
const | 불가능 | 불가능 |
- let은 값을 다시 바꿀 수 있지만 기존에 선언된 변수를 다시 선언할 수는 없다. 따라서 변수 이름을 중복해서 만드는 오류를 방지할 수 있기 때문에 var 보다는 let 사용을 권장한다.
- const는 constant(상수)로 한번 선언되면 다시 선언 및 할당이 되지 않는다.
const 사람 = { 이름 : 'Jo' }
사람.이름 = 'Park'; // const 변수를 재할당한 것이 아니라, 변수 내부의 값을 그저 변경한 것임.
// 수정할 수 없는 오브젝트 만들기
Object.freeze(사람);
- 오브젝트 변수의 경우 오브젝트 내의 데이터를 바꾸는 것은 const여도 가능하다. 이는 변수를 재할당하는 것이 아니라 변수 내의 데이터를 그저 변경하는 것이기 때문이다.
- 수정이 불가능한 오브젝트를 만드려면 freeze 함수를 사용하면 된다. (단, 오브젝트 내의 또 다른 오브젝트까지 불변되지는 않는다.)
2. 변수의 범위
- 전역변수가 아닌 지역변수의 경우 변수의 타입에 따라 범위가 달라진다. var의 경우 function, let/const의 경우 {} 가 범위이다. 따라서 for문 if문 등등 function 보다 더 좁은 범위를 갖는다.
3. 변수의 호이스팅
- 자바스크립트 인터프리터가 코드를 위에서 한줄씩 코드를 읽어나간다. 하지만 우리가 선언한 변수나 함수의 경우 위치에 관계없이 최상단에 미리 선언이 되는 현상을 호이스팅이라고 한다.
// 작성한 코드
function 함수(){
console.log('hello');
var 이름 = 'Son';
}
// 해석하는 순서
function 함수(){
var 이름;
console.log('hello');
이름 = 'Son';
}
- 여기서 호이스팅으로 인해 변수가 최상단에 선언되지만 할당은 되지 않는다. 내가 작성한 코드를 만났을 때 비로소 할당이 되는 것이지 맨 처음에는 변수가 선언만 된다.
함수();
function 함수() {
console.log(인사);
let 인사 = 'Good Morning!';
}
- 함수의 실행보다 함수의 선언이 더 밑에 있기 때문에 함수 실행에 오류가 있을거라고 생각하기 쉽다. 하지만 호이스팅을 알았다면, 함수가 정상적으로 실행됨을 알 수 있다. 또한 함수의 출력부분에서 let 변수도 호이스팅이 된다. 여기서 let이 아닌 var를 사용할 경우 호이스팅이 되면서 '인사'를 출력할 때 undefined, 라는 정의되지 않은 변수를 출력한다. 하지만 let을 사용할 경우 변수에 접근할 수 없다는 ReferenceError가 발생한다. var의 경우 호이스팅이 됨과 동시에 undefined가 자동으로 할당되지만 let과 const는 해당되지 않는다. 할당하지 않은 값을 출력하고 싶지 않고 에러문을 발생하는 상식적인 코드를 만들기 위해서는 var보다는 let을 권장한다.
함수();
var 함수 = function(){
console.log(인사);
var 인사 = 'Good Morning';
}
// 코드 진행 순서
var 함수;
함수(); // 아직 함수가 할당되지 않았는데 함수를 실행한다는 변수() 를 사용함
함수 = function(){
console.log(인사);
var 인사 = 'Good Morning';
}
- 함수를 생성할 때 변수에 담아서 선언한 케이스다. 이처럼 변수에 함수를 담을 경우엔 함수라는 이름의 변수만 호이스팅 된다. (호이스팅은 선언만 됨) 따라서 함수 변수의 할당 부분인 실제 함수는 나중에 할당된다. 즉, 함수의 실행보다 나중에 할당이 되는 것이다. 따라서 위 코드를 실행하면 함수가 아니다라는 에러를 발생한다.
let a = 1;
var 함수 = function(){
a = 2;
}
console.log(a);
- let으로 선언된 a는 재선언은 안되지만 재할당은 가능하다. 그리고 var 함수에서 a를 재할당하는 함수를 작성하였다. 이를 실행하면 a가 재할당되어 a는 1이 아닌 2가 출력된다. 하지만 지금 함수를 만들기만 하였지 실제로 '함수();' 식으로 실행하지 않았다. 따라서 a는 2가 아닌 1이 출력된다. (재할당하는 함수가 실행되지 않았기 때문에) 함수를 단순히 선언만 하였고 실행문을 작성하지 않으면 함수는 실행되지 않음을 명심하도록 하자!
let a = 1;
var b = 2;
window.a = 3;
window.b = 4;
console.log(a + b);
- 전역변수를 선언하는 방법은 크게 두 가지가 있다. 첫 번째는 함수 안이 아닌 밖에서 선언하면 전역변수가 된다. 두 번째는 window.변수 라고 선언하는 방법이다. 두 번째의 경우 전역변수라는 의미가 있기에 나중에 변수를 구분하기 용이하므로 두 번째 방법을 권장한다. window라는 최상위 오브젝트의 변수 데이터가 곧 전역변수이기 때문에 window.변수라는 방식으로 선언이 가능한 것이다.
- a과 b가 각각 let과 var로 선언과 할당이 되었다. (초기화 = 선언 + 할당) window.a의 경우 a를 다시 전역변수로 재선언한 코드이다. 하지만 let은 재선언이 불가능하기 때문에 `window.a = 3`은 실행되지 않는다. window.b의 경우 var로 선언된 b 변수를 다시 전역변수로 재선언 하였기 때문에 실행이 가능하며, b는 4로 재할당된다. 따라서 a+b는 1+4로 5가 출력된다.
for (var i = 1; i < 6; i++) {
setTimeout(function() { console.log(i); }, i*1000 );
}
- i를 1부터 5까지 1초 간격으로 출력하고자 작성한 함수이다. 하지만 실제 실행되는 값은 6만 출력이 된다.
- setTimeout의 콜백함수의 경우 특정시간이 지난 뒤에 실행이 되는데, 이를 web api라는 임시 보관함에 잠시 맡겨둔다. 그 사이에 i가 반복문을 다 돌아서 6이 되는 것이다. i를 var로 선언하였기 때문에 함수를 빠져나와 전역변수로 남게 된다. 반복문이 다 끝난 뒤에 setTimeout의 콜백함수를 다시 꺼내서 실행하려고 i를 찾는데 i는 이미 6이 되어있는 상태이다. 참고로 i*1000의 경우 콜백함수 console.log와는 다르게 web api로 보관되지 않기 때문에 반복문의 흐름과 같이 i 값이 변한다. var로 선언했을 때 문제점 중 하나가 바로 반복문이나 조건문을 다 실행했을 때도 변수가 사라지지 않고 남아있다는 것이다.
for (let i = 1; i < 6; i++) {
// let i;
setTimeout(function() { console.log(i); }, i*1000 );
}
- 이를 만약 let으로 선언한다면 변수 i는 반복문이 끝나면 사라지며, i가 for문을 돌면서 { } 안에 참조가 된다. 따라서 i가 1부터 5까지 잘 출력이 된다.
<div style="display : none">모달창0</div>
<div style="display : none">모달창1</div>
<div style="display : none">모달창2</div>
<button>버튼0</button>
<button>버튼1</button>
<button>버튼2</button>
<script>
var 버튼 = document.querySelectorAll('button');
var 창 = document.querySelectorAll('div');
for (var i = 0; i < 3; i++){
버튼[i].addEventListener('click', function(){
창[i].style.display = 'block';
});
}
</script>
- 버튼을 눌렀을 때 해당 버튼에 맞는 모달창을 보여주는 코드이다. 하지만 위와 같이 반복문을 통해 작성할 경우 정상적으로 창이 보여지지 않는다.
- 반복문이 진행되는 시점과 코드가 실행되는 시점이 다르다. 이벤트 리스너의 경우 해당 버튼을 클릭했을 때 콜백 함수가 실행이 된다. 따라서 내가 특정 버튼을 눌렀을 때 이벤트 리스너가 실행이 되고, 그 때 콜백함수 내의 창[i]를 참조할 때는 이미 i가 3으로 남아있는 상태이므로 창[3]을 참조하게 된다.
- 이또한 var를 let으로 선언한다면, i가 반복문 안에 참조할 수 있는 범위로 제한된다. i가 for문을 돌 때마다 해당 차례에 i가 남아있는 것이라고 생각하면 된다. 따라서 해당 콜백함수가 참조할 때 let으로 남아있는 i를 가져다 쓰게 되며 코드가 제대로 실행된다. 반복문 안에 포스트잇으로 붙여놨다고 생각하면 된다. i가 변할 때마다 그 값을 포스트잇에 적어서 붙여놓고 필요할 때 가져다 쓰는 것이다. 중괄호 안에 포스트잇이 없다면 밖에 있는 포스트잇(전역변수, var로 선언)을 찾는 것이다. 변수를 참조하는 순서는 실행되는 코드를 기준으로 가장 가까이 있는 변수를 찾으며 없을 시에 조금씩 더 멀리 있는 변수를 찾는다.
<script>
var 버튼들 = document.querySelectorAll('button');
var 모달창들 = document.querySelectorAll('div');
for (let i = 0; i < 3; i++){
// let i;
버튼들[i].addEventListener('click', function(){
모달창들[i].style.display = 'block';
});
}
</script>
정답은 var 대신 let을 쓰도록 하자..
자료 출처 : https://codingapple.com/
코딩애플 온라인 강좌 - 개발자도 단기완성!
단연 NO1 강사님의 NO.1 강의 역시나 명강입니다. IT 업계의 대치동 NO1. 강사같은 엄청난 강의력. 코딩애플님의 강의는, 엄청나게 기초적인 것부터 가르치는 듯 보이지만, 실제로 다루는 깊이는 절
codingapple.com
'JavaScript > JS 문법 정리' 카테고리의 다른 글
Data Type, Constructor (객체 지향 1) (0) | 2022.09.06 |
---|---|
함수 Default 값, Rest 파라미터 (0) | 2022.09.05 |
literals, Spread Operator, Apply/Call 함수 (0) | 2022.09.03 |
ES6 Arrow Function (0) | 2022.08.31 |
this 문법 정리 (0) | 2022.08.30 |