자바스크립트 기초 #5 객체와 배열

#4 함수 에서 동작을 묶는 도구를 봤으니, 이제 데이터를 묶는 도구 차례입니다. 자바스크립트의 두 핵심 자료구조 — 객체와 배열.

객체 — 키-값 쌍의 모음 #

객체 기본
const user = {
  id: 'u1',
  name: '커티스',
  age: 30,
  isAdmin: false,
};

console.log(user.name);     // 커티스
console.log(user['name']);  // 커티스 — 같은 효과

객체는 키-값 쌍을 묶는 자료구조입니다. 키는 문자열(또는 심볼)이고, 값은 어떤 타입이든 됩니다.

속성 추가/수정/삭제 #

객체 변경
const user = { id: 'u1', name: '커티스' };

user.age = 30;          // 추가
user.name = '다른 이름';  // 수정
delete user.id;          // 삭제

console.log(user);
// { name: '다른 이름', age: 30 }

const로 선언했는데 객체 안의 값이 바뀌는 게 처음엔 헷갈려요. const는 변수가 가리키는 곳을 못 바꾼다는 뜻이지, 객체 안의 값이 못 바뀐다는 뜻이 아닙니다(#2 참조 vs 원시).

단축 표기 #

같은 이름의 변수를 객체 키로 쓸 때 단축할 수 있습니다.

속성 단축
const id = 'u1';
const name = '커티스';

// 풀 표기
const user = { id: id, name: name };

// 단축 — 변수 이름과 키 이름이 같으면 한 번만
const userShort = { id, name };

ES2015 이후로 기본인 모양이라, 옛 코드만 풀 표기를 쓰는 정도입니다.

메서드 단축 #

값으로 함수를 둘 때도 단축이 있습니다.

메서드 단축
const calc = {
  add(a, b) { return a + b; },          // 단축
  subtract: function(a, b) { return a - b; },  // 풀 표기
};

calc.add(2, 3);        // 5
calc.subtract(5, 2);   // 3

배열 — 순서 있는 값들의 묶음 #

배열 기본
const fruits = ['사과', '바나나', '포도'];

console.log(fruits[0]);       // 사과 — 인덱스는 0부터
console.log(fruits.length);   // 3
console.log(fruits[10]);      // undefined — 없는 인덱스

배열도 객체의 한 종류입니다(typeof []'object'). 다만 인덱스로 순서가 있는 데이터를 다루기 좋게 특화돼 있습니다.

추가/제거 #

배열 변경
const fruits = ['사과'];

fruits.push('바나나');     // 끝에 추가
// ['사과', '바나나']

fruits.unshift('포도');    // 앞에 추가
// ['포도', '사과', '바나나']

fruits.pop();              // 끝에서 제거
// ['포도', '사과']

fruits.shift();            // 앞에서 제거
// ['사과']

이 네 메서드는 원본을 직접 바꿔요. 원본을 안 바꾸고 새 배열을 만들고 싶으면 spread(뒤에서)를 쓰는 게 안전합니다.

자주 쓰는 배열 메서드 — map, filter, reduce #

순회하면서 변환/거르기/누적하는 세 메서드. 모던 자바스크립트의 가장 자주 쓰는 도구입니다.

map — 변환 #

map — 모든 원소를 변환
const nums = [1, 2, 3, 4, 5];

const doubled = nums.map((n) => n * 2);
// [2, 4, 6, 8, 10]

const labels = nums.map((n) => `#${n}`);
// ['#1', '#2', '#3', '#4', '#5']

원본은 그대로 두고 새 배열을 반환합니다. 길이는 같고, 각 원소는 콜백의 반환값.

filter — 거르기 #

filter — 조건에 맞는 것만
const nums = [1, 2, 3, 4, 5];

const evens = nums.filter((n) => n % 2 === 0);
// [2, 4]

const big = nums.filter((n) => n > 3);
// [4, 5]

콜백이 truthy를 반환하는 원소만 남습니다. 길이가 줄어들 수 있습니다.

reduce — 누적 #

reduce — 하나의 값으로 모음
const nums = [1, 2, 3, 4, 5];

const sum = nums.reduce((acc, n) => acc + n, 0);
// 15

const max = nums.reduce((acc, n) => n > acc ? n : acc, -Infinity);
// 5

두 번째 인자(0, -Infinity)는 초깃값. 이걸로 시작해서 콜백을 한 원소씩 적용하며 값을 누적해 갑니다. 가장 강력한 메서드지만 처음에는 흐름이 보이기 어려워요. map/filter 만으로 안 풀리는 경우에 꺼내는 도구입니다.

체이닝 #

세 메서드는 서로 이어 쓰기 좋습니다.

체이닝 — 짝수만 골라 두 배
const nums = [1, 2, 3, 4, 5];

const result = nums
  .filter((n) => n % 2 === 0)
  .map((n) => n * 2);
// [4, 8]

각 메서드가 새 배열을 반환하기 때문에 자연스럽게 이어집니다.

자주 쓰는 다른 메서드들 #

검색 / 검사
[1, 2, 3].includes(2);            // true
[1, 2, 3].indexOf(2);              // 1
[{id: 'a'}].find((x) => x.id === 'a');    // {id: 'a'}
[1, 2, 3].some((n) => n > 2);     // true (하나라도)
[1, 2, 3].every((n) => n > 0);    // true (전부)
변형 — 새 배열 반환
[1, 2, 3].slice(1, 3);            // [2, 3]
[1, 2, 3].concat([4, 5]);         // [1, 2, 3, 4, 5]
[3, 1, 2].toSorted();             // [1, 2, 3] (원본 유지)
문자열 변환
[1, 2, 3].join(',');              // '1,2,3'
[1, 2, 3].join(' / ');            // '1 / 2 / 3'

toSorted는 ES2023에 추가된 모던 메서드. 옛날에는 sort가 원본을 바꿔서 사고가 잦았는데, toSorted가 깔끔합니다.

Spread (...) — 펼치기 #

객체와 배열을 풀어서 다시 묶는 문법.

배열 spread
const a = [1, 2, 3];
const b = [...a, 4, 5];          // [1, 2, 3, 4, 5]
const copy = [...a];              // [1, 2, 3] — 얕은 복사

const merged = [...a, ...b];      // 두 배열 이어붙이기
객체 spread
const user = { id: 'u1', name: '커티스' };
const updated = { ...user, age: 30 };
// { id: 'u1', name: '커티스', age: 30 }

const overridden = { ...user, name: '다른 이름' };
// { id: 'u1', name: '다른 이름' }   ← 뒤에 적은 게 이김

이 패턴이 자바스크립트로 일하면서 가장 자주 쓰는 관용구 중 하나입니다. 원본을 안 바꾸고 변경된 새 값을 만드는 상황에 적합합니다.

디스트럭처링 (Destructuring) — 풀어 받기 #

객체나 배열의 값을 한 번에 여러 변수로 풀어 받는 문법.

객체 디스트럭처링 #

객체 디스트럭처링
const user = { id: 'u1', name: '커티스', age: 30 };

const { name, age } = user;
console.log(name);  // 커티스
console.log(age);   // 30

// 이름 바꿔서 받기
const { name: userName } = user;
console.log(userName);   // 커티스

// 기본값
const { email = '없음' } = user;
console.log(email);   // 없음

함수 매개변수에서도 자주 씁니다. 리액트 강좌의 props 디스트럭처링이 정확히 이 문법입니다.

매개변수 디스트럭처링
function greet({ name, age }) {
  console.log(`안녕, ${name} (${age})`);
}

greet({ name: '커티스', age: 30 });

배열 디스트럭처링 #

배열 디스트럭처링
const [first, second, third] = [10, 20, 30];
console.log(first, second, third);   // 10 20 30

// 건너뛰기
const [, , third] = [10, 20, 30];
console.log(third);   // 30

// rest로 나머지
const [head, ...tail] = [1, 2, 3, 4];
console.log(head);   // 1
console.log(tail);   // [2, 3, 4]

useStateconst [count, setCount] = useState(0)가 정확히 이 문법입니다.

객체 키 동적으로 — 계산된 속성 #

키 자체를 변수로 만들고 싶을 때.

계산된 속성
const key = 'name';
const value = '커티스';

const user = {
  [key]: value,
  [`is${key}Required`]: true,
};
// { name: '커티스', isnameRequired: true }

대괄호 [...] 안에 표현식을 넣을 수 있습니다. 폼 핸들러에서 name 어트리뷰트를 키로 쓰는 패턴 등 실전에서 자주 등장합니다.

Object.keys / values / entries #

객체를 배열처럼 다루고 싶을 때.

객체를 배열로
const user = { id: 'u1', name: '커티스', age: 30 };

Object.keys(user);    // ['id', 'name', 'age']
Object.values(user);  // ['u1', '커티스', 30]
Object.entries(user); // [['id', 'u1'], ['name', '커티스'], ['age', 30]]

순회와 결합하면 강력합니다.

entries로 객체 순회
for (const [key, value] of Object.entries(user)) {
  console.log(`${key} = ${value}`);
}

마무리 #

이번 글에서 정리한 내용:

  • 객체 — 키-값 쌍, 단축 표기와 메서드 단축
  • 배열 — 순서 있는 묶음, push/pop/shift/unshift는 원본 변경
  • map/filter/reduce — 변환/거르기/누적의 세 메서드
  • some/every/find/includes 같은 검사 메서드
  • spread ...로 객체/배열 펼치기와 얕은 복사
  • 디스트럭처링 — 객체/배열을 풀어 변수로 받기
  • 계산된 속성 — { [key]: value }
  • Object.keys/values/entries로 객체를 배열처럼 다루기

다음 글(#6 문자열과 템플릿 리터럴)에서는 문자열 다루기 — 자주 쓰는 메서드, 템플릿 리터럴, 그리고 정규식 기본을 다룹니다.

X