본문 바로가기

2022-2 웹개발 스터디

[모던 JS] CH27. 배열 (#15)

CH27. 배열

27.1 배열이란?

- 배열이 가지고 있는 값: 요소

- 모든 값은 배열의 요소가 될 수 있음

- 인덱스를 가짐

- 대괄호 내에 접근하고 싶은 요소의 인덱스 지정

- 배열의 길이를 나타내는 length 프로퍼티를 가짐

const arr = ['apple', 'banana', 'orange'];

 

- for 문을 통해 순차적으로 요소에 접근할 수 있음

- JS에 배열이라는 타입은 존재하지 않음 (배열은 객체 타입)

- 배열의 프로토타입 객체는 Array.prototype

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

typeof arr // object

 

- 일반 객체와 구별되는 배열의 특징

 

! 배열의 장점- 처음부터 순차적으로 요소에 접근 가능- 마지막부터 역순으로 요소 접근 가능- 특정 위치 접근 가능

27.2 자바스크립트 배열은 배열이 아니다

- 배열의 요소는 하나의 데이터 타입으로 통일되어 있으며, 서로 연속적으로 인접해 있음 (밀집 배열)- 일반적 의미의 배열은 각 요소가 동일한 데이터 크기를 가지며,   빈틈없이 연속적으로 이어져 있으므로 인덱스를 통해 단 한 번의 연산으로 임의의 요소에 접근할 수 있음.   (시간 복잡도 O(1)) : 효율적이고 고속으로 동작함

 

!! JS 배열은 이런 일반적인 의미의 배열과 다름- 배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 가지지 않아도 되며,   연속적으로 이어져 있지 않을 수도 있음 (희소배열)

const arr = [
    'string',
    10,
    true,
    NaN,
    [],
    {},
    function() {}
]; // 어떤 타입의 값이라도 배열의 요소가 될 수 있음

27.3 length 프로퍼티와 희소 배열

- 배열의 길이를 나타내는 0 이상의 정수를 값으로 가짐

- 배열에 요소를 추가하거나 삭제하면 자동 갱신

- 현재 프로퍼티 값보다 작은 숫자 값을 할당하면 배열의 길이가 줄어듦

const arr = [1, 2, 3, 4, 5];

arr.length = 3;
console.log(arr); // [1, 2, 3]

 

- length 프로퍼티 값보다 큰 숫자 값을 할당하는 경우?

: length 프로퍼티 값은 변경되지만, 실제 배열의 길이가 늘어나지는 않음

const arr = [1];

arr.length = 3;
console.log(arr.length);
console.log(arr); // [1, empty * 2]

 

- 배열의 요소가 연속적으로 위치하지 않고 일부가 비어 있는 배열: 희소 배열

- 중간이나 앞 부분이 비어 있을 수 있음

- 희소배열은 length와 배열 요소의 개수가 일치하지 않음

- 희소배열의 length는 희소 배열의 실제 요소 개수보다 언제나 큼

- 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선

27.4 배열 생성

27.4.1 배열 리터럴

- 배열 리터럴은 0개 이상의 요소를 쉼표로 구분하고 []로 묶는 것

- 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재함

const arr = [1, 2, 3];
console.log(arr.length); // 3

const arr = [];
console.log(arr.length); // 0

// 배열 리터럴에 요소를 생략하면 희소 배열이 생성됨
const arr = [1, , 3];
console.log(arr.length); // 3
console.log(arr); // [1, empty, 3]
console.log(arr[1]); // undefined

27.4.2 Array 생성자 함수

- 전달된 인수의 개수에 따라 다르게 동작함

const arr = new Array(10);

console.log(arr); // [empty * 10]
console.log(arr.length); // 10

// ===========================
// 배열은 요소를 최대 2^32 - 1 개 가질 수 있음
// 전달된 인수가 음수면 에러 발생
new Array(-1) // RangeError

// 전달된 인수가 없는 경우 빈 배열 생성
new Array(); // []

// 전달된 인수가 1개지만, 숫자가 아니면 인수를 요소로 갖는 배열을 생성함
new Array({}); // [{}]

27.4.3 Array.of

- Array 생성자 함수와 다르게 전달된 인수가 1개이고, 숫자이더라도 인수를 요소로 갖는 배열을 생성함

Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of(1); // ['string']

27.4.4 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]

27.5 배열 요소의 참조

- 대괄호 안 인덱스로 접근

- 존재하지 않는 요소에 접근하면 undefined가 반환됨

const arr = [1, 2];

console.log(arr[0]); // 1
console.log(arr[2]); // undefined

