스코프란 식별자의 유효 범위를 말한다.
선언된 변수는 어느 범위까지 유효할까? 어디서나 접근이 가능하면 안될까? 스코프가 필요한 이유는 아래와 같다.
let global = 30;
function scope() {
let local = 21;
console.log(global);
};
scope(); // 30
console.log(local); // Uncaught ReferenceError: local is not defined
scope
라는 함수의 내부에선 변수 global
의 값을 참조할 수 있다. 모든 코드 블럭에서 접근하여 값을 알아낼 수 있는 변수를 전역 변수라고 한다.
반면 외부에서 변수 local
에 접근하여 값을 콘솔창에 출력하려니 참조 오류(Reference Error)가 발생한다.
이처럼 코드 블럭 안에서 선언되어 전역에서 접근할 수 없는 변수를 지역 변수라고 한다.
블록, 함수 레벨 스코프는 참조 가능한 유효 범위의 경계를 조건문, 반복문을 포함한 코드 블럭{ }
으로 할지, 오직 함수의 코드 블럭으로 할지의 차이이다. 성에 비유하자면, 어떤 것을 성벽으로 삼을지 정하는 것과 같다.
성벽 안에서 쉽게 바깥을 관측할 순 있지만 반대는 어려운 것 처럼, 스코프 내부에서 바깥을 참조하는 것은 가능하지만 전역에서 스코프 내부의 값을 참조하는 것은 불가능하다.
var
로 선언된 변수와 함수는 함수 내부만 스코프로 인정해주며, 다른 코드 블럭의 변수와 함수는 전역 변수, 전역 함수로 간주한다.
function sayHi(name) {
if (name) {
var greet = `Hi, ${name}!`;
}
console.log(greet);
}
sayHi('Wonkook');
// "Hi, Wonkook!"
위와 같이 console.log
명령어가 조건문 바깥에 있음에도 greet
의 값을 참조할 수 있는 것을 확인할 수 있다.
ES6부터 등장한 let
과 const
로 선언된 변수는 블록 레벨 스코프를 지원한다.
function sayHi(name) {
if (name) {
let greet = `Hi, ${name}!`;
}
console.log(greet);
}
sayHi('Wonkook');
// Uncaught ReferenceError: greet is not defined
함수 레벨 스코프와 달리 조건문을 포함한 모든 실행 블럭을 스코프로 간주하기 때문에 greet
을 참조할 수 없게 된다.
스코프는 필요한 영역에 한정하여 유효 범위가 좁을수록 좋다.
높은 망루에선 낮은 위치의 성채 내부와 바깥에 뭐가 있는지 관찰(참조)할 수 있다. 그러나 성벽에 시야가 가리기 때문에 바깥에서 내부를 관측할 수 없다. 함수의 코드 블럭은 성벽과 같다. 코드 블럭 안쪽에 있을 수록 바깥을 관측할 순 있지만 코드 블럭 바깥에서 내부의 변수를 참조할 순 없다.
블록 스코프에서 지역 변수 y
는 전역 변수 x
값을 참조할 수 있고, 반환되는 함수의 지역 변수z
는 y
의 값을 참조할 수 있다.
높은 망루(내부 코드 블럭)에 있더라도 완전히 다른 코드 블럭에 속해있는 지역 변수를 참조할 순 없다. 코드 블럭의 실행은 독립적이며 실행이 끝나면 더 이상 참조할 수 없기 때문에 더욱이 다른 위치에서 참조할 수 없다.
자신이 속해있는 지역의 변수들을 참조할 수 있게 되며, 해당 코드 레벨에 참조값이 없다면 상위 레벨의 스코프로 참조 값을 찾아 나가는 현상을 스코프 체인(Scope Chain)이라고 한다. 전역 스코프에도 참조값이 없다면 null
을 반환하게 된다.
자바스크립트는 렉시컬 스코프 원칙을 따른다. 렉시컬 스코프는 또 다른 말로 정적 스코프(Static Scope)라고 한다.
렉시컬 스코프란 함수를 호출한 곳이 아닌 선언한 곳을 기준으로 스코프를 결정하는 원칙이다.
let greet = 'Hello';
function sayHi() {
let greet = 'Hi';
print();
}
function print() {
console.log(greet);
}
sayHi(); // 예상: "Hi" | 출력값: "Hello"
print(); // 예상: "Hello" | 출력값: "Hello"
print
는 sayHi
코드 블럭 내부에서 호출되기 때문에 가장 가까운 지역 변수 greet
의 값 'Hi'
를 참조할 것 같지만, 전역 변수 greet
의 값 'Hello'
를 참조하고 있다.
만약 동적 스코프(Dynamic Scope)를 따랐다면, print
속의 greet
은 자신을 호출한 함수 sayHi
내부의 greet
값을 참조했을 것이다.
글과 이미지
Wonkook Lee ⓒ All Rights Reserved
🙏🏻 잘못된 정보가 있다면 지적해주세요
안녕하세요. 글 잘 읽고 많은 도움을 받아갑니다. JavaScript를 공부하는 학생으로서 궁금한 점이 한 가지 있는데 스코프체인 부분에서 전역 스코프에도 참조값이 없다면 null을 반환한다고 적혀있는데, 일반적으로 참조값이 없다면 레퍼런스 에러를 발생시키는게 아닌가 하는 궁금증에 댓글을 남기게 되었습니다.