[JS] Array와 Array 메소드

❗️ 자바스크립의 배열(인척 하는 객체)

- 일반 배열

  • 같은 데이터 타입으로 이루어짐
  • 배열의 각 요소가 동일한 크기의 메모리 공간을 차지함
  •  메모리 공간이 빈틈 없이 연속적으로 나열되어 있음

이런 배열을 밀집 배열(dense array)라고 한다. 

 

- 자바스크립트의 배열

  • 다양한 타입의 요소들이 저장될 수 있음
  • 배열의 요소들이 동일한 크기의 메모리 공간을 가지지 않음
  • 메모리 공간이 연속적으로 이어져 있지 않을 수도 있음

이런 배열을 희소 배열(sparse array)라고 한다.

 

결론적으로 자바스크립트의 배열은 배열의 동작을 흉내 낸 특수한 객체이다.

 

이러한 특성 때문에 인덱스로 접근하면 일반 배열에 비해 느리지만

특정 요소를 탐색하거나 요소를 삽입/삭제 하는 경우에는 일반 배열보다 빠르다.

🤔 어떻게 하나의 배열에 여러 타입이 저장될 수 있는걸까?

console.log(Object.getOwnPropertyDescriptors(['a','b',1]))
/*
{
  '0': { value: 'a', writable: true, enumerable: true, configurable: true },
  '1': { value: 'b', writable: true, enumerable: true, configurable: true },
  '2': { value : 1, writable: true, enumerable: true, configurable: true },
  length: { value: 3, writable: true, enumerable: false, configurable: false }
}
*/

 

콘솔 창에 배열 정보를 출력해보면 위와 같이 나온다.

인덱스를 key로 가지고, length 프로퍼티를 가지는 특수한 객체이다.

 

value의 값으로 배열의 요소가 저장되는데,

자바스크립트의 객체는 어떤 값이든 프로퍼티로 저장할 수 있기 때문에 여러 타입이 저장될 수 있는 것이다.


❗️ 배열 vs 객체

가장 큰 차이점은 '순서'이다.

배열 : 순서가 존재하고, 순서는 인덱스로 구성되어 있다. 구성하는 값을 요소라고 부른다.

객체 : key : value 형태로 구성된 순서가 존재하지 않는 집합이다. 구성하는 값을 프로퍼티라고 부른다.

 

또한 배열의 프로토타입 객체는 Array.prototype이고 객체의 프로토타입 객체는 Object.prototype이다.


❗️ 배열 생성

- 배열 리터럴

가장 간단한 방법으로는 대괄호 안에 요소를 넣어줄 수 있다.

const arr = ['a', 'b'];

 

- Array 생성자

const arr = new Array(10);

배열의 길이를 인자로 받아 배열을 생성한다.

0을 전달하거나 아무것도 전달하지 않으면 길이가 0인 배열을 생성한다.

 

인자를 2개 이상 전달하면 Array.of와 동일하게 동작한다.

const arr = new Array(1, 2, 3);

위와 같이 쓰면  [1, 2, 3]이 생성되는 것이다.

 

- Array.of()

전달된 인자로 배열을 생성한다.

const arr = Array.of(1, 2, 3);

위와 같이 쓰면  [1, 2, 3]이 생성되는 것이다.

 

- Array.from()

유사 배열 객체와 이터러블을 인자로 받아 배열을 생성한다.

유사 배열 객체 : key가 인덱스이고 length 프로퍼티를 가지는 객체
이터러블  : 자료를 반복할 수 있는 객체

 

첫번째 인자로 배열을, 두번째 인자로 콜백함수를 넣어준다.

Array.from("abc") // ["a", "b", "c"]
Array.from([1, 2, 3], x => x + x ); // [2, 4, 6]

콜백 함수를 각 요소에 적용하는 걸 볼 수 있다.

Array.from(
	{length :20},
    () => Array(10).fill(0)
);

 

아래의 결과는 무엇일까?

0으로 초기화된 20 * 10의 이차원 배열이다.

 

{ length : 20 }이 유사배열로 인식되었고, 각 요소에 길이가 10이고 0으로 채워진 배열을 만든다.

 

또한 1~10을 인자로 가지는 배열을 생성하고 싶을 때도 유용하다

for문을 사용하지 않고 만들 수 있다.

const arr = Array.from(Array(10), (_, index) => index + 1);

❗️ splice, slice 

이름이 비슷해서 헷갈릴 수 있는 splice, slice 함수를 알아보자!

 

- slice

slice(start, end)

배열의 일부분을 추출하여 새로운 배열을 반환한다.(원본 배열 변경 X)

 

  • start(필수) : 시작 인덱스
  • end(선택) : 종료 인덱스
    종료 인덱스 - 1까지의 값을 추출한다.생략한다면 배열의 끝까지 추출한다.
const arr = [1, 2, 3, 4, 5];
const arr2 = arr.slice(0,2); 	// [1, 2]

시작 인덱스와 종료 인덱스에 음수 값을 사용한다면 배열의 끝부터 역방향으로 계산한다.

 

주의할 점

