Post

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

27.1 배열이란?

  • 배열: 여러 개의 값을 순차적으로 나열한 자료구조
    • 요소: 배열이 가지고 있는 값
  • 배열은 값의 순서와 길이를 나타내는 length 프로퍼티를 가짐 ⇒ 순차적 접근 가능
  • 배열은 객체 타입
  • 배열 리터럴, Array 생성자 함수, Array.of 메서드, Array.from 메서드로 생성 가능
  • 배열의 생성자 함수는 Array, 프로토타입 객체는 Array.prototype

    1
    2
    3
    4
    
      const arr = [1, 2, 3];
        
      arr.constructor === Array // -> true
      Object.getPrototypeOf(arr) === Array.prototype // -> true
    

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

일반적인 배열

  • 밀집 배열(dense array): 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조
    • 따라서 배열의 요소는 같은 데이터 타입을 가지며 연속적으로 인접해 있다.
    • 임의 접근이 가능하다.
    • 배열에 요소를 삽입하거나 삭제하는 경우 요소를 이동시켜야 하는 단점이 있다.
  • 임의 접근(random access): 인덱스를 이용해 한 번의 연산으로 임의의 요소에 접근하는 것
    • 매우 효율적이며 빠르다(시간 복잡도 O(1)).
    • 정렬되지 않은 배열의 경우 임의 접근이 불가하며, 선형 검색(linear search)해야 한다.
  • 희소 배열(sparse array): 배열의 요소가 연속적으로 이어져있지 않은 배열
    • 요소가 모두 동일한 크기를 갖지 않아도 된다.

자바스크립트의 배열

1
2
3
4
5
6
7
8
9
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 프로퍼티를 가짐
  • 자바스크립트 배열의 요소는 사실 프로퍼티 값이다.
    • 객체는 프로퍼티 값으로 모든 값이 가능 ⇒ 배열도 요소로 모든 값이 가능

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        const arr = [
          'string',
          10,
          true,
          null,
          undefined,
          NaN,
          Infinity,
          [ ],
          { },
          function () {}
        ];
      
  • 일반적인 배열과의 차이

    | 일반적인 배열 | 자바스크립트 배열 | | — | — | | 인덱스로 요소에 빠르게 접근 가능 | 해시 테이블로 구현되어 일반적인 배열보다 요소 접근이 느림 | | 요소 삽입, 삭제가 비효율적 | 빠른 요소 삽입, 삭제 가능 |

    • 모던 자바스크립트 엔진은 배열을 일반 객체와 구별하여 최적화 구현 ⇒ 일반 객체보다 더 빠름

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
        const arr = [];
              
        console.time('Array Performance Test');
              
        for (let i = 0; i < 10000000; i++) {
          arr[i] = i;
        }
        console.timeEnd('Array Performance Test');
        // 약 340ms
              
        const obj = {};
              
        console.time('Object Performance Test');
              
        for (let i = 0; i < 10000000; i++) {
          obj[i] = i;
        }
              
        console.timeEnd('Object Performance Test');
        // 약 600ms
      

27.3 length 프로퍼티와 희소 배열

  • length 프로퍼티의 최댓값은 2^32 - 2다.
  • length 프로퍼티에 더 작은 숫자 값을 할당하면 배열의 길이가 줄어든다.

    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3, 4, 5];
        
      // 현재 length 프로퍼티 값인 5보다 작은 숫자 값 3을 length 프로퍼티에 할당
      arr.length = 3;
        
      // 배열의 길이가 5에서 3으로 줄어든다.
      console.log(arr); // [1, 2, 3]
    
  • length 프로퍼티에 더 큰 숫자 값을 할당하면 length 프로퍼티 값만 바뀌고 배열엔 변함이 없다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      const arr = [1];
        
      // 현재 length 프로퍼티 값인 1보다 큰 숫자 값 3을 length 프로퍼티에 할당
      arr.length = 3;
        
      // length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지는 않는다.
      console.log(arr.length); // 3
      console.log(arr); // [1, empty × 2]
        
      console.log(Object.getOwnPropertyDescriptors(arr));
      /*
      {
        '0': {value: 1, writable: true, enumerable: true, configurable: true},
        length: {value: 3, writable: true, enumerable: false, configurable: false}
      }
      */
    
    • 배열의 요소가 연속적으로 위치하지 않고 비어 있게 됨 ⇒ 희소 배열
  • 언제나 희소 배열의 실제 요소 개수 < 희소 배열의 length
    • 사용을 권장하지 않는다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      // 희소 배열
      const sparse = [, 2, , 4];
        
      // 희소 배열의 length 프로퍼티 값은 요소의 개수와 일치하지 않는다.
      console.log(sparse.length); // 4
      console.log(sparse); // [empty, 2, empty, 4]
        
      // 배열 sparse에는 인덱스가 0, 2인 요소가 존재하지 않는다.
      console.log(Object.getOwnPropertyDescriptors(sparse));
      /*
      {
        '1': { value: 2, writable: true, enumerable: true, configurable: true },
        '3': { value: 4, writable: true, enumerable: true, configurable: true },
        length: { value: 4, writable: true, enumerable: false, configurable: false }
      }
      */
    
  • 최적화가 잘 된 모던 자바스크립트 엔진은 같은 타입의 요소를 갖는 배열 생성 시 연속된 메모리 공간을 확보한다.
    • 따라서 희소 배열을 생성하지 않도록 주의, 즉 배열에는 같은 타입 요소를 연속적으로 위치하는 것이 좋다.

27.4 배열 생성

27.4.1 배열 리터럴

  • 0개 이상의 요소를 쉼표로 구분하여 대괄호로 묶는다.

    1
    2
    
      const arr = [1, 2, 3];
      console.log(arr.length); // 3
    
  • 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재한다.
  • 요소를 추가하지 않으면 length 프로퍼티 값이 0인 빈 배열이다.

    1
    2
    
      const arr = [];
      console.log(arr.length); // 0
    
  • 배열 리터럴에 요소를 생략하면 희소 배열이다.

    1
    2
    3
    4
    5
    6
    
      const arr = [1, , 3]; // 희소 배열
        
      // 희소 배열의 length는 배열의 실제 요소 개수보다 언제나 크다.
      console.log(arr.length); // 3
      console.log(arr);        // [1, empty, 3]
      console.log(arr[1]);     // undefined
    

27.4.2 Array 생성자 함수

  • 전달된 인수가 한 개인 숫자인 경우 ⇒ length 프로퍼티 값이 인수인 희소 배열 생성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      const arr = new Array(10);
        
      console.log(arr); // [empty × 10]
      console.log(arr.length); // 10
      console.log(Object.getOwnPropertyDescriptors(arr));
      /*
      {
        length: {value: 10, writable: true, enumerable: false, configurable: false}
      }
      */
    
    • 배열은 요소를 최대 2³² - 1개 가질 수 있다. 따라서 0 ≤ 전달할 인수 ≤ 2³² - 1 범위를 만족해야 하며, 벗어나면 RangeError가 발생한다.

      1
      2
      3
      4
      5
      6
      7
      8
      
        // 배열은 요소를 최대 4,294,967,295개 가질 수 있다.
        new Array(4294967295);
              
        // 전달된 인수가 0 ~ 4,294,967,295를 벗어나면 RangeError가 발생한다.
        new Array(4294967296); // RangeError: Invalid array length
              
        // 전달된 인수가 음수이면 에러가 발생한다.
        new Array(-1); // RangeError: Invalid array length
      
  • 전달된 인수가 없는 경우 ⇒ 빈 배열 생성

    1
    
      new Array(); // -> []
    
  • 전달된 인수가 2개 이상이거나 숫자가 아닌 경우 ⇒ 인수를 요소로 갖는 배열 생성

    1
    2
    3
    4
    5
    
      // 전달된 인수가 2개 이상이면 인수를 요소로 갖는 배열을 생성한다.
      new Array(1, 2, 3); // -> [1, 2, 3]
        
      // 전달된 인수가 1개지만 숫자가 아니면 인수를 요소로 갖는 배열을 생성한다.
      new Array({}); // -> [{}]
    
  • Array 생성자 함수를 일반 함수로 호출해도 생성자 함수로 동작한다.

    • Array 생성자 함수 내부에서 new.target을 확인하기 때문
    1
    
      Array(1, 2, 3); // -> [1, 2, 3]
    

27.4.3 Array.of

1
2
3
4
5
6
// 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.
Array.of(1); // -> [1]

