동그란 도그린
[모던 자바스크립트 Deep Dive🦎] 27장 : 배열 본문
27-1. 배열이란?
- 여러 개의 값을 순차적으로 나열한 자료구조
- 배열은 배열의 길이를 나타내는 length 프로퍼티를 가짐
- 자바스크립트에는 배열이라는 타입 존재 X, 배열은 객체 타입
- 배열은 배열 리터럴, Array 생성자 함수, Array.of, Array.from 메서드로 생성 가능
- Array.prototype은 배열을 위한 빌트인 메서드 제공
- 배열은 객체지만, 일반 객체와는 다른 특징이 있음
27-2. 자바스크립트 배열은 배열이 아니다
- 일반 배열 vs 자바스크립트 배열
- 자료구조에서 말하는 배열
- 밀집 배열
- 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조
- 배열의 요소는 하나의 데이터 타입으로 통일되어 있으며 서로 연속적으로 인접
- 요소에 빠르게 접근 가능하다는 장점
- 배열에 요소를 삽입 및 삭제하는 경우, 배열의 요소를 연속적으로 유지하기 위해 요소를 이동해야 하는 단점
- 자바스크립트의 배열
- 희소 배열
- 요소를 위한 각각의 메모리 공간은 동일한 크기 갖지 않아도 됨
- 연속적으로 이어져 있지 않을 수 있음
- 일반적인 배열의 동작을 흉내 낸 특수 객체
- 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며, length 프로퍼티를 갖는 특수 객체
- 배열 요소는 사실 프로퍼티 값 ⇒ 어떤 값이라도 배열의 요소가 될 수 있음
- 해시 테이블로 구현된 객체
- 인덱스로 요소에 접근하는 경우 일반 배열보다 느리다는 단점
- 요소를 삽입 및 삭제하는 경우 더 빠르다는 장점
- 자료구조에서 말하는 배열
- 모던 자바스크립트 엔진은 배열을 일반 객체와 구별하여 더 배열처럼 동작하도록 구현함 ⇒ 배열이 일반 객체보다 약 2배 빠르다
27-3. length 프로퍼티와 희소 배열
- length 프로퍼티의 값
- 0과 2^23 -1 (4,294,967,296 - 1) 사이의 양의 정수
- 배열에 요소를 추가 및 삭제하면 자동 갱신됨
- 현재 length 프로퍼티 값보다 작은 숫자 할당하면, 배열의 길이 줄어듦
- 현재 length 프로퍼티 값보다 큰 숫자를 할당하면, length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나진 X
- 희소 배열의 length는 희소 배열의 실제 요소 개수보다 언제나 크다
const arr = [1, 2, 3, 4, 5];
arr.length = 3;
console.log(arr); // [1, 2, 3]
const arr = [1];
arr.length = 3;
console.log(arr.length); // 3
console.log(arr); // [1, empty * 2];
// arr[1]과 arr[2]에는 값이 존재하지 않음
27-4. 배열 생성
- 다양한 생성 방식
- 배열 리터럴 (가장 일반적, 간편)
- 0개 이상의 요소를 쉼표로 구분하여 대괄호로 묶음
- 객체 리터럴과는 달리 프로퍼티 키가 없고 값만 존재
const arr = [1, , 3]; console.log(arr.length); // 3 console.log(arr[1]); // undefined : 프로퍼티 키가 1인 프로퍼티가 존재하지 않으므로
- Array 생성자 함수
- 전달된 인수의 개수에 따라 다르게 동작하므로 주의가 필요
- 전달된 인수가 범위를 벗어나면 RangeError가 발생
- 전달된 인수가 2개 이상이거나 숫자가 아닌 경우, 인수를 요소로 갖는 배열을 생성
- Array 생성자 함수는 new 연산자와 함께 호출하지 않아도 생성자 함수로 동작 ⇒ Array 생성자 함수 내부에서 new.target을 확인하기 때문
const arr = new Array(10); console.log(arr); // [empty * 10]; new Array(4294967296); // RangeError: Invalid array length new Array({}); // [{}] Array(1, 2, 3); // [1, 2, 3]
- Array.of
- 전달된 인수를 요소로 갖는 배열을 생성
Array.of(1); // [1] Array.of(1, 2, 3); // [1, 2, 3] Array.of('string'); // ['string']
- Array.from
- 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환
- 두 번째 인수로 전달한 콜백 함수를 통해 값을 만들면서 요소를 채울 수 있음
- 두 번째 인수로 전달한 콜백 함수에 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하면서 호출하고, 콜백 함수의 반환값으로 구성된 배열을 반환
Array.from({ length: 2, 0: 'a', 1: 'b' }); // ['a', 'b'] Array.from('Hello'); // ['H', 'e', 'l', 'l', 'o'] Array.from({ length: 3}); // [undefined, undefined, undefined] Array.from({ length: 3}, (_,i) => i); // => [0, 1, 2]
- 배열 리터럴 (가장 일반적, 간편)
※ 유사 배열 객체
: 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체
27-5. 배열 요소의 참조
- 배열의 요소를 참조할 때에는 대괄호([ ]) 표기법을 사용
- 배열(희소 배열도)은 존재하지 않는 요소를 참조하면 undefined를 반환
27-6. 배열 요소의 추가와 갱신
- 배열에 요소를 동적으로 추가 가능
- 존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가됨 ( 이때, length 프로퍼티 값은 자동 갱신)
- length 프로퍼티 값보다 큰 인덱스로 새로운 요소를 추가하면 희소배열이 됨
- 이미 요소가 존재하는 요소에 값을 재할당하면 요소 값이 갱신됨
- 정수 이외의 값을 인덱스처럼 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성되고, 추가된 프로퍼티는 length 프로퍼티 값에 영향을 주지 X
const arr = [];
// 배열 요소 추가
arr[0] = 1;
arr['1'] = 2;
// 프로퍼티 추가
arr['foo'] = 3;
arr.bar = 4;
arr[1.1] = 5;
console.log(arr); // [1, 2, foo: 3, bar: 4, '1.1': 5]
console.log(arr.length); // 2 : 프로퍼티는 length에 영향 주지 X
27-7. 배열 요소의 삭제
- 배열도 객체이기 때문에 배열의 특정 요소를 삭제하기 위해 delete 연산자 사용 가능
- 희소 배열을 만드는 delete 연산자는 사용하지 않는 게 좋음
- 희소 배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 Array.prototype.splice 메서드를 사용
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, empty, 3]
console.log(arr.length); // 3 : lengthm 프로퍼티에 영향 주지 X, 희소 배열이 됨
const arr = [1, 2, 3];
// Array.prototype.splice(삭제를 시작할 인덱스, 삭제할 요소 수)
arr.splice(1,1);
console.log(arr); // [1, 3]
console.log(arr.length); // 2
27-8. 배열 메서드
- Array 생성자 함수는 정적 메서드를 제공하고, 배열 객체의 프로토타입인 Array.prototype은 프로토타입 메서드를 제공
- 배열 메서드는 결과물을 반환하는 패턴이 2가지이므로 주의가 필요
- 원본 배열을 직접 변경하는 메서드 (초창기 배열 메서드는 원본 배열을 직접 변경하는 경우가 많음)
- 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드
⇒ 가급적이면 원본 배열을 직접 변경하지 않는 메서드를 사용하는 게 좋음
27-8-1. Array.isArray
- Array 생성자 함수의 정적 메서드
- 전달된 인수가 배열이면 true, 배열이 아니면 false를 반환
// true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());
// false
Array.isArray();
Array.isArray({});
27-8-2. Array.prototype.indexOf
- 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환
- 중복되는 요소가 여러 개 있으면 첫 번째로 검색된 요소의 인덱스를 반환
- 존재하지 않으면 -1 반환
- indexOf 메서드 대신 Array.prototype.includes 메서드를 사용하면 가독성이 더 좋음
const foods = ['apple', 'banana', 'orange'];
// indexOf 메서드
if (foods.indexOf('orange') === -1) {
foods.push('orange');
}
// includes 메서드
if(!foods.includes('orange')) {
foods.push('orange');
}
27-8-3. Array.prototype.push
- push 메서드는 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가하고, 변경된 length 프로퍼티 값을 반환
- 원본 배열을 직접 변경
- push 메서드를 사용하는 것보다는 length 프로퍼티를 사용하여 배열의 마지막에 요소를 직접 추가하는 게 더 빠름
- push 메서드를 사용하는 것보다는 ES6의 스프레드 문법을 사용하는 게 좋음
// push 메서드
const arr = [1, 2];
let result = arr.push(3, 4);
console.log(result); // 4
console.log(arr); // [1, 2, 3, 4]
// 스프레드 문법
const arr = [1, 2];
const newArr = [...arr, 3];
console.log(newArr); // [1, 2, 3]
27-8-4. Array.prototype.pop
- 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환
- 빈 배열이면 undefined를 반환
- 원본 배열을 직접 변경
const arr = [1, 2];
let result = arr.pop();
console.log(result); // 2 : 제거한 요소
console.log(arr); // [1]
27-8-5. Array.prototype.unshift
- 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가하고 변경된 length 프로퍼티 값을 반환
- 원본 배열을 직접 변경
- unshift 메서드보다는 ES6의 스프레드 문법을 사용하는 게 좋음
// unshift 메서드
const arr = [1, 2];
let result = arr.unshift(3, 4);
console.log(result); // 4
console.log(arr); // [3, 4, 1, 2]
// 스프레드 문법
const arr = [1, 2];
const newArr = [3, ...arr];
console.log(newArr); // [3, 1, 2]
27-8-6. Array.prototype.shift
- 원본 배열에서 첫 번째 요소를 제거하고 제거한 요소를 반환
- 빈 배열이면 undefined를 반환
- 원본 배열을 직접 변경
- shift 메서드와 push 메서드 사용하면 큐를 쉽게 구현 가능
const arr = [1, 2];
let result = arr.shift();
console.log(result); // 1
console.log(arr); // [2]
27-8-7. Array.prototype.concat
- 인수로 전달된 값들을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환
- 인수로 전달한 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가
- 원본 배열은 변경 X
const arr1 = [1, 2];
const arr2 = [3, 4];
let result = arr1.concat(arr2);
console.log(result); // [1, 2, 3, 4]
result = arr1.concat(3);
console.log(result); // [1, 2, 3]
console.log(arr1); // [1, 2] : 원본 배열은 변경 X
- push 와 unshift 메서드는 concat 메서드로 대체 가능 ⇒ 미묘한 차이가 있음
push와 unshift 메서드 concat 메서드 원본 배열 직접 변경 원본 배열 변경 X, 새로운 배열 반환 원본 배열을 반드시 변수에 저장해두어야 함 반환값을 반드시 변수에 할당받아야 함
- concat 메서드는 ES6의 스프레드 문법으로 대체 가능
⇒ push, unshift, concat 메서드보다는 ES6의 스프레드 문법을 일관성 있게 사용하는 것을 권장// concat 메서드 let result = [1, 2].concat([3, 4]); console.log(result); // [1, 2, 3, 4] // 스프레드 문법 result = [...[1,2], ...[3, 4]]; console.log(result); // [1, 2, 3, 4]
27-8-8. Array.prototype.splice
- 원본 배열의 중간에 요소를 추가하거나 중간에 있는 요소를 제거
- 제거한 요소가 배열로 반환됨
- 원본 배열을 직접 변경
- 3개의 매개변수가 있음
- start
- 원본 배열의 요소를 제거하기 시작할 인덱스
- start만 지정하면 원본 배열의 start부터 모든 요소 제거
- 음수인 경우 배열 끝에서의 인덱스를 나타냄 (-1이면 마지막 요소, -n이면 마지막에서 n번째 요소)
- deleteCount (옵션)
- start부터 제거할 요소의 개수
- 0인 경우 아무런 요소도 제거되지 않음
- items (옵션)
- 제거한 위치에 삽입할 요소들의 목록
- 생략하면, 원본 배열에서 요소들을 제거만 함
- start
const arr = [1, 2, 3, 4];
// 인덱스 1부터 2개의 요소를 제거하고, 그 자리에 새로운 요소 20과 30을 삽입
const result = arr.splice(1, 2, 20, 30);
console.log(result); // [2, 3] : 제거한 요소가 배열로 반환됨
console.log(arr); // [1, 20, 30, 4]
- 배열에서 특정 요소 제거하려면 indexOf 메서드를 통해 특정 요소의 인덱스를 취득한 후 splice 메서드를 사용
const arr = [1, 2, 3, 1, 2];
// array에서 item 요소 제거, item 요소가 여러개 있으면 첫 번째 요소만 제거
function remove(array, item) {
// 제거할 item 요소의 인덱스를 취득
const index = array.indexOf(item);
// 제거할 item 요소가 있다면 제거
if (index !== -1) array.splice(index, 1);
return array;
}
console.log(remove(arr,2)); // [1, 3, 1, 2]
console.log(remove(arr,10)); // [1, 3, 1, 2]
- filter 메서드를 통해 특정 요소를 제거할 수도 있으나, 특정 요소가 중복된 경우 모두 제거됨
const arr = [1, 2, 3, 1, 2];
function removeAll(array, item) {
return array.filter(v => v !== item);
}
console.log(removeAll(arr, 2)); // [1, 3, 1]
27-8-9. Array.prototype.slice
- 인수로 전달된 범위의 요소들을 복사하여 배열로 반환
- 원본 배열은 변경되지 X
- 2개의 매개변수 가짐
- start
- 복사를 시작할 인덱스
- 음수이면, 배열 끝에서의 인덱스를 나타냄
- end (생략 가능)
- 복사를 종료할 인덱스
- 이 인덱스에 해당되는 요소는 복사되지 X
- 생략 시 기본 값은 length 프로퍼티 값
- start
const arr = [1, 2, 3];
arr.slice(0, 1); // [1]
arr.slice(1, 2); // [2]
console.log(arr); // [1, 2, 3] : 원본 배열은 변경 X
27-8-10. Array.prototype.join
- 원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달반은 문자열(구분자)로 연결한 문자를 반환
- 구분자는 생략 가능
- 기분 구분자는 콤마( , )
const arr = [1, 2, 3, 4];
// 구분자 생략 시 기분 구분자로 연결
arr.join(); // '1,2,3,4'
// 빈 문자열로 연결한 문자열
arr.join(''); // '1234'
// :로 연결한 문자열
arr.join(':'); // '1:2:3:4'
27-8-11. Array.prototype.reverse
- 원본 배열의 순서를 반대로 뒤집음
- 원본 배열 변경됨
- 반환 값은 변경된 배열
const arr = [1, 2, 3];
const result = arr.reverse();
console.log(arr); // [3, 2, 1]
console.log(result); // [3, 2, 1]
27-8-12. Array.prototype.fill
- 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채움
- 원본 배열 변경됨
- 첫 번째 인수 : 배열을 채울 값
- 두 번째 인수 : 요소 채우기를 시작할 인덱스
- 세 번째 인수 : 요소 채우기를 멈출 인덱스
const arr = [1, 2, 3];
arr.fill(0);
console.log(arr); // [0, 0, 0]
- fill 메서드를 사용할 경우, 모든 요소를 하나의 값만으로 채울 수 밖에 없다는 단점
- Array.from 메서드를 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 요소 값을 만들면서 배열을 채울 수 있다는 장점
const sequences = (length = 0) => Array.from({length}, (_, i) => i);
console.log(sequences(3)); // [0, 1, 2]
27-8-13. Array.prototype.includes
- 배열 내에 특정 요소가 포함되어 있는지 확인하여 true나 false 반환
- 첫 번째 인수 : 검색할 대상 지정
- 두 번째 인수 : 검색 시작할 인덱스 (생략 시 기본 값 0)
- 두 번째 인수에 음수를 전달하면 length 프로퍼티 값과 인덱스를 합산하여 (length+index)로 검색 시작 인덱스를 설정
const arr = [1, 2, 3];
arr.includes(2); // true
arr.includes(1, 1); // false
arr.includes(3, -1); // true : 인덱스 2부터 검색
- indexOf 메서드를 사용하면 반환 값이 -1인지 확인해야 하고, NaN이 포함되어 있는지 확인할 수 없다는 단점
[NaN].indexOf(NaN) !== -1; // false
[NaN].includes(NaN); // true
27-8-14. Array.prototype.flat
- 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화
- 중첩 배열을 평탄화할 깊이를 인수로 전달할 수 있음 (생략 시 기본 값 1)
- 인수로 Infinity를 전달하면 중첩 배열 모두를 평탄화함
[1, [2, 3, 4, 5]].flat(); // [1, 2, 3, 4, 5]
[1, [2, [3, [4]]]].flat(); // [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
27-9. 배열 고차 함수
- 고차 함수
- 함수를 인수로 전달받거나 함수를 반환하는 함수
- 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에 기반을 두고 있음
- ※ 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임
- 자바스크립트는 고차 함수를 다수 지원함
- 특히 배열은 매우 유용한 고차 함수를 제공
27-9-1. Array.prototype.sort
- 배열의 요소를 정렬
- 원본 배열을 직접 변경
- 정렬된 배열을 반환
- 기본적으로 오름차순으로 정렬
const fruits = ['Banana', 'Orange', 'Apple'];
fruits.sort();
console.log(fruits); // ['Apple', 'Banana', 'Orange']
- 내림차순으로 정렬하려면 sort 메소드를 통해 오름차순으로 정렬한 뒤 reverse 메서드를 통해 요소의 순서를 뒤집으면 됨
const fruits = ['Banana', 'Orange', 'Apple'];
// 오름차순 정렬
fruits.sort();
console.log(fruits); // ['Apple', 'Banana', 'Orange']
// 내림차순 정렬
fruits.reverse();
console.log(fruits); // ['Orange', 'Banana', 'Apple']
- 숫자 요소로 이루어진 배열 정렬 시 주의 필요
- sort 메서드의 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따르므로 배열 요소를 일시적으로 문자열로 변환 후 유니코드 코드 포인트 순서를 기준으로 정렬함
const points = [40, 100, 1, 5, 2, 25, 10];
points.sort();
console.log(points); // [1, 10, 100, 2, 25, 40, 5]
- 따라서 숫자 요소 정렬 시에는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 함
const points = [40, 100, 1, 5, 2, 25, 10];
points.sort((a, b) => a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]
points.sort((a, b) => b - a);
consote.log(points); // [100, 40, 25, 10, 5, 2, 1]
27-9-2. Array.prototype.forEach
- for문을 대체할 수 있는 고차 함수
- 원본 배열 변경 X (콜백 함수를 통해 원본 배열을 변경할 수는 있음)
- 반환 값은 언제나 undefined
- 자신의 내부에서 반복문을 실행함
- 반복문을 추상화한 고차 함수로, 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출
const numbers = [1, 2, 3];
const pows = [];
numbers.forEach(item => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]
- forEach 메서드는 콜백 함수를 호출할 때 3개의 인수를 전달함
- forEach 메서드를 호출한 배열의 요소 값
- forEach 메서드를 호출한 배열의 요소의 인덱스
- forEach 메서드를 호출한 배열
[1, 2, 3].forEach((item, index, arr) => { console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`); }); /* 요소값: 1, 인덱스: 0, this: [1, 2, 3] 요소값: 2, 인덱스: 1, this: [1, 2, 3] 요소값: 3, 인덱스: 2, this: [1, 2, 3] */
- forEach 메서드는 for문과는 달리 break, continue 문을 사용할 수 X
- 즉, 배열의 모든 요소를 빠짐없이 모두 순회하며 중간에 순회를 중단할 수 없음
- 희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외됨
- forEach 메서드는 for문에 비해 성능은 안 좋지만, 가독성은 좋음
⇒ 많은 배열을 순회하는 경우, 복잡한 코드, 높은 성능이 필요한 경우가 아니면 for문보다는 forEach 메서드를 사용하는 편이 좋음
'FrontEnd > Javascript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive🦎] 33장 : Symbol (0) | 2023.08.26 |
---|---|
[모던 자바스크립트 Deep Dive🦎] 32장 : String (2) | 2023.08.26 |
[모던 자바스크립트 Deep Dive🦎] 25장 : 클래스 (0) | 2023.07.09 |
[모던 자바스크립트 Deep Dive🦎] 19장 : 프로토타입 (0) | 2023.05.27 |
[모던 자바스크립트 Deep Dive🦎] 18장 : 함수와 일급 객체 (0) | 2023.05.21 |
Comments