본문 바로가기
JavaScript/JavaScript 예복습

JavaScript 예습 | 27장 배열

by 히욤 2021. 9. 26.

 

27장 배열 정리

https://github.com/kheeyaa/TIL/tree/main/javascript/deepdive/Array

 

GitHub - kheeyaa/TIL: Today I Learned..

Today I Learned.. Contribute to kheeyaa/TIL development by creating an account on GitHub.

github.com

 

Array

// Array
// 배열: 여러 개의 값을 순차적으로 나열한 자료구조
// 요소: 배열이 가지고 있는 값
// 인덱스: 자신의 위치를 나타내는 0 이상의 정수
// length 프로퍼티: 배열의 길이

// 자바스크립트 배열 특징 ------------------------------------------------------------------
// - 밀집 배열 dense array
// 배열의 요소는 하나의 데이터 타입으로 통일되어 있고, 서로 연속적으로 인접해 있다.
// - 희소 배열
// 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되고, 연속적으로 이어져있지 않아도 된다.
// 희소배열의 length는 희소 배열의 실제 요소 개수보다 언제나 크다

// 배열 생성 ------------------------------------------------------------------
// 1. 배열 리터럴
{
  const arr1 = [1, 2, 3];
  console.log(arr1.length); // 3

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

  const arr3 = [1, , 3]; // 희소배열
  console.log(arr3.length); // 3
  console.log(arr3); // [ 1, <1 empty item>, 3 ]
  console.log(arr3[1]); // undefined
}
// 2. Array 생성자 함수
{
  // 2-1. 전잘된 인수가 1개이고 숫자인 경우 => length 프로퍼티 값이 인수인 배열 생성
  const arr1 = new Array(10);
  console.log(arr1); // [ <10 empty items> ] => 희소 배열
  console.log(arr1.length); // 10

  // 2-2. 빈 배열
  const arr2 = new Array(); // []

  // 2-3. 인수가 2개 이상이거나, 숫자가 아닌 경우 => 인수를 요소로 갖는 배열 생성
  const arr3 = new Array(1, 2, 3); // [1,2,3]
  const arr4 = new Array({}); // [{}]
}
// 3. Array.of
{
  // 전달된 인수를 요소로 갖는 배열 생성
  // 전달된 인수가 1개고 숫자여도 인수를 요소로 가진다.
  const arr1 = Array.of(1); // [1]
  const arr2 = Array.of(1, 2, 3); // [1,2,3]
  const arr3 = Array.of('string'); // ['string']
}
// 4. Array.from 메서드
{
  // 인수로 유사 배열 객체 또는 이터러블 객체를 받아 배열로 변환하여 반환함
  // 4-1. 유사 배열 객체
  const arr1 = Array.from({ length: 2, 0: 'a', 1: 'b' }); // ['a', 'b']

  // 4-2. 이터러블
  const arr2 = Array.from('Hello'); // [ 'H', 'e', 'l', 'l', 'o' ]

  // 4-3. 두 번째 인수로 전달한 콜백함수의 반환값으로 구성된 배열을 반환함
  const arr3 = Array.from({ length: 3 }); // [ undefined, undefined, undefined ]
  const arr4 = Array.from({ length: 3 }, (_, i) => i); // [ 0, 1, 2 ]
}

// 배열 참조 ------------------------------------------------------------------
{
  // 존재하지 않는 요소에 접근하면 undefined
  // 존재하지 않는 프로퍼티 키로 객체의 프로퍼티에 접근했을 때 undefined를 반환하는 것처럼
  const arr = [1, 2];
  console.log(arr[2]); // undefined
}

// 배열 요소 추가 갱신 ------------------------------------------------------------------
{
  // 1. 요소 추가시 length 자동 갱신
  const arr = [0];
  arr[1] = 1;
  console.log(arr.length); // 2

  // 2. length보다 큰 인덱스로 새로운 요소를 추가하면 희소배열이 된다
  arr[100] = 100;
  console.log(arr); // [ 0, 1, <98 empty items>, 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
    }
  }
  */

  // 3. 요소값 갱신
  arr[1] = 10;
  console.log(arr); // [ 0, 10, <98 empty items>, 100 ]
}

