희소배열과 반복문(Sparse Arrays and Loops)
for-in
문은 프로퍼티를 반복하기 때문에, for-in
문을 사용하여 희소 배열(sparse array)을 반복할 때 프로퍼티가 없는(빈) 인덱스는 건너뜁니다.
공통된 3가지 for
문 특징
<문>
부분에 두 개 이상의 문(statement)이나 선언을 작성하려면 블록 문({ ... }
)을 사용해 하나의 문으로 묶어야 합니다.for (...) { console.log("Hello"); console.log("World"); }
- 하나의 문만 작성할 때도 블록 문을 사용할 수 있습니다.
for (...) { console.log("Hello"); }
for
문 자체는 하나의 문이므로 문이 기대되는 곳에 작성할 수 있습니다.for
문을 중첩해서 사용할 수 있습니다.for (...) for (...) ...;
- 함수의 중괄호 안에
for
문을 작성할 수 있습니다.() => { for (...) ...; }
function foo() { for (...) ...; }
주의: () => for (...) ...;
와 같이 작성할 수는 없습니다.
화살표 함수식에서 =>
뒤에 {
가 오지 않으면, =>
의 오른쪽은 문이 아닌 표현식이 기대되기 때문입니다.
<문>
내부에서break
문을 사용하면 루프 전체 실행을 종료할 수 있습니다.break
문이 실행되면 현재 반복이 즉시 종료되고, 그 루프의 이후 반복은 실행되지 않습니다.break
문이 실행되면 종료된for
문의 바로 다음으로 실행이 이동합니다.
<문>
내부에서continue
문을 사용하면 현재 반복만 종료할 수 있습니다.continue
문이 실행되면 현재 반복이 즉시 종료되고, 다음 반복의 준비로 이동합니다.continue
문이 실행되면for
문의<문>
부분 바로 다음으로 실행이 이동합니다.- 전통적인
for
문에서는<업데이트>
표현식 평가로 이동합니다.
중첩을 깊게 하지 않고 각 반복에서 처리 대상을 좁힐 때 사용할 수 있습니다.
for (const article of articles) {
if (article.isDraft) continue;
if (article.lastUpdated < oneWeekAgo) continue;
if (article.likes < 10) continue;
console.log(article);
// 중첩이 점점 깊어지지 않습니다.
// if (!article.isDraft)
// if (article.lastUpdated >= oneWeekAgo)
// if (article.likes >= 10)
// console.log(article);
}
<문>
내부에서return
문을 사용하면for
문을 포함하는 함수 전체 실행을 종료할 수 있습니다.return
문이 실행되면 현재 반복이 즉시 종료되고, 그 루프의 이후 반복은 실행되지 않습니다.return
문이 실행되면 함수 전체가 종료되고 함수 호출자에게 실행이 돌아갑니다.
for
문을 포함한 문에는 레이블을 붙일 수 있습니다.- 레이블은 문의 바로 앞에 작성되며, 레이블 이름과 콜론(
:
)으로 구성됩니다. break
문이나continue
문에서 어느 문을 대상으로 할지 지정할 때 레이블을 사용할 수 있습니다.outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (i === 1 && j === 1) { break outer; } console.log(i, j); } }
- 레이블은 문의 바로 앞에 작성되며, 레이블 이름과 콜론(
continue
문에서for
,while
,do-while
문이 아닌 문 레이블을 지정하면 구문 오류가 발생합니다.- 동일한 이름의 레이블을 중첩하면 구문 오류가 발생합니다.
a: for (;;) { a: for (;;) { // 구문 오류 // ... } }
b: for (;;) {
// ...
}
b: for (;;) { // 중첩이 아니므로 구문 오류가 발생하지 않음
// ...
}
- 레이블이 없는
break
문은 가장 내부의for
,while
,do-while
,switch
문을 대상으로 합니다. - 레이블이 없는
continue
문은 가장 내부의for
,while
,do-while
문을 대상으로 합니다. - 해당 문이 존재하지 않으면 구문 오류가 발생합니다.
while
문, do-while
문
while
문과 do-while
문은 반복 처리를 수행하는 기본적인 구문 중 하나입니다.
while
문
while (<조건>)
<문>
while
문이 실행될 때는 다음과 같은 순서로 처리됩니다.
<조건>
표현식이 평가됩니다.- 평가 결과가 falsy이면 루프가 종료되고, 실행이
while
문 바로 다음으로 이동합니다. <문>
이 실행됩니다.- 1단계로 돌아갑니다.
일반적인 배열 반복은(억지로 작성해보면) 다음과 같은 형태입니다.
const array = ["a", "b", "c", "d", "e"];
let i = 0;
while (i < array.length) {
console.log(array[i]);
i++;
}
// 출력:
// a
// b
// c
// d
// e
do-while
문
do
<문>
while (<조건>);
do-while
문이 실행될 때는 다음과 같은 순서로 처리됩니다.
<문>
이 실행됩니다.<조건>
표현식이 평가됩니다.- 평가 결과가 falsy이면 루프가 종료되고, 실행이
do-while
문 바로 다음으로 이동합니다. - 1단계로 돌아갑니다.
do-while
문은 첫 번째 1회는 조건 평가 없이 반드시 <문>
이 실행된다는 점이 while
문과 다릅니다.
while
문, do-while
문의 특징
while
문과 do-while
문은 배열 반복 처리보다는 조건에 따라 반복 처리를 수행하는 데 중점을 둔 구문입니다.
구문적 특징은 for
문과 유사한 점이 많습니다.
<문>
에 두 개 이상의 문을 작성하려면 블록 문({ ... }
)을 사용해 하나의 문으로 묶어야 합니다.while
문과do-while
문은 하나의 문이므로 문이 기대되는 곳에 작성할 수 있습니다.continue
,break
,return
문에 대해서는for
문과 동일한 동작을 합니다.while
문과do-while
문에도 레이블을 붙일 수 있습니다.
while
문, do-while
문의 변형
while (<조건>)
<문>
는
for (; <조건>; )
<문>
와 동일한 의미입니다.
do
<문>
while (<조건>);
는
while (true) {
<문>
if (!(<조건>)) break;
}
와 동일한 의미입니다.
배열 메서드
JavaScript에는 배열을 다루기 위한 유용한 메서드들이 준비되어 있으며, 배열 요소에 대한 반복 처리를 위한 메서드도 있습니다.
반복 처리를 위한 배열 메서드의 대부분은 콜백 함수를 인수로 받습니다.
콜백 함수는 배열의 각 요소에 대해 실행되는 함수로, 반복적으로 수행할 작업을 기술합니다.
콜백 함수는 일반적으로 3개의 인수를 받습니다:
- 요소: 배열 요소의 값
- 인덱스: 요소의 인덱스
- 배열: 반복 대상이 되는 전체 배열
forEach
메서드
배열의 forEach
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행합니다.
모든 요소에 대한 실행이 끝나면 forEach
메서드 호출이 종료되고 undefined
를 반환합니다.
const array = ["a", "b", "c"];
array.forEach((element, i, obj) => {
console.log(element, i, obj);
});
// 출력:
// a 0 [ 'a', 'b', 'c' ]
// b 1 [ 'a', 'b', 'c' ]
// c 2 [ 'a', 'b', 'c' ]
콜백 함수의 반환값은 무시되므로, 화살표 함수식의 내용이 표현식이면 중괄호를 생략하고 =>
뒤에 직접 그 표현식을 써도 동일하게 동작합니다.
const array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
const result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
array.forEach((element) => {
result[element] += 1;
});
// 아래와 동일한 동작
array.forEach((element) => result[element] += 1);
forEach
메서드의 특징
배열의 forEach
메서드는 반복 처리에 의미 있는 결과를 반환하지 않으므로(항상 undefined
를 반환), 반복 처리가 의미를 가지려면 콜백 함수 내에서 부작용을 일으키는 처리를 해야 합니다.
위의 예제에서는 console.log
로 출력하거나 result
객체의 프로퍼티를 변경하는 것이 부작용에 해당합니다.
콜백 함수 내에서 루프 전체를 중간에 종료하고 루프 바로 다음으로 처리 흐름을 이동시키는(for
문에서의 break
문이나 return
문에 해당) 수단은 없습니다.
(forEach
메서드의 실행을 종료하기 위해 에러를 던질 수 있지만, forEach
메서드를 호출하는 문을 try-catch
문으로 둘러싸 에러를 포착해야 합니다.)
현재 반복을 중간에 종료하고 다음 반복으로 넘어가기 위해서는(for
문에서의 continue
문에 해당) return
문을 사용합니다.
이러한 return
문은 각 반복에서 처리 대상을 좁힐 때 중첩을 깊게 하지 않기 위해 사용되기도 합니다.
(이 테크닉은 '조기 반환(Early Return)'이라고 불리며, 반복 처리뿐만 아니라 일반 함수에서도 사용됩니다.)
articles.forEach((article) => {
if (article.isDraft) return;
if (article.lastUpdated < oneWeekAgo) return;
if (article.likes < 10) return;
console.log(article);
// 아래와 같이 중첩이 깊어지지 않습니다.
// if (!article.isDraft)
// if (article.lastUpdated >= oneWeekAgo)
// if (article.likes >= 10)
// console.log(article);
});
인수 변수에 값을 할당해도 원래 배열 요소에 영향을 주지 않습니다.
const array = [1, 2, 3, 4, 5];
array.forEach((element) => {
// 이 할당은 콜백 함수 내의 변수 element만 변경하며,
// 반복 대상인 배열 array의 내용에는 영향을 주지 않음!
element = element * 2;
});
console.log(array);
// 출력: [ 1, 2, 3, 4, 5 ]
인수 분해 할당이 가능합니다.
const users = [
{ name: "Alice", age: 20 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 30 },
];
users.forEach(({ name, age }) => {
console.log(name, age);
});
forEach
메서드로 반복 처리할 때 비동기 처리를 순차적으로 실행할 수 없습니다.
비동기 처리를 순차적으로 실행하려면 for-of
문과 await
연산자를 사용해 반복 처리를 하거나 Promise
메서드를 사용해야 합니다.
const result = [];
const registerElement = async (element) => {
// 1초가 걸리는 비동기 처리를 재현
await new Promise((resolve) => setTimeout(resolve, 1000));
result.push(element);
};
const array = ["a", "b", "c", "d", "e"];
// 이전 `registerElement` 처리가 끝나기 전에 다음 `registerElement` 처리가 시작됨!
array.forEach(async (element) => {
await registerElement(element);
});
// 또한 5번의 `registerElement` 처리는 이 시점에서 어느 것도 끝나지 않음!
console.log(result); // 출력: []
대안:
const array = ["a", "b", "c", "d", "e"];
for (const element of array) {
await registerElement(element);
// 이 시점에서 이 `registerElement` 처리는 끝난 상태
}
console.log(result); // 출력: [ 'a', 'b', 'c', 'd', 'e' ]
const array = ["a", "b", "c", "d", "e"];
let promise = Promise.resolve();
array.forEach((element) => {
// 이전 `registerElement` 처리가 끝난 후 다음 `registerElement` 처리가 시작되도록 함
promise = promise.then(async () => {
await registerElement(element);
});
});
await promise; // 마지막 `registerElement` 처리가 끝날 때까지 기다림
console.log(result); // 출력: [ 'a', 'b', 'c', 'd', 'e' ]
const array = ["a", "b", "c", "d", "e"];
await array.reduce(
(prev, element) => prev.then(async () => {
await registerElement(element);
}),
Promise.resolve()
);
console.log(result); // 출력: [ 'a', 'b', 'c', 'd', 'e' ]
반복 대상이 되는 인덱스
forEach
메서드가 반복하는 범위는 메서드가 호출될 때의 배열 길이를 기준으로 결정됩니다.
따라서 forEach
메서드의 콜백 함수에서 배열의 길이를 늘린 경우, 원래 길이를 초과하는 인덱스의 요소는 반복되지 않습니다.
희소 배열(sparse array)의 프로퍼티가 없는(빈) 인덱스는 반복되지 않습니다.
const array = ["a", , "c"];
array.forEach((element, i) => {
console.log(i, element);
});
// 출력:
// 0 a
// 2 c
콜백 함수의 세 번째 인수
콜백 함수의 세 번째 인수는 메서드 체인의 끝에서 forEach
메서드를 호출할 때 유용할 수 있습니다.
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const twinPrimes = [];
array
.filter((element) => isPrime(element)) // 소수만 추출
.forEach((element, i, primes) => {
// 세 번째 인수 primes의 값은 반복 대상인 배열, 즉 소수만 추출된 배열(array가 아닌 array.filter(...))
if (i === 0) return; // 첫 번째 요소는 바로 앞의 소수가 없으므로 건너뜀
const prev = primes[i - 1]; // 바로 앞의 소수를 primes에서 가져옴
// 쌍둥이 소수인지 판별
if (element - prev === 2) {
twinPrimes.push([prev, element]);
}
});
console.log(twinPrimes);
// 출력: [ [ 3, 5 ], [ 5, 7 ] ]
forEach
메서드의 두 번째 인수
forEach
메서드의 두 번째 인수로 콜백 함수에서 this
로 사용될 값을 지정할 수 있습니다.
class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(element) {
console.log(this.prefix, element);
}
}
const logger = new Logger("LOG:");
const array = ["a", "b", "c"];
array.forEach(logger.log); // TypeError: Cannot read properties of undefined (reading 'prefix')
array.forEach(logger.log, logger);
// 출력:
// LOG: a
// LOG: b
// LOG: c
// 아래와 거의 같은 의미를 가집니다
array.forEach(logger.log.bind(logger));
첫 번째 인수의 값이 화살표 함수인 경우, 두 번째 인수를 지정해도 의미가 없습니다.
화살표 함수식 내부의 this
는 항상 화살표 함수식의 외부 스코프의 this
와 일치하기 때문입니다.
배열의 forEach
메서드와 동일한 이름의 메서드가 다른 객체에도 존재할 수 있습니다.
예를 들어, Map
과 Set
에도 forEach
메서드가 있습니다.
해당 객체에 포함된 요소에 대해 반복 처리를 한다는 점에서 공통적이며, 콜백 함수의 인수가 배열의 것과 유사합니다.
배열의 forEach
메서드는 배열 유사 객체(array-like object)에 대해서도 반복 처리를 할 수 있습니다.
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
Array.prototype.forEach.call(arrayLike, (element, i) => {
console.log(i, element);
});
// 출력:
// 0 a
// 1 b
// 2 c
forEach
메서드의 변형
array
가 배열이고, <처리>
에 return
문이나 var
, 함수 선언이 포함되지 않는 경우:
array.forEach((element) => {
<처리>
});
는 다음과 거의 동일한 의미를 가집니다:
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
<처리>
}
(i
와 length
는 <처리>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열(dense array)일 경우 다음과도 거의 동일한 의미를 가집니다:
for (let element of array) {
<처리>
}
(forEach
메서드 실행 중에 array
의 길이가 증가하면 추가된 인덱스도 반복되는 차이점이 있습니다.)
<처리>
에 return
문이 포함된 경우, 이 for
문에 대한 continue
문으로 대체해야 합니다.
map
메서드
배열의 map
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고 그 반환값을 모읍니다.
모든 요소에 대한 실행이 끝나면 map
메서드는 그 반환값을 모아 새로운 배열을 반환합니다.
원래 배열은 변경되지 않습니다.
const array = [1, 2, 3, 4, 5];
// 각 요소의 값을 두 배로 만든 새로운 배열을 생성
const doubled = array.map((element) => element * 2);
console.log(doubled); // 출력: [2, 4, 6, 8, 10]
// 원래 배열은 변경되지 않았음
console.log(array); // 출력: [1, 2, 3, 4, 5]
새로운 배열의 길이는 원래 배열의(처음의) 길이와 동일합니다.
콜백 함수의 반환값은 새로운 배열의 해당 인덱스 요소의 값이 됩니다.
따라서 forEach
메서드의 경우와 달리 반환값의 유무에 따라 의미가 달라집니다.
const array = [1, 2, 3];
const doubled = array.map((element) => element * 2);
console.log(doubled); // 출력: [2, 4, 6]
const array2 = array.map((element) => { element * 2; });
console.log(array2); // 출력: [undefined, undefined, undefined]
주의: =>
뒤에 {
가 이어지는 화살표 함수식에서는, 함수가 값을 반환하려면 return
문을 사용해야 합니다.
return
문 없이 함수 내부의 실행이(블록의 끝에 도달함으로써) 끝나면, 그 함수의 반환값은 암묵적으로 undefined
가 됩니다.
map
메서드의 특징
배열의 map
메서드는 콜백 함수를 하나의 사상(寫像)으로 간주하고 배열의 각 요소 값을 그 사상에 따라 변환하는 것에 초점을 맞춘 메서드입니다.
따라서 일반적으로 콜백 함수는 반환값을 가지며, map
메서드의 반환값이 되는 배열을 사용해 후속 처리를 진행하는 경우가 많습니다.
배열의 map
메서드의 결과도 역시 배열이 되므로, map
메서드를 호출하는 식의 뒤에 또다시 map
메서드나 다른 배열 메서드를 연결할 수 있습니다.
이러한 메서드를 연결하는 형태를 메서드 체인이라고 부르기도 합니다.
const array = [1, 2, 3, 4, 5];
const doubledPlusOne = array
.map((element) => element * 2) // 각 요소의 값을 두 배로 하고
.map((element) => element + 1); // 한 번 더 더함
console.log(doubledPlusOne); // 출력: [3, 5, 7, 9, 11]
array
.map((element) => element * 3) // 각 요소의 값을 세 배로 하고
.filter((element) => element % 2 === 0) // 2로 나누어 떨어지는 것만 추출
.forEach((element) => console.log(element)); // 각각을 출력
// 출력:
// 6
// 12
콜백 함수 내부에서 루프 전체를 중간에 종료하고 루프 바로 다음으로 처리 흐름을 이동시킬 수 있는 수단이 없다는 점은 forEach
메서드와 동일합니다.
현재 반복을 중간에 종료하고 다음 반복으로 넘어가기 위해서는(for
문에서의 continue
문에 해당) return
문을 사용해야 한다는 점도 forEach
메서드와 동일하지만, 그 return
문으로 반환하는 값은 map
메서드의 반환값이 되는 배열 요소의 값이 됩니다.
map
메서드만으로 결과 배열에서 요소를 제거하는 것은 불가능합니다.
const array = [1, 2, 3, 4, 5];
const newArray = array.map((element) => {
if (element % 2 === 0) return; // 짝수는 무시하고 싶음
return element * element;
});
// 결과 배열에 `undefined`가 포함됨
console.log(newArray); // 출력: [1, undefined, 9, undefined, 25]
요소를 제거하려면 filter
메서드를 추가로 사용해 불필요한 요소를 제외해야 합니다.
const newArray = array
.map((element) => {
if (element % 2 === 0) return; // 짝수는 무시하고 싶음
return element * element;
})
.filter((element) => element !== undefined);
console.log(newArray); // 출력: [1, 9, 25]
map
메서드 대신 flatMap
메서드를 사용할 수도 있습니다.
const newArray = array.flatMap((element) => {
if (element % 2 === 0) return []; // 짝수는 무시하고 싶음
return [element * element];
});
console.log(newArray); // 출력: [1, 9, 25]
map
메서드에 전달하는 콜백 함수를 비동기 함수로 만들면, map
메서드의 반환값은 Promise
로 구성된 배열이 됩니다.
const getDoubled = async (element) => {
// 1초가 걸리는 비동기 처리를 재현
await new Promise((resolve) => setTimeout(resolve, 1000));
return element * 2;
};
const array = [1, 2, 3];
const promises = array.map(async (element) => {
const doubled = await getDoubled(element);
return doubled;
});
console.log(promises);
// 출력: [Promise {<pending>}, Promise {<pending>}, Promise {<pending>}]
Promise.all
을 사용하면 "Promise로 구성된 배열"을 "각 Promise
의 해결값으로 구성된 배열로 해결되는 Promise
"로 변환할 수 있습니다.
이것과 await
연산자를 조합하면 비동기 함수로 각 요소를 처리한 새로운 배열을 얻을 수 있습니다.
const array = [1, 2, 3];
const results = await Promise.all(
array.map(async (element) => {
const doubled = await getDoubled(element);
return doubled;
})
);
console.log(results); // 출력: [2, 4, 6]
이때 각 요소의 비동기 처리는 병렬로 실행된다는 점에 유의하세요.
전체 처리 시간은 가장 오래 걸리는 비동기 처리에 의해 결정됩니다. (위 예제에서는 각 요소의 비동기 처리가 약 1초 걸리므로 전체 처리 시간도 약 1초가 걸립니다.)
비동기 처리를 순차적으로 실행하려면 map
메서드 대신 for-of
문과 await
연산자를 사용해 반복 처리를 하거나 Promise
메서드를 사용해야 합니다.
const array = [1, 2, 3];
const results = [];
for (const element of array) {
const doubled = await getDoubled(element);
results.push(doubled);
}
console.log(results); // 출력: [2, 4, 6]
const array = [1, 2, 3];
const results = await array.reduce(
(prev, element) => prev.then(async (results) => {
const doubled = await getDoubled(element);
results.push(doubled);
return results;
}),
Promise.resolve([])
);
console.log(results); // 출력: [2, 4, 6]
순차 처리의 경우, 전체 처리 시간은 각 요소의 비동기 처리의 합계 시간이 됩니다.
(위 예제에서는 각 요소의 비동기 처리가 각각 약 1초씩 걸리므로 전체 처리 시간은 약 3초가 걸립니다.)
반복 대상이 되는 인덱스
map
메서드가 반복하는 범위와 반환값이 되는 새로운 배열의 길이는 메서드가 호출될 때의 원래 배열의 길이를 기준으로 결정됩니다.
따라서 map
메서드의 콜백 함수에서 배열의 길이를 늘린 경우, 원래 길이를 초과하는 인덱스의 요소는 반복되지 않으며 새로운 배열에도 포함되지 않습니다.
희소 배열의 프로퍼티가 없는(빈) 인덱스는 반복되지 않으며, 새로운 배열에서도 존재하지 않는(빈) 인덱스가 됩니다.
const array = ["a", , "c"];
const newArray = array.map((element, i) => element.repeat(i + 1));
console.log(newArray); // 출력: [ 'a', <1 empty item>, 'ccc' ]
콜백 함수의 세 번째 인수
콜백 함수의 세 번째 인수는 메서드 체인에서 map
메서드를 호출할 때 유용할 수 있습니다.(forEach
와 마찬가지로)
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const twinPrimes = [];
array
.filter((element) => isPrime(element)) // 소수만 추출
.map((element, i, primes) => {
// 세 번째 인수 primes의 값은 반복 대상인 배열, 즉 소수만 추출된 배열(array가 아닌 array.filter(...))
if (i === 0) return; // 첫 번째 요소는 바로 앞의 소수가 없으므로 건너뜀
const prev = primes[i - 1]; // 바로 앞의 소수를 primes에서 가져옴
// 쌍둥이 소수인지 판별
if (element - prev === 2) {
twinPrimes.push([prev, element]);
}
});
console.log(twinPrimes);
// 출력: [ [ 3, 5 ], [ 5, 7 ] ]
콜백 함수에는 항상 3개의 인수가 전달되므로 생략 가능한 인수를 받는 함수의 취급에 주의해야 합니다.
특히, parseInt
함수를 그대로 콜백 함수로 사용하면 의도하지 않은 결과가 나올 수 있습니다.
const array = ["10", "10", "10", "10"];
// 10진수 정수로 해석한 숫자 배열로 변환하고 싶지만……
const newArray = array.map(parseInt);
console.log(newArray); // 출력: [10, NaN, 2, 3]
// `parseInt` 함수는 생략 가능한 두 번째 인수가 지정된 경우 그 값을 기수로 사용해 첫 번째 인수의 문자열을 해석합니다.
// 한편, `map` 메서드는 콜백 함수의 두 번째 인수로 인덱스를 전달합니다.
parseInt("10", 0, array) // → 10 (자동으로 10진수로 해석됨)
parseInt("10", 1, array) // → NaN (1은 기수로 무효)
parseInt("10", 2, array) // → 2 (2진수로 해석됨)
parseInt("10", 3, array) // → 3 (3진수로 해석됨)
// 의도한 대로 동작하는 예: 두 번째 인수 이후는 `parseInt`에 전달하지 않음
const newArray2 = array.map((element) => parseInt(element));
console.log(newArray2); // 출력: [10, 10, 10, 10]
map
메서드의 두 번째 인수
map
메서드의 두 번째 인수로 콜백 함수에서 this
로 사용될 값을 지정할 수 있습니다. (forEach
와 마찬가지로)
class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(element) {
console.log(this.prefix, element);
}
}
const logger = new Logger("LOG:");
const array = ["a", "b", "c"];
const newArray = array.map(logger.log, logger);
console.log(newArray); // 출력: [undefined, undefined, undefined]
배열의 map
메서드는 배열 유사 객체에도 호출할 수 있으며, 반환값은 배열이 됩니다.
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const newArray = Array.prototype.map.call(arrayLike, (element, i) => element.repeat(i + 1));
console.log(newArray); // 출력: [ 'a', 'bb', 'ccc' ]
map
메서드의 변형
array
가 배열일 때:
const newArray = array.map((element) => {
const newElement = <식>;
return newElement;
});
는 <식>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
const newArray = new Array(+array.length);
for (let i = 0; i < newArray.length; i++) {
if (!(i in array)) continue;
let element = array[i];
const newElement = <식>;
newArray[i] = newElement;
}
(i
는 <식>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
const newArray = [];
for (let element of array) {
const newElement = <식>;
newArray.push(newElement);
}
와도 거의 동일한 의미를 가집니다. (map
메서드 실행 중에 array
의 길이가 증가할 때 추가된 인덱스도 반복된다는 차이점이 있습니다.)
map
메서드를 여러 번 연결하는 패턴의 변형
array
가 배열일 때:
const newArray = array
.map((element) => <element를 사용하는 식 1>)
.map((element) => <element를 사용하는 식 2>)
.map((element) => <element를 사용하는 식 3>);
는 <element를 사용하는 식 1>
, <element를 사용하는 식 2>
, <element를 사용하는 식 3>
에 부작용이 없다면 다음과 같이 변형할 수 있습니다.
const newArray = array.map((element) => {
const newElement1 = <element를 사용하는 식 1>;
const newElement2 = <newElement1을 사용하는 식 2>;
const newElement3 = <newElement2를 사용하는 식 3>;
return newElement3;
});
(newElement1
, newElement2
, newElement3
은 3개의 식에서 참조되지 않는 변수명이어야 합니다.)
map
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행한 후 배열을 반환하므로, map
메서드를 여러 번 연결한 경우:
- 모든 요소에 대해 순서대로 식 1이 평가됨
- 모든 요소에 대해 순서대로 식 2가 평가됨
- 모든 요소에 대해 순서대로 식 3이 평가됨
이라는 순서로 처리됩니다. 한편, 하나의 map
메서드에서 3개의 식을 모두 실행하는 경우:
- 첫 번째 요소에 대해 식 1, 식 2, 식 3이 순서대로 평가됨
- 두 번째 요소에 대해 식 1, 식 2, 식 3이 순서대로 평가됨
- 마지막 요소에 대해 식 1, 식 2, 식 3이 순서대로 평가됨
라는 순서로 처리됩니다.
따라서 부작용이 있는 식을 사용하는 경우, 이러한 변형이 의미를 바꿀 수 있습니다.
const array = ["a", "b", "c"];
const newArray = array
.map((element) => {
// 부작용: console.log를 통한 출력
console.log("1:", element);
return element.toUpperCase();
})
.map((element) => {
// 부작용: console.log를 통한 출력
console.log("2:", element);
return element.repeat(2);
});
console.log(newArray);
// 출력:
// 1: a
// 1: b
// 1: c
// 2: A
// 2: B
// 2: C
// [ 'AA', 'BB', 'CC' ]
const array = ["a", "b", "c"];
const newArray = array.map((element) => {
console.log("1:", element);
const newElement1 = element.toUpperCase();
console.log("2:", newElement1);
const newElement2 = newElement1.repeat(2);
return newElement2;
});
console.log(newArray);
// 출력: (최종 배열은 동일하지만, 중간 출력의 순서가 다름)
// 1: a
// 2: A
// 1: b
// 2: B
// 1: c
// 2: C
// [ 'AA', 'BB', 'CC' ]
map
메서드 다음에 flat
메서드를 사용하는 경우
array
가 배열일 때:
const newArray = array
.map((element) => <식>)
.flat();
는(.flat(1)
도 포함하여):
const newArray = array.flatMap((element) => <식>);
과 거의 동일한 의미를 가집니다.
map
메서드의 반환값을 사용하지 않는 경우
배열의 map
메서드의 반환값을 사용하지 않는 경우 대신 forEach
메서드를 사용할 수 있습니다.
array.map((element) => {
<처리>
});
는
array.forEach((element) => {
<처리>
});
와 거의 동일한 의미를 가집니다.
filter
메서드
배열의 filter
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과값이 true
인 요소를 모아 새로운 배열을 반환합니다.
모든 요소에 대한 실행이 끝나면 filter
메서드는 그 결과값이 true
인 요소를 모아 새로운 배열을 반환합니다. 원래 배열은 변경되지 않습니다.
const array = [1, 2, 3, 4, 5];
// 각 요소의 값이 짝수인 요소만 모아 새로운 배열을 생성
const evens = array.filter((element) => element % 2 === 0);
console.log(evens); // 출력: [2, 4]
// 원래 배열은 변경되지 않음
console.log(array); // 출력: [1, 2, 3, 4, 5]
filter
메서드의 특징
배열의 filter
메서드는 콜백 함수의 반환값이 true
인 경우 해당 요소를 결과 배열에 포함시키는 데 중점을 둔 메서드입니다.
따라서 일반적으로 콜백 함수는 반환값을 가지며, filter
메서드의 반환값이 되는 배열을 사용해 후속 처리를 진행하는 경우가 많습니다.
const array = [1, 2, 3, 4, 5];
const evenSquares = array
.filter((element) => element % 2 === 0) // 짝수만 추출
.map((element) => element * element); // 제곱하여 새로운 배열 생성
console.log(evenSquares); // 출력: [4, 16]
filter
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행한 후 배열을 반환하므로, filter
메서드를 여러 번 연결한 경우:
- 모든 요소에 대해 순서대로 조건 1이 평가됨
- 모든 요소에 대해 순서대로 조건 2가 평가됨
- 모든 요소에 대해 순서대로 조건 3이 평가됨
이라는 순서로 처리됩니다. 한편, 하나의 filter
메서드에서 여러 조건을 모두 실행하는 경우:
- 첫 번째 요소에 대해 조건 1, 조건 2, 조건 3이 순서대로 평가됨
- 두 번째 요소에 대해 조건 1, 조건 2, 조건 3이 순서대로 평가됨
- 마지막 요소에 대해 조건 1, 조건 2, 조건 3이 순서대로 평가됨
이라는 순서로 처리됩니다. 따라서 부작용이 있는 조건을 사용하는 경우, 이러한 변형이 의미를 바꿀 수 있습니다.
const array = [1, 2, 3, 4, 5, 6];
const evenAndGreaterThanThree = array
.filter((element) => {
console.log("조건 1:", element);
return element % 2 === 0;
})
.filter((element) => {
console.log("조건 2:", element);
return element > 3;
});
console.log(evenAndGreaterThanThree);
// 출력:
// 조건 1: 1
// 조건 1: 2
// 조건 1: 3
// 조건 1: 4
// 조건 1: 5
// 조건 1: 6
// 조건 2: 4
// 조건 2: 6
// [ 4, 6 ]
const array = [1, 2, 3, 4, 5, 6];
const evenAndGreaterThanThree = array.filter((element) => {
console.log("조건 1:", element);
if (element % 2 !== 0) return false;
console.log("조건 2:", element);
return element > 3;
});
console.log(evenAndGreaterThanThree);
// 출력:
// 조건 1: 1
// 조건 1: 2
// 조건 1: 3
// 조건 1: 4
// 조건 1: 5
// 조건 1: 6
// 조건 2: 4
// 조건 2: 6
// [ 4, 6 ]
filter
메서드의 변형
array
가 배열일 때:
const newArray = array.filter((element) => <조건>);
는 <조건>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
const newArray = [];
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (!<조건>) continue;
newArray.push(element);
}
(i
, length
는 <조건>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
const newArray = [];
for (let element of array) {
if (!<조건>) continue;
newArray.push(element);
}
와도 거의 동일한 의미를 가집니다.
filter
메서드와 forEach
메서드의 조합
filter
메서드로 걸러내고 forEach
메서드로 처리하는 경우:
array
.filter((element) => <조건>)
.forEach((element) => {
<처리>
});
는 다음과 같은 의미를 가집니다.
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (!<조건>) continue;
<처리>
}
또는
for (let element of array) {
if (!<조건>) continue;
<처리>
}
filter
메서드의 반환값을 사용하지 않는 경우
배열의 filter
메서드의 반환값을 사용하지 않는 경우 대신 forEach
메서드를 사용할 수 있습니다.
array.filter((element) => {
if (!<조건>) return false;
<처리>
});
는 다음과 동일한 의미를 가집니다.
array.forEach((element) => {
if (!<조건>) return;
<처리>
});
reduce
메서드
reduce
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과를 누적하여 최종 결과를 반환합니다.
모든 요소에 대한 실행이 끝나면 reduce
메서드는 최종 결과를 반환합니다. 원래 배열은 변경되지 않습니다.
const array = [1, 2, 3, 4, 5];
// 배열의 모든 요소를 더한 값을 반환
const sum = array.reduce((accumulator, element) => accumulator + element, 0);
console.log(sum); // 출력: 15
reduce
메서드의 특징
reduce
메서드의 콜백 함수는 누적된 값(accumulator
)과 현재 요소(element
), 인덱스(index
), 그리고 배열 전체(array
)를 인수로 받습니다.
reduce
메서드의 초기값
reduce
메서드는 누적된 값의 초기값을 두 번째 인수로 받습니다. 생략한 경우 첫 번째 요소가 초기값이 되며, 두 번째 요소부터 반복이 시작됩니다.
const array = [1, 2, 3, 4, 5];
// 초기값을 지정하지 않은 경우
const sum = array.reduce((accumulator, element) => accumulator + element);
console.log(sum); // 출력: 15
// 초기값을 지정한 경우
const sumWithInitial = array.reduce((accumulator, element) => accumulator + element, 10);
console.log(sumWithInitial); // 출력: 25
reduce
메서드의 변형
array
가 배열일 때:
const result = array.reduce((accumulator, element) => {
<처리>
return accumulator;
}, <초기값>);
는 <처리>
가 accumulator
나 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let accumulator = <초기값>;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
<처리>
}
(i
, length
는 <처리>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let accumulator = <초기값>;
for (let element of array) {
<처리>
}
와도 거의 동일한 의미를 가집니다.
reduce
메서드로 객체를 구성하는 예제
const people = [
{ name: "Alice", age: 20 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 30 },
];
const peopleByName = people.reduce((acc, person) => {
acc[person.name] = person;
return acc;
}, {});
console.log(peopleByName);
// 출력: { Alice: { name: "Alice", age: 20 }, Bob: { name: "Bob", age: 25 }, Charlie: { name: "Charlie", age: 30 } }
reduce
메서드로 중첩된 배열을 평탄화하는 예제
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = nestedArray.reduce((acc, array) => acc.concat(array), []);
console.log(flattenedArray); // 출력: [1, 2, 3, 4, 5, 6]
reduce
메서드로 객체 배열에서 특정 프로퍼티의 합계를 구하는 예제
const orders = [
{ id: 1, amount: 250 },
{ id: 2, amount: 400 },
{ id: 3, amount: 100 },
{ id: 4, amount: 325 }
];
const totalAmount = orders.reduce((acc, order) => acc + order.amount, 0);
console.log(totalAmount); // 출력: 1075
reduce
메서드로 배열을 그룹화하는 예제
const people = [
{ name: "Alice", gender: "female" },
{ name: "Bob", gender: "male" },
{ name: "Eve", gender: "female" },
{ name: "David", gender: "male" },
];
const groupedByGender = people.reduce((acc, person) => {
const key = person.gender;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(person);
return acc;
}, {});
console.log(groupedByGender);
// 출력:
// {
// female: [{ name: "Alice", gender: "female" }, { name: "Eve", gender: "female" }],
// male: [{ name: "Bob", gender: "male" }, { name: "David", gender: "male" }]
// }
reduce
메서드로 배열의 중복 요소를 제거하는 예제
const array = [1, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9];
const uniqueArray = array.reduce((acc, element) => {
if (!acc.includes(element)) {
acc.push(element);
}
return acc;
}, []);
console.log(uniqueArray); // 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]
reduce
메서드로 배열의 최대값을 구하는 예제
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const maxValue = array.reduce((max, element) => (element > max ? element : max), array[0]);
console.log(maxValue); // 출력: 9
reduce
메서드를 사용하여 map
과 filter
를 구현하는 예제
map
메서드처럼 동작하는 mapWithReduce
함수:
const mapWithReduce = (array, callback) => {
return array.reduce((acc, element, index, array) => {
acc.push(callback(element, index, array));
return acc;
}, []);
};
const array = [1, 2, 3, 4, 5];
const doubled = mapWithReduce(array, (element) => element * 2);
console.log(doubled); // 출력: [2, 4, 6, 8, 10]
filter
메서드처럼 동작하는 filterWithReduce
함수:
const filterWithReduce = (array, callback) => {
return array.reduce((acc, element, index, array) => {
if (callback(element, index, array)) {
acc.push(element);
}
return acc;
}, []);
};
const evens = filterWithReduce(array, (element) => element % 2 === 0);
console.log(evens); // 출력: [2, 4]
reduce
메서드의 변형
array
가 배열일 때:
const result = array.reduce((acc, element) => {
<처리>
return acc;
}, <초기값>);
는 <처리>
가 acc
나 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let acc = <초기값>;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
<처리>
}
(i
, length
는 <처리>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let acc = <초기값>;
for (let element of array) {
<처리>
}
와도 거의 동일한 의미를 가집니다.
find
메서드
find
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과값이 true
인 첫 번째 요소를 반환합니다.
모든 요소에 대한 실행이 끝나거나 true
인 요소를 찾으면 find
메서드는 그 첫 번째 요소를 반환하고, 그렇지 않으면 undefined
를 반환합니다.
const array = [1, 2, 3, 4, 5];
// 배열에서 첫 번째로 짝수인 요소를 찾음
const firstEven = array.find((element) => element % 2 === 0);
console.log(firstEven); // 출력: 2
find
메서드의 특징
find
메서드는 첫 번째로 조건에 맞는 요소를 찾는 데 주안점을 둔 메서드입니다.
따라서 일반적으로 콜백 함수는 조건에 맞는 요소를 찾을 때 반환값을 true
로 설정하며, find
메서드의 반환값이 되는 요소를 사용해 후속 처리를 진행하는 경우가 많습니다.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
// ID가 2인 첫 번째 사용자를 찾음
const user = users.find((user) => user.id === 2);
console.log(user); // 출력: { id: 2, name: "Bob" }
find
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행하므로, 첫 번째로 조건에 맞는 요소를 찾은 경우 콜백 함수의 실행이 즉시 중단됩니다.
find
메서드의 변형
array
가 배열일 때:
const result = array.find((element) => <조건>);
는 <조건>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let result;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (<조건>) {
result = element;
break;
}
}
(i
, length
는 <조건>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let result;
for (let element of array) {
if (<조건>) {
result = element;
break;
}
}
와도 거의 동일한 의미를 가집니다.
findIndex
메서드
findIndex
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과값이 true
인 첫 번째 요소의 인덱스를 반환합니다.
모든 요소에 대한 실행이 끝나거나 true
인 요소를 찾으면 findIndex
메서드는 그 첫 번째 요소의 인덱스를 반환하고, 그렇지 않으면 -1
을 반환합니다.
const array = [1, 2, 3, 4, 5];
// 배열에서 첫 번째로 짝수인 요소의 인덱스를 찾음
const firstEvenIndex = array.findIndex((element) => element % 2 === 0);
console.log(firstEvenIndex); // 출력: 1
findIndex
메서드의 특징
findIndex
메서드는 첫 번째로 조건에 맞는 요소의 인덱스를 찾는 데 주안점을 둔 메서드입니다.
따라서 일반적으로 콜백 함수는 조건에 맞는 요소를 찾을 때 반환값을 true
로 설정하며, findIndex
메서드의 반환값이 되는 인덱스를 사용해 후속 처리를 진행하는 경우가 많습니다.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
// ID가 2인 첫 번째 사용자의 인덱스를 찾음
const userIndex = users.findIndex((user) => user.id === 2);
console.log(userIndex); // 출력: 1
findIndex
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행하므로, 첫 번째로 조건에 맞는 요소를 찾은 경우 콜백 함수의 실행이 즉시 중단됩니다.
findIndex
메서드의 변형
array
가 배열일 때:
const index = array.findIndex((element) => <조건>);
는 <조건>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let index = -1;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (<조건>) {
index = i;
break;
}
}
(i
, length
는 <조건>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let index = -1;
for (let [i, element] of array.entries()) {
if (<조건>) {
index = i;
break;
}
}
와도 거의 동일한 의미를 가집니다.
some
메서드
some
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과값이 true
인 요소가 하나라도 있으면 true
를 반환합니다.
모든 요소에 대한 실행이 끝나거나 true
인 요소를 찾으면 some
메서드는 true
를 반환하고, 그렇지 않으면 false
를 반환합니다.
const array = [1, 2, 3, 4, 5];
// 배열에 짝수가 하나라도 있는지 확인
const hasEven = array.some((element) => element % 2 === 0);
console.log(hasEven); // 출력: true
some
메서드의 특징
some
메서드는 배열에 하나라도 조건에 맞는 요소가 있는지 확인하는 데 주안점을 둔 메서드입니다.
따라서 일반적으로 콜백 함수는 조건에 맞는 요소를 찾을 때 반환값을 true
로 설정하며, some
메서드의 반환값을 사용해 후속 처리를 진행하는 경우가 많습니다.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
// 이름이 "Alice"인 사용자가 하나라도 있는지 확인
const hasAlice = users.some((user) => user.name === "Alice");
console.log(hasAlice); // 출력: true
some
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행하므로, 첫 번째로 조건에 맞는 요소를 찾은 경우 콜백 함수의 실행이 즉시 중단됩니다.
some
메서드의 변형
array
가 배열일 때:
const result = array.some((element) => <조건>);
는 <조건>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let result = false;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (<조건>) {
result = true;
break;
}
}
(i
, length
는 <조건>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let result = false;
for (let element of array) {
if (<조건>) {
result = true;
break;
}
}
와도 거의 동일한 의미를 가집니다.
every
메서드
every
메서드는 배열의 각 요소에 대해 순서대로 콜백 함수를 실행하고, 그 결과값이 true
인 요소가 전부일 때만 true
를 반환합니다.
모든 요소에 대한 실행이 끝나거나 false
인 요소를 찾으면 every
메서드는 false
를 반환합니다.
const array = [1, 2, 3, 4, 5];
// 배열의 모든 요소가 1보다 큰지 확인
const allGreaterThanOne = array.every((element) => element > 1);
console.log(allGreaterThanOne); // 출력: false
every
메서드의 특징
every
메서드는 배열의 모든 요소가 조건에 맞는지 확인하는 데 주안점을 둔 메서드입니다.
일반적으로 콜백 함수는 조건에 맞는 요소일 때 반환값을 true
로 설정하며, every
메서드의 반환값을 사용해 후속 처리를 진행하는 경우가 많습니다.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
// 모든 사용자가 18세 이상인지 확인
const allAdults = users.every((user) => user.age >= 18);
console.log(allAdults); // 출력: true
every
메서드는 모든 요소에 대해 콜백 함수를 순서대로 실행하므로, 첫 번째로 조건에 맞지 않는 요소를 찾은 경우 콜백 함수의 실행이 즉시 중단됩니다.
every
메서드의 변형
array
가 배열일 때:
const result = array.every((element) => <조건>);
는 <조건>
이 array
의 값을 변경하지 않는다면 다음과 거의 동일한 의미를 가집니다.
let result = true;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
if (!<조건>) {
result = false;
break;
}
}
(i
, length
는 <조건>
내부에서 참조되지 않는 변수명이어야 합니다.)
또한, array
가 밀집 배열일 경우:
let result = true;
for (let element of array) {
if (!<조건>) {
result = false;
break;
}
}
와도 거의 동일한 의미를 가집니다.
find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드
배열의 find
메서드는 배열의 각 요소에 대해 순차적으로 콜백 함수를 실행하고, 그 반환값이 truthy
하면 그 지점에서 반복을 종료하고 해당 요소의 값을 반환합니다.
truthy
한 반환값이 나오지 않으면 반복이 종료되어 undefined
를 반환합니다.
const array = [1, 2, 3, 4, 5];
const firstEven = array.find((element) => {
console.log("is even?", element);
return element % 2 === 0;
});
console.log(firstEven);
// 출력:
// is even? 1
// is even? 2
// 2
const firstNegative = array.find((element) => {
console.log("is negative?", element);
return element < 0;
});
console.log(firstNegative);
// 출력:
// is negative? 1
// is negative? 2
// is negative? 3
// is negative? 4
// is negative? 5
// undefined
배열의 findIndex
메서드는 find
메서드와 유사하지만, 콜백 함수가 truthy
한 값을 반환한 요소의 값 대신 그 요소의 인덱스를 반환합니다.
truthy
한 반환값이 나오지 않으면 반복이 종료되어 -1
을 반환합니다.
const array = [1, 2, 3, 4, 5];
const firstEvenIndex = array.findIndex((element) => {
console.log("is even?", element);
return element % 2 === 0;
});
console.log(firstEvenIndex);
// 출력:
// is even? 1
// is even? 2
// 1
const firstNegativeIndex = array.findIndex((element) => {
console.log("is negative?", element);
return element < 0;
});
console.log(firstNegativeIndex);
// 출력:
// is negative? 1
// is negative? 2
// is negative? 3
// is negative? 4
// is negative? 5
// -1
특히, 원래 배열이 빈 배열일 경우 콜백 함수는 호출되지 않으며, find
메서드는 항상 undefined
를, findIndex
메서드는 항상 -1
을 반환합니다.
findLast
메서드와 findLastIndex
메서드는 각각 find
메서드와 findIndex
메서드와 유사하지만, 배열의 끝에서 시작하여 앞으로 향해 반복한다는 점이 다릅니다.
const array = [1, 2, 3, 4, 5];
const lastEven = array.findLast((element) => {
console.log("is even?", element);
return element % 2 === 0;
});
console.log(lastEven);
// 출력:
// is even? 5
// is even? 4
// 4
const lastNegative = array.findLast((element) => {
console.log("is negative?", element);
return element < 0;
});
console.log(lastNegative);
// 출력:
// is negative? 5
// is negative? 4
// is negative? 3
// is negative? 2
// is negative? 1
// undefined
find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드의 특징
배열의 find
메서드와 findLast
메서드는 특정 조건을 만족하는 요소를 하나만 골라 그 요소의 값을 얻는 작업을 수행합니다. 그 조건은 콜백 함수를 사용해 자유롭게 지정할 수 있습니다.
find
메서드나 findLast
메서드는 배열의 요소 값뿐만 아니라 undefined
를 반환할 가능성도 있습니다.
find
메서드나 findLast
메서드가 undefined
를 반환하면 두 가지 가능성이 있습니다.
- 원래 배열에 조건을 만족하는 요소가 하나도 없었던 경우
- 배열의 요소 값으로
undefined
가 존재하며, 해당 요소가 조건을 만족한 경우
여기서 "요소"에는 프로퍼티가 존재하지 않는(빈) 인덱스도 포함됩니다. 콜백 함수에서 지정한 조건을 undefined
가 반드시 충족하지 않는다면, 후자의 가능성을 배제할 수 있으며, 원래 배열에 조건을 만족하는 요소가 없었다는 것을 의미합니다.
배열의 findIndex
메서드와 findLastIndex
메서드는 특정 조건을 만족하는 요소를 하나만 골라 그 요소의 인덱스를 얻는 작업을 수행합니다.
그 조건은 콜백 함수를 사용해 자유롭게 지정할 수 있습니다.
findIndex
메서드나 findLastIndex
메서드가 -1
을 반환하면, 원래 배열에 조건을 만족하는 요소가 하나도 없었다는 것을 의미합니다.
콜백 함수가 truthy
한 값을 반환하면 그 시점에서 반복이 종료되고, 해당 요소의 값 또는 인덱스가 반환됩니다.
이 동작은 for
문에서의 break
문 또는 return
문에 해당합니다.
이를 통해 긴 배열이라도 앞부분에 조건을 충족하는 요소가 있다면 나머지 요소에 대한 반복을 생략하여 효율적으로 요소를 찾을 수 있습니다.
현재 반복을 중단하고 다음 반복으로 넘어가려면(for
문에서의 continue
문에 해당) return
문으로 falsy
한 값을 반환합니다.
조건을 만족하는 요소의 값 또는 인덱스에 관심이 없고, 조건을 만족하는 요소가 존재하는지만 알고 싶다면 some
메서드나 every
메서드를 사용할 수 있습니다.
반복 대상 인덱스
find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드에 전달하는 콜백 함수에서 비동기로 진리값을 반환할 수 없습니다.
이유는 filter
메서드와 동일합니다. 대신 for
문과 await
연산자를 사용한 순차 반복 처리를 할 수 있습니다.
콜백 함수의 세 번째 인자는 이들 메서드를 메서드 체인에서 호출할 때 유용할 수 있습니다(forEach
와 유사).
find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드의 두 번째 인자로 콜백 함수 내에서 this
로 사용될 값을 지정할 수 있습니다(forEach
와 유사).
배열의 find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드는 배열과 유사한 객체에서도 호출할 수 있습니다.
find
메서드, findIndex
메서드, findLast
메서드, findLastIndex
메서드의 대체 구현
array
가 배열일 때,
let result = array.find((element) => {
const condition = <조건>;
return condition;
});
는 <조건>
이 array
값을 변경하지 않는다면
let result = undefined;
for (let i = 0, length = +array.length; i < length; i++) {
let element = array[i];
const condition = <조건>;
if (condition) {
result = element;
break;
}
}
와 거의 같은 의미가 됩니다(i
와 length
는 <조건>
안에서 참조되지 않는 변수명으로 해야 함).
또한,
let result = undefined;
for (let element of array) {
const condition = <조건>;
if (condition) {
result = element;
break;
}
}
와도 거의 같은 의미가 됩니다(find
메서드 실행 중에 array
의 길이가 늘어난 경우 추가된 인덱스도 반복된다는 차이점이 있음).
array
가 배열일 때,
let result = array.findIndex((element) => {
const condition = <조건>;
return condition;
});
는 <조건>
이 array
값을 변경하지 않는다면
let result = -1;
for (let i = 0, length = +array.length; i < length; i++) {
let element = array[i];
const condition = <조건>;
if (condition) {
result = i;
break;
}
}
와 거의 같은 의미가 됩니다(i
와 length
는 <조건>
안에서 참조되지 않는 변수명으로 해야 함).
또한,
let result = -1;
for (let [i, element] of array.entries()) {
const condition = <조건>;
if (condition) {
result = i;
break;
}
}
와도 거의 같은 의미가 됩니다(findIndex
메서드 실행 중에 array
의 길이가 늘어난 경우 추가된 인덱스도 반복된다는 차이점이 있음).
array
가 배열일 때,
let result = array.findLast((element) => {
const condition = <조건>;
return condition;
});
는 비슷하게
let result = undefined;
for (let i = array.length - 1; i >= 0; i--) {
let element = array[i];
const condition = <조건>;
if (condition) {
result = element;
break;
}
}
와 거의 같은 의미가 되며,
let result = array.findLastIndex((element) => {
const condition = <조건>;
return condition;
});
는
let result = -1;
for (let i = array.length - 1; i >= 0; i--) {
let element = array[i];
const condition = <조건>;
if (condition) {
result = i;
break;
}
}
와 거의 같은 의미가 됩니다(i
는 <조건>
안에서 참조되지 않는 변수명으로 해야 함).
특정 값을 찾는 패턴의 대체 구현
빽빽한 배열 array
에서 특정 값을 가진 요소의 인덱스를 찾을 때,
const search = <값>;
const index = array.findIndex((element) => element === search);
는
const search = <값>;
const index = array.indexOf(search);
// 또는
const index = array.indexOf(<값>);
와 거의 같은 의미가 되며,
const search = <값>;
const index = array.findLastIndex((element) => element === search);
는
const search = <값>;
const index = array.lastIndexOf(search);
// 또는
const index = array.lastIndexOf(<값>);
와 거의 같은 의미가 됩니다.
findIndex
메서드나 findLastIndex
메서드를 사용할 때 <값>
이 배열의 각 요소에 대해 반복적으로 평가되는 것을 피하기 위해 위와 같은 코드 예제에서처럼 콜백 함수 밖에서 변수 search
에 할당하는 경우가 있지만, indexOf
메서드나 lastIndexOf
메서드를 사용하면 <값>
을 직접 인자로 전달할 수 있습니다.
flatMap
메서드
배열의 flatMap
메서드는 배열의 각 요소에 대해 순차적으로 콜백 함수를 실행하여, 반환값이 배열이라면 해당 배열의 요소를, 배열이 아닌 값이라면 그 값을 그대로 수집합니다.
모든 요소에 대해 실행이 완료되면 flatMap
메서드는 수집한 값들로 구성된 새로운 배열을 반환합니다. 원래 배열은 변경되지 않습니다.
const array = [1, 2, 3];
// 각 요소의 값을 그 숫자만큼 반복
const repeated = array.flatMap((element) => new Array(element).fill(element));
console.log(repeated); // 출력: [ 1, 2, 2, 3, 3, 3 ]
// 원래 배열은 변경되지 않음
console.log(array); // 출력: [ 1, 2, 3 ]
flatMap
메서드의 특징
배열의 flatMap
메서드는 map
메서드와 flat
메서드를 결합한 작업을 수행합니다. flat
메서드는 2차원 배열을 1차원 배열로 평탄화하는 작업을 수행합니다.
const array = [1, 2, 3];
const array2 = array.map((element) => new Array(element).fill(element)); // [ [ 1 ], [ 2, 2 ], [ 3, 3, 3 ] ]
const flat = array2.flat(); // [ 1, 2, 2, 3, 3, 3 ] (`flatMap` 메서드의 결과와 동일)
배열의 map
메서드에서는 원래 배열과 결과 배열의 길이가 같으며, 요소가 1:1로 대응됩니다. 반면에 flatMap
메서드에서는 원래 배열의 한 요소에 대해 결과 배열에 여러 요소를 생성하거나, 결과 배열에 요소를 포함하지 않는(0개의 요소를 생성하는) 경우도 가능합니다.
const books = [
{ title: "A", author: "X", tags: ["a", "b"] },
{ title: "B", author: "Y", tags: ["b", "c"] },
{ title: "C", author: "Z", tags: ["c", "d"] },
];
// 태그 "b"를 포함하는 각 책에 대해 제목과 저자를 생성
const result = books.flatMap((book) => {
if (!book.tags.includes("b")) {
return []; // 빈 배열 반환(0개의 요소 생성)
}
// 길이 2의 배열 반환(2개의 요소 생성)
return [
book.title,
`by ${book.author}`,
];
});
console.log(result); // 출력: [ 'A', 'by X', 'B', 'by Y' ]
배열이 아닌 값의 처리
콜백 함수의 반환값이 배열이 아닌 배열과 유사한 객체인 경우, 해당 배열과 유사한 객체는 평탄화되지 않고 결과 배열의 한 요소로 포함됩니다.
평탄화의 깊이
flatMap
메서드는 기본적으로 한 단계만 평탄화합니다. 더 깊은 평탄화가 필요하다면 flat
메서드를 조합하는 것이 좋습니다.
반복 처리
콜백 함수 내에서 반복 전체를 중단하고 바로 반복 이후로 이동하는 수단이 없다는 점은 forEach
메서드와 유사합니다.
비동기 콜백 함수
flatMap
메서드의 콜백 함수에 비동기 처리를 포함할 수는 없지만, for
문과 await
연산자를 사용해 순차 반복 처리를 할 수 있습니다.
메서드 체인에서 유용한 세 번째 인자
콜백 함수의 세 번째 인자는 메서드 체인에서 flatMap
메서드를 호출할 때 유용할 수 있습니다(forEach
처럼).
flatMap
메서드의 this
지정
flatMap
메서드의 두 번째 인자는 콜백 함수 내에서 this
로 사용될 값을 지정할 수 있습니다(forEach
와 유사).
배열과 유사한 객체에 대한 호출
배열의 flatMap
메서드는 배열과 유사한 객체에서도 호출할 수 있습니다. 반환값은 배열입니다.
flatMap
메서드의 대체 구현
array
가 배열일 때,
const newArray = array.flatMap((element) => {
const result = <식>;
return result;
});
는 <식>
이 배열의 값을 변경하지 않는다면
const newArray = [];
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let element = array[i];
const result = <식>;
newArray.push(...[result].flat());
}
와 거의 같은 의미가 됩니다(i
와 length
는 <식>
안에서 참조되지 않는 변수명으로 해야 함).
또한, array
가 빽빽한 배열일 때,
const newArray = [];
for (let element of array) {
const result = <식>;
newArray.push(...[result].flat());
}
와도 거의 같은 의미가 됩니다(flatMap
메서드 실행 중에 array
의 길이가 늘어나면 추가된 인덱스도 반복된다는 차이점이 있음).
특히 <식>
의 값이 반드시 빽빽한 배열이 되는 경우
newArray.push(...[result].flat());
부분은
newArray.push(...result);
로 바꿀 수 있습니다.
array
가 빽빽한 배열이고 <식>
의 값이 반드시 배열이 아닌 값이 되는 경우 flatMap
메서드 대신 map
메서드를 사용해도 대부분의 경우 동일한 결과를 얻을 수 있습니다.
array
가 배열일 때, 콜백 함수가 항등 함수라면
const newArray = array.flatMap((element) => element);
는
const newArray = array.flat();
로 바꿀 수 있습니다.
reduce
메서드, reduceRight
메서드
배열의 reduce
메서드는 지금까지 소개한 메서드와 달리 콜백 함수에 4개의 인자를 전달합니다.
- 누적값: 이전 반복에서의 계산 결과
- 현재 요소의 값: 현재 반복에서 대상이 되는 요소의 값
- 현재 인덱스: 현재 반복에서 대상이 되는 요소의 인덱스
- 배열: 반복 대상인 배열 전체
또한 reduce
메서드의 두 번째 인자로 누적값의 초기값을 전달할 수 있습니다.
reduce
메서드는 배열의 각 요소에 대해 순차적으로 콜백 함수를 실행합니다.
콜백 함수의 반환값은 다음 반복에서 콜백 함수에 전달되는 누적값이 됩니다.
마지막 반복이 끝나면 reduce
메서드의 반환값이 됩니다.
const array = ["Alice", "Bob", "Charlie"];
/** 배열의 각 요소 값인 문자열 길이의 합을 구함 */
const totalLength = array.reduce(
// 누적값 `acc`는 지금까지 처리한 문자열 길이의 합(누적 합)
(acc, element, i) => {
console.log("acc:", acc, "element:", element, "index:", i);
// 현재 요소 값 `element`의 길이를 누적값에 더해 반환
return acc + element.length;
},
0 // 누적값의 초기값은 0
);
console.log(totalLength);
// 출력:
// acc: 0 element: Alice index: 0
// acc: 5 element: Bob index: 1
// acc: 8 element: Charlie index: 2
// 15
reduce
메서드의 두 번째 인자를 생략하면 배열의 첫 번째 요소 값이 누적값의 초기값으로 사용되고, 해당 첫 번째 요소는 반복을 건너뜁니다. (두 번째 인자로 undefined
를 지정한 경우 생략하지 않은 것으로 간주)
const array = [1, 2, 3, 4, 5];
/** 숫자로 구성된 배열 요소 값의 합을 구함 */
const sum = array.reduce((acc, element, i) => {
console.log("acc:", acc, "element:", element, "index:", i);
return acc + element;
});
console.log(sum);
// 출력:
// acc: 1 element: 2 index: 1
// acc: 3 element: 3 index: 2
// acc: 6 element: 4 index: 3
// acc: 10 element: 5 index: 4
// 15
원래 배열이 빈 배열일 경우 콜백 함수는 호출되지 않으며 reduce
메서드는 두 번째 인자를 그대로 반환합니다.
이때 두 번째 인자가 생략되었다면 런타임 오류가 발생합니다.
원래 배열이 한 요소만 가지고 있고 reduce
메서드의 두 번째 인자가 생략된 경우 콜백 함수는 호출되지 않으며, 그 한 요소의 값이 reduce
메서드의 반환값이 됩니다.
배열의 reduceRight
메서드는 reduce
메서드와 유사하지만 반복 순서가 반대(끝에서 시작하여 처음으로 진행)라는 점이 다릅니다.
콜백 함수의 인자 순서는 reduce
메서드와 동일하며, 누적값이 첫 번째 인자가 됩니다.
const operands = [2, 3, 3];
// 2의 (3의 3제곱) 제곱을 계산
const result = operands.reduceRight(
(acc, element, i) => {
console.log("acc:", acc, "element:", element, "index:", i);
return element ** acc;
},
1
);
console.log(result);
// 출력:
// acc: 1 element: 3 index: 2
// acc: 3 element: 3 index: 1
// acc: 27 element: 2 index: 0
// 134217728
reduceRight
메서드의 두 번째 인자를 생략하면 배열의 마지막 요소 값이 누적값의 초기값으로 사용되고, 해당 마지막 요소는 반복을 건너뜁니다.
reduce
메서드, reduceRight
메서드의 특징
배열의 reduce
메서드와 reduceRight
메서드는 다수의 요소를 하나의 값으로 합치는 작업을 수행합니다.
함수형 프로그래밍에서는 이를 폴드(fold)라고 부릅니다.
콜백 함수가 첫 번째 인자 a
와 두 번째 인자 b
를 받아 a⋅b
를 반환한다면(⋅
는 임의의 이항 연산), 배열 요소 값을 a0, ..., an-1
로, 누적값의 초기값(각 메서드에 전달되는 두 번째 인자)을 e
로 표기하면,
reduce
메서드의 반환값은(⋯((e⋅a0)⋅a1)⋯)⋅an-1
reduceRight
메서드의 반환값은(⋯((e⋅an-1)⋅an-2)⋯)⋅a0
이 됩니다.
reduceRight
메서드에 전달되는 콜백 함수의 첫 번째 인자와 두 번째 인자를 반대로 하면 위 식은 좌우 반전된 형태가 되며, 배열 요소 값이 왼쪽에서 오른쪽 순서로 나열되고 이항 연산에 의해 오른쪽에서 왼쪽으로 접혀 들어가는 것을 알 수 있습니다.
배열 요소 값이 속한 집합 S
에 이항 연산 ⋅: S × S → S
와 항등원 e
가 모노이드를 이루는 경우, reduce
메서드와(콜백 함수의 첫 번째 인자와 두 번째 인자를 뒤집은)
reduceRight` 메서드의 반환값은 동일해집니다.
예를 들어 문자열의 집합에서 문자열 결합 (a, b) => a + b
와 빈 문자열 ""
은 모노이드를 이룹니다.
const array = ["a", "b", "c"];
const leftReduced = array.reduce((a, b) => a + b, "");
console.log(leftReduced); // 출력: abc
const rightReduced = array.reduceRight((b, a) => a + b, "");
console.log(rightReduced); // 출력: abc
각 반복에서 콜백 함수가 반환한 값은 다음 반복에서 동일한 콜백 함수에 첫 번째 인자로 전달됩니다.
또한 reduce
메서드나 reduceRight
메서드에 전달되는 두 번째 인자도 첫 번째 반복에서 콜백 함수에 첫 번째 인자로 전달되는 값이 됩니다.
따라서 reduce
메서드나 reduceRight
메서드의 두 번째 인자를 지정할 때 일반적으로 아래 3곳이 같은 종류의 값이 되거나 같은 종류의 값을 예상해야 합니다.
- 콜백 함수가 반환하는 값(그 반복까지의 누적값)
reduce
메서드나reduceRight
메서드에 전달되는 두 번째 인자(누적값의 초기값)- 콜백 함수가 받는 첫 번째 인자(이전 반복까지의 누적값)
이 세 곳에 "배열 요소 값"이나 "콜백 함수가 받는 두 번째 인자"가 포함되지 않는다는 점은 주의하세요.
즉, 배열 요소 값과는 다른 종류의 값을 누적값으로 설정할 수 있습니다.
예를 들어 첫 번째 코드 예제 "배열의 각 요소 값인 문자열 길이의 합을 구함"에서는 배열 요소 값이 문자열이었지만, 위 3곳의 누적값은 문자열 길이의 합을 나타내는 숫자였습니다.
누적값 설계에 자유도가 있기 때문에 reduce
메서드나 reduceRight
메서드는 다양한 작업을 수행할 수 있는 만능 도구가 됩니다.
지금까지 소개한 대부분의 반복을 위한 메서드는 reduce
메서드나 reduceRight
메서드를 사용해도 거의 동일한 처리를 할 수 있습니다. (하지만 전용 메서드를 사용하는 것이 효율성, 가독성, 코드량 측면에서 더 뛰어난 경우가 많습니다.)
각종 메서드를 reduce
메서드로 구현하기
reduce
메서드나 reduceRight
메서드의 두 번째 인자를 생략하는 경우, 첫 번째 반복에서 첫 번째 인자로 배열의 첫 번째 요소 값이 전달되므로 배열 요소 값이 모두 같은 종류라면 일반적으로 누적값도 배열 요소 값과 같은 종류로 설정됩니다.
콜백 함수 내에서 반복 전체를 중단하고 바로 반복 이후로 이동하는 수단이 없다는 점은 forEach
메서드와 유사합니다.
현재 반복을 중단하고 다음 반복으로 넘어가려면(for
문에서의 continue
문에 해당) return
문을 사용합니다.
첫 번째 인자로 전달된 값을 그대로 반환함으로써 현재 반복이 건너뛰어졌음을 나타낼 수 있습니다.
반복 대상 인덱스
reduce
메서드나 reduceRight
메서드의 두 번째 인자를 생략하고 호출하면 배열의 길이가 0이거나 배열의 인덱스가 모두 존재하지 않을 경우 런타임 오류(TypeError
)가 발생합니다.
콜백 함수의 네 번째 인자는 메서드 체인에서 이들 메서드를 호출할 때 유용할 수 있습니다(forEach
등의 콜백 함수의 세 번째 인자와 유사).
reduce
메서드, reduceRight
메서드의 대체 구현
array
가 배열일 때,
let result = array.reduce(
(acc, element) => {
const value = <식>;
return value;
},
<초기값>
);
는 <식>
이 배열의 값을 변경하지 않는다면
let result = <초기값>;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
let acc = result;
let element = array[i];
const value = <식>;
result = value;
}
와 거의 같은 의미가 됩니다(i
와 length
는 <식>
안에서 참조되지 않는 변수명으로 해야 함).
또한, reduce
메서드의 두 번째 인자를 생략한 경우
let result = array.reduce(
(acc, element) => {
const value = <식>;
return value;
}
);
는 <식>
이 배열의 값을 변경하지 않는다면
let initialized = false;
let result;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
if (!initialized) {
result = array[i];
initialized = true;
continue;
}
let acc = result;
let element = array[i];
const value = <식>;
result = value;
}
if (!initialized) throw new TypeError();
와 거의 같은 의미가 됩니다(i
와 length
는 <식>
안에서 참조되지 않는 변수명으로 하고, initialized
는 이 스코프에 정의되지 않은 변수명으로 해야 함).
array
가 배열일 때,
let result = array.reduceRight(
(acc, element) => {
const value = <식>;
return value;
},
<초기값>
);
는 <식>
이 배열의 값을 변경하지 않는다면
let result = <초기값>;
for (let i = +array.length - 1; i >= 0; i--) {
if (!(i in array)) continue;
let acc = result;
let element = array[i];
const value = <식>;
result = value;
}
와 거의 같은 의미가 됩니다(i
는 <식>
안에서 참조되지 않는 변수명으로 해야 함).
또한, reduceRight
메서드의 두 번째 인자를 생략한 경우
let result = array.reduceRight(
(acc, element) => {
const value = <식>;
return value;
}
);
는 <식>
이 배열의 값을 변경하지 않는다면
let initialized = false;
let result;
for (let i = +array.length - 1; i >= 0; i--) {
if (!(i in array)) continue;
if (!initialized) {
result = array[i];
initialized = true;
continue;
}
let acc = result;
let element = array[i];
const value = <식>;
result = value;
}
if (!initialized) throw new TypeError();
와 거의 같은 의미가 됩니다(i
는 <식>
안에서 참조되지 않는 변수명으로 하고, initialized
는 이 스코프에 정의되지 않은 변수명으로 해야 함).
includes 메소드
배열의 includes
메소드는 지정된 값을 배열의 각 요소와 비교하여, 동일한 요소가 있으면 true
를 반환합니다.
어떤 요소와도 값이 같지 않으면 false
를 반환합니다.
이 메소드에는 콜백 함수를 전달할 수 없습니다. 대신, 첫 번째 인수로 비교할 값을 지정합니다.
const array = ["a", "b", "c", "d", "e"];
console.log(array.includes("c")); // true
console.log(array.includes("f")); // false
두 번째 인수로는 비교를 시작할 인덱스를 지정할 수 있습니다. 기본값은 0이며, 모든 요소가 비교됩니다.
이 인수에 음수를 지정하면 배열의 끝에서부터 셉니다.
const array = ["a", "b", "c", "d", "e"];
console.log(array.includes("c", 2)); // true (인덱스 2의 요소가 "c"이므로 true)
console.log(array.includes("c", 3)); // false (인덱스 3 이후에 "c"는 없으므로 false)
console.log(array.includes("c", -3)); // true (끝에서 세어 3번째 = 인덱스 2부터 비교 시작)
console.log(array.includes("c", -2)); // false (끝에서 세어 2번째 = 인덱스 3부터 비교 시작)
includes 메소드의 특징
배열의 includes
메소드는 콜백 함수를 받지 않기 때문에 반복 처리를 직접 지정할 수 없습니다.
대신, 자주 사용되는 유형의 반복 처리에 특화된 메소드로 이해할 수 있습니다.
includes
메소드는 지정된 값이 배열에 존재하는지 여부를 확인하는 데 사용됩니다.
빈 배열에 대해 includes
메소드를 호출하면 항상 false
를 반환합니다.
배열의 includes
메소드는 동일한지 여부를 판단할 때 SameValueZero
추상 연산을 사용합니다.
const sameValueZero = (a, b) => a === b || (a !== a && b !== b);
SameValueZero 추상 연산
비교할 인덱스의 범위는 배열의 includes
메소드가 배열 비슷한 객체에도 호출될 수 있습니다.
includes
메소드는 콜백 함수를 받지 않기 때문에, (처리에 걸리는 시간 등을 제외하고) 반복이 수행되었는지 또는 반복 순서를 코드에서 관찰할 수 없는 경우가 있습니다.
처리 시스템은 그러한 경우에 기본 반복 대신 고속 알고리즘을 사용하는 등의 최적화를 수행할 수 있습니다.
includes 메소드의 재작성
여기서 ===
연산자를 사용하는 부분은 NaN
에 대한 비교에서 동작이 일치하지 않습니다.
엄밀한 재작성에는 앞서 언급한 sameValueZero
함수를 사용해야 합니다.
배열이 array
일 때,
const search = <value>;
let result = array.includes(search);
이는
const search = <value>;
let result = array.findIndex((element) => element === search) !== -1;
또는
const search = <value>;
let result = false;
for (let i = 0, length = +array.length; i < length; i++) {
if (array[i] === search) {
result = true;
break;
}
}
또는
const search = <value>;
let result = false;
for (const element of array) {
if (element === search) {
result = true;
break;
}
}
와 같은 의미입니다.
배열이 밀집 배열일 때,
const search = <value>;
const result = array.includes(search);
는
const search = <value>;
const result = array.some((element) => element === search);
와 같은 의미입니다.
배열이 밀집 배열일 때,
const result = array.includes(<value>, <startIndex>);
에서 <value>
가 NaN
이 아닌 경우
const result = array.indexOf(<value>, <startIndex>) !== -1;
와 같은 의미입니다. (<startIndex>
는 생략 가능합니다)
indexOf 메소드, lastIndexOf 메소드
배열의 indexOf
메소드는 지정된 값을 배열의 각 요소와 순서대로 비교하여, 동일한 요소가 있으면 그 인덱스를 반환합니다. 어떤 요소와도 값이 같지 않으면 -1
을 반환합니다.
이 메소드에는 콜백 함수를 전달할 수 없습니다. 대신, 첫 번째 인수로 비교할 값을 지정합니다.
const array = ["a", "b", "c", "b", "a"];
console.log(array.indexOf("b")); // 1
console.log(array.indexOf("d")); // -1
동일한 요소가 여러 개 있을 경우, 가장 앞에 있는 요소의 인덱스가 반환됩니다.
두 번째 인수로는 비교를 시작할 인덱스를 지정할 수 있습니다. 기본값은 0이며, 모든 요소가 비교됩니다.
이 인수에 음수를 지정하면 배열의 끝에서부터 셉니다. (includes
메소드와 동일)
배열의 lastIndexOf
메소드는 indexOf
메소드와 유사하지만, 배열의 끝에서부터 역순으로 비교한다는 점이 다릅니다.
따라서, 동일한 요소가 여러 개 있을 경우, 가장 뒤에 있는 요소의 인덱스가 반환됩니다.
const array = ["a", "b", "c", "b", "a"];
console.log(array.lastIndexOf("b")); // 3
console.log(array.lastIndexOf("d")); // -1
두 번째 인수로는 비교를 시작할 인덱스를 지정할 수 있습니다.
기본값은 배열의 길이 - 1이며, 모든 요소가 비교됩니다.
이 인덱스부터 시작하여 앞쪽으로 비교가 이루어집니다. 이 인수에 음수를 지정하면 배열의 끝에서부터 셉니다.
const array = ["a", "b", "c", "b", "a"];
console.log(array.lastIndexOf("b", 1)); // 1 (인덱스 1의 요소가 "b")
console.log(array.lastIndexOf("b", 0)); // -1 (인덱스 0까지 "b"는 없음)
console.log(array.lastIndexOf("b", -4)); // 1 (끝에서 세어 4번째 = 인덱스 1부터 시작)
console.log(array.lastIndexOf("b", -5)); // -1 (끝에서 세어 5번째 = 인덱스 0부터 시작)
indexOf 메소드, lastIndexOf 메소드의 특징
배열의 indexOf
메소드와 lastIndexOf
메소드는 콜백 함수를 받지 않기 때문에 반복 처리를 직접 지정할 수 없습니다.
대신, 자주 사용되는 유형의 반복 처리에 특화된 메소드로 이해할 수 있습니다.
indexOf
메소드와 lastIndexOf
메소드는 지정된 값이 배열에 존재하는지 여부 및 어느 위치에 존재하는지를 확인하는 데 사용됩니다.
빈 배열에 대해 indexOf
메소드나 lastIndexOf
메소드를 호출하면 항상 -1
을 반환합니다.
배열의 indexOf
메소드와 lastIndexOf
메소드는 동일한지 여부를 판단할 때 ===
연산자와 동일한 IsStrictlyEqual
추상 연산을 사용합니다.
IsStrictlyEqual 추상 연산
비교할 인덱스의 범위는 배열의 indexOf
메소드와 lastIndexOf
메소드가 배열 비슷한 객체에도 호출될 수 있습니다.
indexOf
메소드나 lastIndexOf
메소드는 콜백 함수를 받지 않기 때문에, (처리에 걸리는 시간 등을 제외하고) 반복이 수행되었는지 또는 반복 순서를 코드에서 관찰할 수 없는 경우가 있습니다.
처리 시스템은 그러한 경우에 기본 반복 대신 고속 알고리즘을 사용하는 등의 최적화를 수행할 수 있습니다.
indexOf 메소드, lastIndexOf 메소드의 재작성
배열이 array
일 때,
const search = <value>;
let result = array.indexOf(search);
는
const search = <value>;
let result = -1;
for (let i = 0, length = +array.length; i < length; i++) {
if (!(i in array)) continue;
if (array[i] === search) {
result = i;
break;
}
}
와 같은 의미이며,
const search = <value>;
let result = array.lastIndexOf(search);
는
const search = <value>;
let result = -1;
for (let i = +array.length - 1; i >= 0; i--) {
if (!(i in array)) continue;
if (array[i] === search) {
result = i;
break;
}
}
와 같은 의미입니다.
배열이 밀집 배열일 때,
const search = <value>;
let result = array.indexOf(search);
는
const search = <value>;
let result = array.findIndex((element) => element === search);
또는
const search = <value>;
let result = false;
for (const element of array) {
if (element === search) {
result = true;
break;
}
}
와 같은 의미이며,
const search = <value>;
let result = array.lastIndexOf(search);
는
const search = <value>;
let result = array.findLastIndex((element) => element === search);
와 같은 의미입니다.
존재 여부 판단의 재작성
배열이 밀집 배열일 때,
const search = <value>;
let result = array.includes(search);
는
const search = <value>;
let result = array.some((element) => element === search);
와 같은 의미입니다.
entries 메소드, keys 메소드, values 메소드, Symbol.iterator 메소드
배열의 이 메소드들은 단독으로 반복을 수행하지 않지만, 반복 처리에서 중요한 역할을 하기 때문에 소개합니다.
이 메소드들은 공통적으로 인수를 받지 않으며, 반환 값으로 배열 이터레이터 객체를 반환합니다.
배열 이터레이터는 이터레이터의 한 종류로, 이를 이용해 배열의 요소 값을 순차적으로 얻을 수 있습니다.
이터레이터에서 값을 순차적으로 꺼내기 위해서는 이터레이터의 next
메소드를 호출합니다.
next
메소드는 값을 생성할 때 { value: <value>, done: false }
형태의 객체를 반환하며, 더 이상 생성할 값이 없을 때는 { value: <returnValue>, done: true }
형태의 객체를 반환합니다. <returnValue>
는 반복의 대상이 아니며, 배열 이터레이터의 경우 undefined
가 됩니다.
이터레이터는 그 자체로 반복 가능한 객체이기 때문에, 이터레이터 it
이 값을 유한 개 생성하고 종료하는 경우, 표현식 [...]
으로 생성된 값을 하나의 배열로 모을 수 있습니다.
배열의 entries
메소드는 배열의 각 요소에 대해 인덱스와 값의 쌍(두 개의 요소를 가진 배열)을 순차적으로 생성하는 이터레이터를 반환합니다.
const array = ["a", "b", "c"];
const it = array.entries();
console.log([...it]); // 출력: [ [ 0, 'a' ], [ 1, 'b' ], [ 2, 'c' ] ]
배열의 keys
메소드는 배열의 각 요소의 인덱스를 순차적으로 생성하는 이터레이터를 반환합니다.
const array = ["a", "b", "c"];
const it = array.keys();
console.log([...it]); // 출력: [ 0, 1, 2 ]
배열의 values
메소드는 배열의 각 요소의 값을 순차적으로 생성하는 이터레이터를 반환합니다.
const array = ["a", "b", "c"];
const it = array.values();
console.log([...it]); // 출력: [ 'a', 'b', 'c' ]
배열의 Symbol.iterator
메소드는 values
메소드와 동일합니다.
const array = ["a", "b", "c"];
const it = array[Symbol.iterator]();
console.log([...it]); // 출력: [ 'a', 'b', 'c' ]
console.log(array.values === array[Symbol.iterator]); // 출력: true
entries 메소드, keys 메소드, values 메소드, Symbol.iterator 메소드의 특징
배열의 entries
메소드는 요소의 인덱스와 값을 모두 사용하는 반복 처리에 유용합니다.
keys
메소드는 인덱스만 사용하는 반복 처리에 유용합니다.
특히, for-of
문에서 배열을 반복 대상으로 하면 배열의 요소 값만 추출됩니다.
entries
메소드를 사용하면 인덱스와 값을 모두 추출하는 반복 처리를 할 수 있습니다.
또는 keys
메소드를 사용하면 인덱스만 추출하는 반복 처리를 할 수 있습니다.
const array = ["a", "b", "c"];
for (const [index, value] of array.entries()) {
console.log(index, value);
}
// 출력:
// 0 a
// 1 b
// 2 c
for (const index of array.keys()) {
console.log(index);
}
// 출력:
// 0
// 1
// 2
배열 이외의 객체에도 동일한 메소드가 존재할 수 있습니다.
예를 들어, Map
과 Set
에도 entries
메소드, keys
메소드, values
메소드가 있습니다.
해당 객체에 포함된 요소 값을 하나씩 추출하기 위한 통일된 방법으로 이 메소드들이 제공됩니다.
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (const [key, value] of map.entries()) {
console.log(key, value);
}
// 출력:
// a 1
// b 2
// c 3
const set = new Set(["a", "b", "c"]);
for (const [key, value] of set.entries()) {
console.log(key, value);
}
// 출력:
// a a
// b b
// c c
values
메소드는 이러한 통일된 방법의 일부로 배열에도 편의상 제공된 것입니다.
values
메소드를 호출하는 객체가 배열임을 알고 있는 경우 굳이 사용할 필요는 없습니다.
한편 배열의 Symbol.iterator
메소드는 배열이 반복 가능한 객체임을 위해 필요한 메소드입니다.
for-of
문이나 스프레드 구문 등에서 배열을 반복 처리할 때는 암묵적으로 이 메소드가 호출됩니다.
for-of
문에서 배열의 요소 값이 순차적으로 추출되는 이유는 배열의 Symbol.iterator
메소드가 배열의 요소 값을 순차적으로 생성하는 이터레이터를 반환하기 때문입니다.
배열 이터레이터는 next
메소드가 호출될 때마다 배열의 길이를 확인하여 종료를 판단합니다.
따라서, 반복 중에 배열의 길이가 증가하거나 감소하더라도 배열의 끝까지 올바르게 값을 생성합니다.
배열의 프로퍼티가 존재하지 않는 (빈) 인덱스도 반복 대상이 됩니다.
이 경우 값은 undefined
가 됩니다.
const array = ["a", , "c"];
const it = array.entries();
console.log([...it]); // 출력: [ [ 0, 'a' ], [ 1, undefined ], [ 2, 'c' ] ]
배열의 entries
메소드, keys
메소드, values
메소드, Symbol.iterator
메소드는 배열 비슷한 객체에도 호출될 수 있습니다.
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const it = Array.prototype.entries.call(arrayLike);
console.log([...it]); // 출력: [ [ 0, 'a' ], [ 1, 'b' ], [ 2, 'c' ] ]
이상으로 배열의 includes
, indexOf
, lastIndexOf
메소드와 반복 관련 메소드들에 대해 알아보았습니다.
이 메소드들은 자주 사용되는 메소드들이며, 배열의 요소를 효율적으로 찾거나 반복하는 데 매우 유용합니다.
따라서, 이러한 메소드들의 동작 원리와 특징을 잘 이해하고 사용하는 것이 중요합니다.
Array.from 메서드
Array
생성자의 from
메서드는 반복 가능한 객체나 배열과 유사한 객체를 새로운 배열로 변환하여 반환합니다.
이 메서드는 배열 메서드가 아니라는 점을 주의하세요. Array.from(o)
형태로 호출됩니다.
이 메서드는 하나의 필수 인자와 두 개의 선택적 인자를 받습니다.
필수 인자인 첫 번째 인자에는 변환 대상인 반복 가능한 객체나 배열과 유사한 객체를 지정합니다.
// `Set`은 반복 가능한 객체입니다.
const iterable = new Set(["a", "b", "c"]);
console.log(iterable); // 출력: Set(3) { 'a', 'b', 'c' }
const array = Array.from(iterable);
console.log(array); // 출력: [ 'a', 'b', 'c' ]
// 배열과 유사한 객체
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const array2 = Array.from(arrayLike);
console.log(array2); // 출력: [ 'a', 'b', 'c' ]
from
메서드의 두 번째 인자에는 각 요소에 대해 순차적으로 호출되는 콜백 함수를 지정할 수 있습니다.
이 콜백 함수는 배열의 map
메서드에 전달하는 함수와 유사하며, 반환값은 새 배열에 추가될 요소의 값이 됩니다.
콜백 함수에는 두 개의 인자가 전달됩니다.
첫 번째 인자는 변환 대상 요소이며, 두 번째 인자는 그 요소의 인덱스입니다.
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const array = Array.from(arrayLike, (element, index) => {
console.log(element, index);
return element.toUpperCase(); // 새로운 배열의 요소 값에는 대문자로 변환된 문자열을 사용
});
console.log(array);
// 출력:
// a 0
// b 1
// c 2
// [ 'A', 'B', 'C' ]
반환값은 빽빽한 배열이 됩니다.
그 길이는 from
메서드의 첫 번째 인자가 반복 가능한 객체인 경우, 해당 객체에서 추출된 요소 수와 동일하며, 반복 가능하지 않은 배열과 유사한 객체인 경우 그 길이와 같습니다.
Array.from
메서드의 특징
Array.from
메서드는 반복 가능한 객체나 배열과 유사한 객체를 배열로 변환하기 위한 메서드입니다.
반복 가능한 객체는 Symbol.iterator
메서드를 가진 객체입니다.
그대로도 for-of
문을 통해 반복 처리가 가능하지만 배열에 존재하는 메서드를 사용해 처리하거나 인덱스로 요소에 접근하고자 할 때 Array.from
메서드가 유용합니다.
배열과 유사한 객체도 마찬가지로 배열에 존재하는 메서드를 사용해 처리하기 위해 Array.from
메서드를 사용할 수 있습니다.
Array.from
에 배열과 유사한 객체를 전달할 경우, 그 객체에 프로퍼티가 존재하지 않는 (비어 있는) 인덱스도 반복 대상이 됩니다.
const arrayLike = { 0: "a", 2: "c", length: 3 };
const array = Array.from(arrayLike);
console.log(array); // 출력: [ 'a', undefined, 'c' ]
const array2 = Array.from(arrayLike, (element, index) => {
console.log(element, index);
return element?.toUpperCase();
});
console.log(array2);
// 출력:
// a 0
// undefined 1
// c 2
// [ 'A', undefined, 'C' ]
이 특징을 이용해 특정 길이의 빽빽한 배열을 Array.from
메서드로 생성할 수 있습니다.
const length = 5;
const array = Array.from({ length }, (_, index) => index);
console.log(array); // 출력: [ 0, 1, 2, 3, 4 ]
// `new Array(length)`는 요소의 프로퍼티가 존재하지 않는 드문 배열이 생성되므로 `map` 메서드를 사용해도 요소가 생성되지 않습니다.
const array2 = new Array(length).map((_, index) => index);
console.log(array2); // 출력: [ <5 empty items> ]
2차원 배열을 생성할 때도 유용합니다.
const rows = 3;
const columns = 4;
// 2차원 배열을 생성합니다.
const array = Array.from({ length: rows }, () => {
return new Array(columns).fill(0);
});
console.log(array);
// 출력: [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ]
배열도 반복 가능한 객체이므로 Array.from
메서드의 첫 번째 인자로 전달할 수 있습니다.
이 경우 반환값은 새로운 배열이 됩니다.
const array = ["a", "b", "c"];
const newArray = Array.from(array);
console.log(newArray); // 출력: [ 'a', 'b', 'c' ]
console.log(array === newArray); // 출력: false
// 드문 배열은 빽빽한 배열로 변환됩니다.
const sparseArray = ["a", , "c"];
const newArray2 = Array.from(sparseArray, (element, index) => {
return element?.toUpperCase();
});
console.log(newArray2); // 출력: [ 'A', undefined, 'C' ]
Array.from
메서드에 전달하는 콜백 함수를 비동기 함수로 만들면 from
메서드의 반환값은 Promise
로 구성된 배열이 됩니다.
이는 배열의 map
메서드와 동일합니다.
Array.from
메서드의 첫 번째 인자로 반복 가능하지 않은 배열과 유사한 객체를 전달할 경우, Array.from
메서드가 반복하는 범위와 반환값이 되는 새 배열의 길이는 메서드가 호출된 시점에 배열과 유사한 객체의 길이를 기준으로 결정됩니다.
따라서 콜백 함수에서 배열과 유사한 객체의 길이를 늘린 경우 원래 길이를 초과하는 인덱스의 요소는 반복되지 않으며 새 배열에도 포함되지 않습니다.
Array.from
메서드의 세 번째 인자로 콜백 함수 내에서 this
로 사용될 값을 지정할 수 있습니다. (배열의 forEach
메서드의 두 번째 인자와 동일)
Array.from
메서드는 Array
이외의 생성자에 대해서도 호출할 수 있습니다.
이 경우 반환값은 해당 생성자의 인스턴스가 됩니다.
const iterable = new Set(["a", "b", "c"]);
// 배열과 유사한 객체를 생성합니다.
const array = Array.from.call(Object, iterable);
console.log(array); // 출력: { '0': 'a', '1': 'b', '2': 'c', length: 3 }
class ArraySubclass extends Array {}
const arraySubclass = ArraySubclass.from(iterable);
console.log(arraySubclass); // 출력: ArraySubclass(3) [ 'a', 'b', 'c' ]
Array.from
메서드의 대체 구현
iterable
이 반복 가능한 객체일 때,
const array = Array.from(iterable);
는 다음과 거의 동일합니다.
const array = [...iterable];
또는
const [...array] = iterable;
또는
const array = [];
for (const element of iterable) {
array.push(element);
}
iterable
이 반복 가능한 객체일 때,
const array = Array.from(iterable, (element) => {
const result = <식>;
return result;
});
는 다음과 거의 동일합니다.
const array = [];
for (const element of iterable) {
const result = <식>;
array.push(result);
}
arrayLike
가 배열과 유사한 객체일 때,
const array = Array.from(arrayLike);
는 다음과 거의 동일합니다.
const array = [];
for (let i = 0, length = +arrayLike.length; i < length; i++) {
array.push(arrayLike[i]);
}
또는
const array = Array.prototype.slice.call(arrayLike, 0).fill(undefined);
또는
const array = [...Array.prototype.values.call(arrayLike)];
'Javascript' 카테고리의 다른 글
2024년에 Remix 앱을 위한 최적의 호스팅 옵션 가이드 (0) | 2024.06.06 |
---|---|
Next.js 버전 15의 핵심 변경 사항 - 기본적으로 비활성화된 라우팅 및 데이터 캐싱 (0) | 2024.05.25 |
TypeScript 고급 타입 마스터하기 - 객체 키 추출부터 infer 활용까지 (1) | 2024.05.15 |
TypeScript의 효율적인 타입 관리 - any 없이 타입 정의, 타입 파생 및 제네릭 마스터하기 (1) | 2024.05.15 |
TypeScript 제네릭 패턴과 활용법 - 클래스, 함수, 메서드 완벽 분석 (0) | 2024.05.15 |