TIL - 프론트엔드 기술 면접 핵심 3대장 - JS 엔진, CRP, Reflow/Repaint

·2025년 6월 20일

📖 TIL

목록 보기
77/90

📚 오늘의 주제

리플로우/리페인트, 자바스크립트의 엔진 구조, 크리티컬 렌더링 패스 이렇게 3가지


🤔 면접 질문 가안

1️⃣ 브라우저에서 자바스크립트는 어떻게 실행될까요? 그 실행 과정의 흐름에 대해 설명해주세요

😅 나의 대답: 크롬에서 실행할 경우 js의 v8엔진이 실행되어..음..
(제대로 대답하지 못했다. node와 연관된 v8엔진까지만 기억났고 이걸 어떻게 흘러 가는지를 인지하지 못했던것이다.. ㅠㅠ)

🔍 분석: 자바스크립트는 브라우저에서 어떻게 실행될까? 그걸 처리하는 자바스크립트 엔진은 무슨 구조로 되어 있을까?

1-1. 🌐 js는 브라우저 안에서 돌아가는데 "누가" 처리를 해줄까

브라우저마다 js엔진이 존재하게 된다

  • 크롬: v8 엔진
  • 파이어 폭스: spiderMonkey
  • 사파리: javascriptCore

그 엔진이 자바스크립트 코드를 읽고, 분석하고 실행하는 역할을 해주는 것이다.

1-2. ⚙️ 자바스크립트 엔진은 "실행 흐름"이 있다

자바스크립트의 엔진 구조는 콜스택, 힙, 이벤트 큐, 이벤트 루프가 존재하게 된다

  • 콜스택: 지금 실행중인 함수들이 쌓이는 곳이라고 보면 된다. 예를 들어 함수1()이 함수2()를 호출할 경우 스택안에 쌓이게 된다.
  • : 객체들이 저장되는 공간이라고 보면 된다
  • 이벤트 큐: 클릭이나 setTimeout처럼 비동기 작업들이 기다리는 대기열이다.
  • 이벤트 루프: 이벤트 큐에 있는 작업들을 스택이 비면 꺼내서 실행해 주는 것

🔄 실행 흐름

브라우저 실행 → 자바스크립트 파일을 만나 V8엔진이 읽게 된다 → 코드 해석을 하고 실행할 건 Call Stack에 쌓이게 된다 → 비동기 작업은 이벤트 큐로 넘기게 된다 → 이벤트 루프가 스택이 비었다고 인지 하게 된다면 큐에서 하나씩 꺼내서 실행 하게 된다 → 이 과정들을 반복하게 된다.

💡 그러면 어떻게 대답하면 좋을까?

브라우저 안에는 자바스크립트 엔진이 있고, 그 엔진은 실행할 함수들을 스택안에 쌓게 됩니다. 비동기 작업은 큐에 대기하다가 이벤트 루프가 꺼내서 실행시키게 됩니다.


2️⃣ 브라우저가 화면에 무언가 보여주기까지 필요한 자원 처리 경로를 뭐라고 부를까요?

✅ 나의 대답: 크리티컬 렌더링 패스 입니다.

🔍 개념을 다시 집어보자

  • 브라우저가 화면에 콘텐츠를 표시하기까지 거치는 필수 렌더링 경로이다.
  • HTML → DOM, CSS → CSSOM 생성 후 결합 → Render Tree → Layout → Paint 흐름
  • 최적화 전략에도 도움이 된다
    • 불필요한 CSS/JS 제거 Minify
    • Critical CSS를 <head>에 직접 삽입하여 초기 렌더링 속도를 향상 시킬수 있다.
    • defer, async로 JS 비동기 로딩

3️⃣ Critical Rendering Path를 최적화하기 위한 방법 중 CSS를 미리 head에 넣어서 먼저 렌더링 되게 만드는 전략을 뭐라고 할까요?

😅 나의 대답: 대답하지 못함

✅ 정답: Critical CSS (크리티컬 CSS 삽입)

