Scope

sangminnn·2020년 4월 7일
0

글로 정리하는 JS

목록 보기
4/5

Scope

scope란 기본적인 사전적 의미로는 ' 범위 ' 라는 의미를 가지고 있고

프로그래밍적인 정의를 찾아보기 위해 항상 많이 배우는 poiemaWeb의 한 구절을 가지고 와보자면

스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 

식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙이다. 자바스크립트는 이 규칙대로 식별자를 찾는다.

라고 한다.

이는 식별자가 자신이 어디에서 선언됐는지에 의해 자신이 유효한(다른 코드가 자신을 참조할 수 있는) 범위를 가지게 되는데,

이를 스코프(Scope) 라고 부른다는 것이다.

그렇다면 왜 스코프라는 개념이 있어야 했던 것일까 ?

이에 대해서도 만약 스코프가 없게 된다면 같은 식별자 이름은 하나의 프로그램 내에서 한번밖에 사용하지 못하게 되고, 프로그램 내에서 동일한 식별자의 이름이 있다면 이는 충돌하게 될 것이다.

이러한 현상을 방지하기 위해 만들어진 개념이 해당 식별자의 유효한 범위를 의미하는 ' 스코프(scope) ' 이다.

Scope의 개념

그렇다면 이 Scope는 어떻게 나타낼 수 있을까 ?

이는 흔히 볼 수 있는 형태인 { ... } 형태를 띄고있는 ' { } ' 이 괄호를 scope라고 부른다.

여기서 한가지 더 깊이 생각해볼 수 있다.

그러면 위의 scope 괄호만 존재한다면 그 안에서 식별자들의 범위가 생기는 것이겠구나 ! 

이러한 생각을 필자도 했었지만, 이는 Javascript에서는 조금 다르게 동작한다는 것을 알았다.

이러한 경우에 대해 각각 알아보자면 각자의 유효한 범위를 정하는 방식에 따라 그 명칭이 달라지는데

' 함수를 선언하고 바로 나타나는 scope를 의미있는 scope 단위로 정해둔 것' 인지 와

' 함수를 선언하고 바로 나타나는 scope가 아니라 본인과 가장 가까운 scope( { } ) 를 의미있는 scope 단위로 정해둔 것 '

인지에 따라 ' 함수 레벨 스코프 ' 와 ' 블록 레벨 스코프 ' 라는 용어로 나뉜다.

여기서의 블록은 정확히 말하자면 if, else, for, while 같은 문법으로 지정된 { } 블록을 의미한다고 한다.

( 블록의 개념은 _Jbee님의 글을 참고했습니다. _Jbee 님 감사합니다. )

Javascript에서의 Scope

그렇다면 우리가 사용하는 JavaScript 는 어떤 레벨의 스코프를 의미있는 단위로 생각하고 있을까 ?

다른 언어 중 흔히들 아는 C언어의 경우는 scope( { } ) 마다 의미있는 범위로 생각하는 ' 블록 레벨 스코프 '를 채택하고 있다고 한다.

하지만 우리가 사용하는 Javascript는 기본적으로는 ' 함수 레벨 스코프 ' 를 채택하고 있다.

또한 모든 변수 역시도 해당 변수가 유효하게 동작하는 스코프를 가지고 있는데,

ES5에서 사용하는 var 를 사용하여 변수를 선언할 경우에는 ' 함수 레벨 스코프 ' 로 동작하게 된다.

하지만 ES6에서 나온 let, const 를 사용하여 선언할 경우에는 ' 블록 레벨 스코프 ' 로 선언된다.

// ES5

function hello() {
    // 함수 스코프 범위의 시작 
    var x = 1; 
    if(something) {
        // 블록 스코프 범위의 시작

        // let, const 가 유효한 범위
        let x = 3;
        // Something .....

        // 블록 스코프 범위의 끝	
    }

    // 함수 스코프 범위의 끝 ( x 값이 유효한 범위 )
}

스코프 체인 (Scope Chain)

여기서 한가지 더 확실하게 알고 넘어가야 할 개념으로는 스코프 체인(Scope Chain) 이 있다.

