seunghyun Note

27장(배열) 본문

스터디/모던자바스크립트 deep dive

27장(배열)

승숭슝현 2024. 1. 8. 10:36

1. 배열이란

배열은 여러 개의 값을 순차적으로 나열한 자료구조. 배열이 가지고 있는 값을 요소(elements)라고 부른다. 원시값은 물론 객체, 함수, 배열 등 모든 값은 배열의 요소가 될 수 있다.

자신의 위치를 나타내는 0이상의 정수인 인덱스(index)를 갖는다.
배열은 요소의 개수, 즉 배열의 길이를 나타내는 length 프로퍼티를 갖는다.
배열은 위처럼 length, 인덱스 프로퍼티를 갖기 때문에 for문을 통해서 순차적으로 요소에 접근 가능
배열이라는 타입은 존재하지 않는다. 배열도 객체다.

구분객체배열
구조프로퍼티 키와 프로퍼티 값인덱스와 요소
값의 참조프로퍼티 키인덱스
값의 순서XO
length프로퍼티XO

2. 자바스크립트의 배열은 배열이 아니다.

배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조이며 이러한 배열을 밀집 배열(dense array)라 부른다.
반면에 희소 배열(sparse array)은 메모리 공간이 동일한 크기가 아니어도 되며, 연속적이지 않을 수도 있다.
자바스크립트 배열은 엄밀히 말하면 일반적인 배열이 아니며, 배열의 동작을 흉내낸 특수한 객체.
자바스크립트 배열의 요소는 사실 프로퍼티 값이다.
일반적인 배열은 인덱스로 요소에 빠르게 접근할 수 있다. 그러나 특정 요소 검색, 삽입 삭제는 효율적이지 않다
자바스크립트는 해시 테이블로 구현된 객체. 접근은 느릴 수밖에 없는 단점이 있지만 삽입 또는 삭제, 검색은 일반적인 배열보다 빠르다.

3. length 프로퍼티와 희소 배열

length 프로퍼티는 요소의 개수 === 가장 큰 인덱스에 +1 한 값과 동일하다. length 프로퍼티의 값은 요소를 추가하거나 삭제하면 자동으로 갱신

length 프로퍼티 값은 0 ~ 2^32 - 1 미만의 양의 정수 를 가진다.
즉, 최대 2^32 - 1 개의 요소 를 가질 수 있다.
기본적으로는 배열의 길이를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당도 가능
이 경우, 현재 프로퍼티의 length 프로퍼티 값보다 작은 숫자 값을 할당할 경우 배열의 길이가 줄어든다.
현재 프로퍼티의 length 프로퍼티 값보다 큰 숫자 값을 할당할 경우는 length 프로퍼티 값은 변경되지만, 실제 배열의 길이는 변함없다.
이것은 length 프로퍼티가 값 없이 비어 있는 요소를 위해 메모리 공간을 확보하지 않으며 빈 요소를 생성하지 않음을 의미
이처럼 배열의 요소가 연속적으로 위치하지 않고 일부가 비어 있는 배열 == 희소 배열(sparse array)
자바스크립트 엔진은 희소 배열을 문법적으로 허용한다.
하지만, 희소 배열은 의도적으로 생성하는 일은 많지 않기 때문에 가능한 사용하지 않는 것이 좋다.
가능한, 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선

4. 배열 생성

  1. 배열 리터럴
    배열을 생성하는 가장 간단한 방식
    배열 리터럴에 요소를 생략하면 희소 배열이 생성
// 배열의 요소를 생략하면 "희소 배열"생성
const arr = [1, , 3];

console.log(arr.length); // 3
console.log(arr); // [ 1, <1 empty item>, 3 ]
console.log(arr[1]); // undefined

2.Array 생성자 함수

인수의 개수에 따라 다르게 동작한다.

전달된 인수가 1개 이며 숫자일 경우 → length 프로퍼티 값이 인수인 배열을 생성

전달된 인수가 없는 경우 → 빈 배열 생성

전달된 인수가 2개 이상이거나 숫자가 아닌 경우 → 인수를 요소로 갖는 배열을 생성

// 인수가 1개이며 정수인 경우 -> 희소 배열 생성
const arr = new Array(10);

console.log(arr); // [ <10 empty items> ]
console.log(arr.length); // 10
console.log(Object.getOwnPropertyDescriptors(arr)); // { length: { value: 10, writable: true, enumerable: false, configurable: false } }

