배열에는 for...of 문처럼 각 요소를 하나씩 살펴보면서 반복적인 동작을 할 수 있는 forEach와 map이라는 배열 만의 메소드가 있다.
메소드를 호출할 때 아규먼트로 콜백 함수를 작성해 주게 되면 콜백 함수의 첫 번째 파라미터로 배열의 요소를 순서대로 하나씩 전달하면서 매번 함수를 실행하는 원리로 동작한다.
const members = ['영훈', '윤수', '동욱', '태호'];
for (let member of members) {
console.log(`${member}님이 입장하셨습니다.`);
}
members.forEach(function (member) {
console.log(`${member}님이 입장하셨습니다.`);
});
그래서 콜백 함수에는 파라미터 하나가 반드시 작성되어야 한다. for...of 문과 비교하면 of 앞에 선언한 변수와 역할이 똑같다고 볼 수 있다.
const members = ['영훈', '윤수', '동욱', '태호'];
for (let member of members) {
console.log(`${member}님이 입장하셨습니다.`);
}
members.forEach((member) => {
console.log(`${member}님이 입장하셨습니다.`);
});
콜백 함수는 당연히 Arrow Function으로 작성할 수도 있다.
그런데 forEach 메소드는 여기서 끝이 아니라 이 콜백 함수에서 추가적으로 다룰 수 있는 두 가지 파라미터가 더 있다. 일단 첫 번째 파라미터는 배열의 요소가 전달되기 때문에 반드시 있어야 하고 두 번째 파라미터를 작성해주게 되면 요소의 인덱스 관리를 할 수 있게 된다.
members.forEach((member, index) => {
console.log(`${member}님이 입장하셨습니다.`);
});
index를 줄여서 i라고도 많이 사용한다.
members.forEach((member, i) => {
console.log(`${member}님이 입장하셨습니다.`);
});
그리고 인덱스는 다른 배열과 함께 활용할 때 같은 인덱스의 요소를 매칭할 때도 응용할 수가 있다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
firstNames.forEach((firstName, i) => {
console.log(`${lastNames[i]${firstName}님이 입장하셨습니다.`)
});
마지막 세 번째 파라미터는 반복 중인 배열 자체가 전달된다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
firstNames.forEach((firstName, i, arr) => {
console.log(`${lastNames[i]${firstName}님이 입장하셨습니다.`)
});
변수에 담긴 배열의 경우에는 변수 이름을 그대로 사용하면 되기 때문에 일반적으로는 잘 사용되지 않는 파라미터이다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
['영훈', '윤수', '동욱', '태호'].forEach((firstName, i, arr) => {
console.log(`${lastNames[i]${firstName}님이 입장하셨습니다.`)
});
그러나 이렇게 변수에 담지 않고 배열값 자체에 forEach 메소드를 활용하는 경우에는 상황에 따라서 이 콜백 함수 내에서 배열이 필요할 때 활용할 수도 있다.
forEach와 동작하는 방식이 거의 비슷하다. 그래서 forEach만 그대로 map으로 바꿔도 코드를 실행해보면 똑같이 동작한다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
['영훈', '윤수', '동욱', '태호'].map((firstName, i, arr) => {
console.log(`${lastNames[i]${firstName}님이 입장하셨습니다.`)
});
한 가지 다른 점은 map은 메소드의 호출 결과로 새로운 배열을 리턴한다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
firstNames.map((firstName, i) => {
return lastNames[i] + firstName;
});
console.log(fullNames); // ["강영훈", "이윤수", "손동욱", "성태호"]
map 메소드는 이렇게 콜백 함수 내에서 리턴문을 작성해주게 되면 각각의 리턴값으로 구성된 새로운 배열이 메소드의 결과로 리턴된다.
이를 콘솔에 출력해보게 되면 이렇게 map 메소드를 호출할 때 작성한 콜백 함수의 리턴값들로 구성된 배열이 출력되는 모습을 확인할 수 있다.
const firstNames = ['영훈', '윤수', '동욱', '태호'];
const lastNames = ['강', '이', '손', '성'];
firstNames.map((firstName, i) => lastNames[i] + firstName);
console.log(fullNames); // ["강영훈", "이윤수", "손동욱", "성태호"]
이렇게 Arrow Function의 문법을 활용해서 좀 더 간결하게 작성할 수도 있다.
forEach 메소드는 리턴값이 없기 때문에 변수에 담았을 때는 항상 undefined 값을 가진다. 그래서 단순히 배열의 반복 작업이 필요한 경우에는 forEach를, 반복 작업을 통해서 새로운 배열이 필요한 경우에는 map 메소드를 활용한다.
두 메소드의 최대 반복 횟수는 메소드를 처음 호출할 때 그 당시 요소의 개수이다.
const members = ['영훈', '윤수', '동욱', '태호'];
members.forEach((member) => {
console.log(`${member}님이 입장하셨습니다.`);
/* 영훈님이 입장하셨습니다.
윤수님이 입장하셨습니다.
동욱님이 입장하셨습니다.
태호님이 입장하셨습니다.
*/
members.push('코드잇');
});
console.log(members); //(8) ["영훈", "윤수", "동욱", "태호", "코드잇", "코드잇", "코드잇", "코드잇", "코드잇"]
forEach 메소드나 map 메소드도 마찬가지로 콜백 함수 내부에서 반복 중인 배열을 편집할 수도 있다. 매번 반복할 때마다 배열의 새로운 요소를 추가하게 되면 무한 루프에 빠질 거라고 예상할 수도 있지만 실제로 코드를 실행해보면 딱 네번만 동작하고 끝이 난다.
하지만 반복 중에 배열의 길이가 줄어드는 경우에는 반복 횟수도 함께 줄어들기 때문에 주의해야한다.
const members = ['영훈', '윤수', '동욱', '태호'];
members.forEach((member) => {
console.log(`${member}님이 입장하셨습니다.`);
/* 영훈님이 입장하셨습니다.
윤수님이 입장하셨습니다.
*/
members.pop();
});
console.log(members); //(8) ["영훈", "윤수", "동욱", "태호", "코드잇", "코드잇", "코드잇", "코드잇", "코드잇"]