Array 생성에 대한 단상

이상문
6 min readOct 17, 2020

--

다른 언어도 그렇겠지만 자바스크립트에서 배열을 사용하는 것에 익숙해지면, 많은 일들을 편하고 문제없이 작업할 수 있습니다. 3종 세트인 map, reduce, filter에 대해서는 말할 필요도 없습니다. 요즘 하는 작업에서 멀티 채널을 다루는 부분이 많았는데, 그래서 16개나 64개와 같은 고정 크기의 배열을 다루는 경우가 많았습니다. 몇 가지 놓치고 있던 부분에서 알게 된 부분이 있었는데 이 부분을 정리해두고자 합니다.

기본적인 배열 생성

배열을 생성하는 방법은 Array 함수를 이용하는 방법과 배열 리터럴을 이용하는 방법이 있습니다. 값을 각각을 지정해서 생성할 때는 당연히 배열 리터럴을 이용하는 것을 권장합니다.

const firstArray = [0, 1, 2, 3];

많은 원소를 가지는 배열 생성

그런데, 많은 원소를 가지는 배열은 어떻게 생성하면 될까요. 물론 앞선 방법과 같은 방법을 이용할 수 있지만, 16개, 64개나 되는 배열을 직접 입력한다고 생각하면 한숨이 나옵니다.

Array는 이 때 필요합니다. 16개의 공간을 가지는 배열을 만들어 보죠.

const secondArray = new Array(16);
// (16) [empty × 16]

16개의 공간은 확보했지만, 속이 모두 비어 있습니다. 갑자기 map이 떠오르네요. 이 메서드를 이용해서 값을 채워 봅시다.

secondArray.map((item, index) => index);
// (16) [empty × 16]

유감스럽게도, 결과값은 여전히 16개의 공간은 확보되어 있지만, 비어있는 배열입니다. 이것은 map 메서드의 사양입니다. 다음 MDN web docs 문서를 보면 이 내용을 확인할 수 있습니다.

mapcallback 함수를 각각의 요소에 대해 한번씩 순서대로 불러 그 함수의 반환값으로 새로운 배열을 만듭니다. callback 함수는 (undefined도 포함해서) 배열 값이 들어있는 인덱스에 대해서만 호출됩니다. 즉, 값이 삭제되거나 아직 값이 할당/정의되지 않은 인덱스에 대해서는 호출되지 않습니다.

Array를 통해서 공간 확보는 했지만, 값이 들어있지 않기 때문에 이런 결과가 나온 것입니다. 그렇다면 값을 넣을 수 있는 방법은 무엇일까요.

배열에 값 채우기

배열의 메서드 중에 fill 이 있습니다. 다시 값 채우기를 도전해 봅시다.

const thirdArray = secondArray.fill(1)
// (16) [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

값을 채우기는 했지만, 만족스럽지는 않다. 1로만 채워지기 때문입니다.

배열에 순차적인 값 채우기

여러가지 방법들을 고안해볼 수 있을 것 같습니다. 무엇보다 먼저, 앞에서 채워진 배열을 이용한 방법을 써 봅시다.

const fourthArray = thirdArray.map((item, index) => index);
// (16) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

재미있는 팁이 있는데, 배열에도 key가 있다는 것을 아시나요? 우리가 firstArray[0] 과 같이 접근할 수 있는 이유도 0이라는 key에 값이 연결되어 있기 때문입니다. 흥미롭게도, 배열에는 keys 라는 메서드가 존재합니다.

secondArray.keys();
// Array Iterator {}

그런데, Array Iterator로 리턴이 되네요. iterator는 spread(…) 연산자를 이용하면 배열값을 얻을 수 있습니다.

const fifthArray = [...secondArray.keys()];
// (16) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

한 가지 또 유용한 메서드가 있는데, Array.from 입니다. 이 함수는 배열이나 유사 배열 객체로부터 배열을 만들어 줍니다. iterator는 이 유사 배열 객체에 포함이 되죠.

const sixthArray = Array.from(secondArray.keys());
// (16) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

초기값 설정에서 fill 과 map을 이용했을 때 차이

이 부분도 16개나 64개의 채널을 생성하면서 뒤늦게 깨달은 부분입니다. 우선 결론부터 얘기하면, 각 원소의 생성의 방식에서 차이가 납니다. 즉, fill 에서는 한번만 생성을 하여 각 원소에 할당하고, map의 경우에는 각 원소마다 생성하여 할당합니다.

이 부분의 예는 실제 실수한 코드를 들어 얘기하는 것이 좋을 것 같습니다. 64의 canvas 원소를 초기에 만들어 놓고, 이를 pool 방식으로 접근해서 사용하는 방식으로 구현하는 중이었습니다. 64개의 배열을 만들고, fill 로 채우면 되리라고 간단히 생각했죠.

const convasArray = Array(64).fill(document.createElement('canvas'));
// (64) [canvas, canvas, canvas, ... canvas, canvas, canvas, canvas]

그러나, 이것은 오류를 만들어내는 재앙의 코드였죠. 바로 64개의 원소가 모두 하나의 객체를 가리키고 있었던 겁니다. 결국 canvas를 제어를 하면 마지막에 canvas를 추가한 DOM 객체에만 출력이 되는 문제점을 낳았습니다.

64개의 원소에는 각각 독립적인 canvas 인스턴스가 필요합니다. 좀 복잡해지기는 했지만, 다음과 같은 코드로 대응을 했습니다.

const convasArray = Array.from(Array(64).keys()).map(document.createElement('canvas'));

정리하면,

  1. 배열을 간단히 생성할 때는 배열 리터럴을 사용합니다.
  2. 많은 원소를 가지는 배열을 생성할 경우는 Array 함수를 사용하는 것이 유리할 수도 있습니다.
  3. 원소를 채울 때는 fill 메서드나 map 메소드를 이용할 수 있지만, 사용하는 경우는 다릅니다. 원시 타입(primitive type)을 채울 경우에는 fill 이 유리할 것입니다. 그러나 각 원소마다 동일한 타입이지만 인스턴스가 달라야 할 경우에는 map을 써야 합니다.

--

--

이상문
이상문

Written by 이상문

software developer working mainly in field of streaming, using C++, javascript

No responses yet