// 배열 요소 삭제 ------------------------------------------------------------------
{
  // 1. delete 연산자
  // 프로퍼티 키가 '1'인 프로퍼티를 삭제하고 희소 배열이 된다. 쓰지말자!
  const arr1 = [1, 2, 3];
  delete arr1[1];
  console.log(arr1); // [ 1, <1 empty item>, 3 ]
  console.log(arr1.length); // 3 => 희소 배열

  // 2. Array.prototype.splice(삭제 시작 인덱스, 삭제할 요소 수) 메서드
  const arr2 = [1, 2, 3];
  arr2.splice(1, 1);
  console.log(arr2); // [1, 3]
  console.log(arr2.length); // 2
}

 

Array Method

// Array method

// mutator method: 원본 배열을 직접 변경하는 메서드
// accessor method: 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드
// 가급적 원본 배열을 직접 변경하지 않는 메서드를 사용하자!

// Array.isArray ------------------------------------------------------------------
{
  // 전달된 인수가 배열이면 true, 배열이 아니면 false
  // 1. true
  Array.isArray([]);
  Array.isArray([1, 2]);
  Array.isArray(new Array());

  // 2. 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.prototype.indexOf ------------------------------------------------------------------
{
  // 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환함
  // 1. 중복되는 요소가 여러 개 있다면 첫 번째로 검색된 요소의 인덱스를 반환
  // 2. 존재하지 않으면 -1
  // 3. 두번째 인수는 검색을 시작할 인덱스. 두번째 인수를 생략하면 처음부터 검색함
  // NaN이 포함되어있는지 확인 불가
  const arr = [1, 2, 2, 3];
  console.log(arr.indexOf(2)); // 1
  console.log(arr.indexOf(4)); // -1
  console.log(arr.indexOf(2, 2)); // 2

  // 배열에 특정 요소가 있는지 확인할 때 유용하다
  // ES7 에 도입된 Array.prototype.include 메서드가 가독성이 더 좋다
}

// Array.prototype.push ------------------------------------------------------------------
{
  // 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가
  // 변경된 length 프로퍼티 값을 반환함
  // 원본 배열을 직접 변경 => 부수 효과 => 스프레드 문법을 사용하자 [...arr, 3, 4]
  const arr = [1, 2];
  let result = arr.push(3, 4);
  console.log(arr); // [ 1, 2, 3, 4 ]
  console.log(result); // 4

  // push 메서드는 성능이 좋지 않다.
  // 마지막 요소로 추가할 요소가 하나 뿐이면 length 프로퍼티를 사용해 직접 추가하는 편이 더 빠르다.
  arr[arr.length] = 5;
  console.log(arr); // [ 1, 2, 3, 4, 5 ]
}

// Array.prototype.pop ------------------------------------------------------------------
{
  // 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환
  // 빈 배열인 경우 undefined를 반환
  // 원본 배열을 직접 변경
  const arr = [1, 2];
  let result = arr.pop(); // 2
}

// Array.prototype.unshift ------------------------------------------------------------------
{
  // 인수로 전달 받은 모든 값을 원본 배열의 선두에 요소로 추가
  // 변경된 length 프로퍼티 값을 반환
  // 원본 배열을 직접 변겅
  const arr = [1, 2];
  let result = arr.unshift(3, 4); // 4
  console.log(arr); // [ 3, 4, 1, 2 ]

  // 부수효과가 있으니 ES6 스프레드 문법을 사용하자
  const newArr = [5, ...arr];
  console.log(newArr); // [ 5, 3, 4, 1, 2 ]
}

// Array.prototype.shift ------------------------------------------------------------------
{
  // 원본 배열에서 첫 번째 요소를 제거함
  // 제거한 요소를반환
  // 빈 배열인 경우 undefined를 반환
  // 원본 배열 직접 변경
  const arr = [1, 2];
  console.log(arr.shift()); // 1
  console.log(arr); // [2]
}

// Array.prototype.concat ------------------------------------------------------------------
{
  // 인수로 전달된 값들(배열 또는 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환
  // 인수로 전달한 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가
  // 원본 배열은 변경되지 않음
  const arr1 = [1, 2];
  const arr2 = [3, 4];
  console.log(arr1.concat(arr2)); // [ 1, 2, 3, 4 ]
  console.log(arr1.concat(3)); // [ 1, 2, 3 ]
  console.log(arr1.concat(arr2, 5)); // [ 1, 2, 3, 4, 5 ]
  console.log(arr1); // [ 1, 2 ]

  // unshift와 push 메서드는 인수로 전달 받은 배열을 그대로 원본 배열의 요소로 추가한다.
  const arr = [3, 4];
  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 ]

  // concat은 ES6의 스프레드 문법으로 대체 가능
  let result2 = [...[1, 2], ...[3, 4]];
  console.log(result2); // [ 1, 2, 3, 4 ]
}