중요한 CSS만 <head>에 inline으로 넣어서 브라우저가 빨리 그릴 수 있게 하는 전략이다. SSR/CSR할때 초기 로딩 속도을 줄일 수 있는 전략이기도 하다.


4️⃣ 스타일이 바뀌었을 때, 다시 그리는 과정은 리페인팅 그런데 레이아웃 자체가 바뀌면 뭐라고 부를까요?

✅ 나의 대답: 리플로우 입니다.

  • Reflow: 레이아웃(위치, 크기 등) 자체를 다시 계산해서 그리는 과정
  • Repaint: 색상, 그림자 등 스타일만 변경되어 다시 그리는 과정

🔄 다시 오늘 배우고 익힌 내용으로 질의 응답!!

💡 Critical Rendering Path 관련 질문

🧩 Q1. 브라우저는 사용자가 웹사이트에 들어오자마자 무얼 먼저 받아들이나요?

😅 내 답변: ...ㅜ (받아들인다. 라는것에 조금 혼동을 느꼈다)

✅ 답: HTML → DOM트리, CSS → CSSOM 트리

브라우저는 웹 페이지에 접근하면 HTML과 CSS파일을 받아옵니다. HTML을 파싱해서 DOM트리를 만들고 CSS를 파싱해서 CSSOM을 생성합니다. 이 두가지를 결합해 Render Tree를 만들고 나서야 브라우저가 실제로 렌더링을 할 수 있게 됩니다.

🧩 Q2. DOM과 CSSOM을 합쳐 만든 구조는 무엇이며, 그게 왜 중요한가요?

✅ 내 답변: 렌더 트리

✅ 답: DOM과 CSSOM을 합쳐 만든 렌더 트리는 브라우저가 실제로 어떤 요소를 어떤 스타일로 그려야 할지 결정하기 위한 구조입니다. 이 트리가 완성되어야 화면이 나타나기 떄문에, 렌더링 속도와 SEO측면에서도 굉장히 중요한 역할을 합니다.

🧩 Q3. 브라우저가 레이아웃을 계산하고, 스타일을 칠하는 과정을 각각 뭐라고 부를까요?

✅ 내 답변: 브라우저가 레이아웃을 계산하여 레이아웃을 그리는 것을 리플로우, 레아아웃과 별게로 스타일을 변경시키는 것을 리페인팅이라고 합니다.

✅ 답: 레이아웃이나 요소 위치, 크기를 다시 계산하는 과정을 리플로우, 색상, 텍스트 스타일, 그림자 등 스타일 변경만 있을떄 다시 그리는 과정을 리페인트라고 합니다.

🧩 Q4. Critical Rendering Path를 최적화할 수 있는 방법 3가지만 예를 들어 말해보세요!

✅ 내 답변: Critical CSS를 활용하여 head태그 안에 인라인으로 스타일을 추가하는 방법을 들수 있습니다. 해당 방법을 통해 사용자는 로딩시간을 단축시킬 수 있습니다. 다른 방법으로는 defer, async를 속성을 추가하는 방법을 들 수 있습니다. 해당 속성을 사용하여 파싱 시간을 단축시킬 수 있기 때문입니다. (다른 하나는 생각하지 못함)

✅ 답: 첫째, 중요한 스타일은 Critical CSS로 <head>에 인라인 삽입입니다. 둘쨰, 자바스크립트는 defer나 aysnc 속성을 사용해 비동기로 로딩하여 HTML파싱을 막지 않도록 합니다. 셋째, 불필요한 리소스는 지연 로딩을 활요해 사용자가 실제로 필요할 떄 로딩되도록 합니다.

🧩 Q5. Critical CSS는 무엇이며 왜 사용하는 걸까요?

✅ 내 답변: head태그 안에 인라인으로 스타일을 추가하는 방법입니다. 해당 방법을 사용하여 Critical Rendering Path를 최적화 할 수 있고 로딩 시간을 단축 시킬 수 있죠.

