티스토리 뷰
공부를 시작하며
앞으로 공부할 내용인 클로저, 실행 컨텍스트와 관련이 있으며 이를 이해하는데 어려움이 없도록 공부를 하게 되었습니다.
책 모던 자바스크립트 Deep Dive를 통해 공부하였습니다.
스코프
스코프(scope)는 자바스크립트를 포함하여 다른 프로그래밍 언어에서도 있는 개념이지만 자바스크립트의 스코프는 다른 언어들과 다른 특징이 있습니다. 변수 선언문에서 var의 변수 선언문과 let, const 변수 선언문이 다르게 동작하는것을 하나의 예시로 들 수 있습니다.
다음과 같이 var은 함수 단위의 유효범위를 가집니다.
var test1 = 1;
console.log("1:", test1);
// 출력 : 1
if (true) {
var test1 = 2;
console.log("2:", test1);
// 출력 : 2
}
function foo() {
var test1 = "test";
console.log("3:", test1);
// 출력 : test
function nested() {
var test1 = "test1";
console.log("4:", test1);
// 출력 : test1
}
nested();
}
foo();
console.log("5:", test1);
// 출력 : 2
위의 경우에선 var을 통해 선언된 위치에 따라 값이 어떻게 할당되고 참조되는지 확인할 수 있습니다.
변수는 선언된 위치에 따른 유효한 범위, 다른 코드가 선언된 변수를 참조할 수 있는 범위가 결정됩니다.
변수뿐만 아니라 모든 식별자도 이와 같은데요. 다시 설명하자면 식별자가 자신이 선언된 위치에 의해서 다른 코드가 선언된 식별자를 참조할 수 있는 유효한 범위가 결정되는것을 스코프라고 말합니다.
식별자 결정
다음 예제를 보면 "global"을 할당한 변수 x 와 "local"을 할당한 변수 x가 있습니다.
선언된 두 변수의 이름은 같지만 console.log를 통해서 참조된 변수의 값을 출력해보면 지역에서 할당된 local 이 전역에 있는 x의 값을 변경을 안한것을 확인할 수 있습니다.
var x = "global";
function foo() {
var x = "local";
console.log(x);
// 출력 : local
}
foo();
console.log(x);
// 출력 : global
이처럼 같은 변수 이름이여도 유효 범위에 따라서 자바스크립트 엔진은 어떤 변수를 참조해야 할 것인지 결정하며 이를 식별자 결정(identifiler resolution)이라고 합니다. 스코프는 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있습니다.
코드 문맥
자바스크립트는 엔진은 코드의 실행이 어디서 되는지와 주변의 코드들의 문맥(context)을 고려합니다.
코드가 어디서 실행되고 주변의 코드들이 어떤 것들이 있는지를 렉시컬 환경(lexical environment)이라고 부릅니다.
환경은 실행 컨텍스트에서 구현되어 있으며 스코프는 실행 컨텍스트와 관련이 있습니다.
렉시컬 환경과 실행 컨텍스트에 관련해서는 다음에 다뤄보겠습니다.
스코프가 없다면
위에서 살펴본 예제에서 x는 각기 다른 스코프에서 선언된 변수입니다.
같은 이름이지만 서로 다른 변수를 참조할 수 있도록 구현될 수 있었던 것은 두 변수의 식별자 이름은 같지만 스코프가 다른 별개의 변수로써 동작하기 때문입니다.
스코프라는 개념이 없었다면 해당 프로그램 전체에서는 같은 이름을 가진 변수를 가질 수 없습니다.
식별자는 고유해야하고 고유한 식별자를 통해서 변수를 참조할 수 있습니다. 동일한 이름의 변수명을 사용할 수 있는 것은 다른 스코프일 경우에만 해당하며 스코프를 통해서 변수의 이름 충돌을 방지를 해줍니다.
다음과 같은 var의 예시가 있습니다.
분명 같은 스코프 내에서 동일한 이름의 변수를 선언할 수 없다고 했었는데 예제에서는 후 순위로 선언한 var1의 값이 console.log를 통해서 참조되고 출력되었습니다. 이는 var의 특징때문인데요, var 변수 선언문으로 선언된 변수는 같은 스코프 내에서 중복 선언이 가능합니다. 후 순위에 선언한 변수는 자바스크립트 엔진에 의해서 var 키워드가 사용된 것이 아닌 할당된 것처럼 동작합니다.
function foo() {
var var1 = 1;
var var1 = 2;
console.log(var1);
// 출력 : 2
}
foo();
스코프의 종류
스코프의 종류로는 두가지로 나눌수 있는데, 이는 코드의 구분을 통해서 나눠집니다.
코드는 전역, 지역으로 구분이 가능한데 스코프도 이와 마찬가지로 선언된 변수가 전역에 선언되면 스코프는 전역 스코프로 결정되며, 지역에 선언되면 지역 스코프가 됩니다.
스코프 체인
스코프의 종류는 전역 스코프와 지역 스코프가 있다고 했는데, 위와 같은 사진의 예제에서는 두 지역 스코프가 중첩이 되고 있습니다. 위와 같은 상황이 가능한 이유는 함수는 중첩이 가능하며, 몸체 내부에 정의한 함수를 중첩 함수(nested function)이라고 하며 중첩 함수를 포함하는 몸체 함수를 외부 함수(outer function) 이라고 합니다.
그리고 각 중첩 함수와 외부 함수들은 독자적인 지역 스코프를 가지고 있습니다.
스코프는 함수의 중첩에 의해서 계층적인 구조를 가지며 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라고 합니다.
이와 같이 계층적으로 연결된 것을 스코프 체인(scope chain)이라고 합니다.
스코프 체인에 의해서 변수를 참조할 때 자바스크립트 엔진은 변수를 참조하는 코드의 스코프로부터 상위의 스코프 방향으로 이동하며 선언된 변수를 검색합니다. 이 검색 방식을 통해서 상위 스코프에서 선언된 변수를 하위 스코프에서도 참조할 수 있게 됩니다.
함수 레벨 스코프
var 변수 선언문은 오로지 함수에 의해서만 지역 스코프를 만듭니다.
다음 예제를 살펴보면 함수가 아닌 다른 코드 블록은 지역 스코프가 적용되지 않은것을 확인할 수 있습니다.
var x = 1;
if (true) {
var x = 10;
}
console.log(x);
// 출력 : 10
var i = 10;
for (var i = 0; i < 5; i++) {
console.log(i);
// 출력 : 0 1 2 3 4
}
console.log(i);
// 출력 : 5
이러한 var의 변수 선언문의 함수 블록 레벨 스코프는 의도치 않은 변수의 값을 참조할 수도 있는 오류를 발생시킬 가능성이 높습니다.
이와 관련된 내용은 여기를 통해서 확인하시면 더욱 자세히 확인 가능합니다.
결론은 var 변수 선언문의 이런 특징을 피하고 싶다면 ES6에서 도입된 let 과 const 변수 선언문을 사용하는 것을 추천드립니다. var 변수 선언문과는 달리 let과 const 변수 선언문은 모든 코드 블록의 스코프를 보장해줍니다.
렉시컬 스코프
스코프의 방식 중에는 동적 스코프(dynamic scope)와 렉시컬 스코프(lexical scope)가 있습니다.
동적 스코프는 함수를 어디서 호출 했는지에 따라서 함수의 상위 스코프를 결정하는 방식이고,
렉시컬 스코프는 함수를 어디서 정의 했는지에 따라 함수의 상위 스코프를 결정하는 방식이며 정적 스코프(static scope)라고도 불립니다.
이 중 자바스크립트는 렉시컬 스코프를 따릅니다. 이는 함수의 호출된 위치는 상위 스코프 결정에 영향을 미치지 않으며, 함수가 어디서 정의되었는지에 따라서 상위 스코프를 결정합니다. 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정되고 생성된 함수 객체는 결정된 상위 스코프를 기억합니다. 이는 함수가 호출될 때마다 함수의 상위 스코프를 참조할 필요가 있기 때문입니다. 렉시컬 스코프는 클로저와 관련이 있으며 다음에 공부를 통해 다뤄보도록 하겠습니다.
다음 예시를 보면 렉시컬 스코프의 동작을 확인할 수 있습니다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo();
// foo 함수를 실행한 것으로 인한 출력 : 1
bar();
// bar 함수를 실행한 것으로 인한 출력 : 1
전역변수로 x를 선언하고 1을 할당합니다. 그리고 foo 함수 내부 지역 변수로 x 를 선언하고 10을 할당합니다.
그리고 bar 함수를 선언하여 x를 참조하고 console.log를 통해 출력하며 foo 함수 하단에 bar 함수를 실행시켜줍니다.
그리고 하단에 foo와 bar 함수를 차례로 실행시켜줍니다.
두 실행을 통한 출력 결과는 동일하게 1이 출력이 되며 이는 bar 함수의 정의가 전역 스코프이므로 상위 스코프가 더 이상 없어 해당 스코프 내에 선언된 x의 변수를 참조하게 됩니다.
정리해볼까요?
Q. 스코프란 무엇인가요?
A. 스코프란 선언된 식별자가 자신이 선언된 위치에 의해서 다른 코드가 선언된 식별자를 참조할 수 있는 유효 범위가 결정되는 것을 스코프라고 합니다.
Q. 렉시컬 스코프는 무엇인가요?
A. 함수 또는 코드 블록으로 정의된 위치로부터 상위 스코프를 결정하는 방식을 말합니다. 또한 함수가 호출된 위치가 상위 스코프에 결정에 영향을 미치지 않습니다.
출처
책 : 모던 자바스크립트 Deep Dive
'웹 개발자 > 자바스크립트' 카테고리의 다른 글
[자바스크립트] "이것" this (0) | 2022.08.27 |
---|---|
[자바스크립트] 엄격 모드 strict mode 란? (0) | 2022.08.25 |
[자바스크립트] var 을 통해 바라보는 let과 const 변수 선언문 (0) | 2022.08.19 |