[JS] 스코프(Scope)

✅ 스코프(Scope)

  • 식별자가 유효한 범위
  • 식별자를 검색할 때 사용하는 규칙
  • 변수는 자신이 선언된 위치에 의해 자신이 유효한 범위, 즉 다른 코드가 변수 자신을 참조할 수 있는 범위가 결정된다. 모든 식별자는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정된다.

🔰 식별자 결정

  • JS 엔진이 이름이 같은 두 개의 변수 중에서 어떤 값을 참조해야 할 지 결정하는 것.
  • 스코프는 네임스페이스다.
var x = 'global';

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

foo(); // 'function scope'
console.log(x); // 'global'
  • 같은 x라는 이름을 갖는 변수를 참조하지만 각 x변수의 스코프 가 다르기 때문에 에러가 발생하지 않는다. -> 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다.

❗ 식별자는 어떤 값을 구별할 수 있어야 하므로 유일해야 한다. -> 하나의 값은 유일한 식별자에 연결되어야 한다.
❗ 스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다.
📌 스코프는 식별자 이름의 충돌을 방지한다.

❗ var의 문제점 ❗

  • var는 변수 재선언이 가능하다.
  var country = 'korea'
   console.log(name) // korea

   var country = 'usa'
   console.log(name) // usa
  • 변수를 한 번 더 선언했음에도 불구하고, 에러가 나오지 않는다.
  • var는 function level scope를 가진다.
var a = 1

if (true) {
 var a = 5
}

console.log(a) // output: 5
  • if block내에서 a가 다시 한번 선언되었고, 아래 output이 바뀌었다.
  • 전역 스코프를 공유하기 때문에 어딘가에 동일한 이름이 있다면 예상치 못한 결과를 가져올 수 있는 위험이 있다.
    -> 이로 인해 let, const가 생겨났다. let, const와 블록 레벨 스코프 정리글
  • let, const로 선언된 변수는 중복 선언을 허용하지 않는다.
function bar() {
	let x = 1;
	let x = 2; //SyntaxError
} 

✅ 스코프의 종류

  • 변수는 자신이 선언된 위치에 의해 자신이 유효한 범위인 스코프가 결정된다.
구분설명스코프변수
전역코드의 가장 바깥 영역전역 스코프전역 변수
지역함수 몸체 내부지역 스코프지역 변수

🔰 전역 스코프(global scope)

  • 전역이란 코드의 가장 바깥 영역을 말한다.
  • 전역에 변수를 선언하면 전역 스코프를 갖는 전역 변수(global variable)가 된다.
  • 코드 어디에서든지 참조할 수 있다.
  • 위 코드에서 x,y변수는 전역 변수다.
  • ❌ 전역 스코프에서는 지역 변수를 참조할 수 없다.

🔰 지역 스코프(local scope)

  • 지역이란 함수 몸체 내부를 말한다.
  • 지역은 지역 스코프(local scope)를 만든다.
  • 함수에 의해서만 지역 스코프가 생성된다.
  • 지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수(local variable)가 된다.
  • 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.
  • 위 코드에서 outer 함수 내부에서 선언된 z 변수는 지역 변수다. inner 함수 내에 x변수도 지역변수이다. 이때 x는 전역 변수가 아닌 자신이 선언된 스코프, inner 함수 내부의 x를 참조한다.

✅ 스코프 체인(scope chain)

  • 스코프는 함수의 중첩에 의해 계층적 구조를 갖는다. 아래 그림은 위 코드의 스코프 체인을 나타낸 그림이다.
  • outer 함수가 만든 지역 스코프는 inner 함수가 만든 지역 스코프의 상위 스코프다.
  • outer 함수의 지역 스코프의 상위 스코프는 전역 스코프다.
  • 모든 지역 스코프의 최상위 스코프는 전역 스코프다.
  • 이렇게 스코프가 계층적으로 연결된 것을 스코프 체인(scope chain)이라 한다.

