<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>yongyong</title>
    <link>https://happy7yong.tistory.com/</link>
    <description>뭇별에 닿기를</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 16:31:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>happy7yong</managingEditor>
    <image>
      <title>yongyong</title>
      <url>https://tistory1.daumcdn.net/tistory/7565666/attach/d43177ed07d540cb83d237fdb648fbc1</url>
      <link>https://happy7yong.tistory.com</link>
    </image>
    <item>
      <title>고정 높이의 가상 스크롤 구현하기</title>
      <link>https://happy7yong.tistory.com/43</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Intro&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 서비스에 무한스크롤을 통해 리스트를 무한적으로 보여주는 기술을 많이 사용하게 되었습니다. 이렇듯 무한 스크롤로 목록을 계속 불러오다보면, 10,000개까지 데이터가 쌓이는 경우가 발생하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 리스트에 10,000개를 그대로 넣어봤더니 아래 영상처럼 2번 Fixed height 에서 1번 Naive 화면이동이 느려지고 상호작용할때는 전반적으로 끊김이 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2026-06-22 20.50.30.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCT8dm/dJMcafNTYzi/YINkn63jeK8BfL8JWcNw41/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCT8dm/dJMcafNTYzi/YINkn63jeK8BfL8JWcNw41/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCT8dm/dJMcafNTYzi/YINkn63jeK8BfL8JWcNw41/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bCT8dm/dJMcafNTYzi/YINkn63jeK8BfL8JWcNw41/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;383&quot; data-filename=&quot;화면 기록 2026-06-22 20.50.30.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10,000개를 모두 브라우저 DOM에 띄워두었기 때문입니다. 브라우저가 아이템을 모두 가지고 있어야하기 때문에 무거워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수치상으로도 확인해볼까요?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순수하게 DOM에 10,000개를 모두 렌더링 했을때&lt;/b&gt;와&lt;b&gt; 가상 스크롤을 적용&lt;/b&gt;했을때를 비교해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 화면(Naive rendering)은 영상에서 1번 Naive 공간인&amp;nbsp;&lt;b&gt;DOM에 10,000개를 모두 렌더링한 상태&lt;/b&gt;이고, 두번째 화면(react window)는 2번인 Fixed height 공간인&amp;nbsp;&lt;b&gt;가상스크롤을 적용한 상태&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;111.png&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHxK9/dJMcaaFORmT/img4R3nkDQr3SKQtTkapq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHxK9/dJMcaaFORmT/img4R3nkDQr3SKQtTkapq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHxK9/dJMcaaFORmT/img4R3nkDQr3SKQtTkapq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHxK9%2FdJMcaaFORmT%2Fimg4R3nkDQr3SKQtTkapq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;375&quot; data-filename=&quot;111.png&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;222.png&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4E4kv/dJMcahdQb1l/XaUQuhu6l3AVkSD2qShUf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4E4kv/dJMcahdQb1l/XaUQuhu6l3AVkSD2qShUf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4E4kv/dJMcahdQb1l/XaUQuhu6l3AVkSD2qShUf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4E4kv%2FdJMcahdQb1l%2FXaUQuhu6l3AVkSD2qShUf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;375&quot; data-filename=&quot;222.png&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS heap size와 DOM node 수를 비교해보면, 10,000개를 모두 DOM에 그린 페이지는 메모리와 노드 수를 엄청나게 차지하는 반면, react-window 페이지는 DOM에 올라와 있는 노드가 현저히 적었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이렇듯, 아이템이 한꺼번에 많이 들어오는 최악의 상황을 어떻게 대비할 수 있을까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 보고 있지 않는 아이템이 함께 dom에 그려져있는 것은 불필요하다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 보고 있는 화면 안에서, 필요한 만큼만 아이템을 렌더링하면 어떨까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 이 방법을 사용한 기술이 &lt;b&gt;가상 스크롤 &lt;/b&gt;입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가상 스크롤이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스크롤은 보이는 부분만 렌더링하는, 즉 &lt;b&gt;윈도잉(windowing)&lt;/b&gt; 기법을 활용한 기술입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;윈도잉(windowing)이란?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 데이터 집합을 한꺼번에 처리하지 않고, 현재 관심 있는&amp;nbsp;&lt;b&gt;일부 구간(window)만 선택해&amp;nbsp;&lt;/b&gt;다루는 기법입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 창문을 봐도 바깥의 모든 배경이 보이는 게 아니라, 창틀에 걸친 구간만 보입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇듯&amp;nbsp;&lt;b&gt;불필요한 부분은 가리고 필요한 부분만 보이도록 하는 동작을 이용한 기술&lt;/b&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음 렌더링되는 요소의 수는 전체의 아주 작은 일부분이며, 이 렌더링 구간을 이동시키면서 보이지 않게 된 구간은 새로 보이는 구간과 교체&amp;middot;재활용합니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crf8ca/dJMcageVXNd/iUkeq3ddRznxutbtjj6SJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crf8ca/dJMcageVXNd/iUkeq3ddRznxutbtjj6SJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crf8ca/dJMcageVXNd/iUkeq3ddRznxutbtjj6SJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcrf8ca%2FdJMcageVXNd%2FiUkeq3ddRznxutbtjj6SJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;309&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트에 윈도잉 기법을 활용하면&lt;b&gt; 가상 스크롤&lt;/b&gt;을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 보고 있는 화면이 창문이라면, 보이지 않는 부분은 가리고 보이는 부분만 창문을 통해 보이도록 구현하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgBVUi/dJMcaaZ4BRA/sjD6qEaYOtSnArKRC5dA00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgBVUi/dJMcaaZ4BRA/sjD6qEaYOtSnArKRC5dA00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgBVUi/dJMcaaZ4BRA/sjD6qEaYOtSnArKRC5dA00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgBVUi%2FdJMcaaZ4BRA%2FsjD6qEaYOtSnArKRC5dA00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;363&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가상 스크롤 API&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-window의 FixedSizeList는&lt;b&gt; height, width, itemSize, itemCount&lt;/b&gt; 네 가지 props를 받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782372236050&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { FixedSizeList } from 'react-window';

const items = Array.from({ length: 10000 }, (_, i) =&amp;gt; `Item ${i}`);

// 각 행을 그리는 컴포넌트
const Row = ({ index, style }) =&amp;gt; (
  &amp;lt;article style={style}&amp;gt;
    {items[index]}
  &amp;lt;/article&amp;gt;
);

const ListComponent = () =&amp;gt; (
  &amp;lt;FixedSizeList
    height={500}
    width={500}
    itemSize={120}
    itemCount={items.length}
  &amp;gt;
    {Row}
  &amp;lt;/FixedSizeList&amp;gt;
);

export default ListComponent;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;76:1-76:41;2066-2106&quot;&gt;&lt;b&gt;height&lt;/b&gt; : 보이는 영역(viewport)의 높이 (px)&lt;/li&gt;
&lt;li data-sourcepos=&quot;77:1-77:22;2107-2128&quot;&gt;&lt;b&gt;width&lt;/b&gt; : 너비 (px)&lt;/li&gt;
&lt;li data-sourcepos=&quot;78:1-78:33;2129-2161&quot;&gt;&lt;b&gt;itemSize&lt;/b&gt; : 각 행의 고정 높이 (px)&lt;/li&gt;
&lt;li data-sourcepos=&quot;79:1-79:28;2162-2189&quot;&gt;&lt;b&gt;itemCount&lt;/b&gt; : 전체 아이템 개수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;80:1-80:81;2190-2270&quot;&gt;추가로 &lt;b&gt;itemData&lt;/b&gt;로 item 전체 데이터를 넘겨주거나, &lt;b&gt;overscanCount&lt;/b&gt;로 앞뒤에 여분 렌더링을 둘 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 눈 여겨볼 부분은 Row 함수입니다. Row 함수는 안에 들어올 각 행의 컴포넌트를 그리는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Row에 넘어오는&lt;b&gt; style&lt;/b&gt;은 react-window가 각 행에 &lt;u&gt;&lt;b&gt;position: absolute와 top 값을 계산&lt;/b&gt;&lt;/u&gt;해서 인라인으로 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 렌더링된 결과를 보면 각 행에 position과 top이 계산되어 적용된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MbqKh/dJMcag6247K/ZJOhGmVQWQBR31CtbxXPLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MbqKh/dJMcag6247K/ZJOhGmVQWQBR31CtbxXPLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MbqKh/dJMcag6247K/ZJOhGmVQWQBR31CtbxXPLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMbqKh%2FdJMcag6247K%2FZJOhGmVQWQBR31CtbxXPLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;432&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;React-window FixedSizeList의 동작과정&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1단계 스크롤 이벤트 받기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FixedSizeList는 내부에 스크롤 가능한 컨테이너&lt;b&gt;(scroll-viewport)&lt;/b&gt;를 만들고 &lt;b&gt;onScroll&lt;/b&gt;을 붙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 스크롤이 발생하면 맨 위에서 얼마나 내려갔는지&lt;b&gt;(scrollTop)&lt;/b&gt;를 알려줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;onScroll에&lt;b&gt; scrollTop&lt;/b&gt;을 알 수 있는 함수를 넘겨줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2단계 창문의 가장 첫 아이템 인덱스 구하기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면이 위에서 1000px 내려갔고 아이템 높이가 72px로 일정하다면, 1000 &amp;divide; 72로 13번 행부터 보일 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782372542181&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;visibleStartIndex = Math.floor(scrollTop / itemSize)
                  = Math.floor(1000 / 72)
                  = Math.floor(13.88...)
                  = 13&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;3단계 창문의 마지막 아이템 인덱스 구하기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 보이는 아이템은 단순히 &lt;b&gt;첫 인덱스 + 보이는 개수&lt;/b&gt;로만 계산하면 부족할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤은 항상 아이템의 시작 지점에서 멈추는 게 아니라 &lt;b&gt;아이템의 중간에서 멈출 수 있기&lt;/b&gt; 때문입니다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;109:1-109:71;3092-3162&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 scrollTop이 어떤 아이템의 중간을 가리키고 있다면, 화면 아래쪽에 예상보다 한 개 더 걸칠 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1087&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljwj0/dJMcabLxu5z/skUqUw2A4Xxjjyq4fHwK8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljwj0/dJMcabLxu5z/skUqUw2A4Xxjjyq4fHwK8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljwj0/dJMcabLxu5z/skUqUw2A4Xxjjyq4fHwK8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fljwj0%2FdJMcabLxu5z%2FskUqUw2A4Xxjjyq4fHwK8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;525&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1087&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제 구현에서는 &lt;b&gt;뷰포트의 끝 위치(viewportHeight)&lt;/b&gt;를 기준으로 마지막 인덱스를 계산합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782372625181&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;visibleStopIndex = Math.ceil((scrollTop + viewportHeight) / itemSize)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1782372650714&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;scrollTop      = 970
viewportHeight = 560
itemSize       = 72

visibleStopIndex = Math.ceil((970 + 560) / 72)
                 = Math.ceil(21.25)
                 = 22&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면의 시작 위치는&amp;nbsp;&lt;b&gt;scrollTop / itemSize&lt;/b&gt;로 구하고,&lt;/li&gt;
