상세 컨텐츠

본문 제목

스코프(Scope, 유효범위)

JavaScript & TypeScript

by Yoonsang's Log 2022. 1. 15. 14:23

본문

코드블록? 스코프? 블록 레벨 스코프? 함수 레벨 스코프?

강의를 듣거나 공식문서를 읽을때 흔히 보이는 키워드들이다.

코드블록과 스코프는 어떤점에서 차이가 있는건지, 블록 레벨과 함수 레벨 스코프는 어떤 차이가 있는지

확실한 이해가 없이 개발을 하고 있었고 "아 왜 에러가 뜨지" 하며 감으로 그리고 경험으로 고쳐가며 근본적인 문제를 놓쳐 왔던 것 같다.

앞서 작성했던 글들도 스코프에 대한 이해가 생기니 원리가 이해가 된다.

그리고 자바스크립트 엔진의 관점에서 값들이 어떻게 연결이 되어 있는지 스코프 체인을 통해 이해했다.

스코프를 통해 생각보다 많은 부분이 결정되고 있다.

그렇기 때문에 이해가 부족하면 자바스크립트의 다른 성격을 이해하는것이 힘들 수 있다.

추후 포스팅할 자바스크립트의 모든 코드의 평가와 실행을 담당하는 실행 컨텍스트에도 깊은 관련이 있다.

오늘은 자바스크립트를 포함한 모든 언어에서의 중요한 개념인 스코프(Scope, 유효범위)에 대해 정리한다.

 

자바스크립트에서의 스코프

스코프라고 딱 명료하게 생각해본적은 없지만 사실 스코프에 대해 경험을 많이 했을 것이다.

변수 선언, 함수의 매개변수, 지역 변수와 전역 변수 등 모두 스코프와 관련이 있다.

스코프는 식별자가 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위를 의미한다.

식별자가 같은 두 개의 변수가 있다고 한다면, 자바스크립트 엔진은 어떤 변수를 참조해야 할지 결정해야 한다.

자바스크립트 엔진은 스코프라는 규칙을 통해 식별자를 결정한다. 스코프는 네임스페이스라고 할 수 있다.

 

스코프의 종류

구분 설명 스코프 변수
전역 코드의 가장 바깥 영역 전역 스코프 전역 변수
지역 함수 몸체 내부 지역 스코프 지역 변수

전역 스코프를 갖는 전역 변수는 어디서든지 참조할 수 있다.

지역 변수는 자신의 지역 스코프와 그 하위 스코프에서 참조할 수 있다.

 

스코프 체인

스코프가 함수의 중첩에 의해 계층적인 구조를 가지게 되면 외부 함수를 그 중첩 함수의 상위 스코프라 한다.

스코프 체인은 이러한 스코프가 계층적으로 연결되어 있는 것을 의미한다. 스코프 체인은 물리적인 실체로 존재한다.

변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 찾는다. 하지만 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다. 즉, 스코프 체인은 실행 컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다. 

 

함수 레벨 스코프

var num = 1;
if(true){ 
  var num = 2;
}
function num3() {
  var num = 3;
}
console.log(num); // 2

위 코드를 보고 결과를 보기 전까지 코드 블록이 지역 스코프를 생성하여 지역 변수를 사용하는 것처럼 오해할 수 있다.

하지만 위 결과는 2를 출력한다. 즉, 지역 스코프는 코드블록이 아닌 함수에 의해서 생성된다.

var 키워드는 위처럼 오로지 함수의 코드블록만을 지역 스코프로 인정하지만, let과 const 키워드는 블록 레벨 스코프를 지원한다.

 

블록 레벨 스코프

let num = 1;

{
  let num = 2;
  const foo = 3;
}

console.log(num); // 1
console.log(foo); // ReferenceError

let과 const 키워드로 선언된 변수는 블록 레벨 스코프를 지원한다고 했다.

블록 레벨 스코프는 모든 코드 블록을 지역 스코프로 인정한다.

 

 

렉시컬 스코프