slice 함수는 얕은 복사를 하기 때문에, 

배열의 요소가 객체나 배열인 경우 원본 배열이나 복사된 배열의 값이 변경되면 둘 다 변경된다.

const obj1 = [{name: 'a'}, {name: 'b'}];
const obj2 = obj1.slice(0, 1);
obj2[0].name = 'c';
console.log(obj1);		// [{name: 'c', name: 'b'}]

 

- splice

splice(start, deleteCount, item1, item2 ...)

배열의 값을 추가, 제거, 교체하기 위해 사용된다. (원본 배열 변경 O)

  • start(필수) : 시작 인덱스
  • deleteCount(선택) : 제거할 요소의 개수
    생략하면 시작 인덱스부터 배열의 끝까지의 요소가 제거된다.
  • item1, item2 ... (선택) : 배열에 삽입할 요소
    시작 인덱스 이후부터 삽입된다.
const arr = [1, 2, 3, 4, 5];
arr.splice(0,3);	// arr = [4, 5]
arr.splice(0, 2, 5, 4);		// arr = [5, 4, 3]

splice 함수는 삭제된 값을 반환하기 때문에 아래처럼 삭제된 값도 알 수 있다.

const arr = [1, 2, 3, 4, 5];
const removed = arr.splice(0, 2);
console.log(removed);		// [1, 2]

❗️ reduce

reduce(callback(accumulator, currentValue, index, array), initialValue)

배열이나 객체를 순차적으로 순회하며 하나의 결과 값으로 줄여 반환한다.

  • callback(필수) : callback 함수
    • accumulator(필수) : 반환 값을 누적하는 곳
      initalValue 제공 시, initialValue 할당된다.
    • currentValue(필수) : 현재 처리할 요소
    • currentIndex(선택) : 현재 인덱스
      initialValue가 있으면 0, 없으면 1으로 시작한다.
    • array(선택) : 원본배열
  • initialValue(선택) : 초기 값
    초기 값을 제공하지 않으면 배열의 첫 번째 요소를 사용한다.

배열/객체의 합이나 최대 값 구하는 예시는 많으니 다른 예시를 봐보자.

reduce 함수로 그룹화를 할 수 있다.

 

아래와 같은 객체 배열이 있다고 하자.

reduce 함수를 사용해서 도시별로 사람을 그룹화 할 수 있다.

const people = [
  { name: 'Alice', city: 'New York' },
  { name: 'Bob', city: 'Los Angeles' },
  { name: 'Charlie', city: 'New York' },
  { name: 'David', city: 'Chicago' },
  { name: 'Eve', city: 'Los Angeles' }
];

 

const groupedByCity = people.reduce((accumulator, person) => {
  if (!accumulator[person.city]) {
    accumulator[person.city] = [];
  }
  
  accumulator[person.city].push(person);
  
  return accumulator;
}, {});

console.log(groupedByCity);

객체 배열을 순회하면서 

해당 도시가 아직 accumulator에 없다면 빈 배열을 추가해주고

accumlator에 존재한다면 사람을 넣어주면 된다.

그룹화가 잘 된 것을 확인할 수 있다!

생각보다 간단해서 놀랐다..!


❗️ map, filter


공통점은 원본 배열을 변경시키지 않으면서 새로운 배열을 반환한다는 점이다.

차이점은 map은 콜백함수가 적용된 새로운 요소, filter는 조건문을 만족하는 요소들을 반환한다는 점이다.

 

- map

map(callback(currentValue, index, array), newValue)

콜백 함수가 리턴하는 값은 새로운 배열이다.

 

-filter

filter(callback(currentValue, index, array), newValue);

콜백 함수가 리턴하는 값은 boolean이다.

콜백 함수의 실행 결과가 조건에 맞으면 true, 아니면 false를 반환해서 최종적으로 true인 요소들만 반환하는 것이다.

const people = [
	{ name: "a", age: 20 }, 
    { name: "b", age: 30 }, 
    { name: "c", age: 20 }, 
];

다음과 같은 객체 배열이 있다.

 

map 함수를 사용해서 이름만 담긴 배열을 생성하고,

filter 함수를 사용해서 나이가 20인 사람의 정보만 담긴 배열을 생성하는 예시를 보자

const names = people.map(person => person.name); 
const age20 = people.filter(person => person.age === 20);

console.log(names);		// ['a', 'b', 'c']
console.log(age20);		// [{name: 'a', age: 20}, {name: 'c', age: 20}]

 

가장 중요한 특징은 기존 배열을 건드리지 않고 아예 새로운 배열을 생성한다는 것이다.

map과 filter 함수의 결과로 반환된 배열을 수정해도 원본 배열은 손상되지 않는다.


❗️ every, some

every 함수는 배열의 모든 요소가 조건을 만족하는지를 확인하고

some 함수는 배열에 조건을 만족하는 요소가 하나라도 존재하는지를 확인한다.

 

const arr = [1, 2, 3, 4, 5];
console.log(arr.some(x => x > 3));		// true
console.log(arr.every(x => x > 3 ));	// false

 

