본문 바로가기
👩‍💻 Programming/JavaScript

컬렉션(collections) 다루기 from Secrets of the JavaScript Ninja

by codingBear 2022. 4. 20.
728x90
반응형

이번 글은 'Secrets of the JavaScript Ninja'의 'Chapter 9. Dealing with collections'를 바탕으로 작성하였습니다.


핵심 Concepts

  • 배열 생성 및 수정
  • 배열 함수 사용 및 재사용
  • 맵(maps)을 활용한 딕셔너리(dictionaries) 생성
  • 세트(sets)을 활용한 고유 객체의 컬렉션 생성

사전 지식 체크!

  • 딕셔너리 혹은 맵으로서 객체를 사용할 때 흔한 위험성은 무엇인가?
  • 맵 안에서 key/value쌍은 어떤 값 유형을 가질 수 있는가?
  • 세트 안의 요소들은 반드시 유형이 같아야 하는가?

들어가며

 JavaScript로 작업을 하면서 요소들의 집합을 다루는 경우가 많다. 이때 흔히 쓰이는 것이 바로 배열(arrays)이다. 이러한 배열을 효율적으로 다루기 위해 JavaScript에 내장된 배열 함수의 사용법을 알아보겠다. 또한 ES6에서 추가된 맵과 세트도 알아볼 것이다. 맵을 사용하면 키와 값 사이의 매핑을 전달하는 일종의 딕셔너리를 만들 수 있다. 세트는 각 항목이 두 번 이상 발생할 수 없는 고유한 항목의 모음이다.


배열

 배열은 흔히 볼 수 있는 자료형(data types) 중 하나로 이를 활용해 요소의 모음을 처리한다. 만약 C와 같은 엄밀한 형식의 언어에 대한 배경 지식을 가진 경우 배열은 같은 형식의 요소를 담은 연속적인 메모리 덩어리이며 각 메모리 덩어리는 크기가 고정되어 있고 손쉽게 액세스 가능한 관련 인덱스를 가진다고 생각할 것이다.

 허나 JavaScript에서 배열은 단순한 객체이다. 이는 성능적인 측면에서 이점을 띤다. 배열은 다른 객체와 마찬가지로 메서드에 액세스할 수 있다.


배열 만들기

 배열은 만드는 데는 기본적으로 두 가지 방식이 있다. 

 

  • 내장된 배열 생성자 사용
  • 배열 리터럴 [] 사용

 

 

 간단한 배열 리터럴로 ninjas 배열을 생성했다. 그리고 samurai 배열은 내장 배열 생성자로 만들었다.

배열 리터럴 VS 배열 생성자
 배열 리터럴을 쓰기를 권장하는 주된 이유는 바로 간결성 때문이다. 이 외에도 JavaScript는 매우 동적인 언어이기 때문에 내장 배열 생성자를 재정의하는 것을 막을 수 없다. 이는 new Array()를 호출이 반드시 배열을 생성한다는 뜻이 아니다.

 어떻게 생성되었든간에 배열은 length 프로퍼티를 갖는다. 알다시피 배열 인덱스를 활용하여 배열 요소에 액세스할 수 있다. 맨 첫 번째 값의 인덱스는 0이고 마지막 요소의 인덱스는 array.length - 1이다. 하지만 배열 범위를 벗어난 인덱스에 액세스하려고 하면 (예를 들어 ninjas[4]) 배열 범위를 벗어났다는 경고 메시지 대신 undefined가 반환된다. 이 같은 결과는 JavaScript에서 배열은 객체이기 때문에 생기는 것이다. 존재하지 않는 객체 프로퍼티에 액세스하려고 하면 undefined이 반환되듯 존재하지 않는 배열 인덱스에 액세스할 때도 undefined를 얻는다.

 반면 다음과 같이 배열 범위를 벗어나서 값을 할당하려고 하면 배열은 새로운 값을 담기 위해 확장된다.

 

 

 위 그림을 보면 알 수 있듯 배열은 확장되어 길이는 5로 늘어나고 값이 할당되지 않은 인덱스 3의 값은 undefined로 남는다. JavaScript는 length 프로퍼티와 관해서 특이한 면이 있다. 바로 길이값을 자유자재로 바꿀 수 있다는 것이다. 현재 길이보다 더 높게 값을 설정하면 undefined 요소와 함께 배열의 길이가 늘어난다. 반면 현재 배열의 길이보다 작은 값을 할당하면 배열은 그에 맞게 축소된다.


