• [JS] ES6의 변수 선언, const와 let

    2020. 4. 29.

    by. 나나 (nykim)

     

     

     

     

    ES6에서 추가된 constlet에 대해 정리해 봅니다.

    예전 블로그 내용을 가져와 살짝 다듬었습니다.

     

    const와 let 키워드를 처음 접하는 초보자가 보시면 좋을 것 같아요 :)

     

     

    프롤로그

     

    예전에 자바스크립트 강의를 듣다가 스크립트 파일에 적힌 const와 let이란 키워드를 보고,

    '허허허 강의 잘못 클릭했네 이건 JS강의가 아니잖아 허허' 이러고 뒤로가기를 누른 적이 있습니다.

     

    그때 var 말고도 다르게 변수를 선언할 수 있다는 걸 알고 엄청난 충격을 받았죠...... 

    너무 충격을 받아서 일기까지 썼을 정도 😵

     

     

    저같은 뉴비는 별게 다 충격적입니다 (aka 별다충)

     

     

     


     

     

    ES6가 뭐지?

     

    const와 let은 ES6에서 등장했습니다.

    그런데 ES가 뭐죠?

     

    이건 ECMAScript를 뜻합니다. Ecma라는 곳에서 '이거 표준안으로 합시다 땅땅'하고 정한 내용입니다.

    자바스크립트는 이 ES 사양을 잘 지키는 착한 스크립트 언어에요!

    하지만 브라우저별로 지원하는 ES가 다르기 때문에 호환성에 차이가 발생합니다.

    const는 ES6에서 등장한 키워드이기 때문에 ES6를 지원하지 않는 옛날 브라우저는 알아듣지 못합니다.

     

     

    말하자면 const는 '개발새발'이란 단어를 쓰는 것과 비슷합니다.

    (TMI: 이 단어는 원래 표준어가 아니지만 2011년에 표준어로 인정되었습니다.)

     

    어느날 제가 "개발새발이야!!" 이라고 외쳤다고 해볼게요. ( 👉 const foo='bar'; )

    요즘 사람들(=최신 브라우저)은 제 말을 알아듣고 이해하겠지만,

    옛날 사람들(=옛날 브라우저)은 이 말을 듣고 "그건 뭔 뜻이여? 괴발개발이라고 써야지!"라고 말하겠죠. ( 👉 var foo='bar'; )

     

    어... 예시가 좀 멀리 간 것 같은데... CSS를 다루시는 분이라면 CSS2와 CSS3의 차이라고 생각하시면 됩니다.

    CSS3에서 등장한 속성인 transform, flex, calc 등은 하위 IE 브라우저에선 쓸 수 없죠.

    다만 차이가 있다면 CSS3 속성은 아예 못 알아듣기 때문에 다른 속성을 써서 대체해야 하지만, ES6 문법은 Babel이라는 어썸한 애를 통해 옛날 브라우저도 알아들을 수 있게 번역이 가능합니다(!) 

    '할머니, 할아버지! 그건 괴발개발이란 뜻이에요.'라고 말해주는 멋진 손주가 있는... 그런 걸까요.... 😌

     

     

    아무튼 ES라함은 어떤 별도의 언어가 아니라 일종의 스펙에 해당합니다.

    그럼 어떤 스펙이 있을까요?

     

    ES3 (1999)

    우리가 일반적으로 알고 있는 자바스크립트!라 함은 보통 ES3를 뜻합니다. 호이스팅, 프로토타입 등 기본적인 특정이 들어있고 거의 모든 브라우저가 대응합니다. IE8까지 지원하는 페이지는 ES3를 쓰고 있다고 보면 됩니다.

     

    ES5 (2009)

    4를 뛰어넘고(??한자 문화권일까요...?) 10년만에 나왔습니다. forEach()map()등의 새로운 메소드를 지원하며, "use strict;"라는 엄격 모드를 선언할 수 있게 됐습니다.

     

    ES6 (ES2015)

    우리가 마주할 까리한 녀석입니다. 연도를 붙여 ES2015라고도 부릅니다.

     

     

     

    이제 ES6가 뭔지 알았으니, 여기서 등장한 const와 let에 대해 본격적으로 알아봅시다.

     

     

     


     

    const, 변치않는 그대

     

    const는 constant(상수)를 뜻합니다. 즉, '항상 같은 수'를 말합니다.

    변수(變數)인데 상수(常數)라니 이 무슨.... 말같지 않은 말이지만 아무튼 그렇습니다.

     

    상수이기 때문에 const 키워드로 선언하면 변치 않는 값을 갖는 변수를 생성합니다.

    그렇기에 const로 선언한 변수는 값을 재할당할 수 없습니다. 시도해도 TypeError를 먹게 될 거에요 🥣

     

    const myName = 'NY KIM';
    myName = 'Nana';  //TypeError: Assignment to constant variable

     

    또한 const 변수는 반드시 값이 할당되어야 합니다. 값 없이 선언하면 이번엔 SyntaxError를 뙇 먹겠죠.

     

    const a;
    console.log(a); //SyntaxError: Missing initializer in const declaration

     

     

     

    let, 변하게 내버려 둬

     

    let은 const와 다르게 다른 값이 재할당될 수 있습니다. (Let it go~ Let it go~ ❄️)

     

    let meal = 'Bulgogi';
    meal = 'Bibimbap';

     

     

    변수를 선언할 때 값을 할당하지 않을 수 있지만, 이 경우 값은 undefined가 됩니다.

     

    let a;
    console.log(a); //undefined

     

    따라서 const와 let 중 무엇을 쓸지는 재할당 여부에 따라 골라 쓰면 되겠습니다.

    - 나중에 다른 값 줄 거야!: let

    - 값 안 바꿀 건데: const

     

     

    근데 여기까지만 보면 굳이 var 대신 const와 let을 쓸 필요가 없어보입니다.

    var를 냅두고 얘네를 써야하는 이유가 뭘까요?

     

     


     

     

    var, 하고픈 거 다 해~

     

    자바스크립트는 다른 언어에 비해 좀 많이 자비롭습니다. 달리 말하면 느슨한 편이고, 안 좋게 말하면 대충 사는 편입니다.

    기존에 쓰던 var를 데려와보겠습니다.

     

    var age = 28;       //결과 : 25 (초기 변수 선언) 
    age = 18;           //결과 : 18 (변수값 재할당)  
    var age = 'old';    //결과 : 'old' (변수 재선언) 
    

     

     

    이걸 let으로 시도하면 어떨까요?

     

    let age = 28;
    age = 18;
    let age = 'old'; //SyntaxError: Identifier 'age' has already been declared

     

    변수 age가 이미 선언되었다면서 재선언을 거부하는 모습입니다.

    넵, const와 let은 재선언을 할 수 없다는 차이점이 있습니다.

     

    얼핏 보기엔 뭐든 다 ㅇㅋㅇㅋ하는 var가 편할 수도 있지만, 대규모 프로젝트에서 다양한 사람들이 작업을 하는 경우엔 영 좋지 못할 수도 있습니다. 누가 이미 찜한 변수명인지도 모르고 가져다 쓸 수도 있잖아요... 으어어 💦

     

    그럼 이렇게 재할당 문제를 막기 위해서 등장한 키워드냐면 그건 아니죠! 또다른 문제들도 해결해 줍니다.

     

     

     


     

     

    노 모어 호이스팅

     

    보통 자바스크립트를 작성할 때 변수를 가장 맨 위에 작성하도록 권장합니다.

    그런데... 이 변수를 나중에 선언하면 어떻게 될까요?

     

    var

    console.log(name); //결과: undefined
    var name = 'nana';

     

    정의되지 않은 타입(undefined)라고만 출력할 뿐, 이 코드는 문제 없이 잘 작동합니다.

     

     

     

    const / let

    console.log(product); //ReferenceError: Cannot access 'product' before initialization
    console.log(price); //ReferenceError: Cannot access 'price' before initialization
    const product = 'watch';
    let price = 500;

     

    var랑은 태도가 딴판이군요.

    어쨌든 변수를 선언한 뒤 써야 흐름에 맞으니 const와 let을 쓰는 게 더 논리적이고 직관적이네요.

     

     

     

    그럼 왜 var은 저렇게 오냐오냐해주는 걸까요? 이유는 바로 호이스팅(Hoisting) 때문입니다.

    이는 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 뜻합니다.

    변수가 함수 내에서 정의된 경우는 선언이 함수의 최상위로, 함수 바깥에서 정의된 경우는 선언이 전역 컨텍스트의 최상위로 변경된다는 거죠.

     

    그래서 var로 변수를 뒤에서 선언했지만 자바스크립트가 이걸 알아서 문서 꼭대기에 갖다 놓은 셈입니다.

    아하, 그럼 선언은 되었지만 값이 할당되지 않았기 때문에 undefined를 출력한 거였네요.

    즉, var 변수를 나중에 선언한 경우 자바스크립트 엔진은 아래처럼 해석한 것입니다.

     

    var name; //일단 꼭대기에서 선언하고 보자
    console.log(name); //name 변수에는 아무 값도 없으니까 undefiend 출력
    name = 'nana'; //변수에 값이 할당됐다!

     

     

    함수도 이와 비슷해서 함수선언문은 호이스팅이 일어나지만 함수표현식은 일어나지 않습니다.

     

    /* 함수선언문 */
    hello(); //결과: "Hello!";
    function hello() {
      console.log("Hello!");
    }
    
    /* 함수표현식 */
    bye();  //TypeError: bye is not a function
    var bye = function () {
      console.log("Bye!");
    }

     

    이처럼 자바스크립트에서는 코드의 순서와 상관 없이 변수와 함수선언문에 대한 메모리를 먼저 확보합니다.

    뭔가 알아서 꼭대기에다 놔주는 게 편한 것 같으면서도 좀 불편한 게... 자바스크립트의 매력인 걸까요?!

     

     


     

     

    const와 let, 우린 무려 블럭-범위라고?

     

    자바스크립트에서 변수, 그러니까 var는 Function-scope입니다. 함수 수준의 범위를 갖는다는 뜻이죠.

     

     

     

    어... 범위요....? 🤔

     

    Scope란, 프로그램 내에서 변수가 어디까지 접근할 수 있는지를 나타냅니다.

    어떤 변수는 아무데나 접근할 수 있지만 어떤 변수는 특정 문맥 내에서만 쓰이기도 합니다.

    예를 들면 창문 밖으로 보이는 풍경이라고도 할 수 있겠죠.

     

    우리는 누구나 창문 밖에 펼쳐진 푸른 하늘을 볼 수 있습니다. 말하자면 이건 '전역 범위'입니다.

    하지만 창문 밖으로 남산을 볼 수 있는 건 그 근처에 사는 사람들 뿐이죠. 이건 '지역 범위'이 되겠네요.

     

     

    아무튼 자바스크립트로 돌아와서, JS 내에서 var가 갖는 범위는 '함수'입니다.

    예시를 통해 알아볼게요.

     

    var level = 10;      //전역 변수 level을 선언했고 10이란 값을 할당했습니다.
    console.log(level);  //결과: 10
    
    function levelUp(){
      var level = 30;      //"함수 내"에서 지역 변수 level을 선언했습니다.
      console.log(level);  //결과: 30
    }
    
    console.log(level);  //결과: 10

     

    위 코드에서 마지막으로 호출한 level은 지역이 아닌 전역이기 때문에 값이 10인 것이 당연합니다.

     

    그런데 말입니다 😐

    다음과 같은 경우라면 어떨까요?

     

    var level = 10;      //전역 변수 level을 선언했고 10이란 값을 할당했습니다.
    console.log(level);  //결과: 10
    
    if (true) {
        var level = 30;      //"if 블럭 내"에서 지역 변수 level을 선언했습니다.
        console.log(level);  //결과: 30
    }
    
    console.log(level);   //결과: 30....??!! if 블럭이 끝나고 나왔더니 전역변수 level의 값이 바뀌었다?!?!?!?

     

    으...으으응????

    if문 안에서 지역변수를 썼는데 왜 전역변수가 바뀌는 거죠....??

     

    이건 if문이 함수가 아니라 블럭이기 때문입니다.

    조금 전에 var의 범위는 함수 수준이라고 했죠?

    즉, 블럭{}으로 감싸더라도 범위가 달라진 게 아니기 때문에 if 블럭 내에서 재할당된 값이 출력된 것입니다.

     

     

     

    허허헣ㅎㅎㅎ

    그럼 const와 let은 어떻게 반응할까요?

    놀랍게도 이들은 Block-scope, 그러니까 블럭 수준의 범위를 갖습니다.

     

    let level = 10;
    console.log(level);  //결과: 10
    
    function levelUp(){
      let level = 30;
      console.log(level);  //결과: 30
    }
    
    console.log(level);  //결과: 10
    let level = 10;
    console.log(level);  //결과: 10
    
    if (true) {
        let level = 30;
        console.log(level);  //결과: 30
    }
    
    console.log(level);   //결과: 10

     

    아까의 var와는 다르게 변수의 값이 바뀌지 않은 걸 볼 수 있습니다.

    즉, if 블럭 내의 let(지역변수)은 블럭 바깥의 let(전역변수)와 서로 다른 변수이고,

    블럭이 닫히는 시점에서 블럭 내 let의 유효범위는 끝나게 됩니다.

     

    다른 예시를 봅시다.

     

    var me = 28;
    let sister = 24;
    
    console.log('me : '+me, '| sister : '+sister);    // me : 28 | sister : 24
    
    if(2021>2020){
      var me = 29;
      let sister = 25;
      console.log('me : '+me, '| sister : '+sister);  // me : 29 | sister : 25
    }
    
    console.log('me : '+me, '| sister : '+sister);    // me : 29 | sister : 24 (???)
    
    function goBack(){
      var me = 12;
      let sister = 8;
      console.log('me : '+me, '| sister : '+sister);   // me : 12 | sister : 8
    }
    
    goBack();
    console.log('me : '+me, '| sister : '+sister);     // me : 29 | sister : 24 (???)

     

    var와 let 변수를 만들어 각각 값을 할당했습니다.

    if 블럭 내에서 지역변수를 만들어 값을 넣었는데, 블럭이 끝나고 나서 전역변수 var의 값이 29로 바뀐 걸 볼 수 있습니다.

    그런데 let은 블럭인 { } 내에서 영향이 끝나므로 값이 그대로 24입니다.

     

    함수를 만난 경우를 볼까요? 함수가 끝나고 변수값을 살펴보니 어라, 29와 24입니다.

    왜냐면 12랑 8의 유효범위는 함수까지니까요.

    함수가 끝나면 이 값들은 영향력이 없어지고, 대신 함수 선언 이전의 값이 출력됩니다.

    다만 var의 경우는 유효 범위가 블럭이 아니여서, 블럭 내 선언된 29의 영향을 받은 거고요.

     

     

    이 내용은 좀 많이 충격적이었어요....

    결국 블럭 내에서 선언한 var 변수는 전역이나 다름 없었다는 거잖아요ㅠㅠ!

     

    for (var i = 0; i < 10; i++) { }
    console.log(window.i) //결과: 10

     

    하지만 이제 const와 let이 있으니 걱정을 덜 수 있게 됐습니다.

     

    for (let = 0; i < 10; i++) {}
    console.log(i) //ReferenceError: i is not defined
    

     


     

    문자열에 변수 집어넣기

     

    ES6부터는 문자열에 변수를 넣는 것이 쉬워졌습니다.

    backticks (` `) 안에 ${변수명}과 같이 넣으면 됩니다.

     

    기존

    var name = 'Nana';
    console.log('Hello, my name is ' + name + ', nice to meet you!');

     

    ES6

    let name = 'Nana';
    console.log(`Hello, my name is ${name}, nice to meet you!`);

     

     


     

     

    const, 참조형이랑 찰떡

     

    아까 const는 값이 변하지 않고 let은 값이 변한다고 했는데,

    주로 값이 참조 타입(Array, Object, Function)일 때 const를 사용하는 걸 추천합니다.

     

    - 참조형(Complex type): Array, Object, Function

    - 원시형(Primitives type): String, Number, Boolean, null, undefined

     

     

    엇, 근데 const는 상수라며? 그럼 배열을 넣으면 안될 거 같은데? 라는 생각이 들 수도 있지만,

    참조 타입은 값을 바로 저장하지 않고 주소를 참조하기 때문에 const를 써도 값을 바꿀 수 있어요.

     

    const item = ['potion', 'sword'];
    const bag = item; //item 배열 참조
    
    item.push('gold bar');
    bag[0] = 'book'
    
    console.log(item); //결과: [ 'book', 'sword', 'gold bar' ]

     

    이 참조 타입에 대한 부분은 제가 좀 더 공부해서 별도 포스팅으로 정리하겠습니당!

     

     

     


     

     

    참고글

     

    자바스크립트의 변수범위와 호이스팅

    [ES6] var VS connst VS let

    [ES6] var, let 그리고 const

    js-scope(스코프)

     

     

    🤘끄읕!

     

     

     

    댓글 0