JavaScript
20210711 JavaSciprt DeepDive 09 : 배열, JS배열, 배열 생성, CRUD, 배열 메서드, 스택(stack) 구현, 큐(queue) 구현
JavaScript Deep Dive 09
용어 및 중요사항 정리
배열 메서드 | 내용 | return |
---|---|---|
new Array() |
배열 생성 (생성자 함수) | Instance |
Array.of(itemValue) |
배열 생성 | 전달된 요소로 구성된 배열 |
Array.from((유사배열, 이터러블 객체), Callback) |
배열 변환 및 가공 | 객체를 배열로 만들고 callback 반환값으로 구성된 배열을 반환 |
Array.isArray(인수) |
전달된 인수가 배열인지 아닌지 확인 | true, false |
Array.prototype.indexOf(searchValue, startSearchIndex) |
요소검색 | 첫번째로 검색된 요소의 인덱스 |
Array.prototype.includes(searchValue, searchStartIndex) |
배열 내에 특정 요소가 포함되어 있는지 확인 | true, false |
Array.prototype.push(인수) |
원본배열의 마지막 요소로 추가 | 변경된 length 프로퍼티 값을 반환 |
Array.prototype.pop() |
원본배열의 마지막 요소 제거 | 제거한 요소 반환 |
Array.prototype.unshift(인수) |
원본배열의 선두에 요소 추가 | 변경된 length 프로퍼티 값을 반환 |
Array.prototype.shift() |
원본배열에서 첫번째 요소 제거 | 제거한 요소 반환, 빈 배열시 undefined 반환 |
Array.prototype.concat() |
마지막 요소로 추가한 새로운 배열 반환 | 새로운 배열 반환 |
Array.prototype.splice(start, deleteCount, items) |
원본배열 추가, 제거, 수정 | 제거한 요소가 배열로 반환됨 |
Array.prototype.slice(startIndex, endIndex) |
원본배열을 직접 변경하지 않고 추출, 얕은 복사 | 인수로 전달된 범위의 요소들을 복사하여 배열로 반환 |
Array.prototype.reserve() |
원본 배열의 순서 반대로 변경 | 변경된 원본 배열 |
Array.prototype.fill(fillValue, startIndex, endIndex) |
인수로 전달받은 값을 배열의 특정 범위를 요소로 채움 | 변경된 원본 배열 |
Array.prototype.flat(flatDepth) |
중첩 배열 평탄화 | 평탄화된 배열 반환 |
배열
- 배열이라는 타입은 존재하지 않음 -> 배열은 객체 타입임
요소(Element)
: 배열이 가지고 있는 값- JS에서는 모든 값은 배열의 요소가 될 수 있음 (원시값, 객체, 함수, 배열 ...)
- 요소 접근시 대괄호 표기법 [index] 사용
인덱스(Index)
: 배열에서 자신의 위치를 나타내는 0이상의 정수length 프로퍼티
: 배열의 길이를 나타내는 프로퍼티 -> for문을 통해 순차적으로 요소 접근 가능생성 방식
: 배열 리터럴, Array 생성자 함수, Array.of 메서드, Array.from 메서드Array.prototype
: 배열의 프로토 타입 객체, 빌트인 메서드 제공일반 객체 vs 배열
구분 | 객체 | 배열 |
---|---|---|
구조 | 프로퍼티 키와 프로퍼티 값 | 인덱스와 요소 |
값의 참조 | 프로퍼티 키 | 인덱스 |
값의 순서 | X | O |
length 프로퍼티 | X | O |
자료구조에서 말하는 일반적인 배열
밀집배열
: 동일한 크기의 메모리 공간 하나의 데이터 타입으로 통일되어 서로 연속적으로 인접해 있는 배열배열의 장점
: 순차적(or 역순)으로 요소에 접근 가능 (반복문을 사용하기 적합한 자료구조)- 임의 접근 : 인덱스를 활용해 한번의 연산으로 임의의 요소에 접근 가능 -> 고속 동작
배열의 단점
:- 선형 검색 : 정렬되지 않은 배열에서 검색하는 경우 모든 요소를 처음부터 발견할 때 까지 연산 해야함 시간 복잡도 O(n)
- 요소의 이동 의무 : 배열에 요소를 삽입, 삭제하는 경우 연속을 유지하기 위해서 요소를 이동시켜야 함
자바스크립트의 배열
희소 배열
: 각각의 메모리 공간 동일 크기 X, 연속 X, 중간 중간에 빌수도 있음- 성능적으로 좋지 않음
JS 배열
:- 희소 배열을 지원 함
- 일반적인 배열의 동작을 흉내낸 특수한 객체임
- 인덱스를 나타내는 문자열 프로퍼티, length 프로퍼티를 갖음
- 해시 테이블로 구현된 객체
JS 배열의 요소
: 프로퍼티 값 , js에서 사용가능한 모든 값은 객체의 프로퍼티 값이 될수 있음 -> 모든 타입의 값은 배열의 요소가 가능- 배열의 요소는 최대 2^32 - 1개까지 가질수 있음
/*
프로퍼티 키(인덱스) - 프로퍼티 값(데이터)
프로퍼티 키(length) - 프로퍼티 값(프로퍼티 개수)
{
'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}
}
*/
const arr = [
"string",
10,
true,
null,
undefined,
NaN,
Infinity,
[],
{},
function () {},
];
JS 배열의 장점/ 단점
: 검색, 삽입, 삭제의 경우에는 빠름/ 요소 접근(인덱싱)은 조금 느림 (그래도, 일반 객체 보단 2배 빠름)
length 프로퍼티 값
: 요소의 개수를 나타내며, 0이상 ~ 2^32-1 미만의 양의 정수 까지 값을 나타냄 (배열의 요소 개수 제한 때문에)- 가장 큰 인덱스 + 1
- 배열에 요소 추가, 삭제시 length 프로퍼티 값 자동 갱신
- length 프로퍼티 값에 명시적으로 값을 할당하는 경우,
- 기존 프로퍼티 길이 보다 작은 숫자 할당 -> 해당 길이로 변환되어 나옴(넘치던 것은 삭제)
- 기존 프로퍼티 길이 보다 큰 숫자 할당 -> length 프로퍼티 값은 변경, 실제 배열은 변화없음
- 비어있는 값은 empty로 표시 됨
- 희소배열 length와 배열 요소의 개수가 일치하지 않음
- 언제나, 희소배열 length > 배열의 요소 개수
- JS 엔진은 타입이 일치하는 요소 배열을 생성시 일반적인 배열처럼 연속 공간 확보함
- 배열에는 같은 타입의 요소를 연속적으로 위치 시키자
const arr = [1, , 3, , 5];
console.log(arr); // [1, empty, 3, empty, 5]
console.log(Object.getOwnPropertyDescriptors(arr));
/*
0: {value: 1, writable: true, enumerable: true, configurable: true}
2: {value: 3, writable: true, enumerable: true, configurable: true}
4: {value: 5, writable: true, enumerable: true, configurable: true}
length: {value: 5, writable: true, enumerable: false, configurable: false}
*/
배열 생성
배열 리터럴
: 프로퍼티 키 없이 값만 표시하는 방식 ([]대괄호와 쉼표 활용)- 요소 생략시 희소 배열 생성
Array 생성자 함수
:- 주의) 전달된 인수의 개수에 따라 다르게 동작함
- 전달된 인수가 없을 때 -> 빈 배열 생성 ([ ])
- 전달된 인수가 숫자이고 1개인 경우 -> length 값을 할당하듯이 작동 함 (요소 생성X)
- 전달된 인수가 2개 이상이거나 숫자가 아닌경우 -> 해당 인수를 요소로 갖는 배열 생성
- 전달된 인수가 숫자가 아니면 1개여도 요소로 인식
- new 연산자 상관없이 생성자함수, 일반함수 호출시 모두 생성자 함수로 동작함 (내부적으로 new.target을 확인함)
- 주의) 전달된 인수의 개수에 따라 다르게 동작함
Array.of 메서드
- 전달된 인수를 요소로 갖는 배열 생성
- 전달된 인수가 1개이고 숫자여도 인수를 요소로 갖는 배열 생성
Array.from 메서드
Array.from(객체, 콜백함수)
첫번째 인수
: 유사 배열 객체 또는 이터러블 객체를 전달받아 배열로 변환하여 반환유사 배열 객체
: 인덱스로 프로퍼티 값에 접근 가능하고, length 프로퍼티 갖는 객체 (for문으로 순회 가능)- length만 있는 유사배열 객체는 length 만큼 undefined를 가진 요소를 생성
이터러블 객체
: Symbol.iterator 메서드로 구현한 객체로, for...of 문 순회 가능하고, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할수 있는 객체
두번째 인수
: 콜백함수를 받아 첫번째 인수에 의해 생성된 배열의 요소값return
: 인덱스를 순차적으로 전달하면서 호출하여 callback 반환값으로 구성된 배열을 반환
// 유사 배열 객체 -> 배열
Array.from({ length: 2, 0: "a", 1: "b" }); // [a, b]
Array.from({ length: 2 }); // [undefined, undefined]
Array.from({ length: 2, 0: "a", 1: "b" }, (v, i) => i); // [0, 1]
// 이터러블 -> 배열
Array.from("Hello"); // ['H', 'e', 'l', 'l', 'o']
배열 요소의 참조, 추가, 갱신, 삭제 (CRUD)
참조
Array[index]
: 대괄호 표기법으로 참조함- index는 정수로 평가되는 표현식이면 넣을 수 있음
- 존재하지 않는 요소 접근시 undefined 반환 (객체에서 존재하지 않는 프로퍼티 키로 접근했을 때 undefined 반환과 같음)
추가와 갱신
- 추가 : 존재하지 않는 인덱스를 사용해 값을 할당하면 요소가 추가됨
- 현재 배열의 length 프로퍼티 값보다 큰 인덱스로 새로운 요소 추가시 희소 배열이 됨
- 명시적으로 값을 할당하지 않으면, 요소는 생성되지 않음
- 정수또는 정수 형태의 문자열을 인덱스로 사용하지 않으면, 요소가 아니라 프로퍼티가 생성됨 (생성된 프로퍼티는 length에 영향을 주지 않음)
- 갱신 : 이미 요소가 존재하는 요소에 값을 재할당 하면 갱신됨
const arr = [0];
arr[1] = 1;
console.log(arr); // [0 ,1]
console.log(arr.length); // 2
arr[40] = 40;
console.log(arr); // [0, 1, empty x38 ,40]
console.log(arr.length); // 41
arr[1] = 2;
console.log(arr); // [0, 2, empty x38 ,40]
삭제
delete 연산자 사용 방식
- 배열도 객체이기 때문에, delete 연산자로 프로퍼티 키로 프로퍼티를 특정하여 삭제
- 희소배열이 되고, length 프로퍼티 값은 변하지 않음
- 비추천
Array.prototype.splice 메서드 방식
Array.prototype.splice(startIndex, deleteCount)
- 희소배열 안만들고 배열의 특정 요소 완전히 삭제 가능
- length 프로퍼티는 자동으로 갱신됨
배열 메서드
원본 배열 직접 변경 메서드(mutator method)
: 예) push- 직접 변경하는 것 보다 반환 메서드를 사용하는게 좋음 (외부상태를 직접 변경하는 부수 효과를 줄이기 위해서)
새로운 배열 생성 반환 메서드(accessor method)
: 예) concat
Array.isArray(인수)
:- Array 생성자 함수의 정적 메서드
- 전달된 인수가 배열인지 아닌지 true, false 반환
- 유사 배열 객체 체크 확인 가능 (false)
Array.prototype.indexOf(searchValue, startSearchIndex)
:- 원본 배열에서 인수로 전달된 요소를 검색하여 첫번째로 검색된 요소의 인덱스 반환
- 원본 배열에 인수로 전달된 요소가 존재하지 않으면 -1 반환
- 배열에 특정요소 존재하는지 확인시 유용
Array.prototype.includes(searchValue)
- 원본 배열에서 인수로 전달된 요소가 존재하는지 확인 -> true / false
Array.prototype.push(인수)
- 인수로 전달 받은 모든 값을 원본 배열의 마지막 요소로 추가
return
: 변경된 length 프로퍼티 값을 반환- 원본 배열 직접 변경 -> 부수 효과 O
스프레드 문법
: 부수 효과 없이 마지막 요소를 추가할 수 있음
arr[arr.length] = value
- 추가할 요소가 하나 뿐이라면 직접적으로 값을 할당해서 추가하는게 빠름
- 배열의 length 프로퍼티 값을 인덱스로 하여 동적으로 값할당해서 추가 방식
Array.prototype.pop()
- 원본 배열에서 마지막 요소 제거
return
: 제거한 요소 반환- 빈 배열이면 undefined 반환
- 원본 배열을 직접 변경 -> 부수 효과 O
Array.prototype.unshift(인수)
- 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가
return
: 변경된 length 프로퍼티 값을 반환- 원본 배열 직접 변경 -> 부수효과 O
- 스프레드 문법 사용 추천
Array.prototype.shift()
- 원본 배열에서 첫번째 요소를 제거
return
: 제거한 요소 반환, 빈 배열시 undefined 반환- 원본 배열 직접 변경 -> 부수효과 O
Array.prototype.concat()
return
: 인수로 전달된 값들(배열 또는 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열 반환- 전달된 값이 배열인 경우, 배열 해체해서 요소로 추가
- 원본 배열은 변경되지 않음
- 스프레드 문법으로 대체 가능
- push, unshift, pop, shift 사용시 원본 배열을 반드시 변수에 저장해 두어야 함
Array.prototype.splice
Array.prototype.splice(start, deleteCount, items)
- 원본 배열의 중간에 요소를 추가하거나, 중간에 있는 요소를 제거하는 경우 사용
- 원본배열 직접 변경
- start : 원본 배열의 요소 제거 시작할 인덱스
- start만 넣으면 start 부터 모든 요소 제거
- start 음수 인덱스(역순 인덱스) 지원
- deleteCount: start 부터 제거할 요소의 개수 (옵션)
- 0으로 하면 아무런 요소도 제거하지 않고 items를 넣을 수 있음
- items : 제거한 위치에 삽입할 요소들의 목록, 생략시 요소들을 제거하기만 함 (옵션)
return
: 제거한 요소가 배열로 반환됨
특정 요소 제거는
indexOf + splice
조합으로 가능- indexOf로 특정 값 인덱스를 찾고 해당 인덱스 값으로 splice를 돌려 제거하거나, 수정 등이 가능
// 단일 제거
function remove(array, item) {
const index = array.indexOf(item);
if (index !== -1) array.splice(index, 1);
return array;
}
// 중복 제거
function removeAll(array, item) {
return array.filter((v) => v !== item);
}
Array.prototype.slice
Array.prototype.slice(startIndex, endIndex)
return
: 인수로 전달된 범위의 요소들을 복사하여 배열로 반환- start : 복사를 시작할 인덱스, 음수 인덱싱을 지원함
- end : 복사를 종료할 인덱스, 해당 인덱스는 미포함
- end 생략시 기본값 length 프로퍼티 값 (즉, 존재하는 마지막 인덱스 까지)
- 원본배열을 직접 변경하지 않음
- 얕은 복사를 통해 생성됨
- 유사 배열 객체(arguments, HTMLCollection, NodeList)를 배열로 변환 가능
- 물론, Array.from 메서드가 더 간단하게 변경 가능
- 주의) slice(직접 변경X) vs splice(직접 변경O)
Array.prototype.join
Array.prototype.join(구분자)
- 원본 배열의 모든 요소를 문자열로 변환
return
: 인수로 전달받은 구분자로 연결한 문자열을 반환- 구분자 생략시 기본 구분자는 콤마(,)
Array.prototype.reserve
- 원본 배열의 순서를 반대로 뒤집음
- 원본 배열이 변경됨
return
: 변경된 원본 배열 (원본과 연결되어 있음)
Array.prototype.fill
Array.prototype.fill(fillValue, startIndex, endIndex)
- 인수로 전달받은 값을 배열의 특정 범위를 요소로 채움
- fillvalue : 배열에 요소를 채울 특정 값
- fillvalue만 있으면 모든 요소를 변경함
- startIndex : fillvalue로 채우기 시작할 index
- endIndex : fillvalue로 채우기를 멈출 미포함 index
- endIndex 생략시 끝까지
- fillvalue : 배열에 요소를 채울 특정 값
return
: 변경된 원본 배열 (원본과 연결되어 있음)- 원본 배열이 변경됨
- empty도 채움
- 모든 요소를 하나의 값만으로 채울 수만 있음
Array.from
을 사용하면, callback함수로 특정 값(여러 값)을 만들면서 인수로 채울수 있음
Array.prototype.includes
Array.prototype.includes(searchValue, searchStartIndex)
- 배열 내에 특정 요소가 포함되어 있는지 확인
- searchValue : 검색할 값
- searchStartIndex : 검색 시작할 인덱스
- 생략시, 기본값 0
- 음수 전달시, (length + index) 검색 시작(즉, 음수 인덱싱 지원)
return
: true or falseindexOf
를 사용해서도 요소가 포함되어 있는지 확인 가능함(-1을 반환하는지 확인)- NaN은 판별이 불가함
Array.prototype.flat
Array.prototype.flat(flatDepth)
- 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화 함(중첩 배열 평탄화)
- flatDepth : 평탄화 할 깊이 또는 회수, 생략시 기본값 1 (안에 첫번째 중첩배열이 1)
- Infinity를 전달하면, 중첩 배열 모두를 평탄화 시킴
- flatDepth : 평탄화 할 깊이 또는 회수, 생략시 기본값 1 (안에 첫번째 중첩배열이 1)
스택 구현하기 (push, pop)
- LIFO (last in first out)
- 가장 나중에 넣은 데이터를 먼저 꺼내는 후입선출
- 생성자 함수 방식
- array가 인스턴스의 프로퍼티라서 인스턴스에서 접근 가능
const Stack = (function () {
function Stack(array = []) {
if (!Array.isArray(array)) {
throw new TypeError(`${array} is not an Array.`);
}
// 인스턴스의 array 프로퍼티 = 생성자 함수에 들어온 array
this.array = array;
}
Stack.prototype = {
constructor: Stack,
push(value) {
return this.array.push(value);
},
pop() {
return this.array.pop();
},
// instance에 계속 연결되게 하기 싫어서
entries() {
return [...this.array];
},
};
// 설정이 끝난 생성자 함수 반환
return Stack;
})();
const stack = new Stack([1, 2]);
console.log(stack.entries()); // [1, 2]
console.log(stack.push(5)); // 3 <- length 값 반환
console.log(stack.entries()); // [1, 2, 5]
console.log(stack.pop()); // 5 <- 제거한 값 반환
console.log(stack.entries()); // [1, 2]
- 클래스 방식으로 구현
- #array로 private로 설정 했기 때문에 인스턴스에서 참조, 접근하지 못함
class Stack {
#array; // private class method
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];
}
}
큐 구현하기 (push, shift)
- FIFO (frist in first out)
- 가장 먼저 넣은 데이터를 먼저 꺼내는 선입 선출
- 생성자 함수 방식
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()); // [1, 2]
console.log(queue.enqueue(5)); // 3 <- length 값 반환
console.log(queue.entries()); // [1, 2, 5]
console.log(queue.dequeue()); // 1 <- 제거한 값 반환
console.log(queue.entries()); // [2, 5]
- 클래스 방식
class Queue {
#array; // private
constructor(array) {
if (!Array.isArray(array)) {
throw 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];
}
}