Bfcache (Back-Forward Cache)

브라우저 페이지 이동 시 페이지의 상태를 완전히 캐싱해 사용자가 <뒤로가기(Back) | 앞으로가기(Forward)> 할때, 빠르게 로드할 수 있도록 하는 매커니즘

Bfcache는 페이지를 새로 로드하지 않고 캐시에서 복원하기 때문에 더 빠른 페이지를 탐색하게 하기 때문에 사용자 경험에 중요하다.

 

주요 특징

1. 전체 페이지 상태 저장

  • DOM 상태, Javascript 메모리(힙 메모리 영역까지 포함), 스크롤 위치 등을 완전한 상태로 저장 (페이지를 메모리에 보관, = 스냅샷 보관)
  • 새로 데이터를 요청하거나 렌더링하지 않고 (Javascript 재실행하지 않음) 이전 페이지의 상태를 그대로 복원
    • 네트워크 요청이 감소되어 서버 부담이 줄어듬
    • Task Queue에서 Timer Promise도 일시 정지되었다가, 복원시 이어서 실행 (=> 즉, 작업들이 모두 보존되었다가 다시 처리)
  • 뒤로 앞으로 이동 시 복원 (폼 입력이나 스크롤 위치 등도 복원됨)

2. 브라우저 수준 캐싱

  • HTTP 캐싱(브라우저 캐시)와 다르게 네트워크 요청 없이 메모리에서 페이지를 즉시 복원
  • 스크롤 위치와 필터 상태가 그대로 유지

 

활성화 확인

1. 브라우저 개발자 도구에서 Network > Disable cache 체크 해제 확인

 

2. Performance 탭 / Network 탭(-> 네트워크 요청 발생 x)

뒤로가기 > 앞으로 가기 시에 캐시 테스트 데모사이트 

페이지 전환 스냅샷을 보고 Event log에서 아래와 같이 확인 가능하다.

 

Javascript 로 확인

  • event.persisted === true이면 페이지가 Bfcache에서 복원된 것.
  • pageshow, pagehide 이벤트를 통해 Bfcache를 관찰할 수 있지만, 페이지가 Bfcache에 의해 복원되었는지 여부는 정확히 알수있지만, 페이지 이동시에는 현재 페이지를 캐시 하려고 했다는 사실만 알 수 있다고 한다.
window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    console.log("Page was loaded from Bfcache");
  } else {
    console.log("Page was loaded normally");
  }
});

 

 

Bfcache와 일반 브라우저 캐시, 같은거 아냐 ??

  • 브라우저캐시는 정적리소스(HTML, CSS, JS)를 캐싱하는데, Bfcache는 DOM 상태나 Javascript 메모리, 스크롤 위치에 대해 캐싱한다. 
  • 또한 Bfcache는 즉시 로드이므로 지연을 느끼기 어려운데 반해, 브라우저캐시는 네트워크 요청이 필요한 경우에 따라 지연이 발생할 수 있다.
    • 브라우저 캐시는 HTTP 캐싱 규칙에 따라 작동한다.
    • Bfcache는 특정 조건을 충족해야 활성화된다..

 

 

Bfcache... 제약 조건이 있다.

페이지가 완전히 캐싱될 수 있는 경우만 동작하고 이 조건에 해당하지 않으면 데이터 요청&렌더링은 당연히 발생한다.

  1. 페이지에서 명시적으로 캐싱 비활성화
    • 응답 헤더에 Cache-Control: no-store 또는 no-cache 포함할 경우
  2. 자바스크립트 코드에 unload, beforeunload 이벤트 리스너 등록한 경우
    • 실제 페이지가 새로 로드되는 것이 아니므로 load 이벤트가 발생되지 않음
    • PV 수집하는 경우 정확한 수집이 어렵다
    • unload -> pagehide 사용
  3. 오픈된 네트워크 연결 정리 (WebSocket 또는 특정 네트워크 연결이 열려있을 경우)
    • 페이지 이동 시 WebSocket이나 EventSource 연결을 닫아야 동작
  4. 일부 브라우저나 버전에서 Bfcache를 제한적으로 지원할 경우 (can i use에서 확인하기)
    • 브라우저마다 캐시가 저장되는 시간도 다르다고 하는데, 크롬은 3분이지만 상황에 따라 유지되지 않을수도 있다고 한다.
    • same-site 여부, 데스크톱/모바일 여부, https 적용 여부등에 따라서도 다른 동작할 수 있다고한다. (구체적인 조건/상황이 다름을 인지하기)
  5. 메모리가 부족한 경우

 

