본문 바로가기

JavaScript

오래된 var

https://ko.javascript.info/var

 

오래된 var

 

ko.javascript.info

 

var message = "안녕하세요."
alert(message)

 

var는 초기 자바스크립트 구현 방식 때문에 let과 const로 선언한 변수와는 다른 방식으로 동작합니다. 근래엔 var를 쓰지 않아서 이를 만나는 건 흔치 않은 일이지만, var는 오래된 스크립트에서 당신을 기다리고 있는 괴물같은 존재입니다.

 

var는 블록 스코프가 없습니다.

var로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프입니다. 블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능합니다. 

 

if (true) {
  var test = true; // 'let' 대신 'var'를 사용했습니다.
}

alert(test); // true(if 문이 끝났어도 변수에 여전히 접근할 수 있음)

 

반복문에도 유사한 일이 일어납니다. var는 블록이나 루프 수준의 스코프를 형성하지 않기 때문입니다.

 

for (var i = 0; i < 10; i++) {
  // ...
}

alert(i); // 10, 반복문이 종료되었지만 'i'는 전역 변수이므로 여전히 접근 가능합니다.

 

코드 블록이 함수 안에 있다면 var는 함수 레벨 변수가 됩니다. 

 

function sayHi() {
  if (true) {
    var phrase = "Hello";
  }

  alert(phrase); // 제대로 출력됩니다.
}

sayHi();
alert(phrase); // Error: phrase is not defined

 

위에서 살펴본 바와 같이, var와 if, for 등의 코드 블록을 관통합니다. 아주 오래전의 자바스크립트에선 블록 수준 렉시컬 환경이 만들어지지 않았기 때문입니다. var는 구식 자바스크립트의 잔재입니다.

 

var는 변수의 중복 선언을 허용합니다.

한 스코프에서 같은 변수를 let으로 두번 선언하면 에러가 발생합니다.

 

let user;
let user; // SyntaxError: 'user' has already been declared

var로 같은 변수를 여러번 중복으로 선언할 수 있습니다. 하지만 이미 선언된 변수에 var를 사용하면 두 번째 선언문은 무시됩니다.

 

var user = "Pete";

var user = "John"; // 이 "var"는 아무것도 하지 않습니다(이전에 이미 선언됨).
// ...에러 또한 발생하지 않습니다.

alert(user); // John

선언하기 전 사용할 수 있는 var

var 선언은 함수가 시작될 때 처리됩니다. 전역에서 선언한 변수라면 스크립트가 시작될 때 처리됩니다. 함수 본문 내에서 var로 선언한 변수는 선언 위치와 상관없이 함수 본문이 시작되는 지점에서 정의됩니다.(단, 변수가 중첩 함수 내에서 정의되지 않아야 이 규칙이 적용됩니다.)

 

따라서 두 예제는 동일하게 동작합니다.

 

function sayHi() {
  phrase = "Hello";

  alert(phrase);

  var phrase;
}
sayHi();

function sayHi() {
  var phrase;

  phrase = "Hello";

  alert(phrase);
}
sayHi();

 

코드 블록은 무시되기 때문에, 아래 코드 역시 동일하게 동작합니다. 

 

function sayHi() {
  phrase = "Hello"; // (*)

  if (false) {
    var phrase;
  }

  alert(phrase);
}
sayHi();

 

이렇게 변수가 끌어올려지는 현상을 호이스팅(hoisting)이라고 부릅니다. var로 선언한 모든 변수는 함수의 최상위로 '끌어올려지기' 때문입니다. 바로 위 예제에서 if(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로 선언한 변수는 어디서든 참조할 수 있습니다. 하지만 변수에 무언가를 할당하기 전까지 값이 undefined이죠.

 

바로 위의 두 예시에서 alert를 호출하기 전 변수 phrase는 선언이 끝난 상태이기 때문에 에러 없이 얼럿 창이 뜹니다. 그러나 값이 할당되지 전이기 때문에 얼럿 창엔 undefined가 출력됩니다. 

 

즉시 실행 함수 표현식

과거엔 var만 사용할 수 있었습니다. 그런데 var의 스코프는 블록 레벨 수준이 아니죠. 개발자들은 var도 블록 레벨 스코프를 가질 수 있게 여러가지 방안을 고려하게 됩니다. 이때 만들어진 것이 즉시 실행 함수 표현식(Immediately-invoked function expressions)입니다. 즉시 실행 함수 표현식은 IIFE라고 부르기도 합니다.

 

(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("표현식 앞에 단항 덧셈 연산자 붙이기");
}();

위와 같은 방법을 사용하면 함수 표현식처럼 인식되어서 바로 실행이 가능합니다. 그런데 모던 자바스크립트에선 이렇게 코드를 작성할 필요가 없습니다.