// Array.prototype.splice ------------------------------------------------------------------
{
  // 원본 배열의 중간에 요소를 추가하거나 제거할 때
  // 제거한 요소가 배열로 반환됨
  // 원본 배열을 직접 변경

  // Array.prototype.splice(start, deleteCount, items)
  // * start: 원본 배열의 요소를 제거하기 시작할 인덱스
  // => -1 : 마지막 인덱스, -n: 마지막에서 n번째 요소
  // * deleteCount: 제거할 요소 개수, 0이면 제거되지 않는다, 생략하면 start부터 이후 요소를 모두 삭제한다.
  // * items: 제거한 위치에삽입할 요소들의 목록, 생략하면 요소들을 제거하기만 한다

  const arr = [1, 2, 3, 4];
  const result = arr.splice(1, 2, 20, 30);
  console.log(result); // 제거한 요소: [ 2, 3 ]
  console.log(arr); // 원본 변경: [ 1, 20, 30, 4 ]
}

// Array.prototype.slice ------------------------------------------------------------------
{
  // 인수로 전달된 범위의 요소들을 복사하여 배열로 반환함
  // 원본 배열은 변경되지 않음
  // splice와 혼돈하지 말자

  // Array.prototype.slice(start, end)
  // * start: 복사를 시작할 인덱스
  // => -1 : 마지막 인덱스, -n: 마지막에서 n번째 요소
  // * end: 복사를 종료한 인덱스, 이 인덱스에 해당하는 요소는 복사되지 않는다. 생략하면 끝까지 복사한다.
  // 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환함 (얕은 복사. lv1 까지만 복사함.)

  const arr = [1, 2, 3];
  console.log(arr.slice(0, 1)); // [1]
  console.log(arr.slice(1, 2)); // [2]
  console.log(arr.slice(1)); // [2, 3]
  console.log(arr.slice(-2)); // [2, 3] : 끝에서부터 두 개 복사
  console.log(arr.slice()); // [ 1, 2, 3 ] : 원본 배열 복사
  console.log(arr); // [ 1, 2, 3 ] => 원본 그대로
}

// Array.prototype.join ------------------------------------------------------------------
{
  // 원본 배열의 모든 요소를 문자열로 변환한 후
  // 인수로 받은 문자열, 즉 구분자로 연결한 문자열을 반환함.
  // 원본은 변하지 않음
  const arr = [1, 2, 3, 4];

  // 기본은 콤마 (',')
  console.log(arr.join()); // 1,2,3,4
  console.log(arr.join('')); // 1234
  console.log(arr.join(':')); // 1:2:3:4
  console.log(arr); // [ 1, 2, 3, 4 ]
}

// Array.prototype.reserve ------------------------------------------------------------------
{
  // 원본 배열의 순서를 반대로 뒤집음
  // 원본 배열이 변경됨
  // 반환값은 변경된 배열
  const arr = [1, 2, 3];
  const result = arr.reverse();
  console.log(arr); // [ 3, 2, 1 ]
  console.log(result); // [ 3, 2, 1 ]
}

// Array.prototype.fill ------------------------------------------------------------------
{
  // 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채운다.
  // 원본 배열이 변경된다
  const arr1 = [1, 2, 3];
  arr1.fill(0);
  console.log(arr1); // [ 0, 0, 0 ]

  // 두 번째 인수로 요소를 채우기 시작할 인덱스를 전달할 수 있다.
  const arr2 = [1, 2, 3, 4, 5];
  arr2.fill(0, 1);
  console.log(arr2); // [ 1, 0, 0, 0, 0 ]

  // 세 번째 인수로 요소 채우기를 멈출 인덱스를 전달
  const arr3 = [1, 2, 3, 4, 5];
  arr3.fill(0, 1, 3);
  console.log(arr3); // [ 1, 0, 0, 4, 5 ]
}