new Array(4294967295);
new Array(4294967296); // RangeError: Invalid array length
new Array(-1); // RangeError: Invalid array length

// 인수가 없는 경우 -> 빈 배열 생성
new Array(); // []

// 인수가 2개 이상이거나 숫자가 아닌 경우 -> 인수를 요소로 갖는 배열을 생성
new Array(1, 2, 3); // [1,2,3]
new Array({}); // [{}]
  1. Array.of 메서드

ES6에 도입
전달된 인수를 갖는 배열을 생성
인수가 1개이고 숫자여도 인수를 요소로 갖는 배열을 생성

const arr1 = Array.of(1);
const arr2 = Array.of(1, 2, 3);
const arr3 = Array.of("string");

console.log(arr1); // [ 1 ]
console.log(arr2); // [ 1, 2, 3 ]
console.log(arr3); // [ 'string' ]
  1. Array.from 메서드

ES6에 도입
유사 배열 객체(array-like object) or 이터러블 객체(iterable object) 를 인수로 전달받아 배열로 변환 후 반환
두 번째 인수로 함께 전달할 콜백 함수를 통해 값을 만들면서 요소를 채울 수도 있다.

// 유사 배열 객체를 배열로 변환
const arr1 = Array.from({ length: 2, 0: "a", 1: "b" });

// 이터러블 객체를 배열로 변환
// 문자열은 "이터러블 객체"이기도 하다.
const arr2 = Array.from("Hi");

console.log(arr1); // [ 'a', 'b' ]
console.log(arr2); // [ 'H', 'i' ]

7 배열 요소의 삭제

delete 연산자는 객체의 프로퍼티를 삭제한다. 따라서 위 예제의 delete arr[1]은 arr에서 프로퍼티 키가 ‘1’인 프로퍼티를 삭제한다. 이때 배열은 희소 배열이 되며 length 프로퍼티 값은 변하지 않는다.

따라서 희소 배열을 만드는 delete 연산자는 사용하지 않는 것이 좋다.

Array.prototype.indexOf

indexOf 메서드는 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환한다.

indexOf 메서드는 배열에 특정 요소가 존재하는지 확인할 때 유용하다

const foods = ['apple', 'banana', 'orange'];

// foods 배열에 'orange' 요소가 존재하는지 확인한다.
if (foods.indexOf('orange') === -1) {
  // foods 배열에 'orange' 요소가 존재하지 않으면 'orange' 요소를 추가한다.
  foods.push('orange');
}

console.log(foods); // ["apple", "banana", "orange"]

Array.prototype.push 메서드

인수로 전달받은 모든 값을 원본 배열 마지막 요소로 추가 , 변경된 length 프로퍼티 값을 반환

mutator method

부수 효과가 있으므로, ES6의 스프레드 문법을 사용하는 편이 좋다.
성능 측면에서 배열에 추가할 요소가 하나라면 마지막 배열 요소를 직접 추가하는 방법이 더 빠르다.

const arr = [1, 2];

arr.push([3, 4]);
console.log(arr); // [ 1, 2, [ 3, 4 ] ]

arr.push("a", "b");
console.log(arr); // [ 1, 2, [ 3, 4 ], 'a', 'b' ]

const arr2 = [...arr, true];
console.log(arr2); // [ 1, 2, [ 3, 4 ], 'a', 'b', true ]

Array.prototype.pop 메서드

원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환

원본 배열이 빈 배열이면 undefined 반환

// 클래스로 구현한 push와 pop 메서드를 활용한 "스택 자료구조"
class Stack {
  #array;

  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 ]

let pop = stack.pop();
console.log(stack.entries(), pop); // [ 1, 2 ] 3

Array.prototype.unshift 메서드

const arr = [1, 2];

let result = arr.unshift(3, 4);
console.log(result); // 4
console.log(arr); // [ 3, 4, 1, 2 ]

const newArr = [100, ...arr];
console.log(newArr); // [ 100, 3, 4, 1, 2 ]

Array.prototype.shift 메서드

const arr = [1, 2];

let shift = arr.shift();
console.log(shift); // 1
console.log(arr); // [ 2 ]

Array.prototype.concat 메서드

인수로 전달된 값들(배열 or 원시값)을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환

인수로 전달한 값이 배열인 경우, 배열을 해체하여 새로운 배열의 요소로 추가

const arr1 = [1, 2];
const arr2 = [3, 4];

