jQuery / Method / .html() - 선택한 요소 안의 내용을 가져오거나, 다른 내용으로 바꾸는 메서드

jQuery의 .html()은 인수가 없으면 선택한 첫 요소의 내부 HTML 문자열을 읽고, 문자열을 전달하면 선택한 모든 요소의 내부 내용을 교체합니다. 바깥 요소 자체는 유지됩니다. 사용자 입력을 글자로 표시하려면 HTML로 해석하는 .html() 대신 .text()를 사용합니다.

읽기·쓰기와 text·val의 차이

.html() 공식 문서에서 읽기 결과는 문자열, 쓰기 결과는 jQuery 객체입니다. 내부 HTML은 DOM(Document Object Model, 문서 객체 모델)을 브라우저가 문자열로 표현한 값이므로 원본 파일의 공백·태그 표기와 완전히 같다고 가정하지 않습니다.

호출 결과 또는 동작 선택한 요소가 여러 개일 때
.html() 안쪽 태그를 포함한 HTML 문자열 첫 요소만 읽음
.html(markup) 내부를 HTML로 교체 모든 요소에 적용
.text() 태그를 제외한 텍스트 전체 요소의 텍스트를 합침
.text(value) 문자열을 텍스트로 표시 모든 요소의 내용 교체
.val() 입력·선택 컨트롤의 현재 값 읽기는 첫 컨트롤 기준

.text()는 문자열을 태그로 해석하지 않습니다. input이나 textarea의 현재 입력값은 안쪽 HTML이 아니므로 .val()로 읽고 씁니다. 선택 결과가 없을 때 .html() 읽기는 undefined입니다. 문자열의 메서드를 바로 호출하기 전에 대상이 존재하는지 확인합니다.

HTML 교체와 텍스트 표시를 비교하는 실습

전체 코드를 UTF-8의 html-demo.html로 저장하고 브라우저에서 엽니다. 공식 jQuery 파일 4.0.0을 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)에서 읽을 인터넷 연결이 필요하며 별도 설치는 없습니다. 읽기·교체·입력 표시·초기화와 아래 콘솔 예제를 3.7.1과 4.0.0에서 실행했습니다.

첫 상자에는 strong의 Alpha, 두 번째에는 em의 Beta가 있습니다. 결과 출력도 .text()를 사용하므로 읽은 HTML의 태그가 화면에서 실행되지 않고 문자열로 보입니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Read and replace HTML contents</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
    button, input { font: inherit; margin: 4px; max-width: 100%; }
    .box { border: 2px solid teal; padding: 16px; margin: 12px 0; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>HTML markup versus plain text</h1>
  <button type="button" id="read">Read</button>
  <button type="button" id="replace">Replace with fixed HTML</button>
  <label>Message <input id="message" value="&lt;b&gt;Hello&lt;/b&gt;"></label>
  <button type="button" id="plain">Show message as text</button>
  <button type="button" id="reset">Reset</button>
  <div id="examples">
    <div class="box"><strong>Alpha</strong></div>
    <div class="box"><em>Beta</em></div>
  </div>
  <p>Delegated clicks: <span id="clicks">0</span></p>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const $boxes = $(".box");
      const initial = $boxes.get().map(node => node.innerHTML);
      let clicks = 0;
      function report() {
        $("#result").text(`first html=${$boxes.html()}\ncombined text=${$boxes.text()}\nboxes=${$boxes.length}`);
      }
      $("#read").on("click", report);
      $("#replace").on("click", function () {
        $boxes.html('<button type="button" class="child">New</button>');
        report();
      });
      $("#plain").on("click", function () {
        $boxes.text($("#message").val());
        report();
      });
      $("#examples").on("click.htmlLesson", ".child", function () {
        clicks++;
        $("#clicks").text(clicks);
      });
      $("#reset").on("click", function () {
        $boxes.html(function (index) { return initial[index]; });
        $("#message").val("<b>Hello</b>");
        clicks = 0;
        $("#clicks").text(clicks);
        report();
      });
      report();
    });
  </script>
</body>
</html>

초기 출력에서 HTML은 Alpha 상자만 읽지만 텍스트는 두 상자를 합칩니다.

