티스토리 뷰

내용 순서

  1. 스코프 (Scope)
  2. 글로벌 스코프 (Global Scope) - Script, Global
  3. 함수 스코프 (Function Scope)
  4. 블록 스코프 (Block Scope)
  5. 스코프 체인 (Scope Chain)
  6. 요약 정리
  7. 마치며

 

참고자료


1. 스코프 (Scope)

범위라는 영단어인 스코프는 자바스크립트에서도 코드가 접근할 수 있는 변수들의 범위를 의미한다. 넓은 의미론 이전에 포스팅했던 실행 컨텍스트의 메모리 영역에 해당한다고 보면 된다.

 

여러 종류의 스코프가 존재하고, 각 스코프를 잘 이해하는 것이 중요하다. 또 다른 핵심 개념인 호이스팅과 클로저에 밀접하게 연관돼 있기 때문이다. 그럼 가보자고~

 

 

 

2. 글로벌 스코프 (Global Scope) - Script, Global

a1 = 1;
var a2 = 2;
let a3 = 3;
const a4 = 4;

const printNumbers = () => {
    aHidden = 99;  // to be continue
    console.log(`a1 = ${a1}`);
    console.log(`a2 = ${a2}`);
    console.log(`a3 = ${a3}`);
    console.log(`a4 = ${a4}`);
};

printNumbers();

 

글로벌 스코프를 알아보기 위해 위의 간단한 자바스크립트 코드를 작성했다. 변수를 선언하는 var, let 키워드와 상수를 선언하는 const 키워드를 사용했고, 아무런 키워드 없이 변수를 선언하기도 했다.

 

지금 글로벌 스코프에서는 var는 함수 스코프이고, let과 const는 블록 스코프라는 게 핵심이 아니다. 얘네들이 모두 최상단의 위치에서 선언되어 글로벌 스코프에 해당한다는 게 중요하다. 글로벌 스코프에 있는 변수는 어느 영역에 있든, 모든 코드가 접근할 수 있다.

 

그럼 이제 이 자바스크립트 코드를 html 파일에 넣어 브라우저에 띄우고, 크롬 개발자 도구에서 자세히 뜯어보자.

 

7번에 breakpoint 걸기

 

15번 라인에 breakpoint를 걸었다. 그럼 2번 라인부터 13번라인까지는 모두 실행된 상태이므로, 선언한 변수/상수는 모두 메모리에 올라간 상태다. 가장 바깥에서 작성된 코드이므로 글로벌 스코프에 해당할 텐데, 뭔가 이상하다.

 

오른쪽의 Scope를 보면 Script와 Global 2개가 존재하는데, a1과 a2만 Global에 존재된다. let과 const로 선언된 a3, a4 (그리고 printNumbers 함수)는 Script에 있다. 오잉?

 

실행 컨텍스트로 표현

 

근데 사실... 스크립트 스코프도 결국엔 글로벌 스코프이다. 전역 실행 컨텍스트 입장에서 그림을 그리면, 위 그림처럼 전역 메모리에 a1, a2, a3, a4가 모두 등록되어 있는 상태다. 그래서 printNumbers 함수 실행 시 printNumbers 실행 컨텍스트의 로컬 메모리에 a1, a2, a3, a4가 없더라도, 전역 메모리에서 데이터를 찾아내 콘솔문에 잘 출력해낸다.

 

원래는 모든 전역 변수나 상수, 함수는 Global에 들어갔다. 엄밀히 말하면 window 객체의 속성으로 들어갔다. 지금도 예전 방식으로 키워드 없이 선언한 a1과 var 키워드로 선언한 a2는 Global에 있는데, 그 밑에 따로 추가한 적 없는 alert, atob 같은 window 객체의 속성들이 있지 않은가? 이미 window 객체의 변수/상수/함수로 인해 굉장히 붐비고 있는 Global에 사용자가 정의한 전역 변수/함수까지 추가되어 다소 복잡시럽다. 의도치 않게 window 객체의 속성을 덮어 써버릴 위험도 있다.

 

반면 2015년에 새로 도입된 let과 const 키워드로 선언한 변수는 Script에 들어간다. 똑같이 글로벌 스코프이긴 하지만, window 객체로 인해 복잡복잡한 Global이 아니라 Script에 들어갔다는 점에 주목하자. window 객체의 속성과 사용자 정의 글로벌 데이터의 영역을 구분하고 있다. 기존처럼 사용자가 정의한 글로벌 데이터를 window 객체의 속성과 똑같은 영역에 넣으니 불편했나 보다.

 

