자바스크립트에서 변수를 선언할 때 var, let, const 형태로 선언할 수 있습니다.


varfunction-scoped라고 함

let, constblock-scoped라고 함


여기서 function-scoped와 block-scoped가 무엇일까 알아보니 다음과 같은 내용을 볼 수 있었습니다.


01. var (function-scoped)

// var는 function-scope이기 때문에 for문이 끝난다음에 i를 호출하면 값이 출력이 잘 됨
// 이건 var가 hoisting이 되었기 때문이다.
for(var j=0; j<10; j++) {
  console.log('j', j)
}
console.log('after loop j is ', j) // after loop j is 10


// 아래의 경우에는 에러가 발생한다.
function counter () {
  for(var i=0; i<10; i++) {
    console.log('i', i)
  }
}
counter()
console.log('after loop i is', i) // ReferenceError: i is not defined


여기서 hoisting이라는 말이 나오는데.. (처음에 hosting이라고 착각함 ㅎㅎ....)

이건 또 무슨 말이고 하니.. 요약하자면 "후선언된 변수나, 함수들이 해당 Scope에서 최상위에 위치하는 걸 뜻함"이라고 합니다.

대충 선언 부분이 잘못되도 자바스크립트에서는 이를 올바르게 코딩된 것처럼 인식한다는 뜻인데, 이는 다음 포스트에서 다루도록 하겠습니다.


위에서 나타난 에러처럼 ReferenceError 즉, 선언이 안 되었다는 에러가 나타납니다.

간단히 이해하기 위해 다음과 같이 코드를 바꿀 수 있습니다.


// var는 function-scope이기 때문에 for문이 끝난다음에 i를 호출하면 값이 출력이 잘 됨
// 이건 var가 hoisting이 되었기 때문이다.
var j=0;
for(j=0; j<10; j++) {
  console.log('j', j)
}
console.log('after loop j is ', j) // after loop j is 10


// 아래의 경우에는 에러가 발생한다.
function counter () {
  var i=0;
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
}
counter()
console.log('after loop i is', i) // ReferenceError: i is not defined
//--> i는 counter 함수의 지역변수이기 때문임


위와 같이 지역변수의 느낌으로 이해하면 쉽게 와닿을 수 있습니다.


자바스크립트에서는 immediately-invoked function expression( or IIFE, pronounced "iffy")라는 것이 있다고 합니다..

무슨 말인지 알아먹기 힘든 영어로 되어있지만 예시를 통해 알아보도록 합시다.


다음은 IIFE로 function-scope처럼 만들 수 있는 예시입니다.


IIFE function-scope 예시(1)

// IIFE를 사용하면
// i is not defined가 뜬다.
(function() {
  // var 변수는 여기까지 hoisting이 된다.
  for(var i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // ReferenceError: i is not defined


위의 소스코드를 보면 소괄호로 묶어 마치 하나의 함수처럼 만들어준 것처럼 보입니다.

hoisting이 되는 위치도 웃긴 게, function 내부에 되어버리게 되어, 결국 function 밖에서 실행할 때 사용되는 i의 값은 undefined 변수가 되어버리게 됩니다.

에러가 나지 않도록 작성한 코드는 다음과 같습니다.


IIFE function-scope 예시(2) - hoisting

// 이 코드를 실행하면 에러없이 after loop i is 10이 호출된다.
(function() {
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // after loop i is 10


위의 소스코드가 에러가 나지 않는 이뉴는 i를 function 내에서 다시 재정의하지 않아서 hoisting이 이루어졌다는 의미입니다.

즉, i 함수는 function 밖에서 선언된 전역(global) 변수라는 것입니다.


이러한 hoisting을 방지하기 위해서는 use strict라는 것을 사용할 수 있습니다.

밖에서 사용한 전역변수가 함수 내에서 hoisting 되지 않도록 방지하는 것입니다.


IIFE function-scope 예시(2) - use strict

// 아까랑 다르게 실행하면 i is not defined라는 에러가 발생한다.
var i;
(function() {
  'use strict'
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // ReferenceError: i is not defined


위의 소스코드는 밖에서 선언된 var i의 값이 hoisting되지 않았기 때문에, i의 값에는 아무런 값이 들어가 있지 않습니다.

따라서 정상적으로 에러를 출력하는 걸 볼 수 있습니다.


변수 하나에 너무나 많은 고민이 들어가게 됩니다. 허허...

이러한 수고를 덜어주기 위해 등장한 것이 바로 let과 const라고 할 수 있습니다.


02. let, const (block-scoped)

자바스크립트에는 var만 존재했을 때 다음과 같은 문제가 있었다고 합니다.

// 이미 만들어진 변수이름으로 재선언했는데 아무런 문제가 발생하지 않는다.
var a = 'test'
var a = 'test2'

// hoisting으로 인해 ReferenceError에러가 안난다.
c = 'test'
var c


위와 같은 문제점으로 인해 자바스크립트를 욕하는 사람이 많았다고 합니다.. ㅎㅎ

하지만 let과 const를 사용하면 var를 사용할 때보다 훨씬 편리하게 코드를 작성할 수 있게 되었다고 합니다.


이 두 가지 형태는 변수의 재선언이 불가능하다는 공통점이 있습니다.(마치 C언어의 #define과 const의 느낌이 아닐까 싶습니다.)


let은 변수에 재할당이 가능하지만, const는 재선언, 재할당이 모두 불가능합니다.


// let
let a = 'test'
let a = 'test2' // Uncaught SyntaxError: Identifier 'a' has already been declared
a = 'test3'     // 가능

// const
const b = 'test'
const b = 'test2' // Uncaught SyntaxError: Identifier 'a' has already been declared
b = 'test3'    // 불가능 Uncaught TypeError:Assignment to constant variable.


이때 let과 const에 hoisting이 발생하지 않는 건 아닙니다.

만약 var가 function-scoped로 hoisting 되었다면 let과 const는 block-scoped로 hoisting이 일어나게 됩니다.


d = "test"
var d; // hoisting이 되어 에러가 나지 않음

/* javascript가 인식할 때 다음과 같이 인식됨
var d;
d = "test";
자기 알아서 순서가 바뀐 채로 인식됨 ㄷㄷ*/

c = 'test' // ReferenceError: c is not defined
let c;


c가 에러가 나는 이유는 let의 특징인 tdz(temporal dead zone) 때문입니다.

이는 let으로 선언된 변수는 값을 할당하기 전에 변수가 먼저 선언되어 있어야 합니다.(위의 4 ~ 6번째 라인과 같은 hoisting이 일어나지 않음)


이는 const도 예외가 아닙니다. 단지 let보다 좀 더 업격할 뿐...


// let은 선언하고 나중에 값을 할당이 가능하지만
let dd
dd = 'test'

// const 선언과 동시에 값을 할당 해야한다.
const aa // Missing initializer in const declaration


자바스크립트에 tdz와 같은 기능이 필요한 이유는 동적언어이다보니 runtime type check가 필요해서라고 합니다.




+ Recent posts