자 여기까지 개념을 익혔고,

사실 실무에서 고민하게 되었던 부분이 무한 스크롤링 패치에서 였다.

클라이언트 상태와 스크롤 위치가 초기화 되었는데, (useEffect로 데이터 초기화), 옵저버로 스크롤 이벤트 마다 데이터를 동적으로 가져와서 넣었기 때문에 데이터가 비어있는 상태에서 스크롤 위치가 복구되지 않는 부분이 문제였다.

초반의 서버사이드 패치로 데이터를 넣은 부분은 뒤로가기 시 다시 요청하지 않으므로, 딱 거기까지 스크롤이 복구되었다.

(스크롤 위치와 데이터 간 불일치)

 

그래서 데이터를 세션스토리지에 담고, 접근 시 복원하는 작업을 진행했다.

(이때 timestamp를 넣어줘서, 세션이어도 만료시간을 지정해주고 특정 시간이 지나면 최신화를 위해 리프레시가 발생하도록 유도했다)

-> 이렇게 하니 스크롤 위치가 복원되어 따로 스크롤 복원을 진행하지 않았다.

 

리스트를 그리고 뒤로가기/ 앞으로가기를 하면서 모든 페이지에서 이러한 방법을 채택한 것은 아니었지만(리소스에 따른 우선순위) 사용자 경험은 역시 재미있는 작업인 것 같다.

 

 

 

자바스크립트 인터프리터가 함수의 선언, 할당, 실행을 나눠해서 모든 선언이 코드의 선두로 끌어올려진 것처럼 동작하는 현상의 개념

즉, 자바스크립트 엔진이 실행컨텍스트가 활성화 될 때 변수정보를 수집하는데 

이때 실제 끌어올려지지는 않았지만 수집과정에서 엔진이 수집된 정보를 토대로 변수들을 알고 있기 때문에 끌어올려진 것으로 간주하는 개념 

 

설명보기

Q. var, let 둘 다 호이스팅이 된다? 

- 정답은 둘 다 호이스팅되나 동작때문에 var만 실행 시 에러가 나지 않는다. 이때문에 var만 호이스팅된다고 착각할 수 있다.

 

설명

var는 선언과 할당을 동시에 실행하므로 호이스팅되었을때, undefined가 메모리에 저장된다.

let은 선언과 할당을 별도로 실행한다.

따라서 호이스팅되었을때, 선언은 되었지만 할당은 되지 않아서 메모리에 저장되지 않으며 TDZ(Temporary Dead Zome)에 들어간다. 따라서 호출 시 초기화 전에는 엑세스할 수 없다는 에러가 뜨는데, 이것때문에 호이스팅 되지 않았다고 착각할 수 있다.

 

 

 

 

 

실행 컨텍스트는 실행한 코드에 제공 할 환경 정보들을 모아놓는 객체라고 생각하자.

콜 스택에 쌓아서 전체 코드를 관리하고 있어서 환경과 순서를 보장한다.

동작

실행 컨텍스트는 크게 3가지 동작을 한다.

  1. 내부 환경 정보 기록: 실행컨텍스트가 활성화 되는 시점(실행)에 선언된 변수를 수집한다. 
  2. 외부 환경 정보를 구성한다.  
  3. this 값을 설정하는 동작 구성

 

수집 정보

- 자바스크립트 엔진이 활용할 목적으로 생성할 뿐, 개발자가 코드에 접근 할 수 없다.

Variable Environment

  • 선언 시점의 Lexical Environment의 스냅샷이므로 변경사항이 반영되지 않는다.
  • Variable Environment를 생성하여 정보를 담고 복사하여 Lexical Environment를 만든다.

Lexical Environment

처음엔 Variable Environment와 같으나 변경사항이 실시간으로 반영된다. (이후에는 Lexical을 사용하는 이유)

  1. Environment Record (환경 기록)
    • 현재 컨텍스트내의 식별자(변수명, 함수명, 매개변수명....) 들에 대한 정보를 순서대로 저장한다.
    • 이때 정보의 수집을 마치게되면 실행컨텍스트가 관여할 코드들은 실행전이지만, 자바스크립트 엔진은 이미 해당 환경에 속해 있는 정보(변수명..)들을 알고 있기 때문에 '호이스팅'이 발생 할 수 있다.
  2. Outer-Environment Reference (외부 환경 참조)
    • 현재 호출된 함수가 선언된 당시 상위의 Lexical Environment를 참조한다.
    • 스코프 체인이 가능하게 하는 수집자료 (중첩된 자바스크립트 코드에서 스코프 탐색을 하기위해 사용)

