드리프트 / Posts / 웹 개발 배우기 9편 - 자바스크립트 객체의 모든 것, 플래시 카드 앱으로 정복하기

Created Mon, 03 Nov 2025 14:49:00 +0900 Modified Sun, 02 Nov 2025 16:26:23 +0900

웹 개발 배우기 9편 - 자바스크립트 객체의 모든 것, 플래시 카드 앱으로 정복하기

웹 개발 배우기 9편 - 자바스크립트 객체의 모든 것, 플래시 카드 앱으로 정복하기

이 글은 프로그래밍 경험이 없는 분들을 대상으로 자바스크립트 웹 앱 제작법을 알려드리는 ‘웹 개발 배우기’ 시리즈의 일부인데요.

이번 시간에는 프로퍼티를 가진 ‘순수 객체(plain object)‘를 만드는 방법에 대해 알아볼 건데요.

그리고 이 객체를 활용해서 간단한 플래시 카드 앱도 함께 만들어 볼 겁니다.

순수 객체 만들고 사용하기

아래 코드는 ‘객체 리터럴(object literal)‘을 사용해서 순수 객체를 만들고 purchase라는 변수에 할당하는 모습인데요.

const purchase = {
  product: 'toothbrush',
  quantity: 1,
};

맨 마지막에 있는 쉼표, 즉 ‘트레일링 콤마(trailing comma)‘는 넣어도 되고 안 넣어도 되는 선택 사항입니다.

어떤 면에서 순수 객체는 변수들의 묶음이라고 할 수 있는데요.

숫자 인덱스를 사용하는 배열과 달리, 이름을 통해 관련된 데이터에 접근할 수 있게 해주는 아주 유용한 구조입니다.

purchase 객체로 할 수 있는 작업은 다음과 같습니다.

.quantity처럼 프로퍼티를 ‘읽을’ 수 있고요.

> purchase.quantity
1

프로퍼티에 새로운 값을 ‘쓸’ 수도 있습니다.

> purchase.quantity += 1;
> purchase
{ product: 'toothbrush', quantity: 2 }

새로운 프로퍼티 추가하기

프로퍼티에 값을 쓰는 방식을 이용하면 객체에 새로운 프로퍼티를 추가할 수도 있거든요.

> const obj = {};
> obj.prop = 'yes';
> obj
{ prop: 'yes' }

없는 프로퍼티 읽기

만약 존재하지 않는 프로퍼티를 읽으려고 하면, 결과로 undefined를 얻게 됩니다.

> const obj = {};
> obj.doesNotExist
undefined

프로퍼티 값 단축 구문

가끔 프로퍼티의 ‘키(key)‘와 그 값으로 사용할 ‘변수’의 이름이 똑같을 때가 있거든요.

예를 들면 아래와 같습니다.

createPoint = (x, y) => ({x: x, y: y});

여기서 객체 리터럴을 괄호로 감싸야 한다는 점을 꼭 기억해주세요.

그렇지 않으면 자바스크립트는 이걸 코드 블록으로 착각할 수 있습니다.

이렇게 키와 값의 이름이 같을 때는 코드를 아주 간단하게 줄여 쓸 수 있는데요.

이걸 ‘프로퍼티 값 단축(property value shorthand)’ 구문이라고 부릅니다.

createPoint = (x, y) => ({x, y});

프로젝트 toggle-content.html

toggle-content.html 프로젝트는 다음 프로젝트를 위한 준비 운동인데요.

콘텐츠를 어떻게 보여주고 숨기는지 배워보는 간단한 실습입니다.

이 프로젝트는 file: URL을 통해 사용할 수 있습니다.

HTML 구조는 다음과 같습니다.

<p>
  <a href="">Toggle the content</a>
</p>
<p id="content" class="hidden">
  [...]
</p>

그리고 아래 CSS는 #content 요소에 hidden 클래스가 있을 경우, 화면에서 보이지 않게 숨겨주는 역할을 하는데요.