Array.of(1, 2, 3); // -> [1, 2, 3]

Array.of('string'); // -> ['string']
  • ES6에서 도입된 Array 생성자 함수의 정적 메서드
  • 전달된 인수를 요소로 갖는 배열 생성

27.4.4 Array.from

  • ES6에서 도입된 Array 생성자 함수의 정적 메서드
  • 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환

    1
    2
    3
    4
    5
    
      // 유사 배열 객체를 변환하여 배열을 생성한다.
      Array.from({ length: 2, 0: 'a', 1: 'b' }); // -> ['a', 'b']
        
      // 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다.
      Array.from('Hello'); // -> ['H', 'e', 'l', 'l', 'o']
    
  • 두 번째 인수로 콜백 함수를 전달하여 값을 만들면서 요소를 채울 수 있다.

    1
    2
    3
    4
    5
    6
    7
    
      // Array.from에 length만 존재하는 유사 배열 객체를 전달하면
      // undefined를 요소로 채운다.
      Array.from({ length: 3 }); // -> [undefined, undefined, undefined]
        
      // Array.from은 두 번째 인수로 전달한 
      // 콜백 함수의 반환값으로 구성된 배열을 반환한다.
      Array.from({ length: 3 }, (_, i) => i); // -> [0, 1, 2]
    
    1. 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하며 호출
    2. 콜백 함수의 반환 값으로 구성된 배열 반환

유사 배열 객체와 이터러블 객체

  • 유사 배열 객체(array-like object)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      // 유사 배열 객체
      const arrayLike = {
        '0': 'apple',
        '1': 'banana',
        '2': 'orange',
        length: 3
      };
        
      // 유사 배열 객체는 마치 배열처럼 for 문으로 순회할 수도 있다.
      for (let i = 0; i < arrayLike.length; i++) {
        console.log(arrayLike[i]); // apple banana orange
      }
    
    • 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체
    • 배열처럼 for문으로 순회 가능
  • 이터러블 객체(iterable object)

    • Symbol.iterator 메서드를 구현하여 for...of문으로 순회할 수 있고, 스프레드 문법과 배열 디스트럭터링 할당의 대상으로 사용할 수 있는 객체
    • ES6에서 Array, String, Map, Set, DOM 컬렉션, arguments 등의 빌트인 이터러블 제공

27.5 배열 요소의 참조

  • 대괄호 안에 인덱스를 넣어 배열의 요소를 참조

    1
    2
    3
    4
    5
    6
    
      const arr = [1, 2];
        
      // 인덱스가 0인 요소를 참조
      console.log(arr[0]); // 1
      // 인덱스가 1인 요소를 참조
      console.log(arr[1]); // 2
    
    • 인덱스 대신 정수로 평가되는 표현식 사용 가능
  • 객체는 존재하지 않는 프로퍼티 키로 프로퍼티에 접근 시 undefined 반환 ⇒ 배열은 객체이므로 존재하지 않는 요소에 접근 시 undefined 반환

    1
    2
    3
    4
    
      const arr = [1, 2];
        
      // 인덱스가 2인 요소를 참조. 배열 arr에는 인덱스가 2인 요소가 존재하지 않는다.
      console.log(arr[2]); // undefined
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      // 희소 배열
      const arr = [1, , 3];
        
      // 배열 arr에는 인덱스가 1인 요소가 존재하지 않는다.
      console.log(Object.getOwnPropertyDescriptors(arr));
      /*
      {
        '0': {value: 1, writable: true, enumerable: true, configurable: true},
        '2': {value: 3, writable: true, enumerable: true, configurable: true},
        length: {value: 3, writable: true, enumerable: false, configurable: false}
      */
        
      // 존재하지 않는 요소를 참조하면 undefined가 반환된다.
      console.log(arr[1]); // undefined
      console.log(arr[3]); // undefined
    

27.6 배열 요소의 추가와 갱신

  • 존재하지 않는 인덱스를 사용해 값을 할당 ⇒ 객체처럼 새로운 요소 추가
    • length 프로퍼티 값은 자동 갱신된다.
    1
    2
    3
    4
    5
    6
    7
    
      const arr = [0];
        
      // 배열 요소의 추가
      arr[1] = 1;
        
      console.log(arr); // [0, 1]
      console.log(arr.length); // 2
    
  • 배열의 length 프로퍼티 값보다 큰 인덱스로 새로운 요소 추가 ⇒ 희소 배열
    • 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      arr[100] = 100;
        
      console.log(arr); // [0, 1, empty × 98, 100]
      console.log(arr.length); // 101
        
      // 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다.
      console.log(Object.getOwnPropertyDescriptors(arr));
      /*
      {
        '0': {value: 0, writable: true, enumerable: true, configurable: true},
        '1': {value: 1, writable: true, enumerable: true, configurable: true},
        '100': {value: 100, writable: true, enumerable: true, configurable: true},
        length: {value: 101, writable: true, enumerable: false, configurable: false}
      */
    
  • 이미 존재하는 요소에 값을 재할당 ⇒ 요소 값 갱신

    1
    2
    3
    4
    
      // 요소값의 갱신
      arr[1] = 10;
        
      console.log(arr); // [0, 10, empty × 98, 100]
    
  • 인덱스가 0 이상의 정수 또는 정수 형태의 문자열이 아닌 경우 ⇒ 프로퍼티 생성
    • 이렇게 생성된 프로퍼티는 length 프로퍼티 값에 영향을 주지 않는다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      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]
        
      // 프로퍼티는 length에 영향을 주지 않는다.
      console.log(arr.length); // 2
    

27.7 배열 요소의 삭제

  • 배열은 객체 ⇒ delete 연산자 사용 가능
    • length 프로퍼티 값이 변하지 않아 희소 배열이 되므로 사용하지 않는다.
    1
    2
    3
    4
    5
    6
    7
    8
    
      const arr = [1, 2, 3];
        
      // 배열 요소의 삭제
      delete arr[1];
      console.log(arr); // [1, empty, 3]
        
      // length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
      console.log(arr.length); // 3
    
  • 희소 배열을 만들지 않는 Array.prototype.splice 메서드 사용을 권장한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const arr = [1, 2, 3];
        
      // Array.prototype.splice(삭제를 시작할 인덱스, 삭제할 요소 수)
      // arr[1]부터 1개의 요소를 제거
      arr.splice(1, 1);
      console.log(arr); // [1, 3]
        
      // length 프로퍼티가 자동 갱신된다.
      console.log(arr.length); // 2
    

27.8 배열 메서드

  • mutator method
    • 원본 배열(배열 메서드 구현체 내부에서 this가 가리키는 객체)을 직접 변경
    • 외부 상태를 직접 변경하는 부수 효과가 있으므로 사용 시 주의 필요
  • accessor method
    • 원본 배열을 변경하지 않고 새로운 배열을 생성해 반환
    • 가급적 accessor method 사용을 권장

27.8.1 Array.isArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());

// false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(1);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray({ 0: 1, length: 1 })
  • Array 생성자 함수의 정적 메서드
  • 전달된 인수가 배열이면 true, 아니면 false 반환

27.8.2 Array.prototype.indexOf

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

// 배열 arr에서 요소 2를 검색하여 첫 번째로 검색된 요소의 인덱스를 반환한다.
arr.indexOf(2);    // -> 1
// 배열 arr에 요소 4가 없으므로 -1을 반환한다.
arr.indexOf(4);    // -> -1
// 두 번째 인수는 검색을 시작할 인덱스다. 두 번째 인수를 생략하면 처음부터 검색한다.
arr.indexOf(2, 2); // -> 2
  • 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환
  • 요소가 중복되어 여러 개 있는 경우 ⇒ 첫 번째로 검색된 요소의 인덱스 반환
  • 요소가 존재하지 않는 경우 ⇒ -1 반환

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const foods = ['apple', 'banana', 'orange'];
        
      // foods 배열에 'orange' 요소가 존재하는지 확인한다.
      if (foods.indexOf('orange') === -1) {
        // foods 배열에 'orange' 요소가 존재하지 않으면 'orange' 요소를 추가한다.
        foods.push('orange');
      }
        
      console.log(foods); // ["apple", "banana", "orange"]
    
    • 배열에 특정 요소가 존재하는지 확인할 때는 indexOf 메서드보다 Array.prototype.includes 메서드가 더 적합하다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        const foods = ['apple', 'banana'];
              
        // foods 배열에 'orange' 요소가 존재하는지 확인한다.
        if (!foods.includes('orange')) {
          // foods 배열에 'orange' 요소가 존재하지 않으면 'orange' 요소를 추가한다.
          foods.push('orange');
        }
              
        console.log(foods); // ["apple", "banana", "orange"]
      

