본문으로 건너뛰기

단원 연습 문제

자바스크립트 목차웹 페이지 움직이기문제 12개

12단원 '웹 페이지 움직이기'의 아홉 레슨에서 아홉 문제를 내고, 1~11단원에서 세 문제를 섞었습니다. 마지막 두 문제는 코드를 직접 짜 봅니다.

한 번에 다 풀 필요는 없습니다. 문제 1~10은 폰으로도 풀 수 있고(약 30~40분), 끝의 만들어 보기 둘은 컴퓨터에서 Node로 직접 돌려 봅니다. 이 단원을 건너뛰어도 되는지 보려면 문제 2, 4, 6, 8, 10 다섯 문제만 먼저 풀어 보세요(약 15분). 건너뛸지 볼 때는 힌트를 펴지 않고 풉니다. 다섯 문제를 모두 맞히면 13단원으로 넘어가고, 하나라도 헷갈리면 정답의 '다시 볼 레슨'을 먼저 봅니다. 파이썬을 배웠다면 파이썬과 달라지는 곳이 이 다섯 문제에 들어 있습니다(정답의 '파이썬에서는' 줄). 끝의 만들어 보기 둘은 골라서 풉니다.

끝의 두 문제는 프로그램을 직접 만들어 봅니다. 글만 읽고 넘어가지 말고, 컴퓨터에서 작업 폴더의 main.js에 옮겨 적고 터미널에서 node main.js로 돌려 보세요. 실행 결과는 모두 Node 26으로 돌린 것입니다.

문제 1

할 일 번호 id와 필터 이름 f로 선택자 문자열 세 개를 만듭니다. 출력되는 세 줄을 쓰세요.

const id = 3;
const f = 'done';
console.log('#count');
console.log(`.todo.${f}`);
console.log(
  `[data-id="${id}"]`,
);
힌트

id 선택자는 #, class 선택자는 ., 속성 선택자는 [속성="값"] 모양입니다.

정답
실행 결과
#count
.todo.done
[data-id="3"]
  • 3번 줄: id="count"를 찾는 '#count'가 찍힙니다.
  • 4번 줄: todo와 done 두 클래스가 함께 붙은 요소를 찾는 '.todo.done'이 찍힙니다.
  • 5~7번 줄: data-id="3" 속성을 찾는 '[data-id="3"]'이 찍힙니다.

다시 볼 레슨: HTML을 처음 본다면

문제 2

선택자에서 #을 빼먹어 요소를 찾지 못하고 null을 받은 상황을 흉내 낸 코드입니다. 몇 번 줄에서 어떤 이름의 오류가 나는지 쓰세요.

function find(sel) {
  if (sel === '#count') {
    return {
      textContent: '3개',
    };
  }
  return null;
}
const el = find('count');
console.log(el);
el.textContent = '2개';
힌트

9번 줄의 find('count')는 오류 없이 null을 돌려주고, null에 점(.)을 찍어 속성을 고치는 줄에서 오류가 납니다.

정답

11번 줄에서 TypeError가 납니다.

실행 결과
nullmain.js:11el.textContent = '2개';               ^ TypeError: Cannot set properties of null (setting 'textContent')    at main.js:11:16
  • 9~10번 줄: '#count' 대신 'count'를 넘겨 요소를 찾지 못했으므로 el에 null이 들어가고 첫 줄에 null이 찍힙니다.
  • 11번 줄: null에 .textContent를 대입하려다 TypeError가 납니다. 오류는 11번 줄에서 나지만 고쳐야 할 곳은 9번 줄의 선택자('#count')입니다.

파이썬에서는 없는 키를 d['count']로 찾으면 찾는 그 줄에서 바로 KeyError가 나지만 — 브라우저의 querySelector는 못 찾으면 조용히 null을 돌려주고 나중에 null을 쓰는 아랫줄에서 TypeError가 납니다.

다시 볼 레슨: 요소 찾기: querySelector

문제 3

브라우저 전용 객체인 document를 Node(node main.js)에서 바로 읽으려고 합니다. 어떤 이름의 오류가 나는지 쓰세요.

console.log(document);
힌트

Node 환경에는 브라우저의 문서 나무 객체인 document가 정의되어 있지 않습니다.

정답

ReferenceError가 납니다.

