본문으로 건너뛰기

블록과 함수 안의 이름

자바스크립트 목차함수레슨 29 / 76

목차

범위(scope)는 이름이 보이는 곳입니다. let·const로 만든 이름은 그 이름을 만든 중괄호 덩어리 안에서만 살고, 함수 안에서 만든 이름은 그 함수 칸에서만 삽니다. 지금 칸에 없는 이름은 바깥 칸으로 나가며 찾습니다.

함수 안과 밖, 덩어리 안과 밖에서 같은 이름을 쓰면 안에서 다시 붙인 값이 밖에도 남는지 헷갈립니다. 이름이 칸마다 따로 있다는 것을 알면 결과를 맞히고, 오류가 난 까닭도 설명할 수 있습니다. 이 레슨에서는 이름이 사는 곳과, 이름을 찾는 차례를 규칙 7로 세웁니다.

파이썬을 배웠어도 이 레슨은 건너뛰지 말고 보세요. 덩어리 안의 이름과 이름을 찾는 차례가 파이썬과 다릅니다.

11단계 중 1단계. 덩어리 안과 밖에서 같은 이름 count를 쓰는 코드입니다.

1덩어리 안의 let 이름은 그 안에서만 산다

그림의 첫 장면 코드입니다.

let count = 10;
if (count > 5) {
  let count = 1;
}
console.log(count);
실행 결과
10

1번 줄은 전역 칸에서 이름 count를 10에 붙입니다. 2번 줄의 조건 count > 5는 이 count로 계산해 true이고, 실행이 중괄호 덩어리로 들어갑니다. 3번 줄의 let은 덩어리 안에 새 이름 count를 만들어 1에 붙입니다. 그림은 이 이름을 전역 칸 안의 작은 칸, 곧 블록 칸에 그립니다. 이름은 같아도 1번 줄의 count와는 다른 이름표입니다. 4번 줄에서 덩어리가 끝나면 블록 칸이 안쪽 count와 함께 사라집니다(규칙 4). 그래서 5번 줄의 count는 전역 칸의 count이고, 10이 찍힙니다.

2함수 안 이름은 그 칸에서만 산다

함수 안에서 만든 이름은 그 함수 칸에서만 삽니다. 함수 안에서 바깥과 같은 이름을 let으로 만들면 무엇이 찍힐까요? 답을 정한 뒤 펼쳐 보세요.

let count = 10;
function add() {
  let count = 1;
  count = count + 1;
  console.log(count);
}

add();
console.log(count);
실행 결과 보기
실행 결과
2
10

예상과 다르면 그곳이 배울 곳입니다. 8번 줄이 add를 부르면 새 칸이 생기고, 3번 줄의 let이 그 칸에 count를 따로 만들어 1에 붙입니다. 4번 줄은 add 칸의 count를 2에 다시 붙이고, 5번 줄이 2를 찍습니다. 함수가 끝나면 칸이 안쪽 count와 함께 사라지고, 9번 줄은 전역 칸의 count가 붙은 10을 찍습니다. 2와 2를 예상했다면, 이름이 같으면 안팎이 같은 이름표라고 본 것입니다.

함수 안에서만 만든 이름을 밖에서 읽으면 그런 이름이 없다는 오류가 납니다.

function tax(won) {
  const t = won / 10;
}

tax(4000);
console.log(t);
실행 결과
main.js:6console.log(t);            ^ ReferenceError: t is not defined    at main.js:6:13

ReferenceError 줄의 t is not defined는 't가 정의되어 있지 않다', 곧 그런 이름이 없다는 뜻입니다. t는 tax 칸에서만 400에 붙어 있다가 칸과 함께 사라졌고, 6번 줄이 찾는 전역 칸에는 t가 없습니다. 함수 안의 값을 밖에서 쓰려면 return으로 돌려받습니다.

3없는 이름은 바깥 칸에서 찾는다

그림의 둘째 장면 코드입니다. 이번에는 함수 안에 let이 없습니다.

let count = 10;
function add() {
  count = count + 1;
}

add();
console.log(count);
실행 결과
11

6번 줄이 add를 부르면 새 칸이 생기지만, 그 칸에는 count가 없습니다. 3번 줄이 count를 찾으면 자바스크립트는 먼저 add 칸에서 찾고, 없으니 add가 만들어진 전역 칸으로 나가 찾습니다. 그래서 3번 줄은 전역 count를 11에 다시 붙이고, 7번 줄이 11을 찍습니다.

규칙 7. 이름은 지금 칸에서 찾고, 없으면 그 함수가 만들어진 칸으로 나가며 찾습니다.

블록 칸도 같습니다. 블록 칸에 없는 이름은 그 덩어리를 감싼 칸에서 찾습니다. 합 모으기와 세기에서 반복 덩어리 안의 total = total + n;이 반복 밖의 total을 다시 붙인 것도 이 차례입니다. 이름을 새로 만드는 것은 let·const뿐이라(규칙 2), let 없이 쓴 count = …는 찾아 나온 그 이름을 다시 붙입니다.

예제

넘기고 돌려받기

함수 안에서 바깥 이름을 바로 다시 붙이면, 이름이 어디서 바뀌는지 함수 몸까지 읽어야 알 수 있습니다. 값을 인자로 넘기고 돌려받은 값에 바깥 이름을 다시 붙이면 바뀌는 곳이 한 줄로 보입니다.

function add(n) {
  return n + 1;
}

let count = 10;
count = add(count);
console.log(count);
실행 결과
11

6번 줄은 전역 count가 붙은 10을 넘기고, 돌려받은 11에 전역 count를 다시 붙입니다. 함수는 받은 값과 자기 칸의 이름만 써서, 무엇이 바뀌는지 6번 줄 하나로 보입니다.

