스코프 — 변수가 사는 범위
Article
3강에서 이런 코드를 보여주고 설명을 미뤘다.
if (true) {
var a = "var 로 선언";
let b = "let 으로 선언";
}
console.log(a);
console.log(b);
- var 로 선언
- ReferenceError: b is not defined
이제 왜 이런지 안다. 스코프, 즉 변수가 살아 있는 범위 때문이다.
안에서는 밖이 보인다
가장 중요한 규칙 하나부터 잡는다.
const name = "지원";
function greet() {
console.log(name);
}
greet();
- 지원
함수 안에서 바깥 변수를 읽었다. 잘 된다.
반대는 안 된다.
function greet() {
const message = "안녕";
}
greet();
console.log(message);
- ReferenceError: message is not defined
message는 함수 안에서 태어나 함수가 끝나면 사라진다. 바깥에서는 존재조차 몰랐다.
스코프 체인
중첩되면 어떻게 될까. 안쪽에서 변수를 찾으면 자기 자리부터 시작해 바깥으로 한 칸씩 나가며 찾는다.
const level = "전역";
function outer() {
const outerVar = "바깥 함수";
function inner() {
const innerVar = "안쪽 함수";
console.log(innerVar);
console.log(outerVar);
console.log(level);
}
inner();
}
outer();
- 안쪽 함수
- 바깥 함수
- 전역
inner가 level을 찾는 경로를 그리면 이렇다.
- 전역 스코프
- outer 스코프
- inner 스코프
- outer 스코프
inner 안에 없으면 outer를 보고, 거기도 없으면 전역을 본다. 전역에도 없으면 그때 ReferenceError가 난다.
- 01
inner 스코프를 뒤진다
innerVar밖에 없다. 못 찾았다. - 02
한 칸 밖, outer 스코프로
outerVar밖에 없다. 여전히 못 찾았다. - 03
또 한 칸 밖, 전역 스코프로
level을 찾았다. 값을 가져와 쓴다.console.log(level); // "전역"
바깥으로만 나간다. 형제 함수의 스코프는 절대 들여다볼 수 없다.
섀도잉 — 같은 이름이 겹치면
안쪽에서 같은 이름을 다시 선언하면, 바깥 것이 가려진다.
const name = "바깥";
function show() {
const name = "안쪽";
console.log(name);
}
show();
console.log(name);
- 안쪽
- 바깥
함수 안에서는 자기 것이 먼저 잡히니 "안쪽"이 나온다. 함수 바깥의 name은 멀쩡하다. 덮어쓴 게 아니라 가린 것이다.
블록 스코프와 함수 스코프
이제 맨 위 코드로 돌아간다. let과 const는 중괄호 하나하나를 스코프로 친다. var는 함수만 스코프로 친다.
let · const
블록 스코프
{ }안에 갇힌다if,for, 그냥 중괄호 전부- 밖에서 접근하면 에러
- 범위가 좁아 예측하기 쉽다
var
함수 스코프
- 함수
{ }만 경계로 본다 if,for의 중괄호는 무시한다- 블록 밖으로 새어나온다
- 의도치 않은 곳에서 살아남는다
for문에서 차이가 극명하게 드러난다.
for (var i = 0; i < 3; i++) {}
console.log(i);
for (let j = 0; j < 3; j++) {}
console.log(j);
- 3
- ReferenceError: j is not defined
var i는 반복이 끝난 뒤에도 살아 있고, 값은 3이다. 조건을 거짓으로 만든 그 값이 그대로 남았다. let j는 반복문과 함께 사라진다.
반복문 카운터가 밖에서 살아 있을 이유가 없다. let이 맞는 동작이다.
전역 스코프를 더럽히지 않는다
어디에도 속하지 않은 최상단이 전역 스코프다. 여기 선언한 변수는 어디서나 보인다. 편해 보이지만 위험하다.
let count = 0;
function addItem() {
count++;
}
function resetCart() {
count = 0;
}
count를 누가 언제 바꿨는지 추적하기 어렵다. 파일이 커지면 이름이 겹칠 위험도 생긴다.
더 나쁜 경우도 있다. 선언 키워드를 빠뜨리면 전역 변수가 만들어진다.
function setName() {
userName = "지원";
}
setName();
console.log(userName);
- 지원
함수 안에서 만들었는데 바깥에서 읽힌다. let도 const도 없이 그냥 대입했더니 자바스크립트가 전역에 만들어버렸다. 오타 하나가 조용히 전역 변수를 만드는 셈이다.
스코프를 좁게 쓴다
원칙은 단순하다. 변수는 필요한 가장 좁은 범위에 둔다.
- 01
쓰는 곳 가까이에서 선언한다
맨 위에 몰아 선언하지 않는다. 처음 쓰는 자리 바로 위에 둔다.
- 02
기본은 const, 바뀌면 let
3강에서 정한 규칙 그대로다.
var는 쓰지 않는다.for (const item of items) { ... } - 03
전역 변수는 마지막 수단으로
함수 사이에 값을 나눠야 한다면 인자와 반환값으로 주고받는다.
정리하면
- 안에서 밖은 보이고, 밖에서 안은 보이지 않는다.
- 변수를 찾을 때는 자기 스코프부터 바깥으로 한 칸씩 나간다. 이 경로가 스코프 체인이다.
- 안쪽에서 같은 이름을 선언하면 바깥 것이 가려진다. 덮어쓰는 게 아니다.
let과const는 중괄호를,var는 함수를 경계로 삼는다.- 선언 키워드를 빠뜨리면 전역 변수가 만들어진다.
"use strict"로 막는다. - 변수는 가장 좁은 범위에 둔다.
다음 강의에서는 미뤄둔 호이스팅을 본다. 11강에서 함수 선언문만 위에서 불렸던 이유, 그리고 var가 왜 그렇게 이상하게 구는지가 한꺼번에 풀린다.