#6 - var, let, const 그리고 호이스팅

만식이형·2023년 5월 28일
0

Javascript

목록 보기
6/10
post-thumbnail

1. var

  • 함수 스코프 : 이와 관련 설명은 이전 포스팅(스코프)
  • 값을 재할당 가능
  • 초기화하지 않아도 됨
  • 이미 선언된 변수에 재선언 가능

1.1 예시

var 는 기본적으로 함수 스코프를 가진다. 그리고 초기화 하지 않고 사용할 수 있으며 var 로 선언한 변수명을 재사용 할 수 있다.(재할당 가능) 아래는 이에 대한 간략한 예시이다.

var variable1;
variable1 ="(var) 초기화된 값1";
console.log(variable1);
function printV(){
  variable1 ="(var) 초기화된 값2";
 console.log(variable1);
}
var variable1 = "(var) 초기화된 값3";
console.log(variable1);
printV();

1.2 결과

  • 최초에 variable1 란 이름으로 변수를 선언했고 var 키워드를 붙였다. 하지만 첫 선언시에는 초기화를 하지 않았다.
  • var는 재할당이 가능하기 때문에 함부 바깥에서 한번, 내부에서 한번 값을 재할당했을 때 콘솔에 값이 정상적으로 출력된다. 또한 같은 변수명을 재선언하는 것이 가능하다.

1.3 var의 호이스팅(Hoisting)

🧐 호이스팅이란?

영어 동사 "hoist"의 사전적 의미는 아래와 같다.

  1. [동사] (흔히 밧줄이나 장비를 이용하여) 들어[끌어]올리다
  2. [명사] (화물장애인을 들어올리기 위한) 승강 장치
    출처: 네이버사전

자바스크립트에서 호이스팅이란 변수와 함수 선언(var, let, const, function 등)이 스코프의 최상단으로 끌어올려지는 동작 을 말한다. 스코프의 최상단으로 끌어올려진다는 것은 무슨 의미인가? 또 이러한 호이스팅은 왜 필요한가?

호이스팅은 자바스크립트 엔진이 동작하는 과정 중 하나로, 자바스크립트 엔진은 코드를 실행하기 전에 두 번의 단계를 거친다.

  1. 컴파일 단계(Compilation Phase) : 코드를 해석하고 실행 가능한 형태로 변환하는 단계이다. 이 단계에서 변수와 함수 선언을 메모리에 등록하고, 스코프를 생성한다.(이때 호이스팅이 이루어진다.)
  2. 실행 단계(Execution Phase) :컴파일 단계에서 생성된 코드를 실행하는 단계이다. 변수 할당 및 계산, 함수 호출 등이 이루어진다.

호이스팅은 컴파일 단계에서 변수와 함수 선언을 메모리에 등록하는 과정으로, 변수나 함수 선언을해당 스코프의 최상단으로 끌어올림으로써 변수와 함수를 선언하기 전에 사용할 수 있는 효과를 갖게 된다. 하지만 호이스팅은 실제 코드의 순서를 변경하는 것이 아니라 자바스크립트 엔진이 코드를 해석하는 과정에서 수행되는 것이다.

[ 호이스팅 사용 예시 ]

  • 호이스팅은 함수 선언을 스코프의 최상단으로 끌어올리므로, 함수를 선언하기 전에 호출할 수 있다.
greet();
function greet() {
  console.log("Hello!");
}
  • 하지만 아래 코드는 TypeError가 발생한다. 호이스팅은 변수 선언만 끌어올리고, 초기화는 끌어올리지 않기 때문이다.(매우 중요!!)
greet(); // TypeError: greet is not a function
var greet = function() {
  console.log("Hello!");
};
  • 아래처럼 블록 내에서 선언된 변수도 블록 외부에서 접근할 수 있다. 호이스팅으로 인해 변수 선언이 해당 스코프의 최상단으로 끌어올려지기 때문이다.
var x = 1;