#content.hidden {
  display: none;
}

그래서 처음에는 <p> 태그 안의 내용이 보이지 않다가, 링크를 클릭할 때마다 보이고 숨겨지는 것을 반복하게 됩니다.

이 기능을 구현한 자바스크립트 코드는 다음과 같습니다.

const content = document.querySelector('#content');
document.querySelector('a')
  .addEventListener(
    'click',
    (event) => {
      event.preventDefault();
      content.classList.toggle('hidden'); // (A)
    }
  );

그렇다면 (A) 지점에서는 무슨 일이 일어나고 있는 걸까요?

.classListcontent 요소의 클래스 목록을 배열과 비슷한 형태로 제공하는데요.

이 목록의 .toggle() 메서드는 필요에 따라 ‘hidden’ 클래스를 추가하거나 제거해주는 역할을 합니다.

(A) 지점의 코드는 아래 코드와 완전히 똑같은 일을 하거든요.

if (content.classList.contains('hidden')) {
  content.classList.remove('hidden');
} else {
  content.classList.add('hidden');
}

이렇게 hidden 클래스가 추가되거나 제거되면, CSS가 그 변화를 즉시 감지해서 #content 요소를 보여주거나 숨겨주는 겁니다.

프로젝트 flash-cards/

flash-cards 프로젝트는 무작위로 플래시 카드를 보여주는 앱인데요.

플래시 카드 앞면에는 스페인어 단어가, 뒷면에는 영어 번역이 나타납니다.

뒷면은 처음에는 숨겨져 있지만, 드러낼 수 있습니다.

프로젝트 실행하기

이 프로젝트는 자바스크립트 모듈을 사용하기 때문에 웹 서버를 실행해야만 하는데요.

cd learning-web-dev-code/projects/
npx http-server

서버를 실행한 후, 아래 주소로 접속하면 프로젝트를 확인할 수 있습니다.

http://127.0.0.1:8080/flash-cards/flash-cards.html

앞으로는 웹 앱 실행 시 http-server 사용법에 대한 설명은 생략할 테니, 혹시 헷갈리신다면 이 부분을 다시 참고해 주세요.

data.js 플래시 카드 데이터

플래시 카드 데이터는 data.js라는 별도의 모듈 파일에 정의되어 있습니다.

export const data = [
  { front: 'perro', back: 'dog' },
  // ...
];

flash-cards.html HTML 구조

사용자 인터페이스는 HTML로 아래와 같이 구성되는데요.

<p>
  <button id="nextCard">Next Flashcard</button>
</p>
<p>
  <a href="" id="toggleBack">Toggle Back</a>
</p>
<div id="flashCard">
  <p id="front"></p>
  <hr>
  <p id="back"></p>
</div>
<p>
  <span id="cardNumber"></span> of <span id="cardTotal"></span>
</p>

마지막 <p> 태그는 “2 of 10"과 같은 텍스트를 표시합니다.

flash-cards.html CSS 스타일

플래시 카드 뒷면을 쉽게 보여주고 감추기 위해 CSS가 필요한데요.

#back {
  display: none;
}
#back.revealed {
  display: initial;
}

기본적으로 뒷면은 보이지 않다가, revealed 클래스가 추가되면 보이도록 설정했습니다.

그리고 아래 CSS는 전체 UI를 화면 중앙에 배치하고 최대 너비를 지정하는 역할을 합니다.

html {
  margin: 2rem auto;
  max-width: 35rem;
}

border-radius를 사용해서 플래시 카드에 둥근 모서리 효과도 주었습니다.

#flashCard {
  border: solid thin gray;
  border-radius: 0.5rem;
  padding: 0 1rem 1rem;
}

flash-cards.html 자바스크립트 로직

먼저 필요한 도구와 데이터를 가져오는 코드부터 시작하는데요.