const arr3 = arr1.concat(arr2);
console.log(arr3); // [ 1, 2, 3, 4 ]
console.log(arr1, arr2); // [ 1, 2 ] [ 3, 4 ]

const arr4 = arr3.concat("a", true);
console.log(arr4); // [ 1, 2, 3, 4, 'a', true ]

Array.prototype.slice

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

Array.prototype.join 메서드

원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달받은 문자열, 즉 구분자(separator)로 연결한 문자열을 반환

구분자는 생략 가능하며 default separator 는 콤마(,)다.

const arr = [1, 2, 3, 4];

console.log(arr.join()); // 1,2,3,4
console.log(arr.join(":")); // 1:2:3:4
console.log(arr.join("")); // 1234

Array.prototype.flat

중첩 배열을 평탄화할 깊이를 인수로 전달할 수 있다. 인수를 생략할 경우 기본값은 1이다. 인수로 Infinity 를 전달하면 중첩 배열 모두를 평탄화한다.

// 중첩 배열을 평탄화하기 위한 깊이 값의 기본값은 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]

9. 배열 고차 함수

고차 함수는 외부 상태의 변경이나 가변(mutable) 데이터를 피하고 불변성(immutability)을 지향하는 함수형 프로그래밍에 기반을 두고 있다.

함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을
제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.

Array.prototype.sort 메서드

배열의 요소를 정렬, 정렬된 배열을 반환

default 로는 오름차순 정렬이다.

const fruits = ["Banana", "Orange", "Apple"];

fruits.sort();
console.log(fruits); // [ 'Apple', 'Banana', 'Orange' ]

mutator method

기본적인 정렬 순서는 유니코드 코드 포인트의 순서를 따른다.

배열의 요소들을 정렬 시, 숫자 타입이어도 암묵적으로 문자열 타입으로 변환 후 유니코드 코드 포인트 순서에 따라 정렬
따라서, 숫자 요소를 정렬 시에는 정렬 순서를 정의하고 비교 함수(compare function)를 인수로 전달해야 한다.
비교 함수는 양수 or 음수 or 0을 반환해야 한다.
양수 : 비교 함수의 두 번째 인수를 우선 정렬
음수 : 비교 함수의 첫 번째 인수를 우선 정렬
0 : 정렬하지 않음

const numbers = [40, 100, 1, 5, 2, 25, 10];

numbers.sort();
console.log(numbers); // [1, 10, 100, 2, 25, 40, 5] << 제대로 된 오름차순 정렬이 아님

numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 2, 5, 10, 25, 40, 100]

Array.prototype.forEach 메서드

[1, 2, 3].forEach((item, idx, arr) => {
  console.log(`요소 값 : ${item}, 인덱스 : ${idx}, this : ${arr}`);
});
// 요소 값 : 1, 인덱스 : 0, this : 1,2,3
// 요소 값 : 2, 인덱스 : 1, this : 1,2,3
// 요소 값 : 3, 인덱스 : 2, this : 1,2,3

자신의 내부에서 반복문을 실행

즉, 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출한다.
각 배열의 요소에 대해 한 번씩 콜백 함수가 호출된다.
콜백 함수는 일반 함수로 적용되기 때문에, this의 참조는 undefined가 바인딩된다. ( 고차함수에서는 strict mode 가 반영되기 때문, 아닐 경우 전역 객체를 가리킨다. )

class Numbers {
  numberArray = [];

  mul(arr) {
    arr.forEach(function (item) {
      // forEach 의 인수로 전달하는 콜백함수가 "일반함수"인 경우, this 참조는 undefined 를 바인딩
      // TypeError: Cannot read property 'numberArray' of undefined
      this.numberArray.push(item * item);
    });
  }
}

const numbers = new Numbers();
numbers.mul([1, 2, 3]);

Array.prototype.map 메서드

콜백 함수의 반환값들로 구성된 새로운 배열을 반환

accessor method

원본 배열의 요소값을 다른 값으로 매핑(mapping)한 새로운 배열을 생성하기 위한 고차 함수

const arr = [1, 2, 3];
const mappingArr = arr.map((item) => Math.pow(item, 2));

console.log(arr); // [ 1, 2, 3 ]
console.log(mappingArr); // [ 1, 4, 9 ]

Array.prototype.filter 메서드

콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환

accessor mehtod

특정 조건에 맞는 배열의 요소들을 추출할 때 유용하게 사용된다.

const arr = [1, 2, 3, 4, 5];
const filteredArr = arr.filter((item) => item < 4);