실행 결과
main.js:1console.log(document);            ^ ReferenceError: document is not defined    at main.js:1:13
  • document는 웹 브라우저가 만들어 주는 객체이므로 터미널에서 node로 실행하면 ReferenceError(document is not defined)⁠가 납니다.

다시 볼 레슨: 웹 페이지에서 돌리기

문제 4

상태 변수 left를 바꾼 뒤 el.textContent를 찍어 봅니다. 출력되는 두 줄을 순서대로 쓰세요.

let left = 3;
const el = { textContent: '' };
el.textContent =
  `남음 ${left}개`;
left = 1;
console.log(el.textContent);
el.textContent =
  `남음 ${left}개`;
console.log(el.textContent);
힌트

3~4번 줄에서 대입한 문자열은 left = 1로 변수를 바꾼다고 해서 저절로 바뀌지 않습니다(규칙 2).

정답
실행 결과
남음 3개
남음 1개
  • 3~4번 줄: 그 시점의 left 값인 3으로 계산된 '남음 3개'가 el.textContent에 들어갑니다.
  • 5~6번 줄: left를 1로 바꿨지만 el.textContent에 다시 대입하지 않았으므로 첫 줄에는 '남음 3개'가 그대로 찍힙니다.
  • 7~9번 줄: 다시 대입한 뒤에는 '남음 1개'가 찍힙니다.

파이썬과 마찬가지로 — 자바스크립트에서도 화면 요소의 속성에 한 번 대입한 문자열은 나중에 변수를 바꾼다고 저절로 갱신되지 않으므로 직접 다시 대입해야 합니다.

다시 볼 레슨: 글자와 클래스 바꾸기

문제 5

목록을 비우지 않는 addAll()과 비우고 그리는 render()를 차례로 부릅니다. 출력되는 두 줄을 쓰세요.

const todos = ['우유', '산책'];
let dom = [];
function addAll() {
  for (const t of todos) {
    dom.push(t);
  }
}
function render() {
  dom = [];
  for (const t of todos) {
    dom.push(t);
  }
}

이어서 두 함수를 부르고 길이를 찍습니다.

addAll();
addAll();
console.log(dom.length);
render();
console.log(dom.length);
힌트

addAll()을 두 번 부르면 비우지 않고 두 번 덧붙여지고, render()는 비운 뒤 다시 채웁니다.

정답
실행 결과
4
2
  • 비우는 단계가 없는 addAll()을 두 번 불러 2개 뒤에 2개가 더 붙어 4가 찍힙니다.
  • render()는 먼저 비운 뒤 todos의 2개만 다시 넣으므로 2가 찍힙니다.

다시 볼 레슨: 요소 만들어 붙이기 · for...of로 배열 돌기

문제 6

클릭할 때마다 '클릭!'이 찍히도록 onClick에 handle 함수를 등록하려 했는데, 아래 코드는 등록할 때만 한 번 찍히고 정작 click()을 부를 때는 TypeError가 납니다. 11번 줄을 어떻게 고쳐야 할까요? 한 줄만 고쳐, 고친 줄을 쓰세요.

let savedFn = null;
function onClick(fn) {
  savedFn = fn;
}
function click() {
  savedFn();
}
function handle() {
  console.log('클릭!');
}
onClick(handle());
click();
힌트

11번 줄에서 handle() 뒤에 붙은 괄호 () 때문에 함수가 아니라 반환값 undefined가 넘어갔습니다.

정답

11번 줄을 onClick(handle);로 고칩니다.

let savedFn = null;
function onClick(fn) {
  savedFn = fn;
}
function click() {
  savedFn();
}
function handle() {
  console.log('클릭!');
}
onClick(handle);
click();
실행 결과
클릭!
  • onClick(handle())처럼 괄호를 붙이면 등록하는 줄에서 handle이 바로 실행되고 반환값 undefined가 savedFn에 저장됩니다.
  • 괄호를 뺀 onClick(handle);로 적어야 함수 자체가 전달되어 click()을 부를 때 실행됩니다.

파이썬에서도 콜백을 넘길 때 괄호를 빼고 함수 이름을 넘기듯 — 브라우저의 addEventListener에서도 반드시 괄호 없이 함수 자체를 넘겨야 클릭할 때 실행됩니다.

다시 볼 레슨: 이벤트와 리스너

문제 7

수량 입력 칸(qtyIn)과 가격 입력 칸(priceIn)에서 읽은 .value를 다루는 코드입니다. 출력되는 세 줄을 쓰세요.

