innerHTML, innerText, textContent는 DOM 요소의 콘텐츠를 읽어오고 설정할 수 있다는 점에서 비슷해 보이지만 조금씩 차이가 있다.
속성 | HTML 태그 처리 | 숨겨진 요소의 텍스트 | 보안(XSS) |
innerHTML | HTML 태그로 렌더링 | 가져올 수 있음 설정할 수 있음 |
위험 가능성 큼 |
innerText | 단순 텍스트로 변환하여 렌더링 | 가져올 수 있음 설정할 수 있음 |
상대적으로 안전 |
textContent | 단순 텍스트로 변환하여 렌더링 | 가져올 수 없음 설정할 수 있음 |
상대적으로 안전 |
innerHTML
innerHTML은 element의 속성으로 element 내에 포함된 HTML 또는 XML 마크업을 가져오거나 태그와 함께 입력하여 내용을 직접 설정할 수 있다. 즉, innerHTML을 사용하면 내부 HTML 코드를 자바스크립트 코드에서 새 내용으로 쉽게 변경할 수 있는 것이다.
// HTML 코드와 함께 작성 가능
document.getElementById('a').innerHTML = '<p>innerHTML 테스트</p>'
// 스타일 적용
document.getElementById('a').innerHTML = "<span style='color:grey'>innerHTML style 적용 테스트</span>"
innerHTML의 문제
innerHTML은 이처럼 유용하고 자주 사용되지만 치명적인 단점을 가지고 있다.
1) 보안 취약점
innerHTML을 사용하면 사용자가 입력한 데이터를 직접 HTML에 삽입할 수 있다. 이렇게 하면 크로스 사이트 스크립팅(XSS) 공격과 같은 보안 취약점이 발생할 수 있다. 악의적인 사용자가 스크립트를 주입하여 사이트를 공격하거나 사용자의 개인정보를 탈취할 수 있는 것이다.
cf) XXS(Cross-Site-Scripting)란?
악의적인 목적을 가진 이가 웹 사이트에 악성 스크립트를 주입하는 행위. 사용자의 입력을 받기 위해 만들어진 <input>, <textarea>와 같은 창에서 누군가 스크립트를 작성하고 그 스크립트가 innerHTML과 연결되어 파싱을 거친 뒤 코드단에 주입되면, 관리자가 예상치 못한 결과가 발생할 수 있다. 스크립트 내용에 따라 쿠키나 세션, 토큰 등의 탈취가 가능해서 이를 인증이나 세션 관리에 사용하고 있는 사이트에 침입하거나 심각한 피해를 입힐 가능성이 있다.
2) 성능 문제
innerHTML을 사용하면 해당 요소의 내용을 변경할 때마다 브라우저는 내용을 파싱하고 다시 렌더링해야 한다. 이 작업은 성능에 부정적인 영향을 미칠 수 있으며, 대규모 어플리케이션에서는 특히 문제가 될 수 있다.
3) DOM 구조 파괴
innerHTML을 사용하면 해당 요소의 내용 전체를 대체하므로 요소의 이벤트 핸들러 및 데이터 바인딩과 같은 기존의 DOM 요소와 관련된 작업을 파괴할 수 있다. 또한 무엇보다 데이터를 재사용하는 것이 아니라 처음부터 다시 렌더링하는 것이므로 앞서 언급한 성능 문제로 이어질 수 있다.
innerHTML의 동작 방식은 element가 기존에 가지고 있던 자식 요소를 파싱하고 새 값으로 들어온 newDOMString 또한 파싱하여 DocumentFragment 객체를 생성한다. 이렇게 파싱된 객체를 element의 innerHTML에 넣어 줌으로써 element의 DOM 요소가 새로운 모습으로 변하는 과정을 가진다. 따라서 파싱을 거치는 innerHTML은 강력한 만큼 성능상의 약점을 지니는데 이때 innerText, textContent, insertAdjacentHTML 등 대안의 문법들을 사용하면 성능적인 이점을 가져갈 수 있다.
일반 텍스트의 삽입만을 원한다면 innerText, textContent 두 문법을 사용할 수 있지만 textContent는 원시 텍스트를 다루며 성능상의 이점을 더 취하기 때문에 textContent의 사용이 더 권장될 것이다. 한편 HTML 요소를 직접적으로 삽입하고 싶다면 insertAdjacentHTML을 innerHTML 대신 사용할 수 있는데 보안상의 이슈가 해결되진 않지만 insertAdjacentHTML은 자식 요소의 파싱 과정이 생략되기 때문에 성능상 훨씬 더 좋은 우위를 차지한다.
insertAdjacentHTML
insertAdjacentHTML은 HTML 또는 XML 같은 특정 텍스트를 파싱하고 특정 위치의 DOM Tree 안에 원하는 Node를 추가한다. 이때 이미 사용 중인 element는 다시 파싱하지 않는다. 즉 DOM 요소를 모두 리렌더링하는 innerHTML과는 달리 기존 DOM 요소를 유지한 채 특정 위치에 추가하고자 하는 DOM 요소를 삽입하는 방식으로 동작하는 것이다. 따라서 innerHTML보다 작업이 덜 들고 빠르다.
insertAdjacentHTML을 사용하면 시작 태그의 앞, 시작 태그의 뒤, 종료 태그의 앞, 종료 태그의 뒤에 노드를 삽입하는 것이 가능하다.
element.insertAdjacentHTML(position, text);
위 기본 문법에서 position에는 아래의 단어들만 사용할 수 있다.
- beforebegin: 타겟 요소의 앞에 요소를 생성. 시작 태그의 앞. 형제 레벨.
- afterbegitn: 타겟 요소의 뒤에 요소를 생성. 시작 태그의 뒤. 자식 요소.
- beforeend: 타겟 요소의 종료 태그 바로 앞에 요소를 생성. 종료 태그의 앞. 자식 요소.
- afterend: 타겟 요소의 종료 태그 바로 뒤에 요소를 생성. 종료 태그의 뒤. 형제 레벨.
// <div id="one">one</div>
let el = document.getElementById('one');
el.insertAdjacentTML('afterend', '<div id="two">two</div>')
// <div id="one">one</div><div id="two">two</div>
innerText
innerText는 element의 속성으로 element 내에서 사용자에게 보이는 텍스트 값들을 가져오거나 설정할 수 있다.
// innerHTML과 달리 text 값만 다루기 때문에 HTML 태그 사용 불가능
document.getElementById('a').innerText = 'innerText 테스트';
// HTML 태그를 넣으면 태그도 텍스트 값으로 인식하고 <p>innerText 테스트</p> 문자열 그대로 적용함.
document.getElementById('a').innerText = '<p>innerText 테스트</p>';
innerHTML과 innerText 비교
const innerT = document.getElementById('innerT');
innerT.innerText = "<div style='color:red'>innerText 테스트</div>";
console.log(innerT)
// 스타일이 지정되지 않은 기본 폰트로 <div style='color:red'>innerText 테스트</div> 출력.
const innerH = document.getElementById('innerH');
innerH.innerHTML = "<div style='color:red'>innerHTML 테스트</div>";
console.log(innerH)
// 스타일이 적용된 빨간색 폰트로 'innerHTML 테스트' 출력
textContent
textContent는 Node의 속성으로 사용자에게 보이는 텍스트 값만 읽어오는 innerText와는 달리 <script>나 <style> 태그에 상관 없이 해당 노드가 가지고 있는 모든 텍스트 값들을 읽어 온다.
아래 예제를 통해 display:none으로 숨겨진 텍스트를 어떻게 가져오는지 각 속성들의 차이점을 살펴 보자.
<div id="content">
test
<span style="display: none;">innerText에서는 확인 불가</span>
</div>
const content = document.getElementById('content');
// 1. innerHTML - HTML 전체를 다 가져옴
console.log(content.innerHTML);
/*
test
<span style="display: none;">innerText에서는 확인 불가</span>
*/
// 2. innerText
// 사용자에게 보이는 텍스트만 가져옴. 숨겨진 텍스트는 사용자에게 보이지 않기 때문에 test만 가져옴.
console.log(content.innerText); // test
// 3. textContent - 숨겨진 텍스트까지 포함해 텍스트 값을 모두 다 가져옴.
console.log(content.textContent);
/*
test
innerText에서는 확인 불가
*/
innerHTML의 또 다른 대안
DOM 조작을 수행하기 위해 innerHTML의 또 다른 대안으로 다음과 같은 방법을 고려해 볼 수 있다.
document.createElement()를 사용하여 새로운 요소를 생성하고 appendChild() 또는 insertBefore()와 같은 함수를 사용하여 요소를 DOM에 삽입한다.
이렇게 하면 innerHTML로 삽입한 DOM 요소를 모두 리렌더링할 필요 없이 필요한 DOM 요소만 따로 추가할 수 있으므로 성능상의 이슈를 최소화할 수 있다.
그리고,
textContent 또는 createTextNode()를 사용하여 텍스트 내용을 변경하도록 한다.
innerHTML의 가장 큰 단점은 사용자가 입력한 HTML 요소가 스크립트에 반영될 수 있다는 점이다. 만일 악의적인 사용자가 <script>무시무시한 악의적 코드</script>를 입력하고 그 사이에 악성코드가 삽입한 페이지로 강제 이동시키는 코드 등의 악의적인 스크립트 코드를 작성한다면 이만큼 공포스러운 경우는 없을 것이다.
참고할 점은 HTML5에서는 innerHTML과 함께 삽입된 <script> 태그가 실행되지 않도록 지정하므로 스크립트를 사이에 추가해서 생기는 문제는 크게 걱정할 필요는 없다. 그런데 만일 <img onerror='악의적인 코드'> 방식이라면 어떨까? 스크립트 태그는 HTML5에서 보호해 주지만 <img> 등과 같이 error 이벤트를 실행시킬 수 있는 태그는 그렇지 못하므로 매우 위험하다.
이러한 점에서 textContent의 경우에는 사용자가 입력한 값들을 모두 텍스트 노드로 취급하기 때문에 그런 위험을 줄일 수 있다.
'Javascript > Javascript' 카테고리의 다른 글
[Javascript] 즉시 실행 함수(IIFE) (2) | 2024.10.30 |
---|---|
[Javascript] this (0) | 2024.10.29 |
[Javascript] Fetch API (0) | 2024.10.28 |
[Javascript] XMLHttpRequest로 AJAX 요청하기 (0) | 2024.10.25 |
[Javascript] console 사용 시 쉼표(,)와 +의 차이 (0) | 2024.10.22 |