This Binding

  • this 식별자가 바라봐야 할 대상 객체
  • 실행 컨텍스트 활성화 당시에 this로 지정된 객체가 저장된다. (this는 함수호출 방식 및 전역 여부에 따라 결정)



 

사용자 동작을 나열하며 간략히 공부해보자.

 

>>> 사용자 동작 시작

1. https://okayoon.tistory.com/ 를 검색한다.

 

2. DNS(Domain Name System)에서 주소를 검색, 해당 ip 주소를 찾아서 사용자가 입력한 URL과 함께 전달한다.

- 이때 캐싱된 DNS 기록이 있는지 먼저 확인한다.

 

3. 전달받은 ip 주소를 이용하여 웹 브라우저는 웹 서버에게 해당 웹 사이트의 리소스를 요청하고 해상 웹 서버는 리소스를 전달한다.

 

>>>> 전달받은 리소스를 통해 브라우저 렌더링 과정 시작

Main flow 예시 (출처: http://taligarsiel.com/Projects/howbrowserswork1.htm)

4. DOM Tree 구축을 위해 HTML을 파싱한다.

- DOM Tree는 문서 객체 모델로 모든 요소, 속성, 텍스트 등 (문서노드, 요소노드, 속성노드, 텍스트노드)을 트리 구조로 표현한 것.

- 중간에 외부 스타일 로드를 만나게되면 DOM Tree 생성을 중단하고 CSS 파싱을 시작한다.

- Style Sheets를 통해 CSSOM Tree를 생성한다.

- CSSOM Tree는 스타일 노드들을 트리 구조로 표현한 것. 

 

5. Render Tree 생성 

- DOM Tree와 CSSOM Tree를 통해 Render Tree를 생성한다.

- Render Tree는 최종적으로 실제 브라우저에 표현되어야하는 노드들로만 구성하여 트리 구조로 표현한 것.

(ex: <head> 태그나 display: none;와 같이 브라우저에 그려지지 않는 것들은 포함되지 않는다.)

- 만약 과정 중에 JS 코드를 만나게 된다면 JS 파싱을 위해 렌더링이 잠시 중단되고, JS 파싱이 끝난 후 재개된다.)

 

6. Layout (레이아웃)

- Render Tree와 viewport를 통해 노드들의 화면상 위치와 크기를 계산한다. 

(ex: 1em -> 16px)

 

7. Paint (그리기)

- Layout 단계에서 계산한 레이아웃을 각 레이어에 px 단위로 그린다. (이 단계에서 Layer 생성)

- Layout에 포함안된 속성들도 그린다. (ex: 색상, 그림자효과 등)

- Update Layer Tree: 렌더링에 사용될 최종 레이어들을 계산하여 생성하며 생성조건은 root object, css filter, position, transform, overflow, canvas, video....

- 사용자 경험을 위해 렌더링 엔진은 모든 파싱을 기다리지 않고 동시에 일부를 그리기 시작한다.

 

8. Composite (합성)

- Update Layer Tree에서 생성한 레이어들을 합성하여 한장의 비트맵으로 만들어 실제 화면에 나타낸다.

- 별도의 합성 스레드를 두어 합성 과정만 별도로 분리해서 진행한다.

 

Q. 만약 Re-rendering이 될 경우에는?

Reflow (리플로우)

- Layout 단계의 변화가 있을때 발생한다.

- Layout 계산부터 다시한다 (Layout -> Paint -> Composite)

Repaint (리페인트)

- Paint 단계의 변화가 있을때 발생한다.

- 재 결합된 Render Tree를 기반으로 다시 그린다. 

- Repaint는 Reflow가 발생하지 않고도 발생할 수 있다. (Paint -> Composite)

 

Q. <scrpt> 태그를 만나서 HTML 파싱이 중단되는 문제를 해결할 수는 없나?

script 태그에 async와 defer 속성을 사용할 수 있다. (둘 다 사용 시 우선순위는 async)

- async: 비동기

- defer: html 파싱이 끝나고 진행하는 속성

 

 


더 깊은 내용에 대해 알고 싶거나, 참고한 글을 확인하고 싶다면!

https://d2.naver.com/helloworld/59361

http://taligarsiel.com/Projects/howbrowserswork1.htm

 

+ Recent posts