JavaScript 배열
배열은 여러 개의 요소들을 순차적으로 나열한 자료구조입니다. JavaScript에서는 배열은 다루기 위한 유용한 메서드들을 제공합니다.
1. 배열의 특징
화살표 함수는 ES6에서 도입된 문법입니다. function 키워드를 사용하는 것보다 간결하고 항상 익명으로 동작합니다.
1.1. JavaScript의 배열
JavaScript의 배열은 우리가 일반적으로 알고있는 배열처럼 요소(element)라고 부르는 값을 가지며 배열의 요소는 위치를 나타내는 index를 가지고 있습니다. JavaScript에서 배열은 객체 타입으로 분류되지만, 배열은 값의 순서와 length 프로퍼티를 갖는 특징으로 인하여 일반 객체와 구분됩니다.
const arr = ['macbook', 'iphone', 'ipad'];
for (let i=0; i<arr.length; i++) {
console.log(arr[i]);
}
console.log(typeof arr); // object
JavaScript에서 일반 객체와 배열의 차이는 다음과 같습니다.
구분 | 객체 | 배열 |
구조 | 프로퍼티의 key와 value | index와 element |
값의 참조 | 프로퍼티의 key | index |
값의 순서 | X | O |
length 프로퍼티 | X | O |
자료구조의 관점에서 보면 JavaScript의 배열은 배열이 아니고 배열처럼 동작하도록 구현된 객체라고 볼 수 있습니다. 배열의 종류에는 밀집 배열과 희소 배열이 있는데 차이는 다음과 같습니다.
- 밀집 배열(dense array)인덱스를 이용하여 요소에 접근시 빠름
- 요소의 추가/삭제 및 특정 요소 탐색시 연속성의 유지를 위해 비효율적으로 동작함
- 동일한 크기의 메모리 공간이 연속적으로 나열된 구조
- 희소 배열(sparse array)인덱스를 이용하여 요소에 접근시 일반 배열에 비해 느림
- 요소의 추가/삭제 및 특정 요소 탐색시 일반 배열에 비해 빠름
- 동일하지 않은 크기의 메모리 공간이 비연속적으로 나열된 구조
JavaScript의 배열은 해시 테이블로 구현된 희소 배열이기 때문에 일반적인 배열보다 느리지만, 이러한 구조적인 단점을 보완하기 위해 자바스크립트 엔진에서는 일반 배열과 유사하게 동작하도록 최적화하여 구현되어 있습니다.
다음과 같이 배열에 대해 Object.getOwnPropertyDescriptors()를 호출하여 프로퍼티에 대한 정보를 확인하면 프로퍼티의 key로 사용되는 index와 length 프로퍼티를 갖는 객체라는 것을 확인할 수 있습니다.
const arr = [1,2,3];
console.log(Object.getOwnPropertyDescriptors(arr));
/*
0: {value: 1, writable: true, enumerable: true, configurable: true}
1: {value: 2, writable: true, enumerable: true, configurable: true}
2: {value: 3, writable: true, enumerable: true, configurable: true}
length: {value: 3, writable: true, enumerable: false, configurable: false}
*/
또한 다음과 같이 어떠한 타입의 값이든 프로퍼티의 value로 사용할 수 있다는 특징도 확인할 수 있습니다.
const arr = [
'string',
10,
true,
null,
undefined,
NaN,
[1,2,3],
() => {}
];
console.log(Object.getOwnPropertyDescriptors(arr));
/*
0: {value: "string", writable: true, enumerable: true, configurable: true}
1: {value: 10, writable: true, enumerable: true, configurable: true}
2: {value: true, writable: true, enumerable: true, configurable: true}
3: {value: null, writable: true, enumerable: true, configurable: true}
4: {value: undefined, writable: true, enumerable: true, configurable: true}
5: {value: NaN, writable: true, enumerable: true, configurable: true}
6: {value: Array(3), writable: true, enumerable: true, configurable: true}
7: {value: ƒ, writable: true, enumerable: true, configurable: true}
length: {value: 8, writable: true, enumerable: false, configurable: false}
*/
정리하면 JavaScript의 배열은 배열의 동작을 모방한 특수한 객체라고 정의할 수 있습니다.
1.2 length 프로퍼티
JavaScript 배열의 length 프로퍼티는 배열의 길이를 나타내는 값을 가지고 있습니다. length 프로퍼티의 값은 배열의 길이를 기준으로 결정되지만 사용자가 할당할 수도 있습니다.
다음과 같이 length 프로퍼티에 값을 할당했을 때 작은 값을 할당하면 배열의 길이가 줄어들지만 큰 값을 할당하면 length 프로퍼티의 값만 변경되고 배열의 실제 길이가 늘어나지는 않습니다.
const arr = [1,2,3];
console.log(arr); // [1, 2, 3]
console.log(arr.length); // 3
arr.length = 1;
console.log(arr); // [1]
console.log(arr.length); // 1
arr.length = 5;
console.log(arr); // [1, empty x 4]
console.log(arr.length); // 5
이와 같이 동작하는 것은 JavaScript가 문법적으로 희소 배열을 허용하기 때문에 가능한 것인데, 희소 배열을 생성하는 것은 배열의 기본 개념과 맞지 않고 성능에도 좋지 않은 영향을 주기때문에 사용하지 않는 것이 좋습니다.
또한 JavaScript 배열은 같은 타입의 요소로 구성된 배열을 생성할 때는 일반적인 배열처럼 연속된 메모리 공간을 할당을 합니다. 따라서 배열을 생성할 때는 같은 타입의 요소를 연속적으로 위치하도록 하는 것이 좋습니다.
2. 배열의 생성
2.1. 배열 리터럴
배열 리터럴을 사용하면 가장 간편한 방식으로 배열을 생성할 수 있습니다.
const arr1 = [1,2,3];
console.log(arr1); // [1, 2, 3]
console.log(arr1.length); // 3
const arr2 = [];
console.log(arr2); // []
console.log(arr2.length); // 0
2.2. Array 생성자
Array() 생성자를 이용해서 배열을 생성할 수 있습니다. 전달하는 파라미터에 따라서 동작에 차이가 있습니다. 파라미터는 양의 정수이어야하기 때문에 음수를 전달하면 에러가 발생합니다.
const arr1 = new Array(10);
console.log(arr1); // [empty x 10]
console.log(arr1.length); // 10
const arr2 = new Array()
console.log(arr2); // []
console.log(arr2.length); // 0
const arr3 = new Array('');
console.log(arr3); // [""]
console.log(arr3.length); // 1
const arr4 = new Array(1,2,3);
console.log(arr4); // [1, 2, 3]
console.log(arr4.length); // 3
const arr5 = Array(1,2,3);
console.log(arr5); // [1, 2, 3]
console.log(arr5.length); // 3
const arr6 = new Array(-1); // RangeError: Invalid array length
2.3. Array.of()
Array.of() 메서드는 전달된 파라미터를 요소로 추가하여 배열을 생성합니다.
const arr1 = Array.of();
console.log(arr1); // []
console.log(arr1.length); // 0
const arr2 = Array.of(10);
console.log(arr2); // [10]
console.log(arr2.length); // 1
const arr3 = Array.of(1, 2, 3);
console.log(arr3); // [1, 2, 3]
console.log(arr3.length); // 3
const arr4 = Array.of([1,2,3], [['a','b','c'],['a1','a2']]);
console.log(arr4); // [Array(3), Array(2)]
console.log(arr4.length); // 2
2.4. Array.from()
Array.from() 메서드는 유사 배열 객체 또는 이터러블 객체를 파라미터로 전달 받아 배열을 생성합니다.
Array.from() 메서드는 두번째 파라미터로 콜백 함수를 전달할 수 있습니다. 콜백 함수에서 요소와 index를 순차적으로 전달하여 연산의 결과값을 반환 받아 생성할 배열의 요소로 전달합니다.
const arrLikeObj = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
const iterableStrObj = 'harry';
const arr1 = Array.from(arrLikeObj);
console.log(arr1); // ["a", "b", "c"]
console.log(arr1.length); // 3
const arr2 = Array.from(arrLikeObj, (item, index) => { return index + ':' + item });
console.log(arr2); // ["0:a", "0:b", "0:c"]
console.log(arr2.length); // 3
const arr3 = Array.from(iterableStrObj);
console.log(arr3); // ["h", "a", "r", "r", "y"]
console.log(arr3.length); // 5
- 유사 배열 객체 (array-like object)for문으로 순회 가능 (이터러블 객체가 아니기 때문에 for...of문으로는 순회가 불가능)
- 배열처럼 length 프로퍼티를 갖고 index를 이용하여 접근할 수 있도록 구성된 객체
- 이터러블 객체 (iterable object)for문, for...of문으로 순회 가능
- Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션(NodeList, HTMLCollection) 객체가 해당함
- JavaScript 이터러블 프로토콜을 준수하여 만들어진 빌트인 객체
3. 배열의 사용
3.1. 배열 요소 참조
배열의 요소에는 대괄호와 인덱스를 사용합니다. 존재하지 않는 요소를 참조한 경우엔 undefined가 반환됩니다.
const arr = [1, 2, 3];
console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // 3
console.log(arr[10]); // undefined
console.log(arr[-1]); // undefined
3.2. 배열 요소 추가
배열도 객체처럼 동적으로 요소를 추가할 수 있습니다. 존재하지 않는 인덱스로 요소를 추가하면 length 프로퍼티의 값이 변경되는데 인덱스가 연속적이지 않은 경우라면 해당 배열은 희소 배열이 됩니다. 또한 배열에서 이미 존재하는 요소에 값을 다시 할당하면 해당 요소의 값이 변경됩니다.
const arr = [1, 2, 3];
console.log(arr); // [1, 2, 3]
console.log(arr.length); // 3
arr[3] = 4;
console.log(arr); // [1, 2, 3, 4]
console.log(arr.length); // 4
arr[9] = 10;
console.log(arr); // [1, 2, 3, 4, empty x 5, 10]
console.log(arr.length); // 10
arr[0] = 0;
console.log(arr); // [0, 2, 3, 4, empty x 5, 10]
console.log(arr.length); // 10
배열의 인덱스로 0 이상의 정수를 사용하지 않은 경우엔 해당 값을 key로 사용하는 프로퍼티가 생성됩니다. 배열에 프로퍼티를 생성하여도 length 프로퍼티의 값은 변경되지 않습니다.
const arr = [1, 2, 3];
console.log(arr); // [1, 2, 3]
console.log(arr.length); // 3
arr['name'] = 'harry';
arr.age = 30;
arr[-1] = -1;
console.log(arr); // [1, 2, 3, name: "harry", age: 30, -1: -1]
console.log(arr.length); // 3
3.3. 배열 요소 삭제
배열의 특정 요소 삭제를 위해서는 delete 연산자를 사용하거나 splice() 메서드를 사용합니다. delete 연산자로 배열의 요소를 삭제할 경우엔 해당 배열이 희소 배열이 되어 length 프로퍼티 값이 변경되지 않습니다. 따라서 splice() 메서드를 사용하여 요소를 완전히 삭제하는 것이 좋습니다.
const arr1 = [1, 2, 3];
delete arr1[1];
console.log(arr1); // [1, empty, 3]
console.log(arr1.length); // 3
const arr2 = [1, 2, 3];
arr2.splice(1, 1);
console.log(arr2); // [1, 3]
console.log(arr2.length); // 2
이상으로 JavaScript 배열에 대해 알아봤습니다.
※ Reference
- 모던 자바스크립트 Deep Dive, 이웅모 지음, 위키북스(2020), p492 ~ p507. 27장 배열
- poiemaweb.com, 배열, https://poiemaweb.com/js-array
- poiemaweb.com, 자바스크립트 배열은 배열이 아니다, https://poiemaweb.com/js-array-is-not-array