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

EdLee·2022년 11월 17일

javascript

목록 보기
17/37

27장 배열

1. 배열이란?


const arr = ['apple', 'banana', 'orange'];
console.log(arr[0]); // apple
console.log(arr[2]); // orange
console.log(arr.length); // 3

// 순회가 가능
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

console.log(typeof arr); // object 타입

// 배열의 생성자 함수는 'Array'
console.log(arr.constructor === Array); // true
// 배열의 프로토타입 객체는 Array.prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
  • 배열은 배열 리터럴, Array 생성자 함수, Array.of, Array.from 메서드로 생성할 수 있다
  • 배열은 객체지만, 일반 객체와는 다음과 같은 차이점이 있다.
구분객체배열
구조프로퍼티 키와 프로퍼티 값인덱스와 요소
값의 참조프로퍼티 키인덱스
값의 순서XO
length 프로퍼티XO
  • 배열은 값의 순서를 갖고 있어서 순차 접근이 가능하다.

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


  • 밀집 배열 : 일반적인 배열. 각 요소가 동일한 데이터를 가지며 빈틈없이 연속적으로 이어져 있는 데이터의 집합.
  • 희소 배열 : 각 요소가 동일한 크기를 갖지 않고 연속적으로 이어져 있지 않을 수도 있다.
  • 자바 스크립트의 배열은 바로 이 희소 배열이 가능하다.
const arr = [
  'string',
  10,
  true,
  null,
  undefined,
  NaN,
  Infinity,
  [],
  {}, 
  function (){}
];

// 뭐든 올 수 있다
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
종류밀집배열희소배열
장점익덱스로 요소에 빠르게 접근 가능밀집배열보다 인덱스로 접근하는 것이 느리다
단점요소를 삽입 또는 삭제하는 효율이 좋지 않다요소를 삽입, 삭제하는 경우 밀집배열보다 빠르다
  • 자바스크립트의 배열은 해시 테이블을 이용해 배열의 동작을 흉내 낸 특수한 객체이다.
  • 다만, 느린 속도를 보완하기 위해 JS엔진에서 최적화 과정을 거치기 때문에, 일반적인 객체보다는 2배 정도 속도가 빠르다.

3. length 프로퍼티와 희소 배열


[].length // 0
const arr = [1,2,3,4,5]
arr.length // 5

arr.push(6); // 6
arr.pop(); // 5

// 길이를 줄여서 배열의 요소를 삭제할 수 있다.
arr.length = 3;
console.log(arr.length); // 3
console.log(arr); // 1,2,3

// 길이를 늘리면, 길이는 늘어나지만, 실제 요소가 할당되지도 않고, 메모리 공간이 확보되지도 않는다.
arr.length = 5;
console.log(arr.length); // 5
console.log(arr); // 1,2,3, undefined, undefined

// 배열의 중간이 비어 있을 수도 있다
arr.push(6); // 6
console.log(arr.length); // 6
console.log(arr); // 1,2,3, undefined, undefined, 6
  • JS는 엔진의 최적화 기능에 의해, 요소들의 타입이 서로 일치하는 배열을 생성하면, 밀집 배열과 같이 연속된 메모리 공간을 확보한다.
  • 하지만 위처럼 중간이 비어있거나 타입이 다르면, 희소 배열이 생성되어 성능이 떨어지게 된다.
  • 따라서 되도록 희소 배열을 만들지 않는 것이 성능적으로도, 실수 방지 측면에서도 유리하다.

4. 배열 생성


4.1 배열 리터럴

const arr = [1,2,3];

const arr2 = [1,,3];
console.log(arr2.length); // 3
console.log(arr2[1]); // undefined
  • 배열 리터럴 방식은 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재한다

4.2 Array 생성자 함수

const arr = new Array(10);

console.log(arr); // undefined * 10
console.log(arr.length); // 10

// 배열의 최대 길이는 2^32 -1 이다
new Array(4294967295);

// 넘어서면 에러 발생
new Array(4294967296); // RangeError: Invalid array length

// 음수여도 에러 발생
new Array(-1); // RangeError: Invalid array length

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

// 인수가 2개 이상이거나, 숫자가 아니면? 인수를 요소로 갖는 배열 생성
new Array(1,2,3); // [1,2,3]
new Array({}); // [{}]

// new 연산자가 없어도 일반 함수가 아닌 생성자 함수로 동작한다. new.target을 확인해서 동작하기 때문(17.2.7절)
Array(1,2,3);

4.3 Array.of

