2021년 12월 15일

배열

키를 사용해 식별할 수 있는 값을 담은 컬렉션은 객체라는 자료구조를 이용해 저장하는데, 객체만으로도 다양한 작업을 할 수 있습니다.

그런데 개발을 진행하다 보면 첫 번째 요소, 두 번째 요소, 세 번째 요소 등과 같이 순서가 있는 컬렉션이 필요할 때가 생기곤 합니다. 사용자나 물건, HTML 요소 목록같이 일목요연하게 순서를 만들어 정렬하기 위해서 말이죠.

순서가 있는 컬렉션을 다뤄야 할 때 객체를 사용하면 순서와 관련된 메서드가 없어 그다지 편리하지 않습니다. 객체는 태생이 순서를 고려하지 않고 만들어진 자료구조이기 때문에 객체를 이용하면 새로운 프로퍼티를 기존 프로퍼티 ‘사이에’ 끼워 넣는 것도 불가능합니다.

이럴 땐 순서가 있는 컬렉션을 저장할 때 쓰는 자료구조인 배열을 사용할 수 있습니다.

배열 선언

아래 두 문법을 사용하면 빈 배열을 만들 수 있습니다.

let arr = new Array();
let arr = [];

대부분 두 번째 방법으로 배열을 선언하는데, 이때 대괄호 안에 초기 요소를 넣어주는 것도 가능합니다.

let fruits = ["사과", "오렌지", "자두"];

각 배열 요소엔 0부터 시작하는 숫자(인덱스)가 매겨져 있습니다. 이 숫자들은 배열 내 순서를 나타냅니다.

배열 내 특정 요소를 얻고 싶다면 대괄호 안에 순서를 나타내는 숫자인 인덱스를 넣어주면 됩니다.

let fruits = ["사과", "오렌지", "자두"];

alert( fruits[0] ); // 사과
alert( fruits[1] ); // 오렌지
alert( fruits[2] ); // 자두

같은 방법으로 요소를 수정할 수 있습니다.

fruits[2] = '배'; // 배열이 ["사과", "오렌지", "배"]로 바뀜

새로운 요소를 배열에 추가하는 것도 가능합니다.

fruits[3] = '레몬'; // 배열이 ["사과", "오렌지", "배", "레몬"]으로 바뀜

length를 사용하면 배열에 담긴 요소가 몇 개인지 알아낼 수 있습니다.

let fruits = ["사과", "오렌지", "자두"];

alert( fruits.length ); // 3

alert를 사용해 요소 전체를 출력하는 것도 가능합니다.

let fruits = ["사과", "오렌지", "자두"];

alert( fruits ); // 사과,오렌지,자두

배열 요소의 자료형엔 제약이 없습니다.

예시:

// 요소에 여러 가지 자료형이 섞여 있습니다.
let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];

// 인덱스가 1인 요소(객체)의 name 프로퍼티를 출력합니다.
alert( arr[1].name ); // 이보라

// 인덱스가 3인 요소(함수)를 실행합니다.
arr[3](); // 안녕하세요.
trailing 쉼표

배열의 마지막 요소는 객체와 마찬가지로 쉼표로 끝날 수 있습니다.

let fruits = [
  "사과",
  "오렌지",
  "자두",
];

trailing(길게 늘어지는) 쉼표를 사용하면 모든 줄의 생김새가 유사해지기 때문에 요소를 넣거나 빼기가 쉬워집니다.

pop·push와 shift·unshift

큐(queue)는 배열을 사용해 만들 수 있는 대표적인 자료구조로, 배열과 마찬가지로 순서가 있는 컬렉션을 저장하는 데 사용합니다. 큐에서 사용하는 주요 연산은 아래와 같습니다.

  • push – 맨 끝에 요소를 추가합니다.
  • shift – 제일 앞 요소를 꺼내 제거한 후 남아있는 요소들을 앞으로 밀어줍니다. 이렇게 하면 두 번째 요소가 첫 번째 요소가 됩니다.

배열엔 두 연산을 가능케 해주는 내장 메서드 pushpop이 있습니다.

화면에 순차적으로 띄울 메시지를 비축해 놓을 자료 구조를 만들 때 큐를 사용하는 것처럼 큐는 실무에서 상당히 자주 쓰이는 자료구조입니다.

배열은 큐 이외에 스택(stack)이라 불리는 자료구조를 구현할 때도 쓰입니다.

