TIL - 자바스크립트 스코프(Scope)

Scope(스코프)

스코프란 자바스크립트 런타임 내의 코드에 있는 변수나 함수 혹은 객체에 대한 접근성을 일컫는다. 즉, 스코프(범위) 영역 내에서의 변수나 다른 리소스들만 가시성을 보장받는다.

왜 모든 코드에 접근성을 부여하지 않고 제한을 거는 걸까? 회사에 컴퓨터 관리자가 여러 명이 있고 그들에게 모든 엑세스를 부여했다고 가정하자. 어느날 치명적인 바이러스가 회사 서버에 침투하여 모든 시스템이 망가졌다. 이 상황에서 어떤 관리자의 실수로 보안망이 뚫렸는지 감지할 수 있을까? 당신이 회사의 책임자라면 최소한의 권한을 부여하고 꼭 필요한 책임자에게만 더 높은 권한을 부여하는 방식이 필요하다는 사실을 깨달을 것이다. 이는 The Principle of Least Access(최소한의 권한) 라는 원칙이라 불린다. 이 원칙은 프로그래밍 언어에도 역시 적용 가능하다.

프로그래밍을 하면서 성능 개선을 위해, 버그 트래킹과 버그 최소화를 위해 스코프를 지정하는 것이 필요할 뿐만 아니라 스코프 활용을 통해 변수명 중복에 대한 위험도 피할 수 있다.

자바스크립트에는 두 가지 스코프 종류가 있다.

  • Global Scope(전역 스코프)
  • Local Scope(로컬 스코프)

전역 스코프(Global Scope)

함수 내에 선언된 변수는 로컬 스코프를 가지며 함수 바깥에 선언된 함수는 글로벌 스코프를 가진다. 자바스크립트 파일을 통틀어 전역 스코프는 단 하나만 존재한다. 함수 바깥에 선언된 변수 영역이 그것이다.

var name = "Yeonha"; // Global Scope

console.log(name); // 'Yeonha'

function author() {
	console.log(name);
} //name은 전역변수로 함수 안팎으로 사용 가능

author(); // 'Yeonha'

로컬 스코프(Local Scope)

함수 내에서 선언된 변수는 로컬 스코프를 갖는다. 변수의 이름이 같다고 하더라도 서로 독립적인 로컬 스코프를 갖는다면 전혀 문제가 되지 않는다. 각각의 로컬 변수는 그들이 속한 함수에 메여 있을 뿐 다른 함수에 접근할 수 없기 때문이다.

// Global Scope
function someFunction() {
	// Local Scope #1
	function someOtherFunction() {
		// Local Scope #2
	}
}

function anotherFunction() {
	//Local Scope #3
} // Global Scope

var, let, const의 범위

  • var 를 사용할 때의 범위

함수의 범위와 헷갈려선 안되는 것이 괄호(block) 범위이다. 괄호 범위는 함수와 달리 새로운 스코프를 생성하지 않는다. 아래처럼 중괄호 안에서 선언된 변수는 글로벌 스코프에서도 여전히 유효하다.

if (true) {
	var korean = "한국어";
}
console.log(korean); // '한국어'
  • let, const 를 사용할 때의 범위

ECMAScript 6가 도입되면서 등장한 let, const 키워드는 var 와 달리 블록문 안에서의 로컬 스코프 정의를 가능케한다.

if (true) {
	let state = "블록문 내에서만 유효합니다";
	console.log(state);
	// '블록문 내에서만 유효합니다';
}
console.log(state); // Uncaught ReferenceError: state is not defined

Lexical Scope(렉시컬 스코프)

렉시컬 스코프는 코드가 작성되는 시점에 스코프가 결정되어지는 것을 말한다. 자바스크립트는 렉시컬 스코프 방식이다. 겹겹으로 이루어져있는 함수들을 떠올려보자. 가장 하단에 위치한 함수는 그보다 상단 껍데기에 위치한 함수와 전역 스코프에 접근이 가능하다. 이는 자식 함수(가장 안쪽에 있는 함수)가 부모의 실행 컨텍스트에 속해있다는 말이 된다.

var name = "Park"; // 1번.
function someFunc() {
	console.log(name);
}

function parentFunc() {
	var name = "Kim"; // 2번
	someFunc();
}

parentFunc();

위 코드에서 parentFunc() 를 실행했을 때 결과는 1번과 2번 중 무엇일까? 정답은 1번, ‘Park’이다. 이는 lexical scope의 특징을 잘 보여주는 예제이다.

someFunc 라는 함수를 선언하는 순간 함수 안의 변수는 자기 자신으로부터 가장 가까운 상위 범위에 있는 변수(1번)를 참조한다. 내부 함수에서는 외부 함수의 변수에 접근 가능하기 때문에 변수를 찾기 위해 먼저 자기 자신의 스코프를 탐색하고, 없으면 한 단계 올라간 스코프에서 탐색하고, 궁극적으로는 전역 스코프까지 탐색한다. 만약 전역 스코프에도 찾는 값이 없다면 에러를 리턴한다. 이런 개념을 스코프 체인(Scope Chain)이라고 한다.

함수가 한 번 선언되고 나서는 이미 전역 변수를 가리키고 있는 name이 다른 걸 가리키게 할 수 없다. 만약 값을 변경하고 싶다면 아래와 같은 코드로 변경해야 한다.

var name = "Park"; // 1번.
function someFunc() {
	console.log(name);
}

function parentFunc() {
	name = "Kim";
	someFunc();
}

parentFunc(); // 'Kim'