스코프

김원호·2022년 7월 19일
0

스코프란

var x = 'global';
function foo () {
  var x = 'function scope';
  console.log(x);
}
foo(); // ?
console.log(x); // ?
  • 위 예제에서 전역에 선언된 변수 x는 어디에든 참조할 수 있다. 하지만 함수 foo 내에서 선언된 변수 x는 함수 foo 내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없다. 이러한 규칙을 스코프라고 한다.
  • 스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다. 디렉터리가 없는 컴퓨터를 생각해보자. 디렉터리가 없다면 같은 이름을 갖는 파일을 하나밖에 만들 수 없다. 스코프도 이와 같이 식별자 이름의 충돌을 방지한다.

스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙이다. 자바스크립트는 이 규칙대로 식별자를 찾는다.

[그림 1] 유효범위 종류

스코프의 구분

자바스크립트에서 스코프를 구분해보면 다음과 같이 2가지로 나눌 수 있다.

전역 스코프 (Global scope)
코드 어디에서든지 참조할 수 있다

지역 스코프 (Local scope or Function-level scope)
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.

모든 변수는 스코프를 갖는다. 변수의 관점에서 스코프를 구분하면 다음과 같이 2가지로 나눌 수 있다.

전역 변수 (Global variable)
전역에서 선언된 변수이며 어디에든 참조할 수 있다.

지역 변수 (Local variable)
지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

스코프의 특징

대부분의 C-family language는 블록 레벨 스코프(block-level scope)를 따른다. 블록 레벨 스코프란 코드 블록({…})내에서 유효한 스코프를 의미한다. 여기서 “유효하다”라는 것은 “참조(접근)할 수 있다”라는 뜻이다.

int main(void) {
  // block-level scope
  if (1) {
    int x = 5;
    printf("x = %d\n", x);
  }
  printf("x = %d\n", x); // use of undeclared identifier 'x'
  return 0;
}

C언어 코드를 보면 if문 내에서 선언된 변수 x는 if문 코드 블록 내에서만 유효하다. 즉, if문 코드 블록 밖에서는 참조가 불가능하다.

하지만 자바스크립트는 함수 레벨 스코프(function-level scope)를 따른다. 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.

단, ECMAScript 6에서 도입된 let keyword를 사용하면 블록 레벨 스코프를 사용할 수 있다.

var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1
let y = 0;
{
  let y = 1;
  console.log(y); // 1
}
console.log(y);   // 0

스코프의 종류

1. 전역 스코프(Global scope)

2. 비 블록 레벨 스코프(Non block-level scope)

3. 함수 레벨 스코프(Function-level scope) VS 블록 레벨 스코프(Block-level scope)

  • 블록 레벨 스코프
    • if, for, while, try/catch 등 코드 블록이 지역 스코프 생성
    • let, const 키워드로 선언된 변수는 모든 코드 블록을 지역 스코프로 인정함.

👉함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.


  • 함수 레벨 스코프
    • 함수가 지역 스코프 생성
    • var 키워드로 선언된 변수는 오직 '함수'만을 지역 스코프로 인정함.

👉모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.

4. 렉시컬 스코프(Lexical Scope)

함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정되는 것을 말한다.

즉, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다는 뜻이며, 가장 중요한 점은 함수의 호출이 아니라 함수의 선언에 따라 결정된다는 점이다.

다른 말로, 정적 스코프(Static scope)라 부르기도 하다.

var x = 1; // global

function first() {
  var x = 10;
  second();
}

function second() {
  console.log(x);
}

first(); // ?
second(); // ?

그 이유는?

자바스크립트에서는 위와 같은 코드를 작성할 때, 이미 실행 단계에서 코드들의 스코프를 결정한다.

  • global 범위에 있는 변수 x
  • first() 함수 안에 있는 변수 x
  • second() 함수 안에 있는 변수 x

위 예제의 실행 결과는 함수 second()의 상위 스코프가 무엇인지에 따라 결정된다.

자바스크립트는 렉시컬 스코프(Lexical Scope)를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다.

즉, 이 말은 함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다는 말이다.

그렇기 때문에, second() 함수가 first() 함수 안에서 호출된 것과 상관없이 second() 함수는 global 범위에 선언되어 있으므로, global 범위에 있는 변수 x의 값 1이 두 번 출력된 것이다.

