JavaScript Map Array

lilyoh·2020년 7월 24일
14
post-thumbnail

javascript map array 글을 간단하게 번역했다.

map 을 사용하는 방법

  • 배열의 map 메서드는 배열의 모든 요소들에 대해서 값을 바꿔준다. 콜백 함수를 통해서 값을 바꿔주는데, 배열의 각각 요소들에 콜백 함수가 호출된다. 각 요소들에 대해 콜백 함수를 모두 호출하고 나면, 값이 바뀐 배열을 리턴한다. 기존 배열은 그대로 존재한다.
// 기본
const originalArray = [1, 2, 3, 4, 5];
const newArray = originalArray.map(
  function addOne(number) {
    return number + 1;
  }
);

console.log(originalArray); // [1, 2, 3, 4, 5]
console.log(newArray); // [2, 3, 4, 5, 6]

// 익명함수를 사용해도 된다
const originalArray = [1, 2, 3, 4, 5];
const newArray = originalArray.map(
  function (number) {
    return number + 1;
  }
);

console.log(originalArray); // [1, 2, 3, 4, 5]
console.log(newArray); // [2, 3, 4, 5, 6]

// callback 함수를 standalone 함수로 생성해서 변수로 선언하고 싶을 때는 다음과 같이 한다
// standalone 함수란 1) 클래스에 속하지 않고 2) global namespace 에 있는 함수
const originalArray = [1, 2, 3, 4, 5];
 
function addOne(number) {
  return number + 1;
}
 
const newArray = originalArray.map(addOne);
 
console.log(originalArray); // [1, 2, 3, 4, 5]
console.log(newArray); // [2, 3, 4, 5, 6]

map 을 사용해야 하는 이유

  • map 메서드 말고도 for 문을 사용해서 위와 동일한 결과를 낼 수 있는데 map 메서드를 꼭 사용해야할까? 그렇다. 그 이유는 map 메서드(를 포함한 여러 내장 함수들)를 사용하면 코딩 양이 줄 뿐만 아니라, 코드의 가독성을 높일 수 있기 때문이다.

  • 뿐만 아니라 map 메서드는 기존 배열을 유지하기 때문에 코드를 이해하기 쉽다.

  • 배열의 map 메서드에서 콜백함수는 arrow function 으로 사용하면 좋다. 코드가 간결해지기 때문이다.

  • 다만, 콜백함수로 arrow function 을 쓸 경우, 콜백함수의 함수명을 지정해줄 순 없다. (arrow function 은 익명함수만 사용 가능하기 때문)

// function 키워드로 map 메서드 사용
// const originalArray = [1, 2, 3, 4, 5];
// const newArray = originalArray.map(
//   function addOne(number) {
//     return number + 1;
//   }
// );

// console.log(originalArray);
// console.log(newArray);

// 콜백함수를 arrow function 으로 쓸 때
const originalArray = [1, 2, 3, 4, 5];
const newArray = originalArray.map((number) => number + 1);

console.log(originalArray);
console.log(newArray);

map 이 받을 수 있는 인자들

  • map 메서드를 효과적으로 사용하기 위해서는 콜백 함수가 어떻게 동작하는지 알 필요가 있다.
  • 콜백 함수로 어떤 인자들이 전달되며, 인자들이 어떻게 사용되는지 알아보자.
  • 콜백 함수는 3개 (혹은 2개 혹은 1개) 의 인자를 가진다.
array.map((value, index, array) => {...});
  • value
    : 기존 배열의 값들이다.
  • index
    : 기존 배열의 값들의 '인덱스'이다.
    : map 을 사용해서 데이터를 생성하거나 다른 배열의 같은 아이템을 인덱스로 접근해야 할 때 사용한다. (나중에 더 자세히 다루겠다)
  • array
    : 거의 안 쓴다.
    : 변수에 배열을 바운드해서 쓰지 않아서 배열 메서드를 연결해야 할 때 (?) 사용할 수 있으니 알아만 두자.