// Array.prototype.includes ------------------------------------------------------------------
{
  // 배열 내에 특정 요소가 포함되어 있는지 확인하여 true 또는 false를 반환
  // 첫 번째 인수로 검색할 대상 지정
  // 두 번째 인수로 검색을 시작할 인덱스 전달, 음수라면 (legnth + index)로 검색 시작 인덱스 설정
  // NaN이 포함되어 있는지 확인 가능

  const arr = [1, 2, 3];
  arr.includes(2); // true
  arr.includes(100); // false
  arr.includes(1, 1); // false
  arr.includes(3, -1); // true
}

// Array.prototype.flat ------------------------------------------------------------------
{
  // 인수로 전달한 깊이 만큼 재귀적으로 배열을 평탄화 한다.
  console.log([1, [2, 3, 4, 5]].flat()); // [ 1, 2, 3, 4, 5 ]

  // 깊이의 기본 값은 1
  console.log([1, [2, [3, [4]]]].flat()); // [ 1, 2, [ 3, [ 4 ] ] ]
  console.log([1, [2, [3, [4]]]].flat(1)); // [ 1, 2, [ 3, [ 4 ] ] ]

  // 중첩 배열 모두 평탄화 Infinity
  console.log([1, [2, [3, [4]]]].flat(Infinity)); // [ 1, 2, 3, 4]
}

 

Array Higher Order Function

// 배열 고차 함수 Array Higher-Order Function
// 함수를 인수로 전달 받거나 함수를 반환하는 함수를 말함.
// 고차 함수는 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에 기반을 두고있다.

// 함수형 프로그래밍
// - 순수 함수와 보조 함수의 조합으로 로직 내 조건문과 반복문을 제거하여 복잡성을 해결
// - 변수의 사용을 억제하여 상태 변경을 피함
// 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 프로그맹의 안정성을 높이려는 노력의 일환임.

//  Array.prototype.sort ------------------------------------------------------------------
{
  // 배열의 요소를 정렬함
  // 원본 배열을 직접 변경
  // 정렬된 배열을 반환함

  // 1. 기본 오름차순
  const fruits = ['Banana', 'Orange', 'Apple'];
  fruits.sort();
  console.log(fruits); // [ 'Apple', 'Banana', 'Orange' ]

  // 2. 한글 문자열도 오름차순
  const fruits2 = ['바나나', '오렌지', '사과'];
  fruits2.sort();
  console.log(fruits2); // [ '바나나', '사과', '오렌지' ]

  // 3. 내림차순 정렬
  // sort로 오름차순 한 다음 reverse로 순서 뒤집기
  console.log(fruits); // [ 'Apple', 'Banana', 'Orange' ]
  fruits.reverse();
  console.log(fruits); // [ 'Orange', 'Banana', 'Apple' ]

  // 4. 숫자 요소로 이루어진 배열 정렬
  const points = [40, 100, 1, 5, 2, 25, 10];
  points.sort();
  // 4-1. 숫자 요소들로 이뤄진 배열은 의도한 대로 정렬되지 않는다.
  console.log(points); // [1, 10, 100, 2, 25, 40, 5]
  // 배열의 요소가 숫자 타입이면 일시적으로 문자열로 변환한 후 "유니코드" 코드 포인트의 순서를 기준으로 정렬하기 때문!

  // 4-2. 숫자 요소를 정렬할 때는 sort 매서드에 <정렬 순서를 정의하는 비교 함수>를 인수로 전달해야함.
  // 숫자 배열의 오름차순 정렬.
  // 비교 함수의 반환값이 0보다 작으면 a를 우선하여 정렬한다.
  points.sort((a, b) => a - b);
  console.log(points); // [1, 2, 5, 10, 25, 40, 100]

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

  // 5. 객체를 요소로 갖는 배열을 정렬
  const todos = [
    { id: 4, content: 'JavaScript' },
    { id: 1, content: 'HTML' },
    { id: 2, content: 'CSS' },
  ];

  // 5-1. 비교함수, 매개변수 key는 프로퍼티 키
  function compare(key) {
    // 프로퍼티 값이 문자열인 경우 - 산술 연산을 하면 NaN이 나올 수 있음. 그래서 비교 연산을 수행
    // 비교 함수는 양수/음수/0을 반환하면 됨
    // 오름차순 정렬
    // 양수,0: 그대로 / 음수: a를 기준으로 정렬
    return (a, b) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0);
  }

  // 5-2. id를 기준으로 오름차순 정렬
  todos.sort(compare('id'));
  console.log(todos);
  /*
  [
    { id: 1, content: 'HTML' },
    { id: 2, content: 'CSS' },
    { id: 4, content: 'JavaScript' }
  ]*/

  // 5-3. content를 기준으로 오름차순 정렬
  todos.sort(compare('content'));
  console.log(todos);
  /*
  [
    { id: 2, content: 'CSS' },
    { id: 1, content: 'HTML' },
    { id: 4, content: 'JavaScript' }
  ]
  */
}

