[JS] 스코프와 스코프체인

박시은·2023년 3월 7일
0

JavaScript

목록 보기
10/58
post-thumbnail

실행 컨텍스트 》과 연관된 내용이 많으므로 같이 공부하도록 하자!


▶ scope

스코프(scope)란?

  • 모든 식별자는 자신이 선언된 위치에 다른 코드가 식별자 자신을 참조할 수 있는 범위가 결정된다.
  • 즉, 스코프(유효 범위)는 식별자가 어디까지 유효한지 범위를 결정한다.
    • 식별자는 변수이름이나 함수 이름이다. 즉, 어떤 값을 구별하여 식별해낼수 있는 고유한 이름을 말한다.

예시

  • 아래 코드를 보면 var x가 함수 밖(전역 변수) 에서 1번 함수 안(지역 변수)에서 1번 참조된 것을 알 수 있다.
var x = "global";
function test() {
  var x = "local";
  console.log(x); // "local";
}
test();
console.log(x); // "global";

➡️ 하지만, var x가 어디있는지에 따라 동일한 코드도 다른 결과를 만들어 낸다.

➡️ 그 이유는, 이름은 동일한 식별자이지만 스코프가 다르기 때문이다.

➡️ 즉, 위의 코드를 통해 다른 스코프에서는 같은 이름의 식별자를 사용할 수 있다는 것을 알 수 있다.


스코프가 없으면 어떻게 될까?

스코프라는 개념이 없다면 같은 이름을 갖는 변수는 충돌을 일으킬 것이다.


▷ scope 종류

변수가 어느 위치에 선언되어있는지에 따라 Global Scope(전역 스코프)와 local Scope(지역 스코프)로 나뉜다.

// Global Scope
var x = "global";


// local Scope
function test() {
  var x = "local";
  console.log(x); //1
}

① Global Scope

전역범위에서 변수 선언시 어디서든지 접근할 수 있다.

// global scope에서의 변수 선언
var varvalue = "Happy!";
let letvalue = "Happy!";
const constvalue = "Happy!";


// 일반적인 접근
console.log(varvalue);
console.log(letvalue);
console.log(constvalue);


// 함수에서의 접근
function print_value() {
  console.log(varvalue);
  console.log(letvalue);
  console.log(constvalue);
}
print_value();


// 조건문에서의 접근
if (true) {
  console.log(varvalue);
  console.log(letvalue);
  console.log(constvalue);
}

② local Scope

함수 범위에서 변수 선언시 해당 함수 내부에서만 접근 가능하다.

*lacal(지역)이란 함수 본체 내부를 말한다.

함수 외부에서 변수 값 접근 불가 -> (출력x)

// 함수 내부에서의 변수 선언
function print_Value() {
  const value = "Happy!";
}

console.log(value);

아래처럼 함수 내에서만 변수 값 접근 가능 -> (출력o)

function print_Value() {
  const value = "Happy!";

  console.log(value);
}

print_Value();

③ function level scope

var 키워드로 선언된 변수는 오로지 함수의 코드 블록(if, for, while 등)만을 지역 스코프로 인정하는데, 이러한 특성을 함수 레벨 스코프라고 한다.

var x = 1;
if (true) {
  var x = 10;
}
console.log(x); //10
  • 따라서 위 코드는 1이 아닌 10이출력된다.
  • var 키워드로 선언된 변수는 함수 레벨 스코프만 인정하기 때문에 전역 변수의 값이 재할당 되기 때문이다!

④ lexical scope

아래 코드의 실행 결과를 예측해보자

var x = 1;
function foo() {
  var x = 10;
  bar();
}
function bar() {
  console.log(x);
}
foo();
bar();

위 코드의 실행 결과는 var 함수의 상위 스코프에 따라 정의된다.

  • 함수를 어디서 호출했는지에 따라 상위 스코프를 결정한다. -> dynamic scope(동적 스코프)
  • 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다. -> lexical scope(정적 스코프)

