JavaScript/실행컨텍스트

[JavaScript] 스코프, 렉시컬환경

반응형

1. 스코프(Scope)란?


Scope를 직역하면 범위, 영역 이라는 뜻입니다. 즉 자바스크립트에서의 스코프는 식별자(변수, 매개변수, 함수)가 유효한 범위 를 말합니다.
스코프(유효범위)는 함수, 변수 등이 선언되는 환경을 기반으로 합니다.

var x = 'global';
​
function foo() {
  var x = 'local';
  console.log(x)
}
​
foo();
console.log(x);
  • x라는 변수를 전역환경과 함수내부에서 각각 선언을 해주고 다른 값을 할당해 주었다.
  • 자바스크립트는 어떤 변수를 참조할까?
  • 각각 전역 스코프foo 함수 스코프 를 가짐으로 아래의 결과가 출력
// local
// global

그러므로 스코프식별자를 검색할 때 검색 가능한 범위 라고도 이해할 수 있으며 해당 변수가 어디에서 실행이 되며 주변에 어떤 코드들이 존재하는지에 따라 다른 결과를 출력합니다.

1-1. 스코프(Scope)는 왜 존재하는가?

  • 스코프가 존재하지 않는다면 변수명이 같은 변수가 2개 이상 존재하는 경우 충돌을 일으키므로 동일한 변수명을 프로그램 전체에서 하나밖에 사용할 수 없다.
  • 즉 식별자는 고유한 이름으로써 어떤 값을 구별할 수 있어야 하지만 식별자의 기능을 상실하게 되는 것이다.

2. 스코프의 종류

코드는 전역(global)지역(local)으로 나눌 수 있다.

전역스코프, 전역변수, 지역스코프, 지역변수가 존재한다.

 

​2-1. 전역(global)의 특징

  • 가장 최상위 스코프를 이야기 한다.
  • 전역변수는 어디서든지 참조가 가능하다.(로컬 스코프에서도 가능)

2-2. 지역(local)의 특징

  • 지역이란 함수 몸체 내부 를 말합니다.
  • 지역변수는 자신이 선언된 스코프와 하위 스코프(중첩, 내부 함수)에서만 참조가 가능하다.
  • 일반적으로 글로벌 스코프 에서는 로컬 스코프 의 로컬변수를 참조할 수 없습니다.

 

3. 스코프 체인

함수는 전역환경에서 정의가 될 수도 있지만 함수 내부에서 정의가 될 수도 있다. 이를 함수의 중첩 이라고 한다.

이렇게 중첩된 함수를 중첩함수라고 하고 이를 포함하는 함수를 외부함수라고 한다.

함수를 중첩할 수 있다는 것은 스코프도 중첩이 가능하다는 것다. 즉 스코프는 함수의 중첩에 의해 계층적인 구조 를 갖는다.

스코프체인의 구조

