치춘짱베리굿나이스
스코프, 렉시컬 스코프 본문
스코프와 렉시컬 스코프
원래 클로저, 커링도 같이 적으려고 했다가 분량조절 실패로 렉시컬 스코프 부분을 따로 분리했다
스코프
특정 변수에 접근할 수 있는 범위를 의미한다
스코프는 변수를 다른 변수와 구분할 수 있는 규칙이 되며, 같은 이름을 갖더라도 어느 스코프에 속해 있는지에 따라 변수를 구분한다
이름이 같더라도 변수가 속한 스코프에 따라 서로 다른 변수로 인식한다는 뜻이다
function foo() {
let n = 2; // foo 스코프
}
function bar() {
let n = 3; // bar 스코프
}
function baz() {
let n = 4; // baz 스코프
}
위의 예시에서 foo
, bar
, baz
내의 변수 n
은 이름이 모두 같아 언뜻 보면 식별이 불가능할 것 같지만, 속해 있는 범위 (스코프) 가 다르기 때문에 서로 다른 변수로서 동작한다
foo
함수 내에서는 foo
스코프를 갖는 n
만이 참조되고, bar
함수 내에서는 bar
스코프를 갖는 n
만이 참조되는 식이다
101동 202호에서 동현이의 이름을 불렀을 때 101동 203호의 동현이가 대답하지 않는 것과 같은 이치다
전역 스코프, 지역 스코프
let globalVar = 1;
// 전역에서 접근 가능
function foo() {
let localVar = 2;
// 함수 내에서만 접근 가능
console.log(globalVar); // 가능
}
console.log(localVar); // 불가능
이번에는 함수별로 스코프를 따로 두지 않고, 전역 스코프와 지역 스코프로 구분해 보자
위의 예시에서
globalVar
는 최상단 (전역) 에 선언되어 전역 스코프를 갖기 때문에 어디서든 접근 가능하다localVar
는foo
함수 내부에 선언되어 지역 스코프를 갖기 때문에 함수 내부에서만 접근이 가능하다
let n = 1;
function foo() {
let n = 2;
console.log(n);
}
console.log(n); // 1
foo(); // 2
예시를 조금 바꿔 전역 스코프와 지역 스코프의 변수명을 똑같이 해 보았다
같은 이름의 변수를 재선언했으므로 동작이 되지 않아야 할 것 같지만, 잘 동작한다
foo
함수 내에서n
에 접근할 때엔foo
내의n
(지역 스코프의n
) 을 참조한다- 전역 범위에서
n
에 접근할 때엔 전역 스코프의n
을 참조한다
지역 스코프에서도 전역 변수에 접근할 수 있지만, 지역 변수가 우선적으로 호출된다
- 101동 202호에서 동현이의 이름을 부르면 집 밖의 동현이보다 집 안의 동현이가 우선적으로 응답하고,
- 집 밖에서 동현이의 이름을 부르면 집 밖에 있는 동현이가 응답하는 것이라 생각하자
스코프의 중첩
let n = 1;
function foo() {
let n = 2;
function bar() {
let n = 3;
}
}
스코프는 중첩이 가능하며, 최상위 스코프는 전역 스코프가 되고, 하위에 여러 지역 스코프가 중첩된다
상위 스코프의 변수를 하위 스코프에서 참조할 수 있으나, 하위 스코프의 변수는 상위 스코프에서 참조할 수 없다
스코프마다 한쪽 면만 특수 코팅된 유리벽이 막고 있어서, 안쪽에서는 바깥쪽을 볼 수 있지만 바깥쪽에서는 안쪽을 볼 수 없다고 생각하면 된다
각 스코프에서 변수를 호출할 땐, 이름이 같다면 가장 가까이 있는 스코프의 변수를 참조하게 된다
함수 레벨 스코프, 블록 레벨 스코프
#include <stdio.h>
int main(void) {
{
int a = 0;
printf("%d\n", a);
}
{
int b = 0;
printf("%d%d\n", a, b); // 식별자 a가 정의되어 있지 않습니다
}
}
C 계열 언어들은 블록 단위 스코프 (블록 레벨 스코프) 를 따른다
블록 단위 스코프라 함은, 중괄호 ({ }
) 를 기준으로 스코프를 구분하여, 중괄호 내부 영역에 한해서만 유효한 스코프를 뜻한다
위의 코드에서 main
스코프 내부의 상단 스코프에 선언된 a
는 하단 스코프에서 접근할 수 없다
블록 레벨 스코프에서는 모든 중괄호로 나뉘어지는 영역 (if
문, for
문, 함수, 그냥 중괄호 등…) 으로 스코프를 구분한다
function main() {
if (false) {
var n = 1;
}
console.log(n); // undefined 출력, 오류 발생 X
}
console.log(n); // n is not defined
반면 자바스크립트는 함수 단위 스코프 (함수 레벨 스코프) 를 따른다
함수 단위 스코프는 함수가 생성될 때마다 스코프가 구분되며, 함수 내부 영역에 한해서 유효한 스코프를 뜻한다
위의 코드에서는 main
함수가 선언됨과 동시에 독자적인 스코프가 형성되었고, 변수 n
은 함수 내부 어디에서나 접근 가능하므로 if
문 밖에서도 출력이 가능하다 (물론 if
절 내부로 들어가지 못했기 때문에 변수가 선언만 되고 초기화가 되지 않아 undefined
가 출력된다)
함수 레벨 스코프는 함수의 정의 단위로 스코프를 구분한다
자바스크립트에서의 블록 스코프
// 전역
if (false) {
var n = 1;
}
console.log(var);
(var
기준) 자바스크립트에선 블록 단위 스코프를 적용받지 않으므로, 위의 코드에서 n
은 전역 변수로 취급되어 접근이 가능해진다
function main() {
if (false) {
let n = 1;
}
console.log(n); // n is not defined
}
var
은 함수 단위 스코프만을 적용받고 블록 단위 스코프를 적용받지 않으므로 if
, for
절 내부에 있는 변수라도 함수 내부에서 자유롭게 접근이 가능했으나, ES6 이후로 등장한 let
, const
는 블록 단위 스코프를 지원한다
따라서 위의 코드에서는 블록 단위 스코프인 if
절에 의해 n
의 유효 범위가 한정되어 if
절 외부에서 출력을 시도하면 오류가 발생하게 된다
렉시컬 스코프 (어휘적 환경)
함수가 자신이 선언된 영역의 스코프를 따르는 현상을 렉시컬 스코프 (= 정적 스코프) 라고 하며, 자바스크립트에서 따르는 함수 스코핑 방식이다
렉시컬 스코프의 ‘렉시컬 (lexical) 은 ‘어휘적인, 어휘의’ 라는 뜻으로, 여기서 어휘라 함은 자바스크립트 상의 코드를 나타내고, 결국 렉시컬 스코프는 코드 상에서의 위치에 따른 환경이라는 의미가 된다
렉시컬 스코프의 반댓말은 ‘동적 스코프’ 로, 함수가 호출될 때마다 호출된 영역의 스코프를 (동적으로) 따르는 방식이다
예시
var x = 5;
function foo() {
var x = 1;
function bar() {
console.log(x);
}
bar(); // 1
baz(); // 5
}
function baz() {
console.log(x);
}
위의 예시에서 bar
은 foo
스코프 내에 선언되어 있으므로, foo
스코프를 따르고, baz
는 전역 스코프 내에 선언되어 있으므로 전역 스코프를 따른다
따라서 bar
함수는 1을 출력하고, baz
함수는 5를 출력하게 된다
var x = 5;
function foo() {
var x = 1;
function bar() {
console.log(x);
}
bar(); // 1
baz(); // 1
}
function baz() {
console.log(x);
}
반대로 동적 스코핑을 적용하면, bar
과 baz
가 둘 다 foo
함수 스코프 내에서 호출되므로 해당 스코프의 변수인 1을 출력한다
참고 자료
https://velog.io/@fromzoo/함수스코프-vs-블록스코프
'Javascript + Typescript > 이론과 문법' 카테고리의 다른 글
[Typescript] 인덱스 시그니쳐 (0) | 2023.07.14 |
---|---|
이벤트 버블링과 캡쳐링 (0) | 2023.07.13 |
[Violation] took 1000⬆️ms (0) | 2022.09.21 |
이벤트 핸들러 this 오류 (0) | 2022.09.21 |
모듈과 모듈 번들러 (0) | 2022.09.12 |