Array.of(1); // [1]
Array.of(1,2,3); // [1,2,3]
Array.of('string'); // ['string']
  • 전달된 인수를 요소로 갖는 배열 생성

4.4 Array.from

// 유사 배열 객체를 변환하여 배열 생성
Array.from({ length: 2, 0: 'a', 1:'b'}); // ['a','b']

// 이터러블은 변환하여 배열을 생성
Array.from('Hello'); // ['H','e','l','l','o']

// 길이만 있는 유사 배열 객체 전달 시, undefined 요소로 채운다
Array.from({ length: 3}); // ['undefined', 'undefined', 'undefined']

// 두 번째 인수로 콜백 함수를 전달하면, 그 반환값으로 구성된 배열을 만든다
Array.from({ length: 3}, (_, i) => i); // [0, 1, 2]
// 위 화살표 함수의 동작 과정
// 인자1에는 첫번째 요소 전달, 인자2에는 index 전달
// (undefined, 0) => return 0
// (undefined, 1) => return 1
// (undefined, 2) => return 2
Array.from({ length: 3, 1:'a'}, (i, _) => i); // [undefined, a, undefined]
  • 이터러블을 주면 분해해서 배열을 만든다
  • 두번째 인자로 콜백 함수를 줄 수 있다.

5. 배열 요소의 참조


  • 대괄호 표기법 사용
  • 존재하지 않는 요소를 참조하면 undefined를 반환
const arr = [0,,2];
console.log(arr[2]); // 2
console.log(arr[1]); // undefined

console.log(Object.getOwnPropertyDescriptors(arr));
/*
0: Object
configurable: true
enumerable: true
value: 0
writable: true
__proto__: Object

2: Object
configurable: true
enumerable: true
value: 2
writable: true
__proto__: Object

length: Object
configurable: false
enumerable: false
value: 3
writable: true
__proto__: Object
*/

배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 갖는 객체다. 따라서 배열이 undefined를 반환하는 것은 존재하지 않는 프로퍼티 키로 객체의 프로퍼티에 접근했을 때 undefined를 반환하는 것과 완전히 동일.

6. 배열의 요소 추가와 갱신


  • 배열에 요소를 동적으로 추가할 수 있다.
  • length는 자동으로 갱신된다.
  • 인덱스로 정수를 전달하지 않으면, 배열 요소가 아닌 프로퍼티가 생성되며 length는 갱신되지 않는다.
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]

console.log(arr.length); // 2
console.log(arr['foo']); // 3, 키값 던지면 잘 가져오긴 한다

7. 배열 요소의 삭제


  • Array.prototype.slice 메서드를 사용해 배열 요소를 삭제 가능
  • 배열을 객체이기 때문에, delete 연산자도 사용 가능하다
  • 하지만 프로퍼티만 삭제하고, length는 변하지 않아 희소 배열이 된다.
const arr = [1,2,3,4];

arr.splice(1,1); // index 1 부터 1개 제거
console.log(arr); // [1,3,4]
console.log(arr.length); // 3

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

8. 배열 메서드


  • 다양한 빌트인 메서드가 제공된다.
  • 배열 메서드의 return 패턴은 다음 두 가지이다.

배열 메서드
1. 원본 배열을 직접 변경하는 메서드
2. 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드

const arr = [1];

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

// concat은 새로운 배열을 생성해 반환
const result = arr.concar(3);
console.log(arr); // [1,2]
console.log(result); // [1,2,3]

8.1 Array.isArray

  • Array 생성자 함수의 정적 메서드
  • 전달된 인수가 배열이면 true, 배열이 아니면 false 반환
// true
Array.isArray([]);
Array.isArray([1,2]);
Array.isArray(new Array());

// false
Array.isArray();
Array.isArray(1);
Array.isArray({});
Array.isArray(null);
Array.isArray(true);
Array.isArray({0:1, length:1});

8.2 Array.prototype.indexOf

  • 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스 반환
  • 두 번째 인수는 검색을 시작할 인덱스, 생략 시 처음부터 검색함
  • 중복되는 여러 요소 있으면 첫 번째로 검색된 요소의 인덱스 반환
  • 인수로 전달한 요소가 존재하지 않으면 -1 반환
  • 인덱스가 필요없다면, ES7 Array.prototype.includes 메서드 사용
const foods = ['apple', 'banana', 'orange'];
if(foods.indexOf('orange') === -1) {
  foods.push('orange'); 
}
if(foods.includes('orange')) {
  foods.push('orange'); 
}
console.log(foods); // ['apple', 'banana', 'orange']