Adding and removing items at either end of an array

다음과 같은 메소드를 통해 배열에 요소를 추가하거나 제거 가능하다.

 

  • push: 배열 맨 끝에 요소 추가
  • unshift: 배열 맨 앞에 요소 추가
  • pop: 배열 맨 끝의 요소 제거
  • shift: 배열 맨 앞의 요소 제거

 

 

 우선 빈 ninjas 배열을 생성한다. 그리고 내장 메서드 push로 요소를 배열 맨 끝에 추가하여 배열의 길이를 늘린다. 또한 unshift 메소드를 활용해 배열의 맨 앞에 요소를 추가할 수 있다. 이때 기존의 배열 요소가 조정된다는 점에 주목하자. unshift 메소드를 호출하기 전에 "Kuma"의 인덱스는 "0"이었지만 호출된 뒤에는 "1"로 바뀐다.

 배열의 맨 앞과 맨 끝의 값을 제거하는 것도 가능하다. pop 메서드는 배열의 맨 끝 값을 제거하여 배열의 길이를 줄인다. 맨 앞의 값을 제거하는 것은 shift 메서드이다.

 다음 그림은 위에서 살펴본 메서드들이 어떻게 동작하는지 나타낸 그림이다.

성능 비교: pop & push VS shift & unshift
 pop과 push는 배열 맨 끝의 요소에만 영향을 미친다. 허나 shift와 unshift는 맨 앞의 요소를 바꾸어 나머지 요소들의 인덱스가 차례로 조정되게 한다. 따라서 pop과 push의 작업 처리 속도가 훨씬 빠르다.

Adding and removing items at any array location

 이전까지 살펴본 메서드들은 맨 앞 혹은 맨 뒤의 요소만을 변경 가능하기 때문에 사용상 제약이 있다. 다음 예제들을 통해 원하는 배열 위치에 요소를 넣는 방법에 대해 알아보자.

 

 

 위 코드를 실행한 결과값은 부적절함을 볼 수 있다. delete로써 요소를 제거하기는 했으나 배열 내 제거된 요소가 있던 자리에 undefined가 있어 결과적으로 배열 길이는 그대로이다. 

 이때 splice 메서드를 활용하면 배열 요소를 성공적으로 제거할 수도 있고 추가도 가능하다. 예제를 살펴보자.

 

 

 요소 네 개를 가진 배열을 선언하고 시작한다. 그 다음 splice 메서드를 다음과 같이 호출한다.

 

 

 splice는 두 가지 인수를 받는다. 하나는 잘라내기가 시작될 인덱스이고 또 하나는 제거될 요소의 개수이다. 제거될 요소의 개수를 입력하지 않으면 시작 인덱스부터 뒤에 있는 모든 값이 제거된다. 예제의 경우 인덱스 1의 요소가 배열에서 제거되고 배열 내 나머지 요소들의 위치는 자동으로 조정된다. 

 또한 splice 메서드는 제거된 요소를 배열 형태로 반환한다. 여기서는 "Kuma" 단 하나만 반환된다. 

 splice 메서드를 활용하여 배열 내 임의의 위치에다 값을 추가 가능하다.

 

 

 인덱스 1부터 시작해서 두 가지 요소를 제거하고 세 요소를 추가하는 코드이다.


Common operations on arrays

  • 반복(iterating) 혹은 탐색(traversing)
  • 기존 배열 요소들을 매핑(mapping)하여 이를 기반으로 새 배열 생성
  • 특정 조건을 충족하는지 시험(testing)
  • 검색(finding)
  • 합산(aggregating)

 

Iterating over arrays

