Scope (임시)

김민재·2024년 1월 4일
1

각각의 Statement를 실행하기 전

각각의 Statement를 실행하기 전

  1. var 변수에 할당한 함수 표현식은 Global Scope에 호이스팅이 일어나고 동시에 undefined로 초기화된다.
  2. function 키워드로 생성한 일반 함수는 Global Scope에 호이스팅 일어나고 동시에 함수의 정의부까지 전부 초기화된다.
  3. letconst로 선언한 함수들은 Scirpt Scope에 호이스팅 된다. 또한 초기화도 일어나지 않아 디버거 내에서 값이 <value unavailable>로 표시되는 것을 볼 수 있다.

모든 Statement를 실행한 이후

모든 Statement를 실행한 이후

  1. 각각의 Statement를 실행할 때마다 각각의 변수에 의도한 값으로 초기화된다.
  2. function 키워드로 생성한 일반 함수는 변화가 없다.

브라우저 환경에서의 Global Scope는 window 객체다.

브라우저 환경에서의 Global Scope는 window 객체다.

  1. 브라우저 환경에서의 Global Scope는 window 객체를 가리킨다.
  2. 그래서 this.a_var_function, window.a_var_function, globalThis.a_var_function 모두 같은 함수를 가리킨다.

Global Scope에서 Script Scope는 접근할 수 없다.

  1. this.a_const_function 등의 참조를 반환하면 undefined가 반환된다.
  2. 참조할 때 this 키워드를 붙이면 명시적으로 현재의 this, 즉 Global Scope인 window 객체에서 참조를 찾는다.
  3. 하지만 Global Scope에서 Script Scope는 접근할 수 없기 때문에 undefined가 반환된다.

Scope Chaining

  1. this 키워드를 제거하고 참조를 반환하면 정상적으로 값을 반환한다.
  2. Scope를 명시하지 않을 경우 Scope에 상단부터 값을 찾아나간다.
  3. 여기서는 Script Scope가 Global Scope보다 먼저 있으므로 Script Scope 먼저 조사를 한다.

Closure

예제 코드 설명

function a_function_1() {
  const constant_of_function_1 = 10;
  const a_function_2 = function () {
    console.log(constant_of_function_1);
  };
  a_function_2();
}

a_function_1();

a_function_1은 내부에 중첩함수로 a_function_2를 갖고, a_function_2a_function_1의 지역변수인 constant_of_function_1 변수를 사용한다.

(1) a_function_1 함수 정의

a_function_1function 키워드로 선언된 일반함수이므로 Global Scope에 호이스팅 및 즉시 초기화가 완료된다.

(2) a_function_1 실행

a_function_1가 실행되는 순간 함수 실행 컨텍스트(Functional Execution Context)인 Local Scope가 생성된다. Local Scope가 생성되면서 함수의 지역 변수인 constant_of_function_1a_function_2가 호이스팅되어 Local Scope에 등록된다. 하지만 const 변수이기 때문에 초기화는 되지 않고 <value unavailable>로 남아있다.

(3) a_function_1 지역 변수 초기화

a_function_1 함수의 지역 변수인 constant_of_function_1a_function_2를 초기화하는 statement를 실행하여 초기화한다. Local Scope의 변수들에 값이 할당된 것을 볼 수 있다.

(4) a_function_2 실행

a_function_2 함수가 실행되면서 다시 a_function_2의 함수 실행 컨텍스트인 Local Scope가 만들어진다. 동시에 상위 Lexical Scope인 a_function_1의 Local Scope를 의미하는 Closure Scope가 만들어진다. a_function_2a_function_1의 지역 변수인 constant_of_function_1를 사용하기 때문에 이 변수가 Closure Scope에 초기화된 상태로 등록된다. 이로써 a_function_2a_function_1의 지역 변수인 constant_of_function_1를 사용할 수 있게 되었다.

(5) a_function_2 종료

a_function_2가 종료되면서 a_function_2의 Local Scope가 사라지고, 동시에 Closure Scope도 사라진다.

(6) a_function_1 종료

마지막으로 a_function_1가 종료되면서 a_function_1의 Local Scope도 사라지고, Global Scope만 남게 되었다.

연습

(1) 아래 코드의 출력값은?