✅ 답: Critical CSS는 초기 화면 렌더링에 필요한 핵심 스타일만 <head>에 인라인으로 넣어주는 방식입니다. 이를 통해 사용자에게 빠른 퍼스트 페인트를 제공하고 로딩 지연 없이 콘텐츠를 빠르게 보여줄 수 있어 UX향상에 도움이 됩니다.


🧠 자바스크립트 엔진 구조 관련 질문

🧩 Q6. 브라우저에서 자바스크립트 실행을 담당하는 엔진의 이름은 뭐예요?

✅ 내 답변: V8 엔진입니다.

✅ 답: 크롬은 구글에서 만든 V8 엔진을 사용합니다. 자바스크립트를 빠르게 컴파일하고 실행하는 역할을 하며, Node.js에서도 이 V8엔진을 사용합니다.

🧩 Q7. 함수 실행을 담는 공간으로, 위에서 아래로 쌓였다가 빠지는 이 구조는?

✅ 내 답변: 콜 스택입니다. 함수 실행을 담당하는 공간이죠. 함수를 사용할때 빠질 때는 LIFO 구조를 사용하게 됩니다.

✅ 답: 자바스크립트는 싱글 스레드 방식으로 작동하며, 함수 실행 컨텍스트가 쌓이는 공간을 콜스택 이라고 합니다 이는 LIFO 구조로 가장 나중에 들어간 함수가 가장 먼저 실행 종료됩니다.

🧩 Q8. setTimeout, 이벤트 리스너 같은 비동기 작업은 어디에 보관되나요?

✅ 내 답변: 이벤트 큐에 저장이 되게 됩니다. 해당 비동기 작업을 실행할때는 이벤트 루프를 통해서 실행하게 됩니다.

✅ 답: setTimeout, 이벤트 핸들러, fetch같은 비동기 작업은 이벤트 큐에 저장되고 이벤트 루프가 콜스택이 비었는지 감시하다가 작업을 하나씩 꺼내서 실행시킵니다.

🧩 Q9. Call Stack이 비었는지 계속 감시하면서, Event Queue에서 작업을 꺼내 실행시키는 이 친구는 누구일까요?

✅ 내 답변: 이벤트 루프입니다.

✅ 답: 이벤트 루프로 자바스크립트의 싱글 스레드 환경에서 비동기 처리를 가능하게 해주는 핵심 매커니즘입니다.

🧩 Q10. 자바스크립트는 싱글 스레드 언어예요. 그럼에도 불구하고 비동기 처리가 가능한 이유는 무엇인가요?

🤔 내 답변: 자바스크립트는 비동기 처리는 이벤트 큐에서 별도로 관리하고 있기 때문에 싱글 스레도로도 처리가 가능하게 됩니다.

✅ 답: 자바스크립트는 단일 스레드지만, 브라우저 환경이나 node.js 런타임이 비동기 작업을 Web APIs 또는 백그라운드 스레드로 처리한 후 완료된 작업을 이벤트 큐 → 콜 스택 순서로 실행시킵니다. 이를 통해 동시성을 지원합니다.


🎨 Reflow vs Repaint 관련 질문

🧩 Q11. 글씨 색상, 박스 그림자, 배경색 등이 바뀔 때 발생하는 브라우저의 작업은?

✅ 내 답변: 리페인트 작업을 수행하게 됩니다.

✅ 답: 요소의 구조나 위치 변경이 없고, 색상이나 배경색, 그림자 같은 시각적 속성만 바뀌는 경우, 브라우저는 리페인트 작업만 수행합니다.

🧩 Q12. 요소의 위치, 크기 등 레이아웃이 바뀔 때 다시 계산하는 브라우저 작업은?

✅ 내 답변: 리플로우 작업입니다.

✅ 답: 리플로우 작업이 발생하며 이는 전체 레이아웃을 다시 계산해야 하므로 리페인트 비용이 많이 드는 작업입니다.

