[모던 자바스크립트 Deep Dive]27장 배열
27.1 배열이란?
- 배열: 여러 개의 값을 순차적으로 나열한 자료구조
- 요소: 배열이 가지고 있는 값
- 배열은 값의 순서와 길이를 나타내는
length
프로퍼티를 가짐 ⇒ 순차적 접근 가능 - 배열은 객체 타입
- 배열 리터럴,
Array
생성자 함수,Array.of
메서드,Array.from
메서드로 생성 가능 배열의 생성자 함수는
Array
, 프로토타입 객체는Array.prototype
1 2 3 4
const arr = [1, 2, 3]; arr.constructor === Array // -> true Object.getPrototypeOf(arr) === Array.prototype // -> true
27.2 자바스크립트 배열은 배열이 아니다
일반적인 배열
- 밀집 배열(dense array): 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조
- 따라서 배열의 요소는 같은 데이터 타입을 가지며 연속적으로 인접해 있다.
- 임의 접근이 가능하다.
- 배열에 요소를 삽입하거나 삭제하는 경우 요소를 이동시켜야 하는 단점이 있다.
- 임의 접근(random access): 인덱스를 이용해 한 번의 연산으로 임의의 요소에 접근하는 것
- 매우 효율적이며 빠르다(시간 복잡도 O(1)).
- 정렬되지 않은 배열의 경우 임의 접근이 불가하며, 선형 검색(linear search)해야 한다.
- 희소 배열(sparse array): 배열의 요소가 연속적으로 이어져있지 않은 배열
- 요소가 모두 동일한 크기를 갖지 않아도 된다.
자바스크립트의 배열
1
2
3
4
5
6
7
8
9
console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));
/*
{
'0': {value: 1, writable: true, enumerable: true, configurable: true}
'1': {value: 2, writable: true, enumerable: true, configurable: true}
'2': {value: 3, writable: true, enumerable: true, configurable: true}
length: {value: 3, writable: true, enumerable: false, configurable: false}
}
*/
- 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체
- 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며,
length
프로퍼티를 가짐
- 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며,
- 자바스크립트 배열의 요소는 사실 프로퍼티 값이다.
객체는 프로퍼티 값으로 모든 값이 가능 ⇒ 배열도 요소로 모든 값이 가능
1 2 3 4 5 6 7 8 9 10 11 12
const arr = [ 'string', 10, true, null, undefined, NaN, Infinity, [ ], { }, function () {} ];
일반적인 배열과의 차이
| 일반적인 배열 | 자바스크립트 배열 | | — | — | | 인덱스로 요소에 빠르게 접근 가능 | 해시 테이블로 구현되어 일반적인 배열보다 요소 접근이 느림 | | 요소 삽입, 삭제가 비효율적 | 빠른 요소 삽입, 삭제 가능 |
모던 자바스크립트 엔진은 배열을 일반 객체와 구별하여 최적화 구현 ⇒ 일반 객체보다 더 빠름
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
const arr = []; console.time('Array Performance Test'); for (let i = 0; i < 10000000; i++) { arr[i] = i; } console.timeEnd('Array Performance Test'); // 약 340ms const obj = {}; console.time('Object Performance Test'); for (let i = 0; i < 10000000; i++) { obj[i] = i; } console.timeEnd('Object Performance Test'); // 약 600ms
27.3 length
프로퍼티와 희소 배열
length
프로퍼티의 최댓값은 2^32 - 2다.length
프로퍼티에 더 작은 숫자 값을 할당하면 배열의 길이가 줄어든다.1 2 3 4 5 6 7
const arr = [1, 2, 3, 4, 5]; // 현재 length 프로퍼티 값인 5보다 작은 숫자 값 3을 length 프로퍼티에 할당 arr.length = 3; // 배열의 길이가 5에서 3으로 줄어든다. console.log(arr); // [1, 2, 3]
length
프로퍼티에 더 큰 숫자 값을 할당하면length
프로퍼티 값만 바뀌고 배열엔 변함이 없다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const arr = [1]; // 현재 length 프로퍼티 값인 1보다 큰 숫자 값 3을 length 프로퍼티에 할당 arr.length = 3; // length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지는 않는다. console.log(arr.length); // 3 console.log(arr); // [1, empty × 2] console.log(Object.getOwnPropertyDescriptors(arr)); /* { '0': {value: 1, writable: true, enumerable: true, configurable: true}, length: {value: 3, writable: true, enumerable: false, configurable: false} } */
- 배열의 요소가 연속적으로 위치하지 않고 비어 있게 됨 ⇒ 희소 배열
- 언제나 희소 배열의 실제 요소 개수 < 희소 배열의
length
- 사용을 권장하지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 희소 배열 const sparse = [, 2, , 4]; // 희소 배열의 length 프로퍼티 값은 요소의 개수와 일치하지 않는다. console.log(sparse.length); // 4 console.log(sparse); // [empty, 2, empty, 4] // 배열 sparse에는 인덱스가 0, 2인 요소가 존재하지 않는다. console.log(Object.getOwnPropertyDescriptors(sparse)); /* { '1': { value: 2, writable: true, enumerable: true, configurable: true }, '3': { value: 4, writable: true, enumerable: true, configurable: true }, length: { value: 4, writable: true, enumerable: false, configurable: false } } */
- 최적화가 잘 된 모던 자바스크립트 엔진은 같은 타입의 요소를 갖는 배열 생성 시 연속된 메모리 공간을 확보한다.
- 따라서 희소 배열을 생성하지 않도록 주의, 즉 배열에는 같은 타입 요소를 연속적으로 위치하는 것이 좋다.
27.4 배열 생성
27.4.1 배열 리터럴
0개 이상의 요소를 쉼표로 구분하여 대괄호로 묶는다.
1 2
const arr = [1, 2, 3]; console.log(arr.length); // 3
- 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재한다.
요소를 추가하지 않으면
length
프로퍼티 값이 0인 빈 배열이다.1 2
const arr = []; console.log(arr.length); // 0
배열 리터럴에 요소를 생략하면 희소 배열이다.
1 2 3 4 5 6
const arr = [1, , 3]; // 희소 배열 // 희소 배열의 length는 배열의 실제 요소 개수보다 언제나 크다. console.log(arr.length); // 3 console.log(arr); // [1, empty, 3] console.log(arr[1]); // undefined
27.4.2 Array
생성자 함수
전달된 인수가 한 개인 숫자인 경우 ⇒
length
프로퍼티 값이 인수인 희소 배열 생성1 2 3 4 5 6 7 8 9 10
const arr = new Array(10); console.log(arr); // [empty × 10] console.log(arr.length); // 10 console.log(Object.getOwnPropertyDescriptors(arr)); /* { length: {value: 10, writable: true, enumerable: false, configurable: false} } */
배열은 요소를 최대 2³² - 1개 가질 수 있다. 따라서 0 ≤ 전달할 인수 ≤ 2³² - 1 범위를 만족해야 하며, 벗어나면
RangeError
가 발생한다.1 2 3 4 5 6 7 8
// 배열은 요소를 최대 4,294,967,295개 가질 수 있다. new Array(4294967295); // 전달된 인수가 0 ~ 4,294,967,295를 벗어나면 RangeError가 발생한다. new Array(4294967296); // RangeError: Invalid array length // 전달된 인수가 음수이면 에러가 발생한다. new Array(-1); // RangeError: Invalid array length
전달된 인수가 없는 경우 ⇒ 빈 배열 생성
1
new Array(); // -> []
전달된 인수가 2개 이상이거나 숫자가 아닌 경우 ⇒ 인수를 요소로 갖는 배열 생성
1 2 3 4 5
// 전달된 인수가 2개 이상이면 인수를 요소로 갖는 배열을 생성한다. new Array(1, 2, 3); // -> [1, 2, 3] // 전달된 인수가 1개지만 숫자가 아니면 인수를 요소로 갖는 배열을 생성한다. new Array({}); // -> [{}]
Array
생성자 함수를 일반 함수로 호출해도 생성자 함수로 동작한다.Array
생성자 함수 내부에서new.target
을 확인하기 때문
1
Array(1, 2, 3); // -> [1, 2, 3]
27.4.3 Array.of
1
2
3
4
5
6
// 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.
Array.of(1); // -> [1]
Array.of(1, 2, 3); // -> [1, 2, 3]
Array.of('string'); // -> ['string']
- ES6에서 도입된
Array
생성자 함수의 정적 메서드 - 전달된 인수를 요소로 갖는 배열 생성
27.4.4 Array.from
- ES6에서 도입된
Array
생성자 함수의 정적 메서드 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환
1 2 3 4 5
// 유사 배열 객체를 변환하여 배열을 생성한다. Array.from({ length: 2, 0: 'a', 1: 'b' }); // -> ['a', 'b'] // 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다. Array.from('Hello'); // -> ['H', 'e', 'l', 'l', 'o']
두 번째 인수로 콜백 함수를 전달하여 값을 만들면서 요소를 채울 수 있다.
1 2 3 4 5 6 7
// Array.from에 length만 존재하는 유사 배열 객체를 전달하면 // undefined를 요소로 채운다. Array.from({ length: 3 }); // -> [undefined, undefined, undefined] // Array.from은 두 번째 인수로 전달한 // 콜백 함수의 반환값으로 구성된 배열을 반환한다. Array.from({ length: 3 }, (_, i) => i); // -> [0, 1, 2]
- 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하며 호출
- 콜백 함수의 반환 값으로 구성된 배열 반환
유사 배열 객체와 이터러블 객체
유사 배열 객체(array-like object)
1 2 3 4 5 6 7 8 9 10 11 12
// 유사 배열 객체 const arrayLike = { '0': 'apple', '1': 'banana', '2': 'orange', length: 3 }; // 유사 배열 객체는 마치 배열처럼 for 문으로 순회할 수도 있다. for (let i = 0; i < arrayLike.length; i++) { console.log(arrayLike[i]); // apple banana orange }
- 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고
length
프로퍼티를 갖는 객체 - 배열처럼
for
문으로 순회 가능
- 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고
이터러블 객체(iterable object)
Symbol.iterator
메서드를 구현하여for...of
문으로 순회할 수 있고, 스프레드 문법과 배열 디스트럭터링 할당의 대상으로 사용할 수 있는 객체- ES6에서
Array
,String
,Map
,Set
, DOM 컬렉션,arguments
등의 빌트인 이터러블 제공
27.5 배열 요소의 참조
대괄호 안에 인덱스를 넣어 배열의 요소를 참조
1 2 3 4 5 6
const arr = [1, 2]; // 인덱스가 0인 요소를 참조 console.log(arr[0]); // 1 // 인덱스가 1인 요소를 참조 console.log(arr[1]); // 2
- 인덱스 대신 정수로 평가되는 표현식 사용 가능
객체는 존재하지 않는 프로퍼티 키로 프로퍼티에 접근 시
undefined
반환 ⇒ 배열은 객체이므로 존재하지 않는 요소에 접근 시undefined
반환1 2 3 4
const arr = [1, 2]; // 인덱스가 2인 요소를 참조. 배열 arr에는 인덱스가 2인 요소가 존재하지 않는다. console.log(arr[2]); // undefined
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 희소 배열 const arr = [1, , 3]; // 배열 arr에는 인덱스가 1인 요소가 존재하지 않는다. console.log(Object.getOwnPropertyDescriptors(arr)); /* { '0': {value: 1, writable: true, enumerable: true, configurable: true}, '2': {value: 3, writable: true, enumerable: true, configurable: true}, length: {value: 3, writable: true, enumerable: false, configurable: false} */ // 존재하지 않는 요소를 참조하면 undefined가 반환된다. console.log(arr[1]); // undefined console.log(arr[3]); // undefined
27.6 배열 요소의 추가와 갱신
- 존재하지 않는 인덱스를 사용해 값을 할당 ⇒ 객체처럼 새로운 요소 추가
length
프로퍼티 값은 자동 갱신된다.
1 2 3 4 5 6 7
const arr = [0]; // 배열 요소의 추가 arr[1] = 1; console.log(arr); // [0, 1] console.log(arr.length); // 2
- 배열의
length
프로퍼티 값보다 큰 인덱스로 새로운 요소 추가 ⇒ 희소 배열- 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
arr[100] = 100; console.log(arr); // [0, 1, empty × 98, 100] console.log(arr.length); // 101 // 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다. console.log(Object.getOwnPropertyDescriptors(arr)); /* { '0': {value: 0, writable: true, enumerable: true, configurable: true}, '1': {value: 1, writable: true, enumerable: true, configurable: true}, '100': {value: 100, writable: true, enumerable: true, configurable: true}, length: {value: 101, writable: true, enumerable: false, configurable: false} */
이미 존재하는 요소에 값을 재할당 ⇒ 요소 값 갱신
1 2 3 4
// 요소값의 갱신 arr[1] = 10; console.log(arr); // [0, 10, empty × 98, 100]
- 인덱스가 0 이상의 정수 또는 정수 형태의 문자열이 아닌 경우 ⇒ 프로퍼티 생성
- 이렇게 생성된 프로퍼티는
length
프로퍼티 값에 영향을 주지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const arr = []; // 배열 요소의 추가 arr[0] = 1; arr['1'] = 2; // 프로퍼티 추가 arr['foo'] = 3; arr.bar = 4; arr[1.1] = 5; arr[-1] = 6; console.log(arr); // [1, 2, foo: 3, bar: 4, '1.1': 5, '-1': 6] // 프로퍼티는 length에 영향을 주지 않는다. console.log(arr.length); // 2
- 이렇게 생성된 프로퍼티는
27.7 배열 요소의 삭제
- 배열은 객체 ⇒
delete
연산자 사용 가능length
프로퍼티 값이 변하지 않아 희소 배열이 되므로 사용하지 않는다.
1 2 3 4 5 6 7 8
const arr = [1, 2, 3]; // 배열 요소의 삭제 delete arr[1]; console.log(arr); // [1, empty, 3] // length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다. console.log(arr.length); // 3
희소 배열을 만들지 않는
Array.prototype.splice
메서드 사용을 권장한다.1 2 3 4 5 6 7 8 9
const arr = [1, 2, 3]; // Array.prototype.splice(삭제를 시작할 인덱스, 삭제할 요소 수) // arr[1]부터 1개의 요소를 제거 arr.splice(1, 1); console.log(arr); // [1, 3] // length 프로퍼티가 자동 갱신된다. console.log(arr.length); // 2
27.8 배열 메서드
- mutator method
- 원본 배열(배열 메서드 구현체 내부에서
this
가 가리키는 객체)을 직접 변경 - 외부 상태를 직접 변경하는 부수 효과가 있으므로 사용 시 주의 필요
- 원본 배열(배열 메서드 구현체 내부에서
- accessor method
- 원본 배열을 변경하지 않고 새로운 배열을 생성해 반환
- 가급적 accessor method 사용을 권장
27.8.1 Array.isArray
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());
// false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(1);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray({ 0: 1, length: 1 })
Array
생성자 함수의 정적 메서드- 전달된 인수가 배열이면
true
, 아니면false
반환
27.8.2 Array.prototype.indexOf
1
2
3
4
5
6
7
8
const arr = [1, 2, 2, 3];
// 배열 arr에서 요소 2를 검색하여 첫 번째로 검색된 요소의 인덱스를 반환한다.
arr.indexOf(2); // -> 1
// 배열 arr에 요소 4가 없으므로 -1을 반환한다.
arr.indexOf(4); // -> -1
// 두 번째 인수는 검색을 시작할 인덱스다. 두 번째 인수를 생략하면 처음부터 검색한다.
arr.indexOf(2, 2); // -> 2
- 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환
- 요소가 중복되어 여러 개 있는 경우 ⇒ 첫 번째로 검색된 요소의 인덱스 반환
요소가 존재하지 않는 경우 ⇒
-1
반환1 2 3 4 5 6 7 8 9
const foods = ['apple', 'banana', 'orange']; // foods 배열에 'orange' 요소가 존재하는지 확인한다. if (foods.indexOf('orange') === -1) { // foods 배열에 'orange' 요소가 존재하지 않으면 'orange' 요소를 추가한다. foods.push('orange'); } console.log(foods); // ["apple", "banana", "orange"]
배열에 특정 요소가 존재하는지 확인할 때는
indexOf
메서드보다Array.prototype.includes
메서드가 더 적합하다.1 2 3 4 5 6 7 8 9
const foods = ['apple', 'banana']; // foods 배열에 'orange' 요소가 존재하는지 확인한다. if (!foods.includes('orange')) { // foods 배열에 'orange' 요소가 존재하지 않으면 'orange' 요소를 추가한다. foods.push('orange'); } console.log(foods); // ["apple", "banana", "orange"]
27.8.3 Array.prototype.push
인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가 → 변경된
length
프로퍼티 값 반환1 2 3 4 5 6 7 8 9
const arr = [1, 2]; // 인수로 전달받은 모든 값을 원본 배열 arr의 마지막 요소로 추가하고 // 변경된 length 값을 반환한다. let result = arr.push(3, 4); console.log(result); // 4 // push 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [1, 2, 3, 4]
성능이 좋지 않으므로 한 개의 요소만 추가할 경우
length
프로퍼티를 사용하는 게 더 빠르다.1 2 3 4 5
const arr = [1, 2]; // arr.push(3)과 동일한 처리를 한다. 이 방법이 push 메서드보다 빠르다. arr[arr.length] = 3; console.log(arr); // [1, 2, 3]
원본 배열을 직접 변경하는 부수 효과 존재 ⇒ ES6의 스프레드 문법 사용 권장
1 2 3 4 5
const arr = [1, 2]; // ES6 스프레드 문법 const newArr = [...arr, 3]; console.log(newArr); // [1, 2, 3]
27.8.4 Array.prototype.pop
1
2
3
4
5
6
7
8
const arr = [1, 2];
// 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환한다.
let result = arr.pop();
console.log(result); // 2
// pop 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [1]
- 원본 배열에서 마지막 요소를 제거하고, 제거한 요소를 반환
- 원본 배열이 빈 배열이면
undefined
반환
pop
메서드와 push
메서드를 활용한 스택 구현
생성자 함수를 사용한 스택 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
const Stack = (function () { function Stack(array = []) { if (!Array.isArray(array)) { throw new TypeError(`${array} is not an array.`); } this.array = array; } Stack.prototype = { constructor: Stack, // 스택의 가장 마지막에 데이터를 밀어 넣는다. push(value) { return this.array.push(value); }, // 스택의 가장 마지막 데이터, 즉 가장 나중에 밀어 넣은 최신 데이터를 꺼낸다. pop() { return this.array.pop(); }, // 스택의 복사본 배열을 반환한다. entries() { return [...this.array]; } }; return Stack; }()); const stack = new Stack([1, 2]); console.log(stack.entries()); // [1, 2] stack.push(3); console.log(stack.entries()); // [1, 2, 3] stack.pop(); console.log(stack.entries()); // [1, 2]
클래스를 사용한 스택 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
class Stack { #array; // private class member constructor(array = []) { if (!Array.isArray(array)) { throw new TypeError(`${array} is not an array.`); } this.#array = array; } // 스택의 가장 마지막에 데이터를 밀어 넣는다. push(value) { return this.#array.push(value); } // 스택의 가장 마지막 데이터, 즉 가장 나중에 밀어 넣은 최신 데이터를 꺼낸다. pop() { return this.#array.pop(); } // 스택의 복사본 배열을 반환한다. entries() { return [...this.#array]; } } const stack = new Stack([1, 2]); console.log(stack.entries()); // [1, 2] stack.push(3); console.log(stack.entries()); // [1, 2, 3] stack.pop(); console.log(stack.entries()); // [1, 2]
27.8.5 Array.prototype.unshift
인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가 → 변경된
length
프로퍼티 값 반환1 2 3 4 5 6 7 8 9
const arr = [1, 2]; // 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가하고 // 변경된 length 값을 반환한다. let result = arr.unshift(3, 4); console.log(result); // 4 // unshift 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [3, 4, 1, 2]
원본 배열을 직접 변경하는 부수 효과 존재 ⇒ ES6의 스프레드 문법 사용 권장
1 2 3 4 5
const arr = [1, 2]; // ES6 스프레드 문법 const newArr = [3, ...arr]; console.log(newArr); // [3, 1, 2]
27.8.6 Array.prototype.shift
1
2
3
4
5
6
7
8
const arr = [1, 2];
// 원본 배열에서 첫 번째 요소를 제거하고 제거한 요소를 반환한다.
let result = arr.shift();
console.log(result); // 1
// shift 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [2]
- 원본 배열에서 첫 번째 요소 제거 → 제거한 요소 반환
- 원본 배열이 빈 배열인 경우 →
undefined
반환
- 원본 배열이 빈 배열인 경우 →
shift
메서드와 push
메서드를 활용한 큐 구현
생성자 함수를 사용한 큐 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
const Queue = (function () { function Queue(array = []) { if (!Array.isArray(array)) { // "47. 에러 처리" 참고 throw new TypeError(`${array} is not an array.`); } this.array = array; } Queue.prototype = { // "19.10.1. 생성자 함수에 의한 프로토타입의 교체" 참고 constructor: Queue, // 큐의 가장 마지막에 데이터를 밀어 넣는다. enqueue(value) { return this.array.push(value); }, // 큐의 가장 처음 데이터, 즉 가장 먼저 밀어 넣은 데이터를 꺼낸다. dequeue() { return this.array.shift(); }, // 큐의 복사본 배열을 반환한다. entries() { return [...this.array]; } }; return Queue; }()); const queue = new Queue([1, 2]); console.log(queue.entries()); // [1, 2] queue.enqueue(3); console.log(queue.entries()); // [1, 2, 3] queue.dequeue(); console.log(queue.entries()); // [2, 3]
클래스를 사용한 큐 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
class Queue { #array; // private class member constructor(array = []) { if (!Array.isArray(array)) { throw new TypeError(`${array} is not an array.`); } this.#array = array; } // 큐의 가장 마지막에 데이터를 밀어 넣는다. enqueue(value) { return this.#array.push(value); } // 큐의 가장 처음 데이터, 즉 가장 먼저 밀어 넣은 데이터를 꺼낸다. dequeue() { return this.#array.shift(); } // 큐의 복사본 배열을 반환한다. entries() { return [...this.#array]; } } const queue = new Queue([1, 2]); console.log(queue.entries()); // [1, 2] queue.enqueue(3); console.log(queue.entries()); // [1, 2, 3] queue.dequeue(); console.log(queue.entries()); // [2, 3]
27.8.7 Array.prototype.concat
- 인수로 전달된 값(배열 또는 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열 반환
- 인수로 배열을 전달한 경우 ⇒ 배열을 해체하여 새로운 배열의 요소로 추가
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
const arr1 = [1, 2]; const arr2 = [3, 4]; // 배열 arr2를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다. // 인수로 전달한 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가한다. let result = arr1.concat(arr2); console.log(result); // [1, 2, 3, 4] // 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다. result = arr1.concat(3); console.log(result); // [1, 2, 3] // 배열 arr2와 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환한다. result = arr1.concat(arr2, 5); console.log(result); // [1, 2, 3, 4, 5] // 원본 배열은 변경되지 않는다. console.log(arr1); // [1, 2]
push
/unshift
메서드와의 차이점push
/unshift
메서드는 원본 배열이 반드시 변수에 저장되어 있어야 변경된 배열을 사용할 수 있다.concat
메서드는 반환 값을 변수에 할당받아야 한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
const arr1 = [3, 4]; // unshift 메서드는 원본 배열을 직접 변경한다. // 따라서 원본 배열을 변수에 저장해 두지 않으면 변경된 배열을 사용할 수 없다. arr1.unshift(1, 2); // unshift 메서드를 사용할 경우 원본 배열을 반드시 변수에 저장해 두어야 // 결과를 확인할 수 있다. console.log(arr1); // [1, 2, 3, 4] // push 메서드는 원본 배열을 직접 변경한다. // 따라서 원본 배열을 변수에 저장해 두지 않으면 변경된 배열을 사용할 수 없다. arr1.push(5, 6); // push 메서드를 사용할 경우 원본 배열을 반드시 변수에 저장해 두어야 // 결과를 확인할 수 있다. console.log(arr1); // [1, 2, 3, 4, 5, 6] // unshift와 push 메서드는 concat 메서드로 대체할 수 있다. const arr2 = [3, 4]; // concat 메서드는 원본 배열을 변경하지 않고 새로운 배열을 반환한다. // arr1.unshift(1, 2)를 다음과 같이 대체할 수 있다. let result = [1, 2].concat(arr2); console.log(result); // [1, 2, 3, 4] // arr1.push(5, 6)를 다음과 같이 대체할 수 있다. result = result.concat(5, 6); console.log(result); // [1, 2, 3, 4, 5, 6]
push
/unshift
메서드는 인수로 배열이 전달된 경우 배열을 그대로 추가하지만concat
메서드는 인수로 전달받은 배열을 해체하여 추가한다.1 2 3 4 5 6 7 8 9 10 11 12
const arr = [3, 4]; // unshift와 push 메서드는 인수로 전달받은 배열을 그대로 원본 배열의 요소로 추가한다 arr.unshift([1, 2]); arr.push([5, 6]); console.log(arr); // [[1, 2], 3, 4,[5, 6]] // concat 메서드는 인수로 전달받은 배열을 해체하여 새로운 배열의 요소로 추가한다 let result = [1, 2].concat([3, 4]); result = result.concat([5, 6]); console.log(result); // [1, 2, 3, 4, 5, 6]
- ES6의 스프레드 문법으로 대체할 수 있다.
push
/unshift
/concat
메서드를 사용하는 대신 일관적으로 스프레드 문법 사용을 권장
1 2 3 4 5 6
let result = [1, 2].concat([3, 4]); console.log(result); // [1, 2, 3, 4] // concat 메서드는 ES6의 스프레드 문법으로 대체할 수 있다. result = [...[1, 2], ...[3, 4]]; console.log(result); // [1, 2, 3, 4]
27.8.8 Array.prototype.splice
원본 배열의 중간에 요소를 추가하거나, 중간 요소를 제거하는 경우 사용
1 2 3 4 5 6 7 8 9
const arr = [1, 2, 3, 4]; // 원본 배열의 인덱스 1부터 2개의 요소를 제거하고 그 자리에 새로운 요소 20, 30을 삽입 const result = arr.splice(1, 2, 20, 30); // 제거한 요소가 배열로 반환된다. console.log(result); // [2, 3] // splice 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [1, 20, 30, 4]
splice(start, deleteCount, items)
→ 제거한 요소를 배열로 반환start
: 원본 배열의 요소를 제거하기 시작할 인덱스start
만 지정된 경우 ⇒ 원본 배열의start
부터 모든 요소를 제거1 2 3 4 5 6 7 8 9
const arr = [1, 2, 3, 4]; // 원본 배열의 인덱스 1부터 모든 요소를 제거한다. const result = arr.splice(1); // 원본 배열이 변경된다. console.log(arr); // [1] // 제거한 요소가 배열로 반환된다. console.log(result); // [2, 3, 4]
음수인 경우 ⇒ 배열의 끝에서의 인덱스 지칭
deleteCount
(옵션):start
부터 제거할 요소의 개수0인 경우 ⇒ 아무런 요소도 제거되지 않음
1 2 3 4 5 6 7 8 9 10
const arr = [1, 2, 3, 4]; // 원본 배열의 인덱스 1부터 0개의 요소를 제거하고 // 그 자리에 새로운 요소 100을 삽입한다. const result = arr.splice(1, 0, 100); // 원본 배열이 변경된다. console.log(arr); // [1, 100, 2, 3, 4] // 제거한 요소가 배열로 반환된다. console.log(result); // []
생략된 경우 ⇒ 원본 배열의
start
부터 모든 요소를 제거1 2 3 4 5 6 7 8 9
const arr = [1, 2, 3, 4]; // 원본 배열의 인덱스 1부터 모든 요소를 제거한다. const result = arr.splice(1); // 원본 배열이 변경된다. console.log(arr); // [1] // 제거한 요소가 배열로 반환된다. console.log(result); // [2, 3, 4]
items
(옵션): 제거한 위치에 삽입할 요소들의 목록생략된 경우 ⇒ 원본 배열에서 요소를 제거만 함
1 2 3 4 5 6 7 8 9
const arr = [1, 2, 3, 4]; // 원본 배열의 인덱스 1부터 2개의 요소를 제거한다. const result = arr.splice(1, 2); // 원본 배열이 변경된다. console.log(arr); // [1, 4] // 제거한 요소가 배열로 반환된다. console.log(result); // [2, 3]
배열의 특정 요소 제거를 위해
indexOf
와 함께 사용될 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const arr = [1, 2, 3, 1, 2]; // 배열 array에서 item 요소를 제거한다. // item 요소가 여러 개 존재하면 첫 번째 요소만 제거한다. function remove(array, item) { // 제거할 item 요소의 인덱스를 취득한다. const index = array.indexOf(item); // 제거할 item 요소가 있다면 제거한다. if (index !== -1) array.splice(index, 1); return array; } console.log(remove(arr, 2)); // [1, 3, 1, 2] console.log(remove(arr, 10)); // [1, 3, 1, 2]
filter
메서드도 특정 요소를 제거하지만 중복된 경우에는 모두 제거한다.1 2 3 4 5 6 7 8
const arr = [1, 2, 3, 1, 2]; // 배열 array에서 모든 item 요소를 제거한다. function removeAll(array, item) { return array.filter(v => v !== item); } console.log(removeAll(arr, 2)); // [1, 3, 1]
27.8.9 Array.prototype.slice
인수로 전달된 범위의 요소들을 복사하여 배열로 반환
1 2 3 4 5 6 7 8 9 10
const arr = [1, 2, 3]; // arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환한다. arr.slice(0, 1); // -> [1] // arr[1]부터 arr[2] 이전(arr[2] 미포함)까지 복사하여 반환한다. arr.slice(1, 2); // -> [2] // 원본은 변경되지 않는다. console.log(arr); // [1, 2, 3]
slice(start, end)
start
: 복사를 시작할 인덱스1 2 3 4 5 6 7 8 9 10
const arr = [1, 2, 3]; // arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환한다. arr.slice(0, 1); // -> [1] // arr[1]부터 arr[2] 이전(arr[2] 미포함)까지 복사하여 반환한다. arr.slice(1, 2); // -> [2] // 원본은 변경되지 않는다. console.log(arr); // [1, 2, 3]
음수인 경우 ⇒ 배열 끝에서부터 요소를 복사
1 2 3 4 5 6 7
const arr = [1, 2, 3]; // 배열의 끝에서부터 요소를 한 개 복사하여 반환한다. arr.slice(-1); // -> [3] // 배열의 끝에서부터 요소를 두 개 복사하여 반환한다. arr.slice(-2); // -> [2, 3]
end
: 복사를 종료할 인덱스생략하는 경우 ⇒ 기본값은
length
프로퍼티1 2 3 4
const arr = [1, 2, 3]; // arr[1]부터 이후의 모든 요소를 복사하여 반환한다. arr.slice(1); // -> [2, 3]
인수를 모두 생략하는 경우 ⇒ 원본 배열의 얕은 복사본 반환
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
const arr = [1, 2, 3]; // 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환한다. const copy = arr.slice(); console.log(copy); // [1, 2, 3] console.log(copy === arr); // false const todos = [ { id: 1, content: 'HTML', completed: false }, { id: 2, content: 'CSS', completed: true }, { id: 3, content: 'Javascript', completed: false } ]; // 얕은 복사(shallow copy) const _todos = todos.slice(); // const _todos = [...todos]; // _todos와 todos는 참조값이 다른 별개의 객체다. console.log(_todos === todos); // false // 배열 요소의 참조값이 같다. 즉, 얕은 복사되었다. console.log(_todos[0] === todos[0]); // true
- 깊은 복사를 위해서는 Lodash 라이브러리의
cloneDeep
메서드를 추천
- 깊은 복사를 위해서는 Lodash 라이브러리의
유사 배열 객체를 배열로 변환하는데 사용 가능
1 2 3 4 5 6 7 8 9 10 11
function sum() { // 유사 배열 객체를 배열로 변환(ES5) var arr = Array.prototype.slice.call(arguments); console.log(arr); // [1, 2, 3] return arr.reduce(function (pre, cur) { return pre + cur; }, 0); } console.log(sum(1, 2, 3)); // 6
유사 배열 객체나 이터러블 객체를 배열로 변환할 때에는
Array.from
메서드가 더 간편1 2 3 4 5 6 7 8
function sum() { const arr = Array.from(arguments); console.log(arr); // [1, 2, 3] return arr.reduce((pre, cur) => pre + cur, 0); } console.log(sum(1, 2, 3)); // 6
이터러블 객체는 ES6의 스프레드 문법으로도 간단하게 배열로 변환 가능
1 2 3 4 5 6 7 8 9
function sum() { // 이터러블을 배열로 변환(ES6 스프레드 문법) const arr = [...arguments ]; console.log(arr); // [1, 2, 3] return arr.reduce((pre, cur) => pre + cur, 0); } console.log(sum(1, 2, 3)); // 6
27.8.10 Array.prototype.join
1
2
3
4
5
6
7
8
9
10
11
const arr = [1, 2, 3, 4];
// 기본 구분자는 ','이다.
// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 기본 구분자 ','로 연결한 문자열을 반환
arr.join(); // -> '1,2,3,4';
// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 빈문자열로 연결한 문자열을 반환
arr.join(''); // -> '1234'
// 원본 배열 arr의 모든 요소를 문자열로 변환한 후, 구분자 ':'로 연결한 문자열을 반환
arr.join(':'); // -> '1:2:3:4'
- 원본 배열의 모든 요소를 문자열로 변환 → 인수로 전달받은 문자열(구분자, separator)로 연결한 문자열 반환
- 기본 구분자는 콤마이며 생략 가능
27.8.11 Array.prototype.reverse
1
2
3
4
5
6
7
const arr = [1, 2, 3];
const result = arr.reverse();
// reverse 메서드는 원본 배열을 직접 변경한다.
console.log(arr); // [3, 2, 1]
// 반환값은 변경된 배열이다.
console.log(result); // [3, 2, 1]
- 원본 배열의 순서를 뒤집어 변경된 배열을 반환
- 원본 배열이 변경됨
27.8.12 Array.prototype.fill
- ES6에서 도입
첫 번째 인수로 전달받은 값을 원본 배열의 처음부터 끝까지 채움
1 2 3 4 5 6 7
const arr = [1, 2, 3]; // 인수로 전달 받은 값 0을 배열의 처음부터 끝까지 요소로 채운다. arr.fill(0); // fill 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [0, 0, 0]
두 번째 인수 ⇒ 채우기를 시작할 인덱스
1 2 3 4 5 6 7
const arr = [1, 2, 3]; // 인수로 전달받은 값 0을 배열의 인덱스 1부터 끝까지 요소로 채운다. arr.fill(0, 1); // fill 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [1, 0, 0]
세 번째 인수 ⇒ 채우기를 멈출 인덱스
1 2 3 4 5 6 7
const arr = [1, 2, 3, 4, 5]; // 인수로 전달받은 값 0을 배열의 인덱스 1부터 3 이전(인덱스 3 미포함)까지 요소로 채운다. arr.fill(0, 1, 3); // fill 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [1, 0, 0, 4, 5]
배열을 생성하며 특정 값으로 요소를 채우는 데 유용
1 2 3 4 5 6 7 8 9 10 11
const arr = new Array(3); console.log(arr); // [empty × 3] // 인수로 전달받은 값 1을 배열의 처음부터 끝까지 요소로 채운다. const result = arr.fill(1); // fill 메서드는 원본 배열을 직접 변경한다. console.log(arr); // [1, 1, 1] // fill 메서드는 변경된 원본 배열을 반환한다. console.log(result); // [1, 1, 1]
Array.from
메서드 사용 시 두 번째 인수로 전달한 콜백 함수를 통해 요소값을 만들며 배열을 채울 수 있다.1 2 3 4 5
// 인수로 전달받은 정수만큼 요소를 생성하고 0부터 1씩 증가하면서 요소를 채운다. const sequences = (length = 0) => Array.from({ length }, (_, i) => i); // const sequences = (length = 0) => Array.from(new Array(length), (_, i) => i); console.log(sequences(3)); // [0, 1, 2]
27.8.13 Array.prototype.includes
- ES7에서 도입
첫 번째 인수가 배열 내의 요소로 포함되어 있는지 확인하고, 확인 결과를 반환
1 2 3 4 5 6 7
const arr = [1, 2, 3]; // 배열에 요소 2가 포함되어 있는지 확인한다. arr.includes(2); // -> true // 배열에 요소 100이 포함되어 있는지 확인한다. arr.includes(100); // -> false
- 두 번째 인수 ⇒ 검색을 시작할 인덱스
- 기본값은 0
- 음수 전달 시
length
프로퍼티 값과 음수 인덱스를 합산하여 검색 시작 인덱스 설정
1 2 3 4 5 6 7
const arr = [1, 2, 3]; // 배열에 요소 1이 포함되어 있는지 인덱스 1부터 확인한다. arr.includes(1, 1); // -> false // 배열에 요소 3이 포함되어 있는지 인덱스 2(arr.length - 1)부터 확인한다. arr.includes(3, -1); // -> true
indexOf
메서드는 반환값이 -1인지 확인해야 하고, NaN이 포함되었는지는 확인할 수 없다.1 2
[NaN].indexOf(NaN) !== -1; // -> false [NaN].includes(NaN); // -> true
27.8.14 Array.prototype.flat
- ES10(ECMAScript 2019)에서 도입
- 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화
- 인수를 생략할 경우 ⇒ 기본값은 1
- 인수로
Infinity
를 전달 ⇒ 중첩 배열 모두를 평탄화
1 2 3 4 5 6 7 8 9 10 11 12 13
[1, [2, 3, 4, 5]].flat(); // -> [1, 2, 3, 4, 5] // 중첩 배열을 평탄화하기 위한 깊이 값의 기본값은 1이다. [1, [2, [3, [4]]]].flat(); // -> [1, 2, [3, [4]]] [1, [2, [3, [4]]]].flat(1); // -> [1, 2, [3, [4]]] // 중첩 배열을 평탄화하기 위한 깊이 값을 2로 지정하여 2단계 깊이까지 평탄화한다. [1, [2, [3, [4]]]].flat(2); // -> [1, 2, 3, [4]] // 2번 평탄화한 것과 동일하다. [1, [2, [3, [4]]]].flat().flat(); // -> [1, 2, 3, [4]] // 중첩 배열을 평탄화하기 위한 깊이 값을 Infinity로 지정하여 중첩 배열 모두를 평탄화 [1, [2, [3, [4]]]].flat(Infinity); // -> [1, 2, 3, 4]
27.9 배열 고차 함수
- 고차 함수(Higher-Order Function, HOF): 함수를 인수로 전달받거나 함수를 반환하는 함수
- 자바스크립트의 함수는 일급 객체 ⇒ 함수를 인수에 전달하고 반환할 수 있다.
- 함수형 프로그래밍
- 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고, 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임
- 조건문과 반복문 ⇒ 로직의 흐름을 이해하기 어렵게 해 가독성을 해친다.
- 변수 ⇒ 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다.
- 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향
- 순수 함수를 통해 부수 효과를 최대한 억제 ⇒ 오류를 피하고 프로그램의 안전성을 높임
- 고차 함수가 기반을 두고 있다.
- 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고, 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임
27.9.1 Array.prototype.sort
- 원본 배열을 직접 변경하여 정렬된 배열 반환
- 기본적으로 오름차순 정렬
1 2 3 4 5 6 7
const fruits = ['Banana', 'Orange', 'Apple']; // 오름차순(ascending) 정렬 fruits.sort(); // sort 메서드는 원본 배열을 직접 변경한다. console.log(fruits); // ['Apple', 'Banana', 'Orange']
1 2 3 4 5 6 7
const fruits = ['바나나', '오렌지', '사과']; // 오름차순(ascending) 정렬 fruits.sort(); // sort 메서드는 원본 배열을 직접 변경한다. console.log(fruits); // ['바나나', '사과', '오렌지']
reverse
메서드를 이용해 내림차순 정렬을 할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13
const fruits = ['Banana', 'Orange', 'Apple']; // 오름차순(ascending) 정렬 fruits.sort(); // sort 메서드는 원본 배열을 직접 변경한다. console.log(fruits); // ['Apple', 'Banana', 'Orange'] // 내림차순(descending) 정렬 fruits.reverse(); // reverse 메서드도 원본 배열을 직접 변경한다. console.log(fruits); // ['Orange', 'Banana', 'Apple']
- 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따름
- 따라서 배열의 요소가 숫자 타입이라도 일시적으로 문자열로 변환한 뒤 유니코드 코드 포인터의 순서를 기준으로 정렬 ⇒ 실제 값대로 정렬되지 않음
1 2 3 4 5 6 7 8 9 10 11 12
const points = [40, 100, 1, 5, 2, 25, 10]; points.sort(); // 숫자 요소들로 이루어진 배열은 의도한 대로 정렬되지 않는다. console.log(points); // [1, 10, 100, 2, 25, 40, 5] ['2', '1'].sort(); // -> ["1", "2"] [2, 1].sort(); // -> [1, 2] ['2', '10'].sort(); // -> ["10", "2"] [2, 10].sort(); // -> [10, 2]
숫자 요소를 정렬할 때는 정렬 순서를 정의하는 비교 함수를 인수로 전달
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
const points = [40, 100, 1, 5, 2, 25, 10]; // 숫자 배열의 오름차순 정렬. 비교 함수의 반환값이 0보다 작으면 a를 우선하여 정렬한다. points.sort((a, b) => a - b); console.log(points); // [1, 2, 5, 10, 25, 40, 100] // 숫자 배열에서 최소/최대값 취득 console.log(points[0], points[points.length]); // 1 // 숫자 배열의 내림차순 정렬. 비교 함수의 반환값이 0보다 작으면 b를 우선하여 정렬한다. points.sort((a, b) => b - a); console.log(points); // [100, 40, 25, 10, 5, 2, 1] // 숫자 배열에서 최대값 취득 console.log(points[0]); // 100
- 비교 함수 반환값 < 0 ⇒ 첫 번째 인수를 우선하여 정렬
- 비교 함수 반환값 = 0 ⇒ 일반적으로 정렬하지 않음
- 0 < 비교 함수 반환값 ⇒ 두 번째 인수를 우선하여 정렬
객체를 요소로 갖는 배열도 정렬할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
const todos = [ { id: 4, content: 'JavaScript' }, { id: 1, content: 'HTML' }, { id: 2, content: 'CSS' } ]; // 비교 함수. 매개변수 key는 프로퍼티 키다. function compare(key) { // 프로퍼티 값이 문자열인 경우 // 산술 연산으로 비교하면 NaN이 나오므로 비교 연산을 사용한다. // 비교 함수는 양수/음수/0을 반환하면 되므로 // 산술 연산 대신 비교 연산을 사용할 수 있다. return (a, b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0)); } // id를 기준으로 오름차순 정렬 todos.sort(compare('id')); console.log(todos); /* [ { id: 1, content: 'HTML' }, { id: 2, content: 'CSS' }, { id: 4, content: 'JavaScript' } ] */ // content를 기준으로 오름차순 정렬 todos.sort(compare('content')); console.log(todos); /* [ { id: 2, content: 'CSS' }, { id: 1, content: 'HTML' }, { id: 4, content: 'JavaScript' } ] */
sort
메서드는 quicksort 알고리즘을 사용했었지만, ES10에서는 timsort 알고리즘을 사용한다.- quicksort 알고리즘은 값의 요소가 중복되어 있을 때 초기 순서가 변경될 수 있어 불안정하다.
27.9.2 Array.prototype.forEach
forEach
메서드는for
문을 대체할 수 있는 고차 함수1 2 3 4 5 6 7 8 9 10 11 12 13
const numbers = [1, 2, 3]; let pows = []; // for 문으로 배열 순회 for (let i = 0; i < numbers.length; i++) { pows.push(numbers[i] ** 2); } console.log(pows); // [1, 4, 9] pows = []; // forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다. numbers.forEach(item => pows.push(item ** 2)); console.log(pows); // [1, 4, 9]
- 조건문이나 반복문은 로직의 흐름을 어렵게 하며, 특히
for
문은 반복을 위한 변수를 선언해야 하며 조건식과 증감식으로 이루어져 함수형 프로그래밍이 추구하는 바와 맞지 않는다. - 메서드 내부에서 반복문을 실행 ⇒ 반복문을 추상화한 고차 함수
- 조건문이나 반복문은 로직의 흐름을 어렵게 하며, 특히
배열을 순회하며 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출
1 2 3 4 5 6 7 8 9
// forEach 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다. [1, 2, 3].forEach((item, index, arr) => { console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`); }); /* 요소값: 1, 인덱스: 0, this: [1,2,3] 요소값: 2, 인덱스: 1, this: [1,2,3] 요소값: 3, 인덱스: 2, this: [1,2,3] */
- 콜백 함수의 첫 번째 인수:
forEach
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
forEach
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
forEach
메서드는 원본 배열을 변경하지 않지만, 콜백 함수를 통해 변경 가능1 2 3 4 5 6 7 8
const numbers = [1, 2, 3]; // forEach 메서드는 원본 배열을 변경하지 않지만 // 콜백 함수를 통해 원본 배열을 변경할 수는 있다. // 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다. // 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다. numbers.forEach((item, index, arr) => { arr[index] = item ** 2; }); console.log(numbers); // [1, 4, 9]
반환값은 언제나
undefined
1 2
const result = [1, 2, 3].forEach(console.log); console.log(result); // undefined
break
,continue
문 사용 불가 ⇒ 배열의 모든 요소를 모두 순회해야 한다.1 2 3 4 5 6 7 8 9 10
[1, 2, 3].forEach(item => { console.log(item); if (item > 1) break; // SyntaxError: Illegal break statement }); [1, 2, 3].forEach(item => { console.log(item); if (item > 1) continue; // SyntaxError: Illegal continue statement: no surrounding iteration statement });
희소 배열의 경우 ⇒ 존재하지 않는 요소는 순회 대상에서 제외
1 2 3 4 5 6 7 8 9 10
// 희소 배열 const arr = [1, , 3]; // for 문으로 희소 배열을 순회 for (let i = 0; i < arr.length; i++) { console.log(arr[i]); // 1, undefined, 3 } // forEach 메서드는 희소 배열의 존재하지 않는 요소를 순회 대상에서 제외한다. arr.forEach(v => console.log(v)); // 1, 3
for
문에 비해 성능이 낮지만, 가독성이 좋으므로 높은 성능이 필요하지 않다면forEach
메서드 사용을 권장한다.
forEach
메서드의 두 번째 인수
forEach
메서드의 두 번째 인수:forEach
메서드의 콜백 함수 내부에서this
로 사용할 객체- 일반 함수로 호출되는
forEach
메서드의 콜백 함수 내부this
⇒undefined
클래스 내부에는 암묵적으로 strict mode가 적용되어
this
는 전역 객체가 아닌undefined
를 가리킨다.1 2 3 4 5 6 7 8 9 10 11 12 13
class Numbers { numberArray = []; multiply(arr) { arr.forEach(function (item) { // TypeError: Cannot read property 'numberArray' of undefined this.numberArray.push(item * item); }); } } const numbers = new Numbers(); numbers.multiply([1, 2, 3]);
두 번째 인수로 콜백 함수 내부에서
this
로 사용할 객체를 전달할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13
class Numbers { numberArray = []; multiply(arr) { arr.forEach(function (item) { this.numberArray.push(item * item); }, this); // forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달 } } const numbers = new Numbers(); numbers.multiply([1, 2, 3]); console.log(numbers.numberArray); // [1, 4, 9]
ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12
class Numbers { numberArray = []; multiply(arr) { // 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다. arr.forEach(item => this.numberArray.push(item * item)); } } const numbers = new Numbers(); numbers.multiply([1, 2, 3]); console.log(numbers.numberArray); // [1, 4, 9]
forEach
메서드의 폴리필
- 폴리필(polyfill): 최신 사양의 기능을 지원하지 않는 브라우저를 위해 누락된 최신 사양의 기능을 구현하여 추가하는 것
forEach
메서드의 폴리필1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 만약 Array.prototype에 forEach 메서드가 존재하지 않으면 폴리필을 추가한다. if (!Array.prototype.forEach) { Array.prototype.forEach = function (callback, thisArg) { // 첫 번째 인수가 함수가 아니면 TypeError를 발생시킨다. if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } // this로 사용할 두 번째 인수를 전달받지 못하면 전역 객체를 this로 사용한다. thisArg = thisArg || window; // for 문으로 배열을 순회하면서 콜백 함수를 호출한다. for (var i = 0; i < this.length; i++) { // call 메서드를 통해 thisArg를 전달하면서 콜백 함수를 호출한다. // 이때 콜백 함수의 인수로 배열 요소, 인덱스, 배열 자신을 전달한다. callback.call(thisArg, this[i], i, this); } }; }
- 반복문을 메서드 내부로 은닉하여 로직의 흐름을 이해하기 쉽게 하고 복잡성을 해결
27.9.3 Array.prototype.map
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값들로 구성된 새로운 배열 반환
- 원본 배열을 변경하지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13
const numbers = [1, 4, 9]; // map 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다. // 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다. const roots = numbers.map(item => Math.sqrt(item)); // 위 코드는 다음과 같다. // const roots = numbers.map(Math.sqrt); // map 메서드는 새로운 배열을 반환한다 console.log(roots); // [ 1, 2, 3 ] // map 메서드는 원본 배열을 변경하지 않는다 console.log(numbers); // [ 1, 4, 9 ]
map
메서드를 호출한 배열과map
메서드가 생성하여 반환한 배열은 1:1 매핑한다.- 두 배열의
length
값 또한 반드시 일치한다.
- 두 배열의
map
메서드의 콜백 함수1 2 3 4 5 6 7 8 9 10
// map 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다. [1, 2, 3].map((item, index, arr) => { console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`); return item; }); /* 요소값: 1, 인덱스: 0, this: [1,2,3] 요소값: 2, 인덱스: 1, this: [1,2,3] 요소값: 3, 인덱스: 2, this: [1,2,3] */
- 콜백 함수의 첫 번째 인수:
map
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
map
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
map
메서드의 두 번째 인수:map
메서드의 콜백 함수 내부에서this
로 사용할 객체1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Prefixer { constructor(prefix) { this.prefix = prefix; } add(arr) { return arr.map(function (item) { // 외부에서 this를 전달하지 않으면 this는 undefined를 가리킨다. return this.prefix + item; }, this); // map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달 } } const prefixer = new Prefixer('-webkit-'); console.log(prefixer.add(['transition', 'user-select'])); // ['-webkit-transition', '-webkit-user-select']
ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14
class Prefixer { constructor(prefix) { this.prefix = prefix; } add(arr) { // 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다. return arr.map(item => this.prefix + item); } } const prefixer = new Prefixer('-webkit-'); console.log(prefixer.add(['transition', 'user-select'])); // ['-webkit-transition', '-webkit-user-select']
forEach
메서드와 map
메서드 비교
- 공통점
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출
- 차이점
forEach
메서드undefined
반환- 반복문을 대체하기 위한 고차 함수
map
메서드- 콜백 함수의 반환값들로 구성된 새로운 배열 반환
- 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수
27.9.4 Array.prototype.filter
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이
**true
인 요소로만 구성된 새로운 배열 반환**- 원본 배열을 변경하지 않는다.
1 2 3 4 5 6 7
const numbers = [1, 2, 3, 4, 5]; // filter 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다. // 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다. // 다음의 경우 numbers 배열에서 홀수인 요소만을 필터링한다(1은 true로 평가된다). const odds = numbers.filter(item => item % 2); console.log(odds); // [1, 3, 5]
- 필터링 조건을 만족하는 특정 요소만 추출하여 새로운 배열을 만들기 위한 고차 함수
- 반환된 배열의
length
프로퍼티 값 ≤filter
메서드를 호출한 배열의length
프로퍼티 값 filter
메서드의 콜백 함수1 2 3 4 5 6 7 8 9 10
// filter 메서드는 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다. [1, 2, 3].filter((item, index, arr) => { console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`); return item % 2; }); /* 요소값: 1, 인덱스: 0, this: [1,2,3] 요소값: 2, 인덱스: 1, this: [1,2,3] 요소값: 3, 인덱스: 2, this: [1,2,3] */
- 콜백 함수의 첫 번째 인수:
filter
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
filter
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
filter
메서드의 두 번째 인수:filter
메서드의 콜백 함수 내부에서this
로 사용할 객체- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.
- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
- 특정 요소를 제거하기 위해 사용 가능
- 특정 요소가 중복되어 있다면 중복된 요소가 모두 제거된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
class Users { constructor() { this.users = [ { id: 1, name: 'Lee' }, { id: 2, name: 'Kim' } ]; } // 요소 추출 findById(id) { // id가 일치하는 사용자만 반환한다. return this.users.filter(user => user.id === id); } // 요소 제거 remove(id) { // id가 일치하지 않는 사용자를 제거한다. this.users = this.users.filter(user => user.id !== id); } } const users = new Users(); let user = users.findById(1); console.log(user); // [{ id: 1, name: 'Lee' }] // id가 1인 사용자를 제거한다. users.remove(1); user = users.findById(1); console.log(user); // []
27.9.5 Array.prototype.reduce
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달 → 콜백 함수를 계속 호출하며 하나의 결괏값을 만들어 반환
- 원본 배열을 변경하지 않는다.
1 2 3 4
// [1, 2, 3, 4]의 모든 요소의 누적을 구한다. const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0); console.log(sum); // 10
reduce
메서드의 콜백 함수- 콜백 함수의 첫 번째 인수: 초깃값 또는 콜백 함수의 이전 반환값
reduce
메서드의 두 번째 인수: 초깃값
- 콜백 함수의 두 번째 인수:
reduce
메서드를 호출한 배열의 요소값 - 콜백 함수의 세 번째 인수: 인덱스
- 콜백 함수의 네 번째 인수:
reduce
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수: 초깃값 또는 콜백 함수의 이전 반환값
- 자신을 호출한 배열의 모든 요소를 순회하며 하나의 결괏값을 구해야 할 때 사용하는 고차 함수
두 번째 인수로 전달하는 초깃값은 생략할 수 있지만, 생략하지 않는 게 안전하다.
1 2 3
// reduce 메서드의 두 번째 인수, 즉 초기값을 생략했다. const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur); console.log(sum); // 10
초깃값 없이 빈 배열로
reduce
메서드를 호출하면 에러가 발생한다.1 2
const sum = [].reduce((acc, cur) => acc + cur); // TypeError: Reduce of empty array with no initial value
1 2
const sum = [].reduce((acc, cur) => acc + cur, 0); console.log(sum); // 0
객체의 특정 프로퍼티 값을 합산하는 경우에도 마찬가지다.
1 2 3 4 5 6 7 8 9 10 11 12 13
const products = [ { id: 1, price: 100 }, { id: 2, price: 200 }, { id: 3, price: 300 } ]; // 1번째 순회 시 acc는 { id: 1, price: 100 }, cur은 { id: 2, price: 200 }이고 // 2번째 순회 시 acc는 300, cur은 { id: 3, price: 300 }이다. // 2번째 순회 시 acc에 함수에 객체가 아닌 숫자값이 전달된다. // 이때 acc.price는 undefined다. const priceSum = products.reduce((acc, cur) => acc.price + cur.price); console.log(priceSum); // NaN
1 2 3 4 5 6 7 8 9 10 11 12 13 14
const products = [ { id: 1, price: 100 }, { id: 2, price: 200 }, { id: 3, price: 300 } ]; /* 1번째 순회 : acc => 0, cur => { id: 1, price: 100 } 2번째 순회 : acc => 100, cur => { id: 2, price: 200 } 3번째 순회 : acc => 300, cur => { id: 3, price: 300 } */ const priceSum = products.reduce((acc, cur) => acc + cur.price, 0); console.log(priceSum); // 600
reduce
메서드의 다양한 활용법
map
,filter
,some
,every
,find
와 같은 모든 배열의 고차 함수는reduce
메서드로 구현할 수 있다.평균 구하기
1 2 3 4 5 6 7 8
const values = [1, 2, 3, 4, 5, 6]; const average = values.reduce((acc, cur, i, { length }) => { // 마지막 순회가 아니면 누적값을 반환하고 마지막 순회면 누적값으로 평균을 구해 반환 return i === length - 1 ? (acc + cur) / length : acc + cur; }, 0); console.log(average); // 3.5
최댓값 구하기
1 2 3 4
const values = [1, 2, 3, 4, 5]; const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0); console.log(max); // 5
reduce
메서드보다Math.max
메서드를 사용하는 게 더 직관적이다.1 2 3 4 5
const values = [1, 2, 3, 4, 5]; const max = Math.max(...values); // var max = Math.max.apply(null, values); console.log(max); // 5
요소의 중복 횟수 구하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
const fruits = ['banana', 'apple', 'orange', 'orange', 'apple']; const count = fruits.reduce((acc, cur) => { // 첫 번째 순회 시 acc는 초기값인 {}이고 cur은 첫 번째 요소인 'banana'다. // 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로, // 요소의 개수를 프로퍼티 값으로 할당한다. // 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1로 초기화한다. acc[cur] = (acc[cur] || 0) + 1; return acc; }, {}); // 콜백 함수는 총 5번 호출되고 다음과 같이 결과값을 반환한다. /* {banana: 1} => {banana: 1, apple: 1} => {banana: 1, apple: 1, orange: 1} => {banana: 1, apple: 1, orange: 2} => {banana: 1, apple: 2, orange: 2} */ console.log(count); // { banana: 1, apple: 2, orange: 2 }
중첩 배열 평탄화
1 2 3 4 5 6
const values = [1, [2, 3], 4, [5, 6]]; const flatten = values.reduce((acc, cur) => acc.concat(cur), []); // [1] => [1, 2, 3] => [1, 2, 3, 4] => [1, 2, 3, 4, 5, 6] console.log(flatten); // [1, 2, 3, 4, 5, 6]
reduce
메서드보다 ES10에서 도입된Array.prototype.flat
메서드를 사용하는 게 더 직관적이다.1 2 3 4
[1, [2, 3, 4, 5]].flat(); // -> [1, 2, 3, 4, 5] // 인수 2는 중첩 배열을 평탄화하기 위한 깊이 값이다. [1, [2, 3, [4, 5]]].flat(2); // -> [1, 2, 3, 4, 5]
중복 요소 제거
1 2 3 4 5 6 7 8 9 10 11 12 13
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; const result = values.reduce( (unique, val, i, _values) => // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소 // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 다르다면 val은 중복된 요소 // 처음 순회하는 요소만 초기값 []가 전달된 unique 배열에 담아 반환하면 // 중복된 요소는 제거된다. _values.indexOf(val) === i ? [...unique, val] : unique, [] ); console.log(result); // [1, 2, 3, 5, 4]
reduce
메서드보다filter
메서드를 사용하는 게 더 직관적이다.1 2 3 4 5 6
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; // 현재 순회 중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소 // 이 요소만 필터링한다. const result = values.filter((val, i, _values) => _values.indexOf(val) === i); console.log(result); // [1, 2, 3, 5, 4]
중복 요소 제거에는
Set
을 사용하는 방법을 추천한다.1 2 3 4 5
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; // 중복을 허용하지 않는 Set 객체의 특성을 활용하여 배열에서 중복된 요소를 제거 가능 const result = [...new Set(values)]; console.log(result); // [1, 2, 3, 5, 4]
27.9.6 Array.prototype.some
1
2
3
4
5
6
7
8
9
10
11
// 배열의 요소 중에 10보다 큰 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item > 10); // -> true
// 배열의 요소 중에 0보다 작은 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item < 0); // -> false
// 배열의 요소 중에 'banana'가 1개 이상 존재하는지 확인
['apple', 'banana', 'mango'].some(item => item === 'banana'); // -> true
// some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환한다.
[].some(item => item > 3); // -> false
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이 단 한 번이라도 참이면
true
, 모두 거짓이면false
반환- 빈 배열이
some
메서드를 호출하면 항상false
반환
- 빈 배열이
some
메서드의 콜백 함수- 콜백 함수의 첫 번째 인수:
some
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
some
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
some
메서드의 두 번째 인수:some
메서드의 콜백 함수 내부에서this
로 사용할 객체- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.
- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
27.9.7 Array.prototype.every
1
2
3
4
5
6
7
8
// 배열의 모든 요소가 3보다 큰지 확인
[5, 10, 15].every(item => item > 3); // -> true
// 배열의 모든 요소가 10보다 큰지 확인
[5, 10, 15].every(item => item > 10); // -> false
// every 메서드를 호출한 배열이 빈 배열인 경우 언제나 true를 반환한다.
[].every(item => item > 3); // -> true
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 콜백 함수의 반환값이 모두 참이면
true
, 단 한 번이라도 거짓이면false
반환- 빈 배열이
every
메서드를 호출하면 항상true
반환
- 빈 배열이
every
메서드의 콜백 함수- 콜백 함수의 첫 번째 인수:
every
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
every
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
every
메서드의 두 번째 인수:every
메서드의 콜백 함수 내부에서this
로 사용할 객체- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.
- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
27.9.8 Array.prototype.find
1
2
3
4
5
6
7
8
9
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 2, name: 'Choi' },
{ id: 3, name: 'Park' }
];
// id가 2인 첫 번째 요소를 반환한다. find 메서드는 배열이 아니라 요소를 반환한다.
users.find(user => user.id === 2); // -> {id: 2, name: 'Kim'}
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 반환값이
true
인 첫 번째 요소 반환, 존재하지 않는다면undefined
반환 find
메서드의 콜백 함수- 콜백 함수의 첫 번째 인수:
find
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
find
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
filter
메서드는 콜백 함수 호출 결과가true
인 요소만 추출한 새로운 배열 반환find
메서드는 콜백 함수의 반환값이true
인 첫 번째 요소 반환1 2 3 4 5
// Array#filter는 배열을 반환한다. [1, 2, 2, 3].filter(item => item === 2); // -> [2, 2] // Array#find는 요소를 반환한다. [1, 2, 2, 3].find(item => item === 2); // -> 2
find
메서드의 두 번째 인수:find
메서드의 콜백 함수 내부에서this
로 사용할 객체- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.
- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
27.9.9 Array.prototype.findIndex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 2, name: 'Choi' },
{ id: 3, name: 'Park' }
];
// id가 2인 요소의 인덱스를 구한다.
users.findIndex(user => user.id === 2); // -> 1
// name이 'Park'인 요소의 인덱스를 구한다.
users.findIndex(user => user.name === 'Park'); // -> 3
// 위와 같이 프로퍼티 키와 프로퍼티 값으로 요소의 인덱스를 구하는 경우
// 다음과 같이 콜백 함수를 추상화할 수 있다.
function predicate(key, value) {
// key와 value를 기억하는 클로저를 반환
return item => item[key] === value;
}
// id가 2인 요소의 인덱스를 구한다.
users.findIndex(predicate('id', 2)); // -> 1
// name이 'Park'인 요소의 인덱스를 구한다.
users.findIndex(predicate('name', 'Park')); // -> 3
- ES6에서 도입
- 자신을 호출한 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출 → 반환값이
true
인 첫 번째 요소의 인덱스 반환, 존재하지 않는다면-1
반환 findIndex
메서드의 콜백 함수- 콜백 함수의 첫 번째 인수:
findIndex
메서드를 호출한 배열의 요소값 - 콜백 함수의 두 번째 인수: 인덱스
- 콜백 함수의 세 번째 인수:
findIndex
메서드를 호출한 배열 자체(this
)
- 콜백 함수의 첫 번째 인수:
findIndex
메서드의 두 번째 인수:findIndex
메서드의 콜백 함수 내부에서this
로 사용할 객체- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
this
를 그대로 참조할 수 있다.
- ES6의 화살표 함수를 사용하면 상위 스코프 내부의
27.9.10 Array.prototype.flatMap
- ES10에서 도입
map
메서드를 통해 생성된 새로운 배열을 평탄화한다.map
메서드와flat
메서드를 순차적으로 실행하는 효과가 있다.
1 2 3 4 5 6 7 8 9
const arr = ['hello', 'world']; // map과 flat을 순차적으로 실행 arr.map(x => x.split('')).flat(); // -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'] // flatMap은 map을 통해 생성된 새로운 배열을 평탄화한다. arr.flatMap(x => x.split('')); // -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
flat
과 다르게 1단계만 평탄화한다.- 평탄화 깊이를 지정해야 하면
map
메서드와flat
메서드를 각각 호출한다.
1 2 3 4 5 6 7 8 9 10
const arr = ['hello', 'world']; // flatMap은 1단계만 평탄화한다. arr.flatMap((str, index) => [index, [str, str.length]]); // -> [[0, ['hello', 5]], [1, ['world', 5]]] => [0, ['hello', 5], 1, ['world', 5]] // 평탄화 깊이를 지정해야 하면 flatMap 메서드를 사용하지 말고 // map 메서드와 flat 메서드를 각각 호출한다. arr.map((str, index) => [index, [str, str.length]]).flat(2); // -> [[0, ['hello', 5]], [1, ['world', 5]]] => [0, 'hello', 5, 1, 'world', 5]
- 평탄화 깊이를 지정해야 하면