function a_counter() {
  for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
      console.log(i); // 출력값 4, 4, 4
    }, 1000);
  }
  console.Log("for 루프 종료", i); // 출력값 4
}
a_counter();

정답은 4만 네 번 나온다. 반복문 변수 ia_counter의 Local Scope에 선언되어 있으며, 내부 함수는 a_counter의 Local Scope를 Closure Scope로 갖는다. 따라서 1초가 지난 이후에는 이미 i가 모두 4로 초기화되어 있기 때문에 4만 나오는 것이다.

내부 함수에서는 Local Scope 다음 a_counter의 Local Scope를 가리키는 Closure Scope를 사용하고, 여기에 i가 정의되어 있다.

(2) 아래 코드의 출력값은?

function a_counter() {
  for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
      console.log(i); // 1, 2, 3 출력
    }, 1000);
  }
  console.log("for 루프 종료", i); // 에러 발생
}
a_counter();

letconst 변수는 Block Scope를 갖는다. 반복문을 반복할 때마다 Block을 새로 생성하고, i 변수는 생성된 Block 내부에 독립적으로 존재한다. 그리고 해당 Block 내부에서 console.log(i)을 실행하기 때문에 1, 2, 3을 정상적으로 출력한다. 하지만 i는 블록 내부에 선언되어 있으므로 a_couter에서 접근할 수 없기 때문에 console.log("for 루프 종료", i)에서 에러가 발생한다.

내부 함수에서는 Local Scope 다음 Block Scope를 사용하고, 여기에 i가 정의되어 있다. 그런데 Closure Scope가 보이지 않는다. 이유가 무엇일까?

내부 함수의 Closure Scope가 없는 이유

이유는 단순하다. Closure Scope를 사용할 이유가 없기 때문에 굳이 만들지 않도록 V8 엔진이 최적화를 한 것이다. a_counter의 지역 변수를 사용하도록 코드를 수정하면 Closure Scope가 생성된다.

(3) 아래 코드의 출력값은?

function a_counter() {
  for (var i = 1; i <= 3; i++) {
    (function () {
      var clone = i;
      setTimeout(function () {
        console.log(clone); // 1, 2, 3 출력
      }, 1000);
    })();
  }
  console.log("for 루프 종료", i); // 4 출력
}
a_counter();

반복문마다 새로운 함수 실행 컨텍스트가 생성되고, 해당 컨텍스트 내부의 clone 변수에 i 값을 복사해서 저장한다. console.log(clone)을 실행할 때는 즉시 실행 함수를 가리키는 Closure Context의 clone 변수를 참조하기 때문에 1, 2, 3이 정상적으로 출력된다. 참고로 a_counter를 가리키는 Closure Context도 생성된다. 즉, Scope Chain은 Local Scope > Closure Scope (즉시 실행함수) > Closure Scope (a_counter) > Global Scope 순으로 생성된다.

네, 맞습니다. 호이스팅은 자바스크립트에서 특정한 동작 방식을 설명하는 용어로, 변수와 함수 선언이 해당 스코프의 최상단으로 끌어올려지는 현상을 말합니다. 이 현상은 컴파일러가 코드를 해석하는 단계에서 일어나며, 이 때문에 변수와 함수 선언은 코드 내 어디에서든 접근이 가능합니다.


자바스크립트는 기본적으로 컴파일 단계에서 코드를 두 번 거치는데, 첫 번째 단계에서는 호이스팅이 일어나고, 두 번째 단계에서는 실제 코드 실행이 이루어집니다. 호이스팅이 일어나는 이유는 컴파일러가 스코프 안에 어떤 변수와 함수들이 존재하는지 미리 알아내기 위해서입니다.


이렇게 스코프 안의 변수와 함수들을 미리 등록함으로써, 런타임 중에 변수나 함수를 찾는 시간을 줄일 수 있습니다. 이를 통해 코드의 실행 속도를 향상시킬 수 있으며, 또한 변수나 함수를 선언하기 전에도 참조할 수 있는 유연성을 제공합니다. 그러나 이런 유연성은 예상치 못한 결과를 초래할 수 있으므로, 호이스팅을 이해하고 올바르게 관리하는 것이 중요합니다.

0개의 댓글