자바스크립트는 함수를 어디에서 정의했는지에 따라 상위 스코프를 정적으로 결정하는 렉시컬 스코프를 따른다.

함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다.

즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.

// 모던자바스크립트 딥다이브 예제 13-09
var x = 1;

function foo(){
  var x = 10;
  bar();
}

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

foo(); // 1
bar(); // 1

위 코드를 살펴보면,

호이스팅이 일어난 뒤, 런타임에 x에 1이 할당되고, foo함수가 호출된다. foo 함수는 호이스팅 과정에서 foo라는 식별자를 암묵적으로 생성해 foo 함수 객체를 할당한다. foo 함수 내부에는 지역변수 x에 10을 할당하고, bar함수를 호출한다. *foo 함수가 호이스팅 되어 함수 객체가 먼저 실행될 것이라 오해할 수 있으나, 함수 객체 내부를 실행하는 것은 함수가 호출된 시점이다. 즉, foo 함수가 호출되면서 var x가 호이스팅되고 그 이후 한줄씩 실행시킨다. 그 이후 bar함수가 호출되면서 x를 출력하는데, bar함수에 출력되는 x가 10일 것이라 오해하기 쉽다. 하지만, 앞서 말한대로 렉시컬 스코프를 따르기 때문에 함수를 어디에 정의 했는지가 상위 스코프를 결정한다. bar함수는 전역에 선언하였으므로 출력되는 x는 1이다. 마찬가지로 전역에서 호출한 bar함수도 1을 출력한다.

호이스팅은 스코프를 단위로 동작한다. 

 

스코프에 의해 결정되는 것들

 

변수의 생명 주기

변수는 생성되고 소멸되는 생명주기를 가진다.

전역 변수의 생명주기는 애플리케이션의 생명주기와 같다.

지역 변수의 생명주기는 자신이 등록된 스코프가 소멸(메모리 해제)될 때까지 유요하다.

 

생명주기가 길다는 것은 메모리 리소스도 오랜 기간 소비한다는 의미이고 변수의 상태를 변경할 소지가 다분하다는 뜻이다.

또한, 스코프 체인 상에서도 가장 상위인 종점에 존재하기 때문에 변수 검색 속도도 가장 느리다.

파일을 분리하더라도 전역 스코프를 공유하기 때문에 다른 파일에서 작업했다하더라도 동일한 식별자로 명명했다면 예상치 못한 오류를 가져올 수 있다.

스코프는 좁을수록 좋다. 즉, 변수의 생명주기는 짧을수록 좋다.

 

모듈 패턴

모듈 패턴은 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행함수로 감싸 하나의 모듈을 만든다.

모듈 패턴을 통해 전역 변수를 억제하고, public, private, protected 등과 같은 접근 제한자를 적용한 것처럼 캡슐화 및 정보 은닉의 효과를 낼 수 있다.

// 모던자바스크립트 딥다이브 예제 14-07
var Counter = (function() {
    // private 변수
    var num = 0;

    return {
        increase(){
            return ++num;
        },
        decrease(){
            return --num;
        }
    }
}());

console.log(Counter.num) // undefined

console.log(Counter.increase()); // 1
console.log(Counter.increase()); // 2
console.log(Counter.decrease()); // 1
console.log(Counter.decrease()); // 0

위 코드를 보면 지역스코프에 선언된 지역 변수 num을 외부에서 접근할 수 없는 특성을 활용해 private 접근 제한자를 사용한 것과 같이 사용했고, return 객체를 통해 public멤버에 접근하는 것처럼 활용했다.

 

 

[참고자료]

- 모던자바스크립트 딥다이브 책

'JavaScript & TypeScript' 카테고리의 다른 글

프로토타입 (Prototype)  (0) 2022.03.01
프로퍼티(Property)  (0) 2022.01.30
이펙티브 타입스크립트  (2) 2022.01.09
호이스팅 (Hoisting)  (8) 2022.01.02
모던자바스크립트 딥다이브  (0) 2021.12.30

관련글 더보기

댓글 영역