제로베이스 네카라쿠배 온라인스쿨 1기 과정(2021. 11 ~ 2021. 12)에서 공부했던 Javascript Deepdive 교재의 내용 요약 및 필자의 생각을 정리한 내용입니다. 원서의 내용 챕터의 일부를 요약한 내용으로 보다 자세한 내용은 원서를 참조 바랍니다.
27장 배열
27.1 배열이란?
- 배열(Array)은 여러 개의 값을 순차적으로 나열한 자료구조다.
- 배열은 사용 빈도가 매우 높은 가장 기본적인 자료구조다.
- 배열이 갖고 있는 값을 요소(element)라고 부른다.
- 자바스크립트의 모든 값은 배열의 요소가 될 수 있다.
- 배열의 요소는 배열에서 자신의 위치를 나타내는 0 이상의 정수인 인덱스(index)를 갖는다.
- 인덱스는 배열의 요소에 접근할 때 사용한다. (제로 베이스)
- 요소에 접근할 때는 대괄호 표기법을 사용한다.
- 배열은 요소의 개수(배열의 길이)를 나타내는 length 프로퍼티를 갖는다.
- 자바스크립트에 배열이라는 타입은 존재하지 않으며 배열은 객체 타입이다.
- 배열은 배열 리터럴, Array 생성자 함수, Array.of, Array.from 메서드로 생성할 수 있다.
- 생성자 함수는 Array 이며, 프로토타입 객체는 Array.prototype이다.
- Array.prototype은 배열을 위한 빌트인 메서드를 제공한다.
- 배열은 객체지만 일반 객체와 달리 특징이 있다.
- 인덱스와 요소로 구성되어 있다
- 값의 참조는 인덱스로 한다.
- 값의 순서가 있다.
- length 프로퍼티가 있다.
- 배열의 장점은 처음부터 순차적으로 요소에 접근할 수도 있고, 마지막부터 역순으로 요소에 접근할 수도 있다. (인덱스, length 프로퍼티 덕분에 가능)
27.2 자바스크립트 배열은 배열이 아니다.
- 자료구조(Data Structure)에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말한다.
- 하나의 데이터 타입으로 통일되어 있고 서로 연속적으로 인접해 있다. (밀집 배열)
- 일반적인 의미의 배열은 각 요소가 동일한 데이터 크기를 가지며, 빈틈없이 연속적으로 이어져 있으므로 단 한번의 연산으로 임의의 요소에 접근할 수 있다.
- 하지만 정렬되지 않은 배열에서 특정한 요소를 검색하는 경우, 배열의 모든 요소를 처음부터 특정 요소를 발견할 때까지 차례대로 검색해야 한다. (선형 검색)
- 또한 배열에 요소를 삽입하거나 삭제하는 경우 배열의 요소를 연속적으로 유지하기 위해 요소를 이동시켜야 하는 단점도 있다.
- 자바스크립트 배열은 배열의 요소를 위한 메모리 공간은 동일한 크기를 갖지 않아도 되며, 연속적으로 이어져 있지 않을수도 있다. 이러한 배열을 **희소 배열(sparse array)**이라 한다.
- 자바스크립트 배열은 엄밀히 말해 일반적 의미의 배열이 아닌, 일반적인 배열의 동작을 흉내 낸 특수한 객체다.
일반적 배열과 자바스크립트 배열의 장단점
- 일반적 배열은 인덱스로 요소에 빠르게 접근할 수 있다. 하지만 특정 요소를 검색하거나 요소를 삽입 또는 삭제하는 경우 효율적이지 않다.
- 자바스크립트 배열은 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적 배열보다 성능적인 면에서 느릴 수밖에 없는 구조적 단점이 있다.
- 특정 요소를 검색하거나 요소를 삽입 또는 삭제하는 경우 일반적 배열보다 빠른 성능을 기대할 수 없다.
- 자바스크립트 배열은 인덱스로 배열 요소에 접근하는 경우 일반적인 배열보다 느리지만 특정 요소를 검색하거나 요소를 삽입 또는 삭제하는 경우에는 일반적인 배열보다 빠르다.
27.3 length 프로퍼티와 희소 배열
- length 프로퍼티는 요소의 개수, 즉 배열의 길이를 나타내는 0 이상의 정수를 값으로 갖는다.
- 빈 배열일 경우 0이며, 빈 배열이 아닐 경우 가장 큰 인덱스에 +1을 더한 것과 같다.
- length 프로퍼티 값은 0과 2의 32제곱 - 1(4,294,967,296 -1) 미만의 양의 정수다. 즉, 배열 요소를 최대 4,294,967,295개(약 42억 9천개) 가질수 있다.
- length 프로퍼티 값은 배열에 요소를 추가하거나 삭제하면 자동 갱신된다.
- length 프로퍼티 값은 요소 개수, 즉 배열의 길이를 바탕으로 결정되지만 임의의 숫자값을, 명시적으로 할당할 수도 있다.
- 만약 length 프로퍼티 값보다 작은 숫자 값을 할당하면 배열의 길이가 줄어든다.
- 주의할 점은 length 프로퍼티 값보다 큰 숫자값을 할당하는 경우다.
- 이 때 length 프로퍼티값은 변경되지만 실제로 배열의 길이가 늘어나진 않는다.
const arr = [1]
arr.length = 3 // 3
console.log(arr) // [1, empty x 2]
- 위 예제에서 출력된 empty x 2는 실제로 추가된 배열 요소가 아니며, arr[1], arr[2]에는 값이 존재하지 않는다.
- 즉, 현재 length 프로퍼티 값보다 큰 숫자 값을 length 프로퍼티에 할당하는 경우 length 프로퍼티 값은 성공적으로 변경되지만 실제 배열에는 아무런 변함이 없다.
- 값 없이 비어있는 요소를 위해 메모리 공간을 확보하지 않으며 빈 요소를 생성하지도 않는다.
- 위와 같은 배열을 희소 배열이라고 하며, 자바스크립트는 희소 배열을 허용한다.
- 일반적인 배열의 length는 배열 요소의 개수, 배열의 길이와 언제나 일치한다.
- 하지만 희소 배열은 length와 배열 요소의 개수가 일치하지 않는다.
- 희소 배열의 length는 희소 배열의 실제 요소 개수보다 언제나 크다.
- 희소 배열은 사용하지 않는 것이 좋다.
- 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선이다.
27.4 배열 생성
27.4.1 배열 리터럴
- 배열 리터럴은 0개 이상의 요소를 쉼표로 구분하여 대괄호([])로 묶는다. 배열 리터럴은 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재한다.
27.4.2 Array 생성자 함수
- Object 생성자 함수를 통해 객체를 생성할 수 있듯이, Array 생성자 함수를 통해 배열을 생성할 수도 있다.
- Array 생성자 함수는 전달된 인수의 개수에 따라 다르게 동작하므로 주의가 필요하다.
- 전달된 인수가 1개고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성한다.
const arr = new Array(10) console.log(arr) // [empty x 10] console.log(arr.length) // 10
- 이때 생성된 배열은 희소 배열이다. length 프로퍼티 값은 0이 아니지만 실제로 배열의 요소는 존재하지 않는다.
- 전달된 인수가 배열 요소 허용 범위를 벗어나면 RangeError가 발생한다.
- 전달된 인수가 없는 경우 빈 배열을 생성한다. (배열 리터럴과 동일)
- 전달된 인수가 2개 이상이거나 숫자가 아닌 경우 인수를 요소로 갖는 배열을 생성한다.
- Array 생성자 함수는 new 연산자와 함께 호출하지 않더라도(일반 함수로 호출해도) 배열을 생성하는 생성자 함수로 동작한다. (내부적으로 new.target을 확인하기 때문)
27.4.3 Array.of
- ES6에서 도입된 Array.of 메서드는 전달된 인수를 요소로 갖는 배열을 생성한다. Array.of는 Array 생성자 함수와 다르게 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.
Array.of(1) // [1]
Array.of(1, 2, 3) // [1,2,3]
Array.of("string") // ['string']
27.4.4 Array.from
- ES6에서 도입된 Array.from 메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.
// 유사 배열 객레를 변환하여 배열을 생성한다.
Array.from({ length: 2, 0: "a", 1: "b" }) // -> ['a', 'b']
// 이터러블을 변환하여 배열을 생성한다.
Array.from("Hello")
- Array.from 을 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 값을 만들면서 요소를 채울 수 있다.
- Array.from 메서드는 두 번째 인수로 전달한 콜백 함수에 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하면서 호출하고, 콜백 함수의 반환값으로 구성된 배열을 반환한다.
Array.from({ length: 3 }) // [undefined, undefined, undefined]
Array.from({ length: 3 }, (_, i) => i) // [0,1,2]
27.5 배열 요소의 참조
- 배열의 요소를 참조할 때는 대괄호([]) 표기법을 사용한다. 대괄호 안에는 인덱스가 와야하며, 정수로 평가되는 표현식이라면 인덱스 대신 사용할 수 있다.
- 존재하지 않은 요소에 접근하면 undefined가 반환된다.
- 배열은 사실 인덱스를 나타내는 문자열을 프로퍼티 키로 갖는 객체다.
27.6 배열 요소의 추가와 갱신
- 객체처럼 배열에도 요소를 동적으로 추가할 수 있다.
- 존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가된다.
- length 프로퍼티는 자동 갱신된다.
- 인덱스로 요소에 접근하여 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다.
- 이미 존재하는 요소에 값을 재할당하면 요소값이 갱신된다.
- 인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수를 사용해야 한다.
- 만약 정수 이외의 값을 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성된다.
- 이때 추가된 프로퍼티는 length 프로퍼티 값에 영향을 주지 않는다.
- 만약 정수 이외의 값을 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성된다.
27.7 배열 요소의 삭제
- 배열의 특정 요소를 삭제하기 위해 delete 연산자를 사용할 수 있다.
- 하지만 delete 연산자는 객체의 프로퍼티를 삭제하고, 희소 배열로 만들기 때문에 length 프로퍼티 값은 변하지 않으므로 delete연산자는 사용하지 않는 것이 좋다.
- 희소 배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 Array.prototype.splicec 메서드를 사용한다.
27.8 배열 메서드
- 배열 메서드는 결과물을 반환하는 패턴이 두 가지이다.
- 원본 배열(배열 메서드를 호출한 배열, 배열 메서드의 구현체 내부에서 this가 가리키는 객체)을 직접 변경하는 메서드(mutator method)
- 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드(accessor method)
27.8.1 Array.isArray
- Array 생성자 함수의 정적 메서드로 전달된 인수가 배열이면 true, 아니면 false를 반환한다.
27.8.2 Array.indexOf
- 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환한다.
- 원본 배열에 인수로 전달한 요소와 중복되는 요소가 여러 개 있다면 첫 번째로 검색된 요소의 인덱스를 반환한다.
- 원본 배열에 인수로 전달한 요소가 존재하지 않으면 -1을 반환한다.
- indexOf 대신 Array.prototype.includes 메서드를 사용하면 가독성이 더 좋다.
27.8.3 Array.prototype.push
- 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가하고 변경된 length 프로퍼티 값을 반환한다. (원본 배열을 직접 변경한다.)
- 성능 면에서 좋지 않다. 추가할 요소가 하나 뿐이라면 push 메서드를 사용하지 않고 요소를 직접 추가하면 더 빠르다.
- push 메서드는 원본 배열을 직접 변경하는 부수 효과가 있다. 따라서 push 메서드보단 스프레드 문법을 사용하는 편이 좋다.
const arr = [1, 2]
// ES6 스프레드 문법;
const newArr = [...arr, 3]
console.log(newArr)
27.8.4 Array.prototype.pop
- 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환한다.
- 빈 배열이면 undefined를 반환한다.
- 원본 배열을 직접 변경한다.
- pop과 push 메서드를 사용하면 스택을 쉽게 구현할 수 있다.
const Stack = (function () {
function Stack(array = []) {
if (!Array.isArray(array)) {
/// 47장 에러 처리 참고
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())
stack.push(3)
console.log(stack.entries())
stack.pop()
console.log(stack.entries())
class Stack {
#array // private class member
constructor(array = []) {
if (!Array.isArray(array)) {
throw new Error(`${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())
stack.push(3)
console.log(stack.entries())
stack.pop()
console.log(stack.entries())
27.8.5 Array.prototype.unshift
- 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가하고 변경된 length 프로퍼티값을 반환한다.
- 원본 배열을 직접 변경한다.
27.8.6 Array.prototype.shift
-
원본 배열에서 첫 번째 요소를 제거하고 제거한 요소를 반환한다.
-
원본 배열이 빈 배열이면 undefined를 반환한다.
-
원본 배열을 직접 변경한다.
-
shift와 push 메서드를 사용하면 큐를 쉽게 구현할 수 있다.
const Queue = (function () {
function Queue(array = []) {
if (!Array.isArray(array)) {
throw new TypeError(`${array} is not an array`)
}
this.array = array
}
Queue.prototype = {
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())
queue.enqueue(3)
console.log(queue.entries())
queue.dequeue()
console.log(queue.entries())
class Queue {
#array
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()
}
entires() {
return [...this.#array]
}
}
const queue = new Queue([1, 2])
console.log(queue.entries())
queue.enqueue(3)
console.log(queue.entries())
queue.dequeue()
console.log(queue.entries())
27.8.7 Array.prototype.concat
- 인수로 전달된 값들을 원본 배열의 마지막 요소로 추가한 새로운 반환한다.
- 인수로 전달한 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가한다.
- 원본 배열은 변경되지 않는다.
- push와 unshift 메서드는 concat 메서드로 대체할 수 있다. (단 미묘한 동작 방식 차이가 있다.)
- push와 unsfhit는 원본 배열을 직접 변경하지만 concat 메서드는 원본 배열을 변경하지 않고 새로운 배열을 반환한다.
- 즉, push와 unshift는 반드시 원본 배열을 변수에 저장해 두어야하며, concat 메서드를 사용할 경우 반환 값을 변수에 할당 받아야 한다.
- 인수로 전달받은 값이 배열인 경우, push와 unsfhit는 배열을 그대로 원본 배열의 마지막/첫 번째 요소로 추가하지만 concat 메서드는 인수로 전달받은 배열을 해체하여 새로운 배열의 마지막 요소로 추가한다.
- concat 메서드는 ES6의 스프레드 문법으로 대체할 수 있다.
27.8.8 Array.prototype.splice
- unsfhit, shift, push, pop 처럼 원본 배열을 변경한다.
- 원본 배열의 중간에 요소를 추가하거나 중간에 있는 요소를 제거하는 경우 사횽한다.
- splice 에는 3개의 매개변수가 있다.
- start: 원본 배열의 요소를 제거하기 시작할 인덱스다. start만 지정하면 start부터 모든 요소를 제거하며, 음수인 경우 배열의 끝에서의 인덱스를 나타낸다. (-1이면 마지막 요소, -n이면 마지막에서 n번째 요소를 가리킨다.)
- deleteCount : 원본 배열을 제거하기 시작할 인덱스인 start부터 제거할 요소의 개수다. (0은 아무것도 제거하지 않는다.)
- items: 제거한 위치에 삽입할 요소들이다. 생략할 경우 원본 배열에서 요소들을 제거하기만 한다.
// 배열의 특정 요소 제거하기
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))
console.log(remove(arr, 10))
- filter 메서드를 사용해서 특정 요소를 제거할 수도 있지만 특정 요소가 중복되면 모두 제거된다.
27.8.9 Array.prototype.slice
- slice 메서드는 인수로 전달된 범위의 요소들을 복사하여 배열로 반환한다.
- 원본 배열은 변경되지 않는다.
- slice 메서드는 두 개의 매개변수를 갖는다.
- start : 복사를 시작할 인덱스다. 음수인 경우 배열의 끝에서 인덱스를 나타낸다.
- slice(-2)는 배열의 마지막 두 개의 욕소를 복사하여 배열로 반환한다.
- end : 복사를 종료할 인덱스다. 이 인덱스에 해당하는 요소는 복사되지 않는다.
- 생략 가능하며, 생략 시 기본 값은 length 프로퍼티 값이다.
- start : 복사를 시작할 인덱스다. 음수인 경우 배열의 끝에서 인덱스를 나타낸다.
const arr = [1, 2, 3]
// arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환한다.
arr.slice(0, 1) // -> [1]
arr.slice(1, 2) // -> [2]
// 원본은 변경되지 않는다.
console.log(arr)
const arr = [1, 2, 3]
// 배열의 끝에서부터 요소를 한 개 복사하여 반환한다.
arr.slice(-1)
// 배열의 끝에서부터 요소를 두 개 복사하여 반환한다.
arr.slice(-2)
-
slice 메서드의 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환한다.
- 이때 생성된 복사본은 얕은 복사를 통해 생성된다.
-
slice 메서드가 복사본을 생성하는 것을 이용하여 arguments, HTMLCollection, NodeList 같은 유사 객체를 배열로 변환할 수 있다.
function sum() {
// 유사 배열 객체를 배열로 변환(ES5);
var arr = Array.prototype.slice.call(arguments)
console.log(arr)
return arr.reduce(function (pre, cur) {
return pre + cur
}, 0)
}
console.log(sum(1, 2, 3))
- Array.from 을 사용하면 더욱 간단하게 유사 배열 객체를 배열로 변환할 수 있다.
27.8.10 Array.prototype.join
- join 메서드는 원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달받은 문자열, 즉 구분자(separator)로 연결한 문자열을 반환한다.
- 구분자는 생략가능하며 기본 구분자는 콤마(’,‘)다.
27.8.11 Array.prototype.reverse
- reverse 메서드는 원본 배열의 순서를 반대로 뒤집는다.
- 원본 배열이 변경되며, 반환 값은 변경된 배열이다.
27.8.12 Array.prototype.fill
-
ES6에서 도입된 fill 메서드는 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채운다.
-
원본 배열이 변경된다.
-
두 번째 인수로 요소 채우기를 시작할 인덱스를 전달할 수 있다.
-
세 번째 인수로 요소 채우기를 멈출 인덱스를 전달할 수 있다.
-
fill 메서드를 사용하면 배열을 생성하면서 특정 값으로 요소를 채울 수 있다.
-
fill 메서드로 요소를 채울 경우 모든 요소를 하나의 값만으로 채울 수밖에 없는 단점이 있다.
- Array.from 메서드를 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 요소값을 만들면서 배열을 채울 수 있다.
- Array.from 메서드는 두 번째 인수로 전달한 콜백 함수에 첫 번째 인수에 의해 생성된 배열의 요소 값과 인덱스를 순차적으로 전달하면서 호출하고, 콜백 함수의 반환값으로 구성된 배열을 반환한다.
const sequences = (length = 0) => Array.from({ length }, (_, i) => i) console.log(sequences(3))
27.8.13 Array.prototype.includes
- ES7 에서 도입된 includes 메서드는 배열 내에 특정 요소가 포함되어 있는지 확인하여 true 또는 false를 반환한다.
- 첫 번째 인수로 검색할 대상을 지정한다.
- 두 번째 인수로 검색을 시작할 인덱스를 전달할 수 있다. (기본값 0)
- 만약 음수를 전달하면 length 프로퍼티 값과 음수 인덱스를 합산하여 검색 시작 인덱스를 설정한다.
- indexOf 메서드를 사용해도 배열 내에 특정 요소가 포함되어 있는지 확인할 수 있지만, 반환값이 -1인지 확인해야 하고, NaN이 포함되어 있는지 확인할 수 없다는 문제가 있다.
27.8.14 Array.prototype.flat
- ES10에서 도입된 flat 메서드는 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화한다.
;[1, [2, 3, 4, 5]].flat() // [1,2,3,4,5]
- 중첩 배열을 평탄화할 깊이를 인수로 전달할 수 있다. (생략 시 기본값 1)
- 인수로 Infinity를 전달하면 중첩 배열 모두를 평탄화한다.
;[1, [2, [3, [4]]]].flat() // -> [1,2,[3,[4]]]
;[1, [2, [3, [4]]]].flat(1) // -> [1,2,[3,[4]]]
;[1, [2, [3, [4]]]].flat(2) // [1,2,3,[4]]
;[1, [2, [3, [4]]]].flat().flat() // [1,2,3,[4]]
;[1, [2, [3, [4]]]].flat() // [1,2,3,4]
27.9 배열 고차 함수
- 고차 함수(HOF, Higher-Order Function)은 함수를 인수로 전달받거나 함수를 반환하는 함수를 말한다.
- 고차 함수는 외부 상태의 변경이나 가변 데이터를 피하고 **불변성(immutability)**을 지향하는 함수형 프로그래밍에 기반을 두고 있다.
- 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.
27.9.1 Array.prototype.sort
- sort 메서드는 배열의 요소를 정렬한다.
- 원본 배열을 직접 변경하며 정렬된 배열을 반환한다.
- sort 메서드는 기본적으로 오름차순으로 요소를 정렬한다.
- 내림차순으로 요소를 정렬하려면 sort 메서드를 사용하여 오름차순으로 정렬한 후 reverse 메서드를 사용하여 요소의 순서를 뒤집는다.
const fruits = ["Banana", "Orange", "Apple"]
// 오름차순(ascending) 정렬
fruits.sort()
// sort 메서드는 원본 배열을 직접 변경한다.
console.log(fruits) // ['Apple', 'Banana', 'Orange']
// 내림차순(descending) 정렬
fruits.reverse()
// reverse 메서드도 원본 배열을 직접 변경한다.
console.log(fruits)
- 문자열 요소로 이루어진 배열의 정렬은 문제가 없지만, 숫자로 이루어진 배열을 정렬할 때는 주의가 필요하다.
const points = [40, 100, 1, 5, 2, 25, 10]
points.sort()
// 숫자 요소들로 이루어진 배열은 의도한 대로 정렬되지 않는다.
console.log(points) // [1, 10, 100, 2, 25, 40, 5]
- sort 메서드의 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따른다.
- 배열의 요소가 숫자 타입이라 할지라도 배열의 요소를 일시적으로 문자열로 변환한 후 유니코드 코드 포인트의 순서를 기준으로 정렬한다.
- 따라서 숫자 요소를 정렬할 때는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야한다.
- 비교 함수는 양수, 음수 또는 0을 반환해야 한다.
- 비교 함수의 반환 값이 0보다 작으면 함수 첫 번째 인수를 우선하여 정렬, 0일 땐 정렬하지 않으며, 0보다 크면 두 번째 인수를 우선하여 정렬한다.
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[(points.length - 1, points[0])])
- 객체를 요소로 갖는 배열을 정렬하는 예제는 다음과 같다.
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)
// content를 기준으로 오름차순 정렬
todos.sort(compare("content"))
console.log(todos)
27.9.2 Array.prototype.forEach
- forEach는 for 문을 대체할 수 있는 고차 함수다. forEach 메서드는 자신의 내부에서 반복문을 실행한다.
- 즉 forEach 메서드는 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출한다.
const numbers = [1, 2, 3]
const pows = []
// forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
numbers.forEach(item => pows.push(item ** 2))
console.log(pows)
- forEach 메서드의 콜백 함수는 forEach 메서드를 호출한 배열의 요소값과 인덱스, forEach 메서드를 호출한 배열 자체, 즉 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]
*/
JSON.stringify 메서드
-
JSON.stringify 메서드는 객체를 JSON 포맷의 문자열로 변환한다.
-
forEach 메서드는 원본 배열을 변경하지 않는다. 하지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
const numbers = [1, 2, 3]
// forEach 메서드는 원본 배열을 변경하지 않지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
// 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다.
// 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다.
numbers.forEach((item, index, arr) => {
arr[index] = item ** 2
})
console.log(numbers)
-
forEach 메서드 반환값은 undefined다
-
forEach 메서드의 두 번째 인수로 forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다.
-
forEach 메서드의 콜백 함수 내부의 this와 multiply 메서드 내부의 this를 일치시키려면 forEach 메서드의 두 번째 인수로 forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달한다.
class Numbers {
numberArray = []
multiply(arr) {
arr.forEach(function (item) {
this.numberArray.push(item * item)
}, this)
}
}
const numbers = new Numbers()
numbers.multiply([1, 2, 3])
console.log(numbers.numberArray)
- 더 나은 방법은 ES6의 화살표 함수를 사용하는 것이다. 화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다. 따라서 화살표 함수 내부에서 this를 참조하면 상위 스코프, multiply 메서드 내부의 this를 그대로 참조한다.
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)
-
forEach 메서드도 내부에서는 반복문을 통해 배열을 순회할 수 밖에 없다.
- 단, 반복문을 메서드 내부로 은닉하여 로직의 흐름을 이해하기 쉽게 하고 복잡성을 해결한다.
-
forEach 메서드는 for문과는 달리 break, continue 문을 사용할 수 없다. 다시 말해, 배열의 모든 요소를 빠짐없이 모두 순회하며 중간에 순회를 중단할 수 없다.
-
희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외된다.
-
forEach 메서드는 성능이 좋지는 않지만 가독성은 더 좋다. 따라서 높은 성능이 필요한 경우가 아니라면 for문 대신 forEach 메서드를 사용할 것을 권장한다.
27.9.3 Array.prototype.map
- map 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.
- 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
- 원본 배열은 변경되지 않는다.
- map 메서드는 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
- 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수다.
- map 메서드가 생성하여 반환하는 새로운 배열의 length 프로퍼티 값은 map 메서드를 호출한 배열의 length 프로퍼티 값과 반드시 일치한다.
- map 메서드를 호출한 배열과 map 메서드가 생성하여 반환한 배열은 1:1 매핑한다.
27.9.4 Array.prototype.filter
- filter 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.
- 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
- filter 메서드가 생성하여 반환한 새로운 배열의 length 프로퍼티 값은 filter 메서드를 호출한 배열의 length 프로퍼티 값과 작거나 같다.
- filter 메서드는 자신을 호출한 배열에서 특정 요소를 제거하기 위해 사용할 수도 있다.
class Users {
constructor() {
this.users = [
{ id: 1, name: "Seo" },
{ id: 2, name: "Kim" },
]
}
// 요소 추출
findById(id) {
return this.users.filter(user => user.id === id)
}
remove(id) {
return this.users.filter(user => user.id !== id)
}
}
const users = new Users()
let user = users.findById(1)
console.log(user)
// id가 1인 사용자를 제거한다.
users.remove(1)
user = users.findById(1)
console.log(user)
- filter 메서드를 사용해 특정 요소를 제거할 경우 특정 요소가 중복되어 있다면 중복된 요소가 모두 제거된다.
- 특정 요소 하나만 제거하려면 indexOf 메서드를 통해 특정 요소의 인덱스를 취급한 다음 splice 메서드를 사용한다.
27.9.5 Array.prototype.reduce
- 자신을 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다.
- 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다.
- 첫 번째 인수로 콜백 함수, 두 번째 인수로 초기값을 전달받는다.
- reduce 메서드의 콜백 함수에는 4개의 인수를 받는다.
- 초기값 또는 콜백 함수의 이전 반환값,
- reduce 메서드를 호출한 배열의 요소값
- 인덱스
- reduce 메서드를 호출한 배열 자체, this
// 1부터 4까지 누적을 구한다.
const sum = [1, 2, 3, 4].reduce(
(accumulator, currentValue, index, array) => accumulator + currentValue,
0,
)
console.log(sum)
- 위 코드에서 reducer 메서드의 콜백 함수는 4개의 인술르 전달받아 배열의 length 만큼 총 4회 호출된다.
- 콜백함수의 반환값과 두 번째 요소값을 콜백 함수의 인수로 전달하면서 호출한다.
- reduce 메서드는 하나의 결과값을 반환한다.
- reduce 메서드는 자신을 호출한 배열의 모든 요소를 순회하며 하나의 결과값을 구해야 하는 경우 사용한다.
평균 구하기
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)
최대값 구하기
const values = [1, 2, 3, 4, 5]
const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0)
console.log(max)
요소의 중복 횟수 구하기
const fruits = ["banana", "apple", "orange", "orange", "apple"]
const count = fruits.reduce((acc, cur) => {
// 첫 번째 순회 시 acc는 초기값인 {}dlrh cur은 첫 번째 요소인 'banana'다
// 초기값으로 전달받은 빈 객체 요소값인 cur을 프로퍼티 키로, 요소의 개수를 프로퍼티 값으로 할당한다.
// 만약 프로퍼티 값이 undefined임녀 프로퍼티 값을 1로 초기화한다.
acc[cur] = (acc[cur] || 0) + 1
return acc
}, {})
console.log(count)
배열 평탄화 하기
const values = [1, [2, 3], 4, [5, 6]]
const flatten = values.reduce((acc, cur) => acc.concat(cur), [])
console.log(flatten)
- flat 메서드를 활용하는 것이 더 직관적이다.
중복 요소 제거
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]
const result = values.reduce((acc, cur, i, arr) => {
// 순회 중인 요소의 인덱스가 자신의 인덱스라면, 처음 순회하는 요소다.
// 이 요소만 초기값으로 전달받은 배열에 담아 반환한다.
// 순회 중인 요소의 인덱스가 자신의 인덱스가 아니라면, 중복된 요소다.
if (arr.indexOf(cur) === i) acc.push(cur)
return acc
}, [])
console.log(result)
- filter를 사용하는 것이 직관적이다.
- 중복 요소 제거시 Set을 사용할 수도 있다.
- 모든 배열의 고차 함수는 reduce 메서드로 구현할 수 있다.
- 두 번째 인수인 초기값이 옵션이므로 생략할 수 있지만, 언제나 초기값을 전달하는 것이 안전하다.
27.9.6 Array.prototype.some
- some 메서드는 콜백 함수의 반환값이 단 한번이라도 참이면 true, 모두 거짓이면 false를 반환한다.
- 배열의 요소 중에 콜백 함수를 통해 정의한 조건을 만족하는 요소가 1개 이상 존재하는지 확인하여 그 결과를 불리언 타입으로 반환한다.
- 단, some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환한다.
27.9.7 Array.prototype.every
- 콜백 함수의 반환값이 모두 참이면 true, 단 한번이라도 거짓이면 false를 반환한다.
- 즉, 배열의 모든 요소가 콜백 함수를 통해 정의한 조건을 모두 만족하는지 확인하여 그 결과를 불리언 타입으로 반환한다.
- 단, 빈 배열인 경우 언제나 true를 반환한다.
27.9.8 Array.prototype.find
- 인수로 전달된 콜백 함수를 호출하여 반환값이 true인 첫 번째 요소를 반환한다.
- 반환값이 true인 요소가 존재하지 않는다면 undefined를 반환한다.
- find의 결과값은 배열이 아닌 해당 요소값이다.
27.9.9 Array.prototype.findIndex
- 인수로 전달된 콜백함수를 호출하여 반환값이 true인 첫 번째 요소의 인덱스를 반환한다.
- 콜백 함수의 반환값이 true인 요소가 존재하지 않으면 -1을 반환한다.
27.9.10 Array.prototype.flatMap
- map 메서를 통해 생성된 새로운 배열을 평탄화한다.
- 즉, map과 flat 메서드를 순차적으로 실행하는 효과가 있다.
- flatMap 메서드는 깊이를 지정할 수 없이 1단계만 평탄화한다.