이전 강의에서는 선언, 할당, 범위라는 기초 필수 개념을 정리했는데
이번엔 변수의 부가 개념들을 정리해보자. (매우 중요한 개념!)
자바스크립트는 변수나 함수를 선언하면 Hoisting이라는 현상이 일어난다.
Hoisting이란변수나 함수의 호출 코드가 선언 코드보다 아래쪽에 있음에도 불구하고 에러가 발생하지 않고, 마치 선언 코드가 호출 코드보다 더 위에 선언된 것과 같이 동작하는 특성을 호이스팅(Hoisting) 이라고 한다.
호이스팅은ES6+의let과const변수에서는var변수에서와 다르게 호이스팅이 일어난다.
지금 정리하는 내용은var변수에서의 호이스팅이다.
쉽게 말해
자바스크립트는 변수나 함수의 선언부분을 변수의 범위 맨 위로 강제로 끌고가서 가장 먼저 해석한다.
예를 들어보자.
function 함수(){
console.log('hello');
var 이름 = 'Kim';
}
이렇게 함수 내에서 변수를 만들면.
자바스크립트가 이 코드를 해석하는 순서는 이렇게 된다.
function 함수(){
var 이름;
console.log('hello');
이름 = 'Kim';
}
변수의 선언 부분을 강제로 변수의 범위 맨 위로 끌고가서 해석하고 지나간다.
우리 눈에 보이진 않지만 자바스크립트는 코드 동작 순서가 이렇다.
이게 Hoisting 현상이다.
함수를 만들어도 똑같고, 변수를 let, const로 만들어도 똑같다. (약간의 차이는 있다.)
그럼 이 코드는 실행결과가 어떻게될까?
console.log(이름);
var 이름 = 'Kim';
console.log(이름);
실행결과
var 이름 console.log(이름); 이름 = 'Kim'; console.log(이름);콘솔창에 첫째로는
undefined가 출력되고
둘째로는Kim이 출력된다.
왜냐면Hoisting때문에 위와 같이 코드가 실행될 것이기 때문이다.
undefined라는건 변수가 선언은 되었는데 값을 아무것도 할당하지 않았을 때undefined가 출력된다.
일종의 자료형같은 것인데 그냥정해지지않은 값이라고 생각하면 된다.
하지만let,const변수의 경우Hoisting이 일어나긴 하는데 약간 이상한 방식으로 일어난다.
그건 다음 강의에서 다루어 보자.
변수를 콤마로 구분하면 여러개를 동시에 만들 수 있다.
var 이름, 나이, 성별;
이렇게 하면 변수가 3개 생성된다. var 키워드 3번 쓰지않아도 되니 코드가 약간 더 줄어든다.
var 이름 = 'Kim', 나이, 성별;
선언과 동시에 할당도 하고 싶으면 그냥 이렇게 하면 된다.
그냥 var let const 키워드를 여러번 안써도 된다는 장점이 있다.
var 이름,
나이,
성별;
이런 방식으로도 사용할 수 있다.
변수는 이런 특징이 있다.
바깥에 있는 변수는 안쪽에서 자유롭게 사용가능하다.
이걸 전문 개발자용어로 참조가능하다 라고 하지만 자바스크립트에서는 이 현상을 부르는 다른 말이 있다.
closure라고 한다.
안쪽 바깥쪽이 뭔지 예를 들자면
var 나이 = 20;
function 함수(){
console.log(나이)
}
함수();
지금 함수(){} 안쪽에서 바깥쪽에 있는 나이라는 변수를 가져다 쓸 수 있다는 것이다.
함수(){} 안쪽에 나이라는 변수 정의가 있으면 그걸 쓰겠지만
없으면 자연스럽게 바깥에 있는 변수를 가져다 쓴다. (참조합니다)
프로그래밍에선 전역변수라는게 있다.
모든 함수나 if나 for 내부에서 공통적으로 사용할 수 있는 (참조할 수 있는) 유용한 변수를 뜻한다.
전역변수를 만들어 쓰고싶으면 대괄호 {} 밖에서 변수 하나를 만들어주면 된다.
var 나이 = 20; // 전역 변수
function 함수(){
console.log(나이)
}
그럼 밑에 나오는 모든 함수, for, if 등에서 전부 나이라는 변수를 사용가능하다.
근데 전역변수는 이상한 특징이 있다.
예전에 window라는 object 있다고 배웠었다.
window는 자바스크립트 기본함수 보관하는 큰 object라고 배웠다.
alert, getElementById, console.log 이런 함수들이 다 들어있다.
진짠지 테스트해보려면
자바스크립트 기본 함수들을 object 다룰 때 처럼 window.alert(), window.document.getElementById() 이렇게 사용해보자
alert이런것도 window에 보관된 애들이기 때문에 window.alert('안녕') 이렇게 해도 나오는 것을 볼 수 있다.

