프로그래밍을 하다 보면 데이터 집합을 조작해야 할 일이 정말 많은데, 자바스크립트의 배열 메서드에 익숙해지면 이런 작업을 쉽게 처리할 수 있고 자바스크립트 개발 능력이 한 단계 올라갈 수 있다.

배열의 기초

배열의 기본적인 사항

  • 배열은 객체와 달리 본질에서 순서가 있는 데이터 집합이며 0으로 시작하는 숫자형 인덱스를 사용한다.
  • 자바스크립트의 배열은 비균질적이다. 즉, 한 배열의 요소가 모두 같은 타입일 필요가 없고 다른 배열이나 객체를 포함할 수 있다.
  • 배열 리터럴은 대괄호로 만들고, 배열 요소에 인덱스로 접근할 때도 대괄호를 사용한다.
  • 모든 배열에는 요소가 몇 개 있는지 나타내는 length 프로퍼티가 있다.
  • 배열에는 배열 길이보다 큰 인덱스를 사용해서 요소를 할당하면 배열은 자동으로 그 인덱스에 맞게 늘어나며, 빈자리는 undefined로 채워진다.
  • Array 생성자를 써서 배열을 만들 수도 있지만 그렇게 해야 하는 경우는 별로 없다.

배열 요소 조작

배열 메서드 중 일부는 배열 자체를 수정하며, 다른 일부는 새 배열을 반환한다. 예를 들어 push는 배열 자체를 수정하며, concat은 새 배열을 반환한다. 메서드 이름에 이런 차이점에 대한 힌트가 전혀 없으므로 프로그래머가 전부 기억해야 한다.

배열의 처음이나 끝에서 요소 하나를 추가하거나 제거하기

배열의 첫번째 요소는 인덱스가 0인 요소를 말하며, 마지막 요소는 arr.length - 1 인 요소를 말한다.
push pop은 각각 배열의 끝에 요소를 추가하거나 제거하고,shift unshift는 각각 배열의 처음에 요소를 추가하거나 제거한다.

push / unshift 는 새 요소를 추가해서 늘어난 길이를 반환하고,
pop / shift 는 제거된 요소를 반환한다.

const arr = [1, 2, 3];

arr.push(4);  // arr = [1, 2, 3, 4]
arr.pop();       // 4  arr = [1, 2, 3]
arr.unshift(0) //    arr = [0, 1, 2, 3]
arr.shift()   // 0  arr = [1, 2, 3]

배열의 끝에 여러 요소 추가하기 - concat

concat 메서드는 배열의 끝에 여러 요소를 추가한 사본을 반환합니다. concat에 배열을 넘기면 이 메서드는 배열을 분해해서 원래 배열에 추가한 사본을 반환한다.

const arr = [1, 2, 3];

arr.concat(4, 5, 6);    // [1, 2, 3, 4, 5, 6] arr은 바뀌지 않음.
arr.concat([4, 5, 6]);    // [1, 2, 3, 4, 5, 6]
arr.concat([4, 5], 6);    // [1, 2, 3, 4, 5, 6]
arr.concat([4, [5, 6]]);    // [1, 2, 3, 4, [5, 6]]

concat은 제공받은 배열을 한 번만 분해하며, 배열 안에 있는 배열을 다시 분해하지는 않는다.

배열 일부 가져오기 - slice

배열의 일부만 가져올 때는 slice 메서드를 사용합니다. 매개변수 두 개를 받는데, 첫 번째 매개변수는 어디서부터 가져올지를, 두 번째 매개변수는 어디까지 (바로 앞 인덱스까지 가져옴) 가져올지를 지정한다. 두 번째 매개변수 생략 시 배열의 마지막까지 반환한다.
이 메서드에서는 음수 인덱스를 쓸 수 있고, 음수를 쓰면 배열의 끝에서 부터 요소를 센다.

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

arr.slice(3);        // [4, 5] arr은 바뀌지 않음.
arr.slice(2, 4);    // [3, 4]
arr.slice(-2);        // [4, 5] 

