37장. Set과 Map
Set
•
Set 객체는 중복되지 않는 유일한 값들의 집합
구분 | 배열 | Set 객체 |
동일한 값을 중복하여 포함할 수 있다. | O | X |
요소 순서에 의미가 있다. | O | X |
인덱스로 요소에 접근할 수 있다. | O | X |
•
Set 객체의 특성 === 수학적 집합의 특성
→ 교집합, 합집합, 차집합 등을 구현할 수 있다!
Set 객체의 생성
•
생성자 함수로 생성. 인수를 전달하지 않으면 빈 Set 객체 생성.
const set = new Set();
console.log(set); // Set(0) {}
JavaScript
복사
•
Set 생성자 함수는 이터러블을 인수로 전달받아 Set 객체를 생성한다.
이때, 이터러블의 중복된 값은 Set 객체에 요소로 저장되지 않는다.
→ 이러한 특성을 활용해 배열에서 중복된 요소를 제거할 수 있다.
const uniq = array => [...new Set(array)]
console.log(uniq([2, 1, 3, 2, 4, 1])); // [2, 1, 3, 4]
JavaScript
복사
•
Set.prototype.size : Set 객체의 요소 개수
const { size } = new Set([1, 2, 3, 3]);
console.log(size); // 3
JavaScript
복사
•
Set.prototype.add : Set 객체에 요소 추가
const set = new Set();
console.log(set); // Set(0) {}
set.add(1);
console.log(set); // Set(1) {1}
// 메서드 체이닝 허용
set.add(1).add(2).add(3).add(3);
console.log(set); // Set(3) {1, 2, 3}
// NaN === NaN 은 false지만 Set은 이를 중복된 값으로 처리한다.
set.add(NaN).add(NaN);
console.log(set); // Set(1) {NaN}
JavaScript
복사
•
Set.prototype.has : Set 객체에 특정 요소가 존재하는지 확인
const set = new Set([1, 2, 3]);
console.log(set.has(1)); // true
console.log(set.has(4)); // false
JavaScript
복사
•
Set.prototype.delete : Set 객체의 특정 요소 삭제
// Index가 아닌, 삭제하려는 요소 값을 전달해야함.
// 존재하지 않는 요소를 삭제하려 하면 에러 없이 무시된다.
const set = new Set([1, 2, 3]);
set.delete(2);
console.log(set); // Set(2) {1, 3}
JavaScript
복사
•
Set.prototype.clear : Set 객체의 모든 요소 일괄 삭제
const set = new Set([1, 2, 3]);
set.clear();
console.log(set); // Set(0) {}
JavaScript
복사
•
Set.prototype.forEach : Set 객체의 요소 순회
◦
첫 번째 인수 : 현재 순회 중인 요소 값
◦
두 번째 인수 : 현재 순회 중인 요소 값
◦
세 번째 인수 : 현재 순회 중인 Set 객체 자체.
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}
*/
JavaScript
복사
•
Set 객체는 이터러블 이므로 for … of 문으로 순회할 수 있으며, 스프레드 문법과 디스트럭처링의 대상이 될 수도 있다.
•
교집합 : 집합 A와 집합 B의 공통 요소
// for ... of 사용
Set.prototype.intersection = function (set) {
const result = new Set(this);
for (const value of set) {
// 2개의 set의 요소가 공통되는 요소이면 교집합의 대상이다.
if (this.has(value)) result.add(value);
}
return result;
}
// Array.prototype.filter 사용
Set.prototype.intersection = function (set) {
return new Set([...this].filter(v => set.has(v)));
}
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 3]);
// setA와 setB의 교집합
console.log(setA.intersection(setB)); // set(2) {2, 3}
// setB와 setA의 교집합
console.log(setB.intersection(setA)); // set(2) {2, 3}
JavaScript
복사
•
합집합 : 집합 A와 집합 B의 중복 없는 모든 요소
// for ... of 사용
Set.prototype.union = function (set) {
// this(Set 객체)를 복사
const result = new Set(this);
for (const value of set) {
// 합집합은 2개의 Set 객체의 모든 요소로 구성된 집합이다. 중복 X
result.add(value)
}
return result;
}
// 스프레드 문법 사용
Set.prototype.union = function (set) {
return new Set([...this, ...set]);
}
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 5]);
// setA와 setB의 합집합
console.log(setA.union(setB)); // set(5) {1, 2, 3, 4, 5}
// setB와 setA의 합집합
console.log(setB.union(setA)); // set(5) {2, 5, 1, 3, 4}
JavaScript
복사
•
차집합 : 집합 A에는 존재하지만 집합 B에는 존재하지 않는 요소
// for ... of 사용
Set.prototype.diffrence = function (set) {
// this(Set 객체)를 복사
const result = new Set();
for (const value of set) {
// 차집합은 어느 한쪽 집합에는 존재하지만 다른 한쪽 집합에는 존재하지 않는 요소로 구성된 집합이다.
result.delete(value)
}
return result;
}
// Array.prototype.filter 사용
Set.prototype.diffrence = 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]);
// setA와 setB의 차집합
console.log(setA.diffrence(setB)); // set(2) {1, 3}
// setB와 setA의 차집합
console.log(setB.diffrence(setA)); // set(0) {}
JavaScript
복사
•
부분 집합과 상위 집합 : 집합 A가 집합 B에 포함되는 경우 집합 A는 집합 B의 부분 집합 이며, 집합 B는 집합 A의 상위 집합 이다.
// this가 subset의 상위 집합인지 확인한다.
Set.prototype.isSuperset = function (subset) {
// for ... of 문법 사용
for (const value of subset) {
// superset의 모든 요소가 subset의 모든 요소를 포함하는지 확인.
if (!this.has(value)) return false;
}
return true;
};
// Array Method 사용
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]);
// setA가 setB의 상위 집합인지 확인한다.
console.log(setA.isSuperset(setB)); // true
// setB가 setA의 상위 집합인지 확인한다.
console.log(setB.isSuperset(setA)); // false
JavaScript
복사
Map
•
Map 객체는 키와 값의 쌍으로 이루어진 컬렉션.
구분 | 객체 | Map 객체 |
키로 사용할 수 있는 값 | 문자열 또는 심벌 값 | 객체를 포함한 모든 값 |
이터러블 | X | O |
요소 개수 확인 | Object.keys(obj).length | map.size |
Map 객체의 생성
•
생성자 함수로 생성. 인수를 전달하지 않으면 빈 Map 객체 생성
const map = new Map();
console.log(map); // Map(0) {}
JavaScript
복사
•
Map 생성자 함수는 이터러블을 인수로 전달받아 Map 객체를 생성한다.
이때, 인수로 전달되는 이터러블은 키와 값의 쌍으로 이루어진 요소로 구성되어야 한다.
const map1 = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(map1) // Map(2) {"key1" => "value1" , "key2" => "value2"}
const map2 = new Map([1, 2]); // TypeError
JavaScript
복사
•
Map 객체에서 중복된 키를 갖는 요소는 존재할 수 없다.
•
Map.prototype.set : Map 객체에 요소 추가
const map = new Map();
console.log(map); // Map(0) {}
map.set('key1', 'value1');
console.log(map); // Map(1) {"key1" => "value1"}
// 메서드 체이닝 허용
const map2 = new Map();
map2
.set('key1', 'value1')
.set('key2', 'value2');
console.log(map2); // Map(2) {"key1" => "value1", "key2" => "value2"}
JavaScript
복사
•
객체는 문자열 또는 심벌 값만 키로 사용할 수 있지만, Map 객체는 키 타입에 제한이 없다.
→ Map 객체와 일반 객체의 가장 두드러지는 차이점
•
Map.prototype.get : Map 객체에서 특정 요소를 취득
const map = new Map();
const lee = { name: "Lee" };
const kim = { name: "Kim" };
map
.set(han, "developer")
.set(kim, "designer");
console.log(map.get(han)); // developer
console.log(map.get(lee)); // undefined
JavaScript
복사
•
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(han)); // false
JavaScript
복사
•
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"}
JavaScript
복사
•
Map.prototype.clear : Map 객체의 요소 일괄 삭제
const lee = { name: "Lee" };
const kim = { name: "Kim" };
const map = new Map([[lee, "developer"], [kim, "designer"]]);
map.clear();
console.log(map); // Map(0) {}
JavaScript
복사
•
Map.prototype.forEach : Map 객체의 요소 순회
◦
첫 번째 인수 : 현재 순회 중인 요소 값
◦
두 번째 인수 : 현재 순회 중인 요소 키
◦
세 번째 인수 : 현재 순회 중인 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"
}
*/
JavaScript
복사
•
Map 객체는 이터러블 이다. 따라서 for … of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상이 될 수 있다.
•
Map 객체는 이터러블이면서 동시에 이터레이터인 객체를 반환하는 메서드를 제공한다.
Map 메서드 | 설명 |
Map.prototype.keys | Map 객체에서 요소 키를 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다. |
Map.prototype.values | Map 객체에서 요소 값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다. |
Map.prototype.entriess | Map 객체에서 요소 키와 요소 값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다. |
38장. 브라우저의 렌더링 과정
•
브라우저는 다음과 같은 과정을 거쳐 렌더링을 수행한다.
1.
브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답받는다.
2.
브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
3.
브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
4.
렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
HTML 파싱
•
브라우저 요청에 의해 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트
→ 브라우저가 이해할 수 있는 자료구조(객체)로 변환하여 메모리에 저장.
•
브라우저 렌더링 엔진은 다음 그림과 같은 과정을 통해 DOM을 생성한다.
•
즉, DOM은 HTML 문서를 파싱한 결과물이다.
CSS 파싱
•
브라우저 렌더링 엔진이 HTML 문서를 파싱하다가, link 태그나 style 태그를 만나면 DOM 생성을 일시 중단하고 link 태그에 연결된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 파싱 과정을 거치며 해석하여 CSSOM을 생성한다. (바이트 → 문자 → 토큰 → 노드 → CSSOM)
•
CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하기 시작하여 DOM생성을 재개한다.
렌더 트리 생성
•
HTML / CSS 파싱의 결과물로 생성된 DOM과 CSSOM은 렌더 트리로 결합됨.
•
렌더 트리는 렌더링을 위한 트리 구조의 자료구조.
•
브라우저 화면에 렌더링되는 노드만으로 구성.
•
완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)을 계산하는데 사용.
•
브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.
•
이 과정들은 아래의 경우에 대해서 반복될 수 있다.
메롱똥히히

