자바스크립트 교과서 - 9. 배열

Seoyong Lee·2023년 10월 3일
0

개발 공부

목록 보기
11/21
post-thumbnail

배열의 특징

  • 배열(array)은 여러 개의 값을 순차적으로 나열한 자료구조다.
  • 배열은 요소(element)로 이루어지며 모든 값은 배열의 요소가 될 수 있다.
  • 배열은 사실 객체의 한 종류라고 볼 수 있다.

    자바스크립트는 처음 배포 당시 배열을 포함시키지 못했습니다. 자바스크립트의 객체가 워낙 강력해서 배열이 빠졌다는 사실을 알아채는 경우는 드물었지만요. 성능 문제만 무시한다면 객체는 배열이 할 수 있는 모든 것을 할 수 있습니다.

    더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p81.

  • 일반적으로 자료구조(data structure)에서 말하는 배열은 밀집 배열(dense array) 이지만 자바스크립트 배열은 희소 배열(sparse array)을 허용하며 둘은 다음과 같은 차이가 있다.
    • 밀집 배열은 배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 가지지만 희소 배열은 그렇지 않다.
    • 밀집 배열은 연속적으로 이어져 있지만 희소 배열은 일부가 비어 있을 수 있다.
  • 희소 배열인 경우 배열의 length와 실제 요소의 개수가 일치하지 않을 수 있으므로 굳이 이렇게 만들지 않는 것이 좋다.

    이처럼 자바스크립트의 배열은 엄밀히 말해 일반적 의미의 배열이 아니다. 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체다.

    이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p496.


인덱스와 길이

  • 배열의 요소는 자신의 위치를 나타내는 인덱스(index)를 갖는다.
  • 인덱스는 0부터 시작하며 이는 대부분의 프로그래밍 언어가 동일하다.

    1960년대 중반, 작지만 영향력 있는 개발자 그룹이 1 대신 0부터 시작하면 어떻겠느냐고 제안했습니다. 그래서 오늘날 거의 모든 개발자는 수를 셀 때 0부터 시작합니다. …0에서 시작하는 것이 off-by-one 에러를 유발하기 때문에 정확하지 않다는 주장도 있지만, 이 또한 확실하지는 않습니다.

    더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p82.

  • 위에서 언급된 off-by-one 에러는 반복문 등에서 종료 조건을 명시할 때, 그 값을 1 작게 혹은 1 크게 지정해서 문제가 생기는 경우로 최근에는 배열을 함수처럼 다루려는 경향 때문에 크게 문제가 되지는 않을 것 같다.

    배열을 다룰 때는 한 번에 하나의 요소만 처리해야 한다는 생각은 최소한 포트란 시절까지는 거슬러 올라가야 맞는 말입니다. 최근에는 배열 요소를 한 번에 하나씩 처리하기보다, 배열을 좀 더 함수처럼 처리해야 한다는 생각이 더 지배적입니다. 이렇게 해야 명시적인 반복문 처리가 없어져서 코드가 단순해지고, 멀티프로세서에 작업을 분산해서 처리할 수 있는 능력이 생깁니다. 잘 설계된 언어로 잘 짜이니 프로그램은 배열이 0에서 시작하든 1에서 시작하든 신경 쓸 필요가 없습니다.

    더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p82-83.

  • 배열의 길이를 반환하는 length 프로퍼티는 바로 이 인덱스 중 가장 큰 값에 1을 더한 값이다. length는 배열에 요소를 추가하거나 삭제하면 자동 갱신된다.
  • 참고로 length 프로퍼티에 임의의 숫자 값을 할당할 수도 있다.
    • 현재 length 프로퍼티 값보다 작은 숫자를 할당하면 배열의 길이는 줄어든다.

      const arr = [1, 2, 3];
      arr.length = 2;
      console.log(arr); // [1, 2]
    • 현재 length 프로퍼티 값보다 큰 숫자를 할당하면 배열 마지막에 빈 영역이 생기지만 실제 새 요소가 추가되지는 않는다.

      const arr = [1];
      arr.length = 3;
      console.log(arr); // [1, empty * 2]

배열 생성

배열 리터럴

  • 가장 일반적인 방식이다. 주의할 점은 요소 없이 콤마만 작성해도 중간이 빈 희소 배열로 생성된다.
const arr = [1, 2, 3];
const arr2 = [1, ,3];
console.log(arr2); // [1, empty, 3]