이게 window object의 역할이다.
근데 쌩으로 전역변수를 만들면 window에도 보관이 된다. (let말고 var 키워드만!)
var 나이 = 20;
console.log(나이);
console.log(window.나이);
나이라는 전역변수를 만들면
자동으로 window object에 보관이 되었으니까
신기하게 window.나이를 써도 출력이 된다.

(전역함수도 마찬가지로 window에 자동으로 보관된다)
그래서 전역변수를 조금 더 엄격하게 관리하거나 구분짓고 싶으면
전역변수를 만들 때와 사용할 때 window를 활용해보자.
window.나이 = 20; //전역변수만들기
console.log(window.나이); //전역변수사용하기
window.나이 = 30; //전역변수변경하기
이런 식으로 사용할 수도 있다.
(node.js 환경에선 window 대체품인 global 이라는 object가 있다.)
다음 코드를 실행하면 콘솔창에 무엇이 뜰까?
if (true) {
let a = 1;
var b = 2;
if (true) {
let b = 3;
}
console.log(b);
}
해당 문제는 전역변수에 대한 문제이다.
대괄호 {}를 통해 파악했어야한다.
현재if문안에 또 다른if문이 있다. (이if문을 두번째if문이라고 부르겠다)
하지만console.log(b);는 첫번째if문대괄호 안에 있기 때문에 두번째if문에서는 아무의미 없는 코드이다.
그냥 이런 코드라고 해도 무방하다.if (true) { let a = 1; var b = 2; console.log(b); }그렇기 때문에 콘솔창에는 2라는 답이 나온다.
함수();
function 함수() {
console.log(안녕);
let 안녕 = 'Hello!';
}
풀이
undefined가 나올 것 같다.
왜냐하면 Hoisting이 되면 아래와 같은 코드로 실행되기 때문이다.
함수();
function 함수() {
let 안녕
console.log(안녕);
안녕 = 'Hello!';
}
하지만 undefined가 나오지 않고 에러가 뜨는 것을 볼 수 있다. 왜일까?

letconst에서는 할당값이 없다면?함수내부 코드가 위 처럼 이상할시에
let 변수는Hoisting이 되긴 하지만undefined라는 값이 할당되지 않는다.
출력시 에러를 출력한다.
let 변수는hoisting되지만var 변수처럼 지동으로undefined라는 값을할당(일명 initialization)해주지 않는다.
선언과할당사이에 시간차가 있기 때문에 그런 현상이 일어나는 것이고let, const 변수는 그래서 쓸 수 없다.
그래서 그냥let const변수는 그래서 엄격하게 쓸 수 있는 변수구나 외우면 된다.
testFun();
var testFun = function() {
console.log(안녕);
var 안녕 = 'Hello!';
}
풀이
현재 첫번째 코드를 보면 testFun()가 실행되는 것을 볼 수 있다. 이 코드는 함수를 호출하는 코드로 이름이 testFun인 함수를 호출하는 것과 같다.
둘째줄에 있는 testFun() 선언부분을 잘 보면 function 키워드 대신 변수만드는 것 처럼 함수를 선언과 할당하고 있다.
이렇게 함수를 만들어도 Hoisting이 되는데, 근데 Hoisting은 변수의 선언부분만 된다고 했었다!
그래서 변수 선언부분만 맨 위로 끌어올려지는데
그 변수에다가 소괄호를 붙여봤자 아직 함수가 아니기 때문에 실행이 되지 않는다. (에러가 난다. 함수가 아닌 변수에다가 소괄호 붙이면 함수 아니라고 에러를 뿜어준다.)