배열 내 모든 요소를 탐색하는 가장 일반적인 코드는 다음과 같다.

 

 

 위의 복잡한 코드를 forEach 메서드를 사용하여 간략히 작성 가능하다.

 

 

 우선 콜백을 제공하여 (여기서는 화살표 함수) 배열 내 모든 요소들을 호출한다. 이렇게만 작성해도 위에 작성한 for 반복문과 똑같이 작동한다.

 

Mapping arrays

 ninja 객체가 있다고 가정해보자. 이 ninja들은 각자 이름과 무기를 갖고 있는데 무기만을 따로 모은 배열을 만든다고 하자. forEach 메서드를 활용해 위와 같은 기능을 구현한다면 다음과 같다.

 

 

 이처럼 기존의 배열을 활용하여 새로운 배열을 생성하는 작업은 매우 일반적이다. 이러한 작업을 바로 매핑(mapping)이라고 한다. 한 배열의 각 항목을 새 배열의 새 항목으로 매핑하는 것이다. 편리하게도 JavaScript는 이를 위한 map 함수를 제공한다. 다음 예제를 살펴보자.

 

 

 map 메서드는 완전히 새로운 배열을 생성하고 삽입된 배열을 순회한다. 삽입된 배열의 각 요소에 대해 map 메서드는 map 메서드에 제공되는 콜백의 결과에 기반하여 새롭게 생성된 배열 안에 정확히 한 요소만을 배치한다. map 함수의 내부 작동 과정은 다음과 같다.

 

 

Testing array items

 

 

 위 예제의 경우 ninjas 배열이 정의되어 있는데 해당 배열 내 모든 요소가 프로퍼티로 name 혹은 weapon을 갖는지 확인할 필요가 있다고 가정하자. 이를 위해 every 메서드를 쓴다. every 메서드는 콜백을 받아 컬렉션 안의 각 닌자에 대하여 name 프로퍼티를 갖고 있는지 확인한다. every 메서드는 전달된 콜백이 배열 내 모든 값에 대해 true를 반환할 때만 true를 반환한다. 아래 그림은 every 메서드가 작동하는 모습을 나타낸 것이다.

 

 

 일부 배열 요소가 특정 조건을 충족하는지 확인할 때 some 메서드를 쓴다. 배열의 첫 번째 값부터 탐색을 시작하여 콜백이 true를 리턴하는 값을 찾을 때까지 각 배열 요소에 대한 콜백을 some 메서드는 호출한다. 만약 그러한 값을 발견한 경우 true를 반환하고 아니라면 false를 반환한다. 아래 그림은 some 메서드의 작동 원리다.

 

 

Searching arrays

 배열 내에서 요소를 찾을 때는 ES6에서 새롭게 추가된 find 메서드를 사용한다.

 

 

 대상 요소가 발견될 때까지 컬렉션 안의 각 값에 대해 호출되는 콜백을 전달받는 find 메서드를 사용하면 특정 조건을 충족하는 배열 내 요소를 찾기 수월하다. 조건을 만족하는 요소가 있다면 해당 요소를 반환한다. 만일 배열 내 조건을 충족하는 요소가 없다면 undefined를 반환한다. 

 

 

 만약 특정 조건을 충족하는 값을 여러 개 찾아야 한다면 filter 메서드를 사용하면 된다. filter 메서드는 조건을 충족하는 모든 요소를 포함하는 배열을 새로 생성한다. 

 

 

 이때까지 여러 예제를 통해 배열 내 특정 요소를 찾아보았는데 특정 요소의 인덱스를 찾아야 할 경우도 생긴다. 다음 예제를 통해 살펴보자.

 

 

 특정 요소의 인덱스를 찾기 위해 indexOf 메서드를 사용하는데 이때 indexOf 메서드에는 인덱스를 찾고자 하는 요소의 값을 전달한다.

 만약 찾고자 하는 요소가 배열 내 여러 군데 존재한다면 여러 개의 해당 요소 중 마지막 요소의 인덱스 번호가 필요할 수도 있다. 이때는 lastIndexOf 메서드를 쓴다. 

 마지막으로 가장 널리 쓰이는 메서드인 findIndex 메서드를 알아보자. 이 메서드는 인덱스를 찾고자 하는 특정한 요소에 대한 참조가 없을 때 자주 쓰인다. findIndex 메서드는 콜백을 취하고 콜백이 true를 반환하는 첫 번째 요소의 인덱스를 반환한다. findIndex 메서드와 find 메서드는 매우 닮았는데 유일하게 다른 점은 find 메서드는 특정 요소를 반환하는 반면 findIndex는 그 요소의 인덱스를 반환한다는 점이다.

 