first html=<strong>Alpha</strong>
combined text=AlphaBeta
boxes=2
  1. Read로 현재 값을 다시 읽습니다. first html은 바깥 div를 제외한 내부 문자열입니다.
  2. Replace with fixed HTML을 누르면 두 상자 모두 New 버튼으로 교체됩니다. 상자 개수는 2로 유지되며 combined text는 NewNew입니다.
  3. 각 New 버튼을 누르면 Delegated clicks가 증가합니다. 교체를 반복해도 새 버튼 한 번 클릭에 한 번만 증가합니다.
  4. Show message as text를 누르면 입력값 <b>Hello</b>가 두 상자에 태그 문자까지 보입니다. b 요소가 생기지 않습니다.
  5. Message를 바꾸고 같은 버튼을 눌러 확인합니다. 이 경로는 입력값을 .val()로 읽어 .text()에 전달합니다.
  6. Reset은 처음 보관한 각 상자의 HTML과 입력값, 클릭 수 0을 복원합니다. 외부 위임 이벤트는 다시 등록하지 않습니다.

사용자 입력을 HTML로 넣지 않기

문자열을 HTML로 해석하는 메서드는 태그뿐 아니라 실행 가능한 속성이나 스크립트 등도 포함할 수 있습니다. 신뢰하지 않는 입력을 그대로 .html()에 넘기면 XSS(Cross-Site Scripting, 교차 사이트 스크립팅) 문제가 생길 수 있습니다. 예제의 교체 버튼은 코드에 고정한 HTML만 사용하며, 사용자가 적는 Message는 텍스트로 처리합니다.

‘서버에서 받았다’는 이유만으로 내용이 안전하다고 판단하지 않습니다. 사용자 댓글·검색어·외부 데이터처럼 글자 표시가 목적이면 .text()를 선택합니다. 서식 있는 외부 HTML이 실제로 필요하다면 허용할 요소·속성과 검증된 정화 처리를 별도로 설계합니다. 단순 문자열 치환이나 태그 일부 제거를 완전한 방어로 간주하지 않습니다.

교체된 자식의 이벤트와 데이터

.html(newHtml)은 기존 내용을 뒤에 추가하는 것이 아니라 전부 바꿉니다. 기존 자식 노드는 사라지고 그 자식에 연결한 jQuery 데이터와 직접 이벤트도 정리됩니다. 개발자 도구의 Console에서 다음 완전한 부분 코드를 실행하면 차이를 확인할 수 있습니다. 임시 컨테이너를 만들고 마지막 줄에서 정리합니다.

const $host = $('<div><button class="child">Old</button></div>').appendTo("body");
const $old = $host.find("button");
let direct = 0;
let delegated = 0;
$old.data("token", "stored").on("click", () => direct++);
$host.on("click", ".child", () => delegated++);
$host.html('<button class="child">New</button>');
$host.find("button").trigger("click");
console.log(`direct=${direct} | delegated=${delegated}`);
console.log(`old attached=${document.contains($old[0])} | old data=${typeof $old.data("token")}`);
$host.remove();
direct=0 | delegated=1
old attached=false | old data=undefined

새 버튼에는 이전 버튼의 직접 이벤트가 없으므로 direct는 0입니다. 부모는 교체되지 않았고 .on()으로 위임한 이벤트가 남아 있으므로 새 .child 버튼에서도 delegated는 1이 됩니다. $old 참조가 있어도 문서에 연결돼 있지 않으며 기존 token 데이터는 undefined입니다. 부모 자체를 교체하면 그 부모에 위임한 처리도 보존된다고 가정할 수 없습니다.

함수형 인수로 요소별 내용 만들기

.html(function(index, oldHtml))은 각 요소의 0부터 시작하는 인덱스와 이전 HTML을 받아 새 문자열을 반환합니다. 콜백에서 이전 내용을 활용할 때는 oldHtml 인수를 사용합니다. 다음 콘솔 예제는 개발자가 정한 A·B HTML에 각각 번호 span을 더합니다.

const $boxes = $("<div><b>A</b></div><div><i>B</i></div>");
$boxes.html(function (index, oldHtml) {
  return oldHtml + "<span>" + (index + 1) + "</span>";
});
console.log($boxes.get().map(node => node.innerHTML).join(" | "));
console.log(`empty getter=${typeof $().html()}`);
$boxes.remove();
<b>A</b><span>1</span> | <i>B</i><span>2</span>
empty getter=undefined

oldHtml은 자동으로 안전해지는 값이 아닙니다. 이 예제처럼 출처가 정해진 내용에 사용하며, 외부 입력을 붙이는 방식으로 확장하지 않습니다. 문서에 변화가 없으면 선택자의 범위와 대상 존재, HTML 문자열이 원하는 내부 구조인지, Network의 jQuery 로딩과 Console 오류를 확인합니다. 내용을 추가하려던 경우라면 내부 전체를 교체하는 .html()이 목적에 맞는지도 다시 살펴봅니다.