Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

동그란 도그린

[모던 자바스크립트 Deep Dive🦎] 27장 : 배열 본문

FrontEnd/Javascript

[모던 자바스크립트 Deep Dive🦎] 27장 : 배열

도그rin 2023. 8. 12. 17:31

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. 배열 생성

  • 다양한 생성 방식
    1. 배열 리터럴 (가장 일반적, 간편)
      • 0개 이상의 요소를 쉼표로 구분하여 대괄호로 묶음
      • 객체 리터럴과는 달리 프로퍼티 키가 없고 값만 존재
      const arr = [1, , 3];
      console.log(arr.length);  // 3
      console.log(arr[1]);      // undefined : 프로퍼티 키가 1인 프로퍼티가 존재하지 않으므로
      
    2. 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]
      
    3. Array.of
      • 전달된 인수를 요소로 갖는 배열을 생성
      Array.of(1);         // [1]
      Array.of(1, 2, 3);   // [1, 2, 3]
      Array.of('string');  // ['string']
      
    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]
      

 

※ 유사 배열 객체

: 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 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가지이므로 주의가 필요
    1. 원본 배열을 직접 변경하는 메서드 (초창기 배열 메서드는 원본 배열을 직접 변경하는 경우가 많음)
    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의 스프레드 문법으로 대체 가능
    // 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]
    
    ⇒ push, unshift, concat 메서드보다는 ES6의 스프레드 문법을 일관성 있게 사용하는 것을 권장

 

27-8-8. Array.prototype.splice

  • 원본 배열의 중간에 요소를 추가하거나 중간에 있는 요소를 제거
  • 제거한 요소가 배열로 반환됨
  • 원본 배열을 직접 변경
  • 3개의 매개변수가 있음
    • start
      • 원본 배열의 요소를 제거하기 시작할 인덱스
      • start만 지정하면 원본 배열의 start부터 모든 요소 제거
      • 음수인 경우 배열 끝에서의 인덱스를 나타냄 (-1이면 마지막 요소, -n이면 마지막에서 n번째 요소)
    • deleteCount (옵션)
      • start부터 제거할 요소의 개수
      • 0인 경우 아무런 요소도 제거되지 않음
    • items (옵션)
      • 제거한 위치에 삽입할 요소들의 목록
      • 생략하면, 원본 배열에서 요소들을 제거만 함
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 프로퍼티 값
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 메서드를 사용하는 편이 좋음

Comments