Sorting arrays

 가장 일반적인 배열 작업 중 하나는 정렬(sorting)이다. sort 메서드를 활용하여 이 같은 정렬 기능을 간단히 구현 가능하다.

 

 

  sort 메서드 활용 시 딱 하나 해야 할 일은 두 배열 요소 간의 관계에 대한 정렬 알고리즘을 알려주는 콜백을 제공하는 일이다.

 

  • 콜백이 0보다 작은 값을 반환하는 경우 요소 a는 요소 b 앞에 온다.
  • 콜백이 0과 같은 값을 반환하는 경우 요소 a와 b는 제자리에 있는다.
  • 콜백이 0보다 큰 값을 반환하는 경우 요소 a는 요소 b 뒤에 온다.

 

 

 위 예제에는 name 프로퍼티를 가지는 ninjas가 정의되어 있다. 우리의 목표는 sort 메서드를 활용해 name 프로퍼티의 값을 사전순으로 정렬하는 것이다. sort 함수에는 두 배열 요소를 비교하는 데 쓰이는 콜백만을 전달하면 된다. 앞서 살펴봤듯 ninja1의 이름이 ninja2의 이름보다 사전순으로 봤을 때 앞이라면 콜백은 -1을 반환하고 뒤라면 1을, 같다면 0을 반환하여 정렬 작업을 수행한다.

 

Aggregating array items

 배열 내 모든 요소의 총합을 구할 때 아래와 같이 코드를 작성해왔을 것이다.

 

 

 위 코드를 실행하면 배열 내 모든 요소를 조회하여 하나의 총합을 구한다. 즉 여기서 핵심은 배열의 길이를 하나의 값으로 줄이는 것이다. 이러한 기능을 구현하기 위해 reduce 메서드를 사용한다.

 

 

 reduce 메서드는 초기값을 받아(이 경우 0) 받아 이전 콜백 호출(또는 초기 값)의 결과와 현재 배열 요소를 인수로 사용하여 각 배열 항목에 대한 콜백 함수를 호출하여 작동한다. reduce 호출의 결과는 마지막 배열 요소에서 호출된 마지막 콜백의 결과이다.

 


Reusing built-in array functions

 코드를 작성하다 보면 데이터 모음을 담는 객체를 만들어야 하는 경우가 많다. 만약 단순한 데이터 모음만을 고려한다면 그냥 배열을 쓰면 된다. 하지만 어떤 경우 단순한 데이터 모음보다 특정한 상태를 저장해야 한다. 이러한 상태란 집합적인 요소에 대한 정렬된 메타 데이터(metadata)를 저장할 필요가 있는 경우일 것이다.

 이러한 기능을 구현하기 위한 한 가지 선택지는 새로운 버전의 객체를 만들 필요가 있을 때마다 매번 새로운 배열을 생성하여 메타 데이터의 프로퍼티와 메서드를 추가하는 것이다. 여기서 배열을 포함하여 원하는 대로 객체에 속성과 메서드를 추가할 수 있음을 기억하자. 그러나 이런 방식은 매우 느리고 번거롭다.

 그렇다면 평범한 객체를 사용하여 원하는 기능을 구현하는 법을 살펴보자. 컬렉션을 처리하는 데 쓰이는 메서드는 이미 Array 객체에 존재한다. 이를 활용한 예제를 살펴보자.

 

 

 위 예제에서는 '평범한' 객체를 생성하여 배열의 일부 동작을 모방하도록 기능을 구현한다. 우선 저장된 요소의 개수를 기록하기 위한 length 프로퍼티를 정의한다. 그런 다음 add 메서드를 호출하여 시뮬레이션된 배열의 끝에 요소를 추가하는 메서드를 정의한다. 임의로 코드를 작성하기 보다는 JavaScript 배열의 내장된 메서드인 Array.prototype.push를 사용하는 편이 좋다.

 일반적으로 Array.prototype.push 메서드는 함수 컨텍스트를 통해 자체 배열에서 작동한다. 하지만 여기에서는 call 메서드를 사용하여 객체를 push 메서드의 컨텍스트로 강제로 만들어 객체를 컨텍스트로 사용하도록 메서드를 속이고 있다.(apply 메서드를 얼마나 쉽게 사용할 수 있었는지 주목하자.) length 프로퍼티(배열의 length 프로퍼티라고 생각)를 증가시키는 push 메서드는 전달된 요소를 참조하는 객체에 번호가 지정된 프로퍼티를 추가한다. 이러한 작업은 변경 가능한 객체 컨텍스트로 할 수 있는 일을 예시로 보여준다.

 add 메서드는 저장소에 전달된 요소 참조를 기대한다. 때때로 주변에 그러한 참조가 있을 수 있지만 그렇지 않은 경우가 더 많기 때문에 id 값으로 요소를 찾아 스토리지에 추가하는 편리한 메서드 gather도 정의한다.

 마지막으로 내장된 배열 find 메서드를 활용하여 사용자 정의 객체에서 임의의 요소를 찾을 수 있는 find 메서드도 정의한다.

 이러한 방법을 통해 이미 작성된 코드를 유용하게 재사용할 수 있다.