//  Array.prototype.forEach ------------------------------------------------------------------
{
  // 내부 반복문을 통해 자신을 호출한 배열을 순회하며 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출함
  // 1. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 2. forEach는 원본 배열을 변경하지 않는다.
  // 3. 반환값은 언제나 undefined
  // 4. forEach 메서드의 두 번째 인수로 forEach 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  // 5. break, continue 문을 사용할 수 없다
  // 6. 존재하지 않는 요소는 순회 대상에서 제외된다.
  const numbers = [1, 2, 3];
  const pows = [];
  numbers.forEach(item => pows.push(item ** 2));
  console.log(pows); // [ 1, 4, 9 ]

  // 1. 콜백 함수의 인수
  // (1) 배열의 요소값 (2) 인덱스 (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] */

  // 2. forEach는 원본 배열을 변경하지 않는다.

  // 3. 반환값은 언제나 undefined

  // 4. forEach 메서드의 두 번째 인수로 forEach 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  {
    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]
  }

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

  // 5. break, continue 문을 사용할 수 없다

  // 6. 존재하지 않는 요소는 순회 대상에서 제외된다.
  {
    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
  }
}

//  Array.prototype.map ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.
  // "콜백 함수의 반환값"들로 구성된 새로운 배열을 반환한다.
  // 원본 배열은 변경되지 않는다.
  // 1. 원본 배열과 반환된 배열은 1:1 매핑된다
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. map 메서드의 두 번째 인수로 map 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  const numbers = [1, 4, 9];
  const roots = numbers.map(item => Math.sqrt(item));
  console.log(roots); // [ 1, 2, 3 ]
  console.log(numbers); // [ 1, 4, 9 ]

  // 1. 원본 배열과 반환된 배열은 1:1 매핑된다
  // map 메서드가 생성하여 반환하는 새로운 배열의 length 프로퍼티 값은
  // map 메서드를 호출한 배열의 length 프로퍼티 값과 반드시 일치함
  // 즉, map 메서드를 호출한 배열과 map 메서드가 생성하여 반환한 배열은 1:1 매핑됨

  // 2. 콜백 함수의 인수
  // (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  [1, 2, 3].map((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] */

  // 3. map 메서드의 두 번째 인수로 map 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  {
    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']
  }

  // 3-1. 화살표 함수가 더 좋다
  {
    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']
  }
}

//  Array.prototype.filter ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 1. 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. filter 메서드의 두 번째 인수로 filter 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다

  // 1. 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
  // filter 메서드가 생성하여 반환한 새로운 배열의 length 프로퍼티 값은
  // filter 메서드를 호출한 배열의 length 프로퍼티 값과 같거나 작다.
  {
    const numbers = [1, 2, 3, 4, 5];
    const odds = numbers.filter(item => item % 2);
    console.log(odds); // [ 1, 3, 5 ]
  }
  // forEach: 반환값은 undfined
  // map: 콜백함수의 반환값들로 구성된 배열을 새로 반환
  // filter: 콜백함수의 반환값이 true인 요소만 추출하여 새로운 배열을 반환

  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (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]
   */

  // 3. filter 메서드의 두 번째 인수로 filter 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  // filter 메서드는 자신이 호출한 배열에서 특정 요소를 제거하기 위해 사용할 수 있음
  {
    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); // []
  }
}