임의의 위치에 요소 추가 또는 제거하기 - splice

splice는 배열을 자유롭게 수정할 수 있다. 첫 번째 매개변수는 수정을 시작할 인덱스이고, 두 번째 매개변수는 제거할 요소 숫자이다. 아무 요소도 제거하지 않을 때는 0을 넘기고 나머지 매개변수는 배열에 추가될 요소이다.

const arr = [1, 5, 7];

arr.splice(1, 0, 2, 3, 4);    // arr = [1, 2, 3, 4, 5, 7] 제거없음.
arr.splice(5, 0, 6);        // arr = [1, 2, 3, 4, 5, 6, 7] 제거없음.
arr.splice(1, 2);            // arr = [1, 4, 5, 6, 7] 제거 [2, 3]

배열 안에서 요소 교체하기 - copyWithin

copyWithin은 ES6에서 도입한 새 메서드로 배열 요소를 복사해서 다른 위치에 붙여넣고, 기존의 요소를 덮어쓴다. 첫 번째 매개변수는 복사한 요소를 붙여넣을 위치이고, 두 번째 매개변수는 복사를 시작할 위치이고, 세 번째 매개변수는 (생략가능) 복사를 끝낼 위치이다. slice와 마찬가지로 음수를 사용하면 배열의 끝에서 부터 센다.

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

arr.copyWithin(1, 2)    // arr = [1, 3, 4, 4]
arr.copyWithin(2, 0, 2)    // arr = [1, 3, 1, 3]
arr.copyWithin(0, -3, -1)    // arr = [3, 1, 1, 3]

특정 값으로 배열 채우기 - fill

fill은 ES6에서 도입한 환영할만한 좋은 메서드로 정해진 값으로 배열을 채운다. 크기를 지정해서 배열을 생성하는 Array 생성자와 잘 어울린다.
배열의 일부만 채우려 할 때는 시작 인덱스와 끝 인덱스를 지정해주면 된다. 음수 인덱스도 사용할 수 있다.

const arr = new Array(5).fill(1)    // arr = [1, 1, 1, 1, 1]로 초기화됨

arr.fill("a");    // arr = ["a", "a", "a", "a", "a"]
arr.fill("b", 1)    // arr = ["a", "b", "b", "b", "b"]
arr.fill("c", 2, 4) // arr = ["a", "b", "c", "c", "b"]
arr.fill(5.5, -4)     // arr = ["a", 5.5, 5.5, 5.5, 5.5]

배열 정렬과 역순 정렬 reverse / sort

reverse는 이름 그대로 배열 요소의 순서를 반대로 바꾼다.

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

arr.reverse();    // arr = [5, 4, 3, 2, 1];

sort는 배열 요소의 순서를 정렬한다.

const arr = [5, 3, 2, 4, 1];
arr.sort();        // arr = [1, 2, 3, 4, 5];

sort는 정렬 함수를 받을 수 있다. 이 기능은 매우 편리하며, 예를 들어 일반적으로는 객체가 들어있는 배열을 정렬할 수 없지만, 정렬 함수를 사용하면 가능하다.

const arr = [
    {name: "Dany"},
    {name: "Suzan"},
    {name: "Aria"},
    {name: "Cookey"},
  ];

  arr.sort(); // arr은 바뀌지 않음
  arr.sort((a,b) => a.name > b.name); // arr은 name프로퍼티의 알파벳 순으로 정렬됨
  arr.sort((a,b) => a.name[1] > b.name[1]); // arr은 name프로퍼티의 두 번째 글자의 알파벳 역순으로 정렬됨

배열 검색

배열 안에서 뭔가 찾으려 할 때는 몇가지 방법이 있다.

indexOf / lastIndexOf

