배열은 자주 사용해서 익숙하다고 생각했지만 의외로 모르는 내용이 많았습니다.
내용은 제가 처음 알게 되었거나, 잘 모르고 있었던 내용 위주로 정리했습니다.
배열은 순차적으로 나열한 자료구조다.
자바스크립트에는 배열이라는 타입은 존재하지 않는다. 배열은 객체 타입이다.
typeof arr // → object
하지만, 일반 객체와 구별되는 특징이 있다.
length
프로퍼티가 존재한다.자료구조에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 구조를 말한다.
즉, 하나의 데이터 타입으로 통일되어 있고, 서로 연속적으로 인접해 있는 자료구조이다.
⇒ 이러한 배열을 밀집 배열이라고 한다.
하지만, 자바스크립트의 배열은 이러한 배열의 의미와는 조금 다르다.
배열을 위한 동일한 크기의 메모리 공간도 필요하지 않고, 연속적으로 있지 않을 수도 있다.
⇒ 이러한 배열을 희소 배열이라고 한다.
→ 자바스크립트의 배열은 일반적인 배열을 흉내 낸 특수한 객체이다.
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}
}
*/
자바스크립트 배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며, length
프로퍼티를 갖는 특수한 객체이다.
자바스크립트 배열은 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느릴수밖에 없는 구조적인 단점이 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 일반적인 배열보다 빠른 성능을 기대할 수 있다.
length
프로퍼티의 값은 배열에 요소를 추가하거나 삭제하면 자동으로 갱신된다.
length
프로퍼티 값은 배열의 길이를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당할 수 있다.
현재 length
프로퍼티 값보다 작은 숫자를 할당하면 배열의 길이가 줄어들고,
더 큰 값을 할당하는 경우에는 length
프로퍼티 값은 변경되지만, 실제로 배열의 길이가 늘어나진 않는다.
const arr = [1];
arr.length = 3;
console.log(arr.length); // 3
console.log(arr) // [1, empty x 2]
위의 출력에서의 empty x 2 는 실제로 추가된 배열의 요소가 아니다.
이처럼 현재 length
프로퍼티의 값보다 큰 숫자 값을 할당하면 length
프로퍼티의 값은 변경되지만 실제 배열에는 아무 변함이 없다.
심지어, 메모리 공간을 보호하지도 않으며 빈 요소를 생성하지도 않는다.
자바스크립트는 문법적으로 희소 배열을 허용하지만 희소 배열은 사용하지 않는 것이 좋다.
모던 자바스크립트 엔진은 요소의 타입이 일치하는 배열을 생성할 때 일반적인 의미의 배열처럼 연속된 메모리 공간을 확보하는 것으로 알려져 있다.
→ 반드시 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선이다.
가장 간단한 방식이다.
const arr = [1, 2, 3];
const arr = new Array()' // []
// 전달된 인수가 없으면 빈 배열을 생성한다.
const arr = new Array(10);
// 전달된 인수가 1개이고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성한다.
// 길이 10인 희소 배열이 생성된다. 배열의 요소는 없다.
// 4_294_967_295 이하인 양의 정수여야 한다.
const arr = new Array(1, 2, 3) // [1, 2, 3]
const arr = new Array({}) // [{}]
// 전달된 인수가 2개 이상이거나 숫자가 아닌 경우 인수를 요소로 갖는다.
ES6에서 도입된 Array.of
메서드는 전달된 인수를 요소로 갖는 배열을 생성한다.
Array.of
는 Array 생성자 함수와 다르게 전달된 인수가 1개이고 숫자이더라도 인수를 갖는 배열을 생성한다.
Array.of(1); // -> [1]
Array.of(1, 2, 3); // -> [1, 2, 3]
Array.of('string'); // -> ['string']
ES6에서 도입된 Array.from
메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.
// 유사 배열 객체를 변환하여 배열을 생성한다.
Array.from({ length: 2, 0: 'a', 1: 'b' }); // ➔ ['a', 'b']
// 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다.
Array.from('Hello'); // ➔ ['H', 'e', 'l', 'l', 'o']
Array.from
을 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 값을 만들면서 요소를 채울 수 있다.
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
배열의 요소를 참조할 때에는 대괄호 []
를 사용한다.
유효한 인덱스가 아니면 undefined
를 반환한다.
인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수(또는 정수 형태의 문자열)를 사용해야 한다.
만약 정수 이외의 값을 인덱스처럼 사용하면 프로퍼티가 생성된다.
이때 추가된 프로퍼티는 length
프로퍼티 값에 영향을 주지 않는다.
const arr = [];
// 배열 요소의 추가
arr[0] = 1;
arr['1'] = 2;
// 프로퍼티 추가
arr['foo'] = 3;
arr.bar = 4;
arr[1.1] = 5;
arr[-1] = 6;
console.log(arr); // [1, 2, foo: 3, bar: 4, '1.1': 5, '-1': 6]
console.log(arr.length) // 2
배열은 사실 객체이므로 delete
연산자를 사용할 수 있다.
const arr = [1, 2, 3];
// 배열 요소의 삭제
delete arr[1];
console.log(arr); // [1, empty, 3]
// length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
console.log(arr.length) // 3
희소 배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 Array.prototype.splice() 메서드를 사용한다.
메서드와 고차 함수 중 제 생각에 중요한, 자주 사용하는, 잊어버리기 쉬운 친구들만 작성해보았습니다.
map
메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.
그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다. (원본 배열은 유지된다.)
map
메서드가 생성한 새로운 배열의 length
프로퍼티의 값은 기존 배열의 length
프로퍼티 값과 반드시 일치한다.
reduce
메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.
그리고 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다. (원본 배열은 유지된다.)
// [1, 2, 3, 4]의 모든 요소의 누적을 구한다.
const sum = [1, 2, 3, 4]
.reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);
console.log(sum); // 10
reduce
메서드를 호출할 때는 언제나 초기값을 전달하는 것이 안전하다.
ES10 에서 도입된 flat
메서드는 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화 한다.
// 중첩 배열을 평탄화하기 위한 깊이 값의 기본값은 1이다.
[1, [2, [3, [4]]]].flat(); // -> [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(1); // -> [1, 2, [3, [4]]]
// 중첩 배열을 평탄화하기 위한 깊이 값을 2로 지정하여 2단계 깊이까지 평탄화한다.
[1, [2, [3, [4]]]].flat(2); // -> [1, 2, 3, [4]]
// 2번 평탄화한 것과 동일하다.
[1, [2, [3, [4]]]].flat().flat(); // -> [1, 2, 3, [4]]
// 중첩 배열을 평탄화하기 위한 깊이 값을 Infinity로 지정하여 중첩 배열 모두를 평탄화한다.
[1, [2, [3, [4]]]].flat(Infinity); // -> [1, 2, 3, 4]