let
const
var
var
로 선언한 변수는 let
으로 선언한 변수와 유사하며,
대부분의 경우에 let
을 var
로, var
을 let
으로 바꾸어도 문제가 발생하지 않는다.
하지만 var
은 초기 자바스크립트 구현방식 때문에 let
과 const
로 선언한 변수와는 다른 방식으로 동작한다.
var
를 사용하는 오래된 스크립트를 let
으로 바꿀 때 var
에 대해 제대로 알아야 한다.
var
로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프이다.
블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능하다.
if (true) { var test = true; // 'let' 대신 'var' 사용. } alert(test); // true (if 문이 끝났어도 변수에 여전히 접근할 수 있다/)
var
는 코드 블록을 무시하기 때문에 test
는 전역 변수가 되어 전역 스코프에서 이 변수에 접근할 수 있다.
var test
가 아닌 let test
를 사용했다면, 변수 test
는 if
문 안에서만 접근할 수 있다.
if (true) { let test = true; // 'let'으로 변수를 선언. } alert(test); // Error: test is not defined
반복문에서도 유사하다. var
는 블록이나 루프 수준의 스코프를 형성하지 않기 때문이다.
for (var i = 0; i < 10; i++) { // ... } alert(i); // 10, 반복문이 종료되었지만 'i'는 전역 변수이므로 여전히 접근 가능하다.
코드 블록이 함수 안에 있다면, car
는 함수 레벨 변수가 된다.
function sayHi() { if (true) { var phrase = "Hello"; } alert(phrase); // 제대로 출력된다. } sayHi(); alert(phrase); // Error: phrase is not defined
위 코드와 같이 var
는 if
,for
등의 코드 블록을 관통한다.
과거의 자바스크립트에서는 블록 수준 렉시컬 환경이 만들어 지지 않았기 때문이다.
한 스코프에서 같은 변수를 let
으로 두 번 선언하면 에러가 발생한다.
let user; let user; // SyntaxError: 'user' has already been declared
var
로 같은 변수를 여러 번 중복으로 선언할 수 있다.
하지만 이미 선언된 변수에 var
를 사용하면 두 번째 선언문은 무시된다.
var user = "Pete"; var user = "John"; // 이 "var"는 아무것도 하지 않는다(이전에 이미 선언). // ...에러 또한 발생하지 않음. alert(user); // John
var
선언은 함수가 시작될 때 처리된다.
전역에서 선언한 변수라면 스크립트가 시작될 때 처리된다.
함수 본문 내에서 car
로 선언한 변수는 선언 위치와 상관없이 함순 본문이 시작되는 지점에서 정의된다.
(단, 변수가 중첩 함수 내에서 정의되지 않아야 이 규칙이 적용)
아래 두 예제는 동일하게 동작한다.
function sayHi() { phrase = "Hello"; alert(phrase); var phrase; } sayHi(); //
var phrase
의 위치와 관계 없이 실행된다.
function sayHi() { var phrase; phrase = "Hello"; alert(phrase); } sayHi();
코드 블록은 무시되기 때문에 아래 코드 역시 동일하게 동작한다.
function sayHi() { phrase = "Hello"; // (*) if (false) { var phrase; } alert(phrase); } sayHi();
이렇게 변수가 끌어올려 지는 현상을 '호이스팅(hoisting)'이라고 부른다.
var
로 선언한 모든 변수는 함수의 최상위로 끌어올려지기 때문이다.
바로 위 예제에서 is (false)
블록 안 코드는 절대 실행되지 않지만,
이는 호이스팅에 전혀 영향을 주지 않는다.
if
내부의 var
는 함수 sayHi
의 시작 부분에서 처리되므로 (*)로 표시한 줄에서
phrase
는 이미 정의가 된 상태인 것이다.
function sayHi() { alert(phrase); var phrase = "Hello"; } sayHi();
`var phrase = "Hello" 행에선 두 가지 일이 일어난다.
1. 변수 선언 (var)
2. 변수에 값을 할당 (=)
변수 선언은 함수 실행이 시작될 때 처리되지만(호이스팅) 할당은 호이스팅 되지 않기 때문에 할당 관련 코드에서 처리된다.
function sayHi() { var phrase; // 선언은 함수 시작 시 처리. alert(phrase); // undefined phrase = "Hello"; // 할당은 실행 흐름이 해당 코드에 도달했을 때 처리. } sayHi();
모든 var
선언은 함수 시작 시 처리되기 때문에, var
로 선언한 변수는 어디서든 참조할 수 있따.
하지만 변수에 무언가를 할당하기 전까진 값이 underfined이다.
alert
를 호출하기 전에 변수 phrase
는 선언이 끝난 상태이기 때문에 에러 없이 얼럿 창이 뜬다.
그러나 값이 할당되기 전이기 때문에 얼럿 창엔 undefined
가 출력된다.
(function() { let message = "Hello"; alert(message); // Hello })();
함수 표현식이 만들어지고 바로 호출되면서, 해당 함수가 바로 실행된다. 이 함수는 자신만의 변수를 갖고있다.
즉시 실행 함수를 만들 땐, 표현식을 괄호로 둘러 쌓아 (function {...})과 같은 형태로 만든다.
괄호로 둘러싸지 않으면 에러 발생. 자바스크립트는 'function'이라는 키워드를 만나면 함수 선언문이 시작될 것이라 예상한다. 그런데 함수 선언문으로 함수를 만들 땐 반드시 함수의 이름이 있어야 한다.
아래와 같이 예시를 실행하면 에러 발생.
// 함수를 선언과 동시에 실행하려고 함 function() { // <-- Error: Function statements require a function name let message = "Hello"; alert(message); // Hello }();
함수의 이름을 넣어도 에러가 발생한다.
자바스크립트는 함수 선언문으로 정의한 함수를 정의와 동시에 바로 호출하는 것을 하용하지 않는다.
// 맨 아래의 괄호 때문에 문법 에러가 발생. function go() { }(); / <-- 함수 선언문은 선언 즉시 호출할 수 없다.
함수를 괄호로 감싸면 자바스크립트가 함수를 함수 선언문이 아닌 표현식으로 인식하도록 속일 수 있다.
함수 표현식은 이름이 없어도 괜챃고, 즉시 호출도 가능하다.
괄호를 사용하는 방법 말고도, 자바스크립트가 함수 표현식이라고 인식하게 해주는 다른 방법들이 있다.
// IIFE를 만드는 방법 (function() { alert("함수를 괄호로 둘러싸기"); })(); ------------------------------------------ (function() { alert("전체를 괄호로 둘러싸기"); }()); ------------------------------------------ !function() { alert("표현식 앞에 비트 NOT 연산자 붙이기"); }(); ------------------------------------------ +function() { alert("표현식 앞에 단항 덧셈 연산자 붙이기"); }();
위와 같은 방법을 사용하면 함수 표현식처럼 인식되어 바로 싱행이 가능하다.
모던 자바스크립트에선 이렇게 코드를 작성할 필요가 없다.
var
로 선언한 변수는 let
이나 const
로 선언한 변수와 다른 두 가지 주요한 특성을 보인다.
1. var
로 선언한 변수는 블록 스코프가 아닌 함수 수준 스코프를 갖는다.
2. var
선언은 함수가 시작되는 시점 (전역 공간에선 스크립트가 시작되는 시점)에서 처리된다.
var
만의 특성은 대부분의 상황에서 좋지 않은 부족용을 만들어 낸다.
let
이 표준에 도입된 이유가 바로 이런 부작용을 없애기 위함.
변수는 블록 레벨 스코프를 갖는 게 좋으므로 앞으로는 let
과 const
를 이용해 변수를 선언하는 것이 좋다.