ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • let 과 const
    Web/자바스크립트 2023. 2. 6. 13:57

    아래 내용은 웹 개발자를 위한 자바스크립트의 모든 것 2장을 읽고 정리한 내용입니다.

    let과 const 란 ?

    var와 마찬가지로 변수를 선언하는 방식

    let

    초기화 필요없이 사용 가능하며, 기본 값이 undefined.

    let a;
    let b = 'let example';

    const

    초기화 필요하며, 기본 값이 없음.
    기본 값 미 설정 시 에러 발생

    const a = 'const example';
    const b; // Uncaught SyntaxError: Missing initializer in const declaration

    블록 스코프와 선언

    var

    선언 위치와 별개로 어디에서도 사용 가능하며, 반복된 선언 가능

    var test = 'abc';
    var test = 3; // 가능 

    let, const

    선언 후에 사용이 가능. 반복된 ‘선언’이 불가

    let test = 'abc';
    let test = 3; // Cannot redeclare block-scoped variable 'test'

    블록 스코프는 중괄호 {}로 둘러싸여지는 영역을 의미
    같은 블록 스코프 내에서는 동일한 let 변수를 선언할 수가 없기 때문에, 오류를 방지하게 해 준다.
    (*) var 변수는 선언 위치와 별개로 사용 가능한 전역 변수이며 여러 번 선언이 가능했기 때문에, 라이브러리 같은 경우 prefix 를 독창적으로 사용하였다

    섀도잉과 호이스팅 : 일시적 데드존

    섀도잉과 호이스팅은 변수가 살아있는 영역에 대한 이야기이다.

    1번 / 2번 예제의 차이는 “p 변수의 블록 내 선언”
    블록 내에 let을 통한 변수를 선언하면 (let p = “haha”) 선언 전에 사용하는 1번 예제의 경우 선언 전 사용이 되버린다. 이를 섀도잉 선언 상태라고 함.

    1번

    let p = "test"
    if(true) {
        p = "inside block"; // Uncaught ReferenceError: Cannot access 'p' before initialization
        let p = "haha";
    }

    2번

    let p = "test"
    if(true) {
        p = "inside block"; // Uncaught ReferenceError: Cannot access 'p' before initialization
    }

    호이스팅이란 ?

    변수가 선언되면 해당 선언은 자바스크립트 가장 상단으로 이동을 함
    이를 끌어올려진다는 의미의 “호이스팅”이라는 단어로 표현

    선언도 되지 않은 변수를 쓸 경우, 타 언어는 에러가 나지만 자바스크립트는 에러가 나지 않는다. 이유는 이미 선언되어 있기 때문이다.
    hoisting 주석 아래의 코드처럼 선언을 끌어올려서 처리 하므로 콘솔 로그에는 undefined로 출력된다. (변수의 기본 값은 undefined)

    // var
    console.log(test); /// undefined 출력 
    var test = "haha";
    
    // hoisting !
    var test;
    console.log(test);
    var test = "haha";
    
    // let
    console.log(abc);
    let abc = "haha"; // Uncaught ReferenceError: abc is not defined

    그렇다면 let 변수는 호이스팅이 되지 않는 것인가 ?
    대답은 아니다
    동일하게 호이스팅 되지만 임시 데드존 (TMZ) 개념을 사용

    아래 예제는 마치 let 변수를 선언도 전에 사용한 것처럼 보이지만, 에러가 나지 않는다.
    임시 데드존은 “위치”의 개념이 아니라 시간의 개념이기 때문.
    변수가 선언되고, 변수를 사용하는 함수가 호출 되므로 시간 적으로는 변수가 “선언 된 후” 사용 되는 것이라 선언 후 사용을 위반하지 않는다.

    아래 예제에서 함수와 변수 선언 위치를 변경하면 당연히 에러가 발생. (tmz2 함수)
    추가로, let_value를 함수 안에서 사용할 수 있는 건 블록 내에 있는 변수 선언과 함수 이기 때문이다.

    function tmz() {
        function tmp () {
            console.log(let_value);        // 3번
        }
        let let_value = 1;                 // 1번 
        tmp();                            // 2번
    }
    
    function tmz2() {
        function tmp () {
            console.log(let_value);        // 2번
        }
        tmp();                            // 1번
        let let_value = 1;                 // 3번 
    }

    새로운 종류의 전역

    전역으로 var 선언 시 window 객체에서 조회가 가능
    전역으로 let, const 선언 시, window 객체에서 조회가 불가

    var haha = "test";
    window.haha = "test";
    
    let hehe = "test2";
    window.hehe = undefined;

    위의 케이스의 let, const의 장점은 보안이 되고, window 객체를 간단하게 만듬
    또한, window 객체와 동일한 이름으로 var 변수를 선언할 경우 덮어 씌어진다
    단, 속성은 유지 된다. (아래 예제에서 숫자로 선언 했으나, 문자로 처리)

    var name = 3;
    window.name = '3';

    루프 내 클로저

    개인적으로는 let, const가 나와서 가장 유용해 진 부분이 클로저라고 생각
    변수 명의 중복 선언이라든가 블록 스코프 같은 부분들은 사실 변수 명을 잘 사용함으로써 습관적으로 해결했던 부분 이였는데, 클로저는 확연히 편해졌다.

    클로저 란?

    “클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.”
    이 말을 듣고 “아 그렇구나” 라고 이해할 수 있는 사람은 많지 않을 것이다.

    나는 아래 예제가 클로저를 이해하는데 도움이 되었다.

    makeadder는 함수 이면서 함수를 return 한다.
    makeAdder(5)는 x 값에 5를 가진 함수이다.
    한 번에 호출 하려면 makeAdder(5)(2) 로 호출하면 된다. (x = 5, z = 2)
    함수의 최종 결과가 나오려면 2번의 호출이 필요하므로, 1번의 호출을 한 결과는 마지막 호출을 위한 “환경”이 되는 것이다.
    makeAdder(5), makeAdder(10) 은 x 값이 5와 10인 환경을 이미 가진 함수인 셈. 이때, makeAdder(5)로 리턴 된 함수를 클로저라고 한다.
    = 함수와 함수가 선언된 어휘적 환경(x 값이 5)이 조합된 상태.

    function makeAdder(x) {
        var y = 1;
        return function(z) {
            y = 100;
            return x + y + z;
        };
    }
    
    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    //클로저에 x와 y의 환경이 저장됨
    
    console.log(add5(2));  // 107 (x:5 + y:100 + z:2)
    console.log(add10(2)); // 112 (x:10 + y:100 + z:2)

    아래 링크를 한 번은 정독하는 것을 추천!
    참고 : 클로저 - JavaScript | MDN

    이렇게 클로저를 열심히 적어둔 것은, 클로저가 for 문과 결합이 되면 ‘문제’라고 부를 만한 상황이 발생한다.

    for 문과 클로저 그리고 let

    아래 예제에서 onfocus는 함수는 클로저를 갖고 있다.
    showHelp는 item.help를 변수로 받는데 이 코드를 짤 때 예상하는 것은 각 i 에 맞는 item이 넘어가는 것이다. 하지만 실제는 가장 마지막 helpText 변수의 값만이 모든 Item의 onfocus에 할당이 된다.
    이유는, onfocus 함수는 실행이 되는 시점에 showHelp 함수를 호출하는데, 이 순간의 i는 이미 for 문이 끝났기 때문에 가장 마지막 helpText가 모든 onfocus에 할당이 된다.
    → 이 개념은 처음 봤을 때 이해를 전혀 못했고, 이제서야 이해하는 개념이라 잘 이해가 되지 않는다면 클로저에 대한 위의 참고 링크를 정독하고 실제로 코딩을 해봐야 함.

    for (var i = 0; i < helpText.length; i++) {
      var item = helpText[i];
      document.getElementById(item.id).onfocus = function () {
        showHelp(item.help);
      };
    }

    위의 문제를 해결하려면 var를 let으로 바꾸기만 하면 된다. (와아아 !)
    내가 적고자 하는 건, 이 다음의 이야기다.
    let으로 바꾸면 된다! 라는 것은 알았는데 “왜?” 라는 이유는 몰랐는데 그 이유에 대해 책에서 상당히 자세히 적어두었다.
    한 줄로 정리하면, let을 사용하면 그 순간의 컨텍스트를 따로 저장하기 때문이다.
    더 쉽게 적어보자면 위의 예제에서 helpText[0], helpText[1] … 을 복사해서 onfocus 함수를 만들 때 연결해 두는 것이다.
    이를 테면 아래와 같이.

      document.getElementById(item.id).onfocus = function () {
        showHelp(helpText[1].help);
      };

    당연히 이렇게 복사하는 방식은 속도 저하를 일으킨다.
    다행히 지금은 꽤 많은 최적화가 된 상태여서 큰 속도 저하 문제가 있다고 하진 않지만, 당연히 필요하지 않은 곳에서 사용하지 않는 것이 좋다.

    클로저가 필요하지 않은 곳에서 쓰지 않는 것은 객체 생성 시에도 동일하다.
    위의 참고 링크 클로저 - JavaScript | MDN 마지막 예제를 보자면, 아래와 같이 코드를 작성할 경우 getName은 object를 생성할 때마다 복사 하므로 protoType을 쓰는 것이 바람직하다고 되어 있다.

    function MyObject(name, message) {
      this.name = name.toString();
      this.message = message.toString();
      this.getName = function () {
        return this.name;
      };
    
      this.getMessage = function () {
        return this.message;
      };
    }
    

    'Web > 자바스크립트' 카테고리의 다른 글

    새로운 객체 기능  (0) 2023.04.15
    Day 5. Are they the "same"? [6kyu]  (0) 2023.03.05
    Javascript 클래스  (0) 2023.03.04
    화살표 함수  (0) 2023.02.11
Designed by Tistory.