27.6 배열 요소의 추가와 갱신

- 배열에도 요소를 동적으로 추가가능

- 존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가됨

- length 프로퍼티 값은 자동 갱신됨

- 만약, length 프로퍼티 값보다 큰 인덱스로 새로운 요소를 추가하면 희소 배열이 됨

- 이때, 인덱스로 요소에 접근하여 명시적으로 값을 할당하지 않은 요소는 생성되지 않음

- 이미 요소가 존재하는 요소에 값을 재할당하면 갱신됨

const arr = []

// 배열 요소의 추가
arr[0] = 1;
arr['1'] = 3;

// 프로퍼티 추가
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]

// 프로퍼티는 Length에 영향을 주지 않음
console.log(arr.length); // 2

27.7 배열 요소의 삭제

- delete 연산자 사용 -> 객체의 프로퍼티를 삭제함 -> 희소배열 만들어짐 -> 사용하지 않는 것이 좋음

- 희소배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 Array.prototype.splice 메서드 사용

const arr = [1, 2, 3];

delete arr[1];
console.log(arr); // [1, empty, 3]
console.log(arr.length); // 3

// ========
const arr = [1, 2, 3];
arr.splice(1, 1); // arr[1] 부터 1개의 요소를 제거
console.log(arr); // [1, 3]
console.log(arr.length); // 2

27.8 배열 메서드

- 원본 배열을 직접 변경하는 메서드와 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드가 있음

const arr = [1];

// push 는 원본 배열을 직접 변경
arr.push(2);
console.log(arr); // [1, 2]

// concat원본 배열을 직접 변경하지 않고 새로운 배열을 생성해 반환함
const res = arr.concat(3);
console.log(arr); // [1, 2]
console.log(res); // [1, 2, 3]

 

 ! 가급적 원본 배열을 직접 변경하지 않는 메서드를 사용하는 편이 좋음!

27.8.1 Array.isArray

: 전달된 인수가 배열이면 true, 아니면 false

 

27.8.2 Array.prototype.indexOf

: 원본 배열에서 인수로 전달된 요소를 검색해 인덱스 반환

- 중복되는 요소가 여러 개 있으면 첫 번째만 반환

- 존재하지 않으면 -1 반환

27.8.3 Array.prototype.push

- 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가함

- 변경된 length 프로퍼티 값을 반환

- 원본 배열을 직접 변경

const arr = [1, 2];

let res = arr.push(3, 4);
console.log(res); // 4

console.log(arr); // [1, 2, 3, 4]

 

- 성능 면에서 좋지 않으므로, 마지막 추가할 요소가 하나뿐이라면, push 말고 length 프로퍼티 사용하는 것이 좋음

const arr = [1, 2];

arr[arr.length] = 3;
console.log(arr); // [1, 2, 3]

27.8.4 Array.prototype.pop

- 마지막 요소를 제거하고 제거한 요소를 반환

- 원본이 빈 배열이었다면 undefined

- 직접 원본 배열을 변경함

const arr = [1, 2];

let res = arr.pop();
console.log(res); // 2

console.log(arr); // [1]

27.8.5 Array.prototype.unshift

- 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가

- 변경된 length 프로퍼티 값을 반환함

- 원본 배열을 직접 변경함

const arr = [1, 2];

let res = arr.unshift(3, 4);
console.log(res); // 4

console.log(arr); // [3, 4, 1, 2]

27.8.6 Array.prototype.shift

- 원본 배열에서 첫 번째 요소를 제거하고 제거한 요소를 반환함

- 원본이 빈 배열이었다면 undefined

- 직접 원본 배열을 변경함

const arr = [1, 2];

let res = arr.shift();
console.log(res); // 1

console.log(arr); // [2]

27.8.7 Array.prototype.concat

- 인수로 전달된 값들을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환함

- 인수로 전달한 값이 배열인 경우, 배열을 해체하여 새로운 배열의 요소로 추가함

- 원본 배열은 변경되지 않음

const arr1 = [1, 2];
const arr2 = [3, 4];

let res = arr1.concat(arr2);
console.log(res); // [1, 2, 3, 4]

res = arr1.concat(3);
console.log(res); // [1, 2, 3]

res = arr1.concat(arr2, 5);
console.log(res); // [1, 2, 3, 4, 5]

console.log(arr1); // 원본 배열은 변경 안됨 [1, 2]

 

- push와 unshift는 concat으로 대체 가능

: 원본 배열을 직접 변경하고 안 하고의 차이

 

