ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 호이스팅과 TDZ는 무엇일까 ?
    Frontend/JavaScript 2022. 7. 21. 18:02

    스코프, 호이스팅, TDZ

     

    스코프


    • 자바스크립트의 스코프는 함수 레벨 스코프를 따른다.
    • 같은 함수 레벨에 존재하면 값을 참조할 수 있다는 건데
    • ES6에서 let 키워드가 도입되면서 블록 레벨 스코프를 사용할 수 있게 됐다.

     

    전역 스코프

    - 어디서든 참조 가능

    전역 변수

    - 전역 스코프를 갖는 전역 변수

    - 어디서든 참조 가능

     

    지역 스코프

    - 함수 자신과 하위 함수에서만 참조 가능

    지역 변수

    - 지역 스코프를 갖는 지역 변수

    - 함수 내에서 선언된 변수로 해당 함수와 해당 함수의 하위 함수에서 참조 가능

     

    암묵적 전역 변수

    - 선언하지 않은 변수에 값을 할당하면 전역 객체의 프로퍼티가 되어 전역 변수처럼 동작한다.

    - 하지만 변수 선언이 없었기 때문에 호이스팅은 발생하지 않는다.

    (variable = 1) === (window.variable = 1)
    
    //////////////////////////////////////
    
    console.log('test', test);
    
    function temp () {
      test = 10;
    };
    
    temp(); // test is not defined

     

     

     

     

    호이스팅


    • 함수의 코드를 실행하기 전에 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것.
    • 초기화를 제외한 선언만 호이스팅.
    • 그렇기 때문에 선언, 정의된 코드보다 호출하는 코드를 먼저 배치할 수 있음.
    • 변수의 선언과 초기화를 분리.
    • 변수의 선언을 코드의 최상단으로 끌어올림.
    catName("조미료");
    
    function catName(name) {
      console.log("제 고양이의 이름은 " + name + "입니다");
    }
    
    // "제 고양이의 이름은 조미료 입니다"

     

    변수 선언 형식에 따른 초기화

    var : 호이스팅 시 undefined로 변수를 초기화

    function : 선언된 위치와 상관없이 동일하게 호출

    let, const : 호이스팅 시 변수를 초기화하지 않음. (호이스팅 대상은 맞음)

    console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
    var num; // 선언
    num = 6; // 초기화
    
    console.log(num2); // ReferenceError: num2 is not defined
    let num2 = 2;
    
    //-----------------------------------------------
    
    catName("조미료"); // "제 고양이의 이름은 조미료 입니다"
    
    function catName(name) {
      console.log("제 고양이의 이름은 " + name + "입니다");
    }
    
    catName("조미료"); // "제 고양이의 이름은 조미료 입니다"

     

    TDZ(Temporal Dead Zone, 일시적 사각지대)

    - TDZ의 영항을 받는 구문 const, let, class

    - 변수 스코프의 맨 위에서부터 ~ 변수의 초기화 완료 시점까지의 변수는 TDZ에 들어간 변수

    - 코드의 작성 순서(위치)가 아니라 코드의 실행 순서(시간)에 의해 형성

     

    let, const

    {
        // <----- TDZ가 스코프 맨 위에서부터 시작
        const func = () => console.log(letVar); // OK
    
        // TDZ 안에서 letVar에 접근하면 ReferenceError
    
        let letVar = 3; // letVar의 TDZ 종료 ------->
    
        func(); // TDZ 밖에서 호출함
    }

    - let 변수 선언 코드가 그 변수에 접근하는 함수보다 아래에 위치하지만 함수의 호출 시점이 사각지대 밖이므로
    정상 동작.

     

    class

    - 부모 클래스를 상속받을 경우, 생성자 안에서 super()호출을 하기 전까지 this바인딩은 TDZ안에 있다.

    // (X)
    class User extends Member {
      constructor(phone) {
        this.phone = phone;
        super(phone);
      }
    }
    
    // (O)
    class User extends Member {
      constructor(phone) {
        super(phone);
        this.phone = phone;
      }
    }

     

     

    결론

    - TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.

    - var의 사용은 의도치 않은 중복선언과 재할당으로 문제가 생길 수 있기 때문에 사용하지 않는편이 좋다.

     

     

     

    함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

    함수 선언식과 함수표현식이란?

    함수 선언식 (function declartion)

    함수명이 정의되어 있고, 별도의 할당 명령이 없는 것

    function sum(a,b) {
        return a + b;
    }

    함수 표현식 (function Expression)

    정의한 function을 별도의 변수에 할당하는 것

    const sum = function(a,b) {
        return a + b;
    }

     

    함수 선언식과 함수 표현식의 차이

    주요 차이점은, 호이스팅에서 차이가 발생합니다.

    함수 선언식은 함수 전체를 호이스팅 합니다. 정의된 범위의 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있다는 것입니다.

    함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅 하게 됩니다. 선언부만 호이스팅하게 됩니다.

     

    예제 1 )

    함수 선언식 - 정상으로 해당 값 출력

    sum(50, 50); // 100
    minus(100, 50) // Uncaught TypeError: minus is not a function
    
    function sum(a, b) { // 함수 선언식
      return a + b;
    }
    
    var minus = function (a,b) { // 함수 표현식
      return a - b;
    }

    위는 호이스팅이 마치게 되면 다음과 같이 표현할 수 있습니다.

    function sum(a, b) { // 함수 선언식 - 함수 전체 호이스팅
      return a + b;
    };
    
    var minus; // 함수표현식 - 선언부만 호이스팅
    
    sum(50, 50); // 100
    minus(100, 50) // Uncaught TypeError: minus is not a function
    
    function (a,b) { // 함수 표현식 - 할당부는 그대로
      return a - b;
    }

    아래와 같이, 오류나는 부분, 즉 함수 표현식을 코드를 호출하는 부분 위에 작성하면 에러없이 정상적으로 출력됩니다. 오류는 나지 않지만, 함수 표현식과 선언식으로 쓰면서 일관성없이 이렇게 작성해야할 필요가 있나 느껴집니다.

    var minus = function (a,b) { // 함수 표현식
      return a - b;
    }
    
    sum(50, 50); // 100
    minus(100, 50) // 50
    
    function sum(a, b) { // 함수 선언식
      return a + b;
    };

     

    함수 선언식으로 작성한 함수는, 함수 전체가 호이스팅 된다고 하였는데, 전역적으로 선언하게 되면, 중복적으로 동명의 함수를 쓰게 된다면, 원치 않는 결과를 초래할 수 있습니다.

    함수 표현식으로 작성하게되면 이를 방지할 수 있습니다.

     

     

     

    여러분이 많이 작성해온 let, const, var, function 이 어떤 원리로 실행되는지 알 수 있어요.

    1. 변수 선언 방식

    우선, var는 변수 선언 방식에 있어서 큰 단점을 가지고 있다.

        var name = 'bathingape'
        console.log(name) // bathingape
    
        var name = 'javascript'
        console.log(name) // javascript

    변수를 한 번 더 선언했음에도 불구하고, 에러가 나오지 않고 각기 다른 값이 출력되는 것을 볼 수 있다.

    이는 유연한 변수 선언으로 간단한 테스트에는 편리 할 수 있겠으나, 코드량이 많아 진다면 어디에서 어떻게 사용 될지도 파악하기 힘들뿐더러 값이 바뀔 우려가 있다.

    그래서 ES6 이후, 이를 보완하기 위해 추가 된 변수 선언 방식이 let  const 이다.

    위의 코드에서 변수 선언 방식만 바꿔보자.

        let name = 'bathingape'
        console.log(name) // bathingape
    
        let name = 'javascript'
        console.log(name) 
        // Uncaught SyntaxError: Identifier 'name' has already been declared

    name이 이미 선언 되었다는 에러 메세지가 나온다. (const도 마찬가지)

    변수 재선언이 되지 않는다.

    그렇다면 let  const 의 차이점은 무엇일까?

    이 둘의 차이점은 immutable 여부이다.

    let 은 변수에 재할당이 가능하다. 그렇지만,

        let name = 'bathingape'
        console.log(name) // bathingape
    
        let name = 'javascript'
        console.log(name) 
        // Uncaught SyntaxError: Identifier 'name' has already been declared
    
        name = 'react'
        console.log(name) //react

    const는 변수 재선언, 변수 재할당 모두 불가능하다.

        const name = 'bathingape'
        console.log(name) // bathingape
    
        const name = 'javascript'
        console.log(name) 
        // Uncaught SyntaxError: Identifier 'name' has already been declared
    
        name = 'react'
        console.log(name) 
        //Uncaught TypeError: Assignment to constant variable.

    2. 호이스팅

    호이스팅(Hoisting)이란, var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말한다.

    자바스크립트는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅한다.

    하지만, var 로 선언된 변수와는 달리 let 로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다.

    	console.log(foo); // undefined
    	var foo;
    
    	console.log(bar); // Error: Uncaught ReferenceError: bar is not defined
    	let bar;

    이는 let 로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

    참고로, 변수는 선언 단계 > 초기화 단계 > 할당 단계 에 걸쳐 생성되는데

    var 으로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 하지만,

    // 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
    // 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
    
    console.log(foo); // undefined
    
    var foo;
    console.log(foo); // undefined
    
    foo = 1; // 할당문에서 할당 단계가 실행된다.
    console.log(foo); // 1

    let 로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다.

    // 스코프의 선두에서 선언 단계가 실행된다.
    // 아직 변수가 초기화(메모리 공간 확보와 undefined로 초기화)되지 않았다.
    // 따라서 변수 선언문 이전에 변수를 참조할 수 없다.
    
    console.log(foo); // ReferenceError: foo is not defined
    
    let foo; // 변수 선언문에서 초기화 단계가 실행된다.
    console.log(foo); // undefined
    
    foo = 1; // 할당문에서 할당 단계가 실행된다.
    console.log(foo); // 1

    3. 정리

    그렇다면, 어떤 변수 선언 방식을 써야할까?

    변수 선언에는 기본적으로 const를 사용하고, 재할당이 필요한 경우에 한정해 let 을 사용하는 것이 좋다.

    그리고 객체를 재할당하는 경우는 생각보다 흔하지 않다. const 를 사용하면 의도치 않은 재할당을 방지해 주기 때문에 보다 안전하다.

    1. 재할당이 필요한 경우에 한정해 let 을 사용한다. 이때, 변수의 스코프는 최대한 좁게 만든다.
    2. 재할당이 필요 없는 상수와 객체에는 const 를 사용한다.

     

    실행 컨텍스트와 콜 스택

    Execution context(실행 컨텍스트)

    자바스크립트 코드가 실행되는 환경을 의미한다.
    자바스크립트에서 대표적으로 두 가지 타입의 Execution context가 있다.

    실행할 코드에 제공할 환경 정보들을 모아놓은 객체들로
    자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다.

    1. Global Execution context
      자바스크립트 엔진이 처음 코드를 실행할 때 Global Execution Context가 생성된다. 생성 과정에서 전역 객체인 Window Object (Node는 Global) 를 생성하고 this가 Window 객체를 가리키도록 한다.
    2. Function Execution context
      자바스크립트 엔진은 함수가 호출 될 때마다 호출 된 함수를 위한 Execution Context를 생성한다.
      모든 함수는 호출되는 시점에 자신만의 Execution Context를 가진다.

    자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음과 같은 현상이 발생한다.

    • 호이스팅이 발생한다(선언된 변수를 위로 끌어올린다)
    • 외부 환경 정보를 구성한다
    • this 값을 설정한다.

    call stack
    코드가 실행되면서 생성되는 Execution Context를 저장하는 자료구조

    엔진이 처음 script를 실행할 때, Global Execution Context를 생성하고 이를 Call Stack에 push한다.

    그 후 엔진이 함수를 호출할 때 마다 함수를 위한 Execution Context를 생성하고 이를 Call Stack에 push 한다.

    자바스크립트 엔진은 Call Stack의 Top에 위치한 함수를 실행하며 함수가 종료되면 stack에서 제거(pop)하고 제어를 다음 Top에 위치한 함수로 이동한다.

    1줄 요약 : 프로그램이 함수 호출을 추적할때 사용한다.

     

     

    스코프 체인, 변수 은닉화

    스코프 체인

    함수는 전역에서 정의할 수도 있고 함수 몸체 내부에서도 할 수 있다. 함수 몸체 내부에서 함수가 정의된 것을 '함수의 중첩', 중첩 함수를 포함하는 함수를 '외부 함수'라고 한다.

    함수는 중첩될 수 있으므로 함수의 지역 스코프도 중첩될 수 있는데, 이는 스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미한다. 이때 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라고 지칭한다.

    var x = "global x";
    var y = "global y";
    
    function outer (){
      var z = "outer's local z";
      console.log(x); //global x
      console.log(y); //global y
      console.log(z); //outer's local z
      
      function inner (){
        var x = "inner's local x";
        console.log(x); //inner's local x
        console.log(y); //global y
        console.log(z); //outer's local z
      }
      inner();
    }
    outer();
    
    console.log(x); //global x
    console.log(z); //ReferenceError: z is not defined

    위의 예제 코드는 outer 함수가 만든 지역 스코프 내에 inner 함수가 만든 지역 스코프가 있으므로 outer 스코프가 inner 스코프의 상위 스코프이다. 그리고 outer 함수의 지역 스코프의 상위 스코프는 전역 스코프이다.

    이처럼 모든 스코프는 하나의 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역 스코프이다.

    스코프 체인에 의한 변수 검색

    변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 참조할 변수가 존재하지 않는다면 상위 스코프의 방향으로 이동하며 선언된 변수를 검색한다. 이를 통해 상위 스코프에서 선언한 변수를 하위 스코프에서 참조할 수 있다. 반대로 하위 스코프에서 유효한 변수는 상위 스코프에서 참조할 수 없다.


    캡슐화와 정보 은닉

    캡슐화는 객체의 상태를 나타내는 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다. 캡슐화는 객체의 특징 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라 한다.

    정보 은닉은 적절치 못한 접근으로부터 객체의 상태가 변경되는 것을 방지해 정보를 보호한다.

    대부분의 객체지향 프로그래밍 언어는 클래스를 정의하고 그 클래스를 구성하는 멤버에 public, private, protected 같은 접근 제한자를 선언하여 공개 범위를 한정할 수 있다. 하지만 자바스크립트는 접근 제한자를 제공하지 않는다.

Designed by Tistory.