Maps

 당신이 freelanceninja.com이라는 사이,트의 개발자이고 더 많은 고객을 유치하고 싶다고 가정하자. 예를 들어 "Ninjas for hire"라는 웹 사이트상의 텍스트에 각 외국어를 매핑한다고 보자. 이러한 컬렉션은 키(key)에다 특정한 값을 매핑하는데 딕셔너리 혹은 맵으로 알려져 있다.

 이러한 딕셔너리 및 맵을 JavaScript에서 효율적으로 관리하려면 어떻게 해야 될까? 전통적인 접근 방법 중 하나는 객체는 이름이 붙여진 프로퍼티와 값의 모음이고 다음과 같은 딕셔너리를 만든다는 사실을 이용하는 것이다.

 

 

 언뜻 보기에 이 예제는 완벽해 보이지만 실질적으로 코드를 작성하는 데 이 같은 작성 방식에만 의존할 수는 없다.


Don't use objects as maps

 앞서 떠올렸던 사이트 어딘가에서 "constructor"라는 단어의 번역에 액세스해야 한다고 생각해보자. 앞선 딕셔너리를 아래와 같이 확장할 필요가 있다.

 

 

 딕셔너리에 깜빡하고 입력하지 않은 constructor라는 단어의 번역에 액세스하려고 하였다. 이러한 경우 보통 딕셔너리가 undefined를 반환하리라고 예상할 것이다. 하지만 결과값은 다음과 같다.

 

 

 이때까지 배워왔듯 모든 객체는 프로토타입을 갖는다. 새롭게 정의하여 맵으로 사용한 객체라도 프로토타입 객체의 프로퍼티에 여전히 액세스할 수 있다. 이러한 프로퍼티 중 하나는 생성자이며(생성자는 생성자 함수를 다시 가리키는 프로토타입 객체의 프로토타입이라는 점을 기억하자.) 위의 문제를 일으키는 장본인이다. 

 또한 객체에서 문자열 값만 키가 될 수 있다. 다른 값에 대한 매핑을 생성하려는 경우 해당 값은 아무도 모르게 문자열로 변환된다. 다음 HTML 노드(node)들을 살펴보자.

 

 

 우선 document.getElementById 메서드를 활용하여 DOM으로부터 전달되는 firstElement와 secondElement라는 HTML 요소 두 개를 만든다. 각 요소에 대한 추가적인 정보를 담을 매핑을 만들기 위해서는 일반적인 JavaScript 객체를 생성한다. 그리고 HTML 요소를 매핑 객체의 키로 사용하고 데이터 몇 가지를 연관시킨다. 이후 데이터를 불러올 수 있는지 확인한다. 두 번째 요소에 대해서도 똑같은 작업을 반복한다.

 테스트를 수행하면 모든 작업은 성공적으로 수행되는듯 보인다. 허나 첫 번째 요소에 다시 액세스하려고 하면 문제가 발생한다.

 

 

 당연히 첫 번째 요소에 대한 정보를 얻으리라 예상했겠지만 결과는 전혀 다르다. 대신 위 그림에서 보듯 두 번째 요소에 대한 정보가 반환된다. 이 같은 문제는 객체에서 키는 문자열로 저장되기 때문에 일어난다. 이는 HTML 요소 같은 문자열이 아닌 값을 객체의 프로퍼티로 쓰려고 하면 그 값은 조용히 toString 메서드를 호출하여 문자열로 변환된다는 뜻이다. 이러한 과정을 거쳐 문자열 "[object HTMLDivElement]"가 반환되고 첫 번째 요소에 대한 정보는 [object HTMLDivElement] 프로퍼티의 값으로 저장된다.

 두 번째 요소에 대한 매핑을 만드려고 할 때도 비슷한 일이 일어난다. HTML div 요소인 두 번째 요소 역시 문자열로 바뀌고 추가적인 데이터 역시 [object HTMLDivElement] 프로퍼티에 할당되며 첫 번째 요소의 값으로 설정한 값을 덮어쓴다.

 프로퍼티는 프로토타입을 통해 상속을 받고 문자열만이 키로 쓰인다는 두 가지 이유 때문에 순수한 객체는 맵으로 쓰이지 않는다. 이러한 제약을 극복하기 위해 ES6에서 Map이라는 새로운 개념이 도입된 것이다.