27.8.3 Array.prototype.push

  • 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가 → 변경된 length 프로퍼티 값 반환

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const arr = [1, 2];
        
      // 인수로 전달받은 모든 값을 원본 배열 arr의 마지막 요소로 추가하고 
      // 변경된 length 값을 반환한다.
      let result = arr.push(3, 4);
      console.log(result); // 4
        
      // push 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [1, 2, 3, 4]
    
  • 성능이 좋지 않으므로 한 개의 요소만 추가할 경우 length 프로퍼티를 사용하는 게 더 빠르다.

    1
    2
    3
    4
    5
    
      const arr = [1, 2];
        
      // arr.push(3)과 동일한 처리를 한다. 이 방법이 push 메서드보다 빠르다.
      arr[arr.length] = 3;
      console.log(arr); // [1, 2, 3]
    
  • 원본 배열을 직접 변경하는 부수 효과 존재 ⇒ ES6의 스프레드 문법 사용 권장

    1
    2
    3
    4
    5
    
      const arr = [1, 2];
        
      // ES6 스프레드 문법
      const newArr = [...arr, 3];
      console.log(newArr); // [1, 2, 3]
    

27.8.4 Array.prototype.pop

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

// 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환한다.
let result = arr.pop();
console.log(result); // 2

// pop 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [1]
  • 원본 배열에서 마지막 요소를 제거하고, 제거한 요소를 반환
  • 원본 배열이 빈 배열이면 undefined 반환

