스코프는 간단하게 생각하면 유효범위라고 할 수 있습니다. 자바스크립트 뿐만 아니라 모든 프로그래밍 언어에서 중요하게 여기는 개념입니다.
특히 자바스크립트는 변수, 함수(사실은 객체), 객체 내부에서 서로 다른 범위를 가지는 데이터들이 존재하기 때문에 잘 알고 있어야 하는 개념입니다.
스코프를 좀 더 자바스크립트 관점에서 설명한다면, 자바스크립트의 모든 식별자는 자신이 선언된 '위치' 에 의해 다른 코드가 식별자를 참조할 수 있는 유효범위를 스코프라 정의합니다. 즉, 식별자의 유효범위입니다.
var x = 'global'
function foo() {
var x = 'local'
console.log(x)
}
console.log(x)
당연한 이야기처럼 들릴 수 있지만, foo
의 블럭 내부에서 선언된 변수의 범위는 해당 코드 블럭 내부이고, 전역에서 선언된 변수의 범위는 전역에서 사용이 됩니다.
따라서 위 코드는 global
이 출력됩니다.
코드 블럭 내부에서 선언된 변수와 가장 바깥에서 선언된 변수가 있습니다.
각각 지역 변수 (local scope) 와 전역변수 (global scope) 로 정의합니다.
이 때 '지역(local)'이란 함수 내부를 의미합니다.
let x = 'global x'
let y = 'global y'
function outer() {
let z = 'outer's local z'
console.log(x)
console.log(y)
console.log(z)
function inner() {
let x = 'inner's local x'
console.log(x)
console.log(y)
console.log(z)
}
inner()
}
outer()
console.log(x)
console.log(y)
es6가 상용화 되면서 거의 사용하지 않는 var 키워드는 블록 레벨 스코프를 지원하지 않는다.
즉, 스코프를 분류하는 기준이 코드 블럭이 아니라, 함수 몸체에 의해서만 지역 스코프가 생성된다.
var x = 10
if(true){
var x = 1
console.log(x) // 1
}
console.log(x) // 1 -> 10을 예상했는데...
위같은 문제를 겪을 수 있다.
그래서 es6에선 const, let 키워드를 통해 블록 레벨 스코프를 지원하고 있다.
outer와 inner 내부에서 선언한 x는 함수 몸체 내부에서만 참조가 가능하고 함수 바깥에서는 전역변수로 선언된 x가 참조된다.
또한 중첩 함수는 바깥 함수에서 선언한 z가 전역 변수로 동작하므로 inner함수에서 z를 출력하면 outer함수에서 선언한 변수가 참조된다.
이렇게 스코프가 함수의 중첩에 의한 계층적 구조를 '스코프 체인'이라 한다.
변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 스코프부터 상위 스코프로 이동하며 해당 변수를 검색한다.
따라서 상위 스코프에 있는 변수를 하위 스코프에서 사용할 수 있다.
var x = 1;
function foo() {
var x = 10
bar()
}
function bar() {
console.log(x)
}
foo()
bar()
난감한 상황이다. 위 예제의 실행 결과는 bar함수의 상위 스코프가 무엇인지에 따라 결정된다.
함수 호출의 위치와 함수 정의 위치에 따라 결정된다.
함수호출에 따라 결정된다면, bar함수의 상위 스코프는 foo함수의 지역 스코프와 전역 스코프이고,
함수를 어디서 정의한지에 따라 결정된다면, bar함수의 상위 스코프는 전역 스코프이다.
이를 각각, 동적 스코프와 렉시컬 스코프라 한다.
자바스크립트는 렉시컬 스코프 환경에서 동작한다. 뿐만 아니라 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.
함수를 어디에서 정의했는지에 따라 상위 스코프가 결정되고, 호출의 위치는 어떠한 영향도 주지 않는다.
따라서, 위 코드의 결과는 모두 1을 출력한다.
bar 함수는 전역에서 정의된 함수이고, 함수 선언문을 사용했기에 전역 코드가 실행되기 전에 먼저 함수 객체를 생성한다. 이때 자신이 정의된 스코프를 기억하고, 해당 함수가 호출되면 자신이 기억하고 있는 스코프를 상위 스코프로 사용한다.
즉, 전역 변수인 var x = 1
을 참조하는 것이다.
전역변수는 반드시 사용해야할 이유가 있을 때 사용하자.
무분별한 사용은 개발자의 실수를 유도하기 너무너무 쉬워진다. 어제 쓴 코드도 100% 이해가 가지 않는 경우도 있는데, 스코프를 신경쓰지 않는 코드를 6개월 후에 봤을 때 100% 이상 이해할 수 있을까 고민해보면 절대 '아니'다. 전역변수 사용을 억제하는 방법이 무엇이 있는지 변수의 특징을 통해 알아보자.
변수는 선언에 의해 생성되고, 할당을 통해 값을 가집니다. 그리고 필요가 없어지면 해제됩니다.
변수가 생성되고 해제되는 과정을 생명 주기 라고 합니다.
전역 변수 는 프로그램이 시작할 때 생성되고, 끝날 때 해제됩니다.
반면에 지역 변수 는 함수가 호출되면 생성되고, 종료하면 해제됩니다.
즉, 전역변수는 프로그램의 생명주기와 같고, 지역변수는 함수의 생명주기와 같습니다.
하지만 지역 변수 중 함수보다 오래 생존하는 경우도 있습니다.
이는 추후 '클로저'를 공부하고 정리하겠습니다.
스코프(전역 스코프, 지역 스코프)에 대해 공부했고, 어떤 기준으로 스코프가 결정이 되는지, 전역 변수를 되도록 지양해야 하는 이유도 알아봤습니다.
해당 개념을 '클로저'에서 어떻게 응용이 되는지 잘 이해해보겠습니다.