Global에 등록된 aHidden

 

참고로 어느 위치에서든 키워드 없이 변수를 선언하면 무조건 글로벌 스코프에 해당한다. 실제로 printNumbers 함수 내부에서 선언한 aHidden가 Global 영역에 추가된 걸 확인할 수 있다. 원래 printNumbers 함수 내부에서 선언된 변수를 외부에서 접근하면 ReferenceError가 발생하지만, 키워드 없이 선언한 aHidden은 Global에 등록되므로 printNumbers 함수 바깥에서 aHidden을 출력하는 콘솔문도 에러 없이 잘 동작한다.

 

정리하자면,

 

  • 글로벌 스코프는 엄밀히 따지면 Global과 Script로 나뉜다.
  • 키워드 없이 선언한 변수와 var로 선언한 전역 변수는 Global
  • let과 const로 선언한 전역 데이터는 Script
  • Script에서 먼저 데이터를 찾고, 없으면 Global에서 데이터를 찾는다.

 

 

 

3. 함수 스코프 (Function Scope)

함수에 선언된 변수에는 해당 함수의 코드만 접근할 수 있는 것이 함수 스코프이다. var로 선언한 변수가 바로 함수 스코프에 해당한다.

 

function aFunc(inputName) {
    var catName = inputName;

    if (catName == 'pong') {
        var age = 3;
    }

    console.log(catName);
    console.log(age);
}

aFunc('pong');

console.log(age);

 

위 코드로 함수 스코프에 대해 알아보자.

 

1번째 라인

 

자바스크립트가 로드되고, 엔진이 이를 처리하면서 전역 실행 컨텍스트가 생성된다. 1번 라인에 의해 aFunc 함수가 전역 메모리에 등록되었다.

 

12번째 라인

 

aFunc 함수가 호출되자마자 aFunc 실행 컨텍스트가 생성되고 콜 스택에도 추가된다. 매개변수 inputName과 그 값이 aFunc 실행 컨텍스트의 로컬 메모리에 등록된다. 그리고 aFunc 함수 내에서 var 키워드로 catName과 age도 로컬 메모리에 등록된다. var 키워드는 함수 스코프이기 때문이다!

 

9번째 라인

 

2번과 5번 라인을 통해 catName과 age 라벨에 값이 할당된 상태고, 현재 9번째 라인을 실행하고 있다. 8번과 9번 콘솔문이 출력하려는 catName과 age는 로컬 메모리에 있으므로 해당 명령어는 잘 동작한다.

 

14번째 라인

 

그러나 aFunc 함수 외부인 14번째 콘솔문에선 age를 출력할 수 없다. aFunc 함수가 종료되자마자 aFunc 실행 콘텍스트와 그 로컬 메모리가 삭제되어 더 이상 aFunc 함수 내에서 선언한 age라는 변수에 접근할 수 없기 때문이다. aFunc 실행 컨텍스트가 종료되어 이제 다시 전역 실행 컨텍스트로 돌아왔기 때문에 전역 메모리에서 데이터를 찾아야 하는데, age는 전역 메모리에 없으니 Reference Error가 발생한다.

 

 

 

4. 블록 스코프 (Block Scope)

블록 스코프는 블록에서 선언한 변수는 해당 블록 내에서만 접근 가능할 수 있다. C언어나 Java 같은 일반적인 프로그래밍 언어는 대부분 블록 스코프를 가진다. 자바스크립트에선 let과  const 키워드로 선언한 데이터가 블록 스코프에 해당한다.

 

function aFunc(inputName) {
    const catName = inputName;

    if (catName == 'pong') {
        let age = 3;
    }

    console.log(catName);
    console.log(age);
}

aFunc('pong');

 

이번에 사용할 코드는 기본적으로 함수 스코프 예제와 동일하지만, var 대신 const와 let을 사용해서 변수를 선언했다.

 

3번째 라인

 