pop 메서드와 push 메서드를 활용한 스택 구현

  • 생성자 함수를 사용한 스택 구현

    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
    31
    32
    33
    34
    35
    
      const Stack = (function () {
        function Stack(array = []) {
          if (!Array.isArray(array)) {
            throw new TypeError(`${array} is not an array.`);
          }
          this.array = array;
        }
        
        Stack.prototype = {
          constructor: Stack,
          // 스택의 가장 마지막에 데이터를 밀어 넣는다.
          push(value) {
            return this.array.push(value);
          },
          // 스택의 가장 마지막 데이터, 즉 가장 나중에 밀어 넣은 최신 데이터를 꺼낸다.
          pop() {
            return this.array.pop();
          },
          // 스택의 복사본 배열을 반환한다.
          entries() {
            return [...this.array];
          }
        };
        
        return Stack;
      }());
        
      const stack = new Stack([1, 2]);
      console.log(stack.entries()); // [1, 2]
        
      stack.push(3);
      console.log(stack.entries()); // [1, 2, 3]
        
      stack.pop();
      console.log(stack.entries()); // [1, 2]
    
  • 클래스를 사용한 스택 구현

    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
    31
    32
    33
    34
    
      class Stack {
        #array; // private class member
        
        constructor(array = []) {
          if (!Array.isArray(array)) {
            throw new TypeError(`${array} is not an array.`);
          }
          this.#array = array;
        }
        
        // 스택의 가장 마지막에 데이터를 밀어 넣는다.
        push(value) {
          return this.#array.push(value);
        }
        
        // 스택의 가장 마지막 데이터, 즉 가장 나중에 밀어 넣은 최신 데이터를 꺼낸다.
        pop() {
          return this.#array.pop();
        }
        
        // 스택의 복사본 배열을 반환한다.
        entries() {
          return [...this.#array];
        }
      }
        
      const stack = new Stack([1, 2]);
      console.log(stack.entries()); // [1, 2]
        
      stack.push(3);
      console.log(stack.entries()); // [1, 2, 3]
        
      stack.pop();
      console.log(stack.entries()); // [1, 2]
    

27.8.5 Array.prototype.unshift

  • 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가 → 변경된 length 프로퍼티 값 반환

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const arr = [1, 2];
        
      // 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가하고 
      // 변경된 length 값을 반환한다.
      let result = arr.unshift(3, 4);
      console.log(result); // 4
        
      // unshift 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [3, 4, 1, 2]
    
  • 원본 배열을 직접 변경하는 부수 효과 존재 ⇒ ES6의 스프레드 문법 사용 권장

    1
    2
    3
    4
    5
    
      const arr = [1, 2];
        
      // ES6 스프레드 문법
      const newArr = [3, ...arr];
      console.log(newArr); // [3, 1, 2]
    

27.8.6 Array.prototype.shift

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

// 원본 배열에서 첫 번째 요소를 제거하고 제거한 요소를 반환한다.
let result = arr.shift();
console.log(result); // 1

// shift 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [2]
  • 원본 배열에서 첫 번째 요소 제거 → 제거한 요소 반환
    • 원본 배열이 빈 배열인 경우 → undefined 반환

shift 메서드와 push 메서드를 활용한 큐 구현

  • 생성자 함수를 사용한 큐 구현

    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
    31
    32
    33
    34
    35
    36
    37
    
      const Queue = (function () {
        function Queue(array = []) {
          if (!Array.isArray(array)) {
            // "47. 에러 처리" 참고
            throw new TypeError(`${array} is not an array.`);
          }
          this.array = array;
        }
        
        Queue.prototype = {
          // "19.10.1. 생성자 함수에 의한 프로토타입의 교체" 참고
          constructor: Queue,
          // 큐의 가장 마지막에 데이터를 밀어 넣는다.
          enqueue(value) {
            return this.array.push(value);
          },
          // 큐의 가장 처음 데이터, 즉 가장 먼저 밀어 넣은 데이터를 꺼낸다.
          dequeue() {
            return this.array.shift();
          },
          // 큐의 복사본 배열을 반환한다.
          entries() {
            return [...this.array];
          }
        };
        
        return Queue;
      }());
        
      const queue = new Queue([1, 2]);
      console.log(queue.entries()); // [1, 2]
        
      queue.enqueue(3);
      console.log(queue.entries()); // [1, 2, 3]
        
      queue.dequeue();
      console.log(queue.entries()); // [2, 3]
    
  • 클래스를 사용한 큐 구현

    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
    31
    32
    33
    34
    
      class Queue {
        #array; // private class member
        
        constructor(array = []) {
          if (!Array.isArray(array)) {
            throw new TypeError(`${array} is not an array.`);
          }
          this.#array = array;
        }
        
        // 큐의 가장 마지막에 데이터를 밀어 넣는다.
        enqueue(value) {
          return this.#array.push(value);
        }
        
        // 큐의 가장 처음 데이터, 즉 가장 먼저 밀어 넣은 데이터를 꺼낸다.
        dequeue() {
          return this.#array.shift();
        }
        
        // 큐의 복사본 배열을 반환한다.
        entries() {
          return [...this.#array];
        }
      }
        
      const queue = new Queue([1, 2]);
      console.log(queue.entries()); // [1, 2]
        
      queue.enqueue(3);
      console.log(queue.entries()); // [1, 2, 3]
        
      queue.dequeue();
      console.log(queue.entries()); // [2, 3]
    

27.8.7 Array.prototype.concat

  • 인수로 전달된 값(배열 또는 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열 반환
    • 인수로 배열을 전달한 경우 ⇒ 배열을 해체하여 새로운 배열의 요소로 추가
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      const arr1 = [1, 2];
      const arr2 = [3, 4];
        
      // 배열 arr2를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다.
      // 인수로 전달한 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가한다.
      let result = arr1.concat(arr2);
      console.log(result); // [1, 2, 3, 4]
        
      // 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다.
      result = arr1.concat(3);
      console.log(result); // [1, 2, 3]
        
      // 배열 arr2와 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다.
      result = arr1.concat(arr2, 5);
      console.log(result); // [1, 2, 3, 4, 5]
        
      // 원본 배열은 변경되지 않는다.
      console.log(arr1); // [1, 2]
    
  • push/unshift 메서드와의 차이점
    • push/unshift 메서드는 원본 배열이 반드시 변수에 저장되어 있어야 변경된 배열을 사용할 수 있다. concat 메서드는 반환 값을 변수에 할당받아야 한다.

      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
      
        const arr1 = [3, 4];
              
        // unshift 메서드는 원본 배열을 직접 변경한다.
        // 따라서 원본 배열을 변수에 저장해 두지 않으면 변경된 배열을 사용할 수 없다.
        arr1.unshift(1, 2);
        // unshift 메서드를 사용할 경우 원본 배열을 반드시 변수에 저장해 두어야 
        // 결과를 확인할 수 있다.
        console.log(arr1); // [1, 2, 3, 4]
              
        // push 메서드는 원본 배열을 직접 변경한다.
        // 따라서 원본 배열을 변수에 저장해 두지 않으면 변경된 배열을 사용할 수 없다.
        arr1.push(5, 6);
        // push 메서드를 사용할 경우 원본 배열을 반드시 변수에 저장해 두어야 
        // 결과를 확인할 수 있다.
        console.log(arr1); // [1, 2, 3, 4, 5, 6]
              
        // unshift와 push 메서드는 concat 메서드로 대체할 수 있다.
        const arr2 = [3, 4];
              
        // concat 메서드는 원본 배열을 변경하지 않고 새로운 배열을 반환한다.
        // arr1.unshift(1, 2)를 다음과 같이 대체할 수 있다.
        let result = [1, 2].concat(arr2);
        console.log(result); // [1, 2, 3, 4]
              
        // arr1.push(5, 6)를 다음과 같이 대체할 수 있다.
        result = result.concat(5, 6);
        console.log(result); // [1, 2, 3, 4, 5, 6]
      
    • push/unshift 메서드는 인수로 배열이 전달된 경우 배열을 그대로 추가하지만 concat 메서드는 인수로 전달받은 배열을 해체하여 추가한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        const arr = [3, 4];
              
        // unshift와 push 메서드는 인수로 전달받은 배열을 그대로 원본 배열의 요소로 추가한다
        arr.unshift([1, 2]);
        arr.push([5, 6]);
        console.log(arr); // [[1, 2], 3, 4,[5, 6]]
              
        // concat 메서드는 인수로 전달받은 배열을 해체하여 새로운 배열의 요소로 추가한다
        let result = [1, 2].concat([3, 4]);
        result = result.concat([5, 6]);
              
        console.log(result); // [1, 2, 3, 4, 5, 6]
      
  • ES6의 스프레드 문법으로 대체할 수 있다.
    • push/unshift/concat 메서드를 사용하는 대신 일관적으로 스프레드 문법 사용을 권장
    1
    2
    3
    4
    5
    6
    
      let result = [1, 2].concat([3, 4]);
      console.log(result); // [1, 2, 3, 4]
        
      // concat 메서드는 ES6의 스프레드 문법으로 대체할 수 있다.
      result = [...[1, 2], ...[3, 4]];
      console.log(result); // [1, 2, 3, 4]
    

27.8.8 Array.prototype.splice

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const arr = [1, 2, 3, 4];
        
      // 원본 배열의 인덱스 1부터 2개의 요소를 제거하고 그 자리에 새로운 요소 20, 30을 삽입
      const result = arr.splice(1, 2, 20, 30);
        
      // 제거한 요소가 배열로 반환된다.
      console.log(result); // [2, 3]
      // splice 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [1, 20, 30, 4]
    
  • splice(start, deleteCount, items) → 제거한 요소를 배열로 반환
    • start: 원본 배열의 요소를 제거하기 시작할 인덱스
      • start만 지정된 경우 ⇒ 원본 배열의 start부터 모든 요소를 제거

        1
        2
        3
        4
        5
        6
        7
        8
        9
        
          const arr = [1, 2, 3, 4];
                    
          // 원본 배열의 인덱스 1부터 모든 요소를 제거한다.
          const result = arr.splice(1);
                    
          // 원본 배열이 변경된다.
          console.log(arr); // [1]
          // 제거한 요소가 배열로 반환된다.
          console.log(result); // [2, 3, 4]
        
      • 음수인 경우 ⇒ 배열의 끝에서의 인덱스 지칭

    • deleteCount(옵션): start부터 제거할 요소의 개수
      • 0인 경우 ⇒ 아무런 요소도 제거되지 않음

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        
          const arr = [1, 2, 3, 4];
                    
          // 원본 배열의 인덱스 1부터 0개의 요소를 제거하고 
          // 그 자리에 새로운 요소 100을 삽입한다.
          const result = arr.splice(1, 0, 100);
                    
          // 원본 배열이 변경된다.
          console.log(arr); // [1, 100, 2, 3, 4]
          // 제거한 요소가 배열로 반환된다.
          console.log(result); // []
        
      • 생략된 경우 ⇒ 원본 배열의 start부터 모든 요소를 제거

        1
        2
        3
        4
        5
        6
        7
        8
        9
        
          const arr = [1, 2, 3, 4];
                    
          // 원본 배열의 인덱스 1부터 모든 요소를 제거한다.
          const result = arr.splice(1);
                    
          // 원본 배열이 변경된다.
          console.log(arr); // [1]
          // 제거한 요소가 배열로 반환된다.
          console.log(result); // [2, 3, 4]
        
    • items(옵션): 제거한 위치에 삽입할 요소들의 목록
      • 생략된 경우 ⇒ 원본 배열에서 요소를 제거만 함

        1
        2
        3
        4
        5
        6
        7
        8
        9
        
          const arr = [1, 2, 3, 4];
                    
          // 원본 배열의 인덱스 1부터 2개의 요소를 제거한다.
          const result = arr.splice(1, 2);
                    
          // 원본 배열이 변경된다.
          console.log(arr); // [1, 4]
          // 제거한 요소가 배열로 반환된다.
          console.log(result); // [2, 3]
        
  • 배열의 특정 요소 제거를 위해 indexOf와 함께 사용될 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      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 메서드도 특정 요소를 제거하지만 중복된 경우에는 모두 제거한다.

      1
      2
      3
      4
      5
      6
      7
      8
      
        const arr = [1, 2, 3, 1, 2];
              
        // 배열 array에서 모든 item 요소를 제거한다.
        function removeAll(array, item) {
          return array.filter(v => v !== item);
        }
              
        console.log(removeAll(arr, 2)); // [1, 3, 1]
      

27.8.9 Array.prototype.slice

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      const arr = [1, 2, 3];
        
      // arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환한다.
      arr.slice(0, 1); // -> [1]
        
      // arr[1]부터 arr[2] 이전(arr[2] 미포함)까지 복사하여 반환한다.
      arr.slice(1, 2); // -> [2]
        
      // 원본은 변경되지 않는다.
      console.log(arr); // [1, 2, 3]
    
  • slice(start, end)
    • start: 복사를 시작할 인덱스

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
        const arr = [1, 2, 3];
              
        // arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환한다.
        arr.slice(0, 1); // -> [1]
              
        // arr[1]부터 arr[2] 이전(arr[2] 미포함)까지 복사하여 반환한다.
        arr.slice(1, 2); // -> [2]
              
        // 원본은 변경되지 않는다.
        console.log(arr); // [1, 2, 3]
      
      • 음수인 경우 ⇒ 배열 끝에서부터 요소를 복사

        1
        2
        3
        4
        5
        6
        7
        
          const arr = [1, 2, 3];
                    
          // 배열의 끝에서부터 요소를 한 개 복사하여 반환한다.
          arr.slice(-1); // -> [3]
                    
          // 배열의 끝에서부터 요소를 두 개 복사하여 반환한다.
          arr.slice(-2); // -> [2, 3]
        
    • end: 복사를 종료할 인덱스

      • 생략하는 경우 ⇒ 기본값은 length 프로퍼티

        1
        2
        3
        4
        
          const arr = [1, 2, 3];
                    
          // arr[1]부터 이후의 모든 요소를 복사하여 반환한다.
          arr.slice(1); // -> [2, 3]
        
  • 인수를 모두 생략하는 경우 ⇒ 원본 배열의 얕은 복사본 반환

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
      const arr = [1, 2, 3];
        
      // 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환한다.
      const copy = arr.slice();
      console.log(copy); // [1, 2, 3]
      console.log(copy === arr); // false
        
      const todos = [
        { id: 1, content: 'HTML', completed: false },
        { id: 2, content: 'CSS', completed: true },
        { id: 3, content: 'Javascript', completed: false }
      ];
        
      // 얕은 복사(shallow copy)
      const _todos = todos.slice();
      // const _todos = [...todos];
        
      // _todos와 todos는 참조값이 다른 별개의 객체다.
      console.log(_todos === todos); // false
        
      // 배열 요소의 참조값이 같다. 즉, 얕은 복사되었다.
      console.log(_todos[0] === todos[0]); // true
    
    • 깊은 복사를 위해서는 Lodash 라이브러리의 cloneDeep 메서드를 추천
  • 유사 배열 객체를 배열로 변환하는데 사용 가능

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      function sum() {
        // 유사 배열 객체를 배열로 변환(ES5)
        var arr = Array.prototype.slice.call(arguments);
        console.log(arr); // [1, 2, 3]
        
        return arr.reduce(function (pre, cur) {
          return pre + cur;
        }, 0);
      }
        
      console.log(sum(1, 2, 3)); // 6
    
    • 유사 배열 객체나 이터러블 객체를 배열로 변환할 때에는 Array.from 메서드가 더 간편

      1
      2
      3
      4
      5
      6
      7
      8
      
        function sum() {
          const arr = Array.from(arguments);
          console.log(arr); // [1, 2, 3]
              
          return arr.reduce((pre, cur) => pre + cur, 0);
        }
              
        console.log(sum(1, 2, 3)); // 6
      
    • 이터러블 객체는 ES6의 스프레드 문법으로도 간단하게 배열로 변환 가능

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        function sum() {
          // 이터러블을 배열로 변환(ES6 스프레드 문법)
          const arr = [...arguments ];
          console.log(arr); // [1, 2, 3]
              
          return arr.reduce((pre, cur) => pre + cur, 0);
        }
              
        console.log(sum(1, 2, 3)); // 6
      

27.8.10 Array.prototype.join

1
2
3
4
5
6
7
8
9
10
11
const arr = [1, 2, 3, 4];

// 기본 구분자는 ','이다.
// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 기본 구분자 ','로 연결한 문자열을 반환
arr.join(); // -> '1,2,3,4';

// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 빈문자열로 연결한 문자열을 반환
arr.join(''); // -> '1234'

// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 구분자 ':'로 연결한 문자열을 반환
arr.join(':'); // -> '1:2:3:4'
  • 원본 배열의 모든 요소를 문자열로 변환 → 인수로 전달받은 문자열(구분자, separator)로 연결한 문자열 반환
  • 기본 구분자는 콤마이며 생략 가능

27.8.11 Array.prototype.reverse

1
2
3
4
5
6
7
const arr = [1, 2, 3];
const result = arr.reverse();

// reverse 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [3, 2, 1]
// 반환값은 변경된 배열이다.
console.log(result); // [3, 2, 1]
  • 원본 배열의 순서를 뒤집어 변경된 배열을 반환
  • 원본 배열이 변경됨

27.8.12 Array.prototype.fill

  • ES6에서 도입
  • 첫 번째 인수로 전달받은 값을 원본 배열의 처음부터 끝까지 채움

    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3];
        
      // 인수로 전달 받은 값 0을 배열의 처음부터 끝까지 요소로 채운다.
      arr.fill(0);
        
      // fill 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [0, 0, 0]
    
  • 두 번째 인수 ⇒ 채우기를 시작할 인덱스

    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3];
        
      // 인수로 전달받은 값 0을 배열의 인덱스 1부터 끝까지 요소로 채운다.
      arr.fill(0, 1);
        
      // fill 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [1, 0, 0]
    
  • 세 번째 인수 ⇒ 채우기를 멈출 인덱스

    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3, 4, 5];
        
      // 인수로 전달받은 값 0을 배열의 인덱스 1부터 3 이전(인덱스 3 미포함)까지 요소로 채운다.
      arr.fill(0, 1, 3);
        
      // fill 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [1, 0, 0, 4, 5]
    
  • 배열을 생성하며 특정 값으로 요소를 채우는 데 유용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      const arr = new Array(3);
      console.log(arr); // [empty × 3]
        
      // 인수로 전달받은 값 1을 배열의 처음부터 끝까지 요소로 채운다.
      const result = arr.fill(1);
        
      // fill 메서드는 원본 배열을 직접 변경한다.
      console.log(arr); // [1, 1, 1]
        
      // fill 메서드는 변경된 원본 배열을 반환한다.
      console.log(result); // [1, 1, 1]
    
    • Array.from 메서드 사용 시 두 번째 인수로 전달한 콜백 함수를 통해 요소값을 만들며 배열을 채울 수 있다.

      1
      2
      3
      4
      5
      
        // 인수로 전달받은 정수만큼 요소를 생성하고 0부터 1씩 증가하면서 요소를 채운다.
        const sequences = (length = 0) => Array.from({ length }, (_, i) => i);
        // const sequences = (length = 0) => Array.from(new Array(length), (_, i) => i);
              
        console.log(sequences(3)); // [0, 1, 2]
      