const qtyIn = { value: '  2 ' };
const priceIn = {
  value: '1500',
};
const qRaw = qtyIn.value.trim();
console.log(
  qRaw + priceIn.value,
);
const total =
  Number(qRaw) *
  Number(priceIn.value);
console.log(total);
console.log(
  '   '.trim() === '',
);
힌트

input.value는 늘 문자열이므로 Number()로 바꾸기 전의 +는 문자열 이어 붙이기가 됩니다.

정답
실행 결과
21500
3000
true
  • 문자열 '2'와 '1500'을 +로 이어 붙여 '21500'이 찍힙니다.
  • Number()로 바꾼 숫자 2 * 1500을 계산해 3000이 찍힙니다.
  • 공백만 있는 문자열을 .trim()하면 빈 문자열 ''가 되어 true가 찍힙니다.

다시 볼 레슨: 폼 제출과 입력 값 · 글자를 수로: Number와 NaN

문제 8

눌린 줄의 li.dataset.id가 '2'일 때 id가 2인 할 일을 지우려 했는데, 아래 코드는 아무것도 지우지 못해 남은 개수가 3으로 나옵니다. 9번 줄을 어떻게 고쳐야 2번 할 일이 지워져 2가 찍힐까요? 한 줄만 고쳐, 고친 줄을 쓰세요.

let todos = [
  { id: 1, text: '우유' },
  { id: 2, text: '복습' },
  { id: 3, text: '산책' },
];
const li = {
  dataset: { id: '2' },
};
const id = li.dataset.id;
todos = todos.filter(
  (t) => t.id !== id,
);
console.log(todos.length);
실행 결과
3
힌트

li.dataset.id는 문자열 '2'이고 t.id는 숫자 2입니다. 9번 줄에서 문자열을 숫자로 바꿔 보세요.

정답

9번 줄을 Number(li.dataset.id)⁠로 바꾸어 id에 대입합니다.

let todos = [
  { id: 1, text: '우유' },
  { id: 2, text: '복습' },
  { id: 3, text: '산책' },
];
const li = {
  dataset: { id: '2' },
};
const id = Number(
  li.dataset.id,
);
todos = todos.filter(
  (t) => t.id !== id,
);
console.log(todos.length);
실행 결과
2
  • li.dataset.id는 늘 문자열 '2'이므로 변환 없이 비교하면 2 !== '2'가 항상 true가 되어 아무 줄도 지워지지 않습니다.
  • Number(li.dataset.id)⁠로 숫자 2로 바꿔야 t.id !== id에서 2번 할 일이 걸러져 남은 개수 2가 찍힙니다.

파이썬과 마찬가지로 자바스크립트의 !==도 숫자와 문자열을 서로 다른 값으로 보며 — 특히 HTML의 dataset 속성은 늘 문자열로 읽히므로 숫자 id와 비교하기 전에 반드시 Number()로 바꿔야 합니다.

다시 볼 레슨: 이벤트 위임과 data 속성 · map·filter·find

문제 9

localStorage에 처음 방문했을 때(null)와 JSON 문자열이 저장되어 있을 때를 처리하는 코드입니다. 출력되는 두 줄을 쓰세요.

function load(raw) {
  const arr = JSON.parse(
    raw ?? '[]',
  );
  return arr.length;
}
const saved = JSON.stringify([
  { id: 1, text: '우유' },
  { id: 2, text: '산책' },
]);
console.log(load(null));
console.log(load(saved));
힌트

raw가 null이면 raw ?? '[]'는 '[]'가 되어 빈 배열이 만들어집니다.

정답
실행 결과
0
2
  • load(null)은 JSON.parse('[]')를 실행해 빈 배열 []의 길이인 0을 돌려줍니다.
  • load(saved)는 항목 두 개가 든 배열을 복원해 길이 2를 돌려줍니다.

다시 볼 레슨: 새로고침해도 남게: localStorage

문제 10

할 일 줄의 클래스 집합을 다루는 toggle(name, force) 함수를 실행합니다. 출력되는 두 줄을 쓰세요.

const cls = new Set(['todo']);
function toggle(name, force) {
  if (force) cls.add(name);
  else cls.delete(name);
}
toggle('done', true);
console.log([...cls].join(' '));
toggle('done', false);
console.log([...cls].join(' '));
힌트

첫 번째 호출은 'done'을 추가하고, 두 번째 호출은 'done'을 똅니다.