어떻게 global 변수를 인식하는지를 알려면 스코프 체인(scope chain) 참고!

  • 만약 함수의 호출로상위 스코프가 결정된다면?

함수의 호출에 따라 상위 스코프가 정해지는 것을 Dynamic Scope라고도 합니다. Perl, Bash Shell 등에서 Dynamic Scope를 따른다.

함수 second()의 상위 스코프는 자신을 호출한 함수 first()와 전역(global)을 가리키게 되어 함수 first()를 호출하여 console.log()를 출력했을 때 x의 결과는 10이 출력될 것이다.

하지만 결과는 전역 변수 x의 값 1이 두 번 출력되었다.

이는 함수의 호출이 아니라 함수의 선언에 따라 상위 스코프가 결정된다는 것을 꼭 기억해야 한다.

호이스팅

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


  • 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징 (var 키워드 뿐만 아니라 let, const도 변수 호이스팅 현상은 존재한다. Error가 발생할 뿐)
  • 발생 이유 : 자바스크립트는 런타임 이전에 코드 평가 과정을 거친다. 이 과정에서 변수 선언이 먼저 평가되고 스코프에 변수 식별자를 등록함으로써 JavaScript 엔진에 변수의 존재를 알린다. 이 때문에 선언문 이전에 변수를 참조하더라도 에러가 발생하지 않는다.
  • 일시적 사각지대 (Temporal Dead Zone, TDZ)

하지만 var 키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다. 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문이다.

console.log(foo); // undefined
var foo;
console.log(bar); // Error: Uncaught ReferenceError: bar is not defined
let bar;
  • 호이스팅은 코드를 실행하기 전 변수선언/함수선언을 해당 스코프의 최상단으로 끌어올리는 것이 아니다.
  • 호이스팅은 코드가 실행하기 전 변수선언/함수선언이 해당 스코프의 최상단으로 끌어 올려진 것 같은 현상을 말한다.
  • 자바스크립트 엔진은 코드를 실행하기 전 실행 가능한 코드를 형상화하고 구분하는 과정( 실행 컨텍스트를 위한 과정 )을 거친다.
  • 자바스크립트 엔진은 코드를 실행하기 전 실행 컨텍스트를 위한과정에서 모든 선언(var, let, const, function, class)을 스코프에 등록한다.
  • 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경을 의미하고 실행되기전 이러한 실행 컨텍스트 과정(코드를 구분하는 과정)을 거친다.

변수 호이스팅 (3단계)

선언 단계(Declaration phase)
• 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다.
• 이 변수 객체는 스코프가 참조하는 대상이 된다.

초기화 단계(Initialization phase)
• 변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다.
• 이 단계에서 변 수는 undefined로 초기화된다.

할당 단계(Assignment phase)
• undefined로 초기화된 변수에 실제 값을 할당한다.

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

var 키워드로 선언된 변수의 생명 주기var 키워드로 선언된 변수의 생명 주기

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

let 키워드로 선언된 변수의 생명 주기let 키워드로 선언된 변수의 생명 주기

  • const의 호이스팅 단계: 1 && 2 && 3
const a = 1;
{
  console.log(a);
  const a = 2;		//TDZ에 갇힘
}
// Reference Error(참조 에러)
  • const는 호이스팅 시 선언 && 초기화 && 할당이 동시에 이루어져야 하는데 선언만 호이스팅되어 Reference Erorr가 출력된다.
  • const는 재할당이 되지 않는다

TDZ

일시적으로 죽은 공간이라는 뜻

  • 스코프의 시작 지점부터 초기화 시작 지점까지의 구간
let a = 1;
{
  console.log(foo);
  let a = 2;
}

간단히 여기서 중괄호 안의 구역을 TDZ라고 한다

  • var은 TDZ의 영향을 안받는다
  • let, const는 TDZ의 영향을 받고 이 떄문에 Reference Error(참조 에러)가 출력되는 것

참고
https://poiemaweb.com/js-scope
https://poiemaweb.com/es6-block-scope
https://velog.io/@zerovo_dka/Javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-TDZ#%EC%8A%A4%EC%BD%94%ED%94%84scope

profile
당신은사랑받기위해태어난사람

0개의 댓글