Hoisting은 변수의 선언부분만 된다! (기억하기!)
let a = 1;
var 함수 = function() {
a = 2;
}
console.log(a);
풀이
a는 1이 출력된다.
a는 1이라는 변수를 만들고
그 다음에 함수를 만들고 함수 안에서 a = 2라고 값을 변경시켰는데
함수를 정의만 했지 실행을 안시켜서 a = 2라는 부분은 없는 코드나 마찬가지이다.
그래서 a 는 그냥 1이다.

let a = 1;
var b = 2;
window.a = 3;
window.b = 4;
console.log(a + b);
풀이
콘솔에는 5가 출력된다.
(a는 1, b는 4가 출력된다.)
b가 4가 되는 이유는 var b = 2 이것과 window.b = 4 이건 거의 동일한 기능을 하는 코드기 때문에
b는 그냥 4로 재할당이 되었다고 보면 된다.
a는 let 변수로 1을 할당하고 글로벌 변수로 3을 할당했다.
이 경우 우리가 a를 사용했을 때 조금 더 범위가 작고 가까운 1을 참조해서 사용한다.
(자바스크립트 변수를 사용할 때 참조할만한 변수가 내 주변에 없으면 계속 상위 중괄호로 시선을 돌리면서 참조한다.)

저번 연습문제에서 setTimeout이라는 유용한 함수를 배웠다.
그래서 코드를 이렇게 작성했다.
setTimeout(function() { console.log(1); }, 1000 );
setTimeout(function() { console.log(2); }, 2000 );
setTimeout(function() { console.log(3); }, 3000 );
setTimeout(function() { console.log(4); }, 4000 );
setTimeout(function() { console.log(5); }, 5000 );
그럼 1초마다 1~5까지의 숫자를 콘솔창에 출력해준다.

하지만 반복되는 코드가 보기 싫어서 반복문 안에 담았다.
for (var i = 1; i < 6; i++) {
setTimeout(function() { console.log(i); }, i*1000 );
}
논리적으로 완벽한 for 반복문이다.
그런데 반복문으로 축약하자마자 제대로 작동하지 않는다.
계속 6라는 숫자가 1초마다 출력된다.