자주 틀리는 곳

틀린 생각: 덩어리 안이나 함수 안에서 만든 이름도 밖에서 쓸 수 있다.

중괄호는 줄을 묶는 표시일 뿐이라, 그 안에서 만든 이름도 파일 어디서나 보일 것 같습니다. 1절의 블록 칸과 2절의 t처럼, 안에서 만든 이름은 덩어리나 칸이 끝나면 사라집니다.

맞는 규칙: let·const 이름은 그 중괄호 덩어리 안에서만, 함수 안 이름은 그 칸에서만 삽니다. 값을 밖에서 쓰려면 밖에서 이름을 만들거나 return으로 돌려받습니다(규칙 4·5).

틀린 생각: 이름이 같으면 안팎 어디서나 같은 이름표다.

같은 글자로 쓴 이름이니 하나의 이름표처럼 보입니다. 2절 맞혀 보기처럼 함수 안에서 let으로 만든 count는 바깥 count와 다른 이름표라, 안에서 다시 붙여도 바깥은 그대로입니다.

맞는 규칙: let·const는 지금 칸에 새 이름을 만듭니다. 안쪽에 같은 이름이 있으면 그 안에서는 안쪽 이름을 먼저 찾고, 바깥 이름은 가려질 뿐 그대로입니다(규칙 7).

틀린 생각: 함수 안에서 let 없이 이름을 붙이면 함수 칸에 새 이름이 생긴다.

함수 안은 따로 칸이니 그 안에서 붙인 이름은 다 함수 칸 것일 것 같습니다. 3절처럼 let 없는 count = count + 1;은 바깥 칸으로 나가 찾은 전역 count를 다시 붙입니다.

맞는 규칙: 이름을 만드는 것은 let·const뿐입니다. let 없이 붙이면 지금 칸부터 바깥으로 찾아 나온 그 이름을 다시 붙입니다(규칙 2·7).

틀린 생각: 반복이 끝나도 반복 변수 i는 마지막 값에 붙은 채 남는다.

반복이 마지막 값까지 셌으니 그 값이 남아 있을 것 같습니다. for의 세 칸에서 본 것처럼 for (let i …)의 i도 1절의 블록 칸처럼 반복 덩어리의 칸에서만 살아, 반복이 끝나면 함께 사라집니다.

맞는 규칙: 반복 머리에서 let으로 만든 이름은 반복 안에서만 삽니다. 반복 뒤에 쓰면 ReferenceError가 나니, 끝난 뒤에 쓸 값에는 반복 밖에서 만든 이름을 붙여 둡니다(규칙 4).

확인 문제

1. 2절 맞혀 보기 코드의 4번 줄을 count = count + 5;로 바꿨습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.

let count = 10;
function add() {
  let count = 1;
  count = count + 5;
  console.log(count);
}

add();
console.log(count);
힌트

5번 줄과 9번 줄이 각각 어느 칸에서 count를 찾는지 봅니다.

정답

모두 2줄입니다.

실행 결과
6
10

5번 줄은 add 칸의 count를 찾아 6을 찍습니다. 함수가 끝나면 그 count는 칸과 함께 사라지고, 9번 줄은 전역 칸의 count가 붙은 10을 찍습니다.

흔한 오답은 6과 6입니다. 함수 안의 count가 전역 count와 한 이름표라고 본 것입니다. 3번 줄의 let은 add 칸에 따로 이름을 만듭니다(규칙 7).

2. count에 1을 더하는 함수 add를 써서 count를 바꾸려 합니다. 6번 줄의 빈칸을 채우세요. 5번 줄이 let count = 5;이면 6이, let count = 9;로 바꾸면 10이 찍혀야 합니다.

function add(n) {
  return n + 1;
}

let count = 5;
count = ____;
console.log(count);
힌트

예제의 6번 줄처럼, 바깥 값을 넘기고 돌려받은 값에 count를 다시 붙입니다.

정답

add(count)입니다. 5번 줄을 let count = 9;로 바꾸면 10이 찍힙니다.

다르게 써도 되는 곳: count + 1도 6과 10을 찍지만 add를 쓰지 않습니다. add(5)나 6은 첫 경우만 맞고, count를 9로 바꿔도 6이 찍힙니다.

흔한 오답은 add()만 쓰는 것입니다. 인자가 없어 n이 undefined에 붙고, undefined + 1이라 NaN이 찍힙니다. 함수에 넘어가는 것은 괄호 안의 값뿐입니다(규칙 5).

3. 시험 점수에 5점씩 더하는 함수를 두 번 불렀습니다. 무엇이 찍힐까요?

let score = 90;
function bonus() {
  score = score + 5;
}

bonus();
bonus();
console.log(score);
힌트

3번 줄의 score가 어느 칸의 이름인지 찾고, 부를 때마다 그 이름이 어느 값에 다시 붙는지 따라갑니다.

정답
실행 결과
100

bonus 칸에는 score가 없어 3번 줄은 bonus가 만들어진 전역 칸의 score를 찾습니다. 6번 줄이 그 score를 95에, 7번 줄이 100에 다시 붙입니다.

흔한 오답은 90입니다. let 없이 붙여도 함수 칸에 새 이름이 생긴다고 본 것입니다. 이름을 만드는 것은 let·const뿐이라, 3번 줄은 찾아 나온 전역 score를 다시 붙입니다(규칙 7). 95라고 썼다면 부를 때마다 몸이 실행되는 것을 놓친 것입니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절 맞혀 보기 코드(아홉 줄)를 main.js에 저장해 돌립니다. 그다음 3번 줄 let count = 1;에서 let을 빼면 두 줄이 무엇으로 찍힐지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. 저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로