호이스팅과 TDZ

최예닮·2022년 9월 23일
0

호이스팅 답변 :

  • 호이스팅 : 변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮기는 것
    따라서 변수를 정의하는 코드보다 사용하는 코드가 앞서 등장할 수 있다.
    다만 선언과 초기화를 함께 수행하는 경우, 선언코드까지 실행해야 변수가 초기화된 상태가 됨 주의
자바스크립트는 함수의 코드를 실행하기 전에 함수 선언에 대해 메모리부터 할당. 덕분에 함수를 호출하는 코드를 함수 선언보다 앞서 배치할 수 있다.
예제 1.

1) 일반적인 경우
    function catName(name) {
    console.log("제 고양이의 이름은 " + name + "입니다");
    }

	catName("호랑이");

  /*
  결과: "제 고양이의 이름은 호랑이입니다"
  */

위의 코드 조각이 일반적으로 코드를 작성하는 순서라면, 함수를 선안하기 전에 먼저 호출했을 때를 봐보자.

예제2.

    catName("클로이");

    function catName(name) {
      console.log("제 고양이의 이름은 " + name + "입니다");
    }

    /*
    결과: "제 고양이의 이름은 클로이입니다"
    */

함수 호출이 함수 자체보다 앞서 존재하지만, 그럼에도 불구하고 이 코드 역시 동작함.

이것이 자바스크립트에서 실행 맥락이 동작하는 방식

TDZ 답변 :

  • let, const,class 구문의 유효성에 영향을 미침.
  • TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.
  • 반대로 var 변수는 선언 전에도 사용할 수 있기 때문에 var 사용은 피하는게 좋다.

1) 스코프,호이스팅,TDZ

스코프 : 
        (1) 전역 스코프 : 어디서든 참조 가능
        (2) 전역 변수 : 전역 스코프를 갖는 전역 변수, 어디서든 참조 가능
        (3) 지역 스코프 : 함수 자신과 하위 함수에서만 참조 가능
        (4) 지역 변수 : 지역 스코프를 갖는 지역 변수, 함수 내에서 선언된 변수로 해당 함수와 해당 함수의 
        	하위 함수에서 참조 가능
        (5) 암묵적 전역 변수 : 선언하지 않은 변수에 값을 할당하면 전역 객체의 프로퍼티가 되어 전역 
        	변수처럼 동작 하지만 변수 선언이 없었기 때문에 호이스팅은 발생하지 않는다.

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

함수선언문

function getName() {
    console.log('name');
}

var name = function() {
   console.log('name');
};

예제로 보는것처럼 함수를 변수에 담을 수 있다. 위 예제처럼 사용하는 것을 함수표현식 이라고 한다 그리고 function getName() 과 같이 함수를 선언하는 것을 함수 선언문 이라고 한다.
정확한 이해를 위해 예시를 살펴보자

1> 함수 표현식 예제

예시1)

var count;    // undefined

count();      // count는 함수가 아닌데 왜 함수를 호출하니?

var count = function() {
    console.log('count는 1이다.');
}

- 결과는 TypeError error : count is not function

count() 호출 후, var count를 선언하며 함수를 담았다.
var 는 호이스팅의 영향을 받으므로 위로 끌어올려진다.
그러므로 아래와 같이 var count; 가 가장 먼저 실행된다. 변수에 아무 값도 담지 않았으므로 undefined 상태이다.
그 후로 count()가 호출되면 위에 선언한 count가 호출되므로 변수를 호출하는 격이된다.
그러므로 not function 이라는 에러 메시지가 나타난다.

var count; // undefined

예시2)

var count;    //undefined

var count = function() {
    console.log('count는 1이다.');
}

count();

- 두번째 결과는 정상적으로 console.log가 동작한다.

var count가 호이스팅으로 인해 위로 끌어올려지지만, count() 호출 전 count에 함수를 담으므로
count() 를 호출하였을 때 정상 작동한다.

예시3)

var count;    //undefined

var count = function() {
    console.log('count는 1이다.');
}

count();

- 세번째 결과는 ReferenceError 이다.

세번째 예시는 첫번째 예시에서 varlet으로 바꾼 것이다.
세번째도 역시 에러를 발생하지만, 첫번째와 다른 Referecne Error가 발생한다.
let 은 호이스팅의 영향을 받지 않기 때문에, 예시 작성한 코드 순서대로 실행된다.
그러므로 count()라는 함수가 정의되지 않았는데 호출하였기 떄문이다.

2> 함수선언문 예시

예시1)

count();

function count() {
    console.log('count는 2이다.');
}

예시2)

function count() {
    console.log('count는 2이다.');
}

count();

- 첫번째, 두번째 모두 정상 작동한다.

호출이 함수선언문의 위에 있든 아래쪽에 있든 함수 선언문은 호이스팅 영향으로 끌어올려지기 때문이다.

3) 실행 컨텍스트와 콜 스택

실행 컨텍스트 (Execution Context) :

자바스크립트 엔진이 코드를 실행하기 위해선 코드에 대한 정보들이 필요합니다. 코드에 선언된 변수와 함수, 스코프, this, arguments 등을 묶어, 코드가 실행되는 위치를 설명한다는 뜻의 Execution Context라고 부릅니다. 자바스크립트 엔진은 Execution Context를 객체로 관리하며 코드를 Execution Context 내에서 실행합니다.