map 을 사용해야 할 때와 사용하지 말아야 할 때

  • map 메서드를 아는 것도 중요하지만 map 메서드를 어떤 함수랑 같이 사용했을 때 좋은지, 어떤 경우에 map 메서드 외의 메서드를 써야 하는지, 어떤 메서드를 써야 하는지 아는 것이 더 중요하다.

1. map vs forEach

: 배열을 돌고 난 뒤에 변환된 새로운 배열을 리턴하는 map 메서드와 달리
forEach 는 아무 값도 리턴하지 않는다.

다음은 map 대신에 forEach 를 쓰면 더 좋은 예제다.

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

// use map
myArray.map(number => {
  console.log(number);
});

// use forEach
myArray.forEach(number => {
  console.log(number);
});

map 을 사용하면 1, 2, 3, 4 와 [ undefined, undefined, undefined, undefined ] 값이 리턴된다.
forEach 를 사용하면 1, 2, 3, 4 값만 출력된다. 리턴되는 값은 없다.

다음은 forEach 대신에 map 을 쓰면 더 좋은 예제다.

const originalArray = [1, 2, 3, 4];
const newArray = [];

// use forEach 
originalArray.forEach((number, i) => {
  newArray[i] = number * 2;
});
 
console.log(newArray); // [2, 4, 6, 8]

// use forEach
const originalArray = [1, 2, 3, 4];
const newArray = originalArray.map((number) => number * 2);

console.log(newArray); // [2, 4, 6, 8]

2. map and filter

  • map 과 filter 는 모두 새로운 배열을 반환하는 immutable operation 이다.
  • map 이 반환하는 새 배열의 요소 개수는 변하지 않지만 filter 가 반환하는 새 배열의 요소 개수는 변한다. filter 는 조건에 따라 요소들이 필터링되어 나오기 때문이다.
  • map 과 filter 를 결합하여 사용하면 cool 하다! 🤟 map 으로 데이터를 변형하기 전에 filter 로 데이터를 한 번 걸러주는 것이다.
const originalArray = [1, 2, undefined, 3];
 
const newArray = originalArray
  .filter(value => {
    return Number.isInteger(value);
  }).map(value => {
    return value * 2;
  });
 
console.log(newArray); // [2, 4, 6]

3. map and reduce

  • map 과 reduce 는 데이터를 가공해서 반환한다는 점에서 비슷한다.
  • map 은 배열 요소를 가공해서 배열로 돌려줄 때 사용하고, reduce 는 a 를 가공해서 b 를 돌려줄 때 사용한다. 여기서 a와 b에는 어떠한 데이터 타입도 올 수 있다. reduce 가 더 flexible 하다! 🥰
  • 하지만 무조건 reduce 가 좋은 것은 아니다. 단순히 배열을 가공해서 또 다른 배열로 반환하려 할 때는 map 을 쓰는 것이 더 깔끔하다.
// 배열을 또 다른 배열로 반환할 때
const originalArray = [1, 2, 3, 4, 5];
const newArray = originalArray.reduce((accumulator, value, index) => {
  accumulator[index] = value * 2;
  return accumulator;
}, []);
 
console.log(newArray); // [2, 4, 6, 8, 10]

// 다른 형태의 데이터 타입으로 반환할 때
const myArray = ['a', 'b', 'c', 'd'];
 
const myObject = myArray.reduce((accumulator, value) => {
  accumulator[value] = true;
}, {});
 
console.log(myObject); // { a: true, b: true, c: true, d: true }

4. map and reverse

  • 배열을 가공한 후 -> 배열 요소의 순서를 뒤집고 싶을 때가 있다.
  • map 메서드는 immutable 이지만 (기존 배열을 그대로 두고 새로운 배열을 생성하지만) reverse 메서드는 mutable 하기 때문에 (기존 배열 자체의 요소를 역순으로 정렬하기 때문에) map 과 reverse 를 같이 쓰고 싶다면 map 을 먼저 쓰고 -> reverse 를 쓰는 것이 좋다. (새로운 배열을 만들고 그 새로운 배열의 요소를 역순 정렬하는 것이 좋다)