every 함수는 조건에 만족하지 않는 요소를 발견하자마자 함수를 중단한다 => return true

마찬가지로 some 함수는 조건에 만족하는 요소를 발견하자마자 함수를 중단한다 => return false


❗️ spread

객체의 값을 복사하고 싶을 때 유용한 문법이다.

const obj = { name: "heeyeon" };
const obj2 = obj;
obj2.name = "donguri";

console.log(obj);		// donguri
console.log(obj2);		// donguri

위와 같은 방법으로 객체를 복사하면 얕은 복사가 되어서 변경된 값이 원본 객체에도 적용이 된다.

하지만 spread 문법을 사용하면 깊은 복사가 되어 위의 문제를 해결할 수 있다.

const obj = { name: "heeyeon" };
const obj2 = {...obj}
obj2.name = "donguri";

console.log(obj);		// heeyeon
console.log(obj2);		// donguri

원본 객체의 값이 변하지 않는 것을 볼 수 있다.

 

🚨 주의할 점

spread 방식은 1 레벨 깊이에서만 동작한다.

const a = {
	name : "heeyeon",
	info : { age : 20 },
};

const b = {...a};
b.info.age = 23;
console.log(a);

분명 나는 b 객체의 age를 바꿨는데,

a 객체를 출력해보면 a 객체 age 속성의 값까지 바뀌었다.

 

2 레벨 깊이부터는 얕은 복사를 진행한다는 점을 유의하고 사용하자! 


❗️ for .. of, for .. in, forEach

배열을 순회하기 위한 방법으로는 for .. of, for .. in, forEach 4가지가 존재한다.

const arr = ['a', 'b', 'c'];

for(let value of arr) {
	console.log(value);		// a b c 
}

for(let value in arr) {
	console.log(value);		// 0 1 2 
}

arr.forEach((value) => {
	console.log(value);		// a b c
});

 

 

- for .. of

  • forEach와 다르게 break, continue, return을 콜백함수에서 사용할 수 있다.
  • iterable한 객체(배열, 문자열, Map객체 ..)에 대해 속성 값을 순회한다.
  • 일반 객체에 사용하면 타입에러가 뜬다.

 

- for .. in

  • for .. in 문은 일반 객체의 key를 순회하기 위해 만들어진 문법이다.
  • 객체의 key만 반복할 수 있고, value에는 접근할 수 없다.
  • 배열에도 사용할 수 있지만, 객체 사용하는 것을 권장한다.
    for .. in으로 순회하면 객체의 prototype chain으로 상속받은 속성들도 함께 순회하기 때문이다.

- forEach

  • break, continue, return을 콜백함수에서 사용할 수 없다.
  • await를 콜백 함수 내부에 쓸 수 없다.
  • 배열의 각 요소에 콜백 함수가 적용한다는 점에서 map()과 비슷하지만,
    forEacah는 반환 값이 없다는 차이점이 존재한다.

 

간단하게 정리하자면,

배열에는 for .. of 또는 for Each

배열의 인덱스에 접근해야 하면 forEach

객체에는 for .. in을 사용하는 것을 권장한다!


❗️ 배열에서 비동기 함수 사용

map과 forEach에서는 비동기 함수를 사용하면 안된다.

 

🤔 콜백 함수에 async/await로 비동기 함수를 넣는다면?

콜백 함수를 호출만 하고, 비동기 함수의 실행이 끝날때까지 기다려주지 않는다.

따라서 모든 작업이 완료되기 전에 반복이 종료될 수 있고, 작업의 순서가 어긋날 수 있다.

 

배열에서 비동기 함수를 올바르게 사용하는 방법을 알아보자.

 

- Promise.all + map

배열의 모든 요소에 대해 비동기 작업을 병렬로 처리한다.

각 요소에 대해 비동기 함수를 호출하고, 결과로 Promise 객체 배열을 반환한다.

Promise.all을 이용해서 모든 Promise가 완료될 때 까지 기다린다.

하지만 작업이 완료되는 순서가 보장되지 않는다.

 

- for .. of

각 요소에 대해 await를 사용하여 비동기 함수를 실행하기 때문에 병렬 처리 방식은 아니다.

따라서 실행 시간이 오래걸린다는 점이 있다.

하지만 작업이 완료되는 순서가 보장된다.

 

- reduce

기본적으로는 병렬 처리를 하지 않는다.

이전의 비동기 작업이 완료될 때까지 기다리고, 모든 반복이 끝나면 결과를 배열로 반환해준다.

따라서 작업이 완료되는 순서가 보장된다.

 

Promise.all과 결합하면 병렬 처리가 가능하다.

Promise가 완료되면,

완료된 순서가 아닌 배열의 순서에 맞춰 결과 배열에 저장되기 때문에 순서도 보장할 수 있다.

'HTML,CSS,JS' 카테고리의 다른 글

[JS] Map, Set(객체? 배열?)  (0) 2024.12.08
[CSS] RequestAnimationFrame  (0) 2024.10.08
[JS] Class  (0) 2024.08.13
[JS] Prototype  (0) 2024.08.08
[JS] closure  (0) 2024.08.07