- concat 메서드는 ES6의 스프레드 문법으로 대체 가능

- ES6의 스프레드 문법을 일관성 있게 사용하는 것을 권장

let res = [1, 2].concat([3, 4]);
console.log(res); // [1, 2, 3, 4]

res = [...[1, 2], ...[3, 4]];
console.log(res); // [1, 2, 3, 4]

27.8.8 Array.prototype.splice

- 원본 배열의 중간에 요소를 추가하거나 중간에 있는 요소를 제거하는 경우 사용

- 3개의 매개변수가 있으며, 원본 배열을 직접 변경함

- 제거할 요소의 개수를 0으로 지정하면 아무런 요소도 제거하지 않고 새로운 요소를 삽입

- 세 번째 인수인 추가할 요소들의 목록을 전달하지 않으면 원본 배열에서 지정된 요소를 제거만 함

- 두 번째 인수인 제거할 요소의 개수를 생략하면 첫 번째 인수로 전달된 시작 인덱스부터 모든 요소를 제거함

const arr = [1, 2, 3, 4];

// 원본 배열의 1부터 2개의 요소를 제거하고, 그 자리에 새로운 요소 20, 30을 삽입
const res = arr.splice(1, 2, 20, 30);

// 제거한 요소가 배열로 반환됨
console.log(res); // [2, 3]

// 원본 배열을 직접 변경함
console.log(arr); // [1, 20, 30, 4]

 

- 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

- 인수도 전달된 범위의 요소들을 복사하여 배열로 반환

- 원본 배열은 변경되지 않음

- 두 개의 매개변수

: start, 복사를 시작할 인덱스. 음수인 경우 배열의 끝 인덱스 의미

: end, 복사를 종료할 인덱스. 생략 가능하며, 기본값은 length 프로퍼티의 값

const arr = [1, 2, 3];

arr.slice(0, 1); // [1]
arr.slice(1, 2); // [2]

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, 4];
const res = arr.reverse();

console.log(arr); // [3, 2, 1]
console.log(res); // [3, 2, 1]

27.8.12 Array.prototype.fill

- 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채움

- 원본 배열 변경

- 두 번째 인수로 요소 채우기를 시작할 인덱스 전달

- 세 번째 인수로 요소 채우기를 멈출 인덱스 전달

const arr = [1, 2, 3];

// 인수로 전달받은 값 0을 배열의 처음부터 끝까지 요소로 채움
arr.fill(0);
console.log(arr); // [0, 0, 0]

// ====
const arr = [1, 2, 3];

// 인수로 전달받은 값 0을 배열의 인덱스 1부터 끝까지 요소로 채움
arr.fill(0, 1);
console.log(arr); // [1, 0, 0]

// ====
const arr = [1, 2, 3, 4, 5];

// 인수로 전달받은 값 0을 배열의 인덱스 1부터 3이전까지 요소로 채움
arr.fill(0, 1, 3);
console.log(arr); // [1, 0, 0, 4, 5]

27.8.13 Array.prototype.includes

- 배열 내에 특정 요소가 포함되어 있는지 확인하여 true/false 반환

- 첫 번째 인수로 검색할 대상을 지정

- 두 번째 인수로 시작할 인덱스 전달

- 만약 두 번째 인수에 음수를 전달하면 length 프로퍼티 값과 음수 인덱스를 합산하여 검색 (length + index)

const arr = [1, 2, 3];

arr.includes(2); // true
arr.includes(102); // false

// 배열에 요소 1이 포함되어있는지 인덱스 1부터 확인
arr.includes(1, 1); // false

// 배열에 요소 3이 포함되어 있는지 인덱스 2(arr.length - 1)부터 확인
arr.includes(3, -1); // true

27.8.14 Array.prototype.flat

- 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화함

[1, [2, 3, 4, 5]].flat(); // [1, 2, 3, 4, 5]

 

- 중첩 배열을 평탄화할 깊이를 인수로 전달 가능

- 인수 생략할 경우 기본값은 1

- Infinity를 전달하면 중첩 배열 모두를 평탄화함

27.9 배열 고차 함수

- 함수를 인수로 전달받거나 함수를 반환하는 함수를 고차 함수라고 함

- 고차함수는 외부 상태의 변경이나 가변 데이터를 피하고, 불변성을 지향하는 함수형 프로그래밍에 기반을 둠

 

? 함수형 프로그래밍

- 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거해

  복잡성을 해결하고 변수의 사용을 억제해 상태 변경을 피하려는 프로그래밍 패러다임

27.9.1 Array.prototype.sort