// Don't do this!
const originalArray = [1, 2, 3, 4, 5];
const reverseNewArray = originalArray.reverse().map(number => number * 2);
console.log(originalArray); // [5, 4, 3, 2, 1]
console.log(reverseNewArray); // [10, 8, 6, 4, 2]
 
// Instead, do this!
const originalArray = [1, 2, 3, 4, 5];
const reverseNewArray = originalArray.map(number => number * 2).reverse();
console.log(originalArray); // [1, 2, 3, 4, 5]
console.log(reverseNewArray); // [10, 8, 6, 4, 2]
  • 하지만 배열 요소를 가공할 필요가 없고, 단순히 '역순 정렬만 하고 싶다'면 map 을 쓸 필요 없다.
  • slice 와 reverse 를 쓰면 더 간단하게 새로운 배열을 역순 정렬할 수 있다.
const originalArray = [1, 2, 3, 4, 5]
const newArray = originalArray.slice().reverse()
 
console.log(newArray) // [5, 4, 3, 2, 1]

map 을 이용한 복잡한 데이터 처리

  • 단순히 배열 요소를 가공해서 새로운 배열로 돌려주는 것 말고도 map 을 이용하면 왕멋진💪 데이터 처리들을 할 수 있다.

객체의 키값 추출

  • 객체인 배열 요소의 모든 키 값을 추출하고 싶을 때 map 을 쓸 수 있다.
const originalArray = [
  { a: 1, b: 'first' },
  { a: 2, b: 'second' },
  { a: 3, b: 'third' },
];
 
const newArray = originalArray.map(object => object.b);
 
console.log(newArray); // ['first', 'second', 'third']

객체를 순회하며 요소 변형하기

  • map 을 써서 직접적으로 객체의 요소를 변형할 순 없지만 map 과 Object.entries 를 쓰면 가능하다.
  • Object.entries 는 객체를 인자로 받아서 객체의 키: 값 쌍을 배열로 돌려준다. 아래 예제를 보자. 배열 안에 배열 형태로 돌려준다. 기존 객체를 mutate 하지 않고 새로운 배열을 반환한다는 점에서 map 과 비슷하다.
const object = {
  a: 1,
  b: 2,
  c: 3,
};
 
const array = Object.entries(object);
  • Object.entries 를 사용해서 객체의 요소를 배열로 바꿔준 다음, map 을 사용하면 객체 요소를 순회하며 수정할 수 있다.
const object = {
  a: 1,
  b: 2,
  c: 3,
};
 
const array = Object.entries(object);
console.log(array); // [['a', 1], ['b', 2], ['c', 3]]
 
const newArray = array.map(([key, value]) => [key, value * 2]);
console.log(newArray); // [['a', 2], ['b', 4], ['c', 6]]
  • 변형된 키: 값 들을 다시 객체로 만들고 싶다면 map 과 reduce 를 쓴다.
const newObject = newArray.reduce((accumulator, [key, value]) => {
    accumulator[key] = value;
    return accumulator;
  }, {});
 
console.log(newObject);
  • map 을 사용하지 않고 Object.entries 와 reduce/forEach 를 사용해도 된다.
  • 코드가 훨씬 깔끔해진 것을 알 수 있다. 그래서 '적당한 메서드를 적당한 때에 쓰는 것'이 중요하다.
  • map 은 '배열' 요소의 요소값을 순회하며 변형해준다. 하지만 순회를 마치면 새로운 배열을 reduce 를 통해서 다시 객체로 만들어줘야 한다. 따라서 이 경우에는 아래처럼 코딩하는 것이 낫다.
const object = {
  a: 1,
  b: 2,
  c: 3,
};
 
const entries = Object.entries(object);
 
const newObject = entries.reduce((accumulator, [key, value]) => {
  accumulator[key] = value * 2;
  return accumulator;
}, {});
 
// also works using forEach and mutating an object
const newObject = {};
entries.forEach(([key, value]) => {
  newObject[key] = value * 2;
});
 
console.log(newObject); // { a: 2, b: 4, c: 6 }

map 과 if 조건문

  • 기존 배열에서 어떤 조건에 맞는 요소만 바꾸고 싶을 때가 있다. 이 때는 map 메서드 안에 if 조건문을 넣어주면 된다.