if (x === 1) {
  var y = 2;
} else {
  var y = 3;
}

console.log(y); // 2

호이스팅은 적절하게 활용하면 유용할 수 있지만, 코드의 가독성을 저해할 수 있으며 예상치 못한 동작을 초래할 수 있다. 아래의 var의 호이스팅 예시를 통해 구체적으로 알아보자.

console.log(myVariable); // undefined
var myVariable = "Hello, world!";
console.log(myVariable); // "Hello, world!"

위 코드의 첫줄에서 myVariable 은 선언된 적이 없다. 하지만 콘솔에서는 에러 메세지가 아니라 "undefined"가 출력된다. 이는 호이스팅 때문으로, 호이스팅에 의해 선언부가 스코프의 최상단으로 끌어올려진다. 따라서 위의 코드를 자바스크립트 엔진이 호이스팅 매커니즘을 통해 아래와 같이 해석한다.

var myVariable;
console.log(myVariable); // undefined
var myVariable = "Hello, world!";
console.log(myVariable); // "Hello, world!"

위에서 언급했지만, 이는 자바스크립트 엔진이 컴파일 과정에서 개발자의 코드를 "해석"하면서 변수나 함수 선언을 최상단으로 끌어올리는 매커니즘을 작동시키기 때문이다. 여기서 문제가 발생한다.

console.log(x); // undefined
var x = 10;

위 예시에서 변수 x를 선언하기 전에 출력하려고 했는데, 출력 결과는 undefined이다. 이는 var 변수의 호이스팅으로 인해 발생하는 현상이다. var x 선언이 코드 상단으로 이동하지만 초기화는 원래 위치에 그대로 남아있게 된다. 따라서 console.log(x)가 실행될 때 변수 x는 선언은 되었지만 초기화되지 않은 상태이기 때문에 undefined가 출력된다.

이러한 동작은 개발자의 의도와 다르게 동작할 수 있으며, 디버깅이 어려워질 수 있다. 물론 아주 짧은 코드를 짤때는 이런 실수를 안 할 수 있겠지만, 프로젝트가 커질 경우에는 문제가 발생할 수 있다. 따라서 변수를 사용하기 전에 선언하고 초기화하는 것이 가독성과 예측 가능성을 높이는데 도움이 된다.

호이스팅은 변수가 중복 선언되는 경우에도 예상치 못한 동작을 초래할 수 있다.

var helloMsg = "Hello World";
if (true) {
  var helloMsg = "Hello Javascript"; 
}
console.log(helloMsg) // "Hello Javascript"

만약 개발자가 의도를 가지고 변수를 재정의했다면 문제가 되지 않는다. 하지만 규모가 큰 프로젝트에서 기존의 정의된 변수가 있다는 사실을 인지하지 못한 채 위와 같은 코드를 작성한다면, 의도치 않게 중복 선언된 변수가 덮어쓰여지는 결과를 초래할 수 있으며, 디버깅과 유지 보수를 어렵게 만들 수 있다.

1.4 var 호이스팅의 단점 정리

  1. 변수의 위치 예측 어려움: var는 선언과 초기화가 분리되어 있으므로, 변수 선언이 실제 코드의 위치와 다르게 끌어올려질 수 있으며, 코드의 가독성과 이해가 어려워질 수 있다. 변수 선언이 실제 사용보다 이후에 위치하면 의도하지 않은 결과를 초래할 수 있다.

  2. 스코프 오염 문제: var로 선언된 변수는 함수 스코프를 갖기 때문에 함수 내에서 선언된 변수는 해당 함수 스코프 전체에서 접근 가능하다. 이로 인해 의도하지 않은 변수의 공유나 값의 변경이 발생할 수 있다. 특히 반복문 내에서 var로 선언된 변수를 사용하면 예상치 못한 동작이 발생할 수 있다.

  3. 재선언 허용: var는 동일한 변수명을 다시 선언할 수 있다. 이는 의도하지 않은 변수 충돌을 유발할 수 있다. 이러한 문제는 코드의 복잡성을 증가시키고 디버깅을 어렵게 만들 수 있다.