이 Scope Chain 이라는 개념은 이전 글들 중 Execution Context 에서 한번 소개했는데,

그 개념과 동일하다.

Scope Chain 이란. 간단하게 생각하자면 Scope들의 상하관계라고 볼 수 있는데,

해당 스코프가 가장 상위 스코프인 전역 스코프까지 도달하는 과정에서 속해있는 스코프들의 배열 이라고 설명할 수 있겠다.

간단하게 아래의 예시를 보자.

var x = 1;

function call() {
    console.log(x); // 1
};

위의 코드에서 보면 전역 객체에 var로 인해 x값이 선언 및 초기화 되어있다.

이후 call 함수가 선언되어있는데, 이 call 함수의 스코프는 Scope Chain(배열)로 보았을 때의 상하 관계는

[ call함수 스코프, 전역 스코프 ] 와 같이 배열의 index 0 값에 본인의 스코프를 넣는 것을 시작으로

전역 스코프에 도달하는 동안 속해있는 스코프들을 모두 배열에 넣어주어 만들어진 배열을 스코프 체인 (Scope Chain) 이라고 표현할 수 있겠다.

위의 코드에서 보다시피 이 Scope Chain의 특징으로는 해당 스코프 내부에 호출하고자 하는 값이 없고,

Scope Chain 내에 상위 스코프가 존재할 경우, 해당 값을 상위 스코프에서 찾고 그 값이 존재할 경우 그 값을 사용할 수 있다는 점이다.

따라서 위의 코드에 call 함수 내부에는 x 값이 존재하지 않음에도 불구하고, 상위 스코프인 전역 스코프에서

x값이 선언되어 있기 때문에, 그 값을 가지고 와서 출력할 수 있게 되는 것이다.

렉시컬 스코프 ( Lexical Scope )

먼저 아래의 코드를 보고 출력 값에 대해 생각해보자.

var x = 5;

function one() {
  var x = 10;
  two();
}

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

one(); // ?
two(); // ?

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

여기서는 두가지 유효값을 예측해볼 수 있다.

  1. 함수를 어디서 호출 하였는지에 따라 상위 스코프를 결정하는 것
  2. 함수를 어디서 선언 하였는지에 따라 상위 스코프를 결정하는 것

자 위의 2가지 경우를 하나씩 생각해보자.

만약, 첫번째 방식으로 함수의 상위 스코프를 결정한다면 함수 two의 상위 스코프는 함수 one와 전역일 것이고

( [ two함수 스코프, one함수 스코프, 전역 스코프 ] )

두번째 방식으로 함수의 스코프를 결정한다면 함수 two의 스코프는 전역일 것이다.

( [ two함수 스코프, 전역 스코프 ] )

프로그래밍 언어는 이 두가지 방식 중 하나의 방식으로 함수의 상위 스코프를 결정한다.

첫번째 방식을 동적 스코프(Dynamic scope)라 하고,

두번째 방식을 렉시컬 스코프(Lexical scope) 또는 정적 스코프(Static scope)라 한다.

자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.

렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.

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

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

이제 위의 예제를 보고 다시한번 생각해보자.

위의 예제 함수 two는 전역에 선언되었다. 따라서 위에서 언급한 두번째 접근 방식에 따라

함수 two의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x의 값 5을 두번 출력하게 된다.

let x = 3;

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

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

foo(); // 3
bar(); // 3

이건 내가 궁금해서 해본거지만 let이여도 여전히 Javascript 자체가 함수의 할당 순간의 스코프를 찾아가는 것이 아니라(동적 스코프), 함수의 선언 순간의 스코프 내에서 변수를 찾아가는 것이기 때문에 (정적 스코프)

두 값은 모두 3이 나오게 된다.


이번 글도 PoiemaWeb을 통해 공부한 내용을 정리한 것입니다.

좋은 글 써주신 _Jbee 님에게도 감사의 말씀드립니다.

profile
생각하며 코딩하려고 노력하는 개발자가 되겠습니다.

0개의 댓글