indexOf 는 찾고자 하는 것과 정확히 일치 (===)하는 첫 번째 요소의 인덱스를 반환한다. indexOf의 짝인 lastIndexOf는 배열의 끝에서부터 검색한다. 배열의 일부분만 검색하려면 시작 인덱스를 지정할 수 있다. indexOf 와 lastIndexOf 는 일치하는 것을 찾지 못하면 -1을 반환한다.

const o = {name: "Jerry"};
  const arr = [1, 5, "a", o, true, 5, [1, 2], "9"];

  arr.indexOf(5); // 1
  arr.lastIndexOf(5);  // 5
  arr.indexOf("a");  // 2
  arr.lastIndexOf("a");  // 2
  arr.indexOf({name: "Jerry"}); // -1 찾지못함
  arr.indexOf(o); // 3
  arr.indexOf("a", 5)  // -1 찾지못함

findIndex

findIndex 는 일치하는 것을 찾지 못했을 때 -1을 반환한다는 점에서는 indexOf와 비슷하지만, 보조 함수를 써서 검색 조건을 지정할 수 있으므로 indexOf보다 더 다양한 상황에서 활용할 수 있다.
하지만 findIndex는 검색을 시작할 인덱스를 지정할 수 없고, 뒤에서 부터 찾는 findLastIndexOf 같은 짝도 없다.

const arr = [
    {id: 5, name: "Judith"},
    {id: 7, name: "Francis"}
  ];

  arr.findIndex(o => o.id === 5); // 0
  arr.findIndex(o => o.name === "Francis"); // 1
  arr.findIndex(o => o.id === 3); // -1

indexOf와 findIndexOf는 조건에 맞는 요소의 인덱스를 찾을 때 알맞지만, 조건에 맞는 요소의 인덱스가 아니라 요소 자체를 원할 때는 find를 사용한다.

find 는 findIndex와 마찬가지로 검색 조건을 함수로 전달할 수 있고, 조건에 맞는 요소가 없을 때는 undefined를 반환한다.

const arr = [
    {id: 5, name: "Judith"},
    {id: 7, name: "Francis"}
  ];

  arr.find(o => o.id === 5)  // {id: 5, name: "Judith"} 객체 반환
  arr.find(o => o.id === 2); // undefined

find와 findIndex에 전달하는 함수는 배열의 각 요소를 첫 번째 매개변수로 받고, 현재 요소의 인덱스와 배열 자체도 매개변수로 받는다. 이런 점을 다양하게 응용할 수 있다.

예를 들어 특정 인덱스보다 뒤에 있는 제곱수를 찾아야 하는 경우.

const arr = [1, 17, 16, 5, 4, 16, 10, 3, 49];

arr.find((x, i) => i > 2 && Number.isInteger(Math.sqrt(x)));  // 4

find와 findIndex에 전달하는 함수의 this도 수정할 수 있다. 이를 이용해서 함수가 객체의 메서드인 것처럼 호출할 수 있다.

ID를 조건으로 Person 객체를 검색하는 예제. 두 방법의 결과는 같다.

class Person {
    constructor(name) {
      this.name = name;
      this.id = Person.nextId++;
    }
  }

  Person.nextId = 0;
  const jamie = new Person("jamie"),
        juliet = new Person("juliet"),
        peter = new Person("peter"),
        jay = new Person("jay");

  const arr = [jamie, juliet, peter, jay];

  // option 1 = ID를 직접 비교
  arr.find(p => p.id === juliet.id); // juliet 객체

  // option 2 = this 매개변수 이용
  arr.find(function(p){             
    return p.id === this.id
  }, juliet);                     // juliet 객체

이런 간단한 예제에서는 find와 findIndex에서 this 값을 바꾸는 의미가 별로 없지만, 나중에는 더 유용하게 쓰이는 경우가 있다.

some / every

some은 조건에 맞는 요소를 찾으면 즉시 검색을 멈추고 true를 반환하며, 조건에 맞는 요소를 찾지 못하면 false를 반환한다.

const arr = [5, 7, 12, 15, 17];