27.8.13 Array.prototype.includes

  • ES7에서 도입
  • 첫 번째 인수가 배열 내의 요소로 포함되어 있는지 확인하고, 확인 결과를 반환

    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3];
        
      // 배열에 요소 2가 포함되어 있는지 확인한다.
      arr.includes(2); // -> true
        
      // 배열에 요소 100이 포함되어 있는지 확인한다.
      arr.includes(100); // -> false
    
  • 두 번째 인수 ⇒ 검색을 시작할 인덱스
    • 기본값은 0
    • 음수 전달 시 length 프로퍼티 값과 음수 인덱스를 합산하여 검색 시작 인덱스 설정
    1
    2
    3
    4
    5
    6
    7
    
      const arr = [1, 2, 3];
        
      // 배열에 요소 1이 포함되어 있는지 인덱스 1부터 확인한다.
      arr.includes(1, 1); // -> false
        
      // 배열에 요소 3이 포함되어 있는지 인덱스 2(arr.length - 1)부터 확인한다.
      arr.includes(3, -1); // -> true
    
  • indexOf 메서드는 반환값이 -1인지 확인해야 하고, NaN이 포함되었는지는 확인할 수 없다.

    1
    2
    
      [NaN].indexOf(NaN) !== -1; // -> false
      [NaN].includes(NaN);       // -> true
    

27.8.14 Array.prototype.flat

  • ES10(ECMAScript 2019)에서 도입
  • 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화
    • 인수를 생략할 경우 ⇒ 기본값은 1
    • 인수로 Infinity를 전달 ⇒ 중첩 배열 모두를 평탄화
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      [1, [2, 3, 4, 5]].flat(); // -> [1, 2, 3, 4, 5]
        
      // 중첩 배열을 평탄화하기 위한 깊이 값의 기본값은 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]
    

27.9 배열 고차 함수

  • 고차 함수(Higher-Order Function, HOF): 함수를 인수로 전달받거나 함수를 반환하는 함수
    • 자바스크립트의 함수는 일급 객체 ⇒ 함수를 인수에 전달하고 반환할 수 있다.
  • 함수형 프로그래밍
    • 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고, 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임
      • 조건문과 반복문 ⇒ 로직의 흐름을 이해하기 어렵게 해 가독성을 해친다.
      • 변수 ⇒ 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다.
    • 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향
    • 순수 함수를 통해 부수 효과를 최대한 억제 ⇒ 오류를 피하고 프로그램의 안전성을 높임
    • 고차 함수가 기반을 두고 있다.

