DOM - create, read, update, delete
11 Nov 2020 -
4 minute read
- Document 객체
- Node vs. Element
- 자식, 부모 element 찾기
- Create - element 생성: createElement, append
- Read - element 조회: querySelector, querySelectorAll
- Update - element 갱신: textContent, classList.add
- Delete - element 삭제: remove, removeChild
Document 객체
- 웹 페이지 자체로서, 페이지 콘텐츠(tree구조)의 진입점
- 웹 페이지의 html 요소에 접근 하기 위해서는 document 객체를 통해야 한다.
Node vs. Element
- node: DOM의 가장 기본이 되는 데이터 타입
- node 타입에 따라 구분된 하위 클래스: Document, Element, DocumentFragment
- 노드 기능을 구현하는 모든 오브젝트들이 이런 하위 클래스 중 하나를 기반으로 하는 것
- Node > Element
자식, 부모 element 찾기
- 자식 엘리먼트 찾기: parentNode.children
- 부모 엘리먼트 찾기: node.parentElement
<body>
<div class = "mom">
<div class="daughter"></div>
</div>
</body>
const parentDiv = document.querySelector('.mom')
const childDiv = document.querySelector('.daughter')
document.body.children[0] // 결과값: <div class="mom">...</div>
parentDiv.children // 결과값: HTMLCollection [div.daughter]
childDiv.parentElement // 결과값: <div class="mom">...</div>
Create - element 생성: createElement, append
Document.createElement(‘태그’)
- html 요소를 만들어 반환하는 메소드. 태그를 지정할 수 있다.
- tag name을 인식할 수 없을 경우 HTMLUnknownElement를 반환한다.
- 이 때 만들어진 element는 아직 dom에 연결되지 않은 상태이다.
// div 태그 만들기
const newEl = document.createElement('div');
Document.createDocumentFragment()
- 비어있는 새 DocumentFragment 생성
- DocumentFragment: 부모가 없는 document 객체로, 노드를 추가할 수 있는 임시 객체
- tree구조에 포함되지 않기 때문에 fragment를 변경해도 문서에는 영향을 미치지 않는다. // 가상의 노드 객체
- children을 추가할 때 브라우저 reflow가 일어나지 않는다. // better performance
- documentFragment 생성 -> element들을 documentFragment에 추가 -> documentFragment를 DOM tree에 추가
append
- parentNode.append(노드)
- 부모 노드의 마지막 자식 뒤에, 즉 맨 끝에 노드 객체 또는 DOMString 객체를 삽입
- 여러 개 노드와 문자를 추가할 수 있다.
- rest parameter를 사용할 수 있다. // parentNode.append(…nodes);
- 반환하는 값이 없다.
- JavaScript 메소드로 확장성이 뛰어나다.
- IE(인터넷 익스플로러) 호환 안됨
// parentDiv에 ChildDiv 추가
parentDiv.append(childDiv)
appendChild
- Node.appendChild()
- append와 같은 기능: 부모 노드의 자식 노드 맨 끝에 노드 삽입
- 오직 노드 객체만 추가 가능하다. (문자열 노드는 지원하지 않는다)
- 추가한 Node 객체를 반환한다.
- 하나의 노드만 추가할 수 있다. // 노드가 이미 존재할 경우 새로운 위치로 이동시킨다.
- DOM 메소드
- IE 호환
append vs. appenChild
- append는 문자열을 바로 추가할 수 있다.
// 결과: <div id="userId">Hello</div>
// append: 문자열을 바로 추가
document.getElementById('userId').append('Hello!');
// appendChild: 텍스트 노드를 따로 만들어 변수에 저장한 후 parameter를 통해 불러온다.
const addText = document.createTextNode('Hello!');
document.getElementById('userId').appendChild(addText);
- append는 element와 text를 동시에 추가할 수 있다.
const parentDiv = document.createElement('div');
const childP = document.createElement('p');
parentDiv.append("Add text", childP);
document.body.append(parentDiv)
// 결과:
// <div>
// "Add text"
// <p></p>
// </div>
prepend
- parentNode.prepend()
- 부모 노드의 첫 자식 노드 앞에, 즉 맨 앞에 노드 객체 또는 DOMString 객체를 삽입
- 여러 개 노드와 문자를 추가할 수 있다.
- rest parameter를 사용할 수 있다. // parentNode.prepend(…nodesToPrepend);
- 반환하는 값이 없다.
- JavaScript 메소드로 확장성이 뛰어나다.
- IE(인터넷 익스플로러) 호환 안됨
append vs. prepend
- 객체를 삽입하는 위치만 다를 뿐 다른 특징은 동일하다.
- append는 자식노드 맨 끝에, prepend는 자식노드 맨 앞에 객체를 삽입한다.
Read - element 조회: querySelector, querySelectorAll
- document.querySelector(‘셀렉터’): 하나의 엘리먼트 조회
- document.querySelecorAll(‘셀렉터’): 모든 엘리먼트 조회
- get으로 시작하는 DOM 조회 메소드는 오래된 방식: 이전 브라우저 호환성에 대응할 때 사용한다. // getElementById, getElementByClassName, getElementByName
Update - element 갱신: textContent, classList.add
textContent
- Node.textContent: 태그 안에 텍스트 콘텐츠 추가
childDiv.textContent = '텍스트 추가'
// 결과: <div class="daughter">텍스트 추가<div>
textConctent vs. innerText vs. innerHTML
- Node.textContent: 모든 요소의 원시 텍스트 반환.
- HTMLElement.innerText: 원시 텍스트가 화면에 렌더링 된 모습(스타일을 인식하며 숨겨진 요소의 텍스트를 반환하지 않음) => 사람이 읽을 수 있는 요소만 처리
- Element.innerHTML: HTML을 반환. 요소의 텍스트를 가져오거나 사용할 수 있지만 HTML로 분석할 필요가 없기 때문에 textConctent를 쓰는 것이 더 유용하다. XSS(Cross-Site Scripting) 공격에 취약하다.
- innerText를 수정 시 요소의 모든 자식 노드와 그 텍스트가 영구히 파괴된다. 이후 해당 텍스트 노드를 새로 삽입하는 것이 불가능하다. innerText 값을 읽을 때 reflow 발생.
- reflow: 웹 페이지(문서)의 일부 또는 전체를 다시 렌더링 -> 속도가 느려지게 되므로 reflow는 최소화되어야 한다.
- 결론: textContent가 성능과 보안 면에서 모두 유리하다!
class update: classList
- element.classList.add(‘클래스명’) : 클래스 값 추가
- element.classList.remove(‘클래스명’) : 클래스 값 제거
const childDiv2 = document.createElement('div')
// <div></div>
childDiv2.classList.add('son')
// <div class="son"></div>
id update: id
- element.id : id 값 추가
const parentDiv = document.querySelector('.mom')
// <div class="mom"></div>
parentDiv.id = 'parent'
// <div class"mom" id="parent"></div>
속성 update: setAttribute, removeAttribute
- element.setAttribute(‘속성name’, ‘속성value’) : 속성값 추가
- element.removeAttribute(‘속성name’) : 속성값 제거
childDiv2.setAttribute('say', 'helloMom')
// <div class"son" say="helloMom"></div>
childDiv2.removeAttribute('say')
// <div class="son"></div>
Delete - element 삭제: remove, removeChild
- node.remove() : 해당 엘리먼트 제거
<div id="div-01"></div>
<div id="div-02"></div>
<div id="div-03"></div>
const el = document.querySelector('#div-02')
el.remove()
// 결과
// <div id="div-01"></div>
// <div id="div-03"></div>
- node.removeChild(child) : 자식 엘리먼트를 지정하여 제거
<div class = "mom">
<div class="daughter"></div>
<div class="son"></div>
</div>
const parentDiv = document.querySelector('.mom')
const childDiv = document.querySelector('.daughter')
parentDiv.removeChild(childDiv)
// 부모 노드에 대한 정보가 없을 경우
const childDiv2 = document.querySelector('.son')
if (childDiv2.parentNode) {
childDiv2.parentNode.removeChild(childDiv2)
}
- innerHTML : 가장 간편한 방법이지만 보안상 문제(XSS공격)가 있기 때문에 사용하지 않는 것이 좋다.
parentDiv.innerHTML = ''