자바스크립트 공부 일지 4

배열

1. 함수


코어 자바스크립트 - 자료구조와 자료형 - 배열
순서가 있는 컬렉션을 저장할 때 쓰는 자료구조 배열에 대해 알아봅시다.

배열 선언

아래 두 문법으로 빈 배열을 생성할 수 있습니다.

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

배열내 요소는 인덱스를 가집니다. 인덱스로 값을 찾기도 합니다. 인덱스로 저장된 값을 수정할 수 있습니다.

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

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

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

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

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

// 인덱스가 3인 요소(함수)를 실행합니다.
arr[3](); // 안녕하세요.
pop-push와 shift-unshift

큐(queue) 배열을 사용해 만들 수 있는 대표적인 자료구조 입니다. 큐는 FIFO(First-In-First-Out)이라고 부릅니다. 큐에서 사용하는 연산은 아래와 같습니다.

  • push - 맨 끝에 요소를 추가합니다.
  • shift - 제일 앞 요소를 꺼내 제가한 후 남아있는 요소들을 앞으로 밀어줍니다. 두번째 요소는 첫 번째 요소가 됩니다. 스택(stack)이라 불리는 자료구조를 구현할 때도 쓰입니다. 스택은 LIFO(Last-In-First-Out) 스택에서 사용하는 연산은 아래와 같습니다.
  • push - 요소를 스택 끝에 넣습니다.
  • pop - 스택 끝 요소를 추출합니다. 이렇듯 처음이나 끝에 요소를 더하거나 빼주는 연산을 제공하는 자료구조를 과학 분야에서 데큐(deque, Double Ended Queue라고 부릅니다.

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

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

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

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

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

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

fruits.push("배");

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

array.pusharray[array.length] = ...와 같은 효과를 보입니다. 아래는 배열 앞에 무언가를 해주는 메서드립니다.

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

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

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

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

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

fruits.unshift('사과');

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

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

js
let fruits = ["사과"];

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

// ["파인애플", "레몬", "사과", "오렌지", "배"]
alert( fruits );
배열의 내부 동작 원리

배열은 특별한 종류의 객체입니다. 배열 arr의 요소를 arr[0]처럼 대괄호를 사용해 접근하는 방식은 객체 문법에서 배웠습니다. 배열은 키가 숫자(index)라는 것이 다릅니다.
length라는 프로퍼티도 제공합니다. 배열의 본질 또한 객체입니다. 배열도 원시 자료형이 아닌 객체형에 속하기 때문에 객체처럼 동작합니다.
배열은 객체와 마찬가지로 참조를 통해 복사됩니다.

js
let fruits = ["바나나"]

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

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

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

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

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

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

js
let fruits = []; // 빈 배열을 하나 만듭니다.

fruits[99999] = 5; // 배열의 길이보다 훨씬 큰 숫자를 사용해 프로퍼티를 만듭니다.

fruits.age = 25; // 임의의 이름을 사용해 프로퍼티를 만듭니다.

배열 또한 객체이므로 예시처럼 원하는 프로퍼티를 추가해도 문제가 발생하진 않습니다.

허나 이렇게 코드를 작성하면 자바스크립트 엔진이 배열을 일반 객쳋럼 다루어 배열을 다룰 때만 적용되는 최적화 기법이 동작하지 않아 배열의 이점이 사라집니다.

잘못된 방법의 예는 다음과 같습니다. 사실 이런 코드는 작성하지 않죠.

  • arr.test = 5같이 숫자가 아닌 값을 프로퍼티 키로 사용하는 경우
  • arr[0]arr[1000]만 추가하고 그 사이에 아무런 요소도 없는 경우
  • arr[2000], arr[999]같이 요소를 역순으로 채우는 경우
성능

pushpop은 빠르지만 shiftunshift는 느립니다. 왜냐하면 배열의 모든 요소에 영향을 미치기 때문입니다.

반복문

for문은 배열을 순회할 때 쓰는 가장 오래된 방법입니다. 이 외에도 객체에 for...in과 같이 배열 순회에 사용되는 for...of가 있습니다.

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

// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
  alert( fruit );
}

for..of를 사용하면 현재 요소의 인덱스는 얻을 수 없고 값만 얻을 수 있습니다. 이 정도 기능이면 원하는 것을 충분히 구현할 수 있습니다. 물론 for..in구문도 사용 가능합니다. 배열도 객체형에 속하기 때문이죠.

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

for (let key in arr) {
  alert( arr[key] ); // 사과, 오렌지, 배
}

다만 배열에 for..in을 사용 시 문제가 발생합니다. 배열 내 length와 같은 프로퍼티도 있고 요소마다 인덱스가 붙어 있기 때문입니다. 또한 성능면에서도 좋지 않습니다.

length 프로퍼티

배열의 사이즈를 구할 때 length프로퍼티를 사용합니다. 배열 내 요소 개수를 구하는 것은 아니고 가장 큰 인덱스에 +1한 값을 반환합니다.

또한 length프로퍼티는 쓰기가 가능합니다. 값을 수동으로 증가시키면 아무 일도 일어나지 않죠. 그런데 값을 감소시키면 배열이 잘립니다. 짧아진 배열을 다시 되돌리 수 없죠.

js
let arr = [1, 2, 3, 4, 5];

arr.length = 2; // 요소 2개만 남기고 잘라봅시다.
alert( arr ); // [1, 2]

arr.length = 5; // 본래 길이로 되돌려 봅시다.
alert( arr[3] ); // undefined: 삭제된 기존 요소들이 복구되지 않습니다.

이 특징을 이용해 배열 내 요소를 비울 수 있습니다.

new Array()

new Array()문법을 사용해도 배열 생성이 가능합니다. 하지만 대부분의 개발자는 대괄호를 이용한 배열을 더 많이 사용하죠. 왜냐하면 실수를 범할 수 있기 때문입니다.

js
let arr = new Array(2); // 이렇게 하면 배열 [2]가 만들어질까요?

alert( arr[0] ); // undefined가 출력됩니다. 요소가 하나도 없는 배열이 만들어졌네요.

alert( arr.length ); // 길이는 2입니다.

위 예시에서 확인해 본 것처럼 new Array(number)를 이용해 만든 배열 요소는 모두 undefined입니다.

다차원 배열

배열 역시 배열의 요소가 될 수 있습니다. 이런 배열을 다차원 배열이라 부릅니다. 다차원 배열은 행렬을 저장하는 용도로 쓰입니다.

js
let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

alert( matrix[1][1] ); // 5, 중심에 있는 요소
toString

배열엔 toString 메서드가 구현되어 있어 이를 호출하면 요소를 쉼표로 구분한 문자열이 반환

js
let arr = [1, 2, 3];

alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true

아래 예시를 실행해 봅시다.

js
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"

배열엔 Symbol.toPrimitivevalueOf메서드가 없습니다. 따라서 위 예시에선 문자열로서 형 변환 []는 빈 문자열, [1]은 문자열 "1", [1,2]는 문자열 "1,2"로 변환됩니다.
이항 뎃셈 연산자 "+"는 피연산자 중 하나가 문자열인 경우 나머지 피연산자도 문자열로 변환합니다. 따라서 위 예시는 아래 예시와 동일하게 동작합니다.

요약

배열은 특수한 형태의 객체로, 순서가 있는 자료를 저장하고 관리하는 용도에 최적화된 자료구조입니다.