자바스크립트 파싱
•
토크나이징
◦
단순한 문자열인 자바스크립트 소스코드를 어휘 분석하여 문법적 의미를 갖는 코드의 최소 단위인 토큰 들로 분해한다. 이 과정을 렉싱 이라고 부르기도 하지만 토크나이징과 미묘한 차이가 있음.
•
파싱
◦
토큰들의 집합을 구문분석하여 AST(추상적 구문 트리) 를 생성.
◦
AST : 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조.
•
바이트코드의 생성과 실행
◦
AST는 인터프리터가 실행할 수 있는 중간 코드인 바이트코드 로 변환되고 인터프리터에 의해 실행됨.
리플로우 / 리페인트
•
DOM API에 의해 DOM, CSSOM이 변경되면, 다시 렌더 트리에 결합되고 변경된 렌더 트리를 기반으로 레이아웃 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링하는 과정.
•
레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행됨.
39장. DOM ^_^
•
DOM 이란?
HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료 구조
노드 객체들로 구성된 트리 자료구조를 DOM(Document Object Model)(이라 한다.
1. 노드
HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다.
•
트리자료구조
- 노드들의 계층 구조로 이루어짐
•
노드 객체의 타입
1 ) 문서노드 : DOM트리의 최상위에 존재하는 루트 노드로서 document 객체를 가르킴
전역 객체 window의 document 프로퍼티에 바인딩 되어 있다.
2 ) 요소노드 : 문서의 구조를 표현
3 ) 어트리뷰트 노드 : 부모 노드와 연결되어 있지 않고 요소 노드에만 연결되어 있다.
어트리뷰트 노드에 접근하여 어트리뷰트를 참조하거나 변경하려면
요소노드에 먼저 접근해야함
4 ) 텍스트 노드 : 요소 노드의 자식이며 DOM트리의 최종단이다.
DOM은 HTML문서의 계층적 구조와 정보를 표현하는 것은 물론노드 객체의 종류,
즉 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API로 제공한다.
2. 요소 노드를 취득할 수 있는 메서드
1.
getElementById ⇒ 인수로 전달한 id 어트리뷰트 값을 갖는 요소 노드 탐색하여 반환
- 언제나 단 하나의 요소 노드를 반환한다. 따라서 문서 내에 중복된
id 값을 갖는 요소가 여러개 존재할 때 첫 번째에 해당하는 요소 노드만 반환한다.
- 해당하는 id 값을 갖는 요소가 존재하지 않을때는 null 을 반환함!
2.
getElementByTagName ⇒ 인수로 전달한 태그 이름을 갖는 모든 요소 노드 탐색하여 반환
- 여러개의 요소 노드 객체를 갖는 HTMLCollection 객체를 반환하며 이 객체는
유사 배열 객체이면서 이터러블임!
3.
getElementByClassName
⇒ 인수로 전달한 class 어트리뷰트 값을 갖는 모든 요소 노드 탐색하여 반환
-여러개의 요소 노드 객체를 갖는 HTMLCollection 객체를 반환하며 이 객체는
유사 배열 객체이면서 이터러블임!
* HTMLCollection 객체
1) 여러 개의 결과값을 반환하기 위한 DOM컬렉션 객체이다.
2) for…of문으로 순회 가능하며 스프레드 문법을 사용하여 간단히 배열로 변환가능하다.
3) 노드 객체의 상태 변화를 실시간으로 반영한다.
4.
querySelector ⇒ 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환
5.
querySelectorAll ⇒ 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환
3. DOM 조작
새로운 노드를 생성하여 DOM에 추가하거나 기존 노드를 삭제 또는 교체하는 것
이 과정이 리플로우 리페인트가 발생하는 원인이 되어 성능에 영향을 주므로 복잡한
콘텐츠를 다루는 DOM 조작은 최적화를 위해 주의해서 다루기^_^
1. HTML 마크업 문자열을 파싱하여 DOM에 반영하는 메서드
1 ) innerHTML (getter , setter 모두 존재하는 접근자 프로퍼티)
⇒ HTML 마크업을 취득하거나 변경
- innerHTML 프로퍼티를 사용한 DOM 조작은 구현이 간단하고 직관적이라는 장점이 있지만
크로스 사이트 스크립팅 (웹사이트에 악성 스크립트를 주입하는 행위) 공격에 취약한 단점이 있다.
- 새로운 요소를 삽입할 때 삽입될 위치를 지정할 수 없다는 단점이 있다.
- 마크업 문자열을 할당하면 기존의 자식 노드까지 모두 제거하고 다시 처음부터 새롭게
자식 노드를 생성
따라서 복잡하지 않은 요소를 새롭게 추가할 때 유용함!
2 )insertAdjacentHTML
⇒ 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입
- 기존 요소에는 영향을 주지않고 새롭게 삽입될 요소만 파싱하여 자식요소로 추가하므로
innerHTML 프로퍼티보다 효율적이고 빠름
- 마크업 문자열을 파싱하므로 크로스 사이트 스크립팅 공격에 취약하다는 점은 동일
3. 노드를 직접 생성/삽입/삭제/치환 하는 메서드
•
생성
createElement(tagName) ⇒ 요소 노드를 생성하여 반환
DOM에 추가되지 않고 홀로 존재하는 상태 (추가 처리 별도로 필요)
createTextNode(text) ⇒ 텍스트 노드를 생성하여 반환
생성할 뿐 요소 노드에 추가하지않음 (추가 처리 별도로 필요)
추가하는 처리 메서드 → appendChild
추가할 요소 노드가 많을때 DOM에 요소 노드를 반복하여 추가하는 과정은 비효율적이므로
컨테이너 요소를 사용하는것이 좋음 DOM에 추가할 요소 노드를 생성하여 DocumentFragment
노드에 자식노드로 추가한 다음 DocumentFragment 노드를 DOM에 추가
•
삽입
appendChild ⇒ 항상 마지막 자식 노드로 추가
insertBefore(newNode, childNode) ⇒ 첫 번재 인수로 전달받은 노드를 두번째 인수로 전달받은
노드 앞에 삽입 (지정한 위치에 노드 삽입 가능)
두번째 전달받은 인수 null일때 마지막 자식 노드로 추가
(appendChild와 같은 동작 실행)
•
복사
cloneNode([deep: true | false]) ⇒ 노드의 사본을 생성하여 반환
true : 깊은 복사 → 모든 자손 노드가 포함된 사본 생성
false: 얕은 복사 → 노드 자신만의 사본 생성,
자손노드 복사하지 않으므로 텍스트 노드 없음
html 파일에 <ul id=”fruits”>가 있고 리스트에는 <li>Apple</li>만 있는 상황
•
삭제
removeChild(child) 메서드 ⇒ child 매개변수에 인수로 전달한 노드를 DOM에서 삭제
•
치환
replaceChild(newChild, oldChild) ⇒ 자신을 호출한 노드의 자식노드를 다른노드로 교체
4. 어트리뷰트
id, class, style 어트리뷰트는 모든 HTML 요소에 사용할 수 있지만
type, value, checked 어트리뷰트는 input 요소에만 사용가능
1.
attributes 프로퍼티
⇒ getter만 존재하는 읽기 전용 접근자 프로퍼티
요소 노드의 모든 어트리뷰트 노드의 참조가 담긴 NamedNodeMap 객체를 반환
2.
HTML 어트리뷰트 조작
⇒ getAttribute/setAttribute 메서드를 사용하면 attribute 프로퍼티를 통하지 않고
요소 노드에서 메서드를 통해 직접 HTML 어트리뷰트 값을 취득하거나 변경 가능
<input id= "user" type="text" value="jiwon">
const $input = document.getElementById('user');
const inputValue = $input.getAttribute('value'); // value 어트리뷰트 값을 취득
console.log(inputValue); // jiwon
$input.setAttribute('value', 'mincheol'); // value 어트리뷰트 값을 변경
console.log($input.getAttribute('value')); // mincheol
JavaScript
복사
•
hasAttribute 메서드 ⇒ 특정 HTML 어트리뷰트가 존재하는지 확인
•
removeAttribute 메서드 ⇒ 특정 HTML 어트리뷰트를 삭제할때
4.
HTML 어트리뷰트 VS DOM 프로퍼티
<input id= "user" type="text" value="jiwon"> 요소가 파싱되어 생성된 요소 노드 객체에는
id, type, value 어트리뷰트에 대응하는 id, type, value 프로퍼티가 존재하며, 이 DOM 프로퍼티들은 HTML 어트리뷰트의 값을 초기값으로 가지고있다.
DOM 프로퍼티는 getter와 setter 모두 존재하는 접근자 프로퍼티
⇒ 참조와 변경이 가능하다.
•
HTML 어트리뷰트의 역할
⇒ HTML 요소의 초기 상태를 지정. 변하지않음
•
DOM 프로퍼티
⇒ 사용자가 입력한 최신상태 관리 HTML 요소에 지정한 어트리뷰트 값에는 어떠한 영향도 주지않음
예외 ) id 어트리뷰트에 대응하는 id 프로퍼티는 사용자의 입력과 아무런 관계없이 항상 동일함
* 요소 노드는 2개의 상태, 즉 초기상태와 최신 상태를 관리해야한다.
* 요소 노드의 초기 상태에는 어트리뷰트 노드가 관리 ⇒ 초기값 그대로 (사용자의 입력 영향 X)
* 요소 노드의 최신 상태 DOM 프로퍼티가 관리 ⇒ 사용자의 입력에 의해 상태가 변하면 값도 변함