스코프에 대하여
스코프란?
프로그래밍 언어에서 스코프는 변수와 함수의 유효 범위를 의미한다.
변수와 함수는 자신이 선언되는 위치에 의해 스코프가 결정되며 위치에 따라 전역 혹은 지역 스코프를 갖는다.
// 전역 스코프: 프로그램 전체에서 접근 가능한 범위
var x = 'global x';
function outer() {
// 지역 스코프: 특정 블록이나 함수 내에서만 접근 가능한 범위
var y = "outer's local y";
function inner() {
var z = "inner's local z";
}
}
스코프 모델
변수와 함수의 상위 스코프가 결정되는 방식에는 렉시컬 스코프와 동적 스코프가 있다.
렉시컬 스코프 (정적 스코프)
변수와 함수의 유효 범위가 코드를 작성할 때 결정되며 어디서 정의했는지에 따라 상위 스코프가 정해진다.
var x = 10;
function foo() {
console.log(x);
}
function bar() {
var x = 20;
foo(); // 10
}
bar(); // 10
동적 스코프
변수의 유효 범위가 함수를 호출하는 시점에 따라 동적으로 결정된다.
# bash
x=10
function foo {
echo $x
}
function bar {
local x=20
foo // 20
}
bar
스코프 in JS
스코프가 정해지는 시기
JS는 스크립트 언어로 분류되며 위에서 아래로 한줄씩 처리된다고 알려져 있다.
실제로 최신 JS 엔진은 프로그램을 처리할 때 수많은 종류의 컴파일러와 인터프리터를 사용한다.
컴파일러가 소스 코드를 평가하는 과정에서 변수, 함수 등의 선언문만 먼저 실행하고 스코프 매니저를 통해 스코프에 식별자를 등록한다. 이때 스코프가 정해지게 된다.
그러나 언제나 예외는 존재한다…
// eval(): 소스 코드를 문자열로 받아 런타임에 컴파일, 실행하는 함수
function badIdea() {
eval("var oops = '이런!';");
console.log(oops);
}
badIdea();
// with: 특정 객체의 스코프를 지역 스코프로 옮기는 키워드
var badIdea = { oops: '이런!' };
with (badIdea) {
console.log(oops);
}
전역 스코프
전역 스코프는 런타임에 프로그램을 구성하는 코드 조각들이 어디에 있는지, 그리고 각 코드 조각이 어떤 방식으로 다른 코드 조각에 접근해 협력하는지 관여한다.
기본 JS 내장 기능 혹은 호스트 환경의 내장 기능을 사용할 때 전역 스코프를 사용한다.
특징
- 전역 스코프에서
var
과function
으로 변수 혹은 함수를 선언하면 전역 객체의 프로퍼티를 통해서도 접근할 수 있다. - 전역 스코프에서
let
과const
로 변수를 선언하면 전역 객체의 프로퍼티가 가려진다. - DOM 요소에
id
속성을 추가하면 전역 변수가 자동으로 생긴다.
모듈 스코프
ES 모듈의 최상위 레벨 스코프에서 선언된 변수는 전역 변수가 되지 않고 모듈 스코프의 변수가 된다.
모듈에서는 최상위 레벨의 선언을 프로퍼티로 추가할 수 있는 모듈 스코프 객체를 지원하지 않는다. 이 말은 모듈 내 최상위 레벨에서 변수를 선언하면 전역 변수가 생성되지 않는다는 의미다.
함수 스코프
var
, function
으로 선언한 변수 혹은 함수는 함수 선언식, 화살표 함수 블록을 지역 스코프로 인정하는 함수 스코프를 따른다.
블록 스코프
let
, const
로 선언한 변수는 모든 코드 블록(함수, if
문, for
문, while
문, try/catch
문 등)을 지역 스코프로 인정하는 블록 스코프를 따른다.
모든 중괄호 쌍이 항상 블록 스코프를 생성하는 건 아니다.
- 객체 리터럴의
{}
은 스코프가 아니다. - class의
{}
은 스코프가 아니다. - switch의
{}
은 스코프가 아니다.
명시적으로 블록을 만들어도 내부에 선언이 없으면 스코프를 만들지 않는다.
if (somethingHappened) {
// 블록이지만 스코프는 아닙니다.
{
// 블록이면서 스코프입니다.
let msg = 'msg';
}
}
매개변수 스코프
단순한 매개변수일 경우 함수 스코프에 속한다.
단순하지 않은 매개변수일 경우 자체 스코프를 형성하고 함수 스코프는 매개변수 스코프에 중첩된다.
단순하지 않은 매개변수의 예로 기본값이 있는 매개변수, 나머지 매개변수, 비구조화 매개변수가 있다.
기본값이 있는 매개변수
function parameterScope(id = defaultID, defaultID) { ... } // TDZ Error
function parameterScope(defaultID, id = defaultID) { ... }
기본값이 있는 매개변수(함수 표현식)
function parameterScope(id, defaultID = () => id) {
id = 5;
console.log(defaultID());
}
parameterScope(3); // 5
function parameterScope(id, defaultID = () => id) {
var id = 5;
console.log(defaultID());
}
parameterScope(3); // 3
function parameterScope(id, defaultID = () => id) {
let id = 5; // SyntaxError: Identifier 'id' has already been declared
console.log(defaultID());
}
parameterScope(3);
function parameterScope(id, defaultID = () => id) {
{
let id = 5;
console.log(defaultID());
}
}
parameterScope(3); // 3
비구조화 매개변수
function parameterScope({ id, defaultID = () => id }) {
var id = 5;
console.log(defaultID());
}
parameterScope({ id: 3 }); // 3
함수 이름 스코프
funcName 식별자(함수명, 변수)가 서로 다른 스코프에 있기 때문에 재선언이 섀도잉으로 처리된다.
var func = function funcName() {
let funcName = 'funcName';
};