위와 같은 단점으로 인해 ES6부터 let과 const가 도입되었다. let과 const는 블록 스코프를 갖고 호이스팅 동작도 var보다 예측 가능하게 변경되었다.

2. let

  • 블록 스코프 : 이와 관련 설명은 이전 포스팅(스코프)
  • 재할당 가능
  • 초기화 하지 않아도 됨
  • 이미 선언된 변수에 재선언 불가능

2.1 예시

아래 코드와 결과들에서 볼 수 있듯이, let도 값을 재할당할 수 있고, 초기화하지 않고 사용할 수도 있다. 하지만 var와는 달리 let은 이미 선언한 동일한 변수명을 재선언(중복선언)할 수 없다.

let variable2;
variable2 = "(let) 초기화된 값1";
console.log(variable2);
variable2 = "(let) 초기화된 값2";
console.log(variable2);
function printL(){
  variable2 ="(let) 초기화된 값3";
  console.log(variable2);
}
// let variable2 = "(let) 초기화된 값3"; // SyntaxError: Identifier 'variable2' has already been declared 
//console.log(variable2);
printL();

2.2 결과

위 코드에서 // let variable2 부분의 주석을 해제하면 에러가 발생한다.

2.3 let의 호이스팅

[ var와 let의 차이점 - let에서 불가능한 것 ]

let은 var와는 달리 변수를 선언하기 전에 출력할 수 없다.

console.log(x); // ReferenceError: x is not defined
let x = 10;

위에서 언급햇듯이 중복선언도 불가능하다.

let x = 10;
let x = 20; // SyntaxError: Identifier 'x' has already been declared

하지만 아래의 예시는 에러가 발생하지 않는다.

let helloMsg = "Hello World";
if (true) {
  let helloMsg = "Hello Javascript"; 
  console.log(helloMsg) // "Hello Javascript"
}
console.log(helloMsg) // "Hello World"

그 이유는 두 변수의 스코프가 다르게 작동하기 때문이다. let 키워드는 기본적으로 블록 스코프(범위)를 가지는데, 위 코드에서는 같은 이름의 변수가 각각 전역 스코프와 블록 스코프를 가지기 때문에 각기 다른 변수로 취급된다.

[ let 호이스팅이 적절히 사용된 예 ]

다음은 let의 호이스팅이 적절히 사용된 예시이다. 이 예시는 let 변수의 호이스팅을 활용하여 변수의 범위를 제한하면서도 반복분 내에서 활용하는 것을 보여준다.

function printNumbers() {
  for (let i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
    }, 1000);
  }
}
printNumbers();

위 코드에서는 반복문 안에서 setTimeout 함수를 사용하여 1초 후에 i 값을 출력하도록 설정하였다. let 키워드를 사용하여 변수 i를 선언하면 매번 반복될 때마다 새로운 블록 스코프가 생성되며, 각 반복마다 독립적인 변수 i가 생성된다. 이로 인해 setTimeout의 콜백 함수는 클로저로써 자신이 선언된 스코프인 반복문의 블록 스코프에 접근할 수 있다. 따라서 setTimeout이 실행되었을 때 해당 반복문의 인덱스 값을 제대로 유지하게 된다.

위 코드를 실행하면 0부터 4까지 순서대로 1초 간격으로 출력되는 것을 확인할 수 있다. 이는 let 변수의 호이스팅을 적절하게 활용하여 반복문에서 인덱스 값을 제대로 유지한 결과이다.

클로져(closure)? 클로저는 함수 내부에서 정의된 함수가 외부 함수의 변수에 접근할 수 있는 것을 의미한다..
함수가 종료되더라도 해당 함수가 생성될 때의 환경을 기억하고, 그 이후에도 접근할 수 있게 해준다.