const originalArray = [5, 10, 15, 20];
 
const newArray = originalArray.map(number => {
  if (number >= 10) {
    return number * 2;
  }
 
  return number;
});
 
console.log(newArray); // [5, 20, 30, 40]
  • 위의 코드는 ternary statement 를 사용해서 더 깔끔하게 만들 수 있다.
const originalArray = [5, 10, 15, 20];
 
const newArray = originalArray.map(number =>
  number >= 10 ? number * 2 : number,
);
 
console.log(newArray); // [5, 20, 30, 40]
  • map 과 if 조건문을 결합해서 사용하는 것의 cool 한 점은 조건을 아~주 넓게 잡을 수도, 아~주 좁게 잡을 수도 있다는 것이다! 예를 들어 배열의 단 하나의 요소를 바꿀 때도 map 과 if 조건문을 사용해도 된다.
const originalArray = [5, 10, 15, 20];
 
const newArray = originalArray.map(number =>
  number === 10 ? number * 2 : number,
);
 
console.log(newArray); // [5, 20, 15, 20]

map 과 2-dimensional Arrays(2차원 배열)

  • 다차원 배열이라도 map 을 한 번 사용하면 가장 높은 차원의 배열 요소에 대해서만 해당 요소값을 변형한다. 따라서 다차원 배열의 요소값을 변형하고 싶을 때는 차원의 수만큼 map 을 써줘야 한다. 다음 예제는 2차원 배열의 요소에 * 2 를 한 것이다. map 을 두 번 써줬다.
const myArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
 
const newArray = myArray.map(value => value.map(number => number * 2));
 
console.log(newArray); // [[2, 4, 6], [8, 10, 12], [14, 16, 18]]
  • 하지만 다차원 배열의 요소를 변형해서 1차원 요소로 반환하고 싶다면? ES2019 는 flatMap 이라는 메서드를 제공한다. 하지만 flatMap 을 쓸 수 없다면 reduce 를 이용해서 같은 결과를 낼 수 있다. 참고

map 을 쓰면서 자주 하는 실수

map is not defined as a function

  • map 은 배열에서만 쓸 수 있는 메서드이다. 객체, null 혹은 다른 데이터 타입에 대해 map 을 쓰려고 하면 오류 메세지가 뜬다.
  • 데이터 타입이 확실치 않은데 map 을 사용하면 이런 오류를 쉽게 마주칠 수 있다. 하지만 자바스크립트 로직을 이용해서 아래와 같이 코딩해주면 문제 없다! 😉
// originalArray could either be [1, 2, 3, 4] or null
const newArray = (originalArray || []).map(number => number * 2);
  • 주의할 점: 1. 위의 경우는 확실치 않은 데이터 타입이 '배열인지 null 인지 모를 때' 만 쓸 수 있다. 데이터 타입이 객체, 문자열 등의 다른 타입이라면 사용할 수 없다. 따라서 100% 안전한 방법이 아니다. 2. 확실치 않은 데이터 타입을 어플리케이션으로 받아 처리한다면 정규화 과정이 오래 걸리기 때문에 지나치게 방어적인 프로그래밍 을 해야한다. 결론은 저 위 방법이 존재하긴 하지만 비추라는 것.

map 안에서 값을 출력하기

  • map 을 쓰면서 기존 배열의 요소값을 체크하고 싶을 때가 있다. 그 때는 map 안에 console.log 를 써서 기존 배열 요소값을 출력하면 된다. 하지만 이 방법은 arrow function 을 쓸 때 curly braces 를 한 번 써 줘야 하기 때문에 귀찮다. 자바스크립트 로직을 이용해서 더 간단하고 멋지게 기존 배열 요소값을 출력할 수 있다.
const originalArray = [5, 10, 15, 20];

// Before
const newArray = originalArray.map(value => value * 2);
 
// Using curly braces
const newArray = originalArray.map(value => {
  console.log(value);
  return value * 2;
});

// Using javascript logic
const newArray = originalArray.map(value => console.log(value) || value * 2);

0개의 댓글