배열은 여러 개의 값을 순차적으로 나열한 자료구조이다. 사용빈도가 매우 높고 가장 기본적인 자료구조다. JS에서는 배열을 다루기 위한 여러 유용한 메서드를 제공한다.
const arr = ["apple", "banana", "orange"];
// 인덱스 값
arr[0] // apple
arr[1] // banana
arr[2] // orange
arr.length // 3
length
프로퍼티를 갖는다.typeof arr // object
배열 생성방법
1. 배열 리터럴
2. Array 생성자 함수
3. Array.of 메서드
4. Array.from 메서드
const arr = [1, 2, 3];
arr.constructor === Array // true 배열의 생성자 함수는 Array이다.
Object.getPrototypeOf(arr) === Array.prototype // true 배열의 프로토타입 객체는 Array.prototype이다.
// 즉, arr의 프로토타입은 Array.prototype
배열은 객체지만 일반 객체와는 구별되는 독특한 특징이 있다.
구분 | 객체 | 배열 |
---|---|---|
구조 | 프로퍼티 키와 프로퍼티 값 | 인덱스와 요소 |
값의 참조 | 프로터티 키 | 인덱스 |
값의 순서 | X | O |
length 프로퍼티 | X | O |
표를 보면 즉, 배열은 값의 순서(인덱스로 표현되는)가 있고 ! length
프로퍼티를 갖는다. 즉 배열의 장점은 순서가 있기에 순차적, 역순이나 특정위치로부터 요소에 접근할 수 있다.
자료구조에서 말하는 배열은 밀집 배열이다.
밀집 배열 (dense array)
배열의 요소가 하나의 데이터 타입으로 통일되어있으며 연속적으로 인접해있다
그러므로
1. 인덱스를 통해 단 한번의 연산으로 특정 요소에 접근이 가능하다. (O(1))
2. 정렬되지 않은 배열의 경우 처음 위치부터 선형적으로 검색해야 한다. (O(n))
// 선형 검색을 통해 배열(array)에 특정 요소(target)가 존재하는지 확인한다.
// 배열에 특정 요소가 존재하면 특정 요소의 인덱스를 반환하고, 존재하지 않으면 -1을 반환한다.
function linearSearch(array, target) {
const length = array.length;
for (let i = 0; i < length; i++) {
if (array[i] === target) return i;
}
return -1;
}
console.log(linearSearch([1, 2, 3, 4, 5, 6], 3)); // 2
console.log(linearSearch([1, 2, 3, 4, 5, 6], 0)); // -1
자바스크립트에서의 배열은 희소 배열이다.
배열 요소 각각의 메모리 공간은 동일한 크기를 갖이 않아도 되며 연속적으로 이어져 있지 않을 수 있다.
즉, 자바스크립트의 배열은 일반적인 배열을 흉내 낸 특수한 객체다.
// 배열의 고유한 속성 설명자
console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));
/*
{
'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}
}
*/
엄밀히 말하면 자바스크립트 배열은 해시 테이블로 구현된 객체이다.
인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느릴수 밖에 없는 구조적인 단점이 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 일반적인 배열보다 빠른 성능을 기대할 수 있다.
그리하여 모던 자바스크립트 엔진은 배열을 일반 객체와 구별하여 좀 더 배열처럼 동작하도록 최적화하여 구현하여서 일반객체보다 약 2배정도 빠르다.
배열의 길이를 나타내는 0 이상의 정수 값을 가진다. (빈 배열이면 "0")
[].length // 0
[1, 2, 3] // 3
length
프로퍼티의 값은 배열에 요소를 추가하거나 삭제하면 자동 갱신된다.length
는 배열의 길이를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당 가능하다. const arr = [1, 2, 3, 4, 5];
// 현재 length 프로퍼티 값인 5보다 작은 숫자 값 3을 length 프로퍼티에 할당
arr.length = 3;
// 배열의 길이가 5에서 3으로 줄어든다.
console.log(arr); // [1, 2, 3]
length
와 배열의 요소 개수가 일치하지 않으면, length
가 항상 더 크다.const arr = [1, 2, 3]
// 현재 length 프로퍼티 값인 3보다 큰 값 5를 length 프로퍼티에 할당
arr.length = 5;
// length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지 않는다
console.log(arr.length); // 5
console.log(arr); // [1, 2, 3, empty * 2];
const bin = [1 , , 3] // 희소 배열
console.log(bin) // [1, enpty, 3]
위에서 설명했듯이 배열 생성 방법은 4가지가 있다.
배열리터럴 []
사용한다. 가장 일반적이고 간편한 배열 생성 방식.
Array
생성자함수로 배열을 생성하면 생성된 배열은 희소 배열이다.
const arr = new Array(10);
console.log(arr); // [empty × 10]
console.log(arr.length); // 10
Array
배열과 다르게 전달된 인수를 요소로 갖는 배열을 생성한다.
Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of("string"); // ['string']
유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.
// 유사 배열 객체를 변환하여 배열을 생성한다.
Array.from({ length: 2, 0: 'a', 1: 'b' }); // -> ['a', 'b']
// length 만 존재하는 유사배열
Array.from({ length: 3 }); // [undefined, undefined, undefined]
// 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다.
Array.from('Hello'); // -> ['H', 'e', 'l', 'l', 'o']
// 두번째 인수로 전달한 콜백함수의 반환 값으로 구성된 배열을 반환
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
배열의 요소에 참조 할때는 대괄호에 인덱스를 넣어 배열의 요소를 참조한다.
const arr = [1, 2];
console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // 없는 요소에 접근하면 undefined
// 희소 배열 또한 undefined
const bin = [1, , 3];
console.log(bin[1]); // undefined
존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가된다. 이때 length
프로터티 값은 자동 갱신된다.
const arr = [0];
arr[1] = 1;
console.log(arr); // [0, 1]
console.log(arr.length); // 2
// 요소값의 갱신도 가능하다.
arr[1] = 10;
console.log(arr); // [0, 10]
배열은 사실 객체이기에 배열의 특정 요소를 삭제할때 delete
연산자를 사용할 수 있다. 이때 delete
연산자는 객체의 프로퍼티를 삭제하기에 프로퍼티 키가 '1'인 프로퍼티를 삭제한다. 이때 배열은 희소 배열이되므로 delete
연산자 보단 splice
메서드를 사용하자.
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, empty, 3]
// length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
console.log(arr.length); // 3
배열의 특정 요소 삭제는 splice
사용 !
const arr = [1, 2, 3];
arr.splice(1, 1);
console.log(arr); // [1, 3]
console.log(arr.length); // 2
자신을 호출한 배열을 순회하면서 콜백 함수를 호출한다.
콜백 함수 반환값이 단 한 번이라도 참이면 true, 거짓이면 false를 반환한다.
// 배열의 요소 중에 10보다 큰 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item > 10); // -> true
// 배열의 요소 중에 0보다 작은 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item < 0); // -> false
// 배열의 요소 중에 'banana'가 1개 이상 존재하는지 확인
['apple', 'banana', 'mango'].some(item => item === 'banana'); // -> true
// some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환한다.
[].some(item => item > 3); // -> false
자신을 호출한 배열을 순회하면서 콜백 함수를 호출한다.
콜백 함수 반환값이 모두 참이면 true, 거짓이면 false를 반환한다.
// 배열의 모든 요소가 3보다 큰지 확인
[5, 10, 15].every(item => item > 3); // -> true
// 배열의 모든 요소가 10보다 큰지 확인
[5, 10, 15].every(item => item > 10); // -> false
// every 메서드를 호출한 배열이 빈 배열인 경우 언제나 true를 반환한다.
[].every(item => item > 3); // -> true
ES10에서 도입된 flatMap
메서드는 map
메서드를 통해 생성된 새로운 배열을 평탄화한다.
즉, map
메서드와 flat
메서드를 순차적으로 실행한다.
const arr = ['hello', 'world'];
// map과 flat을 순차적으로 실행
arr.map(x => x.split('')).flat();
// -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
// flatMap은 map을 통해 생성된 새로운 배열을 평탄화한다.
arr.flatMap(x => x.split(''));
// -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
단, flapMap 메서드는 flat 메서드처럼 인수를 전달하여 평탄화 깊이 지정 못함
그래서 1단계만 평탄화할수있다.
그러므로 중첩 배열의 평탄화 깊이를 지정할때는 map, flat 메서드 따로 호출하자 !!