27.9.1 Array.prototype.sort

  • 원본 배열을 직접 변경하여 정렬된 배열 반환
    • 기본적으로 오름차순 정렬
    1
    2
    3
    4
    5
    6
    7
    
      const fruits = ['Banana', 'Orange', 'Apple'];
        
      // 오름차순(ascending) 정렬
      fruits.sort();
        
      // sort 메서드는 원본 배열을 직접 변경한다.
      console.log(fruits); // ['Apple', 'Banana', 'Orange']
    
    1
    2
    3
    4
    5
    6
    7
    
      const fruits = ['바나나', '오렌지', '사과'];
        
      // 오름차순(ascending) 정렬
      fruits.sort();
        
      // sort 메서드는 원본 배열을 직접 변경한다.
      console.log(fruits); // ['바나나', '사과', '오렌지']
    
  • reverse 메서드를 이용해 내림차순 정렬을 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      const fruits = ['Banana', 'Orange', 'Apple'];
        
      // 오름차순(ascending) 정렬
      fruits.sort();
        
      // sort 메서드는 원본 배열을 직접 변경한다.
      console.log(fruits); // ['Apple', 'Banana', 'Orange']
        
      // 내림차순(descending) 정렬
      fruits.reverse();
        
      // reverse 메서드도 원본 배열을 직접 변경한다.
      console.log(fruits); // ['Orange', 'Banana', 'Apple']
    
  • 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따름
    • 따라서 배열의 요소가 숫자 타입이라도 일시적으로 문자열로 변환한 뒤 유니코드 코드 포인터의 순서를 기준으로 정렬 ⇒ 실제 값대로 정렬되지 않음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      const points = [40, 100, 1, 5, 2, 25, 10];
        
      points.sort();
        
      // 숫자 요소들로 이루어진 배열은 의도한 대로 정렬되지 않는다.
      console.log(points); // [1, 10, 100, 2, 25, 40, 5]
        
      ['2', '1'].sort(); // -> ["1", "2"]
      [2, 1].sort();     // -> [1, 2]
        
      ['2', '10'].sort(); // -> ["10", "2"]
      [2, 10].sort();     // -> [10, 2]
    
  • 숫자 요소를 정렬할 때는 정렬 순서를 정의하는 비교 함수를 인수로 전달

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      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]
        
      // 숫자 배열에서 최소/최대값 취득
      console.log(points[0], points[points.length]); // 1
        
      // 숫자 배열의 내림차순 정렬. 비교 함수의 반환값이 0보다 작으면 b를 우선하여 정렬한다.
      points.sort((a, b) => b - a);
      console.log(points); // [100, 40, 25, 10, 5, 2, 1]
        
      // 숫자 배열에서 최대값 취득
      console.log(points[0]); // 100
    
    • 비교 함수 반환값 < 0 ⇒ 첫 번째 인수를 우선하여 정렬
    • 비교 함수 반환값 = 0 ⇒ 일반적으로 정렬하지 않음
    • 0 < 비교 함수 반환값 ⇒ 두 번째 인수를 우선하여 정렬
  • 객체를 요소로 갖는 배열도 정렬할 수 있다.

    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
    31
    32
    33
    34
    35
    36
    
      const todos = [
        { id: 4, content: 'JavaScript' },
        { id: 1, content: 'HTML' },
        { id: 2, content: 'CSS' }
      ];
        
      // 비교 함수. 매개변수 key는 프로퍼티 키다.
      function compare(key) {
        // 프로퍼티 값이 문자열인 경우
      	// 산술 연산으로 비교하면 NaN이 나오므로 비교 연산을 사용한다.
        // 비교 함수는 양수/음수/0을 반환하면 되므로
      	// 산술 연산 대신 비교 연산을 사용할 수 있다.
        return (a, b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
      }
        
      // id를 기준으로 오름차순 정렬
      todos.sort(compare('id'));
      console.log(todos);
      /*
      [
        { id: 1, content: 'HTML' },
        { id: 2, content: 'CSS' },
        { id: 4, content: 'JavaScript' }
      ]
      */
        
      // content를 기준으로 오름차순 정렬
      todos.sort(compare('content'));
      console.log(todos);
      /*
      [
        { id: 2, content: 'CSS' },
        { id: 1, content: 'HTML' },
        { id: 4, content: 'JavaScript' }
      ]
      */
    
  • sort 메서드는 quicksort 알고리즘을 사용했었지만, ES10에서는 timsort 알고리즘을 사용한다.
    • quicksort 알고리즘은 값의 요소가 중복되어 있을 때 초기 순서가 변경될 수 있어 불안정하다.

27.9.2 Array.prototype.forEach

  • forEach 메서드는 for문을 대체할 수 있는 고차 함수

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      const numbers = [1, 2, 3];
      let pows = [];
        
      // for 문으로 배열 순회
      for (let i = 0; i < numbers.length; i++) {
        pows.push(numbers[i] ** 2);
      }
      console.log(pows); // [1, 4, 9]
        
      pows = [];
      // forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
      numbers.forEach(item => pows.push(item ** 2));
      console.log(pows); // [1, 4, 9]
    
    • 조건문이나 반복문은 로직의 흐름을 어렵게 하며, 특히 for문은 반복을 위한 변수를 선언해야 하며 조건식과 증감식으로 이루어져 함수형 프로그래밍이 추구하는 바와 맞지 않는다.
    • 메서드 내부에서 반복문을 실행 ⇒ 반복문을 추상화한 고차 함수
  • 배열을 순회하며 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      // forEach 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다.
      [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 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: forEach 메서드를 호출한 배열 자체(this)
  • forEach 메서드는 원본 배열을 변경하지 않지만, 콜백 함수를 통해 변경 가능

    1
    2
    3
    4
    5
    6
    7
    8
    
      const numbers = [1, 2, 3];
        
      // forEach 메서드는 원본 배열을 변경하지 않지만 
      // 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
      // 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다.
      // 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다.
      numbers.forEach((item, index, arr) => { arr[index] = item ** 2; });
      console.log(numbers); // [1, 4, 9]
    
  • 반환값은 언제나 undefined

    1
    2
    
      const result = [1, 2, 3].forEach(console.log);
      console.log(result); // undefined
    
  • break, continue문 사용 불가 ⇒ 배열의 모든 요소를 모두 순회해야 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      [1, 2, 3].forEach(item => {
        console.log(item);
        if (item > 1) break; // SyntaxError: Illegal break statement
      });
        
      [1, 2, 3].forEach(item => {
        console.log(item);
        if (item > 1) continue;
        // SyntaxError: Illegal continue statement: no surrounding iteration statement
      });
    
  • 희소 배열의 경우 ⇒ 존재하지 않는 요소는 순회 대상에서 제외

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      // 희소 배열
      const arr = [1, , 3];
        
      // for 문으로 희소 배열을 순회
      for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]); // 1, undefined, 3
      }
        
      // forEach 메서드는 희소 배열의 존재하지 않는 요소를 순회 대상에서 제외한다.
      arr.forEach(v => console.log(v)); // 1, 3
    
  • for문에 비해 성능이 낮지만, 가독성이 좋으므로 높은 성능이 필요하지 않다면 forEach 메서드 사용을 권장한다.

forEach 메서드의 두 번째 인수

  • forEach 메서드의 두 번째 인수: forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체
  • 일반 함수로 호출되는 forEach 메서드의 콜백 함수 내부 thisundefined
    • 클래스 내부에는 암묵적으로 strict mode가 적용되어 this는 전역 객체가 아닌 undefined를 가리킨다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        class Numbers {
          numberArray = [];
              
          multiply(arr) {
            arr.forEach(function (item) {
              // TypeError: Cannot read property 'numberArray' of undefined
              this.numberArray.push(item * item);
            });
          }
        }
              
        const numbers = new Numbers();
        numbers.multiply([1, 2, 3]);
      
    • 두 번째 인수로 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        class Numbers {
          numberArray = [];
              
          multiply(arr) {
            arr.forEach(function (item) {
              this.numberArray.push(item * item);
            }, this); // forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달
          }
        }
              
        const numbers = new Numbers();
        numbers.multiply([1, 2, 3]);
        console.log(numbers.numberArray); // [1, 4, 9]
      
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        class Numbers {
          numberArray = [];
              
          multiply(arr) {
            // 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다.
            arr.forEach(item => this.numberArray.push(item * item));
          }
        }
              
        const numbers = new Numbers();
        numbers.multiply([1, 2, 3]);
        console.log(numbers.numberArray); // [1, 4, 9]
      

forEach 메서드의 폴리필

  • 폴리필(polyfill): 최신 사양의 기능을 지원하지 않는 브라우저를 위해 누락된 최신 사양의 기능을 구현하여 추가하는 것
  • forEach 메서드의 폴리필

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
      // 만약 Array.prototype에 forEach 메서드가 존재하지 않으면 폴리필을 추가한다.
      if (!Array.prototype.forEach) {
        Array.prototype.forEach = function (callback, thisArg) {
          // 첫 번째 인수가 함수가 아니면 TypeError를 발생시킨다.
          if (typeof callback !== 'function') {
            throw new TypeError(callback + ' is not a function');
          }
        
          // this로 사용할 두 번째 인수를 전달받지 못하면 전역 객체를 this로 사용한다.
          thisArg = thisArg || window;
        
          // for 문으로 배열을 순회하면서 콜백 함수를 호출한다.
          for (var i = 0; i < this.length; i++) {
            // call 메서드를 통해 thisArg를 전달하면서 콜백 함수를 호출한다.
            // 이때 콜백 함수의 인수로 배열 요소, 인덱스, 배열 자신을 전달한다.
            callback.call(thisArg, this[i], i, this);
          }
        };
      }
    
    • 반복문을 메서드 내부로 은닉하여 로직의 흐름을 이해하기 쉽게 하고 복잡성을 해결

