업데이트:

[01] 스코프란?

📢 스코프는 식별자가 유효한 범위를 말한다.

함수 파트를 공부할 때 함수의 매개변수가 함수 몸체 내부에서만 참조할 수 있다고 배운바 있다. 이 때 매개변수의 스코프는 함수 몸체 내부라고 보면 된다.

코드를 통해 확인해보자.

var var1 = 1; // 코드의 가장 바깥에서 선언한 변수

if(true) {
	var var2 = 2; // 코드 블록 내에서 선언한 변수
	if(true){
		var var3 = 3; // 중첩된 코드 블록 내에서 선언한 변수
	}
}

function foo() {
	var var4 = 4; // 함수 내에서 선언한 변수
	function bar(){
		var var5 = 5; // 중첩된 함수 내에서 선언한 변수
	}
}
console.log(var1); // 1
console.log(var2); // 2
console.log(var3); // 3 
console.log(var4); // var4는 foo()함수 내에서만 유효한 변수이므로 에러 발생
console.log(var5); // var5는 bar()함수 내에서만 유효한 변수이므로 에러 발생

함수 안에 선언한 변수를 출력하면 에러가 발생하고 함수 바깥에서 선언한 변수들은 출력이 되는 것을 볼 수 있다.

if문의 블록문에서 선언한 변수들도 if문의 조건식이 true이기 때문에 출력이 되었다.

🎯 식별자를 검색할 때 사용하는 규칙 : 스코프

var x = 'global';

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

foo(); // 'local'
console.log(x); // 'global'

자바스크립트 엔진은 위와 같은 코드를 해석할 때 문맥을 고려하여 식별자 결정을 하는데 이 때 스코프를 통해 결정한다. 그래서 스코프는 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.

코드 가장 바깥 영역에 선언된 x와 foo() 함수 내부의 x는 이름이 동일한 식별자이지만 스코프가 다른별개의 변수이다.

스코프는 내에서 하나의 식별자는 유일해야 하지만 다른 스코프에는 같은 이름의 식별자를 사용할 수 있다. var 키워드는 같은 스코프 내에서 중복 선언이 허용되므로 부작용이 발생할 수 있다.

function foo() {
	var x = 1;
	var x = 2; // 중복 선언 허용 => 부작용 발생 가능
	console.log(x); //2
}
foo();

[02] 스코프의 종류

전역스코프

Untitled

전역(코드의 가장 바깥 영역)에 변수를 선언하면 전역 스코프를 갖는 전역 변수가 된다.

전역 변수는 함수 내부를 포함해 어디서든지 참조할 수 있다.

지역스코프

지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수가 된다.

지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.

위 그림을 보면 지역 스코프는 outer() 함수, 그리고 하위 스코프 inner() 함수 두 가지가 있다.

inner() 함수 내부에서 선언된 x는 전역이나 outer함수 지역에서 참조하면 참조 에러가 뜬다.

그렇다면 inner함수 내부에서 변수 x를 참조하면 전역, outer, inner 중 어떤 x를 참조할까?

답은 inner 지역의 x를 참조한다. 이유는 자바스크립트 엔진이 스코프 체인을 통해 변수 x를 검색했기 때문이다. 스코프 체인이 무엇인지 알아보자.

[03] 스코프 체인

  • 중첩 함수 : 함수 몸체 내부에서 정의한 함수( 그림에서 inner)
  • 외부 함수 : 중첩 함수를 포함하는 함수 (그림에서 outer)

스코프는 함수의 중첩에 의해 계층적 구조를 갖는다. 이것을 스코프 체인이라고 한다.

Untitled

자바스크립트 엔진은 변수를 참조할 때 변수를 참조하는 코드의 스코프에서 시작해 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.

그렇기 때문에 inner함수에서 x를 참조하면 inner 지역 스코프에서부터 변수를 검색하고 x는 inner’s local x 로 출력될 수 있는 것이다.

🔍 렉시컬 환경이란?

렉시컬 환경은 코드 블록, 함수, 스크립트를 실행하기 앞서 생성되는 특별한 객체로, 실행할 스코프 범위 안에 있는 변수와 함수를 프로퍼티로 저장하는 객체다.

실제로 변수 선언이 실행되면 변수 식별자가 렉시컬 환경에 키로 등록되고 재할당과 검색도 렉시컬 환경에서 이루어진다.

function foo() {
	console.log('global function foo');
}
function bar(){
	function foo(){
		console.log('local function foo');
	}
	foo(); 
}
bar(); //

[04] 함수 레벨 스코프

var 키워드로 선언된 변수는 코드블록이 아니라 함수에 의해서만 지역 스코프가 생성된다.

var x = 1;
if(true) {
	var x = 10;
}
console.log(x); // 10

위의 코드를 보면, if문 코드블록 안에서 x를 또 선언해주었지만 지역 스코프가 따로 생기진 않았고

전역 변수 x가 중복 선언 되었다. 그래서 의도치 않은 전역 변수의 값이 재할당 된 것이다.

var i = 10;

for (var i = 0; i < 5; i++){
	console.log(i); // 0 1 2 3 4
}
console.log(i); // 5

이 코드도 마찬가지로 전역 변수 i를 처음에 10으로 할당해 주었지만 for 블록문 안에서 i 값이 5까지 재할당 되었고 블록문 밖에서 i를 참조했을 때 블록문 내에서 변경된 값인 5가 출력된 것이다.

var 키워드로 선언된 변수는 함수의 코드 블록만을 지역 스코프로 인정하지만 ES6에서 도입된 let, const 키워드는 블록 레벨 스코프를 지원한다고 한다.

[05] 렉시컬 스코프

프로그래밍 언어에서 상위 스코프를 결정하는 방식에는 동적 스코프와 렉시컬 스코프가 있다.

  • 동적 스코프 : 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정
  • 렉시컬 스코프 : 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정

자바스크립트는 렉시컬 스코프를 따른다. 그러므로 함수가 호출된 위치가 아니라 정의된 스코프가 함수의 상위 스코프이다.

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

위 코드에서 foo() 함수를 호출하면 블록문 안에서 bar()함수가 호출된다. bar() 함수에서 x를 참조하면 자신이 정의된 스코프인 bar 지역과 전역만 참조가능하다. 결국 두 호출 결과 모두 1이 나온다.

렉시컬 스코프는 클로저와 깊은 연관이 있다고 하니 뒤에서 더 자세히 알아보도록 하겠다.

댓글남기기