Creating our first map

 맵을 만들기는 쉽다. 내장된 Map 생성자를 사용하기만 하면 된다. 다음 예제를 살펴보자.

 

 

 위 예제 코드에서는 내장된 Map 생성자를 호출하여 새로운 맵을 생성했다. 그리고 ninja 객체 3개를 생성한 다음 내장된 맵 set 메서드를 사용하여 키와 값 사이(예제의 경우 ninja1 객체와 ninja의 고향에 대한 정보를 갖는 객체) 사이에 매핑을 생성한다. ninja1과 ninja2에다 이 같은 작업을 해둔다. 

 다음으로 또 다른 내장 메서드은 get을 활용하여 두 ninja에 대한 매핑을 얻는다. 당연하게도 매핑은 처음 두 ninja 객체에만 존재하지 세 번째 ninja에는 존재하지 않는다. 왜냐하면 set 메서드의 인수로 ninja3를 사용하지 않았기 때문이다. 이때까지의 코드 상태가 아래 그림에 잘 나와 있다.

 

 

 get, set 메서드 외에도 모든 맵은 size 프로퍼티와 has, delete와 같은 내장 메서드를 갖는다. size 메서드는 매핑을 몇 개나 만들었는지 알려준다. 예제의 경우 두 개이다.

 has 메서드는 특정 값에 대한 매핑이 이미 존재하는지 알려주고 delete 메서드는 맵에서 요소를 제거한다.

 맵을 다룰 때의 기본 개념 중 하나는 두 개의 맵 키가 언제 동일한지 결정하는 것이다. 이러한 개념을 더욱 자세히 알아보자.

 

Key equality

 만약 C#, Java, Python과 같은 언어에 대한 배경지식을 갖고 있다면 다음 예제를 보고 깜짝 놀랄 것이다.

 

 

 

 

 

 

 위 예제에서는 현재 페이지의 URL을 얻기 위해 내장된 location.href 프로퍼티를 썼다. 그 다음 내장된 URL 생성자를 사용하여 현재 페이지와 연결된 새로운 URL 객체를 두 개 생성한다. 그 다음 객체에 대한 설명을 각 링크와 연결시킨다. 마지막으로 매핑이 제대로 되었는지 확인한다. 

 JavaScript로 작업을 해왔던 사람들에게 이 같은 결과는 낯설지 않다. 위 예제에는 우리가 생성한 두 가지 다른 매핑에 대한 두 가지 다른 객체가 있다. 그러나 두 개의 URL 객체가 별도의 객체임에도 불구하고 여전히 현재 페이지라는 동일한 URL 위치를 가리킨다. 매핑을 생성할 때 두 객체가 동일한 것으로 간주되어야 한다고 볼지 모른다. 하지만 JavaScript에서는 동치 연산자를 오버로드(overload)할 수 없으며 두 객체가 동일한 내용을 가지고 있어도 항상 다른 것으로 간주된다. 이는 Java나 C#과는 다른 부분이기 때문에 주의를 요한다.