정답
실행 결과
todo done
todo
  • 6~7번 줄: 'done' 클래스가 추가되어 'todo done'이 찍힙니다.
  • 8~9번 줄: 'done' 클래스가 제거되어 'todo'만 찍힙니다.

파이썬에 없는 브라우저 DOM의 classList.toggle('done', 불리언)⁠은 — 두 번째 인자가 참이면 클래스를 붙이고 거짓이면 떼어 CSS 스타일(.done .text)을 켜고 끕니다.

다시 볼 레슨: 글자와 클래스 바꾸기

문제 11 (만들어 보기)

할 일 배열 todos와 눌린 줄 요소 li({ dataset: { id: '...' } })⁠를 받아, 그 줄과 같은 숫자 id를 가진 항목을 지운 새 배열을 돌려주는 함수 dropRow(todos, li)의 몸통을 채우세요. 이렇게 나와야 합니다.

2
우유, 산책

시험 값의 li.dataset.id를 '2'에서 '1'로 바꾸면 이렇게 나와야 합니다.

2
복습, 산책

아래 뼈대에서 주석 둘 아래를 채우세요.

function dropRow(todos, li) {
  // 1. dataset.id를 숫자로

  // 2. 다른 id만 filter로
}

이어서 시험 배열과 li 객체를 넣어 확인합니다.

const list = [
  { id: 1, text: '우유' },
  { id: 2, text: '복습' },
  { id: 3, text: '산책' },
];
const row = {
  dataset: { id: '2' },
};
const res = dropRow(list, row);
console.log(res.length);
console.log(
  res
    .map((t) => t.text)
    .join(', '),
);
힌트

1번 주석 아래에 Number(li.dataset.id)⁠를 상수 id에 담고, 2번 주석 아래에 todos.filter로 t.id !== id인 항목만 돌려줍니다.

정답

모범 답입니다.

function dropRow(todos, li) {
  const id = Number(
    li.dataset.id,
  );
  return todos.filter(
    (t) => t.id !== id,
  );
}

이어서 시험 배열과 li 객체를 넣어 확인합니다.

const list = [
  { id: 1, text: '우유' },
  { id: 2, text: '복습' },
  { id: 3, text: '산책' },
];
const row = {
  dataset: { id: '2' },
};
const res =
  dropRow(list, row);
console.log(res.length);
console.log(
  res
    .map((t) => t.text)
    .join(', '),
);
실행 결과
2
우유, 산책

다시 볼 레슨: 이벤트 위임과 data 속성 · map·filter·find

문제 12 (만들어 보기)

이번에는 빈 데서 함수를 직접 씁니다. localStorage.getItem이 돌려준 값 raw(null이거나 JSON 문자열, 또는 깨진 문자열)를 받아 안전하게 배열로 복원해 돌려주는 함수 safeLoad(raw)를 작성하세요(null이거나 JSON 문법 오류가 나면 빈 배열 []을 돌려줍니다). 이렇게 나와야 합니다.

0
1
0

두 번째 시험 값을 '[{"id":1},{"id":2}]'⁠로 바꾸면 이렇게 나와야 합니다.

0
2
0

아래 칸의 위쪽에 safeLoad 함수를 쓰세요.

// safeLoad 함수 쓰기

console.log(
  safeLoad(null).length,
);
console.log(
  safeLoad('[1]').length,
);
console.log(
  safeLoad('{bad').length,
);
힌트

try 안에서 JSON.parse(raw ?? '[]')⁠를 돌려주고 catch 안에서 return [];를 돌려줍니다.

정답

모범 답입니다.

function safeLoad(raw) {
  try {
    return JSON.parse(
      raw ?? '[]',
    );
  } catch {
    return [];
  }
}

이어서 세 가지 입력을 넣어 확인합니다.

console.log(
  safeLoad(null).length,
);
console.log(
  safeLoad('[1]').length,
);
console.log(
  safeLoad('{bad').length,
);
실행 결과
0
1
0

다시 볼 레슨: 새로고침해도 남게: localStorage · JSON: 객체와 글 사이


틀린 문제는 그 레슨으로 돌아가 모션 그림과 '자주 틀리는 곳'을 보고, 하루쯤 지난 뒤 그 문제만 다시 풀어 보세요. 그날 바로 다시 풀면 방금 본 정답이 기억나 맞힌 것인지 알기 어렵습니다.

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

자바스크립트 목차로