2가지로 나뉨

1) Global Execution Context :

코드를 실행하며 단 한 개만 정의되는 전역 Context입니다. global object를 생성하며 this 값에 global object를 참조합니다. 전역 실행 컨텍스트는 Call Stack에 가장 먼저 추가되며 앱이 종료 될 때 삭제됩니다.

2) Functional Execution Context :

함수가 실행 될 때 마다 정의되는 Context입니다. 전역 실행 컨텍스트가 단 한 번만 정의되는 것과 달리, 함수 실행 컨텍스트는 매 실행시마다 정의되며 함수 실행이 종료(return)되면 Call Stack에서 제거됩니다.

콜 스택 (CallStack) :

js 엔진은 생성된 Context를 관리하는 목적의 Call Stack(호출스택)을 갖고 있습니다. JS는 단일 스레드 형식이기 때문에 런타임에 단 하나의 Call Stack이 존재합니다. js 엔진은 전역 범위의 코드를 실행하며 Global Execution Context를 생성해 stack에 push합니다. 그리고 함수가 실행 또는 종료 될 때마다 Global Execution Context의 위로 Functional Execution Context stack을 push(추가), pop(제거)합니다.

Call Stack은 최대 stack 사이즈가 정해져있습니다. Call Stack에 쌓인 Context Stack이 최대치를 넘게 될 경우 ‘RangeError: Maximum call stack size exceeded’라는 에러가 발생합니다. 이 에러는 Stack Overflow라고 부르기도 합니다.

4) 스코프 체인, 변수 은닉화

스코프체인 :

변수와 함수는 유효범위가 있다.
자바스크립트는 C와 달리 for 문이나 if 문의 {} 이 유효범위가 없다.
함수만이 유효범위의 단위
이 유효범위는 [[scope]] 프로퍼티로 각 함수 객체 내에서 연결 리스트 형식으로 관리된다.

예제

var var1 = 1;
var var2 = 2;
function func() {
	var var1 = 10;
    var var2 = 20;
console.log(var1); // 결과값 10
console.log(var2); // 결과값 20
}

func();
console.log(var1); // 결과값 1
console.log(var2); // 결과값 2

위 코드를 실행하면 전역 실행 컨텍스트가 만들어지고 func() 함수 객체가 만들어진다.
func()는 전역 실행 컨텍스트 내에 있기 때문에 func 함수 객체의 [[scope]]는 전역 변수 객체가 된다.

출처 유블로그

func 실행 컨텍스트가 [[scope]] 프로퍼티를 만들 때는 자신이 속한 실행 컨텍스트의 [[scope]] 프로퍼티를 복사하고 현재 생성된 변수 객체를 스코프 체인 맨 앞에 추가한다.
이 스코프 체인으로 변수(식별자)를 탐색하는데, var1, var2 를 사용할 때 스코프체인을 따라 먼저 func를 검사하고, 없으면 스코프 체인을 따라가 다음 객체를 탐색한다.

요약 : 자기 자신의 스코프(scope)를 제외한 자신과 가장 가까운 변수 객체의 모든 스코프들을 스코프 체인이라 할 수 있다.

변수 은닉화 :

예제

(function () {
  var a = 'a';
})();

console.log(a); // a is not defined

함수 외부에서 a를 출력해보면, 아직 정의되지 않았다(a is not defined)는 에러메세지를 확인할 수 있습니다. 이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 합니다.

1. 클로저를 통한 은닉화

자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 prototype를 사용하는데 객체 안에서 사용할 속성을 생성할 때 this 키워드를 사용하게 됩니다.

하지만 이러한 Prototype을 통한 객체를 만들 때의 주요한 문제가 하나 있습니다. 바로 Private variables에 대한 접근 권한 문제입니다.

예시 2

function Hello(name) {
  this._name = name;
}

Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}

let a = new Hello('영서');
let b = new Hello('아름');

a.say() //Hello, 영서
b.say() //Hello, 아름

a._name = 'anonymous'
a.say() // Hello, anonymous

현재 Hello() 함수를 통해 생성된 객체들은 모두 name이라는 변수를 가지게 됩니다. 변수명 앞에 underscore()를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해 봤을때 이 변수는 Private variable으로 쓰고싶다는 의도를 알 수 있습니다.

하지만 실제로는 여전히 외부에서도 쉽게 접근가능한 것을 확인할 수 있습니다.

이 경우 클로저를 사용하여 외부에서 직접적으로 변수에 접근할 수 있도록 캡슐화(은닉화)할 수 있습니다.

예시 2-1

function hello(name) {
  let _name = name;
  return function () {
    console.log('Hello, ' + _name);
  };
}

let a = new hello('영서');
let b = new hello('아름');

a() //Hello, 영서
b() //Hello, 아름

이렇게 ab라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하도록 만들 수 있습니다.

예시 2-2
function a(){
  let temp = 'a' 
  
  return temp;
} 

// console.log(temp)  error: temp is not defined
const result = a()
console.log(result); //a

현재 위 함수 내부적으로 선언된 temp에는 직접적으로 접근을 할 수 없습니다. 함수 a를 실행시켜 그 값을 result라는 변수에 담아 클로저를 생성함으로써 temp의 값에 접근이 가능합니다.

profile
산을 오르려고 하는데 이제 주차장에 막 주차한 초보개발자

0개의 댓글