- 배열의 요소를 정렬

- 원본 배열 직접 변경, 정렬된 배열을 반환

- 기본적으로 오름차순으로 요소를 정렬

- 내림차순으로 하고 싶으면 reverse

const alpha = ['b', 'a', 'd', 'f', 'c'];

alpha.sort();
console.log(alpha); // ['a', 'b', 'c', 'd', 'f']

alpha.reverse();
console.log(alpha); // ['e', 'd', 'c', 'b', 'a']

 

- 숫자를 정렬할 경우에 주의

- 이 경우에는 정렬 순서를 정의하는 비교 함수를 인자로 전달해야 함

- 비교 함수는 양수나 음수 또는 0을 반환해야 함

const points = [40, 100, 1, 5, 2, 25, 10];

// 비교 함수의 반환값이 0보다 작으면 a를 우선하여 정렬
points.sort((a, b) => a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]

27.9.2 Array.prototype.forEach

- for 문을 대체할 수 있는 고차 함수

- 자신의 내부에서 반복문을 실행

- 반복문을 추상화한 고차 함수로서, 반복문을 통해 자신을 호출한 배열을 순회하며 콜백 함수로 전달받아 반복 호출함

- 원본 배열을 변경하지 않음

- 콜백 함수를 통해 원본 배열 변경 가능

- 반환값은 언제나 undefined

const nums = [1, 2, 3];
const pows = [];

// for문
for (let i=0; i< nusm.length; i++) {
    pows.push(nums[i] ** 2);
}
console.log(pows); // [1, 4, 9]

// ===================

const nums = [1, 2, 3];
const pows = [];

// forEach 메서드
nums.forEach(item => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]

27.9.3 Array.prototype.map

- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출함

- 콜백 함수의 반환값들로 구성된 새로운 배열을 반환

- 원본 배열은 변경되지 않음

const nums = [1, 4, 9];

const roots = nums.map(itme => Math.sqrt(item));

console.log(roots); // [1, 2, 3]
console.log(nums); // [1, 4, 9]

27.9.4 Array.prototype.filter

- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출

- 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환함

- 원본 배열은 변경되지 않음

const nums = [1, 2, 3, 4, 5];

// 모든 요소를 순회하면서 콜백 함수 반복 호출
// 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
const odds = nums.filter(item => item % 2);
console.log(odds); [1, 3, 5]

27.9.5 Array.prototype.reduce

- 자신을 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출

- 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 인수로 전달하면서 콜백 함수를 호출해 하나의 결과값을 만들어 반환

- 원본 배열은 변경되지 않음

- 첫 번째 인수로 콜백 함수, 두 번째 인수로 초기값을 전달받음

const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);

console.log(sum); // 10

 

- 4개의 인수를 전달받아 배열의 length만큼 총 4회 호출됨

- 이 과정을 반복해 하나의 결과값을 반환함

 

1. 평균 구하기

2. 최대값 구하기

3. 요소의 중복 횟수 구하기

4. 중첩 배열 평탄화

5. 중복 요소 제거

27.9.6 Array.prototype.some

- 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출함

- 반환 값이 단 한 번이라도 참이면 true, 모두 거짓이면 false를 반환함

- some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환하므로 주의

[5, 10, 15].some(item => item > 10); // true
[5, 10, 15].some(item => item < 0); // false

[].some(item => item > 3); // false

27.9.7 Array.prototype.every

- 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출함

- 모두 참이면 true, 하나라도 거짓이면 false

- every 메서드를 호출한 배열이 빈 배열인 경우 언제나 true를 반환하므로 주의

[5, 10, 15].every(item => item > 3); // true
[5, 10, 15].every(item => item < 10); // false

[].every(item => item > 3); // true

27.9.8 Array.prototype.find

- 자신을 호출한 배열의 요소를 순회하며 인수로 전달된 콜백 함수를 호출해 반환값이 true인 첫 번째 요소를 반환

- 콜백 함수의 반환값이 true인 요소가 존재하지 않으면 undefined 반환

27.9.9 Array.prototype.findIndex

- 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출해 반환값이 true인 첫 번째 요소의 인덱스를 반환

- true인 요소가 존재하지 않는다면 -1 반환

27.9.10 Array.prototype.flatMap

- map 메서드를 통해 생성된 새로운 배열을 평탄화함

- map과 flat 메서드를 순차적으로 실행하는 효과가 있음

const arr = ['hello', 'world'];

arr.map(x=>x.split('')).flat();
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'];

arr.flatMap(x=>x.split(''));
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'];