function outerFunction() {
  var outerVariable = 'Hello';
  function innerFunction() {
    console.log(outerVariable);
  }
  return innerFunction;
}
var closure = outerFunction();
closure(); // 출력: Hello

outerFunction 내부에서 innerFunction이 정의되고 반환된다. 반환된 innerFunction은 외부 함수인 outerFunction의 변수인 outerVariable에 접근할 수 있다. 이는 클로저의 특성으로 인해 가능한 것...
바로 위의 let 호이스팅 예시에서도 마찬가지로, setTimeout의 콜백 함수는 자신의 외부에 있는 변수 i에 접근할 수 있다.

3. const

  • 재할당이 불가능하며, 선언과 동시에 초기화해야 한다. const로 선언된 변수는 블록 스코프(이전 포스팅: 스코프)를 갖는다.
  • 객체나 배열과 같은 참조 타입의 const 변수의 경우, 변수 자체는 변경할 수 없지만 해당 객체 또는 배열의 내부 속성이나 요소는 변경할 수 있다.

3.1 예시

//const pi; //SyntaxError: Missing initializer in const declaration 
const pi= 3.141592;
console.log(pi); 3.141592
//pi = 4.2222;  // TypeError: Assignment to constant variable. 

위 코드에서 볼 수 있듯이 const는 선언만 하고 초기화하지 않을 경우 에러가 발생한다. 또한 이미 선언된 변수 pi에 재할당이 불가능하다. const 변수는 한 번 할당된 값은 변경할 수 없다.

하지만 const 변수에 객체를 할당한 후, 객체의 속성 값을 변경하는 것은 가능하다.

const person = {
  name: 'John',
  age: 30
};
console.log(person); // 출력: { name: 'John', age: 30 }

person.name = 'Jane';
console.log(person); // 출력: { name: 'Jane', age: 30 }

위 코드에서 const 변수인 person은 객체 자체를 변경할 수는 없다. person 객체를 아래와 같이 변경할 수는 없다는 말이다.

person = { name: 'Jane',
          age: 30,
          addr: "New York"
         } // TypeError: Assignment to constant variable. 

하지만 person 객체의 name 속성 값을 'Jane'으로 변경할 수 있다.

3.2 const의 호이스팅

const 변수의 호이스팅은 선언 단계까지만 호이스팅되고, 초기화는 호이스팅되지 않는다. 즉, const 변수는 선언 이전에 접근하면 "ReferenceError"가 발생한다. const 변수는 반드시 선언과 동시에 초기화되어야 한다.

console.log(x); // ReferenceError: x is not defined
const x = 10;

재할당이 허용되지 않는다.

const x = 10;
x = 20; // TypeError: Assignment to constant variable.

[ const를 적절히 사용한 예 ]

function calculateArea(radius) {
  if (radius > 0) {
    const pi = 3.14159;
    const area = pi * radius * radius;
    console.log(area);
  } else {
    console.log("반지름은 양수여야 합니다.");
  }
}
calculateArea(5); // 출력: 78.53975
calculateArea(-2); // 출력: 반지름은 양수여야 합니다.

위의 코드는 원의 넓이를 계산해보는 코드이다. 위에서 const로 선언된 pi와 area는 블록 내에서만 유효한 스코프를 갖는다. 또한 호이스팅이 발생할 때 문제가 발생하지 않는다.

4. 정리

  1. var는 함수 스코프, let과 const는 블록 스코프를 가진다.
  2. var, let, const 세 가지 모두 최상위로 호이스팅된다. var 변수만 undefined로 초기화되고 let과 const 변수는 초기화되지 않는다.
  3. var와 let은 동일한 변수명에 값을 재할당하는 것은 가능하며, 초기화 없이 선언만 할 수 있다. 반면 const는 재할당이 불가능하며, 선언과 동시에 초기화를 반드시 해야한다.
  4. var는 동일한 변수명을 재선언(재할당과 다르다)할 수 있지만, let과 const는 재선언이 불가능하다.
profile
즐거운 게 즐거운 것

0개의 댓글