(하위) 내부함수 스코프 -> 외부함수 스코프 -> 전역스코프 (상위)`

이처럼 모든 스코프들은 하나의 계층적 구조로 연결되어있다.
이렇게 모든 스코프들이 연결된 것을 스코프 체인 이라고 명칭한다.

 

변수를 참조할 때 자바스크립트 엔진은 변수를 참조하는 스코프에서 시작하여 상위 스코프로 선언된 변수를 검색한다.
즉 상위스코프에서 생성된 변수는 하위스코프에서 참조가 가능하다.

스코프체인은 물리적으로 존재하며, 자바스크립트 엔진은 코드를 실행함에 앞서 자료구조인 렉시컬 환경(Lexical Environment)을 실제로 생성한다.

 

렉시컬환경

- 렉시컬 환경은 명세서에만 존재한다.
- 자바스크립트가 어떻게 동작하는지 설명하는데 쓰이는 이론상의 객체
- 코드를 사용해 직접 렉시컬 환경을 얻거나 조작하는 것은 불가능

함수 선언문의 렉시컬 환경

// 렉시컬 환경
실행컨텍스트 : {
  렉시컬 환경 컴포넌트: {
    렉시컬 환경 {
      환경 레코드: {
        // 모든 지역변수를 프로퍼티 {key, value}로 저장한 객체이다.
      }
    },
    외부 렉시컬 환경 참조 : 상위 스코프 참조
    // 글로벌 스코프에서는 상위 스코프가 없으므로 null을 참조
  },
  변수 환경 컴포넌트 : {
    // 렉시컬 환경을 복사한 것으로 초기값이 동일하다 , 실행단계에서 참조하는 것은 환경레코드이다.
    // 전역 렉시컬환경의 경우 null을 참조한다.
  }
}

// outerFunc() {
    innerFunc() {
      var x = 10;
    }
}
  • 변수선언이 실행되면 변수 식별자가 렉시컬 환경레코드에 키(key)로 등록되고
  • 변수할당이 일어나면 렉시컬 환경레코드의 변수 식별자에 해당하는 값을 반환한다.

 

즉 스코프체인은 렉시컬환경을 단반향으로 연결되어 있는 것이다. 만일 양방향으로 참조가 가능하다면 무한 탐색이 이어질 것이다..

 

3-1. 스코프체인에 의한 함수 검색

test라는 함수선언문이 전역과 지역의 중첩함수로 동시에 존재하는 경우 런타임 이전에 함수객체가 먼저 생성된다.
즉 동일한 식별자를 가진 함수가 선언된다면 스코프 환경에 따라 식별자결정을 통해 적합한 함수를 호출한다.

// 전역함수
function test() {
  console.log('global')
}
​
function test2() {
  // 중첩 함수
  function test() {
    console.log('local')
  }
  test()
}
// 함수 호출
test2() // -> local

04. 함수 레벨 스코프

지역은 함수 내부를 말하고 지역은 지역스코프를 생성한다. 즉 지역스코프는 함수에 의해서만 생성 이 된다.
var 키워드로 선언된 변수는 let, const 와 달리 함수 내부몸체(코드블록)만을 지역 스코프로 인정 한다.
이를 함수 레벨 스코프 라고 한다.

 

var 키워드로 생성된 변수가 if문 안에 동시에 존재할 경우 if문은 함수가 아님으로 함수레벨스코프를 생성하지 않습니다. 그럼으로 모두 동일한 전역변수가 되어버리고 변수쉐도잉(재할당)이 일어납니다.

// 전역변수 x
var x = 3;
​
if (true) {
  // 전역변수 x 중첩 변수값이 재할당(변수쉐도잉)된다.
  var x = 10;
}
​
console.log(x) // 10

예제 2) var 키워드를 사용하여 함수 레벨 스코프 만을 지원하는 경우

var i = 10;
​
for (var i = 0; i < 5; i++) {
    console.log(i) // 0 1 2 3 4
}
​
console.log(i) // 5

for문은 함수가 아님으로 함수 레벨 스코프 를 생성하지 않기 때문에 i는 전역변수로 선언이 된다.
그럼으로 모두 같은 전역변수i를 출력하고 있다.


예제 3) var가 아닌 let 키워드를 사용하여 블록 레벨 스코프 를 지원하는 경우

let i = 10;
​
for (let i = 0; i < 5; i++) {
  console.log(i); // 0 1 2 3 4
}
​
console.log(i) // 10

var 키워드를 사용하여 전역변수로 선언되는 경우와 다르게 let의 경우 i 변수를 블록레벨스코프를 통해 지역변수로 선언해주기 때문에 i 값이 다르게 출력이 되는 것을 볼 수 있다.


05. 렉시컬 스코프(정적스코프)

var x = 1;

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

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

foo()
bar()

이 경우 bar의 상위 스코프는 두 가지의 경우에 따라 바뀔 수 있다고 예측할 수 있다.

  1. 동적스코프(dynamic scope) : 함수의 호출환경에 따라서 상위 스코프를 결정
  2. 정적, 렉시컬스코프(laxical scope) : 함수의 선언환경에 따라서 상위 스코프를 결정

1번의 경우에는 bar의 상위 스코프는 foo가 될것이고 2번의 경우에는 bar의 상위스코프는 전역스코프가 될 것이다.
실제로 코드를 작성하고 실행을 하게 되면 모두 1을 출력하는 것을 알 수 있다.

  • 자바스크립트를 포함한 대부분의 프로그래밍 언어에서는 렉시컬스코프 를 따른다.
  • 즉 함수의 상위스코프는 언제나 자신이 선언된 환경의 스코프 이다.
  • 함수가 정의,선언이 되면 상위 스코프의 참조를 기억하고 있다.
  • 렉시컬스코프클로저 와 깊은 관계가 있다.

클로저와 실행컨텍스트에 관해서는 다른 포스트에서 작성하겠습니다.

 

<Resource>

모던 자바스크립트 DeepDive : 스코프

반응형