Study/Javascript

스코프(Scope)

kdhoooon 2022. 4. 29. 15:30

정의

function add(x, y){
	console.log(x,y);
   	return x + y;
}

add(2, 5);
console.log(x, y); // ReferenceError: x is no defined

위 코드에서 x, y 는 add 함수 안에서만 유효한 식별자이므로, add 함수 외부에서 참조할 경우 ReferenceError가 발생한다. 이런 식별자의 유효한 범위스코프(Scope)라고 한다.

  • 식별자가 유효한 범위
  • 모든 실별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정됨
  • 식별자를 검색할 때 사용하는 규칙

[예제]

var x = 'global';

function foo(){
	var x = 'local';
   	console.log(x);
}
foo();
console.log(x);


/*

정답:
local
global

*/

위 예제를 보면서 출력이 어떻게 될지 고민을 해보면, foo 함수 내부의 x 는 'local' 이 되고 함수 밖의 x 는 'global'이다.

 

스코프 종류

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

전역 스코프

  • 전역 변수는 어디서든지 참조할 수 있음
  • 어디서든 참조가 가능하기 때문에 함수 내부에서도 참조 할 수 있음

지역 스코프

  • 지역 - 함수 몸체 내부
  • 자신의 지역 스코프와 하위 지역 스코프에서 유효

코드를 보면서 한 스코프 개념을 잡고 가면 좋을 것 같다.

var x = "global x";
var y = "global y";

function outer(){

  var z = "outer's local z";

  console.log(x); //global x
  console.log(y); //global y
  console.log(z); //outer's local z

  function inner(){
    var x = "inner's local x";

    console.log(x); // inner's local x
    console.log(y); // global y
    console.log(z); // outer's local z
  }

  inner();
}

outer();
console.log(x); //global x
console.log(z); //ReferenceError

해당 `console.log` 값이 어떻게 출력이 되는지 생각해보면서 스코프의 범위를 정리해보면 좋을 것 같다.

 

스코프 체인

  • 스코프가 함수의 중첩에 의해 계층적 구조를 갖는 것
  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색함
  • 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수  있지만 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없음
함수의 중첩

- 함수 몸체 내부에서 함수가 정의된 것
- 함수 몸체 내부에서 정의한 함수를 '중첩 함수'
- 중첩 함수를 포함하는 함수를 '외부 함수'

스코프 체인

  • 스코프 체인은 물리적인 실체로 존재함
  • 자바스크립트 엔진은 코드(전역 코드와 함수코드)를 실행하기에 앞서 위 그림과 유사한 자료구조인 렉시컬 환경(Lexical Environment)을 실제로 생성함
렉시컬 환경(Lexical Environment)

- 코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 말한다.
- 전역 렉시컬 환경은 코드가 로드되면 곧바로 생성되고, 함수의 렉시컬 환경은 함수가 호출되면 곧바로 생성된다.

 

함수 레벨 스코프

  • 코드 블록이 아닌 함수에 의해서만 지역 스코프가 생성됨
  • 대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드 블록(if, for, while, try/catch 등)이 지역 스코프를 만들고, 이를 블록 레벨 스코프라고 함
  • var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정하고, 이를 함수 레벨 스코프라고
var x = 1;

if(true){

  var x = 10;
}

console.log(x);

위 해당 코드에서 블록 레벨 스코프라고 하면  `console.log(x)` 는 1이 나와야 한다. 하지만 var 키워드는 함수 레벨 스코프기 때문에 if 문 안의 코드도 전역 변수로 취급하며 `var x = 10`으로 재할당이 일어나며 10이 출력된다. 

  • var 키워드함수 레벨 스코프를 지원함
  • const 키워드블록 레벨 스코프를 지원함

 

렉시컬 스코프

var x = 1;

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

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

foo();
bar();

위 코드의 결과 값을 생각해 보자.

 

위 코드의 실행 결과는 var 함수의 상위 스코프가 무엇인지에 따라 결정됨

  1. 동적 스코프(dynamic scope) - 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정함
  2. 렉시컬 스코프(lexical scope, 정적 스코프) - 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정함

 

bar 함수의 상위 스코프

동적 스코프 방식 ->  foo 함수의 지역 스코프와 전역 스코프

렉시컬 스코프 방식 -> 전역 스코프

 

  • 함수를 어디서 호출했는지가 아닌 함수를 어디서 정의했는지에 따라 상위 스코프를 결정
  • 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않음
  • 함수의 상위 스코프는 언제나 자신이 정의된 스코프
  • 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정
  • 함수 정의(함수 선언문 또는 함수 표현식)가 실행되어 생성된 함수 객체는 이렇게 결정된 상위 스코프를 기억
  • 함수는 자신의 내부 슬롯[[Environment]]에 자신이 정의된 환경(상위 스코프)의 참조를 저장함

 

클로저와 렉시컬 환경

  • 함수형 프로그래밍 언어에서 사용되는 중요한 특성
  • MDN에서는 클로저를 함수와 그 함수가 선언된 렉시컬 환경과의 조합으로 정의함
  • 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있을 때의 중첩 함수
const x = 1;

function outer(){

  const x = 10;
  const inner = function() { console.log(x);};
  return inner;
}

const innerFunc = outer();
innerFunc(); // 10 출력

위 코드에서 outer함수는 inner를 반환하고 생명주기(life cycle)를 마감한다. outer 함수의 실행이 종료되면 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거(pop)된다. 따라서 outer 함수의 지역 변수 x는 더는 유효하지 않게 되어 x 변수에 접근할 수 있는 방법은 달리 없어 보인다. 하지만, `innerFunc();` outer 지역변수인 실행 결과는 10이 출력된다. 이미 생명 주기가 종료되어 실행 컨텍스트 스택에서 제거된 outer 함수의 지역 변수 x가 다시 동작하고 있다.

 

outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer 함수의 렉시컬 환경까지 소멸하는 것은 아님