avatar
miseullang

history API 구조에 대한 호기심

until 에디터 테스트 겸 작성해보는 history 웹 API에 대한 간단한 포스트
history API
a month ago
·
6 min read

until 에디터 테스트 겸 작성해보는 history 웹 API에 대한 간단한 포스트

헤헷 썸네일 등록이 안 되네

✅ 발단


history 웹 API를 이용한 SPA 구현

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 네비게이션 메뉴 -->
    <!-- data-url 속성을 사용하여 각 페이지의 식별자 저장 -->
    <ul>
      <li>
        <a href="/" data-url="home">Home</a>
      </li>
      <li>
        <a href="/about" data-url="about">About</a>
      </li>
      <li>
        <a href="/contact" data-url="contact">Contact</a>
      </li>
    </ul>

    <!-- 페이지 컨텐츠가 표시될 영역 -->
    <div id="page">Home 페이지입니다.</div>

    <script>
      // BOM(Browser Object Model)의 주요 객체들:
      // - history: 브라우저의 방문 기록을 관리
      // - Screen: 사용자의 화면 정보를 제공
      // - Location: 현재 URL 관련 정보와 메서드를 제공

      // 각 페이지의 컨텐츠를 저장하는 객체
      const pages = {
        home: "Home 페이지입니다.",
        about: "About 페이지입니다.",
        contact: "Contact 페이지입니다. <input text='text' placeholder='연락처 입력 하세요' />",
      };

      // 모든 네비게이션 링크 요소를 선택
      const aEls = document.querySelectorAll("a");

      // 각 링크에 클릭 이벤트 리스너 추가
      aEls.forEach((aEl) =>
        aEl.addEventListener("click", function (e) {
          // 링크의 기본 동작(페이지 이동) 방지
          e.preventDefault();
          
          // 클릭된 링크의 data-url 속성값 가져오기
          const page = e.currentTarget.dataset.url;
          
          // History API를 사용하여 새로운 상태 추가
          // pushState(상태객체, 제목(미사용), URL)
          history.pushState({ page: page, custom: "test" }, "", `/${page}`);
          
          // 페이지 컨텐츠 업데이트
          document.getElementById("page").innerHTML = pages[page];
        })
      );

      // 브라우저 뒤로가기/앞으로가기 처리
      window.addEventListener("popstate", function (event) {
        // 상태가 없는 경우 기본값으로 'home' 사용
        // ?. 는 옵셔널 체이닝으로, event.state가 null/undefined일 때 에러 방지
        const page = event.state?.page || "home";
        
        // 현재 상태 로깅 (디버깅용)
        console.dir(event.state?.page || "home");
        console.log("뒤로가기 또는 앞으로 가기가 눌렸음");
        
        // 페이지 컨텐츠 업데이트
        document.getElementById("page").innerHTML = pages[page];
      });
    </script>
  </body>
</html>

// 코드 출처 : 수코딩

🧐 궁금한 점

1. history API는 스택 구조인가?

https://developer.mozilla.org/ko/docs/Web/API/History

공식 문서를 보면 pushpop으로 추가하고 제거하는 형태임을 알 수 있다. 이런 방식은 배열에서 주로 사용되므로, history 데이터가 스택 구조로 저장되는지 궁금했다.

이 부분에 대해 조사해 본 결과, history API는 연결 리스트 구조를 가지고 있지만 뒤로가기/앞으로가기는 스택 구조와 유사하게 동작한다고 정리할 수 있었다.

그리고 여기서 또 다른 질문이 생겼다.

2. 스택처럼 동작한다면 왜 배열로 구현하지 않았을까?

배열이 연결리스트보다 용량 차지가 적고, 배열처럼 동작할건데 왜 연결리스트로 구현했을까?

단순히 메모리 차지 용량을 비교한다면 연결리스트보다 배열이 유리한 게 맞다. 하지만 배열은 메모리 공간을 연달아 차지한다. 하지만 연결리스트는 포인터가 있기 때문에 메모리에 분산해서 저장할 수 있다.

마치 테트리스를 하는 상황과 같다.

포인터가 5칸짜리 막대 블럭이라면 메모리는 1칸짜리 블럭 5개인 셈이니 메모리를 훨씬 유연하게 사용할 수 있다.

이 때 공부했던 내용이 머리를 스치고 지나갔다…! 고마워요 CS 50🥲

브라우저의 가용 메모리는 충분하므로, 배열 형태를 사용해서 얻는 이점(메모리 공간 절약)보다, 그보다 조금 더 큰 연결 리스트 형태를 사용하는 것이 여러 모로 이득이었을 것이다.

연결리스트를 사용하면 동적 할당과 해제가 가능하고, 중간 삽입과 삭제가 가능하다. (그리고 가비지 컬렉션 면에서도 더 효율적이라고 한다!)

결론

사실 정확한 정보는 아직 찾지 못했다. 여러 문서를 뒤져보고 할루시네이션이 가장 적은 AI라는 퍼블렉시티한테도 물어봤지만 명확한 답변은 얻지 못했다. 짐작해 보자면 History API는 웹 API니까 브라우저 엔진마다 조금씩의 차이가 있기 때문에 문서별로 기재해 둔 정보의 차이가 있는 것 같다.

명확한 결론을 내지는 못 했지만, 뭔가에 호기심을 가지고 탐구해봤다는 점에 점수를 주기로 했다.







amugoto morugetda