배열이란?
- 배열이란 여러개의 값을 순차적으로 나열한 자료구조.
- 배열의 값은 요소(element)라고 부름.
- 배열은 length 프로퍼티를 가짐
- 배열은 배열 리터럴, Array 생성자 함수, Array.of, Array.from 메서드로 생성할 수 있음.
자바스크립트 배열은 배열이 아니다
- 자료구조에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말함.
- 이러한 배열을 밀집 배열이라고 함.
- 하지만 자바스크립트의 배열은
- 배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되며
- 연속적으로 이어져있지 않을 수도 있다.
- 이렇게 연속적이지 않은 배열을 희소 배열이라고 한다.
- 자바스크립트의 배열은 일반적인 배열의 동작을 흉내낸 특수한 객체이다.
- 배열의 요소는 사실 프로퍼티 값이다.
- 어떤 타입의 값이라도 배열의 요소가 될 수 있다.
일반적인 배열과 자바스크립트 배열의 장단점
- 일반적인 배열은 인덱스로 요소에 빠르게 접근하지만, 삽입과 삭제 시에는 비효율적이다.
- 자바스크립트 배열은 해시테이블로 구현된 객체이므로, 인덱스로 요소에 접근하는 경우 느리지만, 요소를 삽입 삭제하는 경우 일반 배열보다 빠르다.
- 인덱스로 배열에 요소에 접근할 때 일반적인 배열보다 느릴 수 밖에 없는 단점을 보완하려고
- 일반 객체보다는 더 배열처럼 동작하도록 최적화하긴 했다.
length 프로퍼티와 희소 배열
- length 프로퍼티에 임의의 숫자 값을 명시적으로 할당할 수 있다.
- 기존 length값보다 작은 값을 할당하면 배열의 길이가 줄어든다.
- 큰 값을 할당하면 배열의 길이가 늘어나진 않고, 희소 배열이 된다.
- 큰 값을 할당한 뒤 console.log로 찍어보면
[1, empty * 2]와 같이 있는 듯 보이지만, 실제 배열에는 아무런 변함이 없다. 값 없이 비어있는 요소를 위해 메모리 공간을 확보하지 않고, 빈 요소를 생성하지도 않는다. - 희소 배열은 요소가 연속적이지 않고, 일부가 비어있는 배열이다.
- 희소 배열은 length와 배열 요소의 개수가 일치하지 않는다. length가 개수보다 항상 ㅋ다.
- 되도록 사용하지 않는 것이 좋다.
- 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선이다.
배열 생성
new Array(): 전달된 인수가 2개 이상이거나, 숫자가 아닌 경우 인수를 요소로 갖는 배열 생성Array.of(): ES6 도입, 전달된 인수가 1개이고, 숫자이더라도 배열 생성 가능Array.from(): ES6 도입, 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환.- 두번째 인수로 전달한 콜백함수를 통해 값을 만들면서 요소를 채울 수 있음.
(_, i) => i를 콜백으로 전달하면 요소 값으로 인덱스에 해당하는 값이 할당됨.
배열 요소의 삭제(delete와 splice)
- delete 연산자로 삭제하면 배열은 희소 배열이 된다. length값도 변하지 않는다.
- 따라서, Array.prototype.splice를 사용하자.
arr.splice(1, 1);arr[1]부터 1개의 요소를 제거
배열 메서드
- 결과물을 반환하는 패턴이 두 가지이다.
- 원본 배열을 직접 변경하는 메서드 : mutator method
- 원본 배열 직접 변경 안하고, 새로운 배열 생성하여 반환하는 메서드 : accessor method
Array.prototype.indexOf
- 원본 배열에 인수로 전달된 요소를 검색하여 인덱스를 반환한다.
- 인수로 전달한 요소가 여러 개 있다면 첫 번째로 검색된 요소의 인덱스를 반환한다.
- 원본 배열에 인수로 전달한 요소가 없으면 -1을 반환한다.
if (foods.indexOf('Orange') === -1) { foods.push('Orange'); } - ES7에서 가독성 더 좋게 쓸 수 있는 Array.prototype.includes 메서드가 도입됐다.
if (!foods.includes('Orange')) { foods.push('Orange'); }
Array.prototype.push ⭐️
- push메서드는 성능 면에서 좋지 않다.
- 마지막 요소로 추가할 요소가 하나뿐이면 push보다 length를 이용하자. 훨씬 빠르다.
const arr = [1, 2]; arr[arr.length] = 3; console.log(arr); // [1, 2, 3] - ES6의 스프레드 문법을 사용하는 것도 좋다. (원본 배열을 직접 변경하지 않으므로)
const arr = [1, 2]; const newArr = [...arr, 3]; - 맨 앞에 요소를 추가하는 unshift의 경우에도 스프레드 문법을 사용하는 것이 좋다.
const arr = [2, 3]; const newArr = [1, ...arr];
Array.prototype.concat
- 인수로 전달된 값들을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환
- 원본 배열은 변경되지 않음
Array.prototype.splice
- 원본 배열의 중간에 요소를 추가하거나, 중간에 있는 요소를 제거하는 경우 사용
- 매개변수
- 제거 시작 인덱스
- 제거할 요소의 개수
- 삽입할 요소들의 목록
- 두 번째 인수를 0으로 지정하면 아무런 요소도 제거하지 않고 세 번째 요소를 삽입함.
- 두 번째 인수를 생략하면 첫 번째 인수로 전달된 시작 인덱스부터 모든 요소를 제거함.
- 세 번째 요소를 전달하지 않으면 제거만 함.
Arrray.prototype.slice
- 인수로 전달된 범위의 요소들을 복사하여 배열로 반환함.
- 원본 배열은 변경되지 않음
- 첫 번째 인수로 전달받은 인덱스부터 모든 요소를 복사하여 배열로 반환함.
- 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환함. (얕은 복사)
Array.prototype.join
- 원본 배열의 모든 요소를 문자열로 변환한 후, 구분자로 연결한 문자열을 반환함.
- 기본 구분자는
,콤마다.
Array.prototype.reverse
- reverse 메서드는 순서를 반대로 뒤집는다.
- 원본 배열이 변경된다.
Array.prototype.fill
- 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채운다.
Array.prototype.includes
- 특정 요소가 포함되어 있는지 확인하여 true 또는 false를 반환한다.
Array.prototype.flat
- ES10(2019)에서 도입되었다.
- 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화한다.
- 평탄화할 깊이를 인수로 전달할 수 있는데, 생략하면 기본값은 1이다.
Infinity를 주면 모두 평탄화한다.[1, [2, [3, [4]]]].flat(); // -> [1, 2,[3, [4]]] [1, [2, [3, [4]]]].flat(2); // -> [1, 2, 3, [4]] [1, [2, [3, [4]]]].flat().flat(); // -> [1, 2, 3, [4]] [1, [2, [3, [4]]]].flat(Infinity); // -> [1, 2, 3, 4]
배열 고차 함수
- 고차함수
- 함수를 인수로 전달받거나 함수를 반환하는 함수
- JS의 함수는 일급 객체이므로 함수를 갚처럼 인수로 전달, 반환이 가능함.
- 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에 기반을 둠.
- 함수형 프로그래밍
- 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임.
- ✏️ 특히 배열에서는 매우 유용한 고차함수들을 제공하기 때문에 알아두면 좋다!
Array.prototype.sort
- 원본 배열을 직접 변경하여 정렬된 배열을 반환한다.
- 기본적으로 오름차순이다.
- 내림차순으로 정렬하려면
reverse메서드를 사용해서 뒤집어야 한다.
🚨 주의할 점!
- 숫자 요소로 이루어진 배열을 정렬할 때는 주의가 필요하다.
const points = [40, 100, 1, 5, 2, 25, 10]; points.sort(); console.log(points); // [1, 10, 100, 2, 25, 40, 5] - 의도한대로 정렬되지 않는다...😮💥
- sort 메서드는 배열의 요소를 일시적으로 문자열로 변환한 후 정렬한다.
- '1'은 유니코드로
U+0031이다 - '2'는
U+0032다. - '10'은
U+0031U+0030이다. - -> 그래서
['2', '10']를 정렬하면['10', '2']가 된다.
- '1'은 유니코드로
- 따라서, 숫자 요소를 정렬할 때에는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 한다.
- 비교 함수의 반환값이 0보다 작으면 첫 번째 인수가 앞으로 온다.
const points = [40, 100, 1, 5, 2, 25, 10]; points.sort((a, b) => a - b); // 오름차순 points.sort((a, b) => b - a); // 내림차순 points.sort((a, b) => a < b); // ❌ -> 이렇게 쓰면 안된다. - 비교 함수는 양수나 음수 또는 0을 반환해야 한다. 그래서 위 예제의 마지막줄이 안된다.
객체의 정렬
const todos = [
{id : 3, name : "JS"},
{id : 1, name : "HTML"},
{id : 2, name : "CSS"}
]
function compare(key) {
return (a, b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
}
todos.sort(compare('id')); // id를 기준으로 오름차순 정렬
Array.prototype.forEach
- 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출한다.
- 콜백함수는 일만 함수로 호출되므로, 내부의 this는 undefined를 가리킨다. (클래스 내부의 모든 코드에서는 암묵적으로 strict mode가 적용되기 때문이다.)
- this를 사용하고 싶다면 직접 전달해주거나, 화살표 함수를 사용하면 된다. 화살표 함수를 쓰자.
- 화살표 함수를 사용하면 상위 스코프의 this를 그대로 참조한다.
- break, continue문을 사용할 수 없다.
- 희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외된다. (map, filter, reduce도 마찬가지다.)
- for문에 비해 성능은 떨어지지만, 가독성은 더 좋다. 높은 성능이 필요한 경우가 아니면 forEach 메서드를 쓰는 것을 권장한다.
Array.prototype.map
- 배열의 보든 요소를 순회하며 콜백 함수를 실행하고, 그 반환값들로 구성된 새로운 배열을 반환한다.
- 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차함수다.
- forEach와 동일하게 this 접근은 명시해주어야 하니 화살표 함수를 쓰자.
Array.prototype.filter
- 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
- forEach, map과 동일하게 this 접근은 명시해주어야 하니 화살표 함수를 쓰자.
Array.prototype.reduce
- 얘는 좀 더 어렵다. 집중해서 보자.
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다.
- 그리고 콜백 함수의 반환값을 다음 순회 시에 첫 번째 인수로 전달하면서 콜백 함수를 호출하고
- 하나의 결과값을 만들어 반환한다.
// 1 ~ 4까지 누적합
const sum = [1, 2, 3, 4];
sum.reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);
console.log(sum); // 10
accumulator: 초기값 또는 콜백 함수의 이전 반환값currentValue: reduce 메서드를 호출한 배열의 요소값index: reduce 메서드를 호출한 배열의 인덱스array: reduce 메서드를 호출한 배열 자체 (this)- , 초기값 : 생략이 가능하지만 전달하는 것이 안전하므로, 전달하자.
여러 방법으로 활용이 가능한데, 몇 가지만 살펴보자.
평균 구하기
const sum = [1, 2, 3, 4, 5, 6];
const average = values.reduce((acc, cur, i, { length }) => {
return i === length - 1 ? (acc + cur) / length : acc + cur;
}, 0);
console.log(average); // 3.5
중복 횟수 구하기
const fruits = ['banana', 'apple', 'orange', 'orange', 'apple'];
const count = fruits.reduce((acc, cur) => {
acc[cur] = (acc[cur] || 0) + 1;
return acc;
}, {});
console.log(count); // {banana: 1, apple: 2, orange: 2}
중복 요소 제거하기
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
const result = values.reduce(
(unique, val, i, _values) =>
_values.indexOf(val) === i ? [...unique, val] : unique,
[]
);
console.log(result); // [1, 2, 3, 5, 4];
- 사실 이건 filter를 쓰는게 더 직관적이다.
- 저자는 set을 사용하는 방법을 추천한다.
이외에도 reduce로 다음과 같은 작업을 할 수 있다.
- 최대값, 최소값
- 중첩 배열 평탄화
'Language > JavaScript' 카테고리의 다른 글
| [비동기] async, await과 구 방식 비교 (1) | 2025.03.24 |
|---|---|
| [JS] 초기화된 배열 만들기 (0) | 2025.03.21 |
| forEach()에서 break가 안된다면 some()과 every() (0) | 2025.03.21 |
| [JS] 구조 분해 할당(디스트럭처링 할당) (0) | 2023.11.05 |
| [JS] 단축 평가 (0) | 2023.02.10 |