Array 생성자

  • Array 생성자는 다음 세 가지 방법으로 호출이 가능하다.
    • 인자 없이 호출
      • let a = new Array();
    • 배열 길이 인자 하나로 호출
      • let a = new Array(10);
    • 배열 요소를 두 개 이상 쓰거나 숫자가 아닌 요소를 하나만 넘겨 호출
      • let a = new Array(1, 2, 3, "testing");

Array.of

  • 위 생성자 방식으로는 숫자 요소가 하나만 있는 배열의 생성이 불가능해 ES6에서 추가되었다.
Array.of(10) // [10]

Array.from

  • ES6에서 도입된 팩토리 메서드로 첫 번째 인자로 이터러블 객체(iterable object)나 유사 배열 객체(array-like object)를 받아 새 배열을 만들어 반환한다.
    • 유사 배열 객체란 length 프로퍼티를 가지면서 인덱스로 프로퍼티 값에 접근할 수 있는 객체를 말한다.
Array.from({ length: 2, 0: 'a', 1: 'b' }); // ['a', 'b']

배열 메서드

  • 배열의 다양한 메서드를 알아두면 유용하게 쓸 수 있다.
  • 배열 메서드의 결과물 반환 패턴은 다음 두 방식이 있다.
    • 원본 배열을 직접 변경하는 메서드(mutator method)
    • 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드(accessor method)

검색

  • 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환한다.
  • 그러나 NaN이 포함되어 있는지 확인할 수 없다는 문제가 있다.
  • ES7에서 도입된 includes 메서드를 사용하면 불리언 형태를 반환하기 때문에 가독성이 더 좋다.
const arr = [1, 2, 3];
arr.indexOf(2); // 1
arr.indexOf(4); // -1
arr.includes(2); // true
arr.includes(4); // false
[NaN].indexOf(NaN) !== -1; // false
[NaN].includes(NaN); // true

스택과 큐

  • 스택과 큐에 해당하는 메서드들은 모두 원본 배열을 직접 변경한다.
  • 스택에 해당하는 메서드는 다음과 같다.
    • pop 메서드는 배열의 가장 마지막 요소를 제거하고 반환한다.
    • push 메서드는 새로운 요소를 배열 끝에 추가하고 변경된 length 프로퍼티 값을 반환한다.
  • 큐에 해당하는 메서드는 다음과 같다.
    • shift 메서드는 0번째 요소를 제거하고 반환한다.
    • unshift 메서드는 배열의 가장 앞에 새로운 요소를 추가하고 변경된 length 프로퍼티 값을 반환한다.

결합과 분리

  • concat 메서드는 인수로 전달된 값들을 원본 배열 마지막 요소로 추가한 새로운 배열을 반환한다.
  • 원본 배열은 변경되지 않는다.
const arr1 = [1, 2];
const arr2 = [3, 4];
console.log(arr1.concat(arr2)); // [1, 2, 3, 4]
  • splice 메서드는 원본 배열의 중간에 요소를 추가하거나 중간 요소를 제거하는 경우 사용한다.
  • 3개의 매개변수를 받으며 원본 배열을 직접 변경한다. .splice(start, deleteCount, items)
    • start: 제거를 시작할 인덱스
    • deleteCount: start에서 부터 제거할 요소의 개수
    • items: 제거 위치에 삽입할 요소들의 목록
const arr = [1, 2, 3, 4];
const res = arr.splice(1, 2, 20, 30);
console.log(res); // [2, 3]
console.log(arr); // [1, 20, 30, 4]
  • slice 메서드는 인수로 전달된 범위의 요소들을 복사하여 배열로 반환한다. 원본 배열은 변경되지 않는다.
  • 다음 2개의 매개변수를 받는다.
    • start: 복사를 시작할 인덱스
    • end: 복사를 종료할 인덱스
const arr = [1, 2, 3];
arr.slice(0, 1); // [1]

채우기

  • fill 메서드는 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채운다. 원본 배열은 변경된다.
const arr = [1, 2, 3];
arr.fill(0); // [0, 0, 0]

기타

  • join 메서드는 원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달받은 구분자(seperator)로 연결한 문자열을 반환한다.
  • 구분자는 생략 가능하며 기본 구분자는 콤마(,)다.
const arr = [1, 2, 3, 4];
arr.join(); // '1,2,3,4' 
  • reverse 메서드는 원본 배열의 순서를 반대로 뒤집어 변경된 배열을 반환한다. 원본 배열은 변경된다.
  • flat 메서드는 ES10에서 도입되었으며, 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화한다.
[1, [2, 3, 4, 5]].flat(); // [1, 2, 3, 4, 5]
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]

배열 고차 함수

  • 고차 함수(Higher-Order Function)는 함수를 인수로 전달받거나 함수를 반환하는 함수를 말한다.

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

