📌 [모던 자바스크립트 13주 뿌시기] 37. Set과 Map & 42. 비동기 프로그래밍
![](/assets/img/JavaScript/2022-11-10/bookcover.png)
37. Set과 Map
Set
Set 객체
중복되지 않는 유일한 값들의 집합. 배열과 유사하다. 수학적 집합의 특성과 유사하다.
즉, 수학적 집합을 구현하기 위한 자료구조이다.
배열과의 차이점
Set
은 동일한 값을 중복하여 포함할 수 없다.Set
은 요소 순서에 의미가 없다.Set
은 인덱스로 요소에 접근할 수 없다.
Set 객체의 생성
Set
객체는 생성자 함수로 생성 가능하다.
const set1 = new Set();
console.log(set1); // Set(0) {}
const set2 = new Set([1,2,2,3,3,4,4,5]);
console.log(set2); // Set(5) {1,2,3,4,5}
인수를 전달하지 않은 경우 빈 Set
객체가 생성된다.
Set
생성자 함수는 이터러블을 인수로 전달받아 Set
객체를 성상할 수 있으며 중복된 값은 Set
객체에 저장되지 않는다. 위와 같은 특성을 사용하여 배열에 Set
객체를 사용하여 중복된 요소가 제거된 배열을 만들 수 있다.
요소 개수 확인
Set.prototype.size
프로퍼티를 사용하여 요소 개수를 확인할 수 있다.
const { size } = new Set([1,2,3,3]);
console.log(size); // 3
size
프로퍼티는 setter
없이 getter
만 존재하는 접근자 프로퍼티므로 size
프로퍼티에 숫자를 할당하여 요소 개수를 변경할 수 없다.
요소 추가
Set.prototype.add
메서드를 통해 객체에 요소를 추가할 수 있다.
const set = new Set();
console.log(set); // set(0) {}
set.add(1);
console.log(set); // set(1) {1}
add
메서드는 새로운 요소가 추가된Set
객체를 반환하기 때문에 연속적으로 호출할 수 있다.Set
객체에 중복된 요소의 추가는 허용되지 않는다. 중복이 있다고 에러가 발생하진 않는다.Set
객체는NaN
과NaN
을 같다고 평가하여 중복 추가를 혀용하지 않는다. (기존NaN === NaN
값 평가는false
)Set
객체는 모든 값을 요소로 저장할 수 있다.
요소 일괄 삭제
Set.prototype.clear
메서드를 통해 객체의 모든 요소를 일괄 삭제할 수 있다. 이는 undefined
를 반환한다.
const set = new Set([1,2,3]);
set.clear();
console.log(set); // Set(0) {}
요소 순회
Set.prototype.forEach
메서드를 사용하여 Set
객체의 요소를 순회할 수 있다.
이는 Array.prototype.forEach
메서드와 유사하게 콜백 함수와 forEach
메서드의 콜백 함수 내부에서 this
로 사용될 객체를 인수로 전달한다.
다음은 세 가지 인수에 대한 내용이다.
- 현재 순회 중인 요소값
- 현재 순회 중인 요소값
- 현재 순회 중인
Set
객체 자체
1과 2는 같은 값이며 이처럼 동작하는 이유는 Array.prototype.forEach
메서드와 인터페이스를 통일하기 위함일 뿐이다.
const set = new Set([1,2,3]);
set.forEach((v, v2, set) => console.log(v, v2, set));
/*
1 1 Set(3) {1, 2, 3}
2 2 Set(3) {1, 2, 3}
3 3 Set(3) {1, 2, 3}
*/
Set
객체는 이터러블이기 때문에for ... of
로 순회가 가능하며 스프레드 문법과 배열 디스트럭처링의 대상이 될 수 있다.Set
객체를 순회하는 순서는 요소가 추가된 순서를 따른다.
집합 연산
Set
객체는 수학적 집합을 구현하기 위한 자료구조이므로 Set
객체를 통해 교집합, 합집합, 차집합 등을 구현할 수 있다.
교집합
교집합 A∩B는 집합 A와 집합 B의 공통 요소이다.
// 방법1
Set.prototype.intersection = function (set) {
const result = new Set();
for (const value of set) {
// 공통된 요소가 있는 경우, 결과에 추가한다.
if (this.has(value))
result.add(value);
}
return result;
};
// 방법2
//Set.prototype.intersection = function (set) {
// return new Set([...this].filter(v => set.has(v)));
//};
const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([3, 4, 6, 7]);
console.log(setA.intersection(setB));
// Set(2) { 3, 4 }
console.log(setB.intersection(setA));
// Set(2) { 3, 4 }
합집합
합집합 A∪B는 집합 A와 집합 B의 중복 없는 모든 요소로 구성된다.
// 방법1
Set.prototype.union = function (set) {
// this(Set 객체)를 복사
const result = new Set(this);
for (const value of set) {
// Set 객체는 중복된 요소는 추가되지 않음을 이용함
result.add(value);
}
return result;
};
// 방법2
//Set.prototype.union = function (set) {
// return new Set([...this, ...set]);
//};
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 3, 4, 5, 6]);
console.log(setA.union(setB));
// Set(6) { 1, 2, 3, 4, 5, 6 }
console.log(setB.union(setA));
// Set(6) { 2, 3, 4, 5, 6, 1 }
차집합
차집합 A-B는 집합 A에는 존재하지만 집합 B에는 존재하지 않는 요소로 구성된다.
// 방법1
Set.prototype.difference = function (set) {
// this(Set 객체)를 복사
const result = new Set(this);
for (const value of set) {
result.delete(value);
}
return result;
};
// 방법2
//Set.prototype.difference = function (set) {
// return new Set([...this].filter(v => !set.has(v)));
//};
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 4, 5]);
console.log(setA.difference(setB));
// Set(2) {1, 3}
console.log(setB.difference(setA));
// Set(1) { 5 }
부분 집합과 상위 집합
집합 A가 집합 B에 포함되는 경우(A⊆B) 집합 A는 집합 B의 부분 집합이며, 집합 B는 집합 A의 상위 집합이다.
// 방법1
Set.prototype.isSuperset = function (subset) {
for (const value of subset) {
// superset의 모든 요소가 subset의 모든 요소를 포함하는지 확인
if (!this.has(value)) return false;
}
return true;
};
// 방법2
//Set.prototype.isSuperset = function (subset) {
// const supersetArr = [...this];
// return [...subset].every(v => supersetArr.includes(v));
//};
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 4]);
console.log(setA.isSuperset(setB)); // true
console.log(setB.isSuperset(setA)); // false
Map
Map
객체는 키와 값의 쌍으로 이루어진 컬렉션이다.
객체와 유사하지만 아래와 같은 차이가 있다.
Map과 객체의 차이점
- 키로 사용할 수 있는 값이 객체를 포함한 모든 값이다.
Map
객체는 이터러블이다.- 요소 개수를 확인하는 메서드는
map.size
이다.
Map 객체의 생성
Map
객체는 생성자 함수로 생성 가능하다.
const map1 = new Map();
console.log(map1); // Map(0) {}
const map2 = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(map2); // Map(2) {"key1" => "value1", "key2" => "value2"}
const map3 = new Map([1, 2]); // TypeError: Iterator value 1 is not an entry object
인수를 전달하지 않은 경우 빈 Map
객체가 생성된다.
Map
생성자 함수는 이터러블을 인수로 전달받아 Map
객체를 성상할 수 있으며 이때 인수로 전달되는 이터러블은 키와 값의 쌍으로 이루어진 요소로 구성되어야 한다.
즉, 중복 키가 존재하면 값이 덮어써진다. 따라서 중복된 키를 갖는 요소는 존재할 수 없다.
요소 개수 확인
Map.prototype.size
프로퍼티를 사용하여 Map
객체의 요소 개수를 확인할 수 있다.
const { size } = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(size); // 2
size
프로퍼티는 setter
없이 getter
만 존재하는 접근자 프로퍼티므로 size
프로퍼티에 숫자를 할당하여 요소 개수를 변경할 수 없다.
요소 추가
Map.prototype.set
메서드를 통해 객체에 요소를 추가할 수 있다.
const map = new Map();
console.log(map);
map.set('key1', 'value1');
console.log(map); // Map(1) { 'key1' => 'value1' }
Map
메서드는 새로운 요소가 추가된Map
객체를 반환하기 때문에 연속적으로 호출할 수 있다.Map
객체에 중복된 키를 갖는 요소가 존재할 수 없기 때문에 중복된 키를 갖는 요소를 추가할 경우 해당 값이 덮어써진다. 에러는 발생하지 않는다.Map
객체는NaN
과NaN
을 같다고 평가하여 중복 추가를 혀용하지 않는다. (기존NaN === NaN
값 평가는false
)Map
객체는 모든 값을 키로 저장할 수 있다. (일반 객체와 가장 큰 차이이다.)
요소 취득
Map.prototype.get
메서드를 통해 객체의 특정 요소를 취득할 수 있다. 해당 메서드의 인수로 키를 전달하면 Map
객체에서 인수로 전달한 키를 갖는 값을 반환한다.
키가 없는 경우 undefined
를 반환한다.
const map = new Map();
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };
map
.set(lee, 'developer')
.set(kim, 'designer');
console.log(map.get(lee)); // developer
console.log(map.get('key')); // undefined
요소 존재 여부 확인
Map.prototype.has
메서드를 사용하여 Map
객체에 특정 요소가 존재하는지 확인할 수 있다. 존재 여부를 불리언 값을 반환한다.
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };
const map = new Map([[lee, 'developer'], [kim, 'designer']]);
console.log(map.has(lee)); // true
console.log(map.has('key')); // false
요소 삭제
Map.prototype.delete
메서드를 사용하여 Map
객체의 요소를 삭제할 수 있다.
삭제 성공 여부를 불리언 값으로 반환한다.
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };
const map = new Map([[lee, 'developer'], [kim, 'designer']]);
map.delete(kim);
console.log(map); // Map(1) { {name: "Lee"} => "developer" }
- 만약 존재하지 않는 키로
Map
요소를 삭제한다면 에러 없이 무시된다. - 삭제 성공 여부를 불리언 값으로 반환하기 때문에
set
메서드와 다르게 연속적으로 호출이 불가능하다.
요소 일괄 삭제
Map.prototype.clear
메서드를 사용하여 Map
객체의 요소를 일괄 삭제할 수 있다.
clear
메서드는 언제나 undefined
를 반환한다.
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };
const map = new Map([[lee, 'developer'], [kim, 'designer']]);
map.clear();
console.log(map); // Map(0) {}
요소 순회
Map.prototype.forEach
메서드를 사용하여 Map
객체의 요소를 순회할 수 있다.
이는 Array.prototype.forEach
메서드와 유사하게 콜백 함수와 forEach
메서드의 콜백 함수 내부에서 this
로 사용될 객체를 인수로 전달한다.
다음은 세 가지 인수에 대한 내용이다.
- 현재 순회 중인 요소값
- 현재 순회 중인 요소키
- 현재 순회 중인
Map
객체 자체
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };
const map = new Map([[lee, 'developer'], [kim, 'designer']]);
map.forEach((v, k, map) => console.log(v, k, map));
/*
developer {name: "Lee"} Map(2) {
{name: "Lee"} => "developer",
{name: "Kim"} => "designer"
}
designer {name: "Kim"} Map(2) {
{name: "Lee"} => "developer",
{name: "Kim"} => "designer"
}
*/
Map
객체는 이터러블이다.for ... of
문으로 순회할 수 있으며 스프레드 문법과 배열 디스트럭처링 할당의 대상이 될 수 있다.Map
객체를 순회하는 순서는 요소가 추가된 순서를 따른다.Map
객체는 이터러블이면서 동시에 이터레이터인 객체를 반환하는 아래 메서드를 제공한다.
Map.prototype.keys
: Map
객체에서 요소키를 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다. Map.prototype.values
: Map
객체에서 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다. Map.prototype.entries
: Map
객체에서 요소키와 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다.
42. 비동기 프로그래밍
동기 처리와 비동기 처리
함수는 호출된 순서에 따라 실행 컨텍스트 스택에 순차적으로 푸시되고 실행시킨다.
자바스크립트는 단 하나의 실행 컨텍스트 스택을 가지기 때문에 함수를 동시에 실행시킬 수 없다.
자바스크립트는 싱글 스레드 방식으로 동작한다.
이러한 이유로, 처리에 시간이 걸리는 태스크를 실행시키는 경우, 블로킹(작업중단)이 발생한다.
현재 실행 중인 태스크가 종료할 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고 한다.
동기 처리의 특징
- 태스크를 순서대로 하나씩 처리한다.
- 하나씩 처리하므로 실행 순서가 보장된다.
- 하나씩 처리하므로 실행 중인 태스크가 있다면 대기 중인 태스크는 블로킹된다.
현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식을 비동기 처리라고 한다.
비동기 처리의 특징
- 태스크를 동시에 처리한다.
- 실행 순서가 보장되지 않는다.
- 블로킹이 발생하지 않는다.
비동기 처리 방식으로 동작하는 것들은 다음과 같다.
- 타이머 함수:
setTimeout
,setInterval
- HTTP 요청
- 이벤트 핸들러
이벤트 루프와 태스크 큐
이벤트 루프란?
자바스크립트의 동시성을 지원하는 것. 브라우저에 내장된 기능이다.
자바스크립트 엔진은 두 가지 영역으로 구분할 수 있다.
- 콜 스택
- 힙
콜 스택
소스코드 평가 과정에서 생성된 실행 컨텍스트가 추가/제거되는 실행 컨텍스트 스택이다.
함수를 호출하면 함수 실행 컨텍스트가 순차적으로 콜스택에 푸시되어 순차적으로 실행된다. 자바스크립트 엔진은 하나의 콜 스택을 사용하므로 최상위 실행 컨텍스트가 종료되어 콜 스택에서 제거되기 전까지는 다른 어떤 태스크도 실행되지 않는다.
힙
객체가 저장되는 메모리 공간이다. 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.
메모리에 값을 저장하기 위해서는 값을 저장할 공간의 크기를 정해야 한다. 객체는 원시값이 아니므로 공간의 크기가 정해져 있지 않으므로 할당해야 할 메모리 공간의 크기를 런타임에 결정해야 한다.
따라서 객체가 저장되는 메모리 공간인 힙은 구조화 되어 있지 않다.
자바스크립트 엔진은 태스크가 요청되면 콜 스택을 통해 작업을 순차적으로 실행한다. 비동기 처리에서 소스코드 평가 및 실행을 제외하면 전부 브라우저/Node.js가 담당한다.
브라우저 환경은 태스크 큐와 이벤트 루프를 제공한다.
태스크 큐
태스크 큐는 비동기 함수의 콜백 함수 혹은 이벤트 핸들러가 일시적으로 보관되는 영역이다. 태스크 큐와는 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로태스크 큐도 존재한다.
이벤트 루프
이벤트 루프는 콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 태스크 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지 반복해서 확인한다. 만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킨다. 이때 콜 스택으로 이동한 함수는 실행된다. 즉, 태스크 큐에 일시 보관된 함수들은 비동기 처리 방식으로 동작한다.
정리 자바스크립트 자체는 싱글 스레드 방식으로 동작한다.
브라우저는 멀티 스레드로 동작하기 때문에 브라우저에서 태스크 큐와 이벤트 루프를 통해 비동기로 동작할 수 있는 것.
출처
위키북스, 『모던 자바스크립트 Deep Dive』, 이웅모
https://www.tutorialstonight.com/js/js-dom-navigation