arr.some(x => x%2 === 0);    // true 12는 짝수
arr.some(x => Number.isInteger(Math.sqrt(x)));    // false 제곱수가 없음

every는 배열의 모든 요소가 조건에 맞아야 true를 반환하며, 그렇지 않다면 false를 반환한다.

const arr = [4, 6, 16, 36];

arr.every(x => x%2 === 0);    // true 모두 짝수
arr.every(x => NumberisInteger(Math.sqrt(x)));    // false 6은 제곱수가 아님

map과 filter

map과 filter는 배열 메서드 중 가장 유용한 메서드이다.

map은 배열 요소를 변형한다. 일정한 형식의 배열을 다른 형식으로 바꿔야 할 때 사용하면 된다. map과 filter 모두 사본을 반환하며 원래 배열은 바뀌지 않는다.

 const cart = [
    {name: "Widget", price: 9.95},
    {name: "Gadget", price: 22.95}
  ];
  const names = cart.map(x => x.name);  // ["Widget", "Gadget"]
  const prices = cart.map(x => x.price); // [9.25, 22.95]
  const discountPrices = price.map(x => x * 0.8); // [7.96, 18.36]

filter는 이름이 암시하듯 배열에서 필요한 것을만 남길 목적으로 만들어졌다. 주어진 함수의 테스트를 통과한 요소를 모아 새로운 배열을 반환한다.

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

words.filter(word => word.length > 6);  // ["exuberant", "destruction", "present"]

reducer

map이 배열의 각 요소를 변형한다면 reduce는 배열 자체를 변형한다. reduce라는 이름은 이 메서드가 보통 배열을 값 하나로 줄이는 데 쓰이기 때문에 붙었습니다.

reduce는 map과 filter를 비롯해 여태까지 배운 배열 매서드의 동작을 대부분 대신할 수 있다. 콜백 함수를 받고 여태까지 첫 번째 매개변수는 항상 현재 배열 요소였지만, reduce가 받는 첫 번째 매개변수는 배열이 줄어드는 대상인 어큐물레이터(accumulator)이다. 두 번째 매개변수부터는 콜백의 순서대로 현재 배열 요소, 현재 인덱스, 배열 자체이다.

배열의 순서를 더하는 단순한 예제.

const arr = [5, 7, 2, 4];
const sum = arr.reduce((a, x) => a += x, 0); // 18
  1. 첫 번째 배열 요소 5에서 함수를 호출. a의 초깃값은 0이고 x의 값은 5이다. 함수는 a와 x(5)의 합을 반환. 이 값이 다음 단계에서 a의 값이 됨.

  2. 두 번째 배열 요소 7에서 함수를 호출. a의 초기값은 이전 단계에서 전달한 5이고, x의 값은 7이다. 함수는 a와 x의 합 12를 반환하고 이 값이 다음 단계에 a값이 됨.

  3. 세 번째 요소 2 에서 함수를 호출하고 함수 a와 x의 합인 14를 반환.

  4. 네 번째 요소 4에서 함수를 호출하고 18을 반환하며 이 값이 reduce의 값이고 sum의 할당된다.

문자열 병합

배열의 문자열 요소들을 몇몇 구분자로 합치려 할 때가 많은데, Array.prototype.join은 매개변수로 구분자 하나를 받고 요소들을 하나로 합친 문자열을 반환한다. 매개변수 생략시 기본값은 쉼표이며, 문자열 요소를 합칠 때 정의되지 않은 요소, 삭제된 요소, null, undefined는 모두 빈 문자열로 취급함.

const arr = [1, null, "hello", "world", true, undefined];
delete arr[3];

arr.join();        // "1,,hello,,true,"
arr.join('');    // "1hellotrue"
arr.join(' -- ');    // "1 -- -- hello -- -- true -- "

정리

평소 예제를 따라하며 사용했던 자바스크립트의 Array 메서드 들의 사용 방법을 깊이 알게됐고, 어떤 상황에서 어떤 메서드가 최선일지 판단하는데 도움이 되었다.