함수형 프로그래밍은 순수 함수(pure function)와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경를 피하려는 프로그래밍 패러다임이다. 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 하여 가독성을 해치고, 변수는 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있기 때문이다. 함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환이라고 할 수 있다.

이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p529.

Array.prototype.sort

  • sort 메서드는 배열의 요소를 정렬한다. 그러나 안정성이 높은 편은 아니라고 한다.

자바스크립트의 sort 메서드에는 몇 가지 문제가 있습니다. sort 메서드는 추가 메모리 공간을 사용하지 않고 배열 자체를 수정합니다. …다음 문제점은 안정성이 부족하다는 것입니다. 배열의 요소를 비교했을 때 같은 값, 즉 비교 함수가 영(0)을 반환하는 경우 두 요소의 상대적인 위치를 그대로 유지한다면 sort 함수는 안정적입니다. 하지만 자바스크립트는 안정성을 보장하지 않습니다.

더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p89-90

  • 참고로 sort 메서드는 quicksort 알고리즘을 사용했으나 동일한 요소가 중복된 경우 초기 순서와 변경될 수 있는 불안정성 때문에 ECMAScript2019(ES10)에서 timesort 알고리즘을 사용하도록 변경되었다.
  • 주의점으로 sort의 기본 정렬 순서는 유니코드 포인트의 순서를 따르므로 숫자를 정렬하는 경우 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 한다.

Array.prototype.forEach

  • for 문을 대체할 수 있는 고차 함수로 수행할 처리를 콜백 함수로 전달받아 반복 호출한다.
  • forEach 메서드는 원본 배열을 변경하지 않는다. 반환값은 언제나 undefined이다.
  • forEach 메서드의 폴리필을 살펴보면 결국 내부적으로는 for 반복문을 통해 순회하는 것을 볼 수 있다. 반복문을 메서드 내부에 숨겨 로직의 흐름을 이해하기 쉽도록 처리하는 고차 함수의 특징을 확인할 수 있다.
if (!Array.prototype.forEach) {
	Array.prototype.forEach = function(callback, thisArg) {
		// 첫 번째 인수가 함수가 아니면 TypeError 발생
		if(typeof callback !== 'function'){
			throw new TypeError(callback + 'is not a function');
		}
		// this로 사용할 두 번째 인수를 받지 못하면 전역 객체를 this로 사용한다.
		thisArg = thisArg || window;

		for(var i = 0; i < this.length; i++) {
			// call 메서드를 통해 thisArg를 전달하면서 콜백 함수를 호출한다.
			callback.call(thisArg, this[i], i, this);	
		}
	};
}
  • for 문과 다른 점은 break, continue 문을 사용할 수 없다는 점이다. 배열의 모든 요소를 빠짐없이 모두 순회해야 하며 순회를 중단할 수 없다.

Array.prototype.map

  • map은 forEach와 비슷하게 배열의 모든 요소를 순회하지만 전달받은 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
  • 원본 배열은 변경되지 않는다.

Array.prototype.filter

  • filter 메서드는 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
  • 원본 배열은 변경되지 않는다.

Array.prototype.reduce

  • reduce 메서드는 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서 하나의 결괏값을 만들어 반환한다.
  • 원본 배열은 변경되지 않는다.
const arr = [1, 2, 3, 4];
const sum = arr.reduce((accumulator, currentValue, index, array) => {
	return accumulator + currentValue
}, 0);
console.log(sum); // 10

Array.prototype.some

  • some 메서드는 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 단 한 번이라도 true라면 true, 모두 거짓이면 false를 반환한다.

Array.prototype.every

  • every 메서드는 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 모두 true라면 true, 단 한 번이라도 거짓이면 false를 반환한다.

Array.prototype.find

  • find 메서드는 자신을 호출한 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 첫 번째 요소를 반환한다. true인 요소가 존재하지 않는다면 undefined를 반환한다.

Array.prototype.findIndex

  • find 메서드는 자신을 호출한 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 첫 번째 요소의 인덱스를 반환한다. true인 요소가 존재하지 않는다면 -1을 반환한다.

Array.prototype.flatMap

  • flatMap 메서드는 ES10에서 도입되었으며 map 메서드를 통해 생성된 새로운 배열을 평탄화한다. 단, flat 메서드와 달리 1단계만 평탄화가 가능하다.
    const arr = ['hello', 'world'];
    arr.map(x => x.split('')).flat(); 
    // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
    arr.flatMap(x => x.split(''));
    // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

References

데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022)
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017)
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)

0개의 댓글