27.9.3 Array.prototype.map

  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값들로 구성된 새로운 배열 반환
    • 원본 배열을 변경하지 않는다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      const numbers = [1, 4, 9];
        
      // map 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
      // 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
      const roots = numbers.map(item => Math.sqrt(item));
        
      // 위 코드는 다음과 같다.
      // const roots = numbers.map(Math.sqrt);
        
      // map 메서드는 새로운 배열을 반환한다
      console.log(roots);   // [ 1, 2, 3 ]
      // map 메서드는 원본 배열을 변경하지 않는다
      console.log(numbers); // [ 1, 4, 9 ]
    
  • map 메서드를 호출한 배열과 map 메서드가 생성하여 반환한 배열은 1:1 매핑한다.
    • 두 배열의 length 값 또한 반드시 일치한다.
  • map 메서드의 콜백 함수

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      // map 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다.
      [1, 2, 3].map((item, index, arr) => {
        console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
        return item;
      });
      /*
      요소값: 1, 인덱스: 0, this: [1,2,3]
      요소값: 2, 인덱스: 1, this: [1,2,3]
      요소값: 3, 인덱스: 2, this: [1,2,3]
      */
    
    • 콜백 함수의 첫 번째 인수: map 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: map 메서드를 호출한 배열 자체(this)
  • map 메서드의 두 번째 인수: map 메서드의 콜백 함수 내부에서 this로 사용할 객체

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
      class Prefixer {
        constructor(prefix) {
          this.prefix = prefix;
        }
        
        add(arr) {
          return arr.map(function (item) {
            // 외부에서 this를 전달하지 않으면 this는 undefined를 가리킨다.
            return this.prefix + item;
          }, this); // map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달
        }
      }
        
      const prefixer = new Prefixer('-webkit-');
      console.log(prefixer.add(['transition', 'user-select']));
      // ['-webkit-transition', '-webkit-user-select']
    
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        class Prefixer {
          constructor(prefix) {
            this.prefix = prefix;
          }
              
          add(arr) {
            // 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다.
            return arr.map(item => this.prefix + item);
          }
        }
              
        const prefixer = new Prefixer('-webkit-');
        console.log(prefixer.add(['transition', 'user-select']));
        // ['-webkit-transition', '-webkit-user-select']
      

forEach 메서드와 map 메서드 비교

  • 공통점
    • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출
  • 차이점
    • forEach 메서드
      • undefined 반환
      • 반복문을 대체하기 위한 고차 함수
    • map 메서드
      • 콜백 함수의 반환값들로 구성된 새로운 배열 반환
      • 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수

27.9.4 Array.prototype.filter

  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이 **true인 요소로만 구성된 새로운 배열 반환**
    • 원본 배열을 변경하지 않는다.
    1
    2
    3
    4
    5
    6
    7
    
      const numbers = [1, 2, 3, 4, 5];
        
      // filter 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
      // 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
      // 다음의 경우 numbers 배열에서 홀수인 요소만을 필터링한다(1은 true로 평가된다).
      const odds = numbers.filter(item => item % 2);
      console.log(odds); // [1, 3, 5]
    
  • 필터링 조건을 만족하는 특정 요소만 추출하여 새로운 배열을 만들기 위한 고차 함수
  • 반환된 배열의 length 프로퍼티 값 ≤ filter 메서드를 호출한 배열의 length 프로퍼티 값
  • filter 메서드의 콜백 함수

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      // filter 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다.
      [1, 2, 3].filter((item, index, arr) => {
        console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
        return item % 2;
      });
      /*
      요소값: 1, 인덱스: 0, this: [1,2,3]
      요소값: 2, 인덱스: 1, this: [1,2,3]
      요소값: 3, 인덱스: 2, this: [1,2,3]
      */
    
    • 콜백 함수의 첫 번째 인수: filter 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: filter 메서드를 호출한 배열 자체(this)
  • filter 메서드의 두 번째 인수: filter 메서드의 콜백 함수 내부에서 this로 사용할 객체
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.
  • 특정 요소를 제거하기 위해 사용 가능
    • 특정 요소가 중복되어 있다면 중복된 요소가 모두 제거된다.
    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
    31
    
      class Users {
        constructor() {
          this.users = [
            { id: 1, name: 'Lee' },
            { id: 2, name: 'Kim' }
          ];
        }
        
        // 요소 추출
        findById(id) {
          // id가 일치하는 사용자만 반환한다.
          return this.users.filter(user => user.id === id);
        }
        
        // 요소 제거
        remove(id) {
          // id가 일치하지 않는 사용자를 제거한다.
          this.users = this.users.filter(user => user.id !== id);
        }
      }
        
      const users = new Users();
        
      let user = users.findById(1);
      console.log(user); // [{ id: 1, name: 'Lee' }]
        
      // id가 1인 사용자를 제거한다.
      users.remove(1);
        
      user = users.findById(1);
      console.log(user); // []
    

27.9.5 Array.prototype.reduce

  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달 → 콜백 함수를 계속 호출하며 하나의 결괏값을 만들어 반환
    • 원본 배열을 변경하지 않는다.
    1
    2
    3
    4
    
      // [1, 2, 3, 4]의 모든 요소의 누적을 구한다.
      const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);
        
      console.log(sum); // 10
    
  • reduce 메서드의 콜백 함수
    • 콜백 함수의 첫 번째 인수: 초깃값 또는 콜백 함수의 이전 반환값
      • reduce 메서드의 두 번째 인수: 초깃값
    • 콜백 함수의 두 번째 인수: reduce 메서드를 호출한 배열의 요소값
    • 콜백 함수의 세 번째 인수: 인덱스
    • 콜백 함수의 네 번째 인수: reduce 메서드를 호출한 배열 자체(this)
  • 자신을 호출한 배열의 모든 요소를 순회하며 하나의 결괏값을 구해야 할 때 사용하는 고차 함수
  • 두 번째 인수로 전달하는 초깃값은 생략할 수 있지만, 생략하지 않는 게 안전하다.

    1
    2
    3
    
      // reduce 메서드의 두 번째 인수, 즉 초기값을 생략했다.
      const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur);
      console.log(sum); // 10
    
    • 초깃값 없이 빈 배열로 reduce 메서드를 호출하면 에러가 발생한다.

      1
      2
      
        const sum = [].reduce((acc, cur) => acc + cur);
        // TypeError: Reduce of empty array with no initial value
      
      1
      2
      
        const sum = [].reduce((acc, cur) => acc + cur, 0);
        console.log(sum); // 0
      
    • 객체의 특정 프로퍼티 값을 합산하는 경우에도 마찬가지다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        const products = [
          { id: 1, price: 100 },
          { id: 2, price: 200 },
          { id: 3, price: 300 }
        ];
              
        // 1번째 순회 시 acc는 { id: 1, price: 100 }, cur은 { id: 2, price: 200 }이고
        // 2번째 순회 시 acc는 300, cur은 { id: 3, price: 300 }이다.
        // 2번째 순회 시 acc에 함수에 객체가 아닌 숫자값이 전달된다. 
        // 이때 acc.price는 undefined다.
        const priceSum = products.reduce((acc, cur) => acc.price + cur.price);
              
        console.log(priceSum); // NaN
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        const products = [
          { id: 1, price: 100 },
          { id: 2, price: 200 },
          { id: 3, price: 300 }
        ];
              
        /*
        1번째 순회 : acc => 0,   cur => { id: 1, price: 100 }
        2번째 순회 : acc => 100, cur => { id: 2, price: 200 }
        3번째 순회 : acc => 300, cur => { id: 3, price: 300 }
        */
        const priceSum = products.reduce((acc, cur) => acc + cur.price, 0);
              
        console.log(priceSum); // 600
      

