Js 변수 정의 과정, Hoisting

김종현·2023년 3월 17일
0

Js

목록 보기
12/12

변수 정의 과정

자바스크립트 엔진

-작성한 Js 코드는 Js 엔진을 통해 파싱되고 실행된다.
-node.js는 브라우저 외의 환경에서 Js 코드를 실행하도록 하는 프로그램
-브라우저 환경과 node.js 환경은 Js 코드를 작성해도 다르게 동작할 수 있다

자바스크립트 코드 실행

-Js 엔진은 코드 실행 전 실행 컨텍스트 생성

  • 생성 단계 : 변수 선언 읽기
  • 실행 단계 : 변수 값을 할당

렉시컬 환경(Lexical Environment

https://developer-alle.tistory.com/407
-코드 block, function, script를 실행하기 앞서 생성되는 특별한 객체로, 실행할 스코프 범위 안에 있는 변수와 함수를 프로퍼티로 저장하는 객체

  • 소스 코드를 실행하면서 참조가 필요한 변수의 값을 이 Lexical Environment 라는 객체에서 식별자 이름을 키로 찾는다고 보면 된다.
    -코드가 실행될 때 주변에 어떤 코드들이 있는지 그 환경을 의미.
    -식별자와 식별자에 바인딩 된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조
    -실행 컨텍스트 = Lexical Environment 컴포넌트 + Variable Environment 컴포넌트

-EC 내 LE에 variable object, scope chain이 속한다.

-함수의 렉시컬 환경은 함수가 사용하는 변수들을 둘러싼 환경을 의미한다
-특정 변수의 값은 함수의 렉시컬 환경 안에서 찾을 수 있다
-렉시컬 환경은 실행 컨텍스트 안에 정의된 Variable Object로 이해할 수 있다
-특정 변수가 렉시컬 환경에 존재하지 않는다면 Scope chain을 따라 외부 scope에서 변수를 찾는다.

  • 변수 d는 Lexcial Environment내에 존재하지 않기 때문에 Scope chain을 따라 외부 scope에서 변수 d를 seacrching

생성 단계에서의 코드 실행

[실행 컨텍스트 생성]
-자바스크립트 엔진은 생성 단계에서 함수 선언문, 함수 표현식, 변수 등을 읽어 실행 컨텍스트에 저장한다.(엄밀히 따지면 LE에 저장)
-변수의 경우 실행 컨텍스트의 렉시컬 환경을 구성한다.
-생성 단계에서 함수 전체(이름, 값)가 EC에 저장된다. 반면 변수는 값이 저장되지 않는다.

실행 단계에서의 코드 실행

[변수 값 할당, 코드 실행]
-Js 엔진은 변수에 값을 할당하는 구문을 만나야 실행 컨텍스트에 값을 저장. 그 전까진 아래와 같음.

  • var : undefined로 초기화
  • let/const : uninitialized 즉, 초기화x. 그전에 접근시 Error 발생.
    -그 외 코드를 한 줄씩 읽어 나가며 실행

자바 스크립트 Hoisting

-Hositing은 변수가 선언된 시점보다 앞에서 사용되는 현상
-함수는 생성 단계에서 함수 전체가 저장되므로 뒤에서 선언되어도 호출이 가능

-let, const 변수는 생성 단계에서 초기화되지 않는다.
-선언문 이전에 접근시 Reference Error가 발생
-이 경계를 Temporal Dead Zone(TDZ)라 한다
-따라서 let, const는 hoisting이 발생하지 않는다.

var, let, const

-var는 함수 스코프
-let, const는 블록 스코프

fop문의 동작 방식

for문의 loop는 한 번 실행될때마다 제거되었다가 다시 스택에 올라간다. 또한 '일정 간격'으로 0, 1, 2... 이렇게 나오는 게 아니라 한 번에 '쫘라락' 돌고 한꺼번에 '쫘라락' 나오게 된다.

함수 스코프 var의 for문

function countWithVar () {

  //0에서 4까지 for loop을 돌면서
  for (var i = 0; i < 5; i++) {

    //'1초 뒤 i를 출력하라'는 지시를 내린다.
    setTimeout(function () {

      //i를 출력
      console.log(i)
    }, 1000)
  }
}

countWithVar();
5
5
5
5
5

-i는 countWithVar 함수 스코프에 존재하는 변수.
-따라서 var i는 for 블럭이 끝난 시점에 소멸하지 않는다. 그러므로 for문에 의해 i=5인 상태로 남아있기 때문에 결과 값이 5/5/5/5/5가 나온다
-var i 는 countWithVar의 closure에 저장된다.

var의 함수 스코프 영역

function countWithVar () {
  // 여기부터
  for (var i = 0; i < 5; i++) {
    ...
    ...
  }
  // 여기까지가 var가 가지는 함수 스코프 영역
}

따라서 사실상 countWithVar는 아래와 같다.

function countWithVar () {

  var i; // ◀ ◀ ◀︎︎︎︎︎︎︎︎ i가 여기 선언된 것과 같다

  //0에서 4까지 for loop을 돌면서
  for (i = 0; i < 5; i++) {

    //'1초 뒤 i를 출력하라'는 지시를 내린다.
    setTimeout(function () {

      //i를 출력
      console.log(i)
    }, 1000)
  }
}

countWithVar();

-for문이 돌면서 i는 5까지 증가된다.
-그 1초 뒤부터 연속으로 실행될 console.log들은 let을 썼을때처럼 for문 블록 안에 있는, 즉 루프가 돌 때마다 주어진 각자만의 i를 출력하는 게 아니라 함수 내에 공유되는 i, 이미 5가 되어버린 i를 다 같이 출력하게 된다.

블록 스코프 let의 for문

function countWithLet () {

  //0에서 4까지 for loop을 돌면서
  for (let i = 0; i < 5; i++) {

    //'1초 뒤 i를 출력하라'는 지시를 내린다.
    setTimeout(function () {

      //i를 출력
      console.log(i)
    }, 1000)
  }
}

countWithLet(); //위의 함수를 호출

0
1
2
3
4

-for문 블록 안에 있는 즉 루프가 돌 때마다 주어진 각자만의 i를 출력.
-i는 for블럭 안에 존재하는 변수.
-따라서 let의 경우 for 루프가 돌 때마다 주어진 각자만의 i를 출력하므로 결과 값이 0/1/2/3/4가 나온다.
-각 for block이 실행된 후에 i는 소멸한다.

let의 블록 스코프 영역


  for (let i = 0; i < 5; i++) {
    ...
    // let이 가지는 for문의 블록 영역
    ...
  }

let/const vs var

f2(); //1번

var f = function (){
  console.log('function 1');
  console.log('f3-', f3);  //2번
};

f3(); //3번

let f4 = () => console.log('function 4');

function f2(){
  console.log('function 2');
  f4(); //4번
}

var f3 = () => console.log('function 3');

-4번에서 레퍼런스에러가 발생하는 이유는 let으로 선언된 변수가 호이스팅 되지 않기 때문이다. 따라서 f4가 호이스팅 되지 않으며 f2 호출 시 f4는 uninitialized 상태의 변수가 된다.
*let/const는 호이스팅 되지 않은 상태에서 호출시 'reference error'

-3번 실행 시 2번이 'f3-undefined'가 되는 이유는 f3이 hoisting 되었기 때문이다.
=>f3는 변수만 선언된 상태이므로 할당이 되지 않아 undefined이다.

-4번의 에러를 수정하려면 f4가 f2의 렉시컬 스코프에 등록되고 초기화되어야 한다.
=>4번의 에러는 f2 호출 시 여전히 f4가 uninitialized 상태이기 때문이다. 따라서 f2 호출 시 완전히 초기화 되어야 한다.

profile
나는 나의 섬이다.

0개의 댓글