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.spliceArray.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.sliceArray.prototype.slice(startIndex, endIndex)return: 인수로 전달된 범위의 요소들을 복사하여 배열로 반환- start : 복사를 시작할 인덱스, 음수 인덱싱을 지원함
 - end : 복사를 종료할 인덱스, 해당 인덱스는 미포함
            
- end 생략시 기본값 length 프로퍼티 값 (즉, 존재하는 마지막 인덱스 까지)
 
 
- 원본배열을 직접 변경하지 않음
 - 얕은 복사를 통해 생성됨
 - 유사 배열 객체(arguments, HTMLCollection, NodeList)를 배열로 변환 가능
        
- 물론, Array.from 메서드가 더 간단하게 변경 가능
 
 - 주의) slice(직접 변경X) vs splice(직접 변경O)
 
Array.prototype.joinArray.prototype.join(구분자)- 원본 배열의 모든 요소를 문자열로 변환
 return: 인수로 전달받은 구분자로 연결한 문자열을 반환- 구분자 생략시 기본 구분자는 콤마(,)
 
Array.prototype.reserve- 원본 배열의 순서를 반대로 뒤집음
 - 원본 배열이 변경됨
 return: 변경된 원본 배열 (원본과 연결되어 있음)
Array.prototype.fillArray.prototype.fill(fillValue, startIndex, endIndex)- 인수로 전달받은 값을 배열의 특정 범위를 요소로 채움
        
- fillvalue : 배열에 요소를 채울 특정 값
            
- fillvalue만 있으면 모든 요소를 변경함
 
 - startIndex : fillvalue로 채우기 시작할 index
 - endIndex : fillvalue로 채우기를 멈출 미포함 index
            
- endIndex 생략시 끝까지
 
 
 - fillvalue : 배열에 요소를 채울 특정 값
            
 return: 변경된 원본 배열 (원본과 연결되어 있음)- 원본 배열이 변경됨
 - empty도 채움
 - 모든 요소를 하나의 값만으로 채울 수만 있음
        
Array.from을 사용하면, callback함수로 특정 값(여러 값)을 만들면서 인수로 채울수 있음
 
Array.prototype.includesArray.prototype.includes(searchValue, searchStartIndex)- 배열 내에 특정 요소가 포함되어 있는지 확인
        
- searchValue : 검색할 값
 - searchStartIndex : 검색 시작할 인덱스
            
- 생략시, 기본값 0
 - 음수 전달시, (length + index) 검색 시작(즉, 음수 인덱싱 지원)
 
 
 return: true or falseindexOf를 사용해서도 요소가 포함되어 있는지 확인 가능함(-1을 반환하는지 확인)- NaN은 판별이 불가함
 
Array.prototype.flatArray.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];
  }
}