🔰 변수 검색

  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색(identifier resolution)한다.
  • 이를 통해 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조할 수 있다.
  • ❌ 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있지만, 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다.

🔰 함수 검색

  • 함수도 식별자에 할당되기 때문에 스코프를 갖는다.
	function foo() {
	  console.log('global function foo');
   	}
	
	 function bar() {  
           // 내부함수
	   function foo() {
	   console.log('local function foo');
	  }
	  foo(); //내부 foo가 실행됨.
	}
	bar(); //'local function foo'
  • 변수 x는 코드 블록 내에서 선언되었다. 하지만 자바스크립트는 블록 레벨 스코프를 사용하지 않으므로 함수 밖에서 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 스코프을 갖게된다. 따라서 변수 x는 전역 변수이다.

✅ 함수 레벨 스코프(Function-level scope)

  • C나 자바 등을 비롯한 대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드 블록이 지역 스코프를 만든다. 이러한 특성을 블록 레벨 스코프라 한다.
  • var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다. 이러한 특성을 함수 레벨 스코프라 한다.
	var x = 1;
	if (true) {
     	  //if문은 함수가 아니다.
          //함수 밖에서 var로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수다.
	  var x = 5; //x가 중복 선언되었다.
	}
	console.log(x); //5
  • let, const로 선언된 변수는 블록 레벨 스코프를 지원한다.

✅ 렉시컬 스코프(Lexical scope)

  • 상위 스코프를 결정하는 방식.
  • 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정된다.
  • 어디서 정의했는지에 따라 결정된다.
  • 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.
	var x = 1;
	
	function foo() {
	  var x = 10;
	  bar();
	}
	
	function bar() {
	  console.log(x);
	}
	
	foo(); // ?
	bar(); // ?

❓ 위 코드의 결과값은 어떻게 될까? 먼저 bar의 상위 스코프를 알아야 한다.

    1. 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다.
    1. 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다.
  • 첫번째 방식으로 함수의 상위 스코프를 결정한다면 함수 bar의 상위 스코프는 함수 foo와 전역일 것이다. -> 이를 동적 스코프(Dynamic scope)라 한다.
    📌 동적 스코프 : 함수를 정의하는 시점에는 함수가 어디서 호출될지 알 수 없다. 따라서 함수가 호출되는 시점에 동적으로 상위 스코프를 결정해야 하기 때문에 동적 스코프라고 부른다.
  • 두번째 방식으로 함수의 스코프를 결정한다면 함수 bar의 스코프는 전역일 것이다. -> 이를 렉시컬 스코프(Lexical scope) 또는 정적 스코프(Static scope)라 한다.
  • 동적 스코프 방식처럼 상위 스코프가 동적으로 변하지 않고 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정되기 때문에 정적 스코프라고 부른다.

🔰 함수의 스코프 결정 시기

  • 📌 자바스크립트는 렉시컬 스코프를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다. 함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다.

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

  • 위 예제의 함수 bar는 전역에 선언되었다. 따라서 함수 bar의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x의 값 1을 두번 출력한다.

  • 📌 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정된다.

  • 함수 정의가 실행되어 생성된 함수 객체는 이렇게 결정되 상위 스코프를 기억한다.

var f = (function () {
    const x = 1;
    //중첩 함수는 상위 스코프보다 오래 살아남는다.
    return function () {
        //함수가 호출되기 전까지는 이곳을 들여다보지 않음.
        //얘의 [[Enviroment]]라고 하는 내부 슬롯이 상위 스코프를 가리키고 있기 때문에 상위 스코프가 아직 사라지지 않았음.(closure)
        console.log(x);
    };
})();

//얘가 호출되면 return 부분의 function 객체가 만들어짐.
f(); //1

//상위 스코프를 찾는 방법 - Enviroment값을 보는 것.

<모던 자바스크립트 deepdive와, 추가 탐구한 내용을 정리한 포스트입니다.>

좋은 웹페이지 즐겨찾기