//  Array.prototype.reduce ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 1. 콜백 함수의 반환값을 다음 순회 시 콜백 함수의 첫 번째 인수로 전달하면서
  // 콜백 함수를 호출하여 <하나의 결과값>을 만들어 반환한다.
  // 2. reduce의 인수 (1) 콜백 함수 (2) 초기값 : 생략은 가능하지만 안전하지 않다.
  // 3. 콜백 함수의 인수 (1) 초기값 또는 함수의 이전 반환값 (2) 배열의 요소값 (3) 인덱스 (4) 호출한 배열 자체(this)

  // 누적 합 구하기
  {
    const sum = [1, 2, 3, 4].reduce(
      (accumulator, currentValue, index, array) => accumulator + currentValue,
      0
    );
    console.log(sum); // 10
  }

  // 평균 구하기
  {
    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
  }

  // 최대값 구하기
  {
    const values = [1, 2, 3, 4, 5];
    const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0);
    console.log(max); // 5
  }

  // 요소의 중복 횟수 구하기
  {
    const fruits = ['banana', 'apple', 'orange', 'orange', 'apple'];
    const count = fruits.reduce((acc, cur) => {
      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 }
  }

  // 중첩 배열 평탄화
  {
    const values = [1, [2, 3], 4, [5, 6]];
    const flatten = values.reduce((acc, cur) => acc.concat(cur), []);
    console.log(flatten); // [ 1, 2, 3, 4, 5, 6 ]
  }

  // 중복 요소 제거
  {
    const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
    const result = values.reduce((acc, cur, i, arr) => {
      if (arr.indexOf(cur) === i) acc.push(cur);
      return acc;
    }, []);
    console.log(result); // [ 1, 2, 3, 5, 4 ]
  }
}

//  Array.prototype.some ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 배열의 요소 중에 콜백 함수를 통해 정의한 조건을 만족하는 요소가 1개 이상 존재하는지 그 결과를 불리언 타입으로 반환한다.
  // 1. 콜백 함수의 반환값이 단 한 번이라도 참이면 treu, 모두 거짓이면 false, 빈 배열인 경우 언제나 false
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. some 메서드의 두 번째 인수로 some 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다

  [5, 10, 15].some(item => item > 10); // true
  [5, 10, 15].some(item => item < 0); // false
  ['apple', 'banana', 'mango'].some(item => item === 'banana'); // true
  [].some(item => item > 3); // false
}

//  Array.prototype.every ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 배열의 요소 중에 콜백 함수를 통해 정의한 조건을 모두 만족하는지 확인하여 그 결과를 불리언 타입으로 반환한다.
  // 1. 콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false, 빈 배열인 경우 언제나 true
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. every 메서드의 두 번째 인수로 every 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다

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

//  Array.prototype.find ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 1. 콜백 함수의 반환값이 true인 첫 번째 요소를 반환한다. 콜백 함수의 반환값이 true인 요소가 없다면 undefined를 반환한다.
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. find 메서드의 두 번째 인수로 find 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
  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'}

  // filter 메서드는 배열을 반환한다.
  [1, 2, 2, 3].filter(item => item === 2); //  [2, 2]
  // find 메서드는 요소를 반환한다.
  [1, 2, 2, 3].find(item => item === 2); //  2
}

//  Array.prototype.findIndex ------------------------------------------------------------------
{
  // 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출, 원본 배열을 바꾸지 않는다.
  // 1. 콜백 함수의 반환값이 true인 첫 번째 요소의 인덱스를 반환한다. 콜백 함수의 반환값이 true인 요소가 없다면 -1를 반환한다.
  // 2. 콜백 함수의 인수 (1) 배열의 요소값 (2) 인덱스 (3) 호출한 배열 자체(this)
  // 3. findIndex 메서드의 두 번째 인수로 findIndex 메서드 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다

  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
}

//  Array.prototype.flatMap ------------------------------------------------------------------
{
  // flatMap 메서드는 map 메서드를 통해 생성된 새로운 배열을 평탄화한다.
  // 즉 map 메서드와 flat 메서드를 순차적으로 실행하는 효과가 있다.
  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']

  // 단, flatMap 메서드는 flat 메서드처럼 인수를 전달하여 평탄화 깊이를 지정할 수는 없고 1단계만 평탄화한다.
  // map 메서드를 통해 생성된 중첩 배열의 평탄화 깊이를 지정해야 하면 flatMap 메서드를 사용하지 말고 map 메서드와 flat 메서드를 각각 호출한다.
  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]
}