&lt;li&gt;화면의 끝 위치는&amp;nbsp;&lt;b&gt;(scrollTop + viewportHeight) / itemSize&lt;/b&gt;로 구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;4단계 행의 위치(top) 계산&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보이는 인덱스 범위를 알았으니, 각 행에 입힐 top을 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 높이에서는 &lt;b&gt;인덱스에 아이템 높이를 곱하기만 하면&lt;/b&gt; 해당 행의 절대 위치가 바로 나옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782372743214&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;15번 행의 top = index &amp;times; itemSize = 15 &amp;times; 72 = 1080px
&amp;rarr; style = { position: &quot;absolute&quot;, top: 1080, height: 72, ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 style을 각 Row에 인라인으로 붙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 행 높이가 같다는 전제 덕분에 곱셈 한 번이면 위치가 나온다는 점을 이용한 것이 포인트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;고정 높이 가상 스크롤 직접 구현해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스크롤에 필요한 값은 itemCount, itemSize, height, width 네 가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 부모 컴포넌트 역할을 했던 &lt;b&gt;FixedSizeList와 Row&lt;/b&gt;를 직접 만들어봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782372876832&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 정적인 높이 리스트
export function List() {
  return (
    &amp;lt;section className=&quot;demo-panel&quot;&amp;gt;
      &amp;lt;FixedSizedList
        className=&quot;scroll-viewport&quot;
        height={VIEWPORT_HEIGHT}
        width=&quot;100%&quot;
        itemCount={items.length}
        itemSize={ITEM_SIZE}
        itemData={items}
        overscanCount={5}
      &amp;gt;
        {Row}
      &amp;lt;/FixedSizedList&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FixedSizedList는 최상위 컴포넌트이므로 scroll-viewport 스타일을 적용할 수 있도록 className과 item에 대한 값들을 props로 받도록하고, Array.from으로 실제 Row 행들을 만들 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Row는 각 행을 의미하는 부분이기 때문에, Index를 각 행에 반영이 필요하고, top의 값은 위에서 언급했던 것처럼 itemIndex는 오프셋을 더해서 구합니다.&amp;nbsp; handleScroll은 scorllTop을 구하기 위한 함수로 사용될 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visibleOffset을 구하기 위해 2단계 3단계에서 본 계산으로 보이는 인덱스 범위(visibleStartIndex, visibleStopIndex)를 구해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 범위를 벗어나는 경우를 막기 위해 max/min으로 가드를 두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;renderIndex는, 추후에 보이는 아이템 외에 앞뒤로 미리 렌더링할 아이템(overScan)을 추가한 실제 렌더 범위를 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;renderStartIndex부터 renderEndIndex까지의 행만 렌더링하기 때문에, 그릴 개수는 renderEndIndex - renderStartIndex이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 행의 실제 인덱스는 renderStartIndex + offset입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 행의 top은 4단계의 계산처럼 인덱스에 아이템 높이를 곱해서 계산할 수 있도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오프셋&lt;/b&gt;이란, 렌더 범위 안에서의 순번을 뜻합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;visibleOffset&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;보이는 아이템(visible)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가리킬 아이템 바로 이전까지의 인덱스(Offset)&lt;/b&gt;를 말합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1782373232131&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [scrollTop, setScrollTop] = useState(0);
  const visibleStartIndex = Math.max(0, Math.floor(scrollTop / itemSize));
  const visibleStopIndex = Math.min(
    itemCount - 1,
    Math.ceil((scrollTop + viewportHeight) / itemSize) - 1
  );
  const renderStartIndex = Math.max(0, visibleStartIndex - overscan);
  const renderEndIndex = Math.min(itemCount, visibleStopIndex + 1 + overscan);

  const handleScroll = (event: UIEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    setScrollTop(event.currentTarget.scrollTop);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;가상 스크롤에서 scrolltop은 보여지는 창문(범위)계산하는 기준이기 때문이므로 state로 관리할 수 있어야하지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스크롤 이벤트는 유저가 가장 많이 실행하는 이벤트입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;스크롤 이벤트가 고빈도로 발생하기때문에 매 이벤트마다 setState를 호출하게 되면, 렌더링이 과도해질수있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 부분의 문제 개선은 호출빈도를 줄일수있도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;쓰로틀링&lt;/b&gt;을 사용하는 방식을 이용해볼수있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 requestAnimationFrame을 이요하면 한 프레임안에 한번만 state를 갱신하도록 묶을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782373397480&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function FixedSizedList({
  className,
  height,
  width,
  itemCount,
  itemSize,
  itemData,
  overscanCount,
}: FixedSizedListProps) {
  return (
    &amp;lt;div
      className={className}
      style={{ height, width }}
      onScroll={handleScroll}
    &amp;gt;
      &amp;lt;div className=&quot;virtual-space&quot;&amp;gt;
        {Array.from(
          { length: renderEndIndex - renderStartIndex },
          (_, offset) =&amp;gt; {
            const itemIndex = renderStartIndex + offset;

            return (
              &amp;lt;RowComponent
                key={itemData[itemIndex].id}
                index={itemIndex}
                data={itemData}
                style={{
                  position: &quot;absolute&quot;,
                  top: itemIndex * itemSize,
                  left: 0,
                  right: 0,
                  height: itemSize,
                }}
              /&amp;gt;
            );
          }
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 style에 absolute와 top으로 하는걸까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 스크롤은 전체 1만 개 중 보이는 15개 정도만 DOM에 그립니다. 예를 들어 13번 아이템부터 그려야 한다면, 1~12번 아이템은 DOM에 존재하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;absolute는 상위 요소를 기준으로 dom에 절대좌표 (top)에 배치합니다. 덕분에 dom 순서와 화면상의 위치를 분리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dom에선 첫 자식이지만 화면에서는 900xp지점 배치가 viewport 밖의 좌표라도 상관없이 둘 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 absolute의 이 특성을 윈도잉에서 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마치 창문 처럼 렌더링 전체 캔버스에서 절대적인 top 위치에 아이템을 그립니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 아이템의 top의 위치가 100인데 scorlltop이 120으로 그 아이템은 화면 위쪽으로 20px 밀려나 viewport에서 벗어난 상태입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 아이템은 어차피 우리 눈에 보이지 않기 때문에 윈도잉 기법대로 굳이 그리지 않아도 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아이템 위치가 viewport 범위 안에 들어온 경우에만 재빠르게 그리면 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 영상은 마우스의 위치를 스크롤로 제어중이라고 가정했을 때, 실제 가상 스크롤에서 absolute top방식이 영향을 끼치는 것을 눈으로 확인할 수 있는 영상입니다. 만약 저 격자를 전체 viewoport라고 했을때, 스크롤이 된다고 느껴지게끔 하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2930&quot; data-origin-height=&quot;1590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvkVc/dJMcabLxBge/wIEqIvn46Ay65cs9lhS7Ik/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvkVc/dJMcabLxBge/wIEqIvn46Ay65cs9lhS7Ik/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvkVc/dJMcabLxBge/wIEqIvn46Ay65cs9lhS7Ik/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mvkVc/dJMcabLxBge/wIEqIvn46Ay65cs9lhS7Ik/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2930&quot; height=&quot;1590&quot; data-origin-width=&quot;2930&quot; data-origin-height=&quot;1590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;absolute 말고 다른 방법은 없을까요?&lt;/b&gt;&lt;br /&gt;top을 바꾸면 브라우저는 해당 요소의 위치를 레이아웃 단계에서 다시 계산합니다.&lt;br /&gt;absolute 요소라 다른 형제 요소까지 밀리는 전체 리플로우를 일으키지 않지만, 레이아웃단계에 관여하게 됩니다.&lt;br /&gt;레이아웃, 페인팅 단계를 최적화를 위해 transform : translateY(top)을 사용해볼수있습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;transform은 레이아웃과 페인트 단계를 건너뛰고 마지막 합성(composite) 단계에서만 처리됩니다.&lt;/b&gt; &lt;br /&gt;또한 transform이 걸린 요소는 별도 레이어로 승격되기 쉽고, 그 레이어의 합성은 CPU가 아닌 GPU가 담당할 수 있어 성능에 유리합니다.&lt;br /&gt;&lt;br /&gt;실제로 TanStack Virtual 공식 문서의 기본 예제도 보이는 아이템을 &lt;br /&gt;position: absolute로 두고 transform: translateY(virtualItem.start)로 위치를 잡습니다.&lt;br /&gt;&lt;br /&gt;핵심은,&amp;nbsp;&lt;b&gt;absolute + top이든 absolute + transform이든 근본 동작 원리는 같다는 점입니다.&lt;/b&gt;&lt;br /&gt;둘 다 요소를 좌표로 배치하는 것이고, transform은 그 중 더 저렴한 단계에서 좌표를 적용하는 최적화일 뿐입니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;고정 높이의 한계&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 높이의 계산은 간단한 수학 연산으로 가능할 정도로 단순했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782373856775&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;startIndex = Math.floor(scrollTop / itemSize)   // 스크롤 위치 &amp;rarr; index
top        = index &amp;times; itemSize                    // index &amp;rarr; 위치
전체 높이   = itemCount &amp;times; itemSize                // 스크롤바 높이&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능했던 이유는 모든 행의 itemSize가 동일하다는 전제가 있었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요즘 리스트는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아이템 &lt;/span&gt;높이를 고정으로 둘 수 없는 경우가 많습니다. 아이템 높이를 고정이 아니라면 어떤 문제가 발생할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 특정 행의 위치(top)를 즉시 알 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7번 행의 위치 (top)를 구하려면 기존방식으로는 7*indexSize(높이)를 계산하면 충분했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 높이가 다 다르면,&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; 0 ~ 6번 행의 실제 높이를 전부 알아야 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 스크롤 위치로 시작 인덱스를 즉시 알 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이템 사이즈가 일관되지 않으면 scrollTop/itemSize로 startindex를 구할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높이를 하나씩 누적해가면서 scrollTop을 넘는 지점을 찾아야하며, 선형탐색이 필요해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1만개의 아이템이라면 최악의 경우 1만번 순회하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1782374316043&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sum = 0
for (i = 0; i &amp;lt; itemCount; i++) {
  sum += h(i)
  if (sum &amp;gt; scrollTop) { 
  		startIndex = i; 
        break 
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 전체 높이(스크롤바)를 미리 알 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;itemCount * itemSize로 구했던 전체 높이도, itemSize가 일관되지 않게 되면, 모든 행의 높이를 알아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가장 큰 문제는&lt;/b&gt; &lt;b&gt;그리기 전엔 높이를 알 수 없다는 점&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높이를 안다면 누적합으로 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 동적 높이는&lt;b&gt; 각 행의 실제 높이를 DOM에 그려보기 전까진 알 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내용이 길어지면 줄바꿈이 되어 길어지고, 이미지가 늦게 로드되면 처음의 아이템 높이에서 변경됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이것이 고정 높이 문제입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고정 높이 가상 스크롤의 개선 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 개선 방법은 크게 두 가지 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 높이를 미리 알려주기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 itemSize를 index 높이들을 모두 제공해줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주어진 높이들을 통해 계산하여 동적 가상 스크롤 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 추정 후 측정 및 보정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 문제였던 부분은 그리기 전엔 높이를 알 수 없다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 그리고 난 후 실제 높이를 측정해볼 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상 스크롤은 윈도잉을 UI에 적용해, 보이는 것만 그리고 전체 높이는 빈 공간으로 유지하는 기법이다.&lt;/li&gt;
&lt;li&gt;고정 높이에서는 scrollTop &amp;divide; itemSize, index &amp;times; itemSize처럼 위치 계산이 곱셈&amp;middot;나눗셈 한 번으로 끝나 빠르다.&lt;/li&gt;
&lt;li&gt;viewport에 보이는 행은 position : absolute로 배치해 DOM 순서와 화면 위치를 분리하고, transform을 쓰면 한단계 더 최적화 할수있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;297:1-297:42;8513-8554&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/1450243&quot;&gt;https://d2.naver.com/helloworld/1450243&lt;/a&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;296:1-296:68;8445-8512&quot;&gt;&lt;a href=&quot;https://web.dev/articles/virtualize-long-lists-react-window?hl=ko&quot;&gt;https://web.dev/articles/virtualize-long-lists-react-window?hl=ko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/virtual/latest/docs/introduction&quot;&gt;https://tanstack.com/virtual/latest/docs/introduction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/43</guid>
      <comments>https://happy7yong.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 23 Jun 2026 22:20:44 +0900</pubDate>
    </item>
    <item>
      <title>2025 개발 회고</title>
      <link>https://happy7yong.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025 개발 회고 작성 시작을 1월부터 계속 써왔는데 바쁜 일정으로 인해 미루고 미루다가 지금에서야 올리게 되네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;br /&gt;벌써 2025년이 지나갔습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양띵 마인크래프트 콘텐츠 개발과 일러스트레이터에서 작별하고 취업을 위한 개발자가 되기 위해 웹 개발에 발을 들인지 2024년부터 시작한 여정은 벌써 2년이 지나가고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgpAjn/dJMcaiBV6Uf/lrKaLqkQDKsJqytVYIpcZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgpAjn/dJMcaiBV6Uf/lrKaLqkQDKsJqytVYIpcZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgpAjn/dJMcaiBV6Uf/lrKaLqkQDKsJqytVYIpcZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgpAjn%2FdJMcaiBV6Uf%2FlrKaLqkQDKsJqytVYIpcZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;360&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtVEm/dJMcagYsBJp/nkjkCkGsXHK1UxNsxf2ac1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtVEm/dJMcagYsBJp/nkjkCkGsXHK1UxNsxf2ac1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtVEm/dJMcagYsBJp/nkjkCkGsXHK1UxNsxf2ac1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrtVEm%2FdJMcagYsBJp%2FnkjkCkGsXHK1UxNsxf2ac1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;193&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취업을 하기 위해 하는 개발이기 때문에 흥미가 없었는데, 이 개발 속에서 내가 즐길 수 있는 개발 분야는 무엇일까 찾아다녔습니다. 그것은 인터렉티브 개발자였습니다. 하지만 인터렉티브는 국내에서도 수요가 적기 때문에 그만큼 되기 힘든 개발 분야였습니다. 우선적으로 프론트엔드의 역량을 키워서 개발역량을 키우고 인터렉티브 개발자로 이어가야겠다는 생각을 했었고, 그래서 올해도 여전히 부단히 노력했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년은 작년보다 더 다양한 직업군을 만나며 다양한 인연을 쌓아갈수있었던 해였던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 선배들을 만나, 개발이라는 분야에 더 깊게 공부해볼수있었고, 스타트업을 목표로 하시는 분을 만나서 개발자로서 어떤 태도로 임하고 무엇을 중요시 여겨야하는지 진대를 해볼수있어서 좋았습니다. 또한, 좋은 벤처기업을 만나서 또 좋은 대표님을 만나서 대표님께 웹 개발의 흐름을 코치해주셨습니다 ㅋㅋㅋ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메타버스 아카데미 4기 5기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해도 여전히 메타버스 아카데미를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 대학교 4학년이라서 졸업 프로젝트를 진행하는 김에 메타버스 아카데미를 함께 진행했습니다. 백2 프1 AI+기획1 으로 진행했고 누구보다 검증이 되어있는 사람들과 함께 진행하는 프로젝트였기 때문에 정말 좋은 시작이었습니다. 거의 한달을 기획회의를 계속 진행했습니다. 기획 회의를 정말 싫어했는데 이 한달을 통해 정말많은 기획이 나왔고 덕분에 기획할수있는 힘을 기르고, 지금까지 기획이 막히면 한번씩 볼수있는 아카이브를 만든것같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 활용한 프로젝트를 개발해보고 싶어서 열심히 고안하다가, 다양한 생성형 AI가 대화한다면? 으로 프로젝트를 진행하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 너무나도 인터렉티브 개발을 하고 싶어하던 저였고, 이번에야말로 AI를 3D로 만들면서 마침 레퍼런스 사이트로 artificial Client를 참고하게 되었는데 해당 사이트에서 ai가 3D 모델로 되어있었기 때문에, 블렌더를 거침없이 공부했던 것 같습니다. 작년에 에프터 이팩트를 사용해야되는 일이있어서 AE를 서슴없이 공부했는데 생각보다 노력과 도전은 결과를 보여준다는 인사이트를 크게 얻었던 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;1418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ub7mm/dJMcajaK3aO/TAkPnZUGskUyCs3Hc0p2Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ub7mm/dJMcajaK3aO/TAkPnZUGskUyCs3Hc0p2Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ub7mm/dJMcajaK3aO/TAkPnZUGskUyCs3Hc0p2Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fub7mm%2FdJMcajaK3aO%2FTAkPnZUGskUyCs3Hc0p2Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;505&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;1418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하며 메타버스를 3기수동안진행하고 있는데 한번도 프로젝트 수상을 못한게 너무 아쉬워서 꼭 프로젝트 수상을 하는 것을 목표로 달렸습니다. 그래서 눈앞에 어떤 방해물이 있어도 꿋꿋하게 달려가려고 더 노력했던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운좋게도 좋게 봐주셔서 우수교육생이라는 타이틀을 얻게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXpd7O/dJMcajaK282/jDdsnp24Yu3IEjcGVR6nBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXpd7O/dJMcajaK282/jDdsnp24Yu3IEjcGVR6nBK/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;2765&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.6542%; margin-right: 10px;&quot; data-widthpercent=&quot;57.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXpd7O/dJMcajaK282/jDdsnp24Yu3IEjcGVR6nBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXpd7O%2FdJMcajaK282%2FjDdsnp24Yu3IEjcGVR6nBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;2765&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp1XDT/dJMcaa50o92/CO8cNZ9VcryYqh1zKV9Sy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp1XDT/dJMcaa50o92/CO8cNZ9VcryYqh1zKV9Sy1/img.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;1034&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.183%;&quot; data-widthpercent=&quot;42.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp1XDT/dJMcaa50o92/CO8cNZ9VcryYqh1zKV9Sy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp1XDT%2FdJMcaa50o92%2FCO8cNZ9VcryYqh1zKV9Sy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타버스 아카데미는 IITP 이기 때문에 우수교육생으로 뽑힐 시엔 GETEX 에 참여할수있는 해외연수 기회가 주어집니다. 해외연수는 두바이 GETEX 10월 초였는데 ... 일주일동안 다녀오는 거였고... 카테부 기간과 겹쳐있었기 때문에 ... 많은 고민 끝네 해외연수는 포기하였습니다....ㅠㅠ 솔직히 많이 아쉬웠는데 두마리 토끼 모두 잡기에는 나라는 그릇이 너무 작았기 때문에 하나를 깊게 잡고 싶었습니다. 때문에 저는 카테부를 열심히 해야만합니다... 두바이 해외연수를 포기하게 만든 카테부 이기 때문에 ...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;뉴노멀 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 이어 진행한 뉴노멀 프로젝트. 작년에 뉴노멀 프로젝트의 결과가 너무 좋았기 때문에 노래를 부르며 선배들에게 하자고 한 뉴노멀 프로젝트라 그런지 이번 뉴노멀 프로젝트 팀들이 라인업이 정말 좋았습니다. 모두 하나같이 열심히 하는 사람들이었고 모두 개발에 나름 일가견있다고 생각한 선배들이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뉴노멀 프로젝트를 진행하면서 작년이 처음 제대로된 개발자의 커리어를 쌓아보자고 마음을 먹은 해이기도 하고, 그래서 더 미숙하기도 했지만 굉장히 자유분방한 기업을 만나서 너무 자유로운 코드를 작성했는지도 모릅니다. 어떻게 수상을 했는지... 우리팀 디자이너분들께서 다한것이었다..^^&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7월경 DX 해커톤을 준비하기 위해 대표님 사무실에서 해커톤 시작전주 거의 밤새 내리 달렸었습니다. 그때 대표님께 웹에 대한 서버 개념을 많이 배운것 같습니다. 하지만 그이는 프론트엔드를 싫어하셨기에 ... 대표님께서 너무 감사하게도 스카웃 해주셨지만 운이 좋게 카테부에 합격해서 카테부 이후에 연락하자고 하셨습니다. &lt;s&gt;하지만 카테부를 수료해도 여전히 백엔드에 대한 지식은 적을 것 같아서 ... 도움이 되어드리지 못할것같...&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무언가에 집중하여 밤을 샌 적이 많은데 특히 이때의 기억으로 개발을 열심히 하고 있습니다. 개발을 좋아하는 것보다는 무언가에 집중하고 있는 것을 좋아하기 때문에, 더 열심히 집중하고 싶어서 개발을 하고 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교 3,4년은 학교에서 진행하는 개발 관련 프로그램은 모두 참여한것같습니다 ... 사업을 좋아하시는 두분의 교수님이셔서 더 사업적인 프로그램을 많이 진행했고 사실 사업에 관심이 많지 않은 저였지만 2년동안 전혀 몰랐던 기획 개념들을 많이 알수있었던 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2025의 DX 해커톤&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;은 청자장이라는 카페건물을 일주일동안 대관해주셔서 그곳에서 해커톤 대회 전 주에 숙박할수있도록 도와주셨습니다. 일주일동안 갇혀서 개발만 하라는 뜻이었지만 다같이 개발하는 환경이었기 때문에 즐거웠습니다. 밤을 새고 아침 7시에 퇴근하고 씻고 잠깐 자서 오후 1시에 일어나면 청자장 가고 ... 거의 내리 밤을 지새우며 개발만했습니다.... 그럼에도 너무 즐거웠던 것같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3780&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E909o/dJMcahxivnn/uO79Iqj0TqqpFNdBtmkCi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E909o/dJMcahxivnn/uO79Iqj0TqqpFNdBtmkCi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E909o/dJMcahxivnn/uO79Iqj0TqqpFNdBtmkCi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE909o%2FdJMcahxivnn%2FuO79Iqj0TqqpFNdBtmkCi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;400&quot; data-origin-width=&quot;3780&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뉴노멀에서의 큰 수확인 수상도 있지만, 정말 하나도 빠짐없이 완벽한 디자이너와 인연을 만든 것이 제일 큰 수확이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 돌이켜보면 디자인이 일가견있는 대학에 왔기 때문에 누릴수있는 디자이너와의 한팀이었을 것인데, 당연한 인프라라고 생각햇던 과거가 아쉽습니다. 디자이너들과 함께 팀프로젝트를 진행할수있을때 디자인시스템을 제대로 해보고 기획 디자이너 개발자를 한팀으로 이루어 진행할수있는 유일한 시간이었는데 당연하다고 생각했는지 모릅니다... 돌아간다면 더 적극적으로 프론트엔드로서 개발에 임하고 싶습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HUSS 융합 캠프&lt;/b&gt;에서 2박 3일동안 곤지암 리조트에서 AI 경진대회를 하면서 비록 수상은 못했지만 좋은 경험이었습니다. 2박3일동안 밤새며 팀원들과 유대감과 결속력으로 끝까지 최선을 다하여서 졌지만 잘 싸운 우리 팀이었습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L2jS7/dJMcaakCBnt/A7hLqri8412LcHD4tPPmTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L2jS7/dJMcaakCBnt/A7hLqri8412LcHD4tPPmTk/img.png&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;1410&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; height=&quot;424&quot; style=&quot;width: 54.9241%; margin-right: 10px;&quot; data-widthpercent=&quot;55.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L2jS7/dJMcaakCBnt/A7hLqri8412LcHD4tPPmTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL2jS7%2FdJMcaakCBnt%2FA7hLqri8412LcHD4tPPmTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1662&quot; height=&quot;1410&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mXSK9/dJMcaiJGpQw/71XfcTwBL4pphgFxJcuOC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mXSK9/dJMcaiJGpQw/71XfcTwBL4pphgFxJcuOC1/img.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;1424&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.9131%;&quot; data-widthpercent=&quot;44.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mXSK9/dJMcaiJGpQw/71XfcTwBL4pphgFxJcuOC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmXSK9%2FdJMcaiJGpQw%2F71XfcTwBL4pphgFxJcuOC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;1424&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코딩테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트 준비를 2025년 1월부터 시작해서 0레벨도 못풀었던 나였는데 ... 0레벨 정복 머쓱이 스탬프를 얻었습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4학년 1학기 때 코딩테스트 준비하는 것도 웃긴데, 영어 듣기 평가를 항상 7개를 틀렸었는데, 3개월동안 영어듣기평가를 계속 공부하고 준비하고 결과로 수능날에 영어듣기에서 하나만 틀렸던 그 노력에 대한 결과가 좋은 기억으로 남아서 뭐든 열심히 꿋꿋하게 걸어가려고 노력할수있는 것같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 프로그래머스 코테 0레벨 구현만 계속 풀다가 점점 2레벨도 만져보고 알고리즘 공부가 너무 하기싫었지만 피할수없는 미래를 깨닫고 고득점 키트를 쉬운 문제부터 풀었었던것같습니다. 그러다 네이버 부스트 캠프 코틀린으로 참여하게 되었는데 이때 코테에서 떨어지고 난뒤 정말 알고리즘을 열심히 더 풀어야겠다는 생각을했습니다. 이후 다양한 부트캠프를 신청했지만 코딩테스트라는 문을 넘지 못하고 계속 불합해서 코테를 더 열심히 풀고 싶었습니다. 지금의 저는 3레벨을 풀고 있습니다. 2025의 내가 언제쯤 3레벨을 풀수있을까 하며 먼 미래를 바라보기만 했는데 결국 내가 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(02.20 기준)카테부를 다니며 알고리즘에 제대로 공부하게 되면서 실버도 간신히 풀더 내가 2달동안 열심히 등산하여 골드를 갔다는 소식입니다... 프로그래머스로 단련 시킨 그 1년이 아무것도 아니었음을 입증하게 되었습니다...ㅠㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;카카오테크 부트캠프&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러번의 부트캠프 신청 끝에 운좋게 카카오테크 부트캠프에 합격하여 다니고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;1316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxMRAn/dJMcagZqovF/Xm1fqgf5E3AJiKAC7Uwwi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxMRAn/dJMcagZqovF/Xm1fqgf5E3AJiKAC7Uwwi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxMRAn/dJMcagZqovF/Xm1fqgf5E3AJiKAC7Uwwi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxMRAn%2FdJMcagZqovF%2FXm1fqgf5E3AJiKAC7Uwwi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;589&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;1316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4년동안의 학부생이 아무것도 아닌것처럼 느껴지는 6개월이었습니다. 배운것이 정말 많고 카테부 아니었으면 어떻게 개발자로서 취업할생각을 했을지 정말 성장이라는 타이틀에 맞게 수확이 큰 6개월입니다 ... 제 20대의 반환점이라고 해도 무방할정도로 정말 좋은 경험, 좋은 사람들을 많이 만난 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Awards&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- [ 2025.06.17 ] 홍익대학교 메타버스 융합 SW 아카데미 4기 우수교육생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- [ 2025.06.17 ] 홍익대학교 메타버스 융합 SW 아카데미 4기 프로젝트 최우수상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- [ 2025.08.03 ]&lt;span&gt; SW융합클러스터 2.0 세종 DX 해커톤 우수상&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- [ 2025.08.13 ]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;2025 디지털 창업 아이디어 경진대회 우수상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- [ 2025.10.22 ]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;홍익대학교 표창장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- [ 2025.11.28 ]&lt;span&gt; 2025년 2학기&amp;nbsp;&lt;span data-token-index=&quot;1&quot;&gt;푸른등대 기부장학금&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1501&quot; data-origin-height=&quot;1597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXT71L/dJMcab3VD9v/KGWRgP3K79RQQV3ZvC7vr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXT71L/dJMcab3VD9v/KGWRgP3K79RQQV3ZvC7vr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXT71L/dJMcab3VD9v/KGWRgP3K79RQQV3ZvC7vr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXT71L%2FdJMcab3VD9v%2FKGWRgP3K79RQQV3ZvC7vr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;775&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1501&quot; data-origin-height=&quot;1597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2025를 돌아보며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Keep&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거침없이 하고 싶은것 모두 도전하고, 부딫치고, 추구하는 가치관 처럼 더 넓은 세상을 마주하고 지금의 경험에 안주하지 않는 삶을 바라본 내 자신에게 박수를 쳐주고 싶습니다. 기억은 기억을 잇는 것처럼, 아무것도 안했다고 생각했던 과거지만 돌아보면 지금의 나를 만들어준 것들입니다. 누군에겐 그 정도야? 할수있는 사소한 것들이지만 그런 티끌을 모아 태산을 만들었다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각하지말고 행해라. 라는 말을 그대로 시행한 멋진 2025를 지냈다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아쉬운점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자이너와 기획을 희망하는 팀원들과 함께 프로젝트를 진행해봤음에도 그 속에서 이득을 보지 못해서 너무 아쉽다. 디자인시스템이 기획과 디자이너가 있어야 비로소 빛을 발할수있는 요소라는 점을 전혀 몰랐다 ... 다른 학교 학생들도 한 팀에 디자이너가 있을 줄 알았는데 거의 없는 것이 대부분이었고 우리학교는 디자인 자체에 특화되어있는 학교였고 디자인컨버전스가 있는 학교 였기 때문에 UXUI 층이 다른학교에 비해 두터웠기 때문에 접하기 쉬웠던 것이었다 ... 그 장점을 조금더 특혜볼 걸 그랬다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 야매로 개발해왔다는 것을 너무나도 깨달았다 ...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Try&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년도는 내게 부족한 면접준비와 더 전공자 스러운 코테준비, 그리고 포트폴리오를 다듬고 싶습니다. 카테부 들어오기 전에 많은 목표가 있었습니다. 경험이 없는 최적화 진행과 프론트 뿐만 아니라 백엔드까지 접해보며 진정한 &quot;풀스택&quot;이 되고 싶었고, 다양한 기술 스택 접해보는 등 의 목표가 잇었는데 계획한 목표의 90%는 이룬 것 같습니다. 때문에 2026은 더 깊이 있는 프론트엔드가 되고 싶습니다. 취업을 목표로! &lt;b&gt;어제보다 나은 오늘이 되었기를 바라는 마음입니다!&lt;/b&gt;&lt;/p&gt;</description>
      <category>회고</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/28</guid>
      <comments>https://happy7yong.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 13 Apr 2026 23:55:06 +0900</pubDate>
    </item>
    <item>
      <title>JSX란 무엇이고, 화면은 언제 그려질까?</title>
      <link>https://happy7yong.tistory.com/7</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createBrowserRouter와 BrowserRouter의 차이를 파고들다가, 정작 막힌건 라우터가 아니라 더 근본적인 질문이었습니다. &amp;ldquo;JSX는 언제 실행되는것인지, 함수는 언제 실행되는 건지 React는 언제부터 일을 시작하는 것인지&amp;rdquo; 에 대해 물음을 답할 수 없어서 렌더링 전 후 차이가 있다고 이해했지만 정작 그 그리는 단계 자체가 머릿속에 그려지지 않았습니다. 화면이 어떻게 만들어지는지 그 원초적인 과정을 모르니 아무리 설명을 읽어도 겉돌기만 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX가 왜 등장했는지, 정확히 무엇인지 그 객체가 언제 실행되는지, 어느 시점에 화면이 그려지는지 탐구해보려한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JSX란 무엇일까요?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSX는 무엇일까요?&lt;/b&gt; JavaScript 파일 안에서 HTML과 유사한 마크업을 작성할 수 있도록 해주는 JavaScript 구문 확장입니다. 화면을 그리는 함수 호출을 사람이 읽기 편한 HTML 형태로 적게 해주는 문법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한줄만으로는 이해하기 어렵습니다. JSX를 이해하기 위해, JSX 없던 시절로 돌아가보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처음엔 파일을 나눠 작성했다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발을 처음 할 때, 우리는 HTML, CSS, JS를 모두 파일로 분리해서 각각 작성했습니다. &lt;b&gt;관심사 분리&lt;/b&gt; 방식으로, HTML에는 구조와 내용을, CSS는 스타일을, JS는 동작을 각각 다른 파일에 담는 구조였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML에는 어떻게 화면에 그려질지 마크업으로 작성하고,&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;  &amp;lt;h1&amp;gt;카운터&amp;lt;/h1&amp;gt;
  &amp;lt;p id=&quot;count&quot;&amp;gt;0&amp;lt;/p&amp;gt;
  &amp;lt;button id=&quot;plus&quot;&amp;gt;+1&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상은 어떻게 지정하고 싶은지 CSS로 작성하고,&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;body {
  font-family: sans-serif;
  text-align: center;
}

#count {
  font-size: 48px;
  color: tomato;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 눌렀을 때 어떻게 동작하고 싶은지, 로직은 JavaScript 파일이 담당하게 했습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const countEl = document.getElementById(&quot;count&quot;);
const button = document.getElementById(&quot;plus&quot;);

let count = 0;

button.addEventListener(&quot;click&quot;, () =&amp;gt; {
  count += 1;
  countEl.textContent = count;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세가지의 파일이 각각 어떻게 연결될까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html은 화면이 보여지는 주인공이기 때문에, 브라우저는 항상 html을 먼저 읽고, 그 안에 있는 link로 css를 가져와 그림을 그리고, script로 js를 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 id를 통해 html와 css/js를 연결합니다. id=&amp;rdquo;count&amp;rdquo;, id=&amp;rdquo;plus&amp;rdquo;를 붙여두면 css는 #coun로 그리고, JS는 getElementById로 요소를 확인할수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 화면은 상태에 따라 계속 바뀝니다. 로그인 했을 때 &amp;ldquo;환영합니다&amp;rdquo;, 안했으면 &amp;ldquo;로그인하세요&amp;rdquo;, 좋아요 수가 5에서 6으로 바뀝니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 상태에 따라 내용이 바뀌기 때문에 html에 고정할 수 없는 것들입니다. 그래서 상황에 따라 무엇을 보여줄지(콘텐츠)에 대한 역할이 JS 로직으로 넘어가게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로, 상황에 따른 화면이 다르다는 것을 확인하기 위해, JS로 로그인했을때 환영합니다 안하면 로그인하세요를 작성해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function Sidebar() {
  const aside = document.createElement(&quot;aside&quot;);

  if (isLoggedIn()) {
    const p = document.createElement(&quot;p&quot;);
    p.textContent = &quot;환영합니다&quot;;
    aside.appendChild(p);
  } else {
    const p = document.createElement(&quot;p&quot;);
    p.textContent = &quot;로그인하세요&quot;;

    const button = document.createElement(&quot;button&quot;);
    button.textContent = &quot;로그인&quot;;

    aside.appendChild(p);
    aside.appendChild(button);
  }

  return aside;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 화면에 &amp;lt;p&amp;gt;환영합니다&amp;lt;/p&amp;gt; 하나를 띄우기 위해 createElement로 요소를 만들고, textContent로 내용을 채우고, appendChild로 붙이는 세단계를 거쳐야합니다. 최종 결과물이 어떤 모양인지 코드만 봐서는 한눈에 들어오지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소가 하나뿐인데도 이렇게 복잡한데, 요소가 늘어날수록 이 조립과정이 점점 기하급수적으로 복잡해질 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 화면은 한번그리고 끝나는게 아니라, 상태에 따라 계속 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 버튼의 좋아요 갯수를 증가시키는 코드를 작성해보겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;처음 디폴트 값을 선언하는 코드&lt;/b&gt;코드의 디폴트값을 선언해줍니다.&lt;/li&gt;
&lt;li&gt;const button = document.createElement(&quot;button&quot;); &lt;b&gt;button.textContent = &quot;좋아요 0개&quot;&lt;/b&gt;; //처음 선언 document.body.appendChild(button);&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태가 바뀌도록 하는 코드&lt;/b&gt;클릭(click)이 일어나면 1번에서 만들어둔 그 버튼(buttion)를 찾아서 글자를 바꿉니다. (button.textContent)&lt;/li&gt;
&lt;li&gt;button.addEventListener(&quot;click&quot;, () =&amp;gt; { count += 1; &lt;b&gt;button.textContent = `좋아요 ${count}개`&lt;/b&gt;; // &amp;larr; 이미 만든 버튼을 찾아서 다시 고침 });&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 단계에서 문제점은 버튼 모양을 바꾸고 싶을 때 두곳을 모두 고쳐야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;button.textContent&lt;/b&gt;라는 똑같은 화면 요소를 두군데에서 건드리고 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요소가 많아지면&lt;/b&gt; 개발자는 이 요소를 어디서 만들었고 어디서 고치는지 추적하는게 힘들어집니다. 즉, 유지보수가 힘들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식을 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;명령형&lt;/b&gt;&lt;/span&gt;이라고 합니다. 화면을 어떻게 바꿀지, 그 절차를 개발자가 하나하나 직접 지시하는 방식입니다. 버튼을 찾고, 글자를 바꾸고 하는 모든 단계를 개발자가(사람)가 책임져야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;절차를 사람이 직접 들고있다보니, 같은 화면 요소를 다루는 코드가 흩어질수밖에 없고 유지보수가 힘들어집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떻게 개선할 수 있을까요?&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;두군데로 흩어졌던 요소를 한군데로 모은다. : &lt;/b&gt;버튼이 어떻게 생겼는지를 나눠 적지 않고, 한곳에서만 선언합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태에 따라 달라지는 모양이어야 한다. : &lt;/b&gt;좋아요 0개 처럼 값을 박아 넣는대신, 좋아요 {count}개 처럼 상태를 끼워 넣어 화면이 상태로부터 계산하도록 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태가 바뀔때 화면을 고치는 일은 사람이 아니라 시스템에 맡긴다. : &lt;/b&gt;개발자는 어떤 상황에서 상태가 바뀐다만 지시하고, 화면 어디에 어떻게 고칠지는 시스템에서 지시할 수 있도록합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;선언형&lt;/b&gt;&lt;/span&gt;으로 구현하고자합니다. 개발자는 &lt;b&gt;&amp;ldquo;이 상태일때 화면은 이 모양&amp;rdquo;이라고 결과만 한 곳에서 선언하고&lt;/b&gt;, 나머지는 React가 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 &lt;b&gt;상태를 선언&lt;/b&gt;하고, 프레임워크가 그 &lt;b&gt;어떻게할지 명령&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위의 3가지를 반영해서 JS로 어떻게 구현될까요?&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;let count = 0;

//1 : 기본 화면
const button = document.createElement(&quot;button&quot;);
render();
document.body.appendChild(button);

//2 : 버튼을 클릭했을때 
button.addEventListener(&quot;click&quot;, () =&amp;gt; {
  setCount(count + 1);
});

//함수
function render() {
  **button.textContent** = `좋아요 ${count}개`;
}

function setCount(newCount) {
  count = newCount;
  render();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버튼을 &lt;b&gt;만들고(createElement), 추가하고(appendChild), 그립니다(render)&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;이후, 버튼을 **클릭(click)**할때마다 &lt;b&gt;상태를 변환시키고, 그림을 다시 그립니다&lt;/b&gt;.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;setCount는 count 상태를 변경 후, 자동으로 화면을 다시 갱신합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 바꾸면 화면이 따라 갱신되는 구조가 만들어졌습니다. 하지만 아직 아쉬운 점이 남아있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 좋아요 버튼이라는 하나의 대상 정보가 &lt;b&gt;네 군데&lt;/b&gt;에 흩어져있습니다&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const button = document.createElement(&quot;button&quot;);  // 1. 종류: button 태그
button.textContent = `좋아요 ${count}개`;           // 2. 내용: 좋아요 N개
button.addEventListener(&quot;click&quot;, () =&amp;gt; {           // 3. 동작: 누르면 setCount
  setCount(count + 1);
});
document.body.appendChild(button);                 // 4. 위치: body에 붙임
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종류, 내용, 동작, 위치가 따로따로입니다. &lt;b&gt;이 흩어진 정보를 한군데에 모을순 없을까?&lt;/b&gt; 라는 생각을 하게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const 버튼정보 = {
  tag: &quot;button&quot;,// 1. 종류
  text: `좋아요 ${count}개`,//2. 내용
  onClick: () =&amp;gt; setCount(count + 1),//3. 동작
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흩어진 정보를 하나의 주머니(객체)에 담았습니다. 그런데 이 객체는 그냥 데이터일 뿐이라, 만든다고 해서 화면에 아무 일도 일어나지 않습니다. 이 객체는 &quot;버튼은 이렇게 생겨야 한다&quot;는 &lt;b&gt;설명서&lt;/b&gt;를 적어둔 것에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, &lt;b&gt;이 데이터를 보여주는 건 누가할까요?&lt;/b&gt; 위에서 보았던 render() 함수에 역할을 줍니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function render(node) {
  const el = document.createElement(node.tag);   // 설명서의 종류 &amp;gt; 요소 생성
  el.textContent = node.text;                    // 설명서의 내용
  if (node.onClick) el.addEventListener(&quot;click&quot;, node.onClick); // 동작 연결
  return el;
}

document.body.appendChild(render(버튼정보));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 개발자는 무엇을 보여줄지 설명만 작성하고, createElement,appendChild같은 어떻게 만드는지 절차는 render에게 맡깁니다. 앞에서 언급했던 &lt;b&gt;시스템에 맡긴다(3)&lt;/b&gt;를 코드로 실현하게 된것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명서인 객체를 반환하도록 한다면 아래와 같은 객체 반환 함수를 작성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;function LikeButton() {
  return {
    tag: &quot;button&quot;,
    text: `좋아요 ${count}개`,
    onClick: () =&amp;gt; setCount(count + 1),
  };
} //{tag, text, onClick }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이 반환 생각보다 힘듭니다. tag, text,onClick 하나하나 다 작성해줘야되는 노고가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명서를 객체로 직접 적는 방식에는 &lt;b&gt;두가지 불편점&lt;/b&gt;이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;형식이 제각각이 되기 쉽다.&lt;/li&gt;
&lt;li&gt;어떤 요소는 tag로, 어떤 요소는 type으로, 내용은 text또는 children으로 사람마다 다르게 적으면 render함수가 그 설명서를 읽을 수 없습니다.&lt;/li&gt;
&lt;li&gt;요소 안에 요소를 넣는 중첩의 경우입니다.이걸 직접 위에서 작성한대로 객체로 적으면 중첩이 금세 지저분해집니다.&lt;/li&gt;
&lt;li&gt;&amp;lt;aside&amp;gt; &amp;lt;p&amp;gt;좋아요 5개&amp;lt;/p&amp;gt; &amp;lt;button&amp;gt;+1&amp;lt;/button&amp;gt; &amp;lt;/aside&amp;gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;const tree = {
  tag: &quot;aside&quot;,
  children: [
    { tag: &quot;p&quot;, text: &quot;좋아요 5개&quot; },
    { tag: &quot;button&quot;, text: &quot;+1&quot;, onClick: handleClick },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명서 객체를 대신 만들어주는 함수를 하나 둡니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function h(type, props, ...children) {
  return { type, props: props || {}, children: children.flat() };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;h()를 거치면 항상 {type,props,children}이라는 같은 형식의 객체를 얻을 수 있고, 자식을 인자로 넘길수있어 중첩을 함수 호출의 중첩으로 표현할수있습니다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 버튼 하나
h(&quot;button&quot;, { onClick: () =&amp;gt; setCount(count + 1) }, `좋아요 ${count}개`);

// 중첩된 화면도 자연스럽게
h(&quot;aside&quot;, null,
  h(&quot;p&quot;, null, `좋아요 ${count}개`),
  h(&quot;button&quot;, { onClick: () =&amp;gt; setCount(count + 1) }, &quot;+1&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 관심사가 흩어져있던 명령형 코드를 하나의 데이터로 모으고, 그리는 역할을 시스템에 맡기도록 했더니 화면을 h() 호출로 선언하는 형태에 도달했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 이 h() 호출 형태가, JSX를 실제로 작성했을때 내부에서 만들어지는 모습과 똑같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt;지금까지 본 변환 방식 React.createElement()는 classic 방식입니다.&lt;/b&gt;&lt;br /&gt;JSX는 React.createElement로 변환하면 변환 코드는 React를 참조할수있어야했습니다. 그래서 classic 방식에서는 import React from &quot;react&amp;rdquo;가 파일마다 맨 위에 반드시 적어야했습니다.&lt;br /&gt;하지만 React 17부터 automatic runtime이 기본값이 되면서 JSX는 react.createElement가 아니라 react/jsx-runtime의 jsx() 함수 호출로 변환되고, 변환 도구가 그 함수를 알아서 import 를 해줄수있게 되었습니다.&lt;br /&gt;
&lt;pre id=&quot;code_1782026760620&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// automatic 방식의 변환 결과
import { jsx as _jsx } from &quot;react/jsx-runtime&quot;;
_jsx(&quot;h1&quot;, { className: &quot;hi&quot;, children: &quot;안녕&quot; });​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;그래서 요즘 React 코드에는 import React가 보이지 않습니다. 함수 이름과 children의 위치가 달라졌을뿐, 설계 객체를 만든다는 동작원리는 똑같습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JSX의 실체&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS가 위에서 아래로 한줄씩 실행한다고 하면, JSX 코드를 만나면 즉시 렌더링이 되는 것이 아닌가 라고 생각해볼수있습니다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;&amp;lt;Route path=&quot;/&quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라우트 JSX는 빌드 타임에 Babel(또는 SWC)같은 도구가 아래와 같이 변환합니다&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;React.createElement(Route, { path: &quot;/&quot;, element: ... })
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.createElement는 document.createElement처럼 생겨서 실제 dom에 반영하나? 하는 생각이 들겠지만, 아쉽게도 실제 dom과는 전혀 무관합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React.createElement는 객체를 만드는 코드이고, 화면 반영은 나중에 React가 처리하는 설계도를 만드는 역할을 합니다&lt;/li&gt;
&lt;li&gt;document.createElement는 즉시 화면에 반영이 되며 실제 DOM노드가 만들어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바벨이 변환한 React.createElement가 반환하는 것은 아래와 같은 React가 처리할수있도록하는 &lt;b&gt;React Element 객체&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;{ type: Route, props: { path: &quot;/&quot;, element: ... } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 객체로 만드는 시점에서는 객체가 실행되거나 안에 있는 요소가 실행되거나 하는 일은 일어나지 않는다. 단지 JSX를 객체로 반환하는 일만 한다. &lt;b&gt;즉, 이후에 react가 실제로 DOM을 그릴 수 있도록 설계도를 만듭니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 위의 객체가 바로 Virtual DOM(VDOM)의 한 조각입니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;console.log(&amp;lt;h1 className=&quot;hi&quot;&amp;gt;안녕&amp;lt;/h1&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 빌드(번들링)과정에서 JSX는 아래와 같이 전부 React.createElement() 호출로 바뀌고, 브라우저에 전달되는 최종 JS 파일에는 JSX 문법이 없습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;console.log(React.createElement(&quot;h1&quot;, { className: &quot;hi&quot; }, &quot;안녕&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 받은 브라우저는 JSX의 존재는 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 표준 JavaScript 문법이 아니기 때문에, 브라우저의 JS 엔진(V8)은 코드로 이해하지 못하기 때문입니다. 그래서 반드시 빌드 타임에 누군가가 미리 일반 JS로 바꾸어야 하는데, 역할을 Babel/SWC/번들러가 하는 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 질문으로 돌아가보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 화면에 언제 그려질까? 리액트는 언제부터 일을 시작할까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 답할 수 있습니다. JSX를 만나는 순간에는 화면이 그려지지 않습니다. 그 줄이 실행되면 단지 { type, props, children }이라는 설계도 객체 하나가 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React가 일을 시작하는 것은, render가 이 객체를 받아 VDOM을 구성하고 실제 DOM에 반영하는 단계입니다. &lt;b&gt;JSX는 그리는 행위가 아니라 그릴 내용을 적은 설계도를 만드는 작업입니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명령형의 한계&lt;/b&gt; &amp;rarr; 화면을 만드는 코드와 동작하는 코드가 흩어져있어, 사람이 추적하해야만 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선언형으로의 전환&lt;/b&gt; &amp;rarr; 이 상태면 이 모양이라고 결과만 선언하고, 그리는 절차는 시스템에 맡깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체 &lt;/b&gt;&amp;rarr; 그 선언을 한곳에모으니 화면이 데이터가 되었고 데이터라서 비교하고 재사용할 수 있게 되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JSX&lt;/b&gt; &amp;rarr; React.createElement 함수 호출을 HTML처럼 개발자가 읽기 편하게 작성하도록 하는 문법이 JSX입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 선언형의 주체는 JSX가 아니라 React 입니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSX는 읽기 좋게 적는 문법일 뿐, &quot;이 상태면 이 모양이다.&quot; 라는 결과만 받고 실제 DOM을 알아서 갱신해주는 것은 React 입니다. JSX없이 React.createElement를 직접 써도 선언형은 똑같이 성립합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 변경 부분만 갱신한다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VDOM 비교는 JSX 덕분이 아닙니다. 그것이 가능한 이유는 React가 화면을 객체로 표현하기 때문에 가능한 일입니다. JSX를 쓰든, 안 쓰든 만들어지는 객체는 같고, 비교도 똑같이 일어납니다. JSX는 객체를 편하게 적게 해주는 문법이고, 객체를 비교해 화면을 갱신하는 것은 React의 몫입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그럼 JSX는 하는 일이 뭘까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 우리가 명령형의 불편을 풀어가다 자연스럽게 도착한 화면을 선언적으로 적는 방법이고, React는 그 선언을 받아 화면을 그려주는 시스템입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추가&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JS와 React의 경계&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;[JS 실행]브라우저 JS 엔진&lt;/b&gt;이 코드를 위에서 아래로 실행한다.&lt;/li&gt;
&lt;li&gt;React.createElement() 호출문을 만나면 실행되고, {type, prorps} 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;createRoot().render() 호출&lt;/li&gt;
&lt;li&gt;이 시점에 React에게 일을 넘긴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[React]&lt;/b&gt;React Fiber가 그 객체를 받아서 렌더링을 시도한다. VDOM을 구성하고, 렌더링 단계, 커밋단계를 지나 실제 DOM에 반영된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React는 왜 JSX를 쓸까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;개발자의 편의성을 위해 등장했다&lt;/b&gt; &lt;/span&gt;JSX는 결국 &lt;b&gt;React.createElement() 호출을 사람이 읽기 편하게 쓰는 문법일 뿐입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX 없이도, React.createElement를 직접 쓸 수 있지만, 최악의 경우 중첩이 깊어지면 읽기 힘듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 구분할 점이 있습니다. 바뀐부분만 실제 DOM에 반영되는 VDOM 비교는 JSX 덕분이 아닙니다. 그것은 React가 화면을 객체로 표현하기 때문에 가능한 일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 그 객체를 편하게 적게 해주는 문법이고, 객체를 비교해서 화면을 갱신하는 것은 React의 몫입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;React와 JSX의 공존 관계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react가 최종적으로 필요한것은 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 먼저 함수 호출로 변환되고, 그 함수가 실행되어 객체를 반환합니다. 그렇다면 JSX를 함수 호출로 바꾸라고 지시한 것은 누구일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX를 변환하는 일꾼은 &lt;b&gt;바벨/SWC/&amp;hellip; &lt;/b&gt;이고, 무엇으로 바꿔라고 지시하는 건 &lt;b&gt;변환 도구의 설정&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 @babel/preset-react는 &amp;ldquo;JSX를 만나면 React.createElement() 형태로 바꿔라&amp;rdquo;라는 규칙을 갖고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 변환 대상은 react 전용 문법이 아니기 때문에 그걸 무슨 함수로 바꿀지는 설정에서 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 앞서 본것처럼 우리가 만든 h를 지정하면 JSX가 h로 변환해주고 automatic 방식에서는 jsx()를 변환되는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;// 1. 빌드 타임: JSX &amp;rarr; 함수 호출로 변환 (이게 바벨의 일)
&amp;lt;h1&amp;gt;안녕&amp;lt;/h1&amp;gt;  &amp;rarr;  React.createElement(&quot;h1&quot;, null, &quot;안녕&quot;)

// 2. 런타임: 그 함수가 실행되어 객체를 반환 (이게 React의 일)
React.createElement(&quot;h1&quot;, null, &quot;안녕&quot;)  &amp;rarr;  { type: &quot;h1&quot;, props: {...} }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 바벨 뿐만 아니라 vite 8.0.16 에서는 vitejs/plugin-react 6.0.2 플러그인에서는 babel 없이도 Oxc으로 jsx 변환을 담당하고 있습니다.&lt;/p&gt;</description>
      <category>JavaScript</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/7</guid>
      <comments>https://happy7yong.tistory.com/7#entry7comment</comments>
      <pubDate>Sat, 29 Nov 2025 10:28:47 +0900</pubDate>
    </item>
    <item>
      <title>렉시컬 환경과 클로저</title>
      <link>https://happy7yong.tistory.com/6</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인트로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트를 공부하다 보면 클로저(Closure)라는 용어를 자주 접하게 됩니다.&lt;br /&gt;또한 자바스크립트의 중요한 특징 중 하나로 &amp;ldquo;함수는 자신의 환경을 기억한다&amp;rdquo;는 설명도 많이 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;290&quot; data-start=&quot;200&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 이 두 개념은 어떻게 연결되어 있을까요?&lt;br /&gt;이 글에서는 &lt;b&gt;렉시컬 환경과 클로저의 관계&lt;/b&gt;를 중심으로, 클로저가 어떤 원리로 동작하는지 알아보고자 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;렉시컬 환경 (Lexical Env)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드가 작성된 위치 기준으로 만들어진 변수 저장 환경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;선언된 위치&lt;/b&gt; 기준으로 접근 가능한 모든 변수 공간을 렉시컬 환경이라고 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렉시컬 환경은 실행 컨텍스트를 구성하는 컴포넌트이며, 스코프와 식별자를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;실행 컨텍스트 내부 구조&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;실행 컨텍스트 (Execution Context)
├── LexicalEnvironment
│   └── Environment Record
│       ├── 식별자 바인딩 (let, const, class, 함수 매개변수 등)
│       └── [[ThisValue]]  &amp;larr; FunctionER/GlobalER의 경우
│   └── [[OuterEnv]]  &amp;larr; 바깥 렉시컬 환경 참조
├── VariableEnvironment
│   └── Environment Record (var, 함수 선언)
│   └── [[OuterEnv]]
└── (Realm, ScriptOrModule 등 기타 필드)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;⭐️ 렉시컬 환경&lt;/b&gt; 이라는 뜻을 이해하기가 어려워요.&lt;br /&gt;&lt;b&gt;환경 :&lt;/b&gt; 주위의 사물 또는 사정, 거주하는 주위의 외계, 생활체를 둘러싸고 그것과 일정한 접촉을 유지하고 있는 외계 - 국어사전&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&amp;ldquo;나&amp;rdquo;로 비유 :&lt;/b&gt; &lt;/span&gt;&quot;나는 어떤 환경에서 자랐나&quot; = 내 주변의 조건과 맥락 전체&lt;br /&gt;내가 나로 있기 위해서는, 외부 환경만 있어야 되는 것이 아니라 내가 어떤 것을 좋아하는지 어떤 요소가 있는지도 함께 봐야합니다. 그런 의미에서 나의 렉시컬 환경은 나에 대한 조건과 맥락도 함께 포함된다고 할 수 있습니다.&lt;br /&gt;&lt;br /&gt;나를 코드로 대입해보자면 : 함수(코드)가 &lt;b&gt;실행되기 위해&lt;/b&gt; &lt;b&gt;필요한 모든 정보와 조건의 집합&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;이 코드가 동작하려면 뭐가 필요한가?&lt;/b&gt;&lt;/span&gt; &amp;rarr; 어떤 변수가 있는지, 그 변수의 값은 뭔지, 바깥에 뭐가 있는지&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;렉시컬 환경&lt;/b&gt; = 자기 자신의 환경 레코드(Environment Record) + Outer Environment Reference 체인&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렉시컬 환경은 &lt;span data-token-index=&quot;1&quot;&gt;객체 형태로 스코프를 구분&lt;/span&gt;하며, 식별자를 키로 하며 식별자에 바인딩된 값을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &amp;lt;&lt;b&gt;Yong의 렉시컬 환경&amp;gt;&lt;/b&gt;을 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래, Yong은 렉시컬 환경을 객체 형태로 스코프를 구분하고 있습니다. Yong은 phone과 mac을 갖고 있고, &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;University에 다니고&lt;/span&gt;&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776437585997&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Yong {
	phone : ...
	mac : ...
	외부 환경 참조(부모) -&amp;gt; University
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 phone, mac은 Yong의 렉시컬 환경의 환경 레코드(Env Record)라고 할 수 있으며, 외부 렉시컬 환경 참조(Outer Env Ref)는 &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;University &lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여기서 Yong의 전체 렉시컬 환경을 나타내보자면, &lt;span data-token-index=&quot;0&quot;&gt;스코프 체인&lt;/span&gt;까지 더해서 ran의 전체 렉시컬 환경을 표현해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776437835180&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Yong {
  phone: 'iPhone'
  mac: 'MacBook'
  외부 환경 참조(부모) &amp;rarr; University {
    강의실: 'A동 301호'
    wifi: 'University_5G'
    외부 환경 참조(부모) &amp;rarr; global {
      도시: '서울'
      나라: '한국'
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크드 리스트처럼 렉시컬 환경의 Outer Ref가 모두 연결되어있다는 것을 확인할 수 있고, 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스코프 체인&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;여기서 알 수 있는 점은, 모든 실행 컨텍스트의 렉시컬 환경은&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;(1) 환경 레코드(Environment Record)&lt;/b&gt;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;(2) 외부 환경 참조(Outer Reference, 해당 실행 컨텍스트가 선언된 위치를 기준으로 한 환경)&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;두 가지 요소로 구성된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 컨텍스트의 기본 구조 : &lt;b&gt;실행컨텍스트의 렉시컬 환경의 형태&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776664242934&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;실행 컨텍스트 {
	a: ... //환경 레코드 : Environment Record
	외부 환경 참조(부모) :  //Outer Lexical Environment Reference
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선언된 위치에 따라 다른 렉시컬 환경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 선언된 곳과 호출되는 곳이 다를 수 있습니다. 렉시컬 환경이란 &lt;b&gt;선언된 위치를 기준으로 접근 가능한 모든 변수 공간&lt;/b&gt;을 말합니다. 그렇다면 &quot;선언된 위치를 기준으로 한다&quot;는 건 정확히 어떤 의미일까요? 호출된 위치를 기준으로 할 때와는 무엇이 다를까요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;foo()의 호출위치는 같지만, foo()의 선언된 위치가 다른 케이스 2가지 코드&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에는 2가지 케이스가 있습니다. foo()가 bar 안에서 호출된다는 점은 같지만, &lt;b&gt;foo()가 선언된 위치가 서로 다릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. foo가 전역에 선언된 경우&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo() 함수는 전역 스코프에 선언되어 있지만, bar 안에서 호출됩니다. 그런데 출력값은 100이 아니라 1입니다. foo()가 &lt;b&gt;선언된 위치&lt;/b&gt;가 전역이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체로 표현한 foo의 렉시컬 환경을 살펴보면, &lt;b&gt;외부 렉시컬 환경 참조(Outer Environment Reference)가 전역 렉시컬 환경을 가리키고 있습니다.&lt;/b&gt; 따라서 foo 내부의 x는 상위 스코프인 전역 환경에서 값을 찾게 되고, 1이 출력됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776664595400&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 1;

function foo() {
  console.log(x);
}

function bar() {
  const x = 100;
  foo(); // foo는 bar 안에서 호출됐지만
}

bar(); // &amp;rarr; 1 출력&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1776664627433&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//foo의 렉시컬 환경
foo [[Environment]] {
  (없음)             &amp;larr; foo 환경 레코드 (지역변수 없음)
  Outer &amp;rarr; 전역 LE {  &amp;larr; foo가 선언된 위치가 전역이라서
    x: 1
    foo: [함수 객체]
    bar: [함수 객체]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. foo가 bar 안에 선언된 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 foo가 bar 안에 선언되어 있고, bar 안에서 호출됩니다. 이 코드의 출력값은 100입니다. foo()가 &lt;b&gt;선언된 위치&lt;/b&gt;가 bar 내부이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체로 표현한 foo의 렉시컬 환경을 살펴보면, &lt;b&gt;외부 렉시컬 환경 참조가 bar의 렉시컬 환경을 가리키고 있습니다.&lt;/b&gt; 따라서 foo 내부의 x는 상위 스코프인 bar 환경에서 값을 찾게 되고, bar의 환경 레코드에 저장된 100이 출력됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776664833766&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 1;

function bar() {
  const x = 100;
  
  function foo() {
    console.log(x);
  }
  
  foo();
}

bar(); // &amp;rarr; 100&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1776664848066&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo [[Environment]] {
  (없음)              &amp;larr; foo 환경 레코드 (지역변수 없음)
  Outer &amp;rarr; bar LE {   &amp;larr; foo가 선언된 위치가 bar 안이라서!
    x: 100
    foo: [함수 객체]
    Outer &amp;rarr; 전역 LE {
      x: 1
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 가지 케이스를 통해 &lt;b&gt;코드가 선언된 위치에 따라 상위 스코프가 달라진다&lt;/b&gt;는 것을 확인했습니다. 함수가 선언된 위치의 렉시컬 환경을 계속 기억한다는 이 특성은, &lt;b&gt;클로저&lt;/b&gt;의 핵심 원리이기도 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클로저&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 자바스크립트만의 고유 개념이 아닙니다. &lt;b&gt;함수를 일급 객체로 취급하고 렉시컬 스코프를 따르는 언어&lt;/b&gt;라면 어디서든 나타나는 특성입니다. MDN은 클로저를 이렇게 정의합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;ldquo;A closure is the combination of a function and the lexical environment within which that function was declared&amp;rdquo;&lt;br /&gt;클로저는 함수와 그 함수가 &lt;b&gt;선언된 렉시컬 환경&lt;/b&gt;과의 조합&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심적으로 봐야 할 부분은 &lt;b&gt;함수가 선언된 렉시컬 환경(Lexical Environment)&lt;/b&gt;입니다.&lt;br /&gt;이 개념은 사실 서로 다른 용어로 반복해서 등장해왔습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;252&quot; data-start=&quot;148&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;167&quot; data-start=&quot;148&quot; data-section-id=&quot;1ekk26k&quot;&gt;함수가 선언된 위치의 스코프&lt;/li&gt;
&lt;li data-end=&quot;191&quot; data-start=&quot;168&quot; data-section-id=&quot;76efij&quot;&gt;상위 스코프(Outer Scope)&lt;/li&gt;
&lt;li data-end=&quot;252&quot; data-start=&quot;192&quot; data-section-id=&quot;1kyt4hk&quot;&gt;실행 컨텍스트의 외부 렉시컬 환경 참조(Outer Lexical Environment Reference)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;254&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지는 모두 결국 &lt;b&gt;함수가 어디에서 정의되었는가&lt;/b&gt;를 기준으로 스코프를 결정한다는 동일한 의미를 가집니다.&lt;/p&gt;
&lt;p data-end=&quot;423&quot; data-start=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;즉, 자바스크립트는 &lt;b&gt;함수를 호출한 위치가&lt;/b&gt; 아니라 &lt;b&gt;선언된 위치를 기준&lt;/b&gt;으로 변수 탐색 범위를 결정합니다.&lt;/p&gt;
&lt;p data-end=&quot;423&quot; data-start=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 아래 코드에서는 어떤 값이 출력될까요? 1일까요, 10일까요?&lt;/p&gt;
&lt;pre id=&quot;code_1776666196558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 1;

function outer() {
	const x = 10;
	
	const inner = function () {
		console.log(x); 
	};
	
	return inner //outer는 inner 함수 객체 반환
}

const innerFunc = outer();

innerFunc();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;980&quot; data-start=&quot;913&quot; data-ke-size=&quot;size16&quot;&gt;outer 함수를 호출하면 중첩 함수 inner를 반환하고 실행이 종료됩니다. 이때 outer의 실행 컨텍스트는 콜 스택에서 제거되고, outer는 생명주기를 마감합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적으로 함수 실행이 끝나면 해당 함수의 지역 변수들도 함께 사라집니다.&lt;/b&gt; 더 이상 참조할 곳이 없으니 가비지 컬렉터가 메모리에서 수거하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 inner 함수가 outer의 변수 x를 참조하고 있다면&amp;nbsp;누군가 아직 x를 참조하고 있기 때문에, 자바스크립트 엔진은 outer의 렉시컬 환경을 메모리에서 제거하지 않고 유지합니다. 덕분에 outer이 생명주기를 마감했음에도 inner는 여전히 x = 10에 접근할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 함수보다 내부 함수가 더 오래 살아남아, 이미 종료된 외부 함수의 변수를 참조할 수 있는 현상. 그리고 이 내부 함수를 우리는 &lt;span style=&quot;color: #ee2323;&quot;&gt;클로저(closure)&lt;/span&gt;라고 부릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해, inner 함수는 단순히 함수 코드만 반환된 것이 아니라 &lt;b&gt;자신이 선언될 당시의 렉시컬 환경까지 함께 들고&lt;/b&gt; 반환된 것입니다. 그래서 &lt;b&gt;innerFunc()를 호출하면 변수 x는 전역이 아닌&lt;span style=&quot;color: #006dd7;&quot;&gt; outer 스코프에서 탐색&lt;/span&gt;되어 10&lt;/b&gt;이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 코드의 전체 실행 흐름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &lt;span data-token-index=&quot;0&quot;&gt;전역 실행 컨텍스트&lt;/span&gt; &lt;span style=&quot;color: #409d00;&quot; data-token-index=&quot;2&quot;&gt;생성 단계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776667384266&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global LE(렉시컬 환경) {
  x: uninitialized  (const라서 TDZ)
  outer: [함수 객체]
  innerFunc: uninitialized
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;전역 실행 컨텍스트&lt;/span&gt; &lt;span data-token-index=&quot;2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot; data-token-index=&quot;3&quot;&gt;실행 단계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776667411665&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x = 1 할당
outer() 호출 &amp;rarr; outer 실행 컨텍스트 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;3&quot;&gt;3. outer() 실행 컨텍스트 &lt;span style=&quot;color: #409d00;&quot;&gt;생성 단계&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776667423418&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;outer LE(렉시컬 환경) {
  x: uninitialized
  inner: uninitialized
  Outer Reference &amp;rarr; Global LE(전역 렉시컬 환경)[부모 스코프]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;3&quot;&gt;4. outer() 실행 컨텍스트 &lt;span style=&quot;color: #f89009;&quot;&gt;실행 단계&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776667472070&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x = 10 할당
inner = function() { console.log(x) } 할당
return inner &amp;rarr; outer 콜 스택에서 pop(소멸)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;inner의 스코프 체인으로 연결된 전체 렉시컬 환경&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776667531765&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inner [[Environment]] { 
  x &amp;rarr; (없음)
  Outer(외부 환경 참조 : 부모 스코프) &amp;rarr; outer LE {
    x &amp;rarr; 10
    inner &amp;rarr; [함수 객체]
    Outer(외부 환경 참조 : 부모 스코프) &amp;rarr; global LE {
      x &amp;rarr; 1
      outer &amp;rarr; [함수 객체]
      innerFunc &amp;rarr; [함수 객체]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모든 함수는 클로저다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 모든 함수는 &lt;b&gt;자신이 선언된 위치의 상위 스코프(외부 렉시컬 환경)를 기억&lt;/b&gt;합니다. 자바스크립트가 렉시컬 스코프를 기반으로 동작하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 생성될 때 항상 자신의 외부 렉시컬 환경을 참조하므로 이론적으로는 모든 함수가 클로저라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;일반적으로&lt;/b&gt; 모든 함수를 클로저라고 하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 함수가, 외부 함수의 변수를 참조하고 그 외부 함수의 실행이 종료된 이후에도 해당 변수에 계속 접근할 수 있는 경우 즉, 단순히 외부 환경을 기억하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;생명주기를 넘어서&lt;/b&gt;&lt;/span&gt; 그 &lt;b&gt;환경을 실제로 사용하면서 유지하는 상황&lt;/b&gt;을 &lt;b&gt;클로저&lt;/b&gt;라고 부르는 것이 일반적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드의 3가지 유형&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;1. 상위 스코프의 식별자를 참조하지 않는 경우 클로저가 아니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776667927162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
	const x = 1;
	const y = 2;
			
	function bar() {
		const z=3;
				
		debugger;
		//상위 스코프의 식별자를 참조하지 않는다.
		console.log(z);
	}
			
	return bar;
			
}
		
const bar = foo();
bar();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2704&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxkjI/dJMcag6ip5B/IISUKgrVT6mTraoGtsGWAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxkjI/dJMcag6ip5B/IISUKgrVT6mTraoGtsGWAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxkjI/dJMcag6ip5B/IISUKgrVT6mTraoGtsGWAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxkjI%2FdJMcag6ip5B%2FIISUKgrVT6mTraoGtsGWAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;359&quot; data-origin-width=&quot;2704&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo()는 bar를 반환함과 즉시 콜 스택에서 pop이 되어 생명주기를 마감합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;bar의 console.log는 z를 필요로 하지만 bar의 환경 레코드 안에 z가 존재하기 때문에 외부 렉시컬 환경 참조를 따라 상위 스코프를 참조하지 않아도 식별자를 찾을 수 있습니다. 이러한 경우 클로저라고 하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Scope&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776668058129&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Local       &amp;larr; bar의 환경 레코드
  this: Window
  z: 3      &amp;larr; bar 안에서 선언한 const z = 3

Script      &amp;larr; foo의 환경 레코드

Global      &amp;larr; 전역 환경 레코드
  Window&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Call Stack&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776668118363&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bar &amp;lt;- 현재 실행 컨텍스트
(anonymous) //전역 실행 컨텍스트의 실행단계에서 bar()를 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;내부 함수 bar는 클로저였지만, 외부 함수보다 일찍 소멸되기 때문에 클로저의 본질에 부합되지 않는다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776668268219&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
	const x = 1;
			
	function bar() {
		debugger;
		//상위 스코프의 식별자를 참조하지 않는다.
		console.log(x);
	}
	bar();
}

foo();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chwNg2/dJMcagSLwiw/F8I0WVo1Wlt4cGvYsKWyq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chwNg2/dJMcagSLwiw/F8I0WVo1Wlt4cGvYsKWyq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chwNg2/dJMcagSLwiw/F8I0WVo1Wlt4cGvYsKWyq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchwNg2%2FdJMcagSLwiw%2FF8I0WVo1Wlt4cGvYsKWyq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;403&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo() 내부에서 bar()를 선언하고 호출합니다. bar는 외부 변수 x를 참조하고 있기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스펙상으로는 클로저&lt;/b&gt;가 맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 브라우저 개발자 도구의 Scope 패널을 확인해보면 Closure 항목에 x: 1이 표시되는 걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 코드의 콜 스택 흐름을 먼저 확인해보면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전역 실행 컨텍스트가 콜 스택에 쌓입니다.&lt;/li&gt;
&lt;li&gt;foo() 호출 &amp;rarr; foo의 실행 컨텍스트가 그 위에 쌓입니다.&lt;/li&gt;
&lt;li&gt;foo 내부에서 bar() 호출 &amp;rarr; bar의 실행 컨텍스트가 맨 위에 쌓입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;bar가 x를 참조하는 시점에 foo의 실행 컨텍스트는 아직 콜 스택에 존재합니다. &lt;/span&gt;&lt;/span&gt;&lt;span&gt;즉, foo가 아직 살아있는 상태에서 bar가 x에 접근하는 것이기 때문에, &lt;/span&gt;&lt;span&gt;'외부 함수보다 내부 함수가 더 오래 살아남는다'는 클로저의 실용적 정의에는 &lt;/span&gt;&lt;span&gt;부합하지 않습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;스펙상으로는 클로저가 맞지만, &lt;/span&gt;&lt;span&gt;일반적인 스코프 체인 탐색만으로도 충분히 설명되는 경우입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  클로저의 본질에는 부합하지 않다는데, 브라우저 디버깅에 표시된 Closure는 뭔가요?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;foo가 콜 스택에 살아있는데도 브라우저가 Closure로 표시하는 건, bar가 foo의 렉시컬 환경을 Outer Reference로 참조하고 있기 때문&lt;br /&gt;브라우저는 &quot;Outer Reference로 상위 스코프를 참조하는 구조&quot; 자체를 Closure로 표시&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Scope&lt;/blockquote&gt;
&lt;pre id=&quot;code_1776668687299&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Local          &amp;larr; bar의 환경 레코드
  this: Window

Closure (foo)  &amp;larr; foo의 환경 레코드 (살아있음!)
  x: 1

Global         &amp;larr; 전역
  Window&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #2c2c2b;&quot; data-token-index=&quot;0&quot;&gt;3. 중첩 함수 bar는 외부 함수보다 더 오래 유지(생명주기가 길다.)되고 상위 스코프의 식별자를 참조하기 때문에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클로저이다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776670102584&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 1;

function outer() {
	const x = 10;
	const y = 20;
	
	const inner = function () {
		console.log(x); 
	};
	
	return inner //outer는 inner 함수 반환
}

const innerFunc = outer();
innerFunc();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1064&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9KUM1/dJMcadhqoiy/UkKBoIxyorDAHMAhFLnLW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9KUM1/dJMcadhqoiy/UkKBoIxyorDAHMAhFLnLW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9KUM1/dJMcadhqoiy/UkKBoIxyorDAHMAhFLnLW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9KUM1%2FdJMcadhqoiy%2FUkKBoIxyorDAHMAhFLnLW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;401&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;1064&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 클로저를 설명할 때 보여드린 코드입니다. outer의 실행 컨텍스트는 이미 콜 스택에서 제거됐지만, inner는 여전히 outer의 지역 변수 x를 참조하고 있습니다. 이처럼 &lt;b&gt;외부 함수의 실행이 끝난 뒤에도 외부 변수를 참조할 수 있는 내부 함수&lt;/b&gt;를 클로저라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저라고 해서 outer의 &lt;b&gt;모든 것&lt;/b&gt;이 메모리에 남아 있는 건 아닙니다. inner가 실제로 &lt;b&gt;참조하는 식별자만&lt;/b&gt; 기억됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 outer 안에 x, y라는 두 개의 변수가 있는데 inner가 x만 참조한다면, 실제로 메모리에 유지되는 건 x뿐입니다. (V8 같은 모던 엔진은 이런 식으로 최적화합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 x는 &lt;b&gt;자유변수&lt;/b&gt;라고도 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자유 변수(Free Variable) 함수 내부에서 참조하지만, 그 함수의 스코프에서 선언되지 않은 변수를 뜻합니다. 즉, 외부 스코프에서 선언되어 내부에서 참조되는 변수예요. inner에서의 x가 바로 자유 변수입니다. 클로저는 이 자유 변수를 기억하는 함수라고도 정의할 수 있어요.&amp;nbsp;&lt;br /&gt;&lt;span&gt;&lt;span&gt;한 가지 혼동하기 쉬운 점은, 자유 변수는 외부 함수의 생명주기와 무관한 개념이라는 것입니다. &lt;/span&gt;&lt;/span&gt;&lt;span&gt;외부 함수가 아직 실행 중이어도, 내부 함수 입장에서 외부 스코프에 선언된 변수를 참조하고 있다면 &lt;/span&gt;&lt;span&gt;그것은 자유 변수입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;자바스크립트 실행 구조의 계층: &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;콜 스택 &amp;rarr; 실행 컨텍스트(전역 / 함수 / eval / 모듈) &lt;/span&gt;&lt;span&gt;&amp;rarr; 렉시컬 환경 / 변수 환경 &amp;rarr; 환경 레코드 / 외부 환경 참조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;자바스크립트의 함수는 호출된 위치가 아니라 선언된 위치의 렉시컬 환경을 기억합니다. 그래서 외부 함수가 실행을 마치고 콜 스택에서 제거된 뒤에도, &lt;span&gt;&lt;span&gt;내부 함수가 외부 변수를 참조하고 있다면&lt;b&gt; 그 변수를 담고 있는 &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;외부 함수의 렉시컬 환경(Environment Record)이 가비지 컬렉션 대상이 되지 않습니다. &lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이렇게 이미 종료된 외부 함수의 변수를 계속 기억하는 내부 함수를 &lt;b&gt;클로저&lt;/b&gt;라고 부릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레퍼런스&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MDN &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;모던 자바 스크립트 24장 클로저&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/6</guid>
      <comments>https://happy7yong.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 28 Nov 2025 14:19:11 +0900</pubDate>
    </item>
    <item>
      <title>실행 컨텍스트로 이해하는 호이스팅과 TDZ</title>
      <link>https://happy7yong.tistory.com/5</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인트로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla JS로 VDOM을 직접 구현하면서, 이해되지 않는 에러를 반복해서 마주했습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1776158909865&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReferenceError: Cannot access 'x' before initialization
ReferenceError: vnode is not defined&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;우연치 않게 var을 선언하기 전에 console.log를 찍게 되는 일이 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;그런데, undefined만 뜨고 에러가 발생하지 않아서 &quot;아 JS는 변수자체가 선언되기 전에 변수를 저장해두는건가&quot;라고 생각했는데, const에서는 레퍼런스에러가 발생했습니다.&lt;/p&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 var은 되고 const는 안되지?&lt;/b&gt; 대체 JS에는 어떤 특징이 있어서 이런 일이 발생하는거지 하고 의문이 생겼습니다. 이 질문을 시작으로 따라가다보니 자연스럽게 실행 컨텍스트(Execution Context)라는 개념을 마주쳤고, 그 안에서 호이스팅(Hoisting)과 TDZ(Temporal Dead Zone)를 만나게 되었습니다. 호이스팅은 들어본 적 있었지만, &quot;내 코드에서 크게 문제가 될 일 없겠지&quot; 하고 넘겼던 개념이었는데, 이번 기회에 호이스팅과 TDZ, 그리고 var,let/const가 다른 이유에 대해 알아보고자 합니다.&lt;/p&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;469&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;본격적으로 알아보기 전에, 아래 코드의 실행 결과를 예측해볼까요?&lt;/p&gt;
&lt;pre id=&quot;code_1776159730668&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let x = 'global';

function test() {
  console.log(x); // 뭐가 출력될까요?
  let x = 'local';
}

test();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;저는 당연히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;global&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 출력될 거라고 생각했습니다. 하지만 실제로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;ReferenceError&lt;/b&gt;가 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 선언 시점의 문제가 아니라, &lt;b&gt;자바스크립트가 변수를 &lt;u&gt;어떤 범위 안에서 찾고, 어떻게 접근하는지&lt;/u&gt;&lt;/b&gt;와 연결되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 이유를 이해하기 위해, 먼저&amp;nbsp;&lt;b&gt;변수의 범위를 뜻하는 &amp;lt;스코프&amp;gt;&lt;/b&gt; 에 대해 먼저 살펴보고자 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;스코프&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; 스코프란?&lt;/b&gt;&lt;br /&gt;변수나 함수의 유효 범위 &amp;rarr; 렉시컬 스코프 규칙에 따른다&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;렉시컬 스코프(=정적 범위)란?&lt;/b&gt;&lt;br /&gt;- 변수가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;선언된 위치&lt;/b&gt;&lt;/span&gt;에 따라 접근 가능 범위가 결정되는것&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역 스코프&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;global scope ) :&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;전역 범위&lt;/li&gt;
&lt;li&gt;로컬 스코프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;function scope(var)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 함수 범위
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;var이 따르는 스코프&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;함수 블록({ })만 경계로 인식, if/for 블록은 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;code block Scope (let, const) :&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록 범위 - 블록 스코프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;let/const가 따르는 스코프&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;모든 블록({ })을 경계로 인식 (함수 포함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;var은 함수 전체를 스코프로 가지며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;호이스팅&lt;/b&gt;&lt;/span&gt;으로 인해 블록 안에 선언해도 함수 최상단에 선언한 것처럼 동작합니다. 때문에 블록 밖에서도 접근이 가능합니다. 반면 let/const는 블록 {}을 스코프 경계로 인식하기 때문에, 블록이 닫히면 해당 변수는 블록 밖에서 접근할 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776137453975&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function foo() {
  if (true) {
    var a = 1;   // 함수 스코프 &amp;rarr; foo 전체에서 접근 가능
    let b = 2;   // 블록 스코프 &amp;rarr; if 블록 안에서만 접근 가능
  }
  console.log(a); // 1 
  console.log(b); // ReferenceError
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;실행 컨텍스트&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JS는 코드를 실행하기 전에 먼저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;이 코드를 실행하려면 뭐가 필요하지?&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 정리합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 변수가 있는지, this는 누구인지, 바깥 스코프는 무엇인지. 그 정보를 담아두는 공간이 바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실행 컨텍스트&lt;/b&gt;입니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt; &lt;b&gt;실행 컨텍스트&lt;/b&gt;란?&lt;br /&gt;JS 엔진이 코드를 실행하기 위해 세팅해두는 환경&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ECMAScript에서 &lt;b&gt;소스 코드를 4가지 타입&lt;/b&gt;으로 구분합니다. 각각의 4가지 타입의 소스코드는 실행 컨텍스트를 생성합니다. 각 코드는 코드평가가 수행한 후에 실행 컨텍스트가 생성됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전역 코드 -&amp;gt; 전역 실행 컨텍스트&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot; data-token-index=&quot;0&quot;&gt;Global Execution Context&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;JS파일을 처음 로드할 때 최초 1개 생성&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;프로그램이 종료될 때까지 &lt;u&gt;&lt;b&gt;콜 스택 가장 아래&lt;/b&gt;&lt;/u&gt;에 항상 깔려있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;전역에서 선언한 var 변수와 함수가 등록됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;&lt;b&gt;this 값은 실행 환경&lt;/b&gt;에 따라 다릅니다: &lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;브라우저 일반 &amp;lt;script&amp;gt; : this === window &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;브라우저 &amp;lt;script type=&quot;module&quot;&amp;gt; : this === undefined&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;Node.js CommonJS 모듈 최상단 : this === module.exports (빈 객체)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;globalThis는 모든 환경에서 전역 객체를 가리킴&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;함수 코드 -&amp;gt; 함수 실행 컨텍스트&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot; data-token-index=&quot;0&quot;&gt;Function Execution Context&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;b&gt;함수&lt;/b&gt;가&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출될 때마다 새로 생성 (함수 스코프를 갖는 실행 컨텍스트)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;매개변수, 지역변수, 내부 함수, this 등이 여기에 포함됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;같은 함수라도 호출할 때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 컨텍스트&lt;/b&gt;가 생성되기 때문에 호출마다 독립적인 실행 환경을 갖습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;eval 코드 -&amp;gt; eval() 실행 컨텍스트&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;문자열을 코드로 실행하는 eval() 함수가 호출될 때 생성 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0051c2;&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color: #2b303b;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #008000;&quot;&gt;&quot;var x = 1&quot;&lt;/span&gt;&lt;span style=&quot;color: #2b303b;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #2b303b;&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;보안 취약점(XSS 등) 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실무에서는 사용을 금지&lt;/b&gt;하는 경우가 대부분&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;&lt;b&gt;모듈 코드 -&amp;gt; 모듈 실행 컨텍스트&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Module Execution Context&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;import/export를 사용하는 &lt;b&gt;ES 모듈&lt;/b&gt;이 로드될 때 생성됩니다.&lt;/li&gt;
&lt;li&gt;&amp;lt;script type=&quot;module&quot;&amp;gt; 로 선언된 스크립트나 .mjs 파일이 여기에 해당해요.&lt;/li&gt;
&lt;li&gt;각 모듈은 자체적인 &lt;b&gt;모듈 스코프&lt;/b&gt;를 가지며, 최상위 코드에서 선언한 변수도 전역을 오염시키지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1776137453976&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
  if (true) {
    var a = 1;   // 함수 스코프 &amp;rarr; foo 전체에서 접근 가능
    let b = 2;   // 블록 스코프 &amp;rarr; if 블록 안에서만 접근 가능
  }
  console.log(a); // 1 
  console.log(b); // ReferenceError
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt; 그럼 블록({})은요?&lt;/b&gt;&lt;br /&gt;if, for, while 같은 블록은 실행 컨텍스트를 새로 만들지 않습니다. 대신 블록 진입 시 현재 실행 컨텍스트의 렉시컬 환경(Lexical Environment) 이 새로운&lt;b&gt; 블록용 환경&lt;/b&gt;으로 교체됩니다. 블록을 빠져나오면 이전 환경으로 다시 돌아갑니다.&lt;br /&gt;&lt;br /&gt;함수/전역 진입 시점에는 둘이 같은 환경을 가리키지만, 블록에 진입할 때는 LexicalEnvironment만 새 블록 환경으로 바뀌고, VariableEnvironment는 그대로 유지됩니다.&lt;br /&gt;&lt;br /&gt;- let, const, class &amp;rarr; &lt;b&gt;LexicalEnvironment&lt;/b&gt;에 등록, 블록 진입 시 교체되는 쪽에 등록되기 때문에, 블록이 닫히면 사라집니다. &amp;rarr; &lt;b&gt;블록 스코프&lt;/b&gt; &lt;br /&gt;- var, 함수 선언 &amp;rarr; &lt;b&gt;VariableEnvironment&lt;/b&gt;에 등록, 블록 진입 시에도 교체되지 않는 쪽에 등록되기 때문에, 블록 안에서 선언해도 함수 전체에서 살아있습니다.&lt;b&gt; &amp;rarr; 함수 스코프&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;let/const가 블록 스코프를 갖는 건 바로 이 차이 때문&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;실행 컨텍스트(=코드 실행 환경) 객체 내부&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Lexical Environment (렉시컬 환경)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;let, const, 함수 선언, class 등&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;블록/함수 스코프 변수를 관리&lt;/b&gt;하는 공간&lt;/li&gt;
&lt;li&gt;&amp;ldquo;이 이름이 어떤 값을 가리키지?&amp;rdquo; 할 때 찾아보는 일종의 환경 지도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렉시컬 환경 : 정보 꾸러미 메타데이터&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span data-token-index=&quot;1&quot;&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #409d00;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;Environment Record (환경 레코드)&lt;/b&gt;&lt;/span&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;식별자 바인딩 : 식별자를 키 값 상태로 저장됨. (호이스팅)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;this 바인딩&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 컨텍스트에서 this가 무엇인지 (window, 어떤 객체 인스턴스 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Outer Lexical&lt;/span&gt;&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;En&lt;/span&gt;&lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;vironment Reference(외부 렉시컬 환경 참조)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;바깥 스코프의 렉시컬 환경에 대한 참조&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;함수 실행 컨텍스트가 콜 스택에서 pop되면 보통 그 환경 레코드는 GC 대상이 됩니다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; 하지만 &lt;b&gt;내부 함수가 Outer Reference를 통해 바깥 환경 레코드를 여전히 참조&lt;/b&gt;하고 있다면, &lt;/span&gt;&lt;span&gt; GC가 해당 환경 레코드를 수거하지 않습니다. &lt;/span&gt;&lt;span&gt;이것이 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;클로저의 동작 원리&lt;/b&gt;&lt;/span&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span data-token-index=&quot;1&quot;&gt;&lt;span data-token-index=&quot;1&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Variable Environment (변수 환경)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;var 선언과 함수 선언을 관리하는 환경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Lexical Environment와 동일하게 Environment Record와 Outer Environment Reference를 가짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;함수/전역 진입 시점에는 LexicalEnvironment와 같은 환경을 가리키지만, &lt;/span&gt;&lt;span&gt; 블록 진입 시에는 교체되지 않고 원래 환경을 유지함 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이 &quot;교체되지 않는다&quot;는 특성이 var의 함수 스코프 동작을 만들어냄&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;실행 컨텍스트 내부 구조&lt;br /&gt;
&lt;pre id=&quot;code_1776137453978&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;실행 컨텍스트 (Execution Context)
├── LexicalEnvironment
│   └── Environment Record
│       ├── 식별자 바인딩 (let, const, class, 함수 매개변수 등)
│       └── [[ThisValue]]  &amp;larr; FunctionER/GlobalER의 경우
│   └── [[OuterEnv]]  &amp;larr; 바깥 렉시컬 환경 참조
├── VariableEnvironment
│   └── Environment Record (var, 함수 선언)
│   └── [[OuterEnv]]
└── (Realm, ScriptOrModule 등 기타 필드)&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;소스 코드가 실행 컨텍스트를 생성하기 까지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진은 소스코드를 2개의 과정으로 나누어 처리합니다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;Creation Phase (생성/평가 단계) &lt;/span&gt;: 소스 코드의 평가&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 컨텍스트를 만들고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변수/함수 선언을 먼저 스캔&lt;/b&gt;해서 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;환경 레코드&lt;/b&gt;&lt;/span&gt;에 등록 &amp;rarr; 이때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;호이스팅&lt;/b&gt;&lt;/span&gt;이 발생&lt;/li&gt;
&lt;li&gt;전역/함수 실행 컨텍스트 모두 이 단계를 거침&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;Execution Phase (실행 단계)&lt;/span&gt; &lt;/span&gt;: 소스 코드를 한 줄씩 실행한다.(런타임 실행)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선언 할당, 함수 호출, 연산 등 로직 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[ &lt;span style=&quot;color: #ee2323;&quot;&gt;변수에 대한 정보&lt;/span&gt;를 찾는 구조적 단계 ]&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 Lexical Environment(렉시컬 환경) 안의&lt;b&gt; Environment Record(환경 레코드)&lt;/b&gt;에서 찾음 &amp;rarr; 없으면&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Outer Lexical Environment (렉시컬 환경의 외부 참조)&lt;/b&gt;으로 이동해서 다시 찾음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하게 되면 &lt;b&gt;스코프 체인&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot; data-token-index=&quot;0&quot;&gt;실행 컨텍스트(단위)들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;콜 스택(Call Stack)&lt;/span&gt;(&lt;span style=&quot;color: #409d00;&quot;&gt;자바스크립트의 메인/싱글 스레드 담당&lt;/span&gt;)&lt;/b&gt;에 쌓였다가, 맨 위의 컨텍스트가 끝나면 스택에서 빠지고, 그 아래 컨텍스트가 돌아가는 구조&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/curAwZ/dJMcacpdoTx/wszLYc97S8S18L1NgKRfq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/curAwZ/dJMcacpdoTx/wszLYc97S8S18L1NgKRfq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/curAwZ/dJMcacpdoTx/wszLYc97S8S18L1NgKRfq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcurAwZ%2FdJMcacpdoTx%2FwszLYc97S8S18L1NgKRfq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;884&quot; height=&quot;228&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;실행 컨텍스트가 콜 스택에 쌓이는 순서&lt;br /&gt;&lt;/b&gt;  핵심 : 호출될 때마다 생성 단계부터 다시 시작&lt;b&gt;&lt;br /&gt;&lt;br /&gt;1. 전역 실행 컨텍스트&lt;/b&gt; &lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;생성 단계&lt;/b&gt;&lt;/span&gt; (전역 변수/함수 스캔, &lt;span style=&quot;color: #666666;&quot;&gt;환경 레코드 등록&lt;/span&gt;) &lt;br /&gt;&amp;rarr; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;실행 단계&lt;/b&gt;&lt;/span&gt; (한 줄씩 실행) &lt;br /&gt;&amp;rarr; &lt;u&gt;&lt;b&gt;func2()&lt;/b&gt;&lt;/u&gt; 호출 발견 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. &lt;u&gt;func2()&lt;/u&gt; 실행 컨텍스트&lt;/b&gt; &lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;생성 단계&lt;/b&gt; &lt;/span&gt;(func2 스코프 스캔, 환경 레코드 등록) &lt;br /&gt;&amp;rarr; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;실행 단계&lt;/b&gt;&lt;/span&gt; (한 줄씩 실행) &lt;br /&gt;&amp;rarr;&lt;u&gt; &lt;b&gt;func1()&lt;/b&gt;&lt;/u&gt; 호출 발견 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. &lt;u&gt;func1()&lt;/u&gt; 실행 컨텍스트&lt;/b&gt; &lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;생성 단계&lt;/b&gt;&lt;/span&gt; (func1 스코프 스캔, 환경 레코드 등록) &lt;br /&gt;&amp;rarr; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;실행 단계 &lt;/b&gt;&lt;/span&gt;(한 줄씩 실행) &lt;br /&gt;&amp;rarr; 완료 시 pop &amp;rarr; func2로 복귀 &lt;br /&gt;&lt;br /&gt;4. func2 완료 &amp;rarr; pop &amp;rarr; 전역으로 복귀 &lt;br /&gt;5. 전역 완료 &amp;rarr; pop&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호이스팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 단계에서 미리 스캔해서 등록하기 때문에, 실행 단계에서 코드를 위에서부터 읽어 내려올 때 이미 변수가 올라가 있는 것처럼 동작하는 것을 호이스팅이라고 말합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  호이스팅이란?&lt;/b&gt;&lt;br /&gt;&lt;b&gt;실행컨텍스트의 생성 단계&lt;/b&gt;에서 렉시컬 환경의 &lt;b&gt;환경 레코드&lt;/b&gt;에 변수가 저장되는 현상&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;function, var, let/const 호이스팅 차이&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;◻️ &lt;/span&gt;function&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function 키워드 함수는&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; 선언과 초기화, 할당 단계&lt;/b&gt;가 내부적으로 &lt;b&gt;동시에&lt;/b&gt;&lt;/span&gt; 진행&lt;/p&gt;
&lt;pre id=&quot;code_1776139220225&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo(); // 여기서도 호출 가능 -&amp;gt; foo : 선언 및 초기화 동시에 진행

function foo() {
  console.log(&quot;hi&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성단계(Creation Phase):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;foo 라는 식별자를 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;환경 레코드&lt;/span&gt;에 등록(호이스팅)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;foo 에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;함수 객체 자체&lt;/b&gt;를 바로&lt;b&gt; 할당(초기값 : 객체 할당)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; 그래서 실행단계(Execution Phase) 들어가기 전부터 foo는 &lt;b&gt;완전히 초기화된 함수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function는&lt;u&gt; &lt;b&gt;선언과 초기화 모두 호이스팅&lt;/b&gt;&lt;/u&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 함수는 초기화 전에 호출해도 정상적으로 호출이 가능하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;주의&lt;br /&gt;foo(); // TypeError 또는 ReferenceError &lt;br /&gt;const foo = function() { ... }; &lt;br /&gt;&lt;br /&gt;이 경우 foo는 const 변수의 호이스팅 규칙을 따르기 때문에 TDZ에 걸립니다. &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;◻️ &lt;/span&gt;var&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var은 변수 선언은 하지만, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;undefined를 초기값으로 갖는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776139418023&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(num); //undefined
var num = 10;
console.log(num); //10&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성단계(Creation Phase):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;num 라는 식별자를 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;환경 레코드&lt;/span&gt;에 등록(호이스팅)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;num의 &lt;span style=&quot;color: #ee2323;&quot;&gt;초기값을 &lt;b&gt;undefined&lt;/b&gt;를 넣어둠.&lt;b&gt;(초기값 : undefined)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행단계(Execution Phase)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;console.log(num) 은 이미 num이 존재하지만 &lt;b&gt;undefined인 상태&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이후 라인 2에서 num 값 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 아래와 같은 경우로 진행됨.&lt;/p&gt;
&lt;pre id=&quot;code_1776139508608&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var num; //&amp;lt;- 환경 레코드에 식별자 저장 및 undefined 
console.log(num)
num=10;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;◻️ &lt;/span&gt;let/const&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let/const는 변수 선언은 하지만 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;초기화되지 않은 상태를 갖는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776157605468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(num); //ReferenceError
let num = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성단계(Creation Phase):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;num 라는 식별자를 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;환경 레코드&lt;/span&gt;에 등록(호이스팅)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;num의 &lt;span style=&quot;color: #ee2323;&quot;&gt;초기값을 &lt;b&gt;uninitialized(초기화되지 않은 상태) -&amp;gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이렇게 초기화되지 않은 상태를 갖는 구간을 TDZ라고 합니다.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;TDZ(Temporal Dead Zone) : 일시적 사각지대&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;일시적 사각지대에 포함되어있는 변수를 사용하지 못하도록 막았다고 함.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;코드의 예측 가능성&lt;/b&gt;을 높이기 위해 &lt;b&gt;TDZ라는 개념을 도입&lt;/b&gt; : var 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행단계(Execution Phase)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;let num = 10; 도달 시 값 할당 이 된다. num : 10&lt;/li&gt;
&lt;/ul&gt;
&amp;rarr; 값이 할당되기이전에 접근 시 referenceError : TDZ 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테스트&lt;br /&gt;
&lt;pre id=&quot;code_1776157762081&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(num);
let num = 10;
console.log(num);​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
위와 같은 코드가 있을 경우 첫번째 console.log 라인은 &lt;b&gt;TDZ 구간&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o0WSk/dJMcaakDhdS/KCn3mVJqWu0KdYzGBPQtyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o0WSk/dJMcaakDhdS/KCn3mVJqWu0KdYzGBPQtyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o0WSk/dJMcaakDhdS/KCn3mVJqWu0KdYzGBPQtyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0WSk%2FdJMcaakDhdS%2FKCn3mVJqWu0KdYzGBPQtyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;262&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음, 인트로에서의 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;console.log는 어떻게 출력될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1776160288999&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let x = 'global';

function test() {
  console.log(x); // 뭐가 출력될까요?
  let x = 'local';
}

test();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;test() 함수 실행 컨텍스트의 생성 단계&lt;/b&gt;에서 내부의&lt;b&gt; let x가 먼저 호이스팅&lt;/b&gt;되어 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;uninitialized 상태로 환경 레코드에 등록&lt;/b&gt;&lt;/span&gt;됩니다. 때문에 console.log(x) 시점에 x는 &lt;b&gt;TDZ&lt;/b&gt; 구간에 있고, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ReferenceError&lt;/b&gt;&lt;/span&gt;가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 퀴즈의 인사이트는 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;호이스팅은 무조건 일어난다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역에 같은 이름의 변수가 있어도, &lt;b&gt;현재 스코프 안에 선언이 있다면 그게 먼저 호이스팅되어 TDZ에 들어갑니다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바깥 변수를 참조할 것 같지만, 실제로는 내부 선언이 먼저 잡히는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 ,&lt;i&gt;var은 선언 전에도 undefined로 접근이 가능하니, 코드가 예측 불가능해지는 것 아닐까요?&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞습니다. 이것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이상한 게 맞습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;실제로 var의 호이스팅 동작(undefined 초기화)은 JS 설계 과정에서 의도한 기능이 아니라, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;함수 호이스팅을 구현하는 과정에서 부수적으로 딸려온 결과물이라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r4EN7/dJMcagkPQLb/i9hpyc3G4fGpyXAkKPXQBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r4EN7/dJMcagkPQLb/i9hpyc3G4fGpyXAkKPXQBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r4EN7/dJMcagkPQLb/i9hpyc3G4fGpyXAkKPXQBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr4EN7%2FdJMcagkPQLb%2Fi9hpyc3G4fGpyXAkKPXQBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;409&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;JS를 만든 브랜든 아이크(Brendan Eich)의 언급&lt;/b&gt;&lt;br /&gt;: &quot; var hoisting was thus &lt;b&gt;unintended consequence&lt;/b&gt; of function hoisting, no block scope, JS as a 1995 rush job. ES6 'let' may help.&quot;&lt;br /&gt;&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;var 호이스팅은 함수 호이스팅의 &lt;b&gt;의도치 않은 결과(unintended consequence)&lt;/b&gt;였습니다. &lt;br /&gt;&lt;/span&gt;&lt;span&gt;블록 스코프가 없던 1995년 급하게 만들어진(JS as a 1995 rush job) &lt;/span&gt;&lt;span&gt;자바스크립트에서는 불가피한 부작용이었던 셈이고, &lt;/span&gt;&lt;span&gt;ES6의 let이 이 문제를 해결해줄 수 있을 거라고 말하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;호이스팅과 TDZ 관계&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호이스팅은 JS의 엔진이 실행 컨텍스트의 환경 레코드에 필수적으로 변수를 저장하는 특징 때문에 자연스럽게 발생하는 현상&lt;/li&gt;
&lt;li&gt;하지만 var은 호이스팅 즉시 undefined로 초기화 되기 때문에 선언전에 접근해도 에러가 나지 않는 비직관적인 동작이 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1776158282845&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(x); // undefined //이딴게 에러가 안뜬다고? 
var x = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dipfPe/dJMcacW2sfW/vJmIN3mWlPUQCkdQBmFR2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dipfPe/dJMcacW2sfW/vJmIN3mWlPUQCkdQBmFR2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dipfPe/dJMcacW2sfW/vJmIN3mWlPUQCkdQBmFR2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdipfPe%2FdJMcacW2sfW%2FvJmIN3mWlPUQCkdQBmFR2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;194&quot; height=&quot;259&quot; data-origin-width=&quot;194&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서 let과 const가 등장할때, 호이스팅은 JS언어 스펙상 없앨수없기때문에 버그성 동작을 막자. 라는 철학으로 -&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;TDZ(Temporal Dead Zone)&lt;/span&gt;&lt;/b&gt;가 등장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레퍼런스&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/9I1dzg20r1g?si=HaxggK9FtolsCGs4&quot;&gt;https://youtu.be/9I1dzg20r1g?si=HaxggK9FtolsCGs4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eDcaBeaOrqI&quot;&gt;https://www.youtube.com/watch?v=eDcaBeaOrqI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/_zMVlKxmWHg?si=hAMBfYb76_wvQVZe&quot;&gt;https://youtu.be/_zMVlKxmWHg?si=hAMBfYb76_wvQVZe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://junilhwang.github.io/TIL/Javascript/Domain/Execution-Context/#_1-&quot;&gt;https://junilhwang.github.io/TIL/Javascript/Domain/Execution-Context/#_1-&lt;/a&gt;&lt;a href=&quot;https://junilhwang.github.io/TIL/Javascript/Domain/Execution-Context/#_1-%E1%84%80%E1%85%A2%E1%84%82%E1%85%A7%E1%86%B7&quot;&gt;개념&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/5</guid>
      <comments>https://happy7yong.tistory.com/5#entry5comment</comments>
      <pubDate>Wed, 26 Nov 2025 09:10:26 +0900</pubDate>
    </item>
    <item>
      <title>LinkedHashMap은 왜 멀티스레드 환경에서 위험한가?</title>
      <link>https://happy7yong.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #0077aa; text-align: start;&quot; href=&quot;https://happy7yong.tistory.com/1&quot; data-tiara-id=&quot;/1&quot; data-tiara-plink=&quot;/1&quot; data-tiara-provider=&quot;yongyong&quot; data-tiara-name=&quot;멀티스레드 환경에서 index++ 는 왜 위험한가?&quot; data-tiara-click_url=&quot;https://happy7yong.tistory.com//1&quot; data-tiara-image=&quot;&quot; data-tiara-copy=&quot;멀티스레드 환경에서 index++ 는 왜 위험한가?&quot; data-tiara-action-kind=&quot;ClickContent&quot; data-tiara-action-name=&quot;블로그글_클릭&quot;&gt;멀티스레드 환경에서 index++ 는 왜 위험한가? &lt;/a&gt;에서 index++ 연산의 원자성 문제를 해결하기 위해 AtomicLong을 적용하는 방법을 살펴보았다.&lt;br /&gt;하지만 ID 생성만 안전하다고 해서, &lt;b&gt;애플리케이션 전체의 동시성이 보장되는 것은 아니다.&lt;/b&gt; 게시글이나 사용자 정보를 관리하는 Map 자체가 스레드에 안전하지 않다면, 여전히 데이터 꼬임이나 손실이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;LinkedHashMap을 사용할 때 발생할 수 있는 동시성 문제&lt;/b&gt;와 이를 해결하기 위한 &lt;b&gt;ConcurrentHashMap의 내부 구조&lt;/b&gt;를 비교해본다.&lt;/p&gt;
&lt;pre id=&quot;code_1761458119261&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class InMemoryPostRepository implements PostRepository {
    private AtomicLong postId = new AtomicLong(0);
    //Map&amp;lt;Key : index, Value : PostEntity&amp;gt;
    private Map&amp;lt;Long, PostEntity&amp;gt; Posts = new LinkedHashMap&amp;lt;&amp;gt;();
    
    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LinkedHashMap의 구조&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt; &lt;b&gt; LinkedHashMap&lt;/b&gt; 이란?&lt;br /&gt;LinkedHashMap은&amp;nbsp;&lt;u&gt;&lt;b&gt;HashMap + 이중 연결 리스트(Doubly Linked List)&amp;nbsp;&lt;/b&gt;&lt;/u&gt;로 구성되어 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;즉, 각 Entry(Node)는&lt;b&gt; next와 prev 포인터를 가지고&amp;nbsp;삽입 순서를 유지&lt;/b&gt;하도록 만들어졌다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HashMap : 해시 버킷 구조&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(해시 인덱스로 계산)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Linked :&lt;/b&gt; Entry&amp;lt;K,V&amp;gt; 내부에서 next(after)와 prev(before) 포인터로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;삽입 순서 유지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;내부적으로 Lock이나 CAS 같은 동기화 메커니즘을 사용하지 않기 때문에, &lt;/b&gt;멀티스레드 환경에서는 안전하지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노드 삽입 순서&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761462266831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;table[3] &amp;rarr; Node(A,1) &amp;rarr; Node(B,2) &amp;rarr; Node(C,3) &amp;rarr; null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운&lt;b&gt; Node(D,4)&lt;/b&gt; 삽입 시&lt;/p&gt;
&lt;pre id=&quot;code_1761462288946&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;table[3] &amp;rarr; Node(A,1) &amp;rarr; Node(B,2) &amp;rarr; Node(C,3) &amp;rarr; Node(D,4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tail에 연결되며&lt;b&gt; 순서를 보장&lt;/b&gt;하며, 성능측면에서 &lt;b&gt;빠르다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LinkedHashMap은 단일 스레드 환경에서는 문제가 되지 않지만, 멀티스레드 환경에서 동시에 삽입이 진행되면 내부 포인터 (next, prev)가 꼬이면서 &lt;b&gt;구조&amp;nbsp;불일치&lt;/b&gt;가 발생할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1761461601014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Post postCreate(User user, PostCreateFormDto postCreateFormDto) {
    long id = postId.getAndIncrement();
    
    Post post = new Post(
        id,
        postCreateFormDto.getTitle(),
        postCreateFormDto.getContent(),
        user.getUserId(),
        LocalDateTime.now()
    );
	
    //LinkedHashMap : 동시에 삽입 시 연결 꼬임 발생 가능
     Posts.put(post.getPostId(), post);

    return post;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Posts.put이 동시에 실행되면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;하나의 스레드가 연결중인 노드를 다른 스레드가 덮어쓰면서 참조 관계&lt;/u&gt;가 꼬일 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 문제로 인해 데이터가 유실되거나,&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConcurrentModificationException 이 발생할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt; ConcurrentModificationException&lt;/b&gt; 이란?&lt;br /&gt;한 스레드가 &lt;b&gt;컬렉션을 순회&lt;/b&gt;하고 있는 동안 &lt;br /&gt;다른 스레드나 자신이 &lt;b&gt;컬렉션의 구조 변경이 발생됨을 확인하면 던지는&lt;/b&gt; 예외&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ConcurrentHashMap의 구조&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  ConcurrentHashMap&lt;/b&gt; 이란?&lt;br /&gt;&lt;b&gt;CAS(CompareAndSwap)&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;과&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;분할 잠금(Lock Stripping)&lt;/b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;을 이용해서 멀티스레드 환경에서 안전하다.(Safety)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;put()시에는 특정 버킷에만 CAS 연산을 적용한다.&lt;/li&gt;
&lt;li&gt;전체 Map에 lock을 걸지 않기 때문에 병렬 삽입 가능&lt;/li&gt;
&lt;li&gt;기존 노드의 next(after)에 객체를 연결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서를 보장하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노드 삽입 순서&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761463317248&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;table[3] &amp;rarr; Node(A,1) &amp;rarr; Node(B,2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 노드 &lt;b&gt;Node(C,3)&lt;/b&gt; 삽입시&lt;/p&gt;
&lt;pre id=&quot;code_1761463372365&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;table[3] &amp;rarr; Node(C,3) &amp;rarr; Node(A,1) &amp;rarr; Node(B,2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;head에 연결되어&lt;/b&gt; 순서를 보장하지 않지만, CAS 기반으로 안전하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ConcurrentHashMap 내부 구조 살펴보기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761463849680&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public V put(K key, V value) {
        return putVal(key, value, false);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;put의 핵심 로직인 putVal()으로 구현되어있다.&lt;/p&gt;
&lt;pre id=&quot;code_1761463948400&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final V putVal(K key, V value, boolean onlyIfAbsent) {
    ...
    for (Node&amp;lt;K,V&amp;gt;[] tab = table;;) { // 무한 루프 (CAS 실패 시 재시도)
        Node&amp;lt;K,V&amp;gt; f; int n, i, fh;
        
        // 1. table 초기화 (lazy-init)
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();

        // 2. 버킷이 비어있다면 CAS로 바로 삽입
        else if ((f = tabAt(tab, i = (n - 1) &amp;amp; hash)) == null) {
            if (casTabAt(tab, i, null, new Node&amp;lt;K,V&amp;gt;(hash, key, value)))
                break;  // Lock 없이 CAS 성공 &amp;rarr; 삽입 완료
        }

        // 3. 해시 충돌 발생 or 버킷에 노드가 존재하는 경우
        else {
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // 동일 버킷 내 연결리스트 or 트리 처리
                    ...
                }
            }
        }
    }
    addCount(1L, binCount);
    return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번에서&amp;nbsp;&lt;b&gt;버킷이 비어있을 경우,&lt;/b&gt; CAS로 lock없이 노드를 추가한다는 사실을 확인할 수 있으며, &lt;b&gt;3번&amp;nbsp;해시 충돌 발생 또는 버킷에 노드가 존재할 경우,&lt;/b&gt; 해당 버킷을 synchronized 적용하여 &lt;b&gt;부분적으로 잠금(Fine-Grained Locking)&lt;/b&gt;을 건다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ConcurrentHashMap로 문제 개선하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AtomicLong을 통해 ID 생산 과정에 있어 원자성 문제를 해결했더라도, 멀티 스레드 환경에서 같은 버킷에 노드가 연결될 때 안전하게 들어오지 못한다면, Map 내부에서 next,prev 연결 꼬임을 발생시킬 수 있는 LinkedHashMap 때문에 해당 버킷에서 안전하게 노드가 연결될 수 있도록, CAS 기반으로 안전하게 노드를 연결할 수 있는 ConcurrentHashMap을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761464473516&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private AtomicLong postId = new AtomicLong(0);
//Linked -&amp;gt; Concurrent 변경
private Map&amp;lt;Long, PostEntity&amp;gt; Posts = new ConcurrentHashMap&amp;lt;&amp;gt;();
   
public Post postCreate(User user, PostCreateFormDto postCreateFormDto) {
   	
    long id = postId.getAndIncrement();
    
    Post post = new Post(
        id,
        postCreateFormDto.getTitle(),
        postCreateFormDto.getContent(),
        user.getUserId(),
        LocalDateTime.now()
    );

    Posts.put(post.getPostId(), post);

    return post;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 멀티스레드 환경에서의 동시성 문제는 데이터를 누가 언제&amp;nbsp; 수정하느냐의 싸움이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AtomicLong은 &lt;b&gt;값의 원자성&lt;/b&gt;, ConcurrentHashMap은 &lt;b&gt;구조의 일관성&lt;/b&gt;을 보장한다.&lt;br /&gt;두 가지를 함께 사용해야 스레드 안전성이 완전하게 확보된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Trouble Shooting/JAVA</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/3</guid>
      <comments>https://happy7yong.tistory.com/3#entry3comment</comments>
      <pubDate>Sun, 26 Oct 2025 16:59:53 +0900</pubDate>
    </item>
    <item>
      <title>네이버 부스트캠프 베이직[Kotlin] 후기</title>
      <link>https://happy7yong.tistory.com/2</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Naver Boostcamp Basic&lt;br /&gt;Kotlin&lt;br /&gt;25.06.23 - 25.07.04&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmAgPd/dJMb9aX096n/prv0Wyxax05H2dyKDKXlKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmAgPd/dJMb9aX096n/prv0Wyxax05H2dyKDKXlKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmAgPd/dJMb9aX096n/prv0Wyxax05H2dyKDKXlKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmAgPd%2FdJMb9aX096n%2Fprv0Wyxax05H2dyKDKXlKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;386&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;내가-개발을-시작한-이유&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  내가 개발을 시작한 이유&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 때때로 어떤 장면이나 사람의 행위에 대해 감동을 받고, 그 감동은 한 사람의 인생을 바꾸는 성장의 씨앗이 되기도 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 만든 창작물이 누군가에게 아주 작은 빛이라도 되어줄 수 있다면, 그 한 사람만으로도 이 길을 계속 걸을 이유가 충분하다고 믿습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그림그리기를 좋아하던 내가 개발을 시작한 진짜 이유는 돈 때문이었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가정이 행복하기 위해선, 삶이 안정적이어야되고, 삶을 위해선 현실적으로 어느 정도의 경제력이 필요하다고 느꼈기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리 집은 넉넉하지 않았고, 그래서 불안정한 미술이 아닌 확실한 기술로 돈을 벌 수 있는 길을 택해야 했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아이러니하게도 개발을 공부하면서 느낀 점은 그림과 개발은 생각보다 닮아 있다 라는 것이었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;둘 다 무에서 유를 만들어내는 창작의 과정이고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사람들에게 기능적으로, 감성적으로 유효하게 작용한다는 점에서 매우 비슷하게 느꼈습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✨ 그림에서 이루지 못한 꿈을, 개발이라는 새로운 도구를 통해 이루고자 저는 프론트엔드 개발자를 지망하게 되었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;-js-파던-내가-코틀린을-선택하게-된-계기-및-과정&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  JS 파던 내가 코틀린을 선택하게 된 계기 및 과정&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드는 주로 JavaScript 기반 언어를 다루기 때문에 처음에는 JS에 집중하며 코딩 테스트나 프로젝트를 진행하곤 했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2024년, 산학협력 프로젝트에서 React Native를 활용한 경험이 있었는데, 이때 확장자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;.kt&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 우연히 마주쳤습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그때는 코틀린에 대해 깊이 알지 못했고, React Native 위주였기 때문에 별다른 관심을 갖지 않았습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 코틀린에 관심을 갖게 만든 것 &amp;lt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;2024 DevFest Songdo &amp;gt;&lt;/b&gt;에 참여하였을 때였습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;2024 DevFest Songdo &amp;gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서는 우아한형제들, 카카오, 구글 등 다양한 기업의 개발자들이 참여해 강연과 커뮤니티 활동, 미니 컨퍼런스 형식으로 진행되는 행사가 열렸습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 행사에서 많은 기업들이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;안드로이드 앱 개발에 Kotlin을 주요 언어로 사용하고 있다는 사실&lt;/b&gt;을 알게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그때부터 자연스럽게 왜 Kotlin일까 하는 궁금증이 생겼고, Kotlin이라는 언어 자체에 흥미가 생기기 시작했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹개발을 시작한 저에게는 다양한 디바이스의 형태가 진화하고 있는 세상에서, 웹보다 앱이 인간의 심장에 가까운 위치에 있으며 접근성이 높을 거라 생각했고, 더 미래지향성이 높아보였습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 앱 개발 공부를 시작하던 중, 마침 네이버 부스트캠프에서 Kotlin 트랙 참가자를 모집하는 것을 알게 되어 과감하게 지원 하였습니다 !&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt; (지금 생각해도 꽤나 용기있었다.. 용기만 있었다&amp;hellip;)&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;basic-느낀-점&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Basic 느낀 점&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;10일간 진행된 네이버 부스트캠프 베이직 과정은, 단순히 매일 코드를 작성하는 것이 아니라 문제 해결 과정을 기록하고, 그 풀이 과정을 동료들과 공유하는 방식으로 운영되었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각자 문제에 대해 고민한 코드를 글로 정리해 공유하고, 다른 동료들의 풀이를 보며 서로 피드백을 주고받는 커뮤니티 기반 학습이 인상 깊었습니다. 저는 당시 &amp;lsquo;미래내일 ESG 교육&amp;rsquo;도 병행하고 있었기 때문에 오후 6시 이전에는 ESG 교육을 듣고, 이후 시간에는 네부캠 문제 풀이에 몰두했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin 트랙에 참여하기 전, 인프런 강의로 Kotlin 입문 강좌를 완강하고 참여했던 것은 정말 좋은 선택이었습니다.  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 간단한 문법조차 익숙하지 않아 어려움을 겪었지만, 점차 고난도 과제를 마주해도 두려움보다는 기대감이 앞서게 되었고, 한 줄 한 줄 직접 작성하며 몰입할 수 있었던 시간 덕분에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요즘처럼 AI 기반 코딩이 범람하는 시대에, 순수하게 스스로 고민하며 코딩하는 경험이 너무 소중하게 느껴졌습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;-코틀린-느낀-점&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  코틀린 느낀 점&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 ES6 기반의 JavaScript만 다뤘기 때문에 JVM 기반의 Kotlin을 해야 한다는 사실이 부담스럽고 두려웠습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인프런 강의에서도 Java와 Kotlin을 비교해 설명하는 부분이 많아 처음에는 더 헷갈리기도 했고 컴공 전공생임에도 정적 언어를 능숙하게 다루지 못한다는 점이 부끄러웠기에, 이번 기회를 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정적 언어를 마스터하고 싶다는 열망&lt;/b&gt;이 컸습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 직접 Kotlin을 써보면서, 오히려 TypeScript와 비슷한 느낌을 받아서 JS + Java의 장점을 모아놓은 듯한 느낌이 들었고, 덕분에 점점 문법이 익숙해졌습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다도 정적 타입 언어의 안전성과 안정성은 확실한 장점이었고, 이후에는 5개월간 JS를 쓴 저보다, Kotlin을 단 2주 쓴 제가 더 자신 있게 코드를 짤 수 있을 정도가 되었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 베이직이 끝나고도 프로그래머스 3단계 문제를 Kotlin으로 풀 정도로 친숙해졌고, 이후 코딩 테스트에서도 자연스럽게 Kotlin을 선택하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 정적 언어를 공부하면서 동적 할당, 스택, 힙 등 메모리 구조와 하드웨어 관점의 이해도 함께 향상되었던 것도 큰 수확이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;-1차-테스트-후기&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  1차 테스트 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1차 테스트에서는 코딩 테스트 3문제 + CS 지식 문제 가 나왔습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(정확한 문제수는 컨텐츠 유출로 될까봐 작성하지 못했습니다..미안해유..)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 문제는 풀었지만, 두 번째 문제에서 문제 해석과 접근 방식에 시간을 너무 많이 사용해 결국 풀이에 실패했고, CS 문제는 전공자임에도 막상 손이 잘 안 움직이더라고요...  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 2번 문제는 이해하면 간단한 문제였는데, 그 &amp;lsquo;이해&amp;rsquo;까지 가는 데 너무 오래 걸렸고, 문법적으로도 익숙하지 않아 시간 안에 풀지 못한 게 가장 아쉬웠습니다. CS 문제도 평소에 교과서로만 접하다가 직접 풀어보니 막막했고, 전학기 과목 외에는 거의 새롭게 느껴질 정도였습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 회고하며 느낀 점은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코테는 결국 시간 싸움이다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제를 빠르게 이해하고 푸는 연습이 필요함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CS도 평소에 조금씩 정리해둘 것!&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정보처리기사 공부와 병행하면 좋음&lt;/li&gt;
&lt;li&gt;다음에는 반드시 2번까지 먼저 풀고 CS로 넘어가야겠음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;변화된-나의-모습&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✨ 변화된 나의 모습&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  문제 접근 방식의 변화&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 단순히 풀기보다는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;직관적인 사고와 문제 해결 과정을 구조화&lt;/b&gt;해 나가게 되었습니다.&lt;/li&gt;
&lt;li&gt;탑다운 방식으로 큰 범위부터 작은 요소로 차근히 접근하게되었고, 어떤 문제든 해결할 수 있다는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자신감&lt;/b&gt;이 생겼습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주석, 변수명의 중요성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 길어질수록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가독성과 유지보수성&lt;/b&gt;의 중요성을 깨달았고,&lt;/li&gt;
&lt;li&gt;변수명을 명확하게 짓는 습관이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가장 중요&lt;/b&gt;하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #fbfcfd; color: #212529; text-align: start;&quot;&gt;&lt;code&gt;// Before
val count = 5

// After
val numOfBooks = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  함수 분리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에는 하나의 거대한 함수에 모든 로직을 몰아 넣었지만,&lt;/li&gt;
&lt;li&gt;과제를 거치며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클린 코드의 개념들(응집도, 결합도 등)을 실제로 적용하는 연습&lt;/b&gt;을 하게 되었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;문제풀이를 작성하는 습관&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 마크다운 형식으로 정리하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Velog에 회고 글을 남기듯 작성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;시간이 지난 후, 내가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;어떻게 사고했고 어떤 흐름으로 해결했는지를 되돌아볼 수 있다는 점&lt;/b&gt;에서 큰 의미가 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Kotlin 정적언어를 공부하고 난 후 JS를 대하는 자세&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적언어와 정적언어의 차이가 궁금해지기 시작하니,&lt;/li&gt;
&lt;li&gt;개발의 시초, 하드웨이 적인 면까지 공부하려는 자세를 갖게 되었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  타인이 작성한 글을 보고 대하는 자세&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;열등감에 휩싸여 다른 타인이 작성한 글을 보면 잘 받아드리지 못했습니다. 멍청한 사람들의 특징이라고 합니다. 인정하고 싶지 않았지만 제가 그랬었어요&lt;/li&gt;
&lt;li&gt;하지만 이상하게 코드를 보는 것은 무섭지 않았습니다. 왜냐하면 프로그래머스에서 코드를 작성하면 항상 다른 사람 풀이 를 보면서 내 풀이를 비교하며 차이점을 작성하던 버릇이 있었기 때문입니다.&lt;/li&gt;
&lt;li&gt;이것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;도전&lt;/b&gt;에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;두려움&lt;/b&gt;의 차이었습니다.&lt;/li&gt;
&lt;li&gt;부캠에서 동료들의 gist를 보면 내가 얼마나 못하고 내가 더욱 성장해야 겠구나를 gist를 볼때마다 느꼈기 때문에 우울하면서 억울하고 기분이 좋지많은 않았기 때문입니다.&lt;/li&gt;
&lt;li&gt;하지만 부캠을 하면서 다양한 동료들의 gist를 보고 내가 몰랐던 메소드나 놓쳤던 로직이나 요구사항들을 발견하는 것이 반복되면서 내 위치를 인정하게 되고 더욱 배워야할 것들이 많구나를 느꼈습니다.&lt;/li&gt;
&lt;li&gt;그래서 요즘은 기술 블로그를 보며 지나치지 않고 바로 곧장 읽어봅니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;제 위치를 인정했기 때문입니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근 저는 플라톤의 동굴의 우화를 인생 철학처럼 여기고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;인간은 본인이 넓힌 스펙트럼 안에서만 활동한다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 말처럼, 더 큰 세상을 보기 위해서는 먼저 내 생각의 경계부터 넓혀야 한다는 걸 느꼈고, 더 큰 세상을 보고 싶어서 과감히 새로운 경험에도 도전하려하였습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 항상 지역 안에서만 경쟁하고 활동했기에, 세상을 &amp;lsquo;지역&amp;rsquo;이라는 프레임으로만 바라봤습니다. 하지만 이제는 전국으로, 더 나아가 해외로도 나아가고 싶다는 욕심과 용기가 생겼습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 여행을 통해 낯선 곳을 경험하고 견문을 넓히듯, 자기계발 역시 하나의 인생 여행이라 생각합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;낯선 도전, 새로운 배움, 그 모든 과정이 결국 나를 더 넓은 세상으로 이끄는 여정이니까요.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;함께의-가치&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✨ 함께의 가치&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다 이번 부트캠프에서 가장 크게 느낀 점은 바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lsquo;함께&amp;rsquo;라는 가치&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;굳이 부트캠프에 들어가야 해?&amp;rdquo; 라는 질문을 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 저는 이렇게 말하고 싶습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;우리는 인간이기에, 서로에게 동기부여를 받고 영향을 받는 존재입니다.&lt;br /&gt;부트캠프는 단순한 교육이 아닌, &lt;b&gt;서로를 성장시키는 캠프&lt;/b&gt;입니다.&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;혼자였다면 도달하지 못했을 지금의 나는, 열정적인 동료들 덕분에 매일 과제를 해결해낼 수 있었고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;힘들 줄만 알았던 과정에서 오히려 저의 엔진이 다시 불타올랐습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래서 저는 앞으로도, 졸업 전까지 이렇게 모두가 전력질주하는 공간에서 한 번 더 부딪히고 싶다는 강한 바람이 생겼습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 네부캠은 비록 베이직까지만 참여했지만, 이 경험을 발판 삼아 더 높은 도전을 향해 나아가고 싶습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;-후회-없는-선택&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp; 후회 없는 선택&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2주간 거의 쉬지 않고 몰입해서 문제를 풀고 공부했던 시간은 육체적으로 힘들었지만, 정신적으로는 성장의 폭이 엄청났습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고3 이후 이렇게 깊게 몰입해서 공부해본 건 정말 오랜만이었고, 그만큼 이 경험은 저의 개발 인생의 반환점이라고 말할 수 있을 정도로 소중했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비록 코딩 테스트에서는 컷오프를 당했지만, 통과한 동료들이 더 높은 곳에서 빛나고 있을 거라 믿으며 진심으로 축하의 박수를 보냅니다.  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&amp;ldquo;JS 안 하고 Kotlin을 선택한 걸 후회하지 않나요?&amp;rdquo;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;후회하지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오히려 JS라는 작은 동굴 밖으로 나올 수 있게 해준 게 Kotlin이었기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 누가 알았을까요?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2주간의 네부캠이 지금까지도 제 삶에 깊은 동기로 작용하고 있다는 걸.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;혹시 지금, 개발이 아니더래도, 무언가에 대한 열정이 식을까 두렵거나 어딘가에 도전하기 망설이고 있다면&amp;hellip;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 글이 당신에게 작은 불씨가 되었으면 좋겠네요 .&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;짧지만 굵었던 네부캠 베이직, 고생많았다-! &lt;/p&gt;</description>
      <category>회고</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/2</guid>
      <comments>https://happy7yong.tistory.com/2#entry2comment</comments>
      <pubDate>Sun, 26 Oct 2025 15:19:39 +0900</pubDate>
    </item>
    <item>
      <title>멀티스레드 환경에서 index++ 는 왜 위험한가?</title>
      <link>https://happy7yong.tistory.com/1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring으로 커뮤니티 백엔드 코드를 작성하던 중, DB를 사용하지 못하는 환경이었기 때문에 LinkedHashMap을 통해 key : index, value : post(게시글)엔티티 형태로 인&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;메모리 데이터 저장소를 구성하였다. &lt;br /&gt;&lt;/span&gt;이때, 게시글의 식별자를 직접 관리하기 위해 index++연산을 사용하여 새로운 게시글이 생성될 때마다 ID를 1씩 증가시키는 방식으로 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 방식은 단일 스레드에서는 정상적으로 동작하지만, 여러 사용자가 동시에 게시글을 등록하는 &lt;b&gt;멀티스레드 환경&lt;/b&gt;에서는 동일한 index값이 중복으로 부여되어 &lt;b&gt;ID 충돌(중복)&lt;/b&gt;이 발생할 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class InMemoryPostRepository implements PostRepository {
    private long index; 
    //Map&amp;lt;Key : index, Value : PostEntity&amp;gt;
    private Map&amp;lt;Long, PostEntity&amp;gt; Posts = new LinkedHashMap&amp;lt;&amp;gt;();
    
    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동시성(Concurrency)과 원자성(Atomicity)&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;  멀티스레드 환경이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;하나의 프로세스 내에서 두개 이상의 스레드(실행 흐름)가 동시에 실행되는 환경&lt;/span&gt;&lt;/p&gt;
&lt;b&gt;&lt;br /&gt;&lt;br /&gt;  동시성(Concurrency)이란?&lt;br /&gt;&lt;/b&gt;여러 작업이 동시에 수행되는 것처럼 보이지만,&lt;br /&gt;실제로는 CPU가 작업을 번갈아가며 수행한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;+ 병렬성 vs 동시성&lt;/b&gt;&lt;br /&gt;&lt;b&gt;여러 코어에서&lt;/b&gt; &lt;b&gt;동시에 작업을 수행되는 상태&lt;/b&gt;는 &lt;b&gt;병렬성(Parallelism)&lt;/b&gt;이라고 하며&lt;br /&gt;한정된 자원을 &lt;b&gt;여러 스레드가 번갈아가며 처리하는 방식&lt;/b&gt;을 &lt;b&gt;동시성(Concurrency)&lt;/b&gt;라고 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 여러 스레드가 번갈아가며 수행(동시성)하는 과정에서, &lt;b&gt;동시에 같은 자원에 접근&lt;/b&gt;하게 된다면 어떤 일이 발생할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;u&gt;&lt;b&gt;서로 다른 스레드가 동일한 공유 자원(Shared Resource)에 접근하여 데이터를 읽거나 쓰는데 충돌이 발생할 수 있는 문제&lt;/b&gt;&lt;/u&gt;를&lt;b&gt; 동시성 문제(Concurrency Issue)&lt;/b&gt; 라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;하나의 연산 과정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, &lt;b&gt;프로세스가 수행하는 하나의 연산 과정&lt;/b&gt;은 예시를 들어 아래와 같이 진행된다.&lt;/p&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;long count = 0;
count++;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;count++의 내부 동작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. count 값을 읽는다. [0] -&amp;gt; &lt;b&gt;읽기(Read)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. count + 1 연산을 수행한다. [1] -&amp;gt; &lt;b&gt;연산(Compute)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. count 값을 저장한다. [1] -&amp;gt; &lt;b&gt;쓰기(Write)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 3단계를 이룰 때, 이 전체 과정은&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 원자성(Atomic)&lt;/b&gt;&lt;/span&gt;을 가지고 있어야 한다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; &amp;nbsp;원자성이란?&lt;/b&gt;&lt;br /&gt;하나의 작업은 더이상 쪼갤 수 없으며, 하나의 연산은 인터럽트 없이 진행되어야한다.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;: 원자성과 트랜잭션 간단한 차이점&lt;/b&gt;&lt;br /&gt;&lt;b&gt;원자성 :&lt;/b&gt; 연산 단위&lt;br /&gt;&lt;b&gt;트랜잭션 :&lt;/b&gt; 업무 단위&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드의 환경을 가진&lt;b&gt; 멀티 스레드 환경&lt;/b&gt;에서 발생할 수 있는 &lt;b&gt;동시성 문제 시나리오&lt;/b&gt;를 상상해본다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;495&quot; data-start=&quot;450&quot;&gt;A 스레드가 ① &lt;b&gt;읽기(0)&lt;/b&gt; 후, ② &lt;b&gt;연산(1)&lt;/b&gt; 을 수행하기 직전&lt;/li&gt;
&lt;li data-end=&quot;544&quot; data-start=&quot;496&quot;&gt;B 스레드가 거의 동시에 ① 읽기(0) &amp;rarr; ② 연산(1) &amp;rarr; ③ 쓰기(1)까지 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 A 스레드가 연산을 진행하고, 쓰기 과정까지 거치게 될 경우, B스레드가 저장했던 1의 값을 덮기 때문에 결과적으로는 count는 1의 값을 갖는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;동시성 문제(Concurrency Issue)&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;를 띄게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 , count++는 &lt;b&gt;원자적으로 보장되지 않는다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멀티스레드 환경에서 index++는 왜 안전하지 않은가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 개념을 바탕으로 long타입과 LinkedHashMap에서 발생할 수 있는 동시성 문제를 알아보도록 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 게시글 생성 메서드이다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public Post postCreate(User user, PostCreateFormDto postCreateFormDto) {
    //1. ID 생성 : 동시성 문제 발생 위험 
    long postId = index++;
    
    //2. 게시글 객체 생성
    Post post = new Post(
        postId,
        postCreateFormDto.getTitle(),
        postCreateFormDto.getContent(),
        user.getUserId(),
        LocalDateTime.now()
    );

	//3. Posts Map에 게시글 저장
    Posts.put(postId, post);

    return post;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드에서 postCreate를 호출할 경우, 서로 다른 스레드가 동시에 index가 0인 상태에서 각각 증가 연산을 수행하기 때문에, 동일한 ID(0, 1)가 중복 생성될 수 있다. 이후, 서로 다른 객체가 동일한 index를 발급받을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Map에서 &lt;b&gt;index는 사실상 데이터베이스의 &lt;u&gt;PK&lt;/u&gt; 역할을 수행하기 때문에, 중복은 곧 &lt;u&gt;데이터 무결성 위반&lt;/u&gt;&lt;/b&gt;으로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제를 해결하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;long 타입의 index를 atomicLong 타입으로 바꾸어서 index++ 연산 자체에 원자성을 갖도록 한다면 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AtomicLong과 CAS를 이용한 개선&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  atomicLong&lt;/b&gt;이란?&amp;nbsp;&lt;br /&gt;하나의 연산 작업을 원자적이게 진행해주는 클래스(타입)&lt;br /&gt;&lt;b&gt;&amp;nbsp;volatile + CAS&lt;/b&gt;로 결합되어 연산작업이 원자성을 보장할 수 있도록 하여주는 타입이다.&lt;br /&gt;&lt;br /&gt;  &lt;b&gt;CAS&lt;/b&gt; 란?&lt;br /&gt;Compare And Set(Compare And Swap)&amp;nbsp;&lt;br /&gt;예상 값과 비교 후 같으면 set, 다르면 갱신한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AtomicLong의 내부 메소드 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. getAndIncrement (반환 후 증가)&lt;/p&gt;
&lt;pre id=&quot;code_1761454102358&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public final long getAndIncrement() {
        return U.getAndAddLong(this, VALUE, 1L);
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index++ 을 대신하여 사용할 메서드인 getAndIncrement의 핵심 로직인 getAndAddLong의 내부는 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761454124710&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final long getAndAddLong(Object o, long offset, long delta) {
    long v;
    do {
        v = getLongVolatile(o, offset);  // ① 현재 값 읽기 (volatile로 최신 값 보장)
    } while (!weakCompareAndSetLong(o, offset, v, v + delta)); 
			    // ② CAS 시도, 실패하면 반복
    return v;  // ③ 원래 값 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile을 통해 현재 값을 읽었을때, 값이 전과 달라지지 않았다면(예상과 같다면) 원래 값을 반환하고, 다르다면 실패하여 반복문이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;volatile을 사용하는데 차라리 Syncronized를 사용해서 전체 연산에 lock을 걸면 더 안전했을 텐데&lt;br /&gt;&lt;u&gt;&lt;b&gt;굳이 반복문 로직을 선택한 이유&lt;/b&gt;&lt;/u&gt;가 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 방식으로 synchronized 블럭을 통해 구현해볼수있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761454395592&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;synchronized(this) {
    index += delta;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;b&gt;lock이 걸려있다는 것&lt;/b&gt;은&lt;u&gt;&lt;b&gt; 다른&amp;nbsp;&lt;/b&gt;&lt;b&gt;스레드가 대기 상태에 놓인다&lt;/b&gt;&lt;/u&gt;는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드가 동시에 공유자원에 접근하려 할때, 하나의 스레드만 lock 을 점유하고 나머지는&lt;b&gt; 블로킹(Blocking)&lt;/b&gt; 상태에 빠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서&lt;u&gt;&lt;b&gt; 스레드 스위칭(Context Switching) 비용&lt;/b&gt;&lt;/u&gt;이 발생하고, 경쟁이 심할 수록 전체 성능이 저하된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS는 실패하더라도 다른 스레드의 작업을 기다리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &quot;값이 다르네? 다시 시도하자&quot;는 식으로 루프를 반복하기 때문에,&lt;b&gt; lock-free(비차단) 구조&lt;/b&gt;라고 부른다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;378&quot; data-start=&quot;314&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  컨텍스트 스위치&lt;/b&gt;이란?&lt;br /&gt;CPU 코어는 한번에 하나의 스레드만 실행될 수 있다.&lt;br /&gt;이때, 멀티스레드에서 여러 스레드를 실행시키기 위해서는 스레드를 교체가 일어난다.&lt;br /&gt;해당&amp;nbsp;&lt;b&gt;스레드를 교체하는 과정&lt;/b&gt;을&lt;b&gt;&amp;nbsp;Context Switching 문맥 교환&lt;/b&gt;라고 한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;  컨텍스트 스위치 비용&lt;/b&gt;이란?&lt;br /&gt;CPU가 실제로 연산를 수행하지 않고, 스레드를 교체하기 위해 &lt;b&gt;상태를 저장하고 복원하는 데 소비되는 시간&lt;/b&gt;을 말한다.&lt;br /&gt;&lt;br /&gt;즉, 스레드를 바꾸는 동안 CPU는 &amp;ldquo;일&amp;rdquo;을 하지 않기 때문에이 시간 자체가 성능 손실(비용)로 작용한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✨ 개선사항이 반영된 코드 (AtomicLong 적용)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1761454625808&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   private AtomicLong postId = new AtomicLong(0);
   private Map&amp;lt;Long, PostEntity&amp;gt; Posts = new LinkedHashMap&amp;lt;&amp;gt;();
   
   public Post postCreate(User user, PostCreateFormDto postCreateFormDto) {
   	
    //값 반환 후 id값 증가 : i++
    long id = postId.getAndIncrement();
    
    //게시글 객체 생성
    Post post = new Post(
        id,
        postCreateFormDto.getTitle(),
        postCreateFormDto.getContent(),
        user.getUserId(),
        LocalDateTime.now()
    );

	//3. Posts Map에 게시글 저장
    Posts.put(post.getPostId(), post);

    return post;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Trouble Shooting/JAVA</category>
      <author>happy7yong</author>
      <guid isPermaLink="true">https://happy7yong.tistory.com/1</guid>
      <comments>https://happy7yong.tistory.com/1#entry1comment</comments>
      <pubDate>Sun, 26 Oct 2025 14:41:34 +0900</pubDate>
    </item>
  </channel>
</rss>