8.3 Array.prototype.push

  • 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가, 변경된 length 프로퍼티 값 반환
  • 성능 면에서 좋지 않음
  • 원본 배열 변경
const arr = [1, 2];

let result = arr.push(3,4);
console.log(result);// 4, 배열의 길이 리턴
console.log(arr); // 1,2,3,4, 원본 배열이 변경됨
  • push 대신 배열의 마지막에 length 프로퍼티 사용해 직접 추가하는 방법이 push 메서드보다 빠름
const arr = [1,2];

arr[arr.length] = 3;
console.log(arr); // [1,2,3]
  • ES6 스프레드 문법 사용하면 부수 효과 없음
const arr = [1,2];

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

8.4 Array.prototype.pop

  • 원본 배열에서 마지막 요소 제거하고 제거한 요소 반환
  • 원본 배열 변경
const arr = [1,2];

let result = arr.pop();
console.log(result); // 2, 마지막 제거한 요소 반환

console.log(arr); // [1], 원본 배열 직접 변경
  • pop 메서드와 push 메서드 사용하여 스택 쉽게 구현 가능
// 생성자 함수로 Stack 구현
const Stack = (function () {
  function Stack(array = []) {
    if (!Array.isArray(array)) {
      throw new TypeError(`${array} is not an array.`);
    }
    this.array = array;
  }
  
  Stack.prototype = {
    // 인스턴스의 프로토타입이 Object가 아닌 Stack을 가리키도록.. 이게 없으면 자동 생성된 Object의 constructor가 암묵적으로 생성되기 때문
    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]
// 클래스로 Stack 구현
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]

8.5 Array.prototype.unshift

  • 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가, 변경된 length 프로퍼티 값 반환
  • 원본 배열 변경
const arr = [1,2];

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

console.log(arr); // [3, 4, 1, 2]
  • 부수효과가 없는 ES6 스프레드 문법 사용 권장
const arr = [1, 2];

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

8.6 Array.prototype.shift

  • 원본 배열에서 첫 번째 요소 제거하고 제거한 요소 반환
  • 원본 배열 변경
const arr = [1,2];

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

console.log(arr); // [2]
  • shift 메서드와 push 메서드 사용하면 큐 쉽게 구현 가능
// 생성자 함수로 Queue 구현
const Queue = (function () {
  function Queue(array = []) {
    if (!Array.isArray(array)) {
      throw new TypeError(`${array} is not an array.`);
    }
    this.array = array;
  }

  Queue.prototype = {
    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]

queue.dequeue();
console.log(queue.entries()); // [2,3]
// 클래스로 Queue 구현
class Queue {
  #array;
  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]

8.7 Array.prototype.concat

  • 인수로 전달된 값들(배열 또는 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열 반환
  • 인수로 전달받은 배열을 해체하여 새로운 배열의 요소로 추가
  • push와 unshift 메서드는 concat 메서드로 대체하는 것을 권장
  • 원본 배열을 변경하지 않고, 새로운 배열을 반환한다
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]

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

// 원본 배열은 그대로
console.log(arr1); // [1,2]
  • 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]

8.8 Array.prototype.splice

  • 원본 배열의 중간에 요소 추가/제거할 때 사용
  • 원본 배열 변경
  • 3개의 매개변수 (start, deleteCount, items)
    - 제거한 요소가 배열로 반환됨
    - 두 번째 인수 생략하면 첫 번째 인수로 전달된 시작 인덱스부터 모든 요소 제거
const arr = [1,2,3,4];

// index 1에서 2개(2,3) 제거하고, 대신 20, 30을 집어 넣는다.
const result = arr.splice(1,2,20,30);

// 제거한 요소 
console.log(result); // [2,3]

// 원본 배열이 변경됨
console.log(arr); // [1,20,30,4]
  • 배열에서 특정 요소 제거하려면 indexOf 메서드 같이 활용
const arr = [1,2,3,1,2];

function remove(array, item) {
  const index = array.indexOf(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], 없다면 당연히 제거X
  • 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]

8.9 Array.prototype.slice

  • 인수로 전달된 범위의 요소들을 복사하여 배열로 반환
  • 원본 배열 보존
  • 2개의 매개변수 (start, end)
    - end의 요소는 복사되지 않음
    - end는 생략 가능 (기본값 : length 프로퍼티 값)
    - 인수 모두 생략하면 원본 배열의 복사본 생성하여 반환
const arr = [1,2,3];

arr.slice(0,1); // [1]
arr.slice(1,2); // [2]
arr.slice(1); // [2,3]
arr.slice(-1); // [3]
arr.slice(-2); // [2,3]
console.log(arr); // [1,2,3]

const copy = arr.slice(); // [1,2,3]
console.log(copy === arr); // false, 얕은 복사
  • slice 메서드, 스프레드 문법, Object.assign 메서드 모두 얕은 복사 수행
    => 깊은 복사를 위해 Lodash 라이브러리의 cloneDeep 메서드 사용 권장

  • slice 메서드를 이용하면 유사 배열 객체를 배열로 변환 가능 (ES5)

function sum() {
  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 메서드 사용하면 더 간단하게 유사 배열 객체 또는 이터러블 객체를 배열로 변환 가능
function sum() {
  var 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 스프레드 문법 사용하면 간단
function sum() {
  const arr = [...arguments];
  console.log(arr); // [1,2,3]
  return arr.reduce((pre, cur) => pre + cur, 0);
}

console.log(sum(1,2,3)); // 6

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'

// 기본적으로 모든 리턴은 문자열

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], 반환값도 변경된 배열

// arr과 result는 동일 인스턴스.. 참 쓸데없다🤔
arr[0] = 4;
console.log(arr); // [4,2,1]
console.log(result); // [4,2,1]

8.12 Array.prototype.fill

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

// 두번째 인수는 시작 인덱스
arr.fill(1, 1);
console.log(arr); // [0,1,1]

// 세번째 인수는 종료 인덱스(종료 미포함)
arr.fill(0,1,2);
console.log(arr); // [0,0,1]
  • 배열 생성하면서 특정 값 요소로만 채우기 가능
  • Array.from 메서드와 같이 활용하면 원하는 요소값으로 채우기 가능
    😑근데 왜 그렇게 해야 되지?

8.13 Array.prototype.includes

  • ES7, 배열 내에 특정 요소 포함되어 있는지 확인하여 true/false 반환
const arr = [1,2,3];

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

// 두번째 인수는 검사 시작지점
arr.includes(1,1); // false

// 음수도 받는다
arr.includes(3,-1); // true

// indexOf도 비슷
[NaN].indexOf(NaN) !== -1; // false
[NaN].includes(NaN); // true

8.14 Array.prototype.flat

  • ES10, 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화
  • 중첩 배열 모두 평탄화하기 위해서는 Infinity로 깊이 값 지정
  • 원본 배열 보존, 사본을 리턴한다.
const arr = [1,[2,3,4,5]];
arr.flat(); // [1,2,3,4,5]
console.log(arr.flat()); // [1,2,3,4,5]
console.log(arr); // [1,[2,3,4,5]]

// 인수로 평탄화 레벨을 받는다. 기본값는 1, Infinity도 받는다

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

9. 배열 고차 함수


배열 고차 함수
함수를 인자로 전달받거나 함수를 반환하는 함수

9.1 Array.prototype.sort

  • 배열의 요소를 정렬
  • 원본 배열을 변경
  • 기본적으로 오름차순 정렬
const arr = [2,1];
arr.sort(); // [1,2]
  • sort 메서드는 함수를 인자로 받는다.
  • 비교 함수가 음수를 반환하면 첫번째 인자를 우선 정렬, 0을 반환하면 정렬하지 않고, 양수를 반환하면 두번째 인자를 우선 정렬한다.
const points =[40,100,1,5,2,25,10];
// 오름차순 정렬
points.sort((a,b) => a - b); // a와 b를 비교해서 음수가 나오면 a가 작다는 의미. 그렇다면 a를 우선 정렬한다.
  • 객체를 요소로 갖는 배열 정렬
const todos = [
  { id: 4, content: "Javascript" },
  { id: 1, content: "HTML" },
  { id: 2, content: "CSS" }
];

function campare(key) {
  //  a보다 b가 크면 1, 같거나 작으면 다시 비교
  //  a보다 b가 작으면 -1, 같으면 0
  return (a,b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
}

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

9.2 Array.prototype.forEach

  • for문을 대체하는 함수
  • forEach 메서드의 반환값은 언제나 undefined
  • 희소배열에서 존재하지 않는 요소는 아예 순회에서 제외
const arr = [1,2,3];

arr.forEach((item, index, arr) => {
    console.log(`요소값: ${item}, 인덱스: ${index}, this: ${arr}`)
});
/*
요소값: 1, 인덱스: 0, this: 1,2,3
요소값: 2, 인덱스: 1, this: 1,2,3
요소값: 3, 인덱스: 2, this: 1,2,3
*/
console.log(arr.forEach(() => 1)); // undefiend
  • 첫번째 인자로 함수를 받고, 두번째 인자로 this로 사용할 객체를 받는다. 하지만 위 예제처럼 화살표함수 쓰는게 편리
  • 이렇게 좋은 forEach문은 break, continue를 못쓴다😨
  • 미련하게도 무조건 처음부터 끝까지 동작한다🤬
[1,2,3].forEach(item => {
  console.log(item);
  if(item > 1) break; // SyntaxError
}
[1,2,3].forEach(item => {
  console.log(item);
  if(item > 1) continue; // SyntaxError
}               

어차피 forEach도 내부적으로는 for문 쓰니 성능차이도 없을 것 같고.. 참 계륵같은 녀석

9.3 Array.prototype.map

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백 함수를 반복 호출
  • 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환
  • 원본 배열은 변경하지 않는다
const numbers = [1,4,9];
// forEach 때처럼, 화살표 함수의 두번째 인자로 index와 세번째 인자로 arr를 쓸 수 있다.
const roots = numbers.map((item) => Math.sqrt(item));

console.log(roots); // [1,2,3]
console.log(numbers); // [1,4,9]
  • 첫번째 인자로 함수를, 두번째 인자로 내부에서 this로 사용할 객체를 받을 수 있다...forEach처럼.
  • 역시 굳이 두번째 인자 쓸 필요없는 화살표 함수 쓰자

9.4 Array.prototype.filter

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
  • 원본 배열은 변경하지 않는다
const numbers = [1,2,3,4,5];

const odd = numbers.filter(item => item % 2);
console.log(odds); // [1, 3, 5]
  • forEach, map 처럼 첫번째 인자로 함수, 두번째 인자로 this로 쓸 객체를 받을 수 있다
  • 그리고 계속 말하지만 화살표 함수 쓰자

9.5 Array.prototype.reduce

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • 콜백 함수의 반환값을 다음 콜백 함수의 첫번째 인자로 전달하는 것을 반복
  • 최종적으로 하나의 값을 반환
  • 원본 배열은 보존
// 누적 구하기
const sum = [1,2,3,4].reduce((accumulator, currentValue, idx, arr) => accumulator + currentValue, 0); // 0은 초기값

console.log(sum); // 10

reduce 메서드의 인수
첫번째 인수 : 콜백 함수
두번째 인수 : 초기값(생략하지 않는게 안전)

reduce 메서드의 콜백함수의 인수
첫 번째 인수 : 초기값 또는 콜백 함수의 이전 반환값
두 번째 인수 : reduce 메서드를 호출한 배열의 요소값
세 번째 인수 : reduce 메서드를 호출한 배열의 인덱스
네 번째 인수 : reduce 메서드를 호출한 배열 this

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

// 최대값 구하기, 하지만 Math.max() 쓰는게 낫다.
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 은 첫 번째 요소인 'banana'이다.
  // 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로, 요소의 개수를 프로퍼티 값으로 할당한다.
  // 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1 로 초기화 한다.
  acc[cur] = (acc[cur] ?? 0) + 1;
  return acc;
}, {});

console.log(count);

// 중첩 배열 평탄화, 그냥 flat 쓸 것
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]

// 중복 요소 제거
const values = [1, 2, 1, 3, 5, 4, 3, 4, 4];

const result = values.reduce((unique, val, i, _values) => 
  _values.indexOf(val === i ? [...unique, val] : unique, []);

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

9.6 Array.prototype.some

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • 하나라도 true면 true 반환, 아니면 false 반환
const arr = [5, 10, 15];

arr.some((item) => item > 10); // true
arr.some((item) => item < 0); // false

9.7 Array.prototype.every

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • 모두 true면, true, 아니면 false
const arr = [5, 10, 15];

arr.every((item) => item > 0); // true
arr.every((item) => item > 10); // false

9.8 Array.prototype.find

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • true인 첫번째 요소 반환
const arr = ['banana', 'apple', 'orange','banana'];

arr.find((item) => item === 'banana'); // banana

9.9 Array.prototype.findIndex

  • 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
  • find와 유사하지만, index를 반환
const arr = ['banana', 'apple', 'orange','banana'];
arr.findIndex((item) => item === 'banana'); // 0

9.10 Array.prototype.flatMap

  • ES10
  • map 메서드를 통해 생성된 새로운 배열을 평탄화
const arr = ['banana', 'orange'];

arr.map(x => x.split('')); // [['b', 'a', 'n', 'a', 'n', 'a'],['o', 'r', 'a', 'n', 'g', 'e']]
arr.map(x => x.split('')).flat(); // ['b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e']
arr.flatMap(x => x.split('')); // ['b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e']

0개의 댓글