스코프와 스코프 체인

2024년 5월 27일 by이호연
thumbnail_2-3.png

스코프(Scope)와 스코프 체인(Scope Chain)

스코프(Scope)는 식별자에 대한 유효한 범위를 의미합니다.
이러한 스코프를 안쪽에서부터 바깥쪽으로 차례대로 검색해나가는 것을 스코프 체인(Scope Chain)이라고 합니다. 그리고 이것을 가능하게 하는 것이 바로 렉시컬 환경(Lexical Environment)Outer Environment Reference입니다.

환경 레코드의 중첩 구조

모든 환경 레코드는 Outer Environment Reference를 가지고 있습니다.
이것은 현재 환경 레코드의 바깥쪽 환경 레코드를 참조하는 역할을 합니다. 예제를 통해 살펴보겠습니다.

스코프 체인
var a = 1;
var outer = function () {
  var inner = function () {
    console.log(a);
    var a = 3;
  };
  inner();
  console.log(a);
};
outer();
console.log(a);

Outer Environment Reference를 객체로 표현하여 나타내면 아래와 같습니다.

🚨

Outer Environment Reference는 실제로 존재하는 객체가 아니며, 유저가 직접 접근할 수 없습니다.
이해를 돕기 위해 객체로 표현하였습니다.

시작

전역 컨텍스트가 활성화 됩니다.

활성화 컨텍스트: GLOBAL
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: undefined,
    outer: undefined
  }
}

scope_context_1

var a = 1; var outer = function ()

전역 컨텍스트의 EnvironmentRecord에 a와 outer가 저장됩니다.

활성화 컨텍스트 : GLOBAL
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function (){}
  }
}

scope_context_1

outer()

outer 함수 실행 컨텍스트가 활성화 됩니다.

활성화 컨텍스트: outer
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function (){}
  }
};
 
outer = {
  OuterEnvironmentReference: GLOBAL,
    EnvironmentRecord: {
    inner: undefined
  }
}

scope_context_1

inner()

inner 함수 실행 컨텍스트가 활성화 됩니다.

활성화 컨텍스트: inner
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function (){}
  }
};
outer = {
  OuterEnvironmentReference: GLOBAL,
  EnvironmentRecord: {
    inner: function (){}
  }
};
inner = {
  OuterEnvironmentReference: outer,
  EnvironmentRecord: {
    a: undefined
  }
}

scope_context_1

console.log(a);

inner 함수 실행 컨텍스트에서 a를 찾습니다.
현재 활성 상태인 inner 함수의 EnvironmentRecord에 a가 발견되었으나, 값이 할당되지 않았기 때문에 undefined가 출력됩니다.

활성화 컨텍스트: inner
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};
outer = {
  OuterEnvironmentReference: GLOBAL,
  EnvironmentRecord: {
    inner: function () {},
  },
};
inner = {
  OuterEnvironmentReference: outer,
  EnvironmentRecord: {
    a: undefined,
  },
};

scope_context_1

var a = 3;

inner 함수 실행 컨텍스트의 EnvironmentRecord에 a가 저장됩니다.

활성화 컨텍스트: inner
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};
outer = {
  OuterEnvironmentReference: GLOBAL,
  EnvironmentRecord: {
    inner: function () {},
  },
};
inner = {
  OuterEnvironmentReference: outer,
  EnvironmentRecord: {
    a: 3,
  },
};

scope_context_1

} (inner 함수 종료)

inner 함수 실행 컨텍스트가 콜 스택에서 제거됩니다. 바로 아래의 outer 실행 컨텍스트가 다시 활성화 됩니다.
엔진은 7번줄로 이동합니다.

활성화 컨텍스트: outer
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};
outer = {
  OuterEnvironmentReference: GLOBAL,
  EnvironmentRecord: {},
};

scope_context_1

console.log(a);

outer의 EnvironmentRecord에서 a를 찾습니다.
현재 활성 상태인 outer 함수의 EnvironmentRecord에는 a가 없기 때문에 바깥쪽 환경인 전역 컨텍스트로 이동하여 a를 찾습니다.
전역 컨텍스트의 EnvironmentRecord에 a가 있으므로 1이 출력됩니다.

활성화 컨텍스트: outer
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};
outer = {
  OuterEnvironmentReference: GLOBAL,
  EnvironmentRecord: {},
};

scope_context_1

} (outer 함수 종료)

outer 함수가 종료됩니다. outer 함수가 콜 스택에서 제거되고 바로 아래의 전역 컨텍스트가 다시 활성화 됩니다.
엔진은 10번줄로 이동합니다.

활성화 컨텍스트: GLOBAL
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};

scope_context_1

console.log(a);

전역 컨텍스트의 EnvironmentRecord에서 a를 찾습니다.
전역 컨텍스트의 EnvironmentRecord에 a가 있으므로 1이 출력됩니다.

활성화 컨텍스트: GLOBAL
GLOBAL = {
  OuterEnvironmentReference: null,
  EnvironmentRecord: {
    a: 1,
    outer: function () {},
  },
};

scope_context_1

종료

전역 컨텍스트가 콜 스택에서 제거됩니다.

scope_context_1

전역변수와 지역변수

전역변수는 전역 스코프에서 선언된 변수를 의미하며, 어디서든 접근이 가능합니다.
예제에서는 전역 스코프에서 선언한 a와 outer가 전역변수에 해당합니다.
outer 내부에서 선언한 inner와 inner 내부에서 선언한 a는 지역변수에 해당합니다.

🤔

전역변수를 지양해야하는 이유는 뭘까요?