for...in 문은 객체의 키를 담은 차례대로 하나씩 꺼내, 바퀴마다 그 키에 이름을 붙이고 덩어리를 실행합니다. Object.keys와 Object.values는 객체의 키와 값을 각각 배열로 모아 돌려줍니다.
for...of로 배열 돌기에서 배열을 for...in으로 돌면 값이 아니라 자리 번호가 글자로 나왔습니다. for...in은 원래 객체의 키를 꺼내는 반복이고, 배열에서는 자리 번호가 키라서 그렇게 보였던 것입니다(객체: 이름 붙은 칸 묶음).
이 레슨에서는 메뉴 객체를 돌며 키와 값을 함께 찍고, for...of로는 객체를 돌 수 없는 까닭과 객체를 배열로 바꿔 도는 길을 봅니다.
1for...in은 키를 하나씩 꺼낸다
그림의 코드입니다.
const menu = {
커피: 4000,
라테: 4500,
};
for (const name in menu) {
console.log(name, menu[name]);
}
커피 4000
라테 45001~4번 줄은 메뉴 객체를 만들고 이름 menu를 붙입니다. 5번 줄 for (const name in menu)는 menu의 키를 하나 꺼내 이름 name을 붙이고, 6번 줄 덩어리를 실행한 뒤 5번 줄로 돌아옵니다.
| 바퀴 | name이 붙는 값 | 6번 줄이 찍는 것 |
|---|---|---|
| 첫째 | '커피' | 커피 4000 |
| 둘째 | '라테' | 라테 4500 |
둘째 바퀴 뒤에는 꺼낼 키가 남지 않아 반복이 끝납니다.
name은 값 4000이 아니라 키 '커피'에 붙습니다. 값은 6번 줄의 menu[name]이 꺼냅니다. 이름이 가리키는 문자열을 키로 쓰는, 객체에서 본 대괄호 꼴입니다.
키는 객체에 담은 차례대로 나옵니다(수처럼 생긴 키만 다릅니다 — 자주 틀리는 곳). 1~4번 줄에 커피, 라테 차례로 적었으니 커피가 먼저입니다. 가나다 차례였다면 라테가 먼저였을 것입니다.
name도 for...of의 item처럼 바퀴마다 새로 만들어져 const로 쓸 수 있고, 그 반복의 중괄호 안에서만 삽니다(규칙 4).
2객체는 for...of로 돌 수 없다
배열을 돌던 for...of에 객체를 주면, 한 바퀴도 돌지 못하고 멈춥니다.
const menu = { 커피: 4000 };
for (const name of menu) {
console.log(name);
}
main.js:2for (const name of menu) { ^ TypeError: menu is not iterable at main.js:2:20menu is not iterable은 'menu는 하나씩 꺼낼 수 있는 값이 아니다'라는 뜻입니다. for...of는 배열·문자열처럼 값을 차례로 내주는 반복 가능한(iterable) 값만 돌고, 중괄호로 만든 객체는 반복 가능한 값이 아닙니다.
객체를 도는 길은 둘입니다. 키를 꺼내는 for...in을 쓰거나(1절), 객체를 배열로 바꾼 뒤 for...of로 돕니다(3절).
3Object.keys와 Object.values는 배열을 돌려준다
Object.keys(menu)는 menu의 키를, Object.values(menu)는 값을 담은 차례대로 모아 새 배열로 돌려줍니다.
const menu = {
커피: 4000,
라테: 4500,
};
const names = Object.keys(menu);
const prices =
Object.values(menu);
console.log(names);
console.log(prices);
[ '커피', '라테' ]
[ 4000, 4500 ]6~7번 줄은 한 문장입니다. 폰 화면에 맞추려고 등호 뒤에서 줄을 나눴습니다.
키 배열의 칸은 모두 글자입니다. 배열이 되었으니 배열에서 쓰던 것을 그대로 씁니다. 키 배열을 for...of로 돌며 키로 값을 꺼내면, 1절의 for...in과 같은 두 줄이 찍힙니다.
const menu = {
커피: 4000,
라테: 4500,
};
const names = Object.keys(menu);
for (const item of names) {
console.log(item, menu[item]);
}
커피 4000
라테 4500칸 수도 배열에서 셉니다. 객체에는 키 length가 없어서 menu.length는 없는 키처럼 undefined이고, 메뉴 수는 Object.keys(menu).length로 셉니다.
키를 나중에 더하면 어느 자리에 올까요? 무엇이 찍힐지 답을 정한 뒤 펼쳐 보세요.
const menu = { 커피: 4000 };
menu['라테'] = 4500;
menu['녹차'] = 3500;
console.log(Object.keys(menu));
실행 결과 보기
[ '커피', '라테', '녹차' ]예상과 다르면 그곳이 배울 곳입니다. 가나다 차례라면 녹차가 맨 앞이겠지만, 키는 담은 차례대로 나와 나중에 담은 녹차가 맨 끝입니다. Object.values(menu)도 같은 차례로 [ 4000, 4500, 3500 ]이라, 두 배열의 같은 자리에는 같은 메뉴의 키와 값이 있습니다.
예제
가장 비싼 메뉴 찾기
가장 큰 값 찾기의 틀을 메뉴 객체에 씁니다. 가격뿐 아니라 어느 메뉴인지도 찍어야 하므로, 가장 좋은 값지기 best를 가격이 아니라 키에 붙입니다.
const menu = {
커피: 4000,
라테: 4500,
쿠키: 2000,
};
const names = Object.keys(menu);
let best = names[0];
for (const name of names) {
if (menu[name] > menu[best]) {
best = name;
}
}
console.log(best, menu[best]);
라테 45006번 줄에서 키 배열을 만들고, 7번 줄이 best를 첫 키 '커피'에 붙입니다. 9번 줄은 키 글자끼리 견주지 않고, 키로 꺼낸 가격 menu[name]과 menu[best]를 견줍니다. 더 비싸면 10번 줄이 best를 그 키로 바꿉니다.
반복이 끝나면 best는 키 '라테'에 붙어 있고, 13번 줄이 가격을 menu[best]로 다시 꺼내 함께 찍습니다.
자주 틀리는 곳
틀린 생각: 배열을 for...in으로 돌면 값이 나온다.
파이썬의 for x in 리스트는 값을 꺼내서, for...in도 배열의 값을 꺼낼 것 같습니다. 그러나 for...of로 배열 돌기에서 본 대로 나오는 것은 자리 번호 글자입니다. 까닭은 1절과 같습니다. for...in은 키를 꺼내고, 배열의 키는 자리 번호입니다.
const prices = [4000, 4500];
for (const p in prices) {
console.log(p, prices[p]);
}
0 4000
1 4500p는 자리 번호 글자 '0', '1'에 붙고, 값은 prices[p]처럼 그 키로 꺼내야 나옵니다.
맞는 규칙: 배열의 값은 for...of로 꺼냅니다. for...in은 객체의 키를 꺼내는 반복이고, 배열에서는 자리 번호가 키라서 번호가 글자로 나옵니다.
틀린 생각: 객체도 for...of로 키나 값을 꺼낼 수 있다.
배열과 문자열을 돌던 반복이라 객체도 돌 것 같습니다. 그러나 중괄호로 만든 객체는 반복 가능한 값이 아니라서 menu is not iterable이라는 TypeError로 멈춥니다(2절).
맞는 규칙: 객체의 키는 for...in으로 꺼냅니다. for...of로 돌려면 Object.keys나 Object.values로 배열을 먼저 만듭니다.
틀린 생각: 반복 안에서 menu.name이라고 쓰면 name이 붙은 키의 값이 나온다.
name이 키를 가리키니 점으로 꺼내도 될 것 같습니다. 그러나 점 뒤의 글자는 키 글자 그대로라서, menu.name은 키 'name'을 찾습니다. 객체 레슨 예제의 menu.pick과 같은 실수입니다(객체: 이름 붙은 칸 묶음).
const menu = {
커피: 4000,
라테: 4500,
};
for (const name in menu) {
console.log(name, menu.name);
}
커피 undefined
라테 undefined맞는 규칙: 이름이 가리키는 키로 꺼낼 때는 대괄호를 씁니다. menu[name]은 name이 붙은 글자 '커피'를 키로 쓰고, menu.name은 글자 'name'을 키로 씁니다.
틀린 생각: 키는 언제나 담은 차례대로 나온다.
키 대부분은 담은 차례대로 나와서, 늘 그럴 것 같습니다. 그러나 '10', '2'처럼 배열의 자리 번호가 될 수 있는 키는 담은 차례와 상관없이 작은 수부터 먼저 나옵니다.
const sold = { 커피: 2 };
sold['10'] = 1;
sold['2'] = 3;
console.log(Object.keys(sold));
[ '2', '10', '커피' ]맞는 규칙: 자리 번호가 될 수 있는 키는 작은 수 차례로 먼저 나오고, 나머지 키는 그 뒤에 담은 차례대로 나옵니다.
확인 문제
1. 장바구니에 담은 물건과 수를 찍습니다. 무엇이 찍힐까요? 세 줄을 글자 그대로 쓰세요.
const cart = {
우유: 3,
빵: 5,
};
cart['잼'] = 2;
for (const name in cart) {
console.log(name, cart[name]);
}
힌트
6번 줄의 name이 붙는 것이 키인지 값인지, 5번 줄에서 담은 잼이 몇째로 나오는지 봅니다.
정답
우유 3
빵 5
잼 2name은 담은 차례대로 키에 붙습니다. 1~4번 줄에 적은 우유, 빵 다음에 5번 줄에서 담은 잼이 섭니다. 값은 7번 줄의 cart[name]이 키로 꺼냅니다.
흔한 오답은 줄마다 3 undefined처럼 쓴 것입니다. name이 값에 붙는다고 본 것입니다. name이 3이라면 cart[3]은 없는 키라서 undefined가 됩니다. 빵, 우유, 잼 차례로 썼다면 가나다 차례로 나온다고 본 것입니다.
2. 메뉴 가격을 모두 더해 10500을 찍으려 합니다. 쿠키가 3000이면 11500이 찍혀야 합니다. 빈칸을 채우세요.
const menu = {
커피: 4000,
라테: 4500,
쿠키: 2000,
};
const prices =
________;
let total = 0;
for (const price of prices) {
total = total + price;
}
console.log(total);
힌트
9번 줄의 for...of가 돌 수 있도록, 가격만 모은 배열이 있어야 합니다.
정답
Object.values(menu)입니다.
const menu = {
커피: 4000,
라테: 4500,
쿠키: 2000,
};
const prices =
Object.values(menu);
let total = 0;
for (const price of prices) {
total = total + price;
}
console.log(total);10500Object.values가 가격을 모은 배열을 돌려주고, 9~11번 줄이 그 칸을 차례로 더합니다. 쿠키가 3000이면 배열도 따라 바뀌어 11500이 찍힙니다.
흔한 오답은 Object.keys(menu)입니다. 키 글자를 더하게 되어 0 뒤에 글자가 이어진 0커피라테쿠키가 찍힙니다. 빈칸에 menu를 그대로 썼다면 9번 줄에서 prices is not iterable이라는 TypeError로 멈춥니다. 객체는 for...of로 돌 수 없습니다(2절). 빈칸에 [4000, 4500, 2000]을 바로 쓰면 10500은 찍히지만, 쿠키가 3000이 되어도 10500이 찍혀 조건에 맞지 않습니다. 키 배열에 map을 걸어 키마다 값을 꺼내도((k) => menu[k]) 같은 가격 배열이 되어 맞습니다.
3. 80점 이상인 학생의 이름만 찍으려 합니다. 빈칸을 채우세요.
const scores = {
민지: 90,
철수: 75,
영희: 85,
};
for (const name in scores) {
if (________ >= 80) {
console.log(name);
}
}
힌트
name은 키에 붙습니다. 80과 견줄 것은 그 키의 점수입니다.
정답
scores[name]입니다.
const scores = {
민지: 90,
철수: 75,
영희: 85,
};
for (const name in scores) {
if (scores[name] >= 80) {
console.log(name);
}
}민지
영희name은 키 '민지', '철수', '영희'에 차례로 붙고, scores[name]이 그 키의 점수를 꺼내 80과 견줍니다. 80 이상인 민지와 영희만 찍힙니다.
흔한 오답은 scores.name입니다. 키 'name'을 찾아 늘 undefined라서, 조건이 모두 false가 되어 한 줄도 찍히지 않습니다. name만 썼다면 점수가 아니라 이름 글자를 80과 견주게 되어, 이 경우에도 한 줄도 찍히지 않습니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
1절 그림 코드를 main.js에 저장해 돌린 뒤, 5번 줄의 in을 of로 바꿉니다. 무엇이 찍히고 어디서 멈출지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. 5번 줄에서 TypeError로 멈추지 않으면 2절을 다시 봅니다.
저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.