스택에서 사용하는 연산은 아래와 같습니다.

  • push – 요소를 스택 끝에 집어넣습니다.
  • pop – 스택 끝 요소를 추출합니다.

스택은 이처럼 '한쪽 끝’에 요소를 더하거나 뺄 수 있게 해주는 자료구조입니다.

스택은 흔히 카드 한 벌과 비교됩니다. 쌓여있는 카드 맨 위에 새로운 카드를 더해주거나 빼는 것처럼 스택도 '한쪽 끝’에 요소를 집어넣거나 추출 할 수 있기 때문입니다.

스택을 사용하면 가장 나중에 집어넣은 요소가 먼저 나옵니다. 이런 특징을 줄여서 후입선출(Last-In-First-Out, LIFO)이라고 부릅니다. 반면 큐를 사용하면 먼저 집어넣은 요소가 먼저 나오기 때문에 큐는 선입선출(First-In-First-Out, FIFO) 자료구조라고 부릅니다.

자바스크립트 배열을 사용하면 큐와 스택 둘 다를 만들 수 있습니다. 이 자료구조들은 배열의 처음이나 끝에 요소를 더하거나 빼는 데 사용되죠.

이렇게 처음이나 끝에 요소를 더하거나 빼주는 연산을 제공하는 자료구조를 컴퓨터 과학 분야에선 데큐(deque, Double Ended Queue)라고 부릅니다.

아래는 배열 끝에 무언가를 해주는 메서드입니다.

pop

배열 끝 요소를 제거하고, 제거한 요소를 반환합니다.

let fruits = ["사과", "오렌지", "배"];

alert( fruits.pop() ); // 배열에서 "배"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.

alert( fruits ); // 사과,오렌지
push

배열 끝에 요소를 추가합니다.

let fruits = ["사과", "오렌지"];

fruits.push("배");

alert( fruits ); // 사과,오렌지,배

fruits.push(...)를 호출하는 것은 fruits[fruits.length] = ...하는 것과 같은 효과를 보입니다.

아래는 배열 앞에 무언가를 해주는 메서드입니다.

shift

배열 앞 요소를 제거하고, 제거한 요소를 반환합니다.

let fruits = ["사과", "오렌지", "배"];

alert( fruits.shift() ); // 배열에서 "사과"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.

alert( fruits ); // 오렌지,배
unshift

배열 앞에 요소를 추가합니다.

let fruits = ["오렌지", "배"];

fruits.unshift('사과');

alert( fruits ); // 사과,오렌지,배

pushunshift는 요소 여러 개를 한 번에 더해줄 수도 있습니다.

let fruits = ["사과"];

fruits.push("오렌지", "배");
fruits.unshift("파인애플", "레몬");

// ["파인애플", "레몬", "사과", "오렌지", "배"]
alert( fruits );

배열의 내부 동작 원리

배열은 특별한 종류의 객체입니다. 배열 arr의 요소를 arr[0]처럼 대괄호를 사용해 접근하는 방식은 객체 문법에서 왔습니다. 다만 배열은 키가 숫자라는 점만 다릅니다.

숫자형 키를 사용함으로써 배열은 객체 기본 기능 이외에도 순서가 있는 컬렉션을 제어하게 해주는 특별한 메서드를 제공합니다. length라는 프로퍼티도 제공하죠. 그렇지만 어쨌든 배열의 본질은 객체입니다.

이렇게 배열은 자바스크립트의 일곱 가지 원시 자료형에 해당하지 않고, 원시 자료형이 아닌 객체형에 속하기 때문에 객체처럼 동작합니다.

예시를 하나 살펴봅시다. 배열은 객체와 마찬가지로 참조를 통해 복사됩니다.

let fruits = ["바나나"]

let arr = fruits; // 참조를 복사함(두 변수가 같은 객체를 참조)

alert( arr === fruits ); // true

arr.push("배"); // 참조를 이용해 배열을 수정합니다.

alert( fruits ); // 바나나,배 - 요소가 두 개가 되었습니다.

배열을 배열답게 만들어주는 것은 특수 내부 표현방식입니다. 자바스크립트 엔진은 아래쪽 그림에서처럼 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높입니다. 이 방법 이외에도 배열 관련 연산을 더 빠르게 해주는 최적화 기법은 다양합니다.

그런데 개발자가 배열을 '순서가 있는 자료의 컬렉션’처럼 다루지 않고 일반 객체처럼 다루면 이런 기법들이 제대로 동작하지 않습니다.