🧩 Q13. Reflow가 성능에 더 부담이 되는 이유는 무엇인가요?

✅ 내 답변: 레이아웃 구조를 다시 그려야 하고 재 계산 작업이 필요하게 되므로 리소스를 많이 잡아먹게 됩니다. 그러므로 성능적으로 더 부담이 되게 됩니다.

✅ 답: Reflow는 레이아웃 계산 → 렌더 트리 변경 → 연쇄적인 노드 재배치를 유발할 수 있습니다. 이 때문에 작은 변경도 전체 페이지 리렌더링으로 이어질 수 있어 성능에 부담이 큽니다.

🧩 Q14. 성능 최적화를 위해 Reflow를 줄이고 싶다면 어떤 방법들이 있을까요?

🤔 내 답변: 애니메이션적으로 직접적으로 너비나 구조를 width, height를 변경하는것이 아닌 opacity나 transform을 사용하는 것을 예시로 들 수 있을 것 같습니다. 또한 스타일 적인 코드로도 해당 요소를 감추고 싶다 할때 visibility:hidden보다도 display:none을 활용하여 실제 요소의 구조를 잡은뒤 리페인팅을 조절하는 방법처럼요.

✅ 답: DOM 변경을 한번에 몰아서 처리하거나, classList로 스타일을 변경하는 방식이 더 좋고요, 애니메이션은 width, top같은 속성 보다 transform, opacity같은 GPU 가속이 가능한 속성을 사용하는 것이 Reflow를 줄이는데 도움이 됩니다.


💡 보너스! 상황 적용 문제 😈

🧪 상황 Q15. 어떤 웹페이지에서 텍스트 색상만 JS로 바꿨어요. 이때 브라우저는 Reflow를 발생시킬까요?

✅ 내 답변: 아닙니다. 텍스트의 레이아웃 구조가 변경되거나 레이아웃의 위치를 재계산을 할 필요성이 없기떄문에 리플로우가 아닌 리페인팅이 발생시키게 됩니다.

✅ 답: 아닙니다. 텍스트 색상은 시각적 속성만 바뀌므로 Repaint만 발생하고, Reflow는 발생하지 않습니다.

🧪 상황 Q16. Flexbox 안에서 아이템 순서를 바꾸는 .order 속성을 바꿨어요. 이때는 Reflow일까요? Repaint일까요?

❌ 내 답변: 리페인트입니다. 아이템 순서를 바꾸었다 하여도 실질적인 레이아웃 구조나 위치가 변경된것이 아닌 순서만 변경된것이라면 레이아웃을 재 계산할 필요성이 없어지기 때문에 리페인트입니다. (틀림)

✅ 답: .order 속성은 요소의 위치를 재배치하므로, 레이아웃이 변경됩니다. 따라서 이 경우에는 리플로우가 발생합니다.


📝 위 면접 질의에 대한 피드백

✅ 잘한 점들

  • 렌더 트리 부분은 정확하고 좋아요
  • 콜 스택 부분은 적절한 용어를 사용하시어 좋은 답변이라고 봅니다
  • Critical CSS 잘 설명하신거 같아요 굿굿
  • 리플로우/리페인트 차이 개념 구분은 잘하셨지만

🔧 개선이 필요한 부분들

1. 렌더 트리 설명 보완

근데 브라우저가 실제 렌더링할 수 있게 되는 구조라는 문장도 꼭 포함하면 좋을거 같아요.

2. 이벤트 큐/루프/비동기 흐름

잘 이해는 한것 같으나 실제 코드를 다루어 보며 익히며 연습해 보는걸 추천드려요!

3. Critical Rendering Path의 필요성

흐름은 맞지만 "왜 필요한가?"를 덧붙이면 좋을거 같아요. "사용자가 화면을 빨리 보게 하려면 이 경로를 바르게 최적화해야 해요" 정도 추가하심 될거 같은데요?

4. 비동기 처리가 가능한 이유 - 구체화 필요