aFunc 함수가 호출된 상태다. aFunc 실행 컨텍스트와 로컬 메모리가 생성됐고, 매개변수와 catName이 로컬 메모리에 등록됐다. catName은 aFunc 함수 블록 안에서 선언되었기 때문에 Local에 존재하지만, if문 블록 안에 있는 age는 아직 메모리에 존재하지 않는다.

 

6번째 라인

 

catName에는 매개변수 값이 할당됐으며, 그 값이 'pong'이기 때문에 6번째 라인에 진입했다. 이제야 age가 메모리에 등장하는데, if문 블록 안에서 선언된 변수이기 때문에 Local이 아니라, Block에 존재한다.

 

10번째 라인 - if문 바깥

 

if문 블록 바깥으로 빠져 나오면 블록 스코프는 사라진다. 따라서 10번 라인에서 age라는 변수를 출력하려고 하면 Reference Error가 발생한다.

 

 

 

5. 스코프 체인 (Scope Chain)

스코프 체인은 현 실행 컨텍스트의 로컬 메모리에서 변수나 함수를 찾지 못하면 이전 실행 컨텍스트로 탐색 범위를 옮기는 것이다. 이전 실행 컨텍스트에서도 원하는 데이터를 찾지 못하면 그 이전 실행 컨텍스트로 이동하고... 데이터를 찾을 때까지 이런 과정을 반복한다. 콜 스택에 쌓인 모든 실행 컨텍스트를 체인처럼 타고타고 이동하기 때문에 스코프 체인이라고 한다.

 

var N = 2;
step1();

function step1() {
    var square = N * N;
    step2();

    function step2() {
        var cube = square * N;
        console.log(cube);
    }
}

 

위 코드를 예시로 들어보자. step1 함수 안에 step2 함수가 존재한다.

 

9번째 라인

 

현재 step2 함수가 실행되고 있다. step2 함수의 지역 변수 cube는 square와 N이라는 변수의 곱이다. 그러나 square도, N도 step2의 로컬 메모리에 존재하지 않는다. 그럼 이때 곧바로 Reference Error를 발생시킬까? 아니다. 현재 실행 중인 step2의 실행 컨텍스트에 square와 N이 없으면 이전 실행 컨텍스트로 이동해서 해당 변수들을 찾는다.

 

이전 실행 컨텍스트인 step1의 실행 컨텍스트에서 square를 발견했다. 그러나 아직 N은 step1 실행 컨텍스트에 없으므로, step1의 이전 실행 컨텍스트인 전역 실행 컨텍스트로 이동한다. 그럼 전역 메모리 상에 있는 N을 찾을 수 있다. 결과적으로 cube는 step1 실행 컨텍스트에서 square를, 전역 실행 컨텍스트에서 N을 찾아 8이라는 값을 가지게 된다.

 

 

 

6. 요약 정리

스코프

  • 코드가 접근할 수 있는 변수의 범위

 

글로벌 스코프

  • 코드 최상단에 선언된 변수와 함수들이 갖는 스코프
  • 어느 위치의 코드든 글로벌 스코프에 해당하는 데이터에 접근 가능
  • 엄밀히 말하면 글로벌 스코프는 Global과 Script로 나뉨
  • Global: 키워드 없이 선언되거나 var로 선언된 데이터
  • Script: let이나 const로 선언된 데이터

 

함수 스코프

  • 함수 단위로 접근 가능한 스코프
  • 함수 안에서 var 키워드로 선언된 변수가 함수 스코프에 해당
  • 함수 내 if문에서 선언한 var 변수를, if문 바깥에서 접근 가능

 

블록 스코프

  • 블록 단위로 접근 가능한 스코프
  • 함수 안에서 let이나 const 키워드로 선언된 변수가 블록 스코프에 해당
  • 함수 내 if문에서 선언한 let / const 변수를, if문 바깥에서 접근 불가

 

스코프 체인

  • 변수 및 함수의 유효 범위를 결정하는 매커니즘
  • 현재 실행 컨텍스트에서 변수나 함수를 찾지 못하면, 데이터를 찾을 때까지 이전 실행 컨텍스트로 이동하여 검색함

 

 

 

7. 마치며

스코프 종류에는 렉시컬 스코프라는 것도 존재한다. 현재 내용이 길기도 하고, 렉시컬 스코프는 클로저 개념과 매우 밀접한 내용이기 때문에 추후 클로저에 대한 글을 작성할 때 함께 이야기해 보겠다!

728x90