⭐ JS 엔진은 함수를 어디서 "호출" 했는지가 아니라, 어디에 "정의" 했는지에 따라서 상위 스코프를 결정하는데, 이를 렉시컬 스코프(lexical scope)라고 한다.

  • 렉시컬 스코프의 개념을 배웠으니 위 코드를 다시 보자.
  • 위 코드에서 bar 함수가 호출되면 호출된 시점과는 관계없이 정의된 시점에서의 스코프에 의해 결정되므로 자신이 기억하고 있는 전역 스코프를 상위 스코프로 사용한다. 따라서 위 코드를 실행하면 변수 x의 값 1이 두 번 출력된다.



▶ scope chain

  • 코드를 다시 한 번 살펴보자, js엔진은 어떻게 같은 변수명을 가진 x를 구별해 내어 다른 결과를 출력할 수 있었을까?
var x = "global";
function test() {
  var x = "local";
  console.log(x); // "local";
}
test();
console.log(x); // "global";

➡️ js 엔진이 스코프 체인을 사용하여 동일한 변수명을 가진 변수를 구별하였기 때문에 다른 결과를 출력할 수 있었던 것이다.


스코프 체인이란?

  • 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 말한다.

  • 스코프 체인은 실행 컨텍스트의 렉시컬 환경(LE)을 단방향으로 연결(Chaining)한 것이다.

    • 즉, 렉시컬 환경은 스코프 엔진의 실체라고 할 수 있다!!


  • 변수를 참조할 때 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 함수를 검색하는데, 이를 통해 동일한 변수 명이라도 스코프에 따라서 변수를 구별할 수 있는 것이다.

  • 따라서 상위 스코프에서 유효한 변수는 하위 스코프를 참조할 수 없다!!


스코프 참조 자세한 과정

아래 그림이 이해가 안가면 《 호이스팅 》을 보고 오자.

  • 'A'라는 실행 컨텍스트가 생성될 때의 환경은 전역 컨텍스트이므로, LE를 참조하게 되어 그 당시의 환경 정보를 저장하게 된다.

  • 'B'라는 실행 컨텍스트가 생성될 때, 그때의 환경은 'A'이므로, 'A'의 LE를 'B'의 outer로 가지고 있는다.

  • 'C'라는 실행 컨텍스트가 생성될 때, 그때의 환경은 'B'이므로, 'B'의 LE를 'C'의 outer로 가지고 있는다.
    • 그때의 변수 정보 등을 참조하기 위해 가지고 있는것

➡️ 이 과정을 스코프 체이닝이라고 한다.


정리

  1. outer는 현재 호출된 함수가 선언될 당시의 LE를 참조(= 그 당시의 환경 정보를 저장)한다.

  2. 예를 들어, A함수 내부에 B함수 선언B함수 내부에 C함수 선언(Linked List)한 경우

  3. 결국 타고, 타고 올라가다 보면 전역 컨텍스트의 LE를 참조하게 된다.

  4. 항상 outer는 오직 자신이 선언된 시점의 LE를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근이 가능하다.

➡️ 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다.


예시

var a = 1;
var outer = function() {
	var inner = function() {
		console.log(a); // 1 : 이 값은 뭐가 나올지 예상해보자
		var a = 3;
	};
	inner();
	console.log(a); // 2 : 이 값은 뭐가 나올지 예상해보자
};
outer();
console.log(a); // 3 : 이 값은 뭐가 나올지 예상해보자

  • 호이스팅 전
var inner = function() {
		console.log(a); // 1
		var a = 3;
	};
	inner();
  • 호이스팅 후
var inner = function() {
    var a; // 선언이 호이스팅됨
    console.log(a); // 1 : undefined, 초기화는 호이스팅되지 않음
    a = 3; // 할당
};
	inner();


2 : console.log(a);는 전역 영역인 var a =1을 가져오므로 1이 출력된다.


3 : console.log(a);은 전역 영역 내부 스코프의 var a =1이 되므로 1이 출력된다.


정리⭐⭐

➡️ 각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보 (outer 입장에선 전역 context)가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.

profile
블로그 이전했습니다!

0개의 댓글