console.log(arr); // [ 1, 2, 3, 4, 5 ]
console.log(filteredArr); // [ 1, 2, 3 ]

Array.prototype.reduce 메서드
콜백 함수를 호출하여 하나의 결과값을 만들어 반환

매 차례마다, 콜백 함수의 반환값과 두 번째 요소값을 콜백 함수의 인수로 전달하면서 호출한다.
accessor method

4개의 매개변수를 가진다.

accumulator : 누적될 값
currentValue : 현재 조회하는 값
index : 현재 조회하는 값의 인덱스
array : 원본 배열 참조(this)

const arr = [1, 2, 3, 4, 5];

arr.reduce((acc, cur, idx, arr) => {
  console.log(acc, cur, idx, arr);
  return acc + cur;
}, 0);
// 0 1 0 [ 1, 2, 3, 4, 5 ]
// 1 2 1 [ 1, 2, 3, 4, 5 ]
// 3 3 2 [ 1, 2, 3, 4, 5 ]
// 6 4 3 [ 1, 2, 3, 4, 5 ]
// 10 5 4 [ 1, 2, 3, 4, 5 ]

Array.prototype.some 메서드

콜백 함수의 반환값이 단 한 번이라도 참이면 true, 모두 거짓이면 false를 반환

즉, 배열의 요소 중에 콜백 함수를 통해 정의한 조건을 만족하는 요소가 1개 이상 존재하는지 확인하여 그 결과를 boolean 타입으로 반환

const odds = [1, 3, 5, 7];

// odds 배열 내에는 조건인 짝수가 하나도 없으므로, false 반환
console.log(
  odds.some((item) => {
    console.log(item);
    return item % 2 === 0;
  }),
);
// 1
// 3
// 5
// 7
// false

// odds 배열 내에는 첫 요소 1부터 홀수 이므로, 하나라도 조건에 만족하는 요소가 있으니 true 반환
console.log(
  odds.some((item) => {
    console.log(item);
    return item % 2 !== 0;
  }),
);
// 1
// true

Array.prototype.every 메서드

콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false를 반환

즉, 배열의 모든 요소가 콜백 함수를 통해 정의한 조건을 모두 만족하는지 확인하여 그 결과를 boolean 타입으로 반환

단, 빈 배열인 경우 언제나 true를 반환

const odds = [1, 3, 5, 7];

// odds 배열 내에는 조건인 짝수가 하나도 없으므로, false 반환
console.log(
  odds.every((item) => {
    console.log(item);
    return item % 2 === 0;
  }),
);
// 1
// false

// odds 배열 내에는 첫 요소 1부터 홀수 이므로, 하나라도 조건에 만족하는 요소가 있으니 true 반환
console.log(
  odds.every((item) => {
    console.log(item);
    return item % 2 !== 0;
  }),
);
// 1
// 3
// 5
// 7
// true

Array.prototype.find 메서드
ES6에 도입

자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여 true를 반환하는 첫 번째 요소를 반환

true를 반환하는 요소가 존재하지 않는 경우 undefined 를 반환

const users = [
  {
    id: 1,
    name: "YI",
  },
  {
    id: 2,
    name: "KI",
  },
  {
    id: 3,
    name: "WI",
  },
  {
    id: 4,
    name: "MI",
  },
];

console.log(users.find((item) => item.id === 3)); // { id: 3, name: 'WI' }
console.log(users.find((item) => item.name === "YOUNG")); // undefined

Array.prototype.findIndex 메서드

ES6에 도입

findIndex 메서드는 콜백 함수에 정의한 조건에 대해 true를 반환하는 요소의 인덱스를 반환

true를 반환하는 요소가 존재하지 않는 경우 -1을 반환

const users = [
  {
    id: 1,
    name: "YI",
  },
  {
    id: 2,
    name: "KI",
  },
  {
    id: 3,
    name: "WI",
  },
  {
    id: 4,
    name: "MI",
  },
];

console.log(users.findIndex((item) => item.id === 3)); // 2;
console.log(users.findIndex((item) => item.name === "YOUNG")); // -1
728x90

'스터디 > 모던자바스크립트 deep dive' 카테고리의 다른 글

29장(Math)  (1) 2024.01.08
28장(Number)  (1) 2024.01.08
26장 (ES6 함수의 추가 기능)  (1) 2024.01.08
25장(클래스)  (0) 2024.01.08
24장 (클로저)  (0) 2024.01.08