reduce 메서드의 다양한 활용법

  • map, filter, some, every, find와 같은 모든 배열의 고차 함수는 reduce 메서드로 구현할 수 있다.
  • 평균 구하기

    1
    2
    3
    4
    5
    6
    7
    8
    
      const values = [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
    
  • 최댓값 구하기

    1
    2
    3
    4
    
      const values = [1, 2, 3, 4, 5];
        
      const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0);
      console.log(max); // 5
    
    • reduce 메서드보다 Math.max 메서드를 사용하는 게 더 직관적이다.

      1
      2
      3
      4
      5
      
        const values = [1, 2, 3, 4, 5];
              
        const max = Math.max(...values);
        // var max = Math.max.apply(null, values);
        console.log(max); // 5
      
  • 요소의 중복 횟수 구하기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      const fruits = ['banana', 'apple', 'orange', 'orange', 'apple'];
        
      const count = fruits.reduce((acc, cur) => {
        // 첫 번째 순회 시 acc는 초기값인 {}이고 cur은 첫 번째 요소인 'banana'다.
        // 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로, 
      	// 요소의 개수를 프로퍼티 값으로 할당한다. 
      	// 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1로 초기화한다.
        acc[cur] = (acc[cur] || 0) + 1;
        return acc;
      }, {});
        
      // 콜백 함수는 총 5번 호출되고 다음과 같이 결과값을 반환한다.
      /*
      {banana: 1} => {banana: 1, apple: 1} => {banana: 1, apple: 1, orange: 1}
      => {banana: 1, apple: 1, orange: 2} => {banana: 1, apple: 2, orange: 2}
      */
        
      console.log(count); // { banana: 1, apple: 2, orange: 2 }
    
  • 중첩 배열 평탄화

    1
    2
    3
    4
    5
    6
    
      const values = [1, [2, 3], 4, [5, 6]];
        
      const flatten = values.reduce((acc, cur) => acc.concat(cur), []);
      // [1] => [1, 2, 3] => [1, 2, 3, 4] => [1, 2, 3, 4, 5, 6]
        
      console.log(flatten); // [1, 2, 3, 4, 5, 6]
    
    • reduce 메서드보다 ES10에서 도입된 Array.prototype.flat 메서드를 사용하는 게 더 직관적이다.

      1
      2
      3
      4
      
        [1, [2, 3, 4, 5]].flat(); // -> [1, 2, 3, 4, 5]
              
        // 인수 2는 중첩 배열을 평탄화하기 위한 깊이 값이다.
        [1, [2, 3, [4, 5]]].flat(2); // -> [1, 2, 3, 4, 5]
      
  • 중복 요소 제거

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
        
      const result = values.reduce(
        (unique, val, i, _values) =>
          // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소
          // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 다르다면 val은 중복된 요소
          // 처음 순회하는 요소만 초기값 []가 전달된 unique 배열에 담아 반환하면 
      		// 중복된 요소는 제거된다.
          _values.indexOf(val) === i ? [...unique, val] : unique,
        []
      );
        
      console.log(result); // [1, 2, 3, 5, 4]
    
    • reduce 메서드보다 filter 메서드를 사용하는 게 더 직관적이다.

      1
      2
      3
      4
      5
      6
      
        const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
              
        // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소 
        // 이 요소만 필터링한다.
        const result = values.filter((val, i, _values) => _values.indexOf(val) === i);
        console.log(result); // [1, 2, 3, 5, 4]
      
    • 중복 요소 제거에는 Set을 사용하는 방법을 추천한다.

      1
      2
      3
      4
      5
      
        const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
              
        // 중복을 허용하지 않는 Set 객체의 특성을 활용하여 배열에서 중복된 요소를 제거 가능
        const result = [...new Set(values)];
        console.log(result); // [1, 2, 3, 5, 4]
      

27.9.6 Array.prototype.some

1
2
3
4
5
6
7
8
9
10
11
// 배열의 요소 중에 10보다 큰 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item > 10); // -> true

// 배열의 요소 중에 0보다 작은 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item < 0); // -> false

// 배열의 요소 중에 'banana'가 1개 이상 존재하는지 확인
['apple', 'banana', 'mango'].some(item => item === 'banana'); // -> true

// some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환한다.
[].some(item => item > 3); // -> false
  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이 단 한 번이라도 참이면 true, 모두 거짓이면 false 반환
    • 빈 배열이 some 메서드를 호출하면 항상 false 반환
  • some 메서드의 콜백 함수
    • 콜백 함수의 첫 번째 인수: some 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: some 메서드를 호출한 배열 자체(this)
  • some 메서드의 두 번째 인수: some 메서드의 콜백 함수 내부에서 this로 사용할 객체
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

27.9.7 Array.prototype.every

1
2
3
4
5
6
7
8
// 배열의 모든 요소가 3보다 큰지 확인
[5, 10, 15].every(item => item > 3); // -> true

// 배열의 모든 요소가 10보다 큰지 확인
[5, 10, 15].every(item => item > 10); // -> false

// every 메서드를 호출한 배열이 빈 배열인 경우 언제나 true를 반환한다.
[].every(item => item > 3); // -> true
  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false 반환
    • 빈 배열이 every 메서드를 호출하면 항상 true 반환
  • every 메서드의 콜백 함수
    • 콜백 함수의 첫 번째 인수: every 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: every 메서드를 호출한 배열 자체(this)
  • every 메서드의 두 번째 인수: every 메서드의 콜백 함수 내부에서 this로 사용할 객체
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

27.9.8 Array.prototype.find

1
2
3
4
5
6
7
8
9
const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// id가 2인 첫 번째 요소를 반환한다. find 메서드는 배열이 아니라 요소를 반환한다.
users.find(user => user.id === 2); // -> {id: 2, name: 'Kim'}
  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 반환값이 true인 첫 번째 요소 반환, 존재하지 않는다면 undefined 반환
  • find 메서드의 콜백 함수
    • 콜백 함수의 첫 번째 인수: find 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: find 메서드를 호출한 배열 자체(this)
  • filter 메서드는 콜백 함수 호출 결과가 true인 요소만 추출한 새로운 배열 반환 find 메서드는 콜백 함수의 반환값이 true인 첫 번째 요소 반환

    1
    2
    3
    4
    5
    
      // Array#filter는 배열을 반환한다.
      [1, 2, 2, 3].filter(item => item === 2); // -> [2, 2]
        
      // Array#find는 요소를 반환한다.
      [1, 2, 2, 3].find(item => item === 2); // -> 2
    
  • find 메서드의 두 번째 인수: find 메서드의 콜백 함수 내부에서 this로 사용할 객체
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

27.9.9 Array.prototype.findIndex

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
const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// id가 2인 요소의 인덱스를 구한다.
users.findIndex(user => user.id === 2); // -> 1

// name이 'Park'인 요소의 인덱스를 구한다.
users.findIndex(user => user.name === 'Park'); // -> 3

// 위와 같이 프로퍼티 키와 프로퍼티 값으로 요소의 인덱스를 구하는 경우
// 다음과 같이 콜백 함수를 추상화할 수 있다.
function predicate(key, value) {
  // key와 value를 기억하는 클로저를 반환
  return item => item[key] === value;
}

// id가 2인 요소의 인덱스를 구한다.
users.findIndex(predicate('id', 2)); // -> 1

// name이 'Park'인 요소의 인덱스를 구한다.
users.findIndex(predicate('name', 'Park')); // -> 3
  • ES6에서 도입
  • 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 반환값이 true인 첫 번째 요소의 인덱스 반환, 존재하지 않는다면 -1 반환
  • findIndex 메서드의 콜백 함수
    • 콜백 함수의 첫 번째 인수: findIndex 메서드를 호출한 배열의 요소값
    • 콜백 함수의 두 번째 인수: 인덱스
    • 콜백 함수의 세 번째 인수: findIndex 메서드를 호출한 배열 자체(this)
  • findIndex 메서드의 두 번째 인수: findIndex 메서드의 콜백 함수 내부에서 this로 사용할 객체
    • ES6의 화살표 함수를 사용하면 상위 스코프 내부의 this를 그대로 참조할 수 있다.

27.9.10 Array.prototype.flatMap

  • ES10에서 도입
  • map 메서드를 통해 생성된 새로운 배열을 평탄화한다.
    • map 메서드와 flat 메서드를 순차적으로 실행하는 효과가 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      const arr = ['hello', 'world'];
        
      // map과 flat을 순차적으로 실행
      arr.map(x => x.split('')).flat();
      // -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
        
      // flatMap은 map을 통해 생성된 새로운 배열을 평탄화한다.
      arr.flatMap(x => x.split(''));
      // -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
    
  • flat과 다르게 1단계만 평탄화한다.
    • 평탄화 깊이를 지정해야 하면 map 메서드와 flat 메서드를 각각 호출한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      const arr = ['hello', 'world'];
        
      // flatMap은 1단계만 평탄화한다.
      arr.flatMap((str, index) => [index, [str, str.length]]);
      // -> [[0, ['hello', 5]], [1, ['world', 5]]] => [0, ['hello', 5], 1, ['world', 5]]
        
      // 평탄화 깊이를 지정해야 하면 flatMap 메서드를 사용하지 말고 
      // map 메서드와 flat 메서드를 각각 호출한다.
      arr.map((str, index) => [index, [str, str.length]]).flat(2);
      // -> [[0, ['hello', 5]], [1, ['world', 5]]] => [0, 'hello', 5, 1, 'world', 5]
    
This post is licensed under CC BY 4.0 by the author.