오늘(엄밀히 말하면 어제) 2차 화이자 백신을 접종한 탓으로 인해 글이 다소 횡설수설할 수 있습니다.
머리에서 열이 좀 나는 것을 보면 몸이 건강해지려고 열일하고 있나 봅니다!!

 

학습을 하던 중 굉장히 신기한 내용을 발견했습니다.

아래 사진에서 i의 스코프를 var로 했을 때와 let으로 했을 때 동작의 차이가 난다는 것이었습니다.

 

 

저는 믿을 수 없었기 때문에 바로 콘솔창에 적어보았고, 정말 신기하게도 다른 결과가 나왔습니다.

 

 

기존에는 var의 모든 기능을 let이 흡수하고, const는 변하지 않는 케이스에 적용하면 되겠지라는 다소 위험한 생각을 갖고 JS를 사용해왔습니다.

그러다보니 var와 let의 차이점을 제대로 모른 채로 let을 함부로 사용하고 있었던 것이죠.

 

그렇다면 무엇 때문에 이런 차이가 발생하는 것일까요?

 

목차


    0. var와 let의 차이

    먼저 var와 let의 차이에 대한 기본적인 개념부터 알고 넘어가야 합니다.

     

    위에서 했던 예시를 조금 더 간략하게 작성했습니다.

    분명 var를 let으로만 바꿨을 뿐인데 let으로 바꾸자마자 console.log() 안에 있는 t를 인식할 수 없다는 오류 메시지가 뜹니다.

     

     

     

    감이 오시나요?

     

    var는 괄호가 아무리 많이 중첩이 되어있어도 가장 가까운 함수의 스코프 내로 들어가게 됩니다.

    반면 let은 가장 가까운 중괄호의 스코프에 들어가게 됩니다.

    let은 중괄호 내에서 선언되어서 중괄호 밖으로 나가게 되면 인식할 수 없게 되는 것이죠.

     

    조금 어렵게 말하면 var은 함수 레벨 스코프를 사용하고, let은 블록 레벨 스코프를 사용합니다. (이 용어는 뒤에서 설명할 겁니다!)

     

    여기까지 내용으로 var와 let의 차이를 이해하고 사용하실 수 있다면 사실상 포스팅의 모든 내용을 이해하신 것이나 다름이 없습니다. 

     

    하지만.. 이 내용은 저같은 초심자에게는 그렇게 마냥 단순한 내용은 아니었습니다. 저처럼 이해하지 못했다고 해서 너무 좌절하지 않으셨으면 합니다 ㅠㅠ

    아무튼 아직 이해하지 못한 분들을 위해서 좀 더 차근차근 알아봅시다!

     


    1. 스코프에 대한 이해

    위에서 스코프를 잠깐 언급했는데, 그렇다면 스코프가 도대체 뭘까요?!!

     

    스코프는 바로 변수의 값을 들여다볼 때 찾는 범위(유효범위)를 뜻합니다.

     

    ※ 이제부터 예시를 몇 가지 살펴볼텐데, this라는 object가 가리키는 요소는 생각하지 않았습니다. 이번 포스팅에서 this는 중요하지 않으니까요!

     

    1.0. 스코프 예시 1

    function a() {
    	var i = 1;
        console.log(i);
    }

     

     

    어떤 함수에서 변수를 선언했다면, 그 함수 스코프 내에는 내가 선언한 변수가 들어있겠죠?

    위 예시를 보면, a() 안에서는 i를 자유롭게 사용할 수 있다는 것을 알 수 있습니다.

    i라는 변수가 a라는 함수 안에서 선언되었기 때문에 a라는 스코프 안에서 i를 자유롭게 사용할 수 있는 것이죠.

     

     

    현재 스코프의 내용은 다음과 같습니다.

    global 스코프는 전역 공간을 뜻합니다.

    코드를 실행하면 반드시 생성되는 최상위 영역이고 각 웹페이지당 단 1개만 존재합니다.

     

    먼저 global 스코프에 a라는 함수가 선언되었기 때문에 global 스코프 안에 a()가 들어갑니다.

    그리고 a()라는 스코프가 새롭게 생성됩니다.

     

    이제 a()를 살펴보면 i=1을 선언한 부분이 있죠?

    이건 a()라는 스코프에 i가 1이라는 데이터가 들어갔다는 이야기와 같은 말입니다.

    이제 console.log(i)는 a()라는 스코프 내에서 i라는 요소를 찾을 것이고, 스코프 내에 i가 있기 때문에 그대로 가져다 쓰게 됩니다. 

     

     

    1.1. 스코프 예시 2

    function a() {
    	var i = 1;
        console.log(i);
    }
    console.log(i);

     

    그렇다면 이번 예시는 어떨까요?

     

     

    당연히 i는 a()안에서 선언만 되고 끝났기 때문에 i를 전역범위에서 막 사용할 수 없습니다.

    a() 밖에 있는 console.log()는 global에서 i를 찾지만 찾을 수 없는 것이죠!

     

     

     

    1.2. 스코프 예시 3

    var j = 2;
    function a() {
    	var i = 1;
        console.log(j);
    }

     

    마지막 예시입니다. 이번에는 반대로 a()에서 함수 밖의 내용을 참조하려고 하네요!

     

     

    이번에는 문제 없이 작동합니다!

    a() 스코프 내에 j가 없기 때문에, 자동으로 상위 스코프인 global 스코프에서 j를 찾아보게 됩니다.

    그리고 global 스코프에 j가 있기 때문에 그 j를 출력하는 것이죠.

     

     

    1.3. 스코프 체인

    스코프체인이란 위에서 본 예시처럼 현재 스코프에서 식별자(데이터)를 찾고, 원하는 내용이 없으면 다음 스코프로 넘어가는 것을 뜻합니다. (위 그림에서는 화살표가 체이닝을 의미합니다!)

     

    그리고 이렇게 상위 스코프로 이동하기 위한 경로는 연결리스트 형식으로 만들어지게 됩니다.

     

     


    2. 다양한 레벨 스코프

    스코프와 스코프 체인에 대해서 어느 정도 이해했다면, 위에서 언급한 내용중에 유일하게 이해하지 못한 부분이 아마도

    var은 함수 레벨 스코프를 사용하고, let은 블록 레벨 스코프를 사용한다

    라고 말한 부분일 것입니다.

     

    다시 차분히 그 윗부분을 살펴봅시다.

     

    var는 괄호가 아무리 많이 중첩이 되어있어도 가장 가까운 함수의 스코프 내로 들어가게 됩니다.
    반면 let은 가장 가까운 중괄호의 스코프에 들어가게 됩니다

     

    이게 바로 함수 레벨 스코프와 블록 레벨 스코프의 정의입니다.

     

    함수 레벨 스코프(Function-level scope)함수 내에서 선언된 변수는 함수 내에서만 유효하고 함수 외부에서는 참조할 수 없습니다.

    즉 스코프가 함수 단위로 만들어집니다.

    함수 내에서 생성한 변수는 그 함수 내에서는 어디서든 접근할 수 있는 것이죠.

     

    반면 블록 레벨 스코프(Block-level scope)코드 블록(for문, if문, while문, try/catch문) 내에서 선언된 변수는 코드 블록 내에서만 유효하고 코드블록 외부에서는 참조할 수 없습니다.

    스코프 생성에 있어서 중간 단계가 포함되었다고 보면 됩니다.

    이제는 함수가 기준이 아니라 { }를 기준으로 새로운 스코프가 생성되는 것이죠!

     

     

    이제 아까 봤던 예시에서 var을 let으로 바꾸고 코드블록 안에 넣으면 지금까지 내용을 쉽게 이해하실 수 있을 겁니다!!

     

    왼쪽 : var / 오른쪽 : let

     

     

    2.0. 총정리

    위에서 간단한 예시를 봤으니, 마지막으로 맨 처음에 봤던 예시를 다시 보겠습니다.

     

     

    먼저 setTimeout의 경우 비동기로 호출되는 함수이기 때문에 Call Stack에 쌓이지 않고 Task Queue로 먼저 이동하게 된다는 것을 이해해야 합니다.

    그렇기 때문에 setTimeout 내의 콜백함수의 동작은 Call Stack에 있는 내용이 모두 처리된 후, 즉 i가 이미 6까지 증가한 이후가 되겠죠.

     

    여기서 var과 let의 차이가 발생하는데요!

    var는 for문에서 생성된 스코프가 아닌 countSeconds() 스코프에 있는 i를 참조하게 됩니다.

    따라서 1초에 1번씩 countSeconds() 스코프 내에 있는 i인 6을 출력하게 됩니다.

     

     

    그러나 let은 for문이 돌 때마다 새롭게 블록 레벨에서 스코프가 생성되고, i가 저장됩니다.

    그래서 참조할 때 새롭게 생성된 스코프를 참조하기 때문에 우리가 원하는대로 1 2 3 4 5가 출력되는 것이죠!

     

     

     

     

    let으로 작성한 ES6 코드를 ES5 이하 형식으로 바꾸게 되면 더 명확히 보이게 됩니다.

    변화하는 i값을 저장하기 위해서 _loop라는 변수 안에 새로운 함수를 담았죠.

    이렇게 복잡했던 내용을 let을 통해 다른 언어들처럼 쉽게 표현할 수 있게 된겁니다!!

    완전 Awesome하군요 ㅎㅎㅎ

     

     


    3. 렉시컬 스코프

    만약 위 내용을 모두 이해하고도 배움에 대한 갈망이 남았다면, 자바스크립트의 특징중 하나인 렉시컬 스코프에 대해서 알아봅시다.

    렉시컬 스코프(Lexical scope)는 함수의 동작이 어디에서 호출하는 지가 아닌 어디에서 선언하는 지에 따라 결정된다는 것입니다.

     

    var x = 1;
    
    function first() {
      var x = 10;
      second();
    }
    
    function second() {
      console.log(x);
    }
    
    first();
    second();

     

    위 예시에서 출력이 과연 어떻게 나올까요?

    10 / 1 을 예상하셨다면 틀렸습니다.

     

     

    second 함수는 first 함수 내부에서 호출되었지만, second 함수가 선언된 곳이 전역이기 때문에 x를 찾는 스코프체인은 second() -> global 이 되어서 global의 1이 호출됩니다.

     

    자바스크립트는 실행 단계에서 코드들의 스코프를 결정하고, 렉시컬 스코프를 따르기 때문에 위와 같은 결과물이 도출되는 것이죠.

     

    이와 반대로 함수의 호출에 따라 상위 스코프가 결정되는 것을 다이나믹 스코프(Dynamic scope)라고 합니다. 

     


    4. 참고 자료

     

    [JS] 스코프 체인 이란?

    Scope chain 이란? 자바스크립트의 여러 중요한 개념중 하나인 scope chain 에 대해 개괄적인 내용을 담고자 합니다. scope 개념은 알고 있다는 전제로 설명합니다. 시작하기 전에.. function a(){ var a = 1;

    tyle.io

     

    자바스크립트 - 렉시컬 스코프(Lexical Scope)

    들어가기에 앞서, Closure(클로져)를 이해하기 위해서는 반드시 렉시컬 스코프(Lexical Scope)를 이해해야 한다. 렉시컬 스코프란(Lexical Scope)란? 함수를 어디서 호출하는지가 아니라 어디에 선언하였

    ljtaek2.tistory.com

     

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기