Iterating over maps

 이때까지 맵에 대해 알아보았다. 맵은 우리가 넣은 요소만 포함할 수 있으며 무엇이든 키로 사용할 수 있다. 맵은 컬렉션이기 때문에 for-of 반복문을 통해 반복 가능하다. 이 반복문으로 배열 내 값이 삽입된 순서대로 반환할 수 있다.(for-in 반복문으로는 할 수 없다.) 다음 예제를 살펴보자.

 

 

  위 예제에서는 내장된 Set 생성자를 사용하여 새로운 ninjas 세트를 만들었다. 아무런 인수도 전달하지 않으면 빈 세트가 만들어진다. 예제에서 보듯 인수로는 배열도 넣을 수 있다. 앞서 언급했다시피 세트는 유일한 요소들의 집합으로 똑같은 객체가 저장되지 않도록 하는 게 주된 사용처이다. 위 예제에서 "Hattroi"를 두 번 추가하려고 하지만 결국 한 번만 저장된다. 

 모든 세트에서 몇 가지 메서드에 액세스할 수 있다. 예를 들어 has 메서드는 세트 안에 요소가 담겨 있는지 확인한다. add 메서드는 세트에 유일한 요소를 추가할 때 쓰고 size 프로퍼티는 요소가 몇 개 들어 있는지 확인할 때 쓴다.

 맵과 배열과 유사하게 세트도 컬렉션이라서 for-of 반복문으로 반복 가능하다. 아래 그림을 보면 알 수 있듯 각 요소는 추가된 순서대로 반복문을 통해 반환된다.

 

 

 다음으로는 세트의 일반적인 작업인 유니온(union), 인터섹션(intersection), 디퍼런스(differences)에 대해 알아보자.


Union of sets

 A와 B, 두 세트의 유니온은 A와 B의 모든 요소를 포함하는 새로운 세트를 생성한다. 단 각 요소는 새로운 세트에서 중복될 수 없다.

 

 

 우선 ninjas와 samurai 배열을 생성한다. 그리고 두 배열을 모두 포함하는 warriors라는 새로운 세트도 생성한다. warriors라는 세트를 만들 때 스프레드(spread) 연산자 [...ninjas, ...samurai]를 사용하여 ninjas와 samurai의 모든 값을 담은 새로운 배열을 만들었다. 여기서 "Hattori"는 두 배열에 모두 포함되어 있는데 이 때 set로 생성했기 때문에 중복값이 사라져 하나의 "Hattori"만 남는다. 

 


Intersection of sets

 인터섹션은 A와 B 양쪽 모두에 들어 있는 요소를 담는 세트를 만드는 것이다. 다음 예제를 살펴보자.

 

 

 위 예제에서는 두 배열에 모두 속해 있는 값을 담은 set를 만들기 위해 filter 메서드를 활용하였다. 앞서 살펴보았듯이 filter 메서드는 일정 조건을 충족하는 값들을 배열 형식으로 반환해주는 메서드이다. 위 예제에서 조건은 ninjas 및 samurai 세트 모두에 담겨 있는지 여부이다. filter 메서드는 배열에만 사용 가능하므로 ninjas 세트를 스프레드 연산자로써 배열로 만들었다.

 이렇게 하면 결과값으로 "Hattori"가 나온다.


Difference of sets

 디퍼런스 세트는 집합 A에는 있지만 집합 B에는 없는 모든 요소를 포함한다.

 

 

 Listing 9.22와 비교했을 때 유일하게 다른점은 바로  samurai.has(ninja) 앞에 느낌표(!)를 붙인 것이다. 이로써 ninja에는 있으나 samurai에는 없는 값만을 추려낼 수 있다.


