데굴데굴

<JavaScript> 스코프 본문

Programming/JavaScript

<JavaScript> 스코프

aemaaeng 2023. 11. 11. 20:56

식별자가 유효한 범위 / 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙

 

Quiz. 아래 코드에 존재하는 두 개의 console.log(x)의 결과는 각각 어떻게 될까?

var x = 'global';

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

foo();
console.log(x);

 

Answer. 'local' / 'global'

 

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

 

전역 변수는 어디서든지 참조가 가능하다.
지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 참조할 수 있다.

지역 변수를 전역 변수에서 참조하려고 하면 ReferenceError가 발생한다.

스코프 체인

스코프가 계층적으로 연결된 것

 

스코프는 함수의 중첩에 의해 계층적인 구조를 갖게 된다.
자바스크립트는 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.

상위 -> 하위: 참조 가능
하위 -> 상위: 참조 불가능

 

Quiz에서 함수 foo()를 실행했을 때 함수 내부의 console.log(x)에서 'local'이 출력된 것도 이 때문이다. 이후 코드 마지막에 console.log(x)를 수행했을 때에는 전역 스코프의 x를 참조하고 있으므로 'global'이 출력된다. foo() 내에서 전역 변수 x에 값을 재할당하는 것이 아니라 같은 키워드, 같은 이름으로 변수를 새롭게 선언하고 있으므로 전역 변수의 값은 바뀌지 않는다. (즉 x라는 변수가 두 개 존재하게 됨)

 

보충 개념: Lexical Environment

함수 검색

function foo() {
  console.log('global function foo');
}

function bar() {
  function foo() {
    console.log('local function foo');
  }
  foo(); // 1. 'local function foo'
}

foo(); // 2. 'global function foo';
bar(); 

함수 선언문으로 함수를 선언하면 런타임 이전에 함수 객체가 먼저 생성된다.
bar() 내에서 foo() 함수를 실행하면 bar() 함수 내부의 스코프부터 탐색하게 된다.
따라서 가장 가까이에 있는 내부 foo() 함수를 실행한다.

함수 레벨 스코프

if, for, while, try/catch 등에서 지역 스코프가 생성되는데 이걸 블록 레벨 스코프 block level scope라고 한다.

var 키워드로 변수를 선언하게 되면 함수 내부에서만 지역 스코프가 생성되는데 이걸 함수 레벨 스코프 function level scope라고 한다.

// block level scope
var x = 1;
if (true) {
  var x = 10; // 전역 변수 취급 => x의 값이 바뀐다
}
console.log(x); // 10

렉시컬 스코프

// quiz
var x = 1;

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

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

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

내 예측

  • 일단 전부 호이스팅되어서 함수 객체가 먼저 생성될 것이다.
  • foo() 함수 내부의 bar() 함수는 가장 가까운 x를 참조하여 foo() 내부의 x를 참조해 10이 출력될 것 같다.
  • 다음에 bar()를 따로 실행하면 전역 스코프에 있는 x를 참조해 1이 출력될 것 같다.

정답
전부 1이 출력된다. 왜지??

 

이 예제의 실행 결과는 상위 스코프의 결정 방식에 따라 결정된다.
여기서 두 가지 경우를 예상해볼 수 있다.

  1. 함수를 어디서 호출했는지에 따라 상위 스코프가 결정된다.
  2. 함수를 어디서 정의했는지에 따라 상위 스코프가 결정된다.

내가 처음에 답을 유추한 방식은 1번이었다. 1번과 같은 방식으로 스코프가 결정되는 것을 동적 스코프dynamic scope 라고 한다.
자바스크립트는 2번 방식을 따르는데 2번과 같은 방식을 렉시컬 스코프lexical scope 혹은 정적 스코프static scope 한다.

함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정되는 방식이다. 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.

따라서 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.

위 퀴즈를 다시 풀어보면 bar()의 상위 스코프는 전역 스코프이기 때문에 foo() 함수 내부에서 bar()를 호출해도 전역에 있는 변수 x를 참조하여 1이 두 번 출력된다.

 

관련 개념: 클로저

 

참고: 모던 자바스크립트 딥다이브

Comments