Array
메서드에는 여러가지가 있다. 그 중에서 정말 유용하기도 하고 많이 쓰이기도 하는 map
과 forEach
에 대해서 정리해보려 한다.
먼저 메서드가 무엇인지를 알아야 한다. MDN에 따르면
메소드(method)는 객체의 속성인 함수이다. 두 가지 종류의 메소드가 있다. 객체 인스턴스에 의해 수행되는 태스크에 내장된 인스턴스 메소드 또는 오브젝트 생성자에서 직접 호출되는 태스크인 정적 메소드가 여기에 해당된다.
- 참고
자바스크립트 함수에서 그 자체는 객체이므로, 그런 맥락에서 메소드는 실제로 함수에 대한 객체 참조인 것이다.
실제로 개발자 도구 콘솔에서 window
라고 입력하면 window
객체가 출력되는 것을 확인할 수 있다. window
객체 내에 수많은 키들이 포함되어 있는데 그 중에 Array
라는 키의 Prototype
을 확인해보면 우리가 사용하는 수많은 Array
에 대한 메서드들이 함수의 형태로 저장된 것을 찾아볼 수 있다.
이것이 잘 이해가 되지 않는다면 직접 객체를 만들어 그에 대한 키값으로 함수를 입력해서 실행해볼 수 있다.
const obj = {
name: 'John',
age: '24',
fn: function() {
return this.name;
}
}
obj.fn(); // 'John'
obj
라는 임의의 객체에 fn
이라는 키를 할당해주고 그에 대한 value로 obj
의 name
키의 value를 리턴하는 함수를 만들었다. obj.fn()
이라는 형태가 익숙하지 않은가? 우리가 메서드를 쓰는 형태와 동일하다. arr.map(...)
이런식으로 말이다. 우리는 window
라는 객체에 항상 접근하여 메서드를 사용하고 있는 것이다.
map
메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 기존 배열은 그대로 두고 새로운 배열을 반환한다 immutable한 속성을 가지고 있기 때문이다. 그래서 반드시 callback 함수가 필요하다. callback 함수는 undefined
를 포함하여 배열 값이 들어있는 요소에 대해서만 호출된다. 즉, 값이 삭제되거나 할당/정의되지 않는 인덱스에 대해서는 호출되지 않는다.
map
을 활용하는 예시는 다음과 같다.
const nums = [100, 9, 30, 7];
const moreThan100 = nums => {
const result = nums.map(num => {
return num >= 100 ? true : false;
})
return result;
}
moreThan100(nums); // [true, false, false, false]
nums; // [100, 9, 30, 7]
map
메서드를 활용하여 기존 배열의 요소들이 100보다 클 경우 true
로 변환하고 아니면 false
로 변환하는 함수를 만들었다. 실제로 콘솔에 찍어보면 moreThan100
이라는 함수를 실행하면 바뀐 값들이 출력되는것을 확인해볼 수 있다. nums
를 찍어보면 기존 배열이 그대로 출력된다. 반복문을 사용하지 않아도 배열의 각 요소에 대해 callback 함수가 적용된다. 여러모로 편리한 메서드이다.
forEach
메서드도 map
과 비슷한 기능을 한다. forEach
메서드는 주어진 함수를 배열의 각 요소에 대해 오름차순으로 실행한다. 삭제했거나 초기화하지 않은 인덱스 속성에 대해서는 실행하지 않는다. forEach
도 마찬가지로 immutable한 속성을 가지고 있기 때문에 기존 배열은 건드리지 않고 새로운 배열을 반환한다.
forEach
를 활용하는 예시는 다음과 같다.
let startWithNames = [];
let names = ['a', 'ab', 'cbb', 'ada'];
names.forEach(el => {
if (el.startsWith('a')) {
startWithNames.push(el);
}
});
startWithNames; // ['a', 'ab', 'ada']
names; // ['a', 'ab', 'cbb', 'ada']
기존 배열 names
에 forEach
를 활용하여 각 요소에 대해 a
로 시작하는지 체크하여 true면 빈 배열 startWithNames
에 push
하는 방식이다. 각 배열을 호출해보면 기존 배열은 변하지 않는 것을 확인할 수 있다. forEach
를 for
문으로 변형할 수도 있다.
function foo(arr) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].startsWith('a')) {
startWithNames.push(arr[i]);
}
}
return startWithNames;
}
foo(names); // ['a', 'ab', 'ada']
names; // ['a', 'ab', 'cbb', 'ada']
foo
라는 함수는 forEach
와 정확히 똑같은 기능을 하고 있는 것을 확인할 수 있다. 하지만 forEach
에는 단점이 있는데 MDN에 따르면
예외를 던지지 않고는 forEach()를 중간에 멈출 수 없습니다. 중간에 멈춰야 한다면 forEach()가 적절한 방법이 아닐지도 모릅니다.
다음 방법으로는 조기에 반복을 종료할 수 있습니다.
- 간단한 for 반복문
- for...of, for...in 반복문
- Array.prototype.every()
- Array.prototype.some()
- Array.prototype.find()
- Array.prototype.findIndex()
다른 배열 메서드 every(), some(), find(), findIndex()는 배열 요소를 판별 함수에 전달하고, 그 결과의 참/거짓 여부에 따라 반복의 종료 여부를 결정합니다.
배열의 모든 요소에 대해 함수를 실행하기 때문에 원하는 값이 나와도 예외 처리를 해주지 않으면 반복을 멈출수가 없다.