요점 정리

  • 배열은 length 프로퍼티와 Array.prototype을 프로토타입으로 가지는 객체의 특별한 형식이다.
  • 배열 리터럴([]) 혹은 Array 생성자를 호출하여 새로운 배열을 만들 수 있다.
  • 다음과 같은 메서드를 통해 배열 요소를 수정할 수 있다.
    • push와 pop: 배열 끝의 요소를 추가 혹은 제거
    • unshift와 shift: 배열 처음의 요소를 추가 혹은 제거
    • every와 some: 모든 혹은 일부 배열 요소가 조건을 충족하는지 판별
    • find와 filter: 특정 조건을 충족하는 배열 요소를 탐색
    • sort: 배열 정렬 시 사용
    • reduce: 배열 내 모든 요소를 하나의 요소로 합치는 데 사용
  • call 혹은 apply 메서드로 메서드 호출 컨텍스트를 명시적으로 설정하여 고유한 객체를 구현할 때 내장 배열 메서드를 재사용 가능
  • 맵과 딕셔너리는 키와 값 사이의 매핑을 포함하는 객체이다.
  • JavaScript에서 객체는 문자열만 키로 쓸 수 있고 프로토타입 프로퍼티에 액세스할 위험이 항상 있으므로 맵을 만들 때는 Map 컬렉션을 활용한다.
  • 맵은 컬렉션이라서 for-of 반복문으로 반복 가능
  • 세트는 유일한 요소들의 컬렉션이다.

함께 보기

  • push & pop

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

 

Array.prototype.push() - JavaScript | MDN

The push() method adds one or more elements to the end of an array and returns the new length of the array.

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop

 

Array.prototype.pop() - JavaScript | MDN

The pop() method removes the last element from an array and returns that element. This method changes the length of the array.

developer.mozilla.org

  • shift & unshift

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift

 

Array.prototype.shift() - JavaScript | MDN

The shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift

 

Array.prototype.unshift() - JavaScript | MDN

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

developer.mozilla.org

  • forEach

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

 

Array.prototype.forEach() - JavaScript | MDN

The forEach() method executes a provided function once for each array element.

developer.mozilla.org

  • map

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

 

Array.prototype.map() - JavaScript | MDN

The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

developer.mozilla.org

  • every & some

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every

 

Array.prototype.every() - JavaScript | MDN

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

 

Array.prototype.some() - JavaScript | MDN

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn

developer.mozilla.org

  • find

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

 

Array.prototype.find() - JavaScript | MDN

The find() method returns the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.

developer.mozilla.org

  • filter

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

 

Array.prototype.filter() - JavaScript | MDN

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

developer.mozilla.org

  • indexOf

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

 

Array.prototype.indexOf() - JavaScript | MDN

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

developer.mozilla.org

  • lastIndexOf

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf

 

Array.prototype.lastIndexOf() - JavaScript | MDN

The lastIndexOf() method returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex.

developer.mozilla.org

  • findIndex

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

 

Array.prototype.findIndex() - JavaScript | MDN

The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test.

developer.mozilla.org

  • sort

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

 

Array.prototype.sort() - JavaScript | MDN

The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

developer.mozilla.org

  • reduce

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 

Array.prototype.reduce() - JavaScript | MDN

The reduce() method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the a

developer.mozilla.org

  • Set

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

 

Set - JavaScript | MDN

The Set object lets you store unique values of any type, whether primitive values or object references.

developer.mozilla.org

  • Map

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

 

Map - JavaScript | MDN

The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.

developer.mozilla.org

  • for...of

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

 

for...of - JavaScript | MDN

The for...of statement creates a loop iterating over iterable objects, including: built-in String, Array, array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables. It invokes a custom iteration hook with statements

developer.mozilla.org

  • for...in

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

 

for...in - JavaScript | MDN

The for...in statement iterates over all enumerable properties of an object that are keyed by strings (ignoring ones keyed by Symbols), including inherited enumerable properties.

developer.mozilla.org

 

728x90
반응형

댓글