Q. 위 코드는 왜 의도대로 동작하지 않는 것이죠? 설명해보자
자바스크립트는 일단
반복문을 만나면 반복문 내의 코드를 반복해서 실행한다.
지금반복문이i가1부터6가 되기 전까지 반복해주세요~라고 써놓았으니 총 5번 반복이 된다.
근데 내부 코드는setTimeout 어쩌구이다.X초후에콜백함수 내의 console.log(i)를 실행해주세요~라는 코드이다.
그래서 그 부분은반복문과 동시에 실행되지 않는다.
반복문을 해석한 후..1초가 지나면setTimeout내의console.log(i)가 발동된다.
근데i를 채워넣고싶어서 주변을 살펴보았더니i값은6밖에 없는 것이다.
왜냐면 아까 반복문이5번 실행되면서i값은1,2,3 ...이렇게 차례로 변하다가i값이6이 되어 종료되었다.
그리고i값은var로 만든전역변수이다.
그래서i값을 쓰려고 봤더니전역변수 i = 6밖에 없어서6를 집어넣어서 계속 실행해서 콘솔창에6가 계속 출력되었던 것이다.
쉽게 말해반복문5번 실행되어서i는6인채로 반복문이 종료되었고
그 이후에setTimeout내의console.log(i)가 발동되어서 1초마다 6이 출력되던 것이다.
그리고 해결할 방법은 무엇일까?
해결책은
for 반복문에서i변수를 만들 때var대신let으로 바꾸는 것이다.
let변수는 범위가 중괄호이다.for반복문도 중괄호에 해당된다.
그럼 이제1초 후console.log(i)가 실행될 때i값을 채우려고 살펴보면
i값이for 반복문내에 남아있기 때문에 그걸 가져다 쓰게 된다.
그래서 아까처럼 계속5를 출력해주는게 아니라1,2,3,4,5를 출력해준다.for (let i = 1; i < 6; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
버튼(button)과 모달창(div)가 3개 있다.
<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>
//?
</script>
지금 display : none 덕분에 모달창이 아무것도 안보이는 상태이다.
자바스크립트를 잘 짜서
0번째 버튼을 누르면 0번째 모달창,
1번째 버튼을 누르면 1번째 모달창을 보여주고 싶다.
그럼 코드를 어떻게 짜면 될까요?
여기까진 기본 자바스크립트 내용이니 적어보자.
<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>
let buttons = document.querySelectorAll('button');
let modals = document.querySelectorAll('div');
buttons[0].addEventListener('click', function(){
modals[0].style.display = 'block';
});
buttons[1].addEventListener('click', function(){
modals[1].style.display = 'block';
});
buttons[2].addEventListener('click', function(){
modals[2].style.display = 'block';
});
</script>
이렇게 된다.
document.querySelectorAll은 jQuery의 $('') 셀렉터와 매우 유사하다. 동시에 여러 요소를 찾아 어레이 비슷한 자료형에 담아준다.
이렇게 쭉 쓰면
0번째 버튼을 누르면 0번째 모달창,
1번째 버튼을 누르면 1번째 모달창을 보여준다.

그런데 비슷한 코드들이 좀 보인다. 이걸 반복문 안에 담아서 한번 다시 개발해보려고한다.
let buttons = document.querySelectorAll('button');
let modals = document.querySelectorAll('div');
for (var i = 0; i < 3; i++){
buttons[i].addEventListener('click', function(){
modals[i].style.display = 'block';
});
}
이렇게 반복문으로 반복적인 코드를 축약가능하다.
그런데 문법에 맞게 쓰긴 했는데 모달창이 제대로 작동하지않고 있다.
Q. 위 코드는 왜 의도대로 동작하지 않는 것이죠? 설명해보자.
방금 전 문제랑 거의 똑같은 경우의 문제이다.
자바스크립트 입장에서 하단 코드를 해석해보자면
for (var i = 0; i < 3; i++){
buttons[i].addEventListener('click', function(){
modals[i].style.display = 'block';
});
}
자바스크립트는 일단 반복문을 만나면 반복문 내의 코드를 반복해서 실행한다.
지금 반복문이i가0부터3이 되기 전까지 반복해주세요~라고 써놓았으니 총3번 반복이 된다.
근데 내부 코드는addEventListener ~이다. 클릭 되면 콜백함수 내의modals[i].style.display = 'block';을 실행해주세요~ 라는 코드이다.
그래서 그 부분은 반복문과 동시에 실행되지 않는다.
반복문을 해석한 후.. 누군가 버튼을 클릭하면addEventListener내의모달창들[i].style.display = 'block';코드가 발동된다.
근데i를 쓰고싶어서 주변을 살펴보았더니i값은3밖에 없는 것이다.
왜냐면 아까 반복문이 3번 실행되면서i값은0,1,2,3 ...이렇게 차례로 변하다가i값이3이 되어 종료되었다.
그리고i값은var로 만든전역변수이다.
그래서i값을 쓰려고 봤더니전역변수 i = 3밖에 없어서3을 집어넣어서 계속 에러가 났던 것이다.
그리고 해결할 방법은 무엇일까?
해결책은
for 반복문에서i변수를 만들 때var대신let으로 바꾸는 것이다.
let변수는 범위가 중괄호이다.for반복문도 중괄호에 해당된다.
반복문이 돌고 나서도let i = 0;값이{for 반복문}내에 남아있기 때문에 그걸모달창들[i].style.display = 'block'; 의i값으로 가져다 쓰게 된다.
그럼 이제 의도된i값으로 코드가 잘 실행된다.
let buttons = document.querySelectorAll("button");
let modals = document.querySelectorAll("div");
for (let i = 0; i < 3; i++) {
buttons[i].addEventListener("click", function () {
modals[i].style.display = "block";
});
}

역싀 정리왕 언덕님👍