이벤트 큐에서 관리한다 라는 말만 있는데, Web API같은 브라우저의 백그라운드 환경이 작업을 처리해준다. 라는 식으로 더 구체화가 필요해요. 이 부분은 좀더 공부해보심을 추천드려요

5. 리플로우/리페인트 기준 명확화

구조 변경이 일어나는가?를 기준으로 명확히 설명하는 방향으로 좀더 생각해보실 권장드립니다

6. 리플로우 성능 이슈 - 연쇄 반응 설명

구조를 다시 계산하는건 맞는데, 연쇄적인 DOM 재계산으로 이어질 수 있어요 같은 흐름을 추가하면 좀더 깊이가 생길거 같은데요?

7. .order 속성 오해

.order은 repaint된다는 부분은 틀려요 렌더링 순서가 바뀌므로 레이아웃 변경, 즉 리플로우가 발생하게 되죠. display, position, flex 같은 속성은 거의 무조건 리플로우 유발이라는것을 외우시면 좋습니다!

8. 예시 선택의 적절성

visivility vs display를 예시로 두었는데 관련은 있지만 약간 결이 달라요. 리플로우 리페인트 얘기라기보단.. 렌더링 최적화 중 요소 숨기기 방식의 비교에 가깝기 떄문에 적절한 예시는 아닌거 같네요.

🎯 더 좋은 예시:

"애니메이션이나 DOM 조작을 할떄 width, top을 조정하면 브라우저가 레이아웃 전체를 다시 계산해야하므로 리플로우가 발생합니다. 반면 transform은 GPU 가속을 활용하고 레이아웃에 영향을 주지 않기 떄문에 리플로우 없이 리페인트만 발생해서 훨씬 성능이 좋죠. 그래서 성능 최적화를 위해서는 transform, opacity같은 속성을 사용하는 것이 유리합니다."

9. defer/async 설명 정확성

defer/async에 대해서도 언급하셨는데요 약간 모호합니다. defer는 DOM파싱 이후 실행하고, async는 병렬 실행이지만 실행 시점이 랜덤이거든요.

🔧 개선된 설명:

"defer, async 속성을 통해 js를 비동기로 로드하면 HTML파싱을 막지 않아서 렌더링이 끊기지 않고 진행 될 수 있습니다. 특히 defer는 HTML파싱이 끝난 후에 실행되기 떄문에 스크립트가 렌더링을 지연시키지 않고, 페이작 더 빨리 보이게 되어 크리티컬 렌더링 패스 최적화에 효과적입니다"


💻 이제 실전 코드로 한번 다시 복습해보자

1️⃣ 리플로우 / 리페인트 최적화

🚫 안 좋은 예시: DOM을 반복해서 조작하는것

const list = document.getElementById("list")
for (let i = 0; i < 1000; i++){
    const item = document.createElement("li");
    item.textContent = `Item ${i}`;
    list.appendChild(item); // 리플로우 계속 발생
}

✅ 좋은 예시: Document Fragment로 묶어서 한번에 렌더링을 한다

const fragment = document.createDocumentFragment();
for(let i = 0; i < 1000; i++){
    const item = document.createElement("li")
    item.textContent = `Item ${i}`;
    fragment.appendChild(item)
}
list.appendChild(fragment) // 리플로우 한번

2️⃣ 자바스크립트 엔진과 렌더링 블로킹 최적화

🚫 안 좋은 예시

<head>
    <script src="app.js"></script> <!-- HTML 파싱 멈춤 -->
</head>

✅ 좋은 예시

<head>
    <script src="app.js" defer></script> <!-- HTML 파싱 끝난 후 실행 -->
</head>

📝 defer vs async 차이점

  • defer: HTML 파싱 끝난 후 실행, 렌더링 블로킹 없음 (정석)
  • async: 병렬로 받고, 도착 즉시 실행 → 실행 순서 예측 불가

💡 추가 최적화 팁

  • JS가 많으면, 필요한 페이지에만 나눠서 로드 (코드 스플리팅)
  • DOMContentLoaded 전에 중요한 작업은 피하자!

