1월 23일 여정 15일차이다.
var x = 'global';
function foo () {
var x = 'function scope';
console.log(x);
}
foo(); // ?
console.log(x); // ?
여기 '?'의 출력값 정답은 무엇일까?
위의 코드에 정답이 갑자기 궁금해졌다. 물론 그냥 VSCode에 실행시켜 보면 답이 나오지만,
더 깊게 이해하고 싶어졌다.
MDN Web docs와 항해99 문서를 보았지만 생각보다 설명이 부족했다. 그리하여 다른 문서를 검색해보기로 했다.
일단 스코프란?
스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙이다.
한마디로 자바스크립트 내에서 식별자(변수 혹은 어떤 대상과 다른 대상)를 찾아내기 위해 만든 것이다.
기본적으로 자바스크립트 스코프를 구분하면 이러하다.
전역 스코프 (Global scope)
코드 어디에서든지 참조할 수 있다.
지역 스코프 (Local scope or Function-level scope)
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.
이제는 변수의 시점에서 스코프를 보자.
전역 변수 (Global variable)
전역에서 선언된 변수이며 어디에든 참조할 수 있다.
지역 변수 (Local variable)
지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.
간단하게 말해서 전역에서 선언된 변수는 전역 스코프를 가지게 되며, 지역에서 선언된 변수는 지역 스코프를 가지게 된다.
또한 전역 변수는 어디서든 사용 가능하며, 지역 변수는 그 지역 혹은 지역의 하부에서 참조할 수 있다.
#include <stdio.h>
/* global variable declaration */
int g;
int main () {
// local variable declaration
int a, b;
// actual initialization
a = 10;
b = 20;
g = a + b;
printf ("value of a = %d, b = %d and g = %d\n", a, b, g);
return 0;
}
갑자기 C언어가 등장해서 놀랐을 것이다. 사실 C-family language는 전부 블록 레벨 스코프라는 것을 따른다.
(블록 레벨 스코프: 블록({...})내에서 유효한 스코프를 의미한다.)
그러나 자바스크립트는 조금 특별하다. 자바스크립트는 함수 레벨 스코프를 따른다.
(함수 레벨 스코프: 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.)
물론, ES6부터는 let keyword를 사용하면 블록 레벨 스코프가 가능하다고 한다.
var 키워드로 전역 변수를 선언한 경우, 전역 객체 window의 프로퍼티가 된다. 그래서 이름이 중복되거나, 의도치 않은 재할당에 의해 코드를 예측하기 어렵게 만들수도 있다. 그러므로 사용하는 것을 억제해야 한다.
if (true) {
var x = 5;
}
console.log(x);
위에 x가 함수 내에서 선언되었다 할 지라도 결국 자바스크립트는 모두 전역 스코프를 갖게된다.
즉 x는 5가 출력된다.
var x = 10;
function foo(){
var x = 100;
console.log(x);
function bar(){ // 내부함수
x = 1000;
console.log(x); // ?
}
bar();
}
foo();
console.log(x); // ?
중첩 스코프 같은 경우 가장 가까운 지역을 우선 참조한다.
즉 맨 위의 x는 100이 되고, 그 밑에 x는 1000이 되며, 맨 마지막 x는 10이 된다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
위의 코드를 보면 매우 헷갈릴 것이다.
여기서 2가지로 나누어진다. 공통적으로는 bar의 상위 스코프를 봐야한다. 왜냐하면 그 상위 스코프에 따라 값이 결정되기 때문이다.
함수를 어디서 호출 하였는가?
=> 이것의 경우, foo함수가 bar의 상위 스코프가 된다. 그러므로 x값은 10이다.
함수를 어디서 선언하였는가?
=> 이것의 경우, bar의 스코프는 전역이 된다. 그러므로 값은 1이 된다.
첫번째 방식을 동적 스코프라고 한다. 동적으로 호출한 위치에 따라 변하는 것이다.
두번째는 정적 스코프 또는 렉시컬 스코프라고 한다.
자바스크립트는 정적 스코프이다. 물론 다른 대부분의 언어들도 렉시컬 스코프이다.
자, 그럼 답이 보이기 시작할 것이다. 함수의 선언 위치는 전역이다. 그러므로 전역 변수인
1이 2번 출력된다.
var x = 10; // 전역 변수
function foo () {
// 선언하지 않은 식별자
y = 20;
console.log(x + y);
}
foo(); // 30
위 코드를 보면 무엇인가 이상하다. y는 선언하지 않은 식별자다.
다른 프로그램 언어의 경우 에러가 뜬다. 그러나 자바스크립트는 마치 선연된 변수처럼 동작한다.
과정은 이러하다.
- foo 함수 호출
- y에 값 할당을 위해 스코프 체인을 통해 선언된 변수인지 확인
- 이때 y의 선언이 없으므로 에러가 떠야 정상이지만 자바스크립트는 window.y = 20으로 저장한다.
바로 이러한 현상을 암묵적 전역이라고 한다.
이처럼 오늘 스코프에 대해 자세하게 알아보았다. 전역 스코프, 지역 스코프 그리고 전역 변수와 지역 변수, 자바스크립트는 렉시컬 스코프라는 것과 암묵적 전역.
복잡하지만 잘 기억해둘 필요가 있다.
자 그럼 맨 처음으로 가서 질문에 답을 할 수 있을 것이다.
자바스크립트는 함수 레벨 스코프이다. 즉 foo를 실행 시키면 'function scope'가 출력된다.
그렇지만 콘솔에 x를 출력해보면 'global'이 나올것이다.
스코프에 대해 완벽하게는 아니지만, 그동안 헷갈렸던 개념이 바로 잡혀가고 있다.