import { shuffleArray } from './utils.js';
import { data } from './data.js';

shuffleArray()는 이름 그대로 배열의 요소들을 무작위로 섞어주는 함수입니다.

카드를 섞어주면 매번 다른 순서로 카드를 볼 수 있어서 학습 효과가 더 좋겠죠?

currentIndex 변수는 현재 보고 있는 카드가 몇 번째 카드인지 기록하는 역할을 합니다.

let currentIndex = 0;

showCard() 함수는 배열의 특정 카드를 화면에 표시해주는 일을 하거든요.

const showCard = (index) => {
  const front = document.querySelector('#front');
  const back = document.querySelector('#back');
  const cardNumber = document.querySelector('#cardNumber');
  
  back.classList.remove('revealed');

  const card = data[index];
  front.innerText = card.front;
  back.innerText = card.back;
  cardNumber.innerText = index + 1;
};

다음으로, startNewRound() 함수는 새로운 라운드를 시작하는, 일종의 게임 재시작 기능인데요.

모든 것을 초기 상태로 설정해주기 때문에 웹 앱이 시작될 때 가장 먼저 호출됩니다.

const startNewRound = () => {
  const cardTotal = document.querySelector('#cardTotal');

  shuffleArray(data);
  cardTotal.innerText = data.length;

  currentIndex = 0;
  showCard(currentIndex);
};

이 함수는 다음 카드로 넘어가는 기능을 담당합니다.

const goToNextCard = () => {
  currentIndex = currentIndex + 1;
  if (currentIndex >= data.length) {
    startNewRound();
  } else {
    showCard(currentIndex);
  }
}

마지막으로, toggleBack() 함수는 카드의 뒷면을 보여주거나 숨기는 역할을 합니다.

const toggleBack = () => {
  const back = document.querySelector('#back');
  back.classList.toggle('revealed');
};

utils.js

아래 유틸리티 함수는 ‘피셔-예이츠(Fisher–Yates) 알고리즘’을 사용해서 배열을 섞는데요.

export const shuffleArray = (arr) => {
  for (let current = arr.length - 1; current >= 1; current--) {
    const randomPick = Math.floor(Math.random() * (current + 1));
    const tmp = arr[current];
    arr[current] = arr[randomPick];
    arr[randomPick] = tmp;
  }
  return arr;
};

알고리즘은 생각보다 아주 간단합니다.

배열의 맨 끝 요소부터 시작해서, 그 앞쪽 요소들 중에서 무작위로 하나를 골라 맨 끝 요소와 자리를 바꾸는데요.

이 과정을 배열의 맨 앞까지 반복하는 겁니다.

마치 트럼프 카드를 섞는 것과 비슷하거든요.

손에 든 카드 덱에서 무작위로 한 장을 뽑아 테이블에 내려놓고, 남은 카드 중에서 또 한 장을 뽑아 그 위에 쌓는 과정을 반복하는 것과 같습니다.

용어 정리 모델 vs. 뷰

플래시 카드 프로젝트에는 한 가지 흥미로운 현상이 있었는데요.

한쪽에는 플래시 카드를 설명하는 data 배열과 같은 ‘데이터’가 있었고, 다른 한쪽에는 그 데이터가 화면에 ‘표시’되는 사용자 인터페이스가 있었습니다.

전자를 ‘모델(model)’, 후자를 모델을 위한 ‘뷰(view)‘라고 부르는데요.

이 둘을 연결하고 이벤트를 처리하며 모델과 뷰를 업데이트하는 역할을 ‘컨트롤러(controller)‘라고 부르기도 하지만, 앞의 두 용어만큼 흔하게 쓰이지는 않습니다.

우리 프로젝트에서는 showCard()startNewRound() 두 함수가 모델의 데이터를 뷰로 ‘전송’하는, 즉 모델의 변경 사항을 뷰에 반영하는 역할을 수행한 겁니다.