3️⃣ Critical Rendering Path 최적화

<head>
    <style>
        /* 페이지 첫 화면에 필요한 핵심 스타일만 추가한다 */
        header {
            background-color: #282c34;
            color: white;
            padding: 20px;
            font-size: 1.5rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        nav {
            display: flex;
            gap: 15px;
        }

        nav a {
            color: white;
            text-decoration: none;
            font-weight: 600;
        }

        nav a:hover {
            text-decoration: underline;
        }
    </style>

    <!-- 나머지 css는 지연 로딩을 시킨다 : 왜? 렌더링 속도를 빠르게 하기 위함 초기뷰에 필요한 css만 로딩해서 보여주기 떄문에 -->
    <link rel="stylesheet" href="main.css" media="print" onload="this.media='all'"/>
</head>

🤔 media? onload가 뭔가요?

media="print" 속성 활용한 지연 로딩 트릭

<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'"/>

media="print"의 역할:

  • 브라우저에게 "이 CSS는 인쇄용 스타일"이라고 알려주는 트릭
  • 초기 화면 렌더링 시에는 이 CSS 적용을 생략함
  • 결과적으로 초기 화면 렌더링에 방해가 되지 않게 하는 꼼수

onload="this.media='all'"의 역할:

  • CSS 파일이 완전히 다운로드된 후에 실행되는 이벤트 핸들러
  • CSS가 다 로딩되면 media 속성을 'all'로 변경
  • 모든 미디어(화면, 인쇄 등)에 적용되도록 만듦
  • 나중에 CSS가 실제로 화면에 적용되도록 함

Critical Rendering Path 최적화 전체 전략

1. Critical CSS 인라인 삽입

<head>
  <style>
    /* 페이지 첫 화면에 필요한 핵심 스타일만 추가 */
    header {
      background-color: #282c34;
      color: white;
      padding: 20px;
      font-size: 1.5rem;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    
    nav {
      display: flex;
      gap: 15px;
    }
    
    nav a {
      color: white;
      text-decoration: none;
      font-weight: 600;
    }
    
    nav a:hover {
      text-decoration: underline;
    }
  </style>
  
  <!-- 나머지 CSS는 지연 로딩 -->
  <link rel="stylesheet" href="main.css" media="print" onload="this.media='all'"/>
</head>

2. 이미지 지연 로딩

<img src="hero-image.jpg" alt="메인 이미지" loading="lazy">

3. Google Fonts 최적화

<!-- FOUT(Flash of Unstyled Text) 방지 -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">

4. JavaScript 비동기 로딩

<!-- defer: HTML 파싱 끝난 후 실행, 순서 보장 -->
<script src="app.js" defer></script>

<!-- async: 병렬 다운로드 + 즉시 실행, 순서 보장 안됨 -->
<script src="analytics.js" async></script>

핵심 개념 정리

defer vs async 차이점

defer:

  • HTML 파싱이 완료된 후에 실행
  • 여러 스크립트가 있으면 순서대로 실행
  • 렌더링 블로킹 없음 (정석 방법)

async:

  • 스크립트를 병렬로 다운로드
  • 다운로드 완료 즉시 실행
  • 실행 순서 예측 불가능

성능 측정 도구

  • Lighthouse: 렌더링 경로 분석 가능
  • Core Web Vitals: FCP, LCP 등 성능 지표 확인

실무 적용 팁

  1. 첫 화면에 필요한 스타일만 인라인으로 <head>에 삽입
  2. 이미지에는 loading="lazy" 적용 → 스크롤되기 전까지 로딩 안 함
  3. Google Fonts도 display=swap 사용해서 FOUT 방지
  4. 코드 스플리팅으로 필요한 페이지에만 JS 나눠서 로드
  5. DOMContentLoaded 전에 중요한 작업은 피하기
profile
주니어 프론트엔드 성장기 기록기록

0개의 댓글