<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>sjindev 님의 블로그</title>
    <link>https://sjindev.tistory.com/</link>
    <description>sjindev 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 11 May 2026 18:51:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sjindev</managingEditor>
    <image>
      <title>sjindev 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8080602/attach/d1258e193c644befb2674e51e47fac8d</url>
      <link>https://sjindev.tistory.com</link>
    </image>
    <item>
      <title>2025-2026 겨울방학 회고</title>
      <link>https://sjindev.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 3학년이 끝나고 4학년을 맞이하기 전 마지막 방학이 왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 3학년 2학기 드림학기제를 진행하며 9학점을 받게되어 전공은 2과목만 들었고, 덕분에 조금 여유로운 학기를 보낼 수 있었습니다.&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;lt;---- 그 이유는 아래서 설명하겠습니다...&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;일단, 학점도 커리어하이(그래도 드학9학점에 4.5를 맞지못한 아쉬움은 남지만..)였고 무엇보다 좋은건 이번 학기가 끝나면 일주일뒤에 나는 유럽으로 떠난다는 기대감에 부풀어있었습니다!!&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;2025. 12. 27~ 2026. 01. 12&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기다리던 유럽여행. 사실상 2학기 드림학기제를 통해 한가로웠던 시간들을 여행준비에 쏟은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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/dKZRRq/dJMcafe8iqV/9MNJ859crYOVRcSXNU0kM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKZRRq/dJMcafe8iqV/9MNJ859crYOVRcSXNU0kM0/img.png&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;778&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; width=&quot;453&quot; height=&quot;339&quot; style=&quot;width: 52.3586%; margin-right: 10px;&quot; data-widthpercent=&quot;52.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKZRRq/dJMcafe8iqV/9MNJ859crYOVRcSXNU0kM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKZRRq%2FdJMcafe8iqV%2F9MNJ859crYOVRcSXNU0kM0%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;1041&quot; height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LrMt6/dJMcaipmp2u/EQUNJvO8TuO3nOlLolbDs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LrMt6/dJMcaipmp2u/EQUNJvO8TuO3nOlLolbDs0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;884&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; width=&quot;389&quot; height=&quot;328&quot; style=&quot;width: 46.4787%;&quot; data-widthpercent=&quot;47.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LrMt6/dJMcaipmp2u/EQUNJvO8TuO3nOlLolbDs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLrMt6%2FdJMcaipmp2u%2FEQUNJvO8TuO3nOlLolbDs0%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;1050&quot; height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기다리고 기다렸던.. 중국국제항공을 타고 인천-&amp;gt; 베이징(2h) -&amp;gt; 런던 히드로 공항(12h였나?)으로 총 14시간에 거쳐 날아갔습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앉아서 자는걸 못해서 비행기나 대중교통에서 워낙 잠을 못자요.. 5억년버튼 12시간 버전 체험했습니다 진짜..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여자친구랑 같이갔는데 여자친구는 저랑 정 반대여서 12시간동안 거의 풀수면을 하더군요. 정말 힘들었습니다...ㅋㅋㅋ&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;지하철을 탔는데 흠,,,대한민국 지하철이 짱인거같아요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 놀라운 사실&lt;/b&gt; : 런던은 세계최초로 지하철 개통(1863년)을 했습니다. 근데 그 당시 대한민국 땅에서는 대동여지도(1861년)가 만들어졌습니다.. 근데 결국 너무 일찍만들어졌다보니 지금까지도 좀 노후화되어있는거같아요. 좁고..냄새나고.. 별로임 ㅜㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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/bdyPXb/dJMcagd0Y7X/VbfVAkK47Iy3wrkCQRUGKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdyPXb/dJMcagd0Y7X/VbfVAkK47Iy3wrkCQRUGKk/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdyPXb/dJMcagd0Y7X/VbfVAkK47Iy3wrkCQRUGKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdyPXb%2FdJMcagd0Y7X%2FVbfVAkK47Iy3wrkCQRUGKk%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJEaiV/dJMcab4PbTv/cdpHQvRFqgcMo8DezpsFJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJEaiV/dJMcab4PbTv/cdpHQvRFqgcMo8DezpsFJ0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJEaiV/dJMcab4PbTv/cdpHQvRFqgcMo8DezpsFJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJEaiV%2FdJMcab4PbTv%2FcdpHQvRFqgcMo8DezpsFJ0%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;1050&quot; height=&quot;1400&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;빅벤과 타워브릿지입니다.&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;그냥 여자친구 사진 찍어준답시고 발밑에 잠시 내려두고 분명 제 시야에도 있었는데, 찍어주고 잠시 사진확인시켜줄겸 여자친구랑 대화나눈 약 10초새 감쪽같이...사라졌어요 걍 흔적도없이!!&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;무튼 런던에서 2박3일 야무지게 놀고 파리로 넘어가려는데..&lt;/b&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/b1gnVO/dJMcabRhHV4/7tRVA6BCDrrGPKc6NkZHR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1gnVO/dJMcabRhHV4/7tRVA6BCDrrGPKc6NkZHR1/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 61.1849%; margin-right: 10px;&quot; data-widthpercent=&quot;61.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1gnVO/dJMcabRhHV4/7tRVA6BCDrrGPKc6NkZHR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1gnVO%2FdJMcabRhHV4%2F7tRVA6BCDrrGPKc6NkZHR1%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6scfJ/dJMcahYg4HR/REyCaxsuQ8K4DL38lyk5k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6scfJ/dJMcahYg4HR/REyCaxsuQ8K4DL38lyk5k0/img.png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot; data-is-animation=&quot;false&quot; style=&quot;width: 37.6523%;&quot; data-widthpercent=&quot;38.1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6scfJ/dJMcahYg4HR/REyCaxsuQ8K4DL38lyk5k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6scfJ%2FdJMcahYg4HR%2FREyCaxsuQ8K4DL38lyk5k0%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;648&quot; height=&quot;1404&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;갑자기 역에서 방송이 나왔어요. 오늘 열차 전부 취소니까 역에서 전부 나가라고.. 그리고 역사앞에 막 방송국 기자들 와서 뉴스찍고있고 진짜 개구라 같은 날이었어요.&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;/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;근데, 공항에서 표가 있긴한데 아침에 도착하는 경유 항공권에 편도 120만원이라더라구요. 제가 유럽 한국 왕복을 95만원에 끊었는데 이게 무슨 개소리. 진짜 눈물머금고 유로스타 오후에는 복구되겠지? 제발 되라 싶은 마음으로 저녁에 출발하는 유로스타 취소표를 인당 60 만원에 결제하고 다시 기차역으로 돌아갔습니다..&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;lt;-&amp;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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgZFPq/dJMcajhrWK5/UmDssOd1tCRmEyzKjR5oHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgZFPq/dJMcajhrWK5/UmDssOd1tCRmEyzKjR5oHK/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; width=&quot;377&quot; height=&quot;503&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgZFPq/dJMcajhrWK5/UmDssOd1tCRmEyzKjR5oHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgZFPq%2FdJMcajhrWK5%2FUmDssOd1tCRmEyzKjR5oHK%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kF8CK/dJMcagrwLeH/XOlnbUF8d1jAPK2AqG5nO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kF8CK/dJMcagrwLeH/XOlnbUF8d1jAPK2AqG5nO0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kF8CK/dJMcagrwLeH/XOlnbUF8d1jAPK2AqG5nO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkF8CK%2FdJMcagrwLeH%2FXOlnbUF8d1jAPK2AqG5nO0%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;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;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/bKyrlU/dJMcac3Ifw8/NhRkkqbScmMSwWxf7PWke1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKyrlU/dJMcac3Ifw8/NhRkkqbScmMSwWxf7PWke1/img.png&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1600&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.3857%; margin-right: 10px;&quot; data-widthpercent=&quot;42.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKyrlU/dJMcac3Ifw8/NhRkkqbScmMSwWxf7PWke1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKyrlU%2FdJMcac3Ifw8%2FNhRkkqbScmMSwWxf7PWke1%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;901&quot; height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqE3pv/dJMcafTHRGd/46ASiBS0XR2HKZO6MskC40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqE3pv/dJMcafTHRGd/46ASiBS0XR2HKZO6MskC40/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.4515%;&quot; data-widthpercent=&quot;57.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqE3pv/dJMcafTHRGd/46ASiBS0XR2HKZO6MskC40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqE3pv%2FdJMcafTHRGd%2F46ASiBS0XR2HKZO6MskC40%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;p data-ke-size=&quot;size16&quot;&gt;이후 파리에서도 여러 일정도 전부 소화하고 &lt;b&gt;파리 -&amp;gt; 스위스(인터라켄)&lt;/b&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 floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diltwr/dJMcajobsY9/T3SnkvmceMBheZjHKrjqf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diltwr/dJMcajobsY9/T3SnkvmceMBheZjHKrjqf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diltwr/dJMcajobsY9/T3SnkvmceMBheZjHKrjqf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdiltwr%2FdJMcajobsY9%2FT3SnkvmceMBheZjHKrjqf0%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;302&quot; height=&quot;1400&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&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;하아..그리고 매일같이 파리 숙소에 묵을때 아침 몇시에 먹을거냐 물어봐주시고 차려주신 식사가 아직도 그립습니다. 오븐에 구운 크로와상에 베이컨 + 치즈 + 맛있는버터 + 오렌지주스 + 요거트 + 블랙커피까지....매일같이 차려주시고 떠나는날까지 챙겨주셔서 너무 감사했는데, 혹시 파리가실분 계시면 제가 숙소 추천해드릴게요 짱짱!&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;&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&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0CVyP/dJMcaduKktt/An7Tk91R8u0H5sosVvA2u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0CVyP/dJMcaduKktt/An7Tk91R8u0H5sosVvA2u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0CVyP/dJMcaduKktt/An7Tk91R8u0H5sosVvA2u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0CVyP%2FdJMcaduKktt%2FAn7Tk91R8u0H5sosVvA2u0%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;372&quot; height=&quot;661&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1600&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;그리고 스위스에 도착하였습니다! 인터라켄 West 역에 내려서 찍은 하늘사진인데, 저당시 영하 17도였나 그랬어요 ㅠㅠ 무척 추운 스위스..&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;&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;&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;&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;[융프라우 방문]&lt;/b&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/Pi0bE/dJMcaju0cle/J71DTTmdgoZfmP7YuPdzL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pi0bE/dJMcaju0cle/J71DTTmdgoZfmP7YuPdzL0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pi0bE/dJMcaju0cle/J71DTTmdgoZfmP7YuPdzL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPi0bE%2FdJMcaju0cle%2FJ71DTTmdgoZfmP7YuPdzL0%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5JG7l/dJMcaju0cli/nVXO3BcLsfIWuhf3gwqUQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5JG7l/dJMcaju0cli/nVXO3BcLsfIWuhf3gwqUQ1/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5JG7l/dJMcaju0cli/nVXO3BcLsfIWuhf3gwqUQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5JG7l%2FdJMcaju0cli%2FnVXO3BcLsfIWuhf3gwqUQ1%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&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;사실 저기서 고산병걸렸어요. 호흡을해도 숨이안쉬어지고 어지럽고 핑돌고 진짜 죽을거같았는데, 그래도 남는건 사진이니까..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객기부려보겠다고 영하 23도에서 반팔샷 찍었습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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/xtbK7/dJMcadarMKt/T0K4EpPkdti0M08yVX0AS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xtbK7/dJMcadarMKt/T0K4EpPkdti0M08yVX0AS0/img.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;1392&quot; data-is-animation=&quot;false&quot; width=&quot;356&quot; height=&quot;407&quot; data-widthpercent=&quot;53.85&quot; style=&quot;width: 53.22%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xtbK7/dJMcadarMKt/T0K4EpPkdti0M08yVX0AS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxtbK7%2FdJMcadarMKt%2FT0K4EpPkdti0M08yVX0AS0%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;1218&quot; height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpUklr/dJMcaadNxr0/RyeLEmZZWMr98iMcviVyh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpUklr/dJMcaadNxr0/RyeLEmZZWMr98iMcviVyh0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.6172%;&quot; data-widthpercent=&quot;46.15&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpUklr/dJMcaadNxr0/RyeLEmZZWMr98iMcviVyh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpUklr%2FdJMcaadNxr0%2FRyeLEmZZWMr98iMcviVyh0%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패러글라이딩도 재밌었습니다! 2023년에 스위스에서 패러글라이딩 하려고 했었는데 당시에 바람이 너무 많이 불어서 전액환불 당하고 취소당했었어요 ㅠㅠ 이번엔 무조건 하고싶었는데 운이좋아 성공했습니다! 그치만 너무 비쌌어요..(15분정도 탔는데 영상 포함 약 42만원..?)&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2LOuP/dJMcaiQnBLi/9Xzs7yNJk2PHwHj8u6sFz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2LOuP/dJMcaiQnBLi/9Xzs7yNJk2PHwHj8u6sFz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2LOuP/dJMcaiQnBLi/9Xzs7yNJk2PHwHj8u6sFz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2LOuP%2FdJMcaiQnBLi%2F9Xzs7yNJk2PHwHj8u6sFz0%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;438&quot; height=&quot;1400&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&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;이후 루체른으로 넘어가서 예쁜 풍경 마저 감상해주고 다음날 이탈리아로 넘어가기 위해 휴식을 하였습니다.&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;&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;&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;&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;[이탈리아!] 밀라노 -&amp;gt; 피렌체 -&amp;gt; 로마 코스&lt;/b&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/O8cwM/dJMcagrwLFe/YHHW2oYLaSjbfjTzv7qKDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O8cwM/dJMcagrwLFe/YHHW2oYLaSjbfjTzv7qKDK/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; width=&quot;274&quot; height=&quot;365&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O8cwM/dJMcagrwLFe/YHHW2oYLaSjbfjTzv7qKDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO8cwM%2FdJMcagrwLFe%2FYHHW2oYLaSjbfjTzv7qKDK%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/th0dK/dJMcaaLC1dW/eZkNsFIaF8iCAZnuLAb62K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/th0dK/dJMcaaLC1dW/eZkNsFIaF8iCAZnuLAb62K/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; width=&quot;405&quot; height=&quot;540&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/th0dK/dJMcaaLC1dW/eZkNsFIaF8iCAZnuLAb62K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fth0dK%2FdJMcaaLC1dW%2FeZkNsFIaF8iCAZnuLAb62K%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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&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/c61lGY/dJMcah43NlK/QP9TUPeaQb5HiJdkzABSV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c61lGY/dJMcah43NlK/QP9TUPeaQb5HiJdkzABSV1/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; width=&quot;503&quot; height=&quot;671&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c61lGY/dJMcah43NlK/QP9TUPeaQb5HiJdkzABSV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc61lGY%2FdJMcah43NlK%2FQP9TUPeaQb5HiJdkzABSV1%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sDUyv/dJMcafTHSht/JAiPnFkGHJQAYh5Krjyapk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sDUyv/dJMcafTHSht/JAiPnFkGHJQAYh5Krjyapk/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; width=&quot;489&quot; height=&quot;652&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sDUyv/dJMcafTHSht/JAiPnFkGHJQAYh5Krjyapk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsDUyv%2FdJMcafTHSht%2FJAiPnFkGHJQAYh5Krjyapk%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피렌체로 넘어가서 예쁜 두오모도 봐줬습니다. 실제로보니 정말 웅장하더라구요. &lt;b&gt;140년에 걸친 &lt;/b&gt;노력 ㅇㅈ한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecmwhf/dJMcacWXni5/jxElpoyf4fVTOvRKDgvIZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecmwhf/dJMcacWXni5/jxElpoyf4fVTOvRKDgvIZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecmwhf/dJMcacWXni5/jxElpoyf4fVTOvRKDgvIZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fecmwhf%2FdJMcacWXni5%2FjxElpoyf4fVTOvRKDgvIZk%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;587&quot; height=&quot;440&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;/figure&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&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/cGcL4m/dJMcai3TGL3/kjcV7OjWsZJFr6sGeqoUl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGcL4m/dJMcai3TGL3/kjcV7OjWsZJFr6sGeqoUl1/img.png&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1411&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4128%; margin-right: 10px;&quot; data-widthpercent=&quot;49.99&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGcL4m/dJMcai3TGL3/kjcV7OjWsZJFr6sGeqoUl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGcL4m%2FdJMcai3TGL3%2FkjcV7OjWsZJFr6sGeqoUl1%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;1058&quot; height=&quot;1411&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X2lZg/dJMcacCGAjo/lKpj3QKR8JbrKX0EqYvX60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X2lZg/dJMcacCGAjo/lKpj3QKR8JbrKX0EqYvX60/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4244%;&quot; data-widthpercent=&quot;50.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X2lZg/dJMcacCGAjo/lKpj3QKR8JbrKX0EqYvX60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX2lZg%2FdJMcacCGAjo%2FlKpj3QKR8JbrKX0EqYvX60%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;이후 로마로 넘어갑니다!&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/dzU0Ns/dJMcag59aCy/kCmBZb8woFOqDwZCwAOG8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzU0Ns/dJMcag59aCy/kCmBZb8woFOqDwZCwAOG8k/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzU0Ns/dJMcag59aCy/kCmBZb8woFOqDwZCwAOG8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzU0Ns%2FdJMcag59aCy%2FkCmBZb8woFOqDwZCwAOG8k%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uDKgw/dJMcagSCa1E/hQzKLwwIkEUtfTORir5Awk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uDKgw/dJMcagSCa1E/hQzKLwwIkEUtfTORir5Awk/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uDKgw/dJMcagSCa1E/hQzKLwwIkEUtfTORir5Awk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuDKgw%2FdJMcagSCa1E%2FhQzKLwwIkEUtfTORir5Awk%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;왜냐면,,지금 우측 판테온 사진만 보면 밤인거 같으시겠지만 실제로는 오후 4시정도입니다. 5시 되면 해가 아예 내려가고 4시만 지나도 저렇게 시퍼렇게 어둑어둑 해져요 ㅠㅠ 그래서 하루일정이 타이트하고 밤에 유럽 치안도안좋고 하니까 하루에 사용할 수 있는 시간이 많지 않습니다&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdnsMw/dJMb99MGjhC/KhypwhO3qpO344okf0LgrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdnsMw/dJMb99MGjhC/KhypwhO3qpO344okf0LgrK/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdnsMw/dJMb99MGjhC/KhypwhO3qpO344okf0LgrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdnsMw%2FdJMb99MGjhC%2FKhypwhO3qpO344okf0LgrK%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXpCq/dJMb99MGjhE/cMzwTKnPYkCKqwyyWFazZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXpCq/dJMb99MGjhE/cMzwTKnPYkCKqwyyWFazZ0/img.png&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXpCq/dJMb99MGjhE/cMzwTKnPYkCKqwyyWFazZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXpCq%2FdJMb99MGjhE%2FcMzwTKnPYkCKqwyyWFazZ0%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;1050&quot; height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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&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/p0CN2/dJMcabKy5gq/nsm3jvBidMakdNYE5kQYZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p0CN2/dJMcabKy5gq/nsm3jvBidMakdNYE5kQYZK/img.png&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1600&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p0CN2/dJMcabKy5gq/nsm3jvBidMakdNYE5kQYZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp0CN2%2FdJMcabKy5gq%2Fnsm3jvBidMakdNYE5kQYZK%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;901&quot; height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7OxfQ/dJMcabKy5gt/TTqdnAl0hlaegWAYzwT5SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7OxfQ/dJMcabKy5gt/TTqdnAl0hlaegWAYzwT5SK/img.png&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1600&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7OxfQ/dJMcabKy5gt/TTqdnAl0hlaegWAYzwT5SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7OxfQ%2FdJMcabKy5gt%2FTTqdnAl0hlaegWAYzwT5SK%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;901&quot; height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;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;/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;/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;- 2월 초에 봤던 &lt;b&gt;정보처리기사 필기 시험 합격&lt;/b&gt;했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 진행중인 프로젝트 '코르크차지' 리뉴얼 버전에 대한 구현을 QA 전 단계까지 새롭게 마무리 하였습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2월말부터 준비했던 &lt;b&gt;Opic&lt;/b&gt; 영어 시험은 3월 첫주에 시험을 봐서 1트 &lt;b&gt;IH&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;- 처음에 말했듯 드림학기제를 진행했던 3-2학기 한가로운 시간부터 내가 바로 다음주에 코테를 본다 라는 마인드를 장착하고 최소 주당 3PS 를 했으면 얼마나 좋았을까. 지금 상반기 취준하면서 많이 느낍니다. 아 나에게 서류 합격이라는 기회가 와도 코테를 정복하지 못하면 아무 의미가없구나!!!!!!!!!!!!그래서 지금부터라도 열심히 하고 있어요 ㅠㅠ&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 잘 놀았고 가장 이룬것도 많았다! 후회는 남지만 앞으로 어떻게 해야할지 배웠으니 이제부터 더 잘해보자 라는 마음으로 한학기를 또 열심히 살아보겠습니다.&lt;/p&gt;</description>
      <category>겨울방학</category>
      <category>여행</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/25</guid>
      <comments>https://sjindev.tistory.com/25#entry25comment</comments>
      <pubDate>Tue, 7 Apr 2026 17:54:43 +0900</pubDate>
    </item>
    <item>
      <title>[React] 사용자 경험을 극대화하는 '낙관적 업데이트(Optimistic Update)'와 주의할 점</title>
      <link>https://sjindev.tistory.com/24</link>
      <description>&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 &quot;서버 응답을 기다리는 짧은 시간조차 사용자에게는 지루함이 될 수 있다&quot;는 고민을 하게 되었다. 특히 좋아요 버튼, 삭제, 수정처럼 사소하고 빈번하게 일어나는 작업에서 더더욱... 오늘은 진행 중인 프로젝트 '코르크차지(CorkCharge)'의 그룹 관리 기능을 구현하며 적용한 &lt;b data-index-in-node=&quot;169&quot; data-path-to-node=&quot;3&quot;&gt;낙관적 업데이트&lt;/b&gt;와 그 과정에서 마주친 &lt;b data-index-in-node=&quot;190&quot; data-path-to-node=&quot;3&quot;&gt;Race Condition(경쟁 상태)&lt;/b&gt; 문제 해결 과정을 적어보려고 한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;1. 낙관적 업데이트(Optimistic Update)란?&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;낙관적 업데이트는 &quot;서버로 보낸 요청이 성공할 것이라고 &lt;u&gt;낙관적으로 가정&lt;/u&gt;&quot;하고, 서버 응답이 오기 전에 &lt;b data-index-in-node=&quot;61&quot; data-path-to-node=&quot;5&quot;&gt;UI를 먼저 업데이트&lt;/b&gt;하는 기법이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;일반적인 방식:&lt;/b&gt; 요청 &amp;rarr; 대기(Loding) &amp;rarr; 응답 완료 &amp;rarr; UI 업데이트 (느린 네트워크에서 답답함 유발)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;낙관적 업데이트:&lt;/b&gt; 요청 &amp;rarr; &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;6,1,0&quot;&gt;UI 즉시 업데이트&lt;/b&gt; &amp;rarr; 응답 완료 &amp;rarr; (실패 시) 롤백&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;2. 문제의 코드: &quot;삭제했는데 왜 다시 나타나지?&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;코르크차지의 그룹 삭제 로직을 처음 구현했을 때, 코드레빗(CodeRabbit)으로부터 날카로운 지적을 받았다. 바로 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;9&quot;&gt;낙관적 업데이트와 서버 동기화 사이의 타이밍 이슈&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;수정 전 코드 (문제 발생 지점)&lt;/p&gt;
&lt;pre id=&quot;code_1772772989907&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// handleEditGroup 중 일부
const handleDeleteGroup = async (id: number) =&amp;gt; {
  // [1] API 요청 보냄 (비동기)
  deleteBookmarkGroup(id); 

  // [2] 낙관적 업데이트: UI에서 즉시 제거
  setMyGroups((prev) =&amp;gt; prev.filter((g) =&amp;gt; g.id !== id));

  // [3] 목록 새로고침 (문제의 구간!)
  refreshGroups(); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;왜 문제가 될까? (Race Condition 발생)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 삭제를 클릭하면 setMyGroups에 의해 화면에서 항목이 즉시 사라진다. (기분 좋은 사용자 경험)&lt;/li&gt;
&lt;li&gt;동시에 deleteBookmarkGroup(id) API가 서버로 날아간다. 하지만 DB에서 실제로 삭제되기까지는 수 밀리초에서 수 초가 걸린다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;문제는 refreshGroups()이다.&lt;/b&gt; API가 완료되기도 전에 refreshGroups가 실행되어 서버에 &quot;전체 목록 줘!&quot;라고 요청을 보낸다.&lt;/li&gt;
&lt;li&gt;서버는 아직 삭제 처리가 안 된(DB에 남아있는) 데이터를 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,4,0&quot;&gt;결과:&lt;/b&gt; 삭제된 줄 알았던 그룹이 잠시 후 다시 목록에 나타났다가 사라지는 '&lt;b&gt;깜빡임&lt;/b&gt;' 현상이 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;3. 해결책: 실행 순서의 제어 (Async/Await)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 &quot;UI는 즉시 바꾸되, 서버 데이터 재조회는 반드시 서버 작업이 끝난 후에 수행한다&quot;는 순서를 강제해야 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;수정 후 코드 (해결 완료)&lt;/p&gt;
&lt;pre id=&quot;code_1772773094185&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleDeleteGroup = async (id: number) =&amp;gt; {
  const targetGroup = myGroups.find((g) =&amp;gt; g.id === id);
  if (!targetGroup) return;

  // 1단계: UI 먼저 업데이트 (사용자는 즉각적인 피드백을 받음)
  setMyGroups((prev) =&amp;gt; prev.filter((g) =&amp;gt; g.id !== id));

  try {
    // 2단계: 서버 삭제 요청을 await으로 기다림
    // 서버 DB에서 삭제가 완전히 끝날 때까지 다음 줄로 넘어가지 않음
    await deleteBookmarkGroup(id);
    
    // 3단계: 삭제가 확실히 완료된 후, 최신 서버 데이터를 가져옴 (동기화)
    await refreshGroups(); 

  } catch (error) {
    console.error('삭제 실패:', error);
    // 4단계: 만약 서버 요청이 실패한다면? 롤백(Rollback) 수행
    // 사용자에게 에러를 알리고 UI를 이전 상태로 되돌림
    setMyGroups((prev) =&amp;gt; [...prev, targetGroup]);
    alert('그룹 삭제에 실패했습니다.');
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;4. 회고: 왜 이렇게 해결해야만 했는가?&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size20&quot;&gt;1) 신뢰성 있는 데이터 관리&lt;/h4&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;낙관적 업데이트는 UI를 속이는 기법이다. 하지만 결국 최종적인 데이터의 주인(Source of Truth)은 서버다. 서버의 상태가 바뀌기 전에 조회를 요청하는 것은 &lt;b data-index-in-node=&quot;97&quot; data-path-to-node=&quot;22&quot;&gt;Dirty Read&lt;/b&gt;와 유사한 상황을 클라이언트에서 만드는 것과 같다. await을 통해 작업의 원자성(Atomicity)과 순서를 보장해야 한다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size20&quot;&gt;2) 에러 핸들링과 롤백(Rollback)의 중요성&lt;/h4&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;낙관적 업데이트의 필수 짝꿍은 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;24&quot;&gt;롤백&lt;/b&gt;이다. 서버가 500 에러를 뱉었는데 UI만 삭제되어 있다면 사용자는 데이터가 유실되었다고 오해할 수 있다. catch 블록에서 이전 상태를 복구하는 로직은 시스템의 안정성을 높인다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size20&quot;&gt;3) 생성(Create) 시의 예외 상황&lt;/h4&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;수정이나 삭제는 기존 ID를 알고 있어 낙관적 업데이트가 쉽다. 하지만 '생성'은 서버에서 생성된 실제 ID를 받아와야 하므로, 가짜 ID(예: Date.now())를 임시로 부여했다가 서버 응답 후 실제 ID로 교체하는 등 좀 더 복잡한 로직이 필요할 수 있다. (이번 프로젝트에서는 생성 시엔 모달을 띄워 응답을 기다리는 방식으로 처리했다.)&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;요약 및 결론&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;낙관적 업데이트&lt;/b&gt;는 응답 대기 시간을 체감 0으로 만들어 사용자 경험을 극적으로 개선한다.&lt;/li&gt;
&lt;li&gt;하지만 서버 작업 완료 전 재조회(Race Condition)를 하게 되면 데이터 불일치(깜빡임) 현상이 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,2,0&quot;&gt;해결책:&lt;/b&gt; UI는 즉시 갱신하되, await을 사용하여 서버 응답을 확인한 후 동기화(Refresh)를 진행하고, 실패 시 롤백 로직을 반드시 갖춰야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;30&quot;&gt;낙관적 업데이트&lt;/b&gt;에 대하여 처음 접해보았고, 단순한 기능 구현외에 이런 '타이밍 이슈'(race condition 처리)를 전공과목으로만 공부해봤는데, 실제 내가 진행하는 프로젝트의 내가 맡은 프론트엔드 파트에서 고민하는 과정을 겪으니 신기했고, 전공지식이 쓸모있구나 생각도 들어 뿌듯했다....!&lt;/p&gt;</description>
      <category>개발/트러블슈팅</category>
      <category>REACT</category>
      <category>개발</category>
      <category>리액트</category>
      <category>트러블슈팅</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/24</guid>
      <comments>https://sjindev.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 6 Mar 2026 14:03:14 +0900</pubDate>
    </item>
    <item>
      <title>[project] 코르크차지 바텀시트 중첩구조로 인한 topSnapVh 관련 이슈 해결을 위해 DOM 조작하기</title>
      <link>https://sjindev.tistory.com/23</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 상황&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트는 현재 지도 페이지에서 특정 버튼 또는 클릭의 여부에 따라 사전에 구현해둔 바텀 시트에 children 컴포넌트를 각각 분기처리하여 그때 그때 필요한 컴포넌트를 바텀시트로 띄우곤 한다.&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;pre id=&quot;code_1771652887987&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CorkageMap.tsx 의 일부. 각 컴포넌트의 props는 생략
..
..
..
      {/* 바텀시트: 저장한 매장 or 멀티핀 리스트 */}
      &amp;lt;BottomSheet &amp;gt;
        {sheetView === 'list' &amp;amp;&amp;amp; (
          &amp;lt;List /&amp;gt;
        )}
        {sheetView === 'store' &amp;amp;&amp;amp; &amp;lt;MyStore /&amp;gt;}
        {sheetView === 'multipin' &amp;amp;&amp;amp; &amp;lt;MultipinList /&amp;gt;}
        {/* [추가] Detail 컴포넌트 렌더링 (ID 전달) */}
        {sheetView === 'detail' &amp;amp;&amp;amp; selectedRestaurantId &amp;amp;&amp;amp; (
          &amp;lt;Detail /&amp;gt;
        )}
      &amp;lt;/BottomSheet&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 BottomSheet가 지도에서 처음으로 등장하는 바텀시트이다. 여기에 Detail 컴포넌트 속에서 한번더 BottomSheet가 존재한다.&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;외부 바텀시트 : CorkageMap.tsx 지도 페이지로부터 팝업된 BottomSheet(Detail.tsx 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 바텀시트 :&amp;nbsp; 외부 바텀시트의 Children 컴포넌트로 전달된 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Detail.tsx 에서 사용되는 또한번의 BottomSheet&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;이로인해, 바텀시트 이중구조를 띄고있다.&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 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;p data-ke-size=&quot;size16&quot;&gt;이로인해 바텀시트안의 바텀시트는 topSnapVh(바텀시트를 띄울 최대높이) 계산을 뷰포트 전체를 기준으로 하는 것이 아니라, 본인의 바로 외부 컴포넌트. 즉 사용자 관점에서 봤을 때, 첫번째 바텀시트에 표시된 화면을 기준으로 topSnapVh를 계산하게 된다.&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/dYjFZ3/dJMcac9TUo9/7P8mu5qjV7eC9QxKK4C4zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYjFZ3/dJMcac9TUo9/7P8mu5qjV7eC9QxKK4C4zk/img.png&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;1389&quot; data-is-animation=&quot;false&quot; width=&quot;274&quot; height=&quot;454&quot; style=&quot;width: 49.6194%; margin-right: 10px;&quot; data-widthpercent=&quot;50.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYjFZ3/dJMcac9TUo9/7P8mu5qjV7eC9QxKK4C4zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYjFZ3%2FdJMcac9TUo9%2F7P8mu5qjV7eC9QxKK4C4zk%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;839&quot; height=&quot;1389&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTW384/dJMcacozXMQ/y7e29YJ2KLJefe3wcKfld1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTW384/dJMcacozXMQ/y7e29YJ2KLJefe3wcKfld1/img.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;1402&quot; data-is-animation=&quot;false&quot; width=&quot;270&quot; style=&quot;width: 49.2179%;&quot; data-widthpercent=&quot;49.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTW384/dJMcacozXMQ/y7e29YJ2KLJefe3wcKfld1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTW384%2FdJMcacozXMQ%2Fy7e29YJ2KLJefe3wcKfld1%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;840&quot; height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;중첩된 BottomSheet 구조 내에서 자식 컴포넌트(GroupSelector)가 부모의 제약(높이, 위치, 쌓임 맥락)을 무시하고 전체 뷰포트를 기준으로 동작하게 만드는 가장 간단하고 확실한 방법인 React Portal을 사용하는 것&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;pre id=&quot;code_1771653273478&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createPortal } from 'react-dom'; // createPortal을 import 한다.
.
.
.
// return 문을 createPortal로 감싼다.
return createPortal(
    &amp;lt;&amp;gt;
    //내용물
    &amp;lt;/&amp;gt;,
    document.body
    );&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 data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Portal의 첫 번째 인자:&lt;/b&gt; 화면에 보여줄 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;7,0,0&quot;&gt;JSX 전체&lt;/b&gt;(&amp;lt;&amp;gt; ... &amp;lt;/&amp;gt;)가 들어간다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;Portal의 두 번째 인자:&lt;/b&gt; 이 UI가 실제로 붙을 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;7,1,0&quot;&gt;DOM 위치&lt;/b&gt;(document.body) 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 리액트 컴포넌트는 부모 컴포넌트의 자식 위치(DOM 상에서도 안쪽)에 렌더링하지만 포털을 사용하면 &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;4&quot;&gt;&quot;논리적인 위치는 부모-자식 관계를 유지하고, 물리적인 렌더링 위치는 전혀 다른 곳으로 순간이동&quot;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;기존 방식:&lt;/b&gt; CorkageMap &amp;gt; BottomSheet &amp;gt; Detail &amp;gt; GroupSelector 순으로 겹겹이 쌓여있어, 부모가 작으면 자식인 GroupSelector도 부모의 크기에 갇혀버림&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;Portal 방식:&lt;/b&gt; Detail 컴포넌트가 GroupSelector를 호출하는 코드는 그대로 두어 데이터(Props)는 자유롭게 주고받되, 브라우저가 화면을 그릴 때는 &amp;lt;body&amp;gt; 태그 바로 아래에 독립적으로 배치.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;document.body&lt;/b&gt;를 인자로 준 것은, 이 바텀시트를 HTML의 가장 뿌리가 되는 &amp;lt;body&amp;gt; 태그 바로 아래에 붙이겠다는 뜻.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;레이아웃 독립성:&lt;/b&gt; body 바로 아래에 있으면 주변에 방해되는 부모 요소가 없음. 따라서 width: 100%, height: 100vh 같은 설정이 부모의 크기가 아닌 사용자의 전체 브라우저 화면(Viewport)을 기준으로 동작하게 되며 이 문제를 해결할 수 있게됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구조적 변화 시각화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[수정 전 DOM 트리]&lt;/p&gt;
&lt;pre id=&quot;code_1771653550822&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;root&quot;&amp;gt;
    &amp;lt;main&amp;gt; 
    	&amp;lt;div class=&quot;bottom-sheet&quot;&amp;gt; 
    		&amp;lt;div class=&quot;detail&quot;&amp;gt;
          		&amp;lt;div class=&quot;group-selector&quot;&amp;gt;...&amp;lt;/div&amp;gt; 
        	&amp;lt;/div&amp;gt;
      	&amp;lt;/div&amp;gt;
    &amp;lt;/main&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&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;&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;[React Portal 적용 후 DOM 트리]&lt;/p&gt;
&lt;pre id=&quot;code_1771653638331&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;group-selector-portal&quot;&amp;gt; &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/프로젝트</category>
      <category>REACT</category>
      <category>TypScript</category>
      <category>개발</category>
      <category>리액트</category>
      <category>타입스크립트</category>
      <category>프론트엔드</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/23</guid>
      <comments>https://sjindev.tistory.com/23#entry23comment</comments>
      <pubDate>Sat, 21 Feb 2026 15:01:42 +0900</pubDate>
    </item>
    <item>
      <title>Vercel + React Router (CSR) 환경에서 정적 호스팅 라우팅 문제 해결</title>
      <link>https://sjindev.tistory.com/20</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/svXEj/btsQAQL4xHj/n9Cg7Wjmvk9hVsR77c4Ckk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/svXEj/btsQAQL4xHj/n9Cg7Wjmvk9hVsR77c4Ckk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/svXEj/btsQAQL4xHj/n9Cg7Wjmvk9hVsR77c4Ckk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsvXEj%2FbtsQAQL4xHj%2Fn9Cg7Wjmvk9hVsR77c4Ckk%2Fimg.jpg&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;726&quot; height=&quot;380&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;628&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;h3 data-ke-size=&quot;size23&quot;&gt;- 문제 배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Vercel + React Router (CSR- &lt;b&gt;Client Side Rendering&lt;/b&gt;) 환경에서 Vercel 로 배포한&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&quot;&lt;u&gt;&lt;b&gt;https://프로젝트명.vercel.app/&lt;/b&gt;&lt;/u&gt;&quot; 배포주소에서 발생하는 오류가 하나 있다.&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;프로젝트를 하며 작성하는 Router.tsx 파일의 path가 localhost 에서는 잘 작동하지만 배포도메인에서는 맨 뒤에 path를 입력했을 때&lt;b&gt; 404 not found&lt;/b&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;&quot;https://프로젝트명.vercel.app/&quot; : &lt;/b&gt;해당 URL 접속은 잘 됨!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;https://프로젝트명.vercel.app/signin&quot; &lt;/b&gt;: 이처럼 서브 라우트로 직접 접근하면 404 Not Found 발생하는 문제.&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;- 원인: CSR(클라이언트 사이드 라우팅)과 정적 호스팅의 충돌&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Router는 브라우저 내에서 라우팅을 처리하는 &lt;b&gt;CSR&lt;/b&gt; 방식이다. 그런데 Vercel이나 Netlify 같은 정적 서버는 기본적으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청된 경로에 실제 HTML 파일이 없으면 404 에러를 띄운다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 예시와 같이 &quot;&lt;b&gt;/signin&lt;/b&gt;&quot; 라는 URL로 직접 접속하면 Vercel은 &quot;&lt;b&gt;/signin/index.html &lt;/b&gt;&quot; 같은 정적 파일을 찾으려고 시도하며 없다면 &lt;b&gt;404 에러&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;ps) &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;0&quot;&gt;Single Page Application (SPA)&lt;/b&gt; 배포 시 매우 흔하게 발생하는 &lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;0&quot;&gt;라우팅 문제&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 간단히 말해, &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;1&quot;&gt;Vercel 서버는 /home이라는 실제 파일(HTML)을 찾으려고 시도했지만, 리액트 프로젝트에는 index.html 하나만 존재하기 때문에 찾지 못해 404 에러를 띄운 것&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;- 해결 방법: 모든 경로를 index.html로 리디렉션하기&lt;/h3&gt;
&lt;p data-end=&quot;687&quot; data-start=&quot;646&quot; data-ke-size=&quot;size18&quot;&gt;Vercel의 경우 &lt;b&gt;vercel.json&lt;/b&gt; 설정으로 해결 가능하다. 아래와 같이 작성한다.&lt;/p&gt;
&lt;p data-end=&quot;687&quot; data-start=&quot;646&quot; data-ke-size=&quot;size18&quot;&gt;참고로 &lt;b&gt;vercel.json&lt;/b&gt; 의 파일 경로는 아래와 같이 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1757919291902&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;your-project/
├── public/
├── src/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── vercel.json &amp;larr; ✅ 이 위치!&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1757919146169&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;rewrites&quot;: [
    { &quot;source&quot;: &quot;/(.*)&quot;, &quot;destination&quot;: &quot;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vite 사용 시 추가 설정이 필요하며 아래와 같다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;vite.config.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757919203610&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;위 코드에 대해 조금더 설명하자면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;alias: { '@': path.resolve(__dirname, './src') }&lt;/span&gt;&amp;nbsp;: @/components/Button 이런 식으로 &lt;b&gt;절대 경로 import 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는, 프로젝트 규모 커질수록 유지보수에 유리하고, 최근 대부분의 Vite/Next.js 프로젝트에서 사용하는 방식이다.&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;ex) 위 코드에서 사용한 alias 문 사용에 관하여 예시를 들면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1757919855101&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존 상대경로
import Button from '../../components/Button';

// alias 적용 후
import Button from '@/components/Button';&lt;/code&gt;&lt;/pre&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;size18&quot;&gt;Vercel + React Router &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(CSR-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Client Side Rendering&lt;/b&gt;) 환경에서 &lt;b&gt;vercel.json &lt;/b&gt;파일에서 설정을 통하여&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;모든 경로를 index.html로 리디렉션하는 방식으로 문제를 해결할 수 있다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>csr</category>
      <category>Vercel</category>
      <category>개발</category>
      <category>웹개발</category>
      <category>웹배포</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/20</guid>
      <comments>https://sjindev.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 15 Sep 2025 16:15:27 +0900</pubDate>
    </item>
    <item>
      <title>github 액션 yml 파일 만들기 (CI/CD 파이프라인 구축)</title>
      <link>https://sjindev.tistory.com/19</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lO2Iz/btsP8hrsXRh/3zksww0cqA2jTdgkkMsKl1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lO2Iz/btsP8hrsXRh/3zksww0cqA2jTdgkkMsKl1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lO2Iz/btsP8hrsXRh/3zksww0cqA2jTdgkkMsKl1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlO2Iz%2FbtsP8hrsXRh%2F3zksww0cqA2jTdgkkMsKl1%2Fimg.webp&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;583&quot; height=&quot;328&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;오늘은 배포자동화에 필요한 CI/CD 파이프라인 구축을 위해 어떤 코드를 작성해야하는지 알아볼 예정입니다.&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;github 액션을 이용하면 코드를 push 했을때, PR Template 을 통한 체계적인 Pull Request 작성, CI 테스트, CD 테스트, prettier 적용 테스트 등 다양한 테스트를 진행할 수 있습니다.&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;위와 같은 테스트가 필요한 이유는, 사전에 내가 올릴 코드들을 여러가지 test를 통해 협업 브랜치에 merge 됐을 때, 발생하는 오류가 없는지 사전에 확인해보기 위함입니다. 문제발생 가능성이 있거나, 실제로 문제가 있는 코드는&amp;nbsp; 협업 브랜치에 merge 됐을 시 팀원들에게 &lt;s&gt;많은 사랑...을받을 수 있을 겁니다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러지 않도록, 협업에서 주로 사용하는 3가지 테스트를 위한 githun action 설정용 코드를 아래와 같이 남겨드리겠습니다.&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;- CI&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- CD &lt;b&gt;(&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;vercel로 우선 배포하고, 연동한 뒤 작성해야합니다. 안그러면 테스트 실패함&lt;/u&gt;&lt;/span&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- Prettier&amp;nbsp;&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;우선 확장자는 .yml 입니다. 위 파일들은 아래 경로에 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 프로젝트의 루트 디렉토리 아래 /github 이름으로 폴더를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;-&amp;nbsp; /github 폴더 아래 /workflows 폴더를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- workflows 폴더 아래 .yml 파일을 작성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UmMUj/btsQaRSioRx/zvQBMYEjjijRzT3wHNnM3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UmMUj/btsQaRSioRx/zvQBMYEjjijRzT3wHNnM3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UmMUj/btsQaRSioRx/zvQBMYEjjijRzT3wHNnM3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUmMUj%2FbtsQaRSioRx%2FzvQBMYEjjijRzT3wHNnM3k%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;476&quot; height=&quot;225&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 사진과 같이 작성해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;참고로 Pull Request 템플릿을 위한 파일은 workflows 폴더 바로 아래 작성해주시면 됩니다.&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;저는 Pull Request 템플릿을 아래와 같은 양식으로 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756308902554&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;##   작업 내용

- 기능에서 어떤 부분이 구현되었는지 설명해주세요

&amp;lt;br/&amp;gt;

##   PR Point

- 

&amp;lt;br/&amp;gt;

## 이미지 첨부


&amp;lt;br/&amp;gt;

##   다음 할 일

- 다음 할 일을 적어주세요

&amp;lt;br/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;CI 테스트&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ci.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756308967989&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CI (develop)

on:
  push:
    branches: [develop]
  pull_request:
    branches: [develop]

jobs:
  build-test:
    runs-on: ubuntu-latest
    timeout-minutes: 10 # 무한 대기 방지

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false # install 단계에서 직접 캐시 사용

      # 의존성 캐싱
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Install deps
        run: pnpm install

      # 타입체크 + 번들
      - run: pnpm run build

      # ESLint (선택 항목)
      - run: pnpm run lint&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;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;CD 테스트 및 시크릿 변수 등록(github)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;deploy.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756309037094&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Deploy to Vercel (main)

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm run build # 또는 tsc -b &amp;amp;&amp;amp; vite build

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;참고로 위 코드는 이미 &lt;b&gt;pnpm run build&lt;/b&gt; 를 수행하고 있기 때문에 &lt;u&gt;&lt;b&gt;build.sh&lt;/b&gt; 파일이 따로 필요하지 않습니다.&lt;/u&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;secret 변수는 vercel 배포시 &lt;b&gt;Generate new token (classic)&lt;/b&gt; 메뉴를 선택하여 발급받은 뒤 따로 복사해둡니다. 이후 github 의 Org 레포 settings -&amp;gt; Secrets and Variables -&amp;gt; Actions 에서 secret 변수를 등록합니다.&amp;nbsp;&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;ghp_~~ 로 시작하는 토큰을 &lt;b&gt;AUTO_ACTIONS&lt;/b&gt; 라는 이름으로 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 본인 깃허브 계정 이메일을 &lt;b&gt;EMAIL&lt;/b&gt; 이라는 이름으로 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;2504&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2LdNt/btsP8khCUw9/G0OD3GHOhGJXliELdPvQGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2LdNt/btsP8khCUw9/G0OD3GHOhGJXliELdPvQGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2LdNt/btsP8khCUw9/G0OD3GHOhGJXliELdPvQGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2LdNt%2FbtsP8khCUw9%2FG0OD3GHOhGJXliELdPvQGK%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;2504&quot; height=&quot;726&quot; data-origin-width=&quot;2504&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&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;제가 사용하는 deploy.yml 파일의 코드를 보면 사진과 같이 &lt;b&gt;VERCEL_&lt;/b&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;a href=&quot;https://sjindev.tistory.com/18&quot;&gt;vercel 로 배포 및 CI/CD 파이프라인 구축하기(배포 자동화)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756373163493&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;vercel 로 배포 및 CI/CD 파이프라인 구축하기(배포 자동화)&quot; data-og-description=&quot;오늘은 vercel 로 배포및 CI/CD 파이프라인 구축을 통해 프론트엔드 개발환경을 세팅해볼 예정입니다. 위 블로그를 작성할때 기준 저의 프로젝트 개발환경 세팅은 pnpm + react + typescript + tailwind 입니&quot; data-og-host=&quot;sjindev.tistory.com&quot; data-og-source-url=&quot;https://sjindev.tistory.com/18&quot; data-og-url=&quot;https://sjindev.tistory.com/18&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AToyS/hyZDUsngzk/pJgR2Dn0aA227Jlne6sVk0/img.jpg?width=672&amp;amp;height=428&amp;amp;face=0_0_672_428,https://scrap.kakaocdn.net/dn/bjVqTC/hyZC76M0UP/k7sFqW5f5UcQQbtNVrHWiK/img.jpg?width=672&amp;amp;height=428&amp;amp;face=0_0_672_428,https://scrap.kakaocdn.net/dn/bzkSSD/hyZGfhMLVB/wpEMJhQVxksJTxabaC0K40/img.png?width=2604&amp;amp;height=646&amp;amp;face=0_0_2604_646&quot;&gt;&lt;a href=&quot;https://sjindev.tistory.com/18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sjindev.tistory.com/18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AToyS/hyZDUsngzk/pJgR2Dn0aA227Jlne6sVk0/img.jpg?width=672&amp;amp;height=428&amp;amp;face=0_0_672_428,https://scrap.kakaocdn.net/dn/bjVqTC/hyZC76M0UP/k7sFqW5f5UcQQbtNVrHWiK/img.jpg?width=672&amp;amp;height=428&amp;amp;face=0_0_672_428,https://scrap.kakaocdn.net/dn/bzkSSD/hyZGfhMLVB/wpEMJhQVxksJTxabaC0K40/img.png?width=2604&amp;amp;height=646&amp;amp;face=0_0_2604_646');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;vercel 로 배포 및 CI/CD 파이프라인 구축하기(배포 자동화)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 vercel 로 배포및 CI/CD 파이프라인 구축을 통해 프론트엔드 개발환경을 세팅해볼 예정입니다. 위 블로그를 작성할때 기준 저의 프로젝트 개발환경 세팅은 pnpm + react + typescript + tailwind 입니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sjindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;위 블로그 글의 5번과정 참고하시면 됩니다.&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Prettier 테스트&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prettier.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1756309083549&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Prettier

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  prettier:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install pnpm
        run: npm install -g pnpm

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Run Prettier
        run: pnpm prettier --check&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;위 3개의 파일을 참고하셔서 깃허브 자동화 설정 해주시면 됩니다!&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;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;main 브랜치 : 배포 연동용&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;devleop 브랜치(&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;default 로 설정&lt;/u&gt;&lt;/span&gt;) : 개발용 중앙 브랜치&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;feat 브랜치 : 기능 개발용 브랜치이며 이는 develop 브랜치로부터 파생됩니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. develop 브랜치에서 feat(기능 개발용) 브랜치를 생성하여 작업합니다.&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;2. git push origin [feat브랜치명] 으로 push 후 PR을 작성합니다. 아래 사진과 같이 CI 테스트 및 프리티어 테스트가 진행됩니다.&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;3. 통과되면 merge 가능 상태가 되며 협업시 브랜치 팀별 룰셋에 따라 코드리뷰 및 approve가 필요할 수 있습니다.&amp;nbsp;&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;4. 이후 feat브랜치의 코드가 develop 브랜치에 merge 되었다면 로컬에서 git pull origin devleop 으로&amp;nbsp; 최신사항을 가져옵니다.&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;5. main 브랜치로 전환합니다.&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;6. git merge develop 을 하여 develop과 sync를 맞춰줍니다.&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;7. git push origin main 으로 main 브랜치에 push 합니다. 이때 main 브랜치는 배포 연동용이며, 배포자동화 설정이 끝났다면, 배포환경에도 반영이 되며 이때 CI, CD, 프리티어 테스트가 진행됩니다.&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;1970&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsJHjk/btsP81PaWro/hBsrTZbDunCM1pYEfHq4l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsJHjk/btsP81PaWro/hBsrTZbDunCM1pYEfHq4l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsJHjk/btsP81PaWro/hBsrTZbDunCM1pYEfHq4l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsJHjk%2FbtsP81PaWro%2FhBsrTZbDunCM1pYEfHq4l1%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;1970&quot; height=&quot;703&quot; data-origin-width=&quot;1970&quot; data-origin-height=&quot;703&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;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;추가) 프로젝트에 적용한 브랜치 전략&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;참고로 제가 진행한 프로젝트의 브랜치 전략은 아래와 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;1013&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4LCTJ/btsQaLlbRBl/lEq63mShXvgmSPQfjQPmZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4LCTJ/btsQaLlbRBl/lEq63mShXvgmSPQfjQPmZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4LCTJ/btsQaLlbRBl/lEq63mShXvgmSPQfjQPmZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4LCTJ%2FbtsQaLlbRBl%2FlEq63mShXvgmSPQfjQPmZk%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;338&quot; height=&quot;530&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;1013&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;master(main)&lt;/b&gt; : &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;기준이 되는 브랜치로 제품을 배포하는 브랜치&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;develop&lt;/b&gt; : &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;개발 브랜치로 개발자들이 이 브랜치를 기준으로 각자 작업한 기능들을 Merge&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;feature&lt;/b&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;&lt;span&gt; : &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;단위 기능을 개발하는 브랜치로 기능 개발이 완료되면 develop 브랜치에 Merge&lt;/span&gt; &lt;/span&gt;&lt;/span&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;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;&lt;span&gt;대략적으로 위와 같은 분기를 두어 진행했으며, &lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;gitflow&lt;/span&gt;&amp;nbsp;전략&lt;/b&gt;을 간소화 시킨것으로 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;볼 수 있을 것 같네요.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;이를 위해 &lt;b&gt;main&lt;/b&gt;, &lt;b&gt;develop&lt;/b&gt; 브랜치 에 함부로 직접 push 할수 없도록 github 브랜치 보호 &lt;b&gt;rullset&lt;/b&gt;을 잘 설정해두는 것도 꼭 필요합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;개발 프로세스&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. feat 브랜치 즉, 기능 개발 브랜치에서 기능을 개발한 후 원격에 push 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 원격에서 CI 테스트 진행 후 통과하면 develop 브랜치에 merge 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 로컬환경에서 develop 브랜치로 변경한 후 git pull origin develop 으로 최신사항을 유지해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. main 브랜치로 switch 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. git merge develop&amp;nbsp; 으로 develop 브랜치의 내용과 sync 를 맞춰줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. git push origin main 으로 main브랜치를 push 하면 자동배포가 진행됩니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>CICD</category>
      <category>github</category>
      <category>githubAction</category>
      <category>yml</category>
      <category>개발</category>
      <category>배포</category>
      <category>배포자동화</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/19</guid>
      <comments>https://sjindev.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 28 Aug 2025 00:43:44 +0900</pubDate>
    </item>
    <item>
      <title>vercel 로 배포 및 CI/CD 파이프라인 구축하기(배포 자동화)</title>
      <link>https://sjindev.tistory.com/18</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rkDKL/btsP8asbTCv/qtco229DkSKbetNeOOwmg0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rkDKL/btsP8asbTCv/qtco229DkSKbetNeOOwmg0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rkDKL/btsP8asbTCv/qtco229DkSKbetNeOOwmg0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrkDKL%2FbtsP8asbTCv%2Fqtco229DkSKbetNeOOwmg0%2Fimg.webp&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;592&quot; height=&quot;377&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;오늘은 vercel 로 배포및 CI/CD 파이프라인 구축을 통해 프론트엔드 개발환경을 세팅해볼 예정입니다.&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;&amp;nbsp; pnpm + react + typescript + tailwind&lt;/b&gt; 입니다. 또한, 저의 경우 깃허브 레포지토리로 미리 위 세팅에 대해 template 레포지토리가 있기 때문에 레포는 미리 만들어두고 시작했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;1. github에 있는 repository clone&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. vscode 로컬 환경에 워크스페이스 폴더를 등록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 해당 워크스페이스 폴더로 터미널 경로를 맞춰줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. git clone [레포주소]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. pnpm install : 의존성 설치 작업 진행.(저는 기존 템플릿 레포지토리를 클론받은거라 의존성 설치를 해주었습니다.)&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;background-color: #c0d1e7;&quot;&gt;2. 예상치 못한 이슈&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 의존성 설치 후 &lt;b&gt;pnpm run dev&lt;/b&gt; 를 통해 로컬호스트 실행을 하였습니다. 하지만, tailwind css 의 자동완성 및 자동정렬 기능이 실행되지 않는 이슈가 발생했습니다. 이를 해결하는 방법은 아래와 같습니다.&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;background-color: #c0d1e7;&quot;&gt;해결과정&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2025&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IrOWC/btsP8HDeopx/HAk0W7v53trXKarcWwblb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IrOWC/btsP8HDeopx/HAk0W7v53trXKarcWwblb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IrOWC/btsP8HDeopx/HAk0W7v53trXKarcWwblb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIrOWC%2FbtsP8HDeopx%2FHAk0W7v53trXKarcWwblb1%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;2025&quot; height=&quot;630&quot; data-origin-width=&quot;2025&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Ctrl + P 를 눌러 검색창에 &quot;settings.json&quot; 파일로 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 맨 아랫줄 코드를 추가했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1756307281025&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &quot;tailwindCSS.validate&quot;: true,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 추가하면, tailwind 자동완성 및 자동정렬 기능이 정상 동작합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;3. yml 코드를 통한 깃허브 액션봇 설정&lt;/span&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sjindev.tistory.com/19&quot;&gt;github 액션봇 yml 파일 만들기 (CI/CD 파이프라인 구축)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756309507277&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;github 액션봇 yml 파일 만들기 (CI/CD 파이프라인 구축)&quot; data-og-description=&quot;오늘은 배포자동화에 필요한 CI/CD 파이프라인 구축을 위해 어떤 코드를 작성해야하는지 알아볼 예정입니다. github 액션을 이용하면 코드를 push 했을때, PR Template 을 통한 체계적인 Pull Request 작성,&quot; data-og-host=&quot;sjindev.tistory.com&quot; data-og-source-url=&quot;https://sjindev.tistory.com/19&quot; data-og-url=&quot;https://sjindev.tistory.com/19&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tL18q/hyZDVEB2hm/2iwoUhCfF0ZNMyNVNAcBx0/img.jpg?width=746&amp;amp;height=420&amp;amp;face=320_212_392_291,https://scrap.kakaocdn.net/dn/I6YDJ/hyZGewkk0J/uY6HarNurMKSMSOY1lUe11/img.jpg?width=746&amp;amp;height=420&amp;amp;face=320_212_392_291,https://scrap.kakaocdn.net/dn/bmbH1n/hyZCXiKqvO/CJyfBkK4rnTqDk0EehXcX0/img.png?width=406&amp;amp;height=380&amp;amp;face=0_0_406_380&quot;&gt;&lt;a href=&quot;https://sjindev.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sjindev.tistory.com/19&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tL18q/hyZDVEB2hm/2iwoUhCfF0ZNMyNVNAcBx0/img.jpg?width=746&amp;amp;height=420&amp;amp;face=320_212_392_291,https://scrap.kakaocdn.net/dn/I6YDJ/hyZGewkk0J/uY6HarNurMKSMSOY1lUe11/img.jpg?width=746&amp;amp;height=420&amp;amp;face=320_212_392_291,https://scrap.kakaocdn.net/dn/bmbH1n/hyZCXiKqvO/CJyfBkK4rnTqDk0EehXcX0/img.png?width=406&amp;amp;height=380&amp;amp;face=0_0_406_380');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;github 액션봇 yml 파일 만들기 (CI/CD 파이프라인 구축)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 배포자동화에 필요한 CI/CD 파이프라인 구축을 위해 어떤 코드를 작성해야하는지 알아볼 예정입니다. github 액션을 이용하면 코드를 push 했을때, PR Template 을 통한 체계적인 Pull Request 작성,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sjindev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글을 참고해주세요.&lt;b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;주의할 것은 deploy.yml (CD테스트) 는 vercel 로 배포 연동 후 등록해야 합니다.&lt;/u&gt;&lt;/span&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;span style=&quot;background-color: #c0d1e7;&quot;&gt;4. vercel 배포하기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;vercel로 배포할 때, org 에 있는 레포를 그대로 배포하려면 vercel 유료 버전을 결제해야합니다. 따라서 저는 아래 블로그를 참고했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://jjang-j.tistory.com/93&quot;&gt;[Github Action] 깃허브 Organization 레포지토리 vercel 자동 배포&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756353094301&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Github Action] 깃허브 Organization 레포지토리 vercel 자동 배포&quot; data-og-description=&quot;시작하기 앞서...현재 프로젝트를 진행하고 있는데깃허브에서 Organization 레포지토리에서 작업을 하고 vercel 로 배포를 하려면 프로 계정을 사용해야 된다.Pro 계정 무료 체험 2주를 할 수 있지만 &quot; data-og-host=&quot;jjang-j.tistory.com&quot; data-og-source-url=&quot;https://jjang-j.tistory.com/93&quot; data-og-url=&quot;https://jjang-j.tistory.com/93&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QfDIR/hyZDOMnrss/7RmKZt57wCMVv22rZNAL3k/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/bmRY8B/hyZCYvaGwd/K3okMNNIKKKQJRypwGpS81/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/k3OKa/hyZGiyMrp4/VfwDjYG5JCFN4FlksWMpEk/img.png?width=750&amp;amp;height=420&amp;amp;face=0_0_750_420&quot;&gt;&lt;a href=&quot;https://jjang-j.tistory.com/93&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jjang-j.tistory.com/93&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QfDIR/hyZDOMnrss/7RmKZt57wCMVv22rZNAL3k/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/bmRY8B/hyZCYvaGwd/K3okMNNIKKKQJRypwGpS81/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/k3OKa/hyZGiyMrp4/VfwDjYG5JCFN4FlksWMpEk/img.png?width=750&amp;amp;height=420&amp;amp;face=0_0_750_420');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Github Action] 깃허브 Organization 레포지토리 vercel 자동 배포&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시작하기 앞서...현재 프로젝트를 진행하고 있는데깃허브에서 Organization 레포지토리에서 작업을 하고 vercel 로 배포를 하려면 프로 계정을 사용해야 된다.Pro 계정 무료 체험 2주를 할 수 있지만&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jjang-j.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 위 블로그에서 설명하는 것 처럼, org의 레포를 먼저 개인 레포로 fork 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 이후 fork 한 레포를 vercel 에서 배포해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C974T/btsP8dXauvn/WuMPcCSMXzRTNcx477fEqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C974T/btsP8dXauvn/WuMPcCSMXzRTNcx477fEqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C974T/btsP8dXauvn/WuMPcCSMXzRTNcx477fEqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC974T%2FbtsP8dXauvn%2FWuMPcCSMXzRTNcx477fEqK%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;464&quot; height=&quot;270&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. github 웹에서 secret 토큰을 발급받습니다.(블로그 3번과정 참조) 발급받은 토큰은 복사해둡니다.&lt;/p&gt;
&lt;p id=&quot;2-secrets-token-발급&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;secrets token 발급&lt;/b&gt; =&amp;gt; &lt;a href=&quot;https://github.com/settings/tokens&quot;&gt;https://github.com/settings/tokens&lt;/a&gt; 에서 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&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;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;+) 참고로 우리의 방식에서는 build.sh 파일은 작성하지 않아도 됨. .yml 파일에서 build 수행에 관한 명령문을 포함할 것이기 때문 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. Org 레포 settings -&amp;gt; Secrets and Variables -&amp;gt; Actions 에서 secret 변수를 등록합니다. (블로그 5번과정 참조)&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;저의 방식의 경우 build.sh 파일이 필요없습니다. 제가 올려둔 &lt;b&gt;deploy.yml&lt;/b&gt; 파일에서 이미 깃허브 액션으로 빌드 가 진행되기 떄문입니다,&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;5. VERCEL 토큰 발급받기&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2820&quot; data-origin-height=&quot;1336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blJmSf/btsQbWlBSnB/qFVtzv4335Wg5AZJsgDedK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blJmSf/btsQbWlBSnB/qFVtzv4335Wg5AZJsgDedK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blJmSf/btsQbWlBSnB/qFVtzv4335Wg5AZJsgDedK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblJmSf%2FbtsQbWlBSnB%2FqFVtzv4335Wg5AZJsgDedK%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;736&quot; height=&quot;349&quot; data-origin-width=&quot;2820&quot; data-origin-height=&quot;1336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 Accounts Settings 에서 토큰 이름을 아무렇게나 정해주고 발급받습니다. 발급받은 토큰은 본인 계정으로 배포한 프로젝트들에서 사용되는 &lt;b&gt;secrets.VERCEL_TOKEN&lt;/b&gt; 변수가 되며, 이는 깃허브 Actions 서 secret변수로 등록하면 된다.&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;1862&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ueH7/btsP9GkhTye/gNpRAPz4HuWkZ4CeUrPgf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ueH7/btsP9GkhTye/gNpRAPz4HuWkZ4CeUrPgf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ueH7/btsP9GkhTye/gNpRAPz4HuWkZ4CeUrPgf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ueH7%2FbtsP9GkhTye%2FgNpRAPz4HuWkZ4CeUrPgf1%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;1862&quot; height=&quot;305&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;2604&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRKEmu/btsP8ZxNwBy/o7Ii8wLOuzFh0XO52hbaEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRKEmu/btsP8ZxNwBy/o7Ii8wLOuzFh0XO52hbaEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRKEmu/btsP8ZxNwBy/o7Ii8wLOuzFh0XO52hbaEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRKEmu%2FbtsP8ZxNwBy%2Fo7Ii8wLOuzFh0XO52hbaEK%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;2604&quot; height=&quot;646&quot; data-origin-width=&quot;2604&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위는 Org repo 의 settings 화면이며, 저의 deployment.yml 파일 기준으로 등록해야하는 secret 변수는 3개 입니다.&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;VERCEL_TOKEN&lt;/b&gt; : 위에 위에 있는 vercel 홈페이지 Account Settings 에서 발급받으면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- &lt;b&gt;VERCEL_PROJECT_ID &lt;/b&gt;: 아래 사진 참고하셔서 프로젝트 settings -&amp;gt; general 화면에서 확인 가능합나다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2671&quot; data-origin-height=&quot;1289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qG2qY/btsQcoWE5eG/sF666lkoRkrNgdT9Ca9T8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qG2qY/btsQcoWE5eG/sF666lkoRkrNgdT9Ca9T8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qG2qY/btsQcoWE5eG/sF666lkoRkrNgdT9Ca9T8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqG2qY%2FbtsQcoWE5eG%2FsF666lkoRkrNgdT9Ca9T8K%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;744&quot; height=&quot;359&quot; data-origin-width=&quot;2671&quot; data-origin-height=&quot;1289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;VERCEL_ORG_ID &lt;/b&gt;:&amp;nbsp; 아래 사진 참고하셔서 워크스페이스에서 Settings를 클릭한 뒤, Team ID를 복사하면 됩니다.&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;2836&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SvjCg/btsP9cw39ai/25RX9VkPuM1I3Ulg8Q1m8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SvjCg/btsP9cw39ai/25RX9VkPuM1I3Ulg8Q1m8K/img.png&quot; data-alt=&quot;무지 이모티콘이 붙어있는 settings 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SvjCg/btsP9cw39ai/25RX9VkPuM1I3Ulg8Q1m8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSvjCg%2FbtsP9cw39ai%2F25RX9VkPuM1I3Ulg8Q1m8K%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;744&quot; height=&quot;328&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2836&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;무지 이모티콘이 붙어있는 settings 클릭&lt;/figcaption&gt;
&lt;/figure&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2584&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckNRtC/btsQa9sosPh/rkvY4QFaOpd3KZpvTASvb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckNRtC/btsQa9sosPh/rkvY4QFaOpd3KZpvTASvb0/img.png&quot; data-alt=&quot;Team ID 를 복사해주세요!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckNRtC/btsQa9sosPh/rkvY4QFaOpd3KZpvTASvb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckNRtC%2FbtsQa9sosPh%2FrkvY4QFaOpd3KZpvTASvb0%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;740&quot; height=&quot;105&quot; data-origin-width=&quot;2584&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Team ID 를 복사해주세요!&lt;/figcaption&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;size18&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;2418&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca0XCw/btsQcaYHXrb/HLUldkMMkzzvRn7bV4LO2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca0XCw/btsQcaYHXrb/HLUldkMMkzzvRn7bV4LO2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca0XCw/btsQcaYHXrb/HLUldkMMkzzvRn7bV4LO2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca0XCw%2FbtsQcaYHXrb%2FHLUldkMMkzzvRn7bV4LO2k%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;2418&quot; height=&quot;425&quot; data-origin-width=&quot;2418&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;size18&quot;&gt;(&lt;u&gt;참고 : 배포 자동화는 main에 코드를 push 했을 때 발생합니다!&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;size18&quot;&gt;이제 feat 브랜치에서 작업 -&amp;gt; 원격에 push 및 develop 브랜차에 merge -&amp;gt; 로컬환경에서 git pull origin develop 으로 최신사항 반영 -&amp;gt; main 브랜치로 switch 후 develop 브랜치 merge -&amp;gt; main 을 원격에 push 과정을 거치면 변경사항이 배포환경에 자동으로 적용되는 것을 확인할 수 있습니다!&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;span style=&quot;background-color: #c0d1e7;&quot;&gt;+ vercel 에서의 환경변수 등록&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추후 네이버연동이나 카카오연동에 따른 API KEY 등록 및 백엔드 API BASE URL 등록등 환경변수 등록이 필요합니다. 이때, vercel 홈페이지에서 해당 프로젝트를 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2765&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWNhKD/btsP8DgxmDJ/okqNd0Rs373n03R6kqkJm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWNhKD/btsP8DgxmDJ/okqNd0Rs373n03R6kqkJm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWNhKD/btsP8DgxmDJ/okqNd0Rs373n03R6kqkJm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWNhKD%2FbtsP8DgxmDJ%2FokqNd0Rs373n03R6kqkJm0%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;2765&quot; height=&quot;1446&quot; data-origin-width=&quot;2765&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 위와같이 Settings -&amp;gt; Environment Variables 화면으로 들어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lgsLd/btsP996MtYv/J4veM5rnXER0r0aSAg9dnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lgsLd/btsP996MtYv/J4veM5rnXER0r0aSAg9dnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lgsLd/btsP996MtYv/J4veM5rnXER0r0aSAg9dnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlgsLd%2FbtsP996MtYv%2FJ4veM5rnXER0r0aSAg9dnk%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;2028&quot; height=&quot;1142&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 입력란에 Key 값과 Value 값을 컨벤션에 맞게 입력해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfFjmM/btsP8iw7lU8/BY8neI5K2pNm0kBCPFVaT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfFjmM/btsP8iw7lU8/BY8neI5K2pNm0kBCPFVaT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfFjmM/btsP8iw7lU8/BY8neI5K2pNm0kBCPFVaT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfFjmM%2FbtsP8iw7lU8%2FBY8neI5K2pNm0kBCPFVaT1%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;1893&quot; height=&quot;495&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;VITE_REDIRECT_URI&lt;/span&gt; 와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;VITE_CLIENT_ID&lt;/span&gt; 는 네이버 맵 api 를 연동할 때 발급받은 것이며, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;VITE_API_BASE_URL&lt;/span&gt; 은 백엔드 api 도메인 주소를 입력한 것입니다.&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;u&gt;&lt;b&gt;물론 배포환경 말고, 로컬환경에서도 위와 같이 등록을 해야합니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 작업은 vscode 로컬 환경에서도 동일하게 해야하며, 방법은 아래와 같습니다.&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;1. 프로젝트 루트 디렉토리 아래 &lt;b&gt;.env&lt;/b&gt; 파일 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2.&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;VITE_API_BASE_URL&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;=&lt;a style=&quot;color: #000000;&quot; href=&quot;https://3.37.222.222.nip.io&quot;&gt;https://1.2.3.4........~~처럼&amp;nbsp; api base url을 입력해줍니다.&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3.&lt;b&gt; .env&lt;/b&gt; 파일은 gitignore처럼 github 에 push 되지 않으므로, 팀원들과의 커뮤니티를 통해 해당 작업의 내용을 알려주고 각자 로컬에서 똑같이 등록할 수 있도록 공유해야 합니다.&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발</category>
      <category>CICD</category>
      <category>frontend</category>
      <category>Vercel</category>
      <category>vercel배포</category>
      <category>개발</category>
      <category>배포</category>
      <category>배포자동화</category>
      <category>프론트엔드</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/18</guid>
      <comments>https://sjindev.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 28 Aug 2025 00:14:17 +0900</pubDate>
    </item>
    <item>
      <title>[모던 자바스크립트 DeepDive] Js 심화 스터디 week 8</title>
      <link>https://sjindev.tistory.com/17</link>
      <description>&lt;h1&gt;브라우저의 렌더링 과정&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KENnh/btsP9cB3c3x/LL3nhwNsY9LkS9kzQYfSw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KENnh/btsP9cB3c3x/LL3nhwNsY9LkS9kzQYfSw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KENnh/btsP9cB3c3x/LL3nhwNsY9LkS9kzQYfSw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKENnh%2FbtsP9cB3c3x%2FLL3nhwNsY9LkS9kzQYfSw0%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;650&quot; height=&quot;398&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 요청과 응답&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ePQ5uu/btsP8uQEfhQ/jkf8vf2Fm1uOm5npmfKOmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ePQ5uu/btsP8uQEfhQ/jkf8vf2Fm1uOm5npmfKOmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ePQ5uu/btsP8uQEfhQ/jkf8vf2Fm1uOm5npmfKOmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FePQ5uu%2FbtsP8uQEfhQ%2Fjkf8vf2Fm1uOm5npmfKOmK%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;697&quot; height=&quot;192&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 주소창에 URL 입력 시 브라우저가 DNS를 통해 IP 주소 조회함&lt;/li&gt;
&lt;li&gt;해당 서버로 HTTP 요청 전송함&lt;/li&gt;
&lt;li&gt;서버는 HTML 문서를 응답으로 반환함&lt;/li&gt;
&lt;li&gt;브라우저는 HTML 문서 수신 후 렌더링 엔진에 전달함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. HTTP 1.0 과 HTTP 2.0&lt;/h2&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/YPfpi/btsP60366ta/1JyLRu5dKkQ0cJ2M5yKKok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YPfpi/btsP60366ta/1JyLRu5dKkQ0cJ2M5yKKok/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;756&quot; data-is-animation=&quot;false&quot; width=&quot;288&quot; height=&quot;490&quot; data-widthpercent=&quot;49.57&quot; style=&quot;width: 48.9973%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YPfpi/btsP60366ta/1JyLRu5dKkQ0cJ2M5yKKok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYPfpi%2FbtsP60366ta%2F1JyLRu5dKkQ0cJ2M5yKKok%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;444&quot; height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGH6vD/btsP8vouzvp/hSww1gnFAdOLvJgtg7Ak60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGH6vD/btsP8vouzvp/hSww1gnFAdOLvJgtg7Ak60/img.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;770&quot; data-is-animation=&quot;false&quot; width=&quot;331&quot; height=&quot;554&quot; style=&quot;width: 49.84%;&quot; data-widthpercent=&quot;50.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGH6vD/btsP8vouzvp/hSww1gnFAdOLvJgtg7Ak60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGH6vD%2FbtsP8vouzvp%2FhSww1gnFAdOLvJgtg7Ak60%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;460&quot; height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;HTTP 1.0&lt;/b&gt;: 요청마다 TCP 연결 새로 생성함, 다중 요청 불가능함, 리소스가 많은 페이지에서 성능 저하 발생함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTP 2.0&lt;/b&gt;: 하나의 연결에서 다중 요청 동시 처리 가능함, 헤더 압축과 서버 푸시 지원으로 성능 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. HTML 파싱과 DOM 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEPKQR/btsP73ySz08/4Tv0Y4FqtZaMrM8xjFokHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEPKQR/btsP73ySz08/4Tv0Y4FqtZaMrM8xjFokHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEPKQR/btsP73ySz08/4Tv0Y4FqtZaMrM8xjFokHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEPKQR%2FbtsP73ySz08%2F4Tv0Y4FqtZaMrM8xjFokHk%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;702&quot; height=&quot;474&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&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;1. 렌더링 엔진이 HTML 문서를 읽고 토큰 단위로 분해함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 토큰을 객체로 변환하여 노드를 생성(문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. DOM(Document Object Model) 트리 생성함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4(결과). DOM은 HTML 문서의 계층적 구조를 메모리에 표현한 객체 모델임&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 HTML 문서는 &amp;lt;html&amp;gt;, &amp;lt;body&amp;gt;, &amp;lt;h1&amp;gt; 요소를 노드로 가지는 DOM 트리 생성함&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. CSS 파싱과 CSSOM 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blO8NH/btsP6ICE2DD/oSpU4XCAj7oBYiIVrGIqFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blO8NH/btsP6ICE2DD/oSpU4XCAj7oBYiIVrGIqFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blO8NH/btsP6ICE2DD/oSpU4XCAj7oBYiIVrGIqFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblO8NH%2FbtsP6ICE2DD%2FoSpU4XCAj7oBYiIVrGIqFK%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;743&quot; height=&quot;273&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSS 파서가 스타일시트를 파싱하여 CSSOM(CSS Object Model) 트리 생성함&lt;/li&gt;
&lt;li&gt;CSSOM은 각 노드별 스타일 규칙을 담고 있음&lt;/li&gt;
&lt;li&gt;DOM과 결합하여 렌더 트리 생성에 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 렌더 트리 생성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdPHiU/btsP7eOrNic/LJVi5apcR9PRAkAwdcJRX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdPHiU/btsP7eOrNic/LJVi5apcR9PRAkAwdcJRX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdPHiU/btsP7eOrNic/LJVi5apcR9PRAkAwdcJRX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdPHiU%2FbtsP7eOrNic%2FLJVi5apcR9PRAkAwdcJRX1%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;790&quot; height=&quot;405&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM 트리와 CSSOM 트리 결합하여 렌더 트리 생성함&lt;/li&gt;
&lt;li&gt;렌더 트리는 실제 화면에 표시될 노드와 스타일 정보를 포함함&lt;/li&gt;
&lt;li&gt;보이지 않는 노드(&amp;lt;head&amp;gt;, display:none)는 렌더 트리에 포함되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 자바스크립트 파싱과 실행&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ku2rf/btsP6EfYKiZ/InZpioTJkbIuMqmb3Wxn6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ku2rf/btsP6EfYKiZ/InZpioTJkbIuMqmb3Wxn6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ku2rf/btsP6EfYKiZ/InZpioTJkbIuMqmb3Wxn6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKu2rf%2FbtsP6EfYKiZ%2FInZpioTJkbIuMqmb3Wxn6k%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;749&quot; height=&quot;352&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저의 JavaScript 엔진이 자바스크립트 코드 처리함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토크나이징(Tokenizing)&lt;/b&gt;: 소스 코드를 토큰 단위로 분해함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파싱(Parsing)&lt;/b&gt;: 토큰을 AST(Abstract Syntax Tree)로 변환함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행(Execution)&lt;/b&gt;: AST를 바이트코드로 컴파일하고 실행함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 리플로우와 리페인트&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBy9E/btsP9NB32dE/y8emqMiPKsUNJr6cHlIKq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBy9E/btsP9NB32dE/y8emqMiPKsUNJr6cHlIKq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBy9E/btsP9NB32dE/y8emqMiPKsUNJr6cHlIKq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBy9E%2FbtsP9NB32dE%2Fy8emqMiPKsUNJr6cHlIKq1%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;839&quot; height=&quot;237&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&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;리플로우(Reflow)&lt;/b&gt;: 레이아웃이 변경될 때 다시 렌더 트리 계산하는 과정임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리페인트(Repaint)&lt;/b&gt;: 색상, 배경 등 시각적 스타일 변경 시 발생함&lt;/li&gt;
&lt;li&gt;리플로우가 성능에 더 큰 영향을 미침&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 자바스크립트 파싱에 의한 HTML 파싱중단&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 &amp;lt;script&amp;gt; 태그 만나면 HTML 파싱 중단하고 스크립트 실행함&lt;/li&gt;
&lt;li&gt;DOM 생성 지연으로 초기 렌더링 속도 저하 원인 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. script 태그의 async/defer 어트리뷰트&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;async&lt;/b&gt;: 스크립트 다운로드와 HTML 파싱 병렬 처리, 다운로드 완료 시 즉시 실행함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7L48w/btsP93re3Ty/jeYYZo8bZbrMOcBR0HmF4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7L48w/btsP93re3Ty/jeYYZo8bZbrMOcBR0HmF4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7L48w/btsP93re3Ty/jeYYZo8bZbrMOcBR0HmF4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7L48w%2FbtsP93re3Ty%2FjeYYZo8bZbrMOcBR0HmF4K%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;1277&quot; height=&quot;351&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;351&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;defer&lt;/b&gt;: 스크립트 다운로드 병렬 처리, HTML 파싱 끝난 뒤 실행함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTkyCb/btsP7cJROor/eF3mvfCG1C9ZqlF59COMJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTkyCb/btsP7cJROor/eF3mvfCG1C9ZqlF59COMJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTkyCb/btsP7cJROor/eF3mvfCG1C9ZqlF59COMJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTkyCb%2FbtsP7cJROor%2FeF3mvfCG1C9ZqlF59COMJ1%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;1258&quot; height=&quot;499&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;script.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;script.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&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;async는 다운로드 완료 순서대로 실행, defer는 HTML 파싱 완료 후 순서대로 실행함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&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;요청과 응답 &amp;rarr; HTML/CSS 파싱 &amp;rarr; DOM/CSSOM 생성 &amp;rarr; 렌더 트리 생성 &amp;rarr; 자바스크립트 실행 &amp;rarr; 리플로우&amp;middot;리페인트 순으로 렌더링 진행됨&lt;/li&gt;
&lt;li&gt;async, defer 속성 사용 시 HTML 파싱과 자바스크립트 실행 효율적으로 조정 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;DOM(Document Object Model)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 노드&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skSkW/btsP7e8Mq4A/1YSExOralHuXq7Rj35KXu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skSkW/btsP7e8Mq4A/1YSExOralHuXq7Rj35KXu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skSkW/btsP7e8Mq4A/1YSExOralHuXq7Rj35KXu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskSkW%2FbtsP7e8Mq4A%2F1YSExOralHuXq7Rj35KXu0%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;744&quot; height=&quot;147&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;237&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;1227&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RbpsN/btsP55dNRPR/1trNTStMFrPllGh91kie71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RbpsN/btsP55dNRPR/1trNTStMFrPllGh91kie71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RbpsN/btsP55dNRPR/1trNTStMFrPllGh91kie71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRbpsN%2FbtsP55dNRPR%2F1trNTStMFrPllGh91kie71%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;752&quot; height=&quot;196&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM은 문서의 계층적 구조와 정보를 표현하는 트리 구조 모델임&lt;/li&gt;
&lt;li&gt;HTML 요소, 텍스트, 속성 등을 각각 노드(node)로 표현함&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;문서 노드(Document)&lt;/b&gt;: HTML 문서 전체 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요소 노드(Element)&lt;/b&gt;: HTML 태그 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;텍스트 노드(Text)&lt;/b&gt;: 요소 안의 텍스트 콘텐츠 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어트리뷰트 노드(Attribute)&lt;/b&gt;: 요소의 속성 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주석 노드(Comment)&lt;/b&gt;: 주석 데이터 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1 id=&quot;title&quot;&amp;gt;Hello&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 &amp;lt;h1&amp;gt;은 요소 노드, &quot;Hello&quot;는 텍스트 노드임&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sfUwJ/btsP8jhzw2d/Du99SixhCItGAg8TNECCQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sfUwJ/btsP8jhzw2d/Du99SixhCItGAg8TNECCQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sfUwJ/btsP8jhzw2d/Du99SixhCItGAg8TNECCQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsfUwJ%2FbtsP8jhzw2d%2FDu99SixhCItGAg8TNECCQ0%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;1296&quot; height=&quot;709&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문서노드는&amp;nbsp; Document, HTMLDocument 인터페이스를 상속받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어트리뷰트 노드는 Attr을 상속받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스트 노드는 CharacterData 인터페이스를 상속받&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 요소 노드 취득&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바스크립트에서 DOM 요소 접근 방법&lt;/li&gt;
&lt;li&gt;getElementById(id): ID로 요소 선택&lt;/li&gt;
&lt;li&gt;getElementsByTagName(tag): 태그 이름으로 요소 컬렉션 선택&lt;/li&gt;
&lt;li&gt;getElementsByClassName(class): 클래스 이름으로 요소 컬렉션 선택&lt;/li&gt;
&lt;li&gt;querySelector(selector): CSS 선택자로 첫 번째 요소 반환&lt;/li&gt;
&lt;li&gt;querySelectorAll(selector): CSS 선택자로 모든 요소 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;const title = document.getElementById('title');
const items = document.querySelectorAll('.item');
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 노드 탐색&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyS4qP/btsP61Weeip/6D2gPkkfG7kQTtKyiLbDl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyS4qP/btsP61Weeip/6D2gPkkfG7kQTtKyiLbDl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyS4qP/btsP61Weeip/6D2gPkkfG7kQTtKyiLbDl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyS4qP%2FbtsP61Weeip%2F6D2gPkkfG7kQTtKyiLbDl1%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;750&quot; height=&quot;340&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;667&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;1339&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjo7Q0/btsP6WtMov0/amE6n9vfdkO4MvmTXjuYPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjo7Q0/btsP6WtMov0/amE6n9vfdkO4MvmTXjuYPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjo7Q0/btsP6WtMov0/amE6n9vfdkO4MvmTXjuYPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjo7Q0%2FbtsP6WtMov0%2FamE6n9vfdkO4MvmTXjuYPk%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;1339&quot; height=&quot;521&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;HTML 공백문자를 제거하면 공백 텍스트 노드를 생성하지 않지만&lt;/b&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;노드 간 관계를 통해 DOM 트리 탐색 가능함&lt;/li&gt;
&lt;li&gt;parentNode: 부모 노드 반환&lt;/li&gt;
&lt;li&gt;childNodes: 자식 노드 반환 (텍스트 노드 포함)&lt;/li&gt;
&lt;li&gt;children: 자식 요소 노드만 반환&lt;/li&gt;
&lt;li&gt;firstChild, lastChild: 첫/마지막 자식 노드 반환&lt;/li&gt;
&lt;li&gt;previousSibling, nextSibling: 형제 노드 탐색&lt;/li&gt;
&lt;li&gt;previousElementSibling, nextElementSibling: 형제 요소 노드 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const parent = title.parentNode;
const first = parent.firstChild;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 노드 정보 취득&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BVRrJ/btsP7voPWXn/NNUGjObfquYLUSlVFKuVH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BVRrJ/btsP7voPWXn/NNUGjObfquYLUSlVFKuVH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BVRrJ/btsP7voPWXn/NNUGjObfquYLUSlVFKuVH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBVRrJ%2FbtsP7voPWXn%2FNNUGjObfquYLUSlVFKuVH0%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;723&quot; height=&quot;300&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nodeType: 노드 유형 숫자 반환&lt;/li&gt;
&lt;li&gt;nodeName: 노드 이름 반환&lt;/li&gt;
&lt;li&gt;nodeValue: 텍스트 노드 값 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;console.log(title.nodeType); // 1 (요소 노드)
console.log(title.nodeName); // H1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 요소 노드의 텍스트 조작&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;textContent: 요소 내 모든 텍스트 콘텐츠 접근&lt;/li&gt;
&lt;li&gt;innerText: 렌더링된 텍스트만 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;title.textContent = 'Welcome';
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. DOM 조작&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요소 생성: document.createElement(tagName)&lt;/li&gt;
&lt;li&gt;텍스트 노드 생성: document.createTextNode(text)&lt;/li&gt;
&lt;li&gt;노드 추가: appendChild(node), insertBefore(new, ref)&lt;/li&gt;
&lt;li&gt;노드 제거: removeChild(node)&lt;/li&gt;
&lt;li&gt;노드 교체: replaceChild(new, old)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;const newDiv = document.createElement('div');
document.body.appendChild(newDiv);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. innerHTML&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요소 내부 HTML 마크업을 문자열로 접근 및 변경 가능함&lt;/li&gt;
&lt;li&gt;기존 콘텐츠 모두 제거하고 새로운 HTML 파싱하여 DOM으로 변환함&lt;/li&gt;
&lt;li&gt;XSS(교차 스크립팅 공격) 주의 필요함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;웹 사이트의 어드민(관리자)이 아닌 악의적인 목적을 가진 제 3자가 악성 스크립트를 삽입하여 의도하지 않은 명령을 실행시키거나 세션 등을 탈취할 수 있는 취약점&lt;/u&gt;&lt;u&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;예시&lt;/u&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: #000000;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;alert(&quot;hi&quot;)&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;lt;/script&amp;gt; : &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;대놓고 script 태그를 넣어 alert를 실행시키는 구문. &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;이 구문이 실행된다면 해당 사이트는 XSS에 대한 대책이 전혀 없는 것이라 봐도 무방&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;lt;scr&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;ipt&amp;gt;alert(&quot;hi&quot;);&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;lt;/scr&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;ipt&amp;gt; : &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;&amp;lt;script&amp;gt; 태그를 일차적으로 필터링하는 것을 우회하기 위한 구문, &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;중첩된 script 태그가 필터링되면 갈라져있던 script 태그가 결합되면서 구문이 실행&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;onmouseover&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&quot;alert('hi')&quot;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;gt; : &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;a 태그를 이용한 공격입니다. onmouseover 이벤트를 통해 공격을 실행&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r74vk/btsP9aRQuVc/AXYUsY9YvF3TxVZeWj1z4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r74vk/btsP9aRQuVc/AXYUsY9YvF3TxVZeWj1z4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r74vk/btsP9aRQuVc/AXYUsY9YvF3TxVZeWj1z4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr74vk%2FbtsP9aRQuVc%2FAXYUsY9YvF3TxVZeWj1z4k%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;729&quot; height=&quot;251&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;document.body.innerHTML = '&amp;lt;h2&amp;gt;New Content&amp;lt;/h2&amp;gt;';
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 어트리뷰트&lt;/h2&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;getAttribute(name), setAttribute(name, value), hasAttribute(name), removeAttribute(name) 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const link = document.querySelector('a');
link.setAttribute('href', 'https://example.com');
&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;HTML 표준 속성은 프로퍼티처럼 직접 접근 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;link.href = 'https://example.com';
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 스타일&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;style 프로퍼티 통해 인라인 스타일 조작 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;title.style.color = 'red';
title.style.fontSize = '24px';
&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;CSS 클래스 조작 메서드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;classList.add(className)&lt;/li&gt;
&lt;li&gt;classList.remove(className)&lt;/li&gt;
&lt;li&gt;classList.toggle(className)&lt;/li&gt;
&lt;li&gt;classList.contains(className)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;title.classList.add('highlight');
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. DOM 표준&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;W3C와 WHATWG에서 DOM 표준 관리함&lt;/li&gt;
&lt;li&gt;현재는 WHATWG 단일 사양으로 통합됨&lt;/li&gt;
&lt;li&gt;DOM Level 1: 기본 구조와 접근 API 정의함&lt;/li&gt;
&lt;li&gt;DOM Level 2: 이벤트 모델, CSS 지원 추가함&lt;/li&gt;
&lt;li&gt;DOM Level 3: XPath, Load &amp;amp; Save, Keyboard 이벤트 추가함&lt;/li&gt;
&lt;li&gt;이후 HTML5와 함께 지속 확장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&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;DOM은 문서 구조를 객체 모델로 표현하고 자바스크립트를 통해 동적 조작 가능하게 함&lt;/li&gt;
&lt;li&gt;노드 탐색, 조작, 속성 변경, 스타일 제어 등 풍부한 API 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;이벤트(Event)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이벤트 드리븐 프로그래밍&lt;/h2&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;브라우저 환경에서는 사용자 입력, 네트워크 응답, DOM 변화 등이 이벤트임&lt;/li&gt;
&lt;li&gt;이벤트 발생 시 미리 등록한 이벤트 핸들러 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이벤트 타입&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마우스 이벤트: click, dblclick, mousedown, mouseup, mousemove, mouseover 등&lt;/li&gt;
&lt;li&gt;키보드 이벤트: keydown, keyup, keypress&lt;/li&gt;
&lt;li&gt;폼 이벤트: submit, change, input, focus, blur&lt;/li&gt;
&lt;li&gt;윈도우 이벤트: load, resize, scroll, unload&lt;/li&gt;
&lt;li&gt;드래그 &amp;amp; 드롭 이벤트: dragstart, dragover, drop 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgYsv/btsP9aj44rJ/F2iW3rVAO49knkE8IxuMA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgYsv/btsP9aj44rJ/F2iW3rVAO49knkE8IxuMA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgYsv/btsP9aj44rJ/F2iW3rVAO49knkE8IxuMA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgYsv%2FbtsP9aj44rJ%2FF2iW3rVAO49knkE8IxuMA0%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;1204&quot; height=&quot;489&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;489&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;1152&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UQX2g/btsP6YZvfLu/VWP8OGKRiv7uuH4JAuMLXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UQX2g/btsP6YZvfLu/VWP8OGKRiv7uuH4JAuMLXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UQX2g/btsP6YZvfLu/VWP8OGKRiv7uuH4JAuMLXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUQX2g%2FbtsP6YZvfLu%2FVWP8OGKRiv7uuH4JAuMLXK%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;816&quot; height=&quot;112&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;158&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;1176&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl9nPb/btsP8v3dLWR/wa8kpMT9ZS0RU9FB4dWnX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl9nPb/btsP8v3dLWR/wa8kpMT9ZS0RU9FB4dWnX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl9nPb/btsP8v3dLWR/wa8kpMT9ZS0RU9FB4dWnX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl9nPb%2FbtsP8v3dLWR%2Fwa8kpMT9ZS0RU9FB4dWnX0%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;821&quot; height=&quot;359&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;514&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;1153&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPV064/btsP8YKPWym/UfWBIX47DGS3etcj5MnbtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPV064/btsP8YKPWym/UfWBIX47DGS3etcj5MnbtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPV064/btsP8YKPWym/UfWBIX47DGS3etcj5MnbtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPV064%2FbtsP8YKPWym%2FUfWBIX47DGS3etcj5MnbtK%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;835&quot; height=&quot;227&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;314&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;1169&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y4hKg/btsP6LM0Gcg/Vq618CRGQMhOQ6qPImz4F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y4hKg/btsP6LM0Gcg/Vq618CRGQMhOQ6qPImz4F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y4hKg/btsP6LM0Gcg/Vq618CRGQMhOQ6qPImz4F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY4hKg%2FbtsP6LM0Gcg%2FVq618CRGQMhOQ6qPImz4F1%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;844&quot; height=&quot;213&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;295&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;1159&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4g17y/btsP8wOCcHW/FgzaeAvBxQx4d0ulQqLy7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4g17y/btsP8wOCcHW/FgzaeAvBxQx4d0ulQqLy7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4g17y/btsP8wOCcHW/FgzaeAvBxQx4d0ulQqLy7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4g17y%2FbtsP8wOCcHW%2FFgzaeAvBxQx4d0ulQqLy7k%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;840&quot; height=&quot;87&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;120&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;1164&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daG3gb/btsP74xVgnJ/Vk3ZFuHIPxVlbbSe4rMsTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daG3gb/btsP74xVgnJ/Vk3ZFuHIPxVlbbSe4rMsTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daG3gb/btsP74xVgnJ/Vk3ZFuHIPxVlbbSe4rMsTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaG3gb%2FbtsP74xVgnJ%2FVk3ZFuHIPxVlbbSe4rMsTK%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;850&quot; height=&quot;324&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;444&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;1164&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed4coj/btsP8Ohhhrx/U4fdUiXKK2kA70nQPJ2lI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed4coj/btsP8Ohhhrx/U4fdUiXKK2kA70nQPJ2lI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed4coj/btsP8Ohhhrx/U4fdUiXKK2kA70nQPJ2lI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed4coj%2FbtsP8Ohhhrx%2FU4fdUiXKK2kA70nQPJ2lI1%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;850&quot; height=&quot;324&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;444&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;1181&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3heI/btsP6WAFKpy/kTV1Hv2lV0KJEPDhC88k4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3heI/btsP6WAFKpy/kTV1Hv2lV0KJEPDhC88k4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3heI/btsP6WAFKpy/kTV1Hv2lV0KJEPDhC88k4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3heI%2FbtsP6WAFKpy%2FkTV1Hv2lV0KJEPDhC88k4K%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;843&quot; height=&quot;663&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;document.body.addEventListener('click', () =&amp;gt; console.log('clicked'));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이벤트 핸들러 등록&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Zlfa/btsP8GjhJJM/qk8COTLysd4uFqzNfdepmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Zlfa/btsP8GjhJJM/qk8COTLysd4uFqzNfdepmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Zlfa/btsP8GjhJJM/qk8COTLysd4uFqzNfdepmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Zlfa%2FbtsP8GjhJJM%2Fqk8COTLysd4uFqzNfdepmk%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;1214&quot; height=&quot;235&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&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;HTML 속성 방식&lt;/b&gt;: 요소 태그에 on이벤트명 속성으로 등록&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DOM 프로퍼티 방식&lt;/b&gt;: 요소의 이벤트 프로퍼티에 함수 할당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;addEventListener 메서드&lt;/b&gt;: 한 요소에 여러 핸들러 등록 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;alert('clicked')&quot;&amp;gt;Click Me&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const btn = document.querySelector('button');
btn.onclick = () =&amp;gt; console.log('clicked');
btn.addEventListener('click', () =&amp;gt; console.log('another click'));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 이벤트 핸들러 제거&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 속성 방식과 DOM 프로퍼티 방식은 핸들러를 null 할당으로 제거함&lt;/li&gt;
&lt;li&gt;addEventListener 방식은 같은 함수 참조 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function handleClick() { console.log('clicked'); }
btn.addEventListener('click', handleClick);
btn.removeEventListener('click', handleClick);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 이벤트 객체&lt;/h2&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;/li&gt;
&lt;li&gt;주요 프로퍼티: type, target, currentTarget, timeStamp, key, clientX, clientY 등&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;btn.addEventListener('click', e =&amp;gt; {
  console.log(e.type); // click
  console.log(e.target); // 이벤트 발생 요소
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 이벤트 전파&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgLj28/btsP80WeiGv/N5aFOrniGY3v4ty9JeU2YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgLj28/btsP80WeiGv/N5aFOrniGY3v4ty9JeU2YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgLj28/btsP80WeiGv/N5aFOrniGY3v4ty9JeU2YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgLj28%2FbtsP80WeiGv%2FN5aFOrniGY3v4ty9JeU2YK%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;706&quot; height=&quot;433&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&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;: 최상위 요소에서 이벤트 대상까지 내려감&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타깃 단계&lt;/b&gt;: 이벤트 대상 도달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버블링 단계&lt;/b&gt;: 이벤트 대상에서 최상위 요소까지 전파&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const parent = document.querySelector('#parent');
parent.addEventListener('click', () =&amp;gt; console.log('parent'));
btn.addEventListener('click', () =&amp;gt; console.log('button'));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이벤트 위임&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;document.querySelector('#list').addEventListener('click', e =&amp;gt; {
  if (e.target.tagName === 'LI') console.log(e.target.textContent);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. DOM 요소의 기본 동작 조작&lt;/h2&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;preventDefault() 메서드 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;document.querySelector('a').addEventListener('click', e =&amp;gt; {
  e.preventDefault();
  console.log('링크 동작 취소됨');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 이벤트 핸들러 내부의 this&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 속성 방식: 이벤트 핸들러 내부 this는 해당 요소 참조함&lt;/li&gt;
&lt;li&gt;DOM 프로퍼티 / addEventListener: this는 이벤트를 바인딩한 요소 참조함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;btn.addEventListener('click', function() {
  console.log(this === btn); // true
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 이벤트 핸들러에 인수 전달&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 호출 시 이벤트 객체 대신 undefined 전달되므로 주의 필요함&lt;/li&gt;
&lt;li&gt;래퍼 함수 사용으로 인수 전달 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;btn.addEventListener('click', e =&amp;gt; handleClickWithArgs(e, 123));
function handleClickWithArgs(e, id) { console.log(id); }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 커스텀 이벤트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CustomEvent 생성자 사용해 사용자 정의 이벤트 생성 가능함&lt;/li&gt;
&lt;li&gt;dispatchEvent() 메서드로 이벤트 발생시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const custom = new CustomEvent('myEvent', { detail: { key: 'value' } });
btn.addEventListener('myEvent', e =&amp;gt; console.log(e.detail));
btn.dispatchEvent(custom);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&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;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;타이머&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 호출 스케줄링&lt;/h2&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;/li&gt;
&lt;li&gt;호출 스케줄링은 특정 함수 실행 시점 예약하는 기능임&lt;/li&gt;
&lt;li&gt;브라우저 환경에서는 Web API 타이머 함수 활용함&lt;/li&gt;
&lt;li&gt;지정한 시간 경과 후 콜백 함수 실행 예약 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 타이머 함수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;setTimeout&lt;/h3&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;타이머 ID 반환, clearTimeout으로 취소 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const timerId = setTimeout(() =&amp;gt; console.log('3초 후 실행'), 3000);
clearTimeout(timerId);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;setInterval&lt;/h3&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;타이머 ID 반환, clearInterval로 취소 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const intervalId = setInterval(() =&amp;gt; console.log('1초마다 실행'), 1000);
clearInterval(intervalId);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;requestAnimationFrame&lt;/h3&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;/li&gt;
&lt;li&gt;setInterval 대비 성능 최적화 장점 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function animate() {
  console.log('프레임마다 실행');
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 디바운스와 스로틀&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디바운스(Debounce)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc1LiE/btsP6dbyqVi/hURg9bcKFe1t9UUBa4plF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc1LiE/btsP6dbyqVi/hURg9bcKFe1t9UUBa4plF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc1LiE/btsP6dbyqVi/hURg9bcKFe1t9UUBa4plF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc1LiE%2FbtsP6dbyqVi%2FhURg9bcKFe1t9UUBa4plF1%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;741&quot; height=&quot;218&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&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;li&gt;연속 입력 종료 시점에 한 번만 실행되게 함&lt;/li&gt;
&lt;li&gt;주로 검색창 입력 처리에 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() =&amp;gt; fn.apply(this, args), delay);
  }
}

const processChange = debounce(() =&amp;gt; console.log('입력 완료'), 500);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스로틀(Throttle)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfo7BY/btsP5EHfmPr/qiK5vWWNWB6RkuHy8QQwKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfo7BY/btsP5EHfmPr/qiK5vWWNWB6RkuHy8QQwKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfo7BY/btsP5EHfmPr/qiK5vWWNWB6RkuHy8QQwKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfo7BY%2FbtsP5EHfmPr%2FqiK5vWWNWB6RkuHy8QQwKK%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;754&quot; height=&quot;213&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&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;li&gt;스크롤 이벤트 같은 고빈도 이벤트 최적화에 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function throttle(fn, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() =&amp;gt; inThrottle = false, limit);
    }
  }
}

const handleScroll = throttle(() =&amp;gt; console.log('스크롤 중'), 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setTimeout, setInterval, requestAnimationFrame으로 호출 스케줄링 가능함&lt;/li&gt;
&lt;li&gt;디바운스와 스로틀 기법으로 이벤트 처리 최적화 가능함&lt;/li&gt;
&lt;li&gt;불필요한 연산 줄이고 성능 개선에 기여함&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>개발</category>
      <category>자바스크립트</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/17</guid>
      <comments>https://sjindev.tistory.com/17#entry17comment</comments>
      <pubDate>Wed, 27 Aug 2025 13:16:18 +0900</pubDate>
    </item>
    <item>
      <title>[모던 자바스크립트 DeepDive] Js 심화 스터디 week 7</title>
      <link>https://sjindev.tistory.com/16</link>
      <description>&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwCd3T/btsP6E1htwt/i3chKsdrqlv3a6hnQAZarK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwCd3T/btsP6E1htwt/i3chKsdrqlv3a6hnQAZarK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwCd3T/btsP6E1htwt/i3chKsdrqlv3a6hnQAZarK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwCd3T%2FbtsP6E1htwt%2Fi3chKsdrqlv3a6hnQAZarK%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;667&quot; height=&quot;360&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;심벌 (Symbol)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 심벌값의 생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;심벌은 ES6에서 도입된 원시 타입 중 하나임&lt;/li&gt;
&lt;li&gt;Symbol() 함수 호출로 생성함&lt;/li&gt;
&lt;li&gt;생성된 심벌값은 유일무이함&lt;/li&gt;
&lt;li&gt;new 연산자 사용 불가&lt;/li&gt;
&lt;li&gt;심벌값은 문자열로 자동 변환되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const sym1 = Symbol('desc');
const sym2 = Symbol('desc');
console.log(sym1 === sym2); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 심벌과 상수&lt;/h2&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;주로 enum 대체 용도로 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;const DIRECTION = {
  UP: Symbol('up'),
  DOWN: Symbol('down')
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 심벌과 프로퍼티 키&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const key = Symbol('key');
const obj = {
  [key]: 'value'
};
console.log(obj[key]); // value
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 심벌과 프로퍼티 은닉&lt;/h2&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;for...in, Object.keys(), JSON.stringify() 등에서 제외됨&lt;/li&gt;
&lt;li&gt;은닉 프로퍼티처럼 사용 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const secret = Symbol('secret');
const user = {
  name: 'Alice',
  [secret]: 'hidden'
};
console.log(Object.keys(user)); // ['name']
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 심벌과 표준 빌트인 객체 확장&lt;/h2&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const custom = Symbol('custom');
Array.prototype[custom] = function() {
  return 'custom method';
};
const arr = [];
console.log(arr[custom]()); // custom method
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Well-known Symbol&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심벌 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.iterator&lt;/td&gt;
&lt;td&gt;for...of 루프 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.asyncIterator&lt;/td&gt;
&lt;td&gt;비동기 반복자 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.toStringTag&lt;/td&gt;
&lt;td&gt;객체의 기본 toString 태그 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.hasInstance&lt;/td&gt;
&lt;td&gt;instanceof 연산자 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.isConcatSpreadable&lt;/td&gt;
&lt;td&gt;배열 concat 시 전개 여부 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.species&lt;/td&gt;
&lt;td&gt;파생 객체 생성자 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.match&lt;/td&gt;
&lt;td&gt;문자열 매칭 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.replace&lt;/td&gt;
&lt;td&gt;문자열 치환 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.search&lt;/td&gt;
&lt;td&gt;문자열 검색 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol.split&lt;/td&gt;
&lt;td&gt;문자열 분할 동작 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;class Custom {
  get [Symbol.toStringTag]() {
    return 'CustomObject';
  }
}
const c = new Custom();
console.log(Object.prototype.toString.call(c)); // [object CustomObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;이터러블(Iterable)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이터레이션 프로토콜&lt;/h2&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;: 객체가 Symbol.iterator 메서드를 가져야 함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이터레이터 프로토콜&lt;/b&gt;: next() 메서드를 호출할 때 { value, done } 형태 객체 반환해야 함&lt;/li&gt;
&lt;li&gt;done이 true이면 반복 종료 의미함&lt;/li&gt;
&lt;li&gt;자바스크립트의 순회 가능한 객체는 이 두 프로토콜을 모두 준수함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 빌트인 이터러블&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션(NodeList 등) 등이 빌트인 이터러블 객체임&lt;/li&gt;
&lt;li&gt;이들은 모두 for...of문, 스프레드 문법, 배열 디스트럭처링 등에 사용 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'hello';
for (const char of str) {
  console.log(char);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. for ...of 문&lt;/h2&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;/li&gt;
&lt;li&gt;for...in은 열거 가능한 속성 순회, for...of는 값 자체 순회&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [10, 20, 30];
for (const val of arr) {
  console.log(val);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 이터러블과 유사 배열 객체&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유사 배열 객체(Array-like Object)는 length 프로퍼티를 가진 객체임&lt;/li&gt;
&lt;li&gt;예: arguments, NodeList 등&lt;/li&gt;
&lt;li&gt;배열 메서드 사용 불가, 이터러블이 아닐 수도 있음&lt;/li&gt;
&lt;li&gt;스프레드 문법이나 Array.from()으로 배열 변환 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function test() {
  console.log(arguments); // 유사 배열 객체
  const arr = Array.from(arguments);
  console.log(arr.map(x =&amp;gt; x * 2));
}
test(1, 2, 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 이터레이션 프로토콜의 필요성&lt;/h2&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;/li&gt;
&lt;li&gt;ES6의 스프레드 문법, for...of, 디스트럭처링 할당, Promise.all 등에서 활용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const set = new Set([1, 2, 3]);
console.log([...set]); // [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 사용자 정의 이터러블&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 Symbol.iterator 메서드 구현하여 이터러블 객체 생성 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;const customIterable = {
  values: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const values = this.values;
    return {
      next() {
        if (index &amp;lt; values.length) {
          return { value: values[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const val of customIterable) {
  console.log(val);
}
&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;위 예시에서 customIterable 객체는 직접 이터레이터를 구현했기 때문에 for...of문 사용 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;스프레드 문법(Spread Syntax)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 함수 호출문의 인수 목록에서 사용하는 경우&lt;/h2&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;/li&gt;
&lt;li&gt;Function.prototype.apply()를 대체하는 간결한 문법 제공함&lt;/li&gt;
&lt;li&gt;함수 호출 시 가독성 향상 및 코드 간결화 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
&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;Math.max() 같이 여러 인수를 받는 함수에도 사용 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const values = [10, 20, 30];
console.log(Math.max(...values)); // 30
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 배열 리터럴 내부에서 사용하는 경우&lt;/h2&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;concat() 메서드 없이도 간결하게 배열 병합 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4]
&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const original = [1, 2, 3];
const copy = [...original];
console.log(copy); // [1, 2, 3]
console.log(original === copy); // false
&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;const str = 'hello';
console.log([...str]); // ['h', 'e', 'l', 'l', 'o']
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 객체 리터럴 내부에서 사용하는 경우&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES2018에서 도입된 기능임&lt;/li&gt;
&lt;li&gt;기존 객체를 전개하여 새로운 객체 생성 가능함&lt;/li&gt;
&lt;li&gt;객체 병합 시 Object.assign()을 대체함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 3, c: 4 }
&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;const nested = { x: { y: 1 } };
const copyNested = { ...nested };
copyNested.x.y = 2;
console.log(nested.x.y); // 2 (얕은 복사로 인한 영향)
&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;const user = { name: 'Alice', age: 20 };
const updated = { ...user, age: 21, city: 'Seoul' };
console.log(updated); // { name: 'Alice', age: 21, city: 'Seoul' }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;디스트럭처링 할당(Destructuring Assignment)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 배열 디스트럭처링 할당&lt;/h2&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;/li&gt;
&lt;li&gt;코드 간결화와 가독성 향상에 유용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본값 할당&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열 요소가 undefined인 경우 기본값 할당 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const [x, y, z = 5] = [10, 20];
console.log(x, y, z); // 10 20 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필요 없는 요소 무시&lt;/h3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나머지 요소 할당(Rest)&lt;/h3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const [head, ...rest] = [1, 2, 3, 4];
console.log(head); // 1
console.log(rest); // [2, 3, 4]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 객체 디스트럭처링 할당&lt;/h2&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const user = { name: 'Alice', age: 25 };
const { name, age } = user;
console.log(name, age); // Alice 25
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로퍼티 이름 변경&lt;/h3&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;pre class=&quot;pf&quot;&gt;&lt;code&gt;const { name: userName, age: userAge } = user;
console.log(userName, userAge); // Alice 25
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본값 할당&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로퍼티가 undefined이면 기본값 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const { city = 'Seoul' } = user;
console.log(city); // Seoul
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중첩 객체 디스트럭처링&lt;/h3&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;pre class=&quot;groovy&quot;&gt;&lt;code&gt;const person = { info: { firstName: 'Bob', lastName: 'Smith' } };
const { info: { firstName, lastName } } = person;
console.log(firstName, lastName); // Bob Smith
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나머지 프로퍼티 할당(Rest)&lt;/h3&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;pre class=&quot;groovy&quot;&gt;&lt;code&gt;const { name: n, ...others } = { name: 'Alice', age: 25, city: 'Seoul' };
console.log(n); // Alice
console.log(others); // { age: 25, city: 'Seoul' }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Set과 Map&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Set&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set 객체는 중복되지 않는 유일한 값들의 집합임&lt;/li&gt;
&lt;li&gt;원시값과 객체 참조 모두 저장 가능함&lt;/li&gt;
&lt;li&gt;삽입 순서를 기억함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const set = new Set([1, 2, 3, 3]);
console.log(set); // Set(3) {1, 2, 3}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;set.add(value) : 값 추가&lt;/li&gt;
&lt;li&gt;set.has(value) : 값 존재 여부 확인&lt;/li&gt;
&lt;li&gt;set.delete(value) : 특정 값 삭제&lt;/li&gt;
&lt;li&gt;set.clear() : 모든 요소 제거&lt;/li&gt;
&lt;li&gt;set.size : 요소 개수 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const set = new Set();
set.add(1);
set.add(2);
console.log(set.has(1)); // true
set.delete(1);
console.log(set.size); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Set 순회&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for...of, forEach 모두 사용 가능함&lt;/li&gt;
&lt;li&gt;keys(), values(), entries() 메서드 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;const set = new Set([1, 2, 3]);
for (const val of set) {
  console.log(val);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;활용&lt;/h3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const numbers = [1, 2, 2, 3];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Map&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Map 객체는 키-값 쌍으로 이루어진 컬렉션임&lt;/li&gt;
&lt;li&gt;키에는 객체, 함수, 원시값 모두 가능함&lt;/li&gt;
&lt;li&gt;삽입 순서를 기억함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map.get('key1')); // value1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map.set(key, value) : 키-값 쌍 추가&lt;/li&gt;
&lt;li&gt;map.get(key) : 키에 해당하는 값 반환&lt;/li&gt;
&lt;li&gt;map.has(key) : 키 존재 여부 확인&lt;/li&gt;
&lt;li&gt;map.delete(key) : 키와 값 삭제&lt;/li&gt;
&lt;li&gt;map.clear() : 모든 요소 제거&lt;/li&gt;
&lt;li&gt;map.size : 요소 개수 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const map = new Map();
map.set('a', 1);
map.set('b', 2);
console.log(map.has('a')); // true
map.delete('a');
console.log(map.size); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Map 순회&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for...of문, forEach 메서드 사용 가능함&lt;/li&gt;
&lt;li&gt;keys(), values(), entries() 메서드 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const map = new Map([
  ['name', 'Alice'],
  ['age', 25]
]);
for (const [key, value] of map) {
  console.log(key, value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체와의 차이점&lt;/h3&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;Map은 키 타입 제한 없음&lt;/li&gt;
&lt;li&gt;요소 개수는 map.size 사용, 객체는 Object.keys(obj).length 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const obj = { a: 1 };
obj[1] = 'number key';
console.log(obj); // { '1': 'number key', a: 1 }

const map = new Map();
map.set(1, 'number key');
map.set({x: 10}, 'object key');
console.log(map);
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>개발</category>
      <category>자바스크립트</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/16</guid>
      <comments>https://sjindev.tistory.com/16#entry16comment</comments>
      <pubDate>Wed, 27 Aug 2025 11:40:54 +0900</pubDate>
    </item>
    <item>
      <title>[모던 자바스크립트 DeepDive] Js 심화 스터디 week 6</title>
      <link>https://sjindev.tistory.com/15</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOORxW/btsPO78DLE8/ZCQq7geJuPyqSde03o37xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOORxW/btsPO78DLE8/ZCQq7geJuPyqSde03o37xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOORxW/btsPO78DLE8/ZCQq7geJuPyqSde03o37xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOORxW%2FbtsPO78DLE8%2FZCQq7geJuPyqSde03o37xk%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;643&quot; height=&quot;347&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ES6 함수의 추가기능&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수의 구분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6 이후 자바스크립트의 함수는 의도와 동작 방식에 따라 명확하게 구분됨.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 함수의 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 함수 (Normal Function)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;function 키워드를 사용해 선언&lt;/li&gt;
&lt;li&gt;생성자 함수로 사용 가능 (new 가능)&lt;/li&gt;
&lt;li&gt;호출 방식에 따라 this 바인딩이 달라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 (Method)&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;li&gt;super 키워드 사용 가능&lt;/li&gt;
&lt;li&gt;생성자 함수로 사용 불가 (new 불가)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수 (Arrow Function)&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;li&gt;this 바인딩이 없고 상위 스코프를 참조 (렉시컬 this)&lt;/li&gt;
&lt;li&gt;생성자 함수로 사용 불가&lt;br /&gt;&lt;br /&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://sjindev.tistory.com/1&quot;&gt;화살표 함수 (feat : 함수표현식 vs 함수선언식)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수 (Constructor Function)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new 연산자와 함께 호출되어 인스턴스를 생성&lt;/li&gt;
&lt;li&gt;클래스 문법의 기반&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function normalFunc() {
  console.log(&quot;일반 함수&quot;);
}

const obj = {
  method() {
    console.log(&quot;메서드&quot;);
  }
};

const arrow = () =&amp;gt; console.log(&quot;화살표 함수&quot;);

new normalFunc(); // OK
// new obj.method(); // TypeError
// new arrow(); // TypeError&lt;/code&gt;&lt;/pre&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;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const person = {
  name: &quot;KSJ&quot;,
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.sayHello(); // Hello, my name is KSJ&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;li&gt;super 키워드를 사용해 부모 객체 메서드 호출 가능&lt;/li&gt;
&lt;li&gt;생성자 함수처럼 new로 호출 불가.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const parent = {
  greet() {
    return &quot;Hello&quot;;
  }
};

const child = {
  greet() {
    return `${super.greet()} World`;
  }
};

Object.setPrototypeOf(child, parent);
console.log(child.greet()); // Hello World&lt;/code&gt;&lt;/pre&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const add = (a, b) =&amp;gt; a + b;
console.log(add(2, 3)); // 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;this, arguments, super, new.target을 바인딩하지 않음.&lt;/li&gt;
&lt;li&gt;항상 상위 스코프의 this를 참조 (렉시컬 this).&lt;/li&gt;
&lt;li&gt;메서드로 사용하면 안 됨 (객체 메서드 내부의 this 참조 불가).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const obj = {
  value: 42,
  arrow: () =&amp;gt; console.log(this.value),
  normal: function () {
    console.log(this.value);
  }
};

obj.arrow(); // undefined (전역 this 참조)
obj.normal(); // 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표함수 vs 일반함수&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;일반 함수&lt;/th&gt;
&lt;th&gt;화살표 함수&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;this&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호출 방식에 따라 동적 바인딩&lt;/td&gt;
&lt;td&gt;상위 스코프의 &lt;code&gt;this&lt;/code&gt;(렉시컬 바인딩)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;arguments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전달된 인수 유사배열로 접근 가능&lt;/td&gt;
&lt;td&gt;없음(상위 스코프의 arguments 참조)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;생성자 사용&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;super&lt;/code&gt; 사용&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 길이&lt;/td&gt;
&lt;td&gt;길어질 수 있음&lt;/td&gt;
&lt;td&gt;간결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Rest 파라미터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서 추가된 문법으로, 가변 인자를 배열로 한 번에 받을 수 있는 기능&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function sum(...numbers) {
  return numbers.reduce((acc, cur) =&amp;gt; acc + cur, 0);
}

console.log(sum(1, 2, 3, 4)); // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;li&gt;함수의 length 프로퍼티에 영향을 주지 않음.&lt;/li&gt;
&lt;li&gt;arguments 객체보다 직관적&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function log(a, b, ...rest) {
  console.log(a);    // 첫 번째 인자
  console.log(b);    // 두 번째 인자
  console.log(rest); // 나머지 인자 배열
}

log(1, 2, 3, 4, 5);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;매개변수 기본값&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6부터 매개변수에 기본값을 직접 설정할 수 있음.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function greet(name = &quot;Guest&quot;) {
  console.log(`Hello, ${name}`);
}

greet(); // Hello, Guest
greet(&quot;KSJ&quot;); // Hello, KSJ&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;함수 호출 시 인자를 생략하거나 undefined를 전달하면 기본값 사용.&lt;/li&gt;
&lt;li&gt;기본값 표현식에서 다른 매개변수를 참조 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;배열&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자바스크립트 배열은 배열이 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 배열은자료구조에서 말하는 일반적인 배열(Array)과 다름.&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;전통적인 배열(C, Java)&lt;/b&gt;: 메모리상에 같은 타입의 데이터가 연속적으로 저장됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자바스크립트 배열&lt;/b&gt;: 해시 테이블 기반의 객체(Object)로, 인덱스를 키로 사용하는 &lt;b&gt;특수한 객체&lt;/b&gt;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, JS 배열은 &lt;b&gt;동일한 타입과 연속적인 메모리 구조&lt;/b&gt;를 강제하지 않으며, 다양한 타입의 데이터를 섞어 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const arr = [1, 'text', true, { key: 'value' }];
console.log(typeof arr); // 'object'&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. length 프로퍼티와 희소 배열&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 length 프로퍼티&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열의 &lt;code&gt;length&lt;/code&gt;는 요소 개수가 아니라 &lt;b&gt;가장 큰 인덱스 + 1&lt;/b&gt; 값.&lt;/li&gt;
&lt;li&gt;요소를 삭제하거나 인덱스를 건너뛰면 실제 요소 개수보다 &lt;code&gt;length&lt;/code&gt;가 더 클 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;const arr = [];
arr[3] = 'hello';
console.log(arr.length); // 4 (0~3 인덱스)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 희소 배열(Sparse Array)&lt;/h3&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;빈 요소는 &lt;code&gt;undefined&lt;/code&gt;가 아니라 &lt;b&gt;존재하지 않는 값&lt;/b&gt;으로 취급.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;const sparse = [1, , 3];
console.log(sparse.length); // 3
console.log(sparse[1]); // undefined (요소가 없음)
console.log(1 in sparse); // false&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 배열 생성(간단한 코드로 문법 확인)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 배열 리터럴&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 Array 생성자&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr1 = new Array(3); // 길이 3, 빈 요소
const arr2 = new Array(1, 2, 3); // 요소 1,2,3&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 Array.of()&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = Array.of(3); // [3]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 Array.from()&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const arr = Array.from('abc'); // ['a', 'b', 'c']&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 배열 요소의 참조&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;0부터 시작&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;존재하지 않는 인덱스 접근 시 &lt;code&gt;undefined&lt;/code&gt; 반환.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;const arr = ['a', 'b'];
console.log(arr[0]); // 'a'
console.log(arr[5]); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 배열 요소의 추가와 갱신&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2];
arr[2] = 3; // 추가
arr[1] = 20; // 갱신&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;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 배열 요소의 삭제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.1 delete 연산자&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, &amp;lt;empty&amp;gt;, 3]&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;code&gt;length&lt;/code&gt;는 변하지 않고 빈 슬롯 유지.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.2 splice() 사용 (권장)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
arr.splice(1, 1);
console.log(arr); // [1, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 배열 메서드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.1 변경 메서드 (원본을 수정)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;push()&lt;/code&gt;, &lt;code&gt;pop()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shift()&lt;/code&gt;, &lt;code&gt;unshift()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;splice()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort()&lt;/code&gt;, &lt;code&gt;reverse()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.2 비변경 메서드 (새 배열을 반환)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;concat()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;slice()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map()&lt;/code&gt;, &lt;code&gt;filter()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce()&lt;/code&gt;, &lt;code&gt;reduceRight()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 얕은 복사 vs 깊은 복사&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8.1 얕은 복사&lt;/h3&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;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2[0] = 99;
console.log(arr1[0]); // 99&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8.2 깊은 복사&lt;/h3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr1 = [1, 2, 3];
const arr2 = [...arr1];
arr2[0] = 99;
console.log(arr1[0]); // 1&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;code&gt;structuredClone()&lt;/code&gt; 또는 &lt;code&gt;lodash.cloneDeep()&lt;/code&gt; 권장.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 배열 고차 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차 함수(Higher-Order Function)는 함수를 인수로 받거나 함수를 반환하는 함수.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;forEach()&lt;/code&gt; : 단순 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map()&lt;/code&gt; : 요소 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter()&lt;/code&gt; : 조건 필터링&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce()&lt;/code&gt; : 누적 계산&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find()&lt;/code&gt;, &lt;code&gt;findIndex()&lt;/code&gt; : 조건 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const nums = [1, 2, 3, 4];
const doubled = nums.map(n =&amp;gt; n * 2); // [2, 4, 6, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. JSON.stringify 메서드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열이나 객체를 JSON 문자열로 변환.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const json = JSON.stringify(arr);
console.log(json); // &quot;[1,2,3]&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;code&gt;undefined&lt;/code&gt;는 무시됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const arr = [1, undefined, function(){}];
console.log(JSON.stringify(arr)); // [1,null,null]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&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;JS 배열은 &lt;b&gt;객체&lt;/b&gt;이지만, 인덱스 기반 데이터 관리에 최적화된 구조.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;length&lt;/code&gt;와 희소 배열 개념을 이해하면 예기치 못한 버그 방지 가능.&lt;/li&gt;
&lt;li&gt;원본 변경 여부에 따라 메서드를 구분해 사용.&lt;/li&gt;
&lt;li&gt;얕은 복사/깊은 복사 차이를 이해해야 참조 문제를 피할 수 있음.&lt;/li&gt;
&lt;li&gt;JSON 직렬화 시 &lt;code&gt;undefined&lt;/code&gt;와 함수는 제외됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Number, Math, Date&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Number&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Number 생성자 함수&lt;/h3&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;new 연산자와 함께 호출 시 Number 객체 생성&lt;/li&gt;
&lt;li&gt;new 없이 호출 시 숫자 타입 변환 수행&lt;/li&gt;
&lt;li&gt;예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;new Number(10); // Number 객체
Number('123'); // 숫자 123&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Number 프로퍼티&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Number.EPSILON&lt;/code&gt; : 1과 1보다 큰 수 중에서 가장 작은 수의 차이값&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.MAX_VALUE&lt;/code&gt; : 자바스크립트에서 표현 가능한 가장 큰 수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.MIN_VALUE&lt;/code&gt; : 자바스크립트에서 표현 가능한 가장 작은 수(0에 가장 가까운 양수)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt; : 안전하게 표현할 수 있는 최대 정수값 (2^53 - 1)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.MIN_SAFE_INTEGER&lt;/code&gt; : 안전하게 표현할 수 있는 최소 정수값 (-(2^53 - 1))&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.POSITIVE_INFINITY&lt;/code&gt; : 양의 무한대&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.NEGATIVE_INFINITY&lt;/code&gt; : 음의 무한대&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.NaN&lt;/code&gt; : Not-a-Number 값&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Number 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Number.isFinite()&lt;/code&gt; : 유한수 여부 판별&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.isInteger()&lt;/code&gt; : 정수 여부 판별&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.isNaN()&lt;/code&gt; : NaN 여부 판별&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.isSafeInteger()&lt;/code&gt; : 안전한 정수 여부 판별&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.parseFloat()&lt;/code&gt; : 문자열을 부동소수점 숫자로 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number.parseInt()&lt;/code&gt; : 문자열을 정수로 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toExponential()&lt;/code&gt; : 지수 표기법 문자열로 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toFixed()&lt;/code&gt; : 고정 소수점 표기 문자열로 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toPrecision()&lt;/code&gt; : 지정된 자릿수의 문자열로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Math&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Math 프로퍼티&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Math.PI&lt;/code&gt; : 원주율(&amp;pi;) 값&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.E&lt;/code&gt; : 자연로그의 밑(e)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.LN2&lt;/code&gt; : 2의 자연로그&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.LN10&lt;/code&gt; : 10의 자연로그&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.LOG2E&lt;/code&gt; : e의 밑이 2인 로그값&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.LOG10E&lt;/code&gt; : e의 밑이 10인 로그값&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Math 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Math.abs()&lt;/code&gt; : 절대값 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.ceil()&lt;/code&gt; : 올림&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.floor()&lt;/code&gt; : 내림&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.round()&lt;/code&gt; : 반올림&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.trunc()&lt;/code&gt; : 소수점 이하 버림&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.max()&lt;/code&gt; : 최대값 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.min()&lt;/code&gt; : 최소값 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.pow()&lt;/code&gt; : 거듭제곱 계산&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.sqrt()&lt;/code&gt; : 제곱근 계산&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.random()&lt;/code&gt; : 0 이상 1 미만 난수 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.sign()&lt;/code&gt; : 부호 반환 (양수 1, 음수 -1, 0)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Date&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Date 생성자 함수&lt;/h3&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;new Date() : 현재 날짜와 시간 반환&lt;/li&gt;
&lt;li&gt;new Date(milliseconds) : 1970-01-01 00:00:00 UTC부터 지정된 밀리초 경과 시점&lt;/li&gt;
&lt;li&gt;new Date(dateString) : 날짜 문자열 기반 객체 생성&lt;/li&gt;
&lt;li&gt;new Date(year, month[, day, hour, minute, second, millisecond]) : 지정된 날짜/시간 기반 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Date 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Getter 메서드&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;getFullYear()&lt;/code&gt; : 연도 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getMonth()&lt;/code&gt; : 월(0~11) 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getDate()&lt;/code&gt; : 일(1~31) 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getDay()&lt;/code&gt; : 요일(0~6) 반환 (일요일=0)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getHours()&lt;/code&gt; : 시 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getMinutes()&lt;/code&gt; : 분 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSeconds()&lt;/code&gt; : 초 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getMilliseconds()&lt;/code&gt; : 밀리초 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getTime()&lt;/code&gt; : 1970-01-01 00:00:00 UTC부터 경과한 밀리초&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getTimezoneOffset()&lt;/code&gt; : 로컬과 UTC 차이(분)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Setter 메서드&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;setFullYear()&lt;/code&gt; : 연도 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setMonth()&lt;/code&gt; : 월 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setDate()&lt;/code&gt; : 일 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setHours()&lt;/code&gt; : 시 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setMinutes()&lt;/code&gt; : 분 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setSeconds()&lt;/code&gt; : 초 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setMilliseconds()&lt;/code&gt; : 밀리초 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setTime()&lt;/code&gt; : 밀리초 기반 날짜 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;code&gt;toDateString()&lt;/code&gt; : 사람이 읽기 쉬운 날짜 문자열 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toTimeString()&lt;/code&gt; : 사람이 읽기 쉬운 시간 문자열 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toISOString()&lt;/code&gt; : ISO 8601 형식 문자열 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toLocaleDateString()&lt;/code&gt; : 지역화된 날짜 문자열 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toLocaleTimeString()&lt;/code&gt; : 지역화된 시간 문자열 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;RegExp&lt;/h1&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 정규표현식이란?&lt;/h2&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;/li&gt;
&lt;li&gt;주로 데이터 유효성 검사, 문자열 검색, 문자열 변환 등에 사용&lt;/li&gt;
&lt;li&gt;JavaScript에서는 &lt;code&gt;RegExp&lt;/code&gt; 객체로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 정규표현식의 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리터럴 표기법&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const regex = /pattern/flags;&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;code&gt;/&lt;/code&gt;로 시작하고 종료&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pattern&lt;/code&gt;은 검색할 패턴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flags&lt;/code&gt;는 검색 방식 지정&lt;/li&gt;
&lt;li&gt;코드 실행 시점에 정규표현식이 고정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성자 함수 표기법&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;const regex = new RegExp('pattern', 'flags');&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;/li&gt;
&lt;li&gt;런타임에 동적으로 정규표현식 생성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. RegExp 메서드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 RegExp.prototype.exec()&lt;/h3&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;일치하면 배열, 불일치 시 null 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const regex = /abc/;
console.log(regex.exec('abcdef')); // [&quot;abc&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 RegExp.prototype.test()&lt;/h3&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const regex = /abc/;
console.log(regex.test('abcdef')); // true&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 String.prototype.match()&lt;/h3&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;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'hello world';
console.log(str.match(/world/)); // [&quot;world&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 String.prototype.replace()&lt;/h3&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;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'hello world';
console.log(str.replace(/world/, 'JavaScript')); // hello JavaScript&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.5 String.prototype.search()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일치하는 첫 번째 인덱스 반환, 없으면 -1 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'hello world';
console.log(str.search(/world/)); // 6&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.6 String.prototype.split()&lt;/h3&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;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'apple,banana,orange';
console.log(str.split(/,/)); // [&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 플래그&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;g&lt;/code&gt; : 전역 검색 (모든 일치 항목 검색)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; : 대소문자 구분 없이 검색&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; : 다중 행 모드 (각 행의 시작과 끝을 인식)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;u&lt;/code&gt; : 유니코드 모드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; : &quot;sticky&quot; 모드, lastIndex부터만 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const regex = /abc/gi; // 대소문자 무시, 전역 검색&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 메타문자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; : 임의의 한 문자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt; : 문자열 시작&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$&lt;/code&gt; : 문자열 끝&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[]&lt;/code&gt; : 문자 집합&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[^]&lt;/code&gt; : 부정 문자 집합&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt; : OR 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; : 그룹화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 수량자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; : 0회 이상 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; : 1회 이상 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt; : 0 또는 1회&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{n}&lt;/code&gt; : n회 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{n,}&lt;/code&gt; : n회 이상 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{n,m}&lt;/code&gt; : n회 이상 m회 이하 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.3 이스케이프 문자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;\&lt;/code&gt; : 메타문자를 일반 문자로 인식&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;\.&lt;/code&gt; &amp;rarr; &lt;code&gt;.&lt;/code&gt; 문자 자체&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4 자주 쓰는 패턴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;\d&lt;/code&gt; : 숫자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\D&lt;/code&gt; : 숫자가 아닌 문자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\w&lt;/code&gt; : 알파벳, 숫자, 밑줄&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\W&lt;/code&gt; : 위 문자가 아닌 것&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\s&lt;/code&gt; : 공백 문자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\S&lt;/code&gt; : 공백이 아닌 문자&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;String 객체 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;String 생성자 함수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;String 생성자 함수는 문자열을 생성하는 함수 객체임&lt;/li&gt;
&lt;li&gt;&lt;code&gt;new String(value)&lt;/code&gt; 형태로 호출 시 String 래퍼 객체 생성됨&lt;/li&gt;
&lt;li&gt;인자를 전달하지 않으면 빈 문자열 생성됨&lt;/li&gt;
&lt;li&gt;문자열 리터럴(&lt;code&gt;&quot;hello&quot;&lt;/code&gt;)과 달리, 생성자 함수로 만든 값은 객체로 취급됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typeof&lt;/code&gt; 연산 시 객체(&lt;code&gt;object&lt;/code&gt;)로 반환됨&lt;/li&gt;
&lt;li&gt;문자열 원시값과 달리 프로토타입 메서드 호출 시 내부적으로 객체로 변환되는 과정 존재함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const str1 = 'hello';
const str2 = new String('hello');
console.log(typeof str1); // string
console.log(typeof str2); // object&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;length 프로퍼티&lt;/h2&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;/li&gt;
&lt;li&gt;&lt;code&gt;str.length&lt;/code&gt; 형태로 접근함&lt;/li&gt;
&lt;li&gt;변경 불가, 재할당 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;const str = 'hello world';
console.log(str.length); // 11&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;String 메서드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문자 접근&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;charAt(index)&lt;/code&gt; : 지정 인덱스 위치 문자 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;charCodeAt(index)&lt;/code&gt; : 지정 인덱스 문자의 UTF-16 코드 반환&lt;/li&gt;
&lt;li&gt;대괄호 표기(&lt;code&gt;str[index]&lt;/code&gt;)로 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 문자열 검색&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;indexOf(searchValue, fromIndex)&lt;/code&gt; : 왼쪽에서 검색하여 첫 번째 인덱스 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastIndexOf(searchValue, fromIndex)&lt;/code&gt; : 오른쪽에서 검색하여 첫 번째 인덱스 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;includes(searchString, position)&lt;/code&gt; : 포함 여부 불리언 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startsWith(searchString, position)&lt;/code&gt; : 시작 여부 확인&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endsWith(searchString, length)&lt;/code&gt; : 끝 여부 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 문자열 변환&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;toUpperCase()&lt;/code&gt; : 대문자 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toLowerCase()&lt;/code&gt; : 소문자 변환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trim()&lt;/code&gt; : 양쪽 공백 제거&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trimStart()&lt;/code&gt; / &lt;code&gt;trimEnd()&lt;/code&gt; : 시작/끝 공백 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 문자열 추출&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;slice(start, end)&lt;/code&gt; : start~end 미만 잘라내기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substring(start, end)&lt;/code&gt; : 인덱스 기반 부분 문자열 추출&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substr(start, length)&lt;/code&gt; : 시작 인덱스와 길이 기반 추출 (Deprecated)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 문자열 치환&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;replace(searchValue, replaceValue)&lt;/code&gt; : 첫 번째 일치 부분 치환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replaceAll(searchValue, replaceValue)&lt;/code&gt; : 모든 일치 부분 치환&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 문자열 분리/결합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;split(separator, limit)&lt;/code&gt; : 구분자로 분할하여 배열 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;concat(...strings)&lt;/code&gt; : 문자열 결합&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 기타 메서드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;repeat(count)&lt;/code&gt; : 지정 횟수만큼 반복한 문자열 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padStart(targetLength, padString)&lt;/code&gt; : 앞쪽 채우기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padEnd(targetLength, padString)&lt;/code&gt; : 뒤쪽 채우기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/15</guid>
      <comments>https://sjindev.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 10 Aug 2025 17:45:24 +0900</pubDate>
    </item>
    <item>
      <title>[모던 자바스크립트 DeepDive] Js 심화 스터디 week 05</title>
      <link>https://sjindev.tistory.com/14</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7hAAh/btsPA0jmsmP/RfSvMLAJY6mi8vZHqHUR1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7hAAh/btsPA0jmsmP/RfSvMLAJY6mi8vZHqHUR1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7hAAh/btsPA0jmsmP/RfSvMLAJY6mi8vZHqHUR1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7hAAh%2FbtsPA0jmsmP%2FRfSvMLAJY6mi8vZHqHUR1K%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;629&quot; height=&quot;339&quot; data-filename=&quot;모던자바스크립트png.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;자바스크립트의 Strict Mode&lt;/h2&gt;
&lt;p&gt;자바스크립트는 유연한 문법을 제공하는 언어이지만,&lt;br&gt;이로 인해 개발자가 실수하더라도 에러 없이 조용히 넘어가는 경우가 많다.&lt;br&gt;이를 보완하기 위해 ES5부터 도입된 기능이 바로 &lt;strong&gt;Strict Mode(엄격 모드)&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strict Mode&lt;/strong&gt;는 더 엄격한 문법과 실행 규칙을 적용하여 &lt;strong&gt;잠재적인 버그를 사전에 방지&lt;/strong&gt;할 수 있게 해준다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. Strict Mode란?&lt;/h2&gt;
&lt;p&gt;Strict Mode는 자바스크립트 실행을 더 엄격하게 만들어 &lt;strong&gt;잠재적인 에러나 비표준 사용을 방지&lt;/strong&gt;하는 모드이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 문장을 코드 최상단이나 함수 내부에 작성하면 Strict Mode가 적용된다.&lt;/p&gt;
&lt;h3&gt;주요 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;암묵적 전역 변수 사용 금지&lt;/li&gt;
&lt;li&gt;읽기 전용 프로퍼티에 값 할당 시 에러&lt;/li&gt;
&lt;li&gt;삭제 불가능한 프로퍼티 삭제 시 에러&lt;/li&gt;
&lt;li&gt;&lt;code&gt;this&lt;/code&gt;가 명시되지 않은 경우 &lt;code&gt;undefined&lt;/code&gt;로 설정됨&lt;/li&gt;
&lt;li&gt;중복된 매개변수 이름 사용 금지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;with&lt;/code&gt; 문 사용 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. Strict Mode의 적용 방법&lt;/h2&gt;
&lt;h3&gt;전역 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;
// 이후의 모든 코드에 적용&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;함수 단위 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function test() {
  &amp;#39;use strict&amp;#39;;
  // 이 함수 내부만 strict mode
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;모듈은 자동 strict mode&lt;/h3&gt;
&lt;p&gt;ES6 모듈(&lt;code&gt;.mjs&lt;/code&gt; 파일 또는 type=&amp;quot;module&amp;quot;)은 기본적으로 strict mode가 적용된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// .mjs 파일 또는 type=&amp;quot;module&amp;quot;에서는 생략해도 strict mode가 적용됨&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;3. 전역에 Strict Mode 적용은 피하자&lt;/h2&gt;
&lt;p&gt;파일 전체에 &lt;code&gt;&amp;#39;use strict&amp;#39;&lt;/code&gt;를 선언하면 &lt;strong&gt;파일 내 모든 스크립트가 강제로 strict 규칙을 따르게 된다.&lt;/strong&gt;&lt;br&gt;이는 라이브러리나 외부 코드와 충돌할 가능성을 높일 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, 아래처럼 여러 스크립트가 섞여 있을 경우:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;&amp;#39;use strict&amp;#39;;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;legacy-lib.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;legacy-lib.js&lt;/code&gt;가 strict mode와 호환되지 않는 문법을 포함하고 있다면 실행 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결 방안&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;전역이 아닌 &lt;strong&gt;클래스/모듈/함수 내부에 국한하여 사용&lt;/strong&gt;하는 것이 바람직&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4. 함수 단위 Strict Mode 적용도 피하자&lt;/h2&gt;
&lt;p&gt;함수 내부에만 &lt;code&gt;use strict&lt;/code&gt;를 선언하면 &lt;strong&gt;코드 일관성이 깨지고, 스코프 추적이 복잡해질 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function a() {
  &amp;#39;use strict&amp;#39;;
  // 엄격 모드
}

function b() {
  // 비엄격 모드
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이처럼 혼합 사용은 유지보수 시 실수를 유발할 수 있으며, 디버깅이 어려워짐&lt;/p&gt;
&lt;h3&gt;권장 방식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;모듈 또는 클래스 전체에 적용하거나, 전체 코드 통일 적용&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. Strict Mode가 발생시키는 에러&lt;/h2&gt;
&lt;p&gt;Strict Mode에서 기존에는 조용히 무시되던 동작들이 &lt;strong&gt;명시적인 에러&lt;/strong&gt;로 전환된다.&lt;/p&gt;
&lt;h3&gt;암묵적 전역 변수 금지&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
  x = 10; // ReferenceError
}
foo();&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;읽기 전용 속성 변경 금지&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;
const obj = {};
Object.defineProperty(obj, &amp;#39;x&amp;#39;, {
  value: 42,
  writable: false
});
obj.x = 100; // TypeError&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;삭제 불가능한 속성 삭제 금지&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;
delete Object.prototype; // TypeError&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;중복 매개변수 금지&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function sum(a, a) { // SyntaxError in strict mode
  return a + a;
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;with 문 사용 금지&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;
with (obj) { // SyntaxError
  console.log(x);
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;6. Strict Mode 적용에 의한 변화&lt;/h2&gt;
&lt;p&gt;Strict Mode를 적용하면 기존 코드에서 다음과 같은 변화가 생긴다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;비 Strict 모드&lt;/th&gt;
&lt;th&gt;Strict 모드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;암묵적 전역 변수&lt;/td&gt;
&lt;td&gt;허용됨&lt;/td&gt;
&lt;td&gt;ReferenceError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중복 매개변수&lt;/td&gt;
&lt;td&gt;허용됨&lt;/td&gt;
&lt;td&gt;SyntaxError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;with&lt;/code&gt; 문&lt;/td&gt;
&lt;td&gt;허용됨&lt;/td&gt;
&lt;td&gt;SyntaxError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;this&lt;/code&gt; (함수 내부)&lt;/td&gt;
&lt;td&gt;전역 객체(window)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;읽기 전용 속성 할당&lt;/td&gt;
&lt;td&gt;무시됨&lt;/td&gt;
&lt;td&gt;TypeError&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이러한 변화는 &lt;strong&gt;코드의 예측 가능성, 안정성, 보안성&lt;/strong&gt;을 높이기 위한 설계이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;빌트인 객체&lt;/h1&gt;
&lt;p&gt;자바스크립트는 다양한 내장 객체(Built-in Object)를 제공하여, 개발자가 복잡한 기능을 손쉽게 구현할 수 있도록 돕는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 빌트인 객체란?&lt;/h2&gt;
&lt;p&gt;빌트인 객체(Built-in Object)는 &lt;strong&gt;자바스크립트 엔진에 내장되어 있는 객체&lt;/strong&gt;로, 별도의 선언 없이 언제든지 사용할 수 있다. 예를 들어, &lt;code&gt;Math&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;JSON&lt;/code&gt;, &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Object&lt;/code&gt; 등이 있다.&lt;/p&gt;
&lt;p&gt;이러한 객체는 자바스크립트 사양(ECMAScript 표준)에 정의되어 있으며, 표준 빌트인 객체(Standard Built-in Object)라고도 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 자바스크립트 객체의 분류&lt;/h2&gt;
&lt;p&gt;자바스크립트에서 객체는 다음과 같이 분류된다:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;분류&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;표준 빌트인 객체&lt;/td&gt;
&lt;td&gt;ECMAScript 사양에 정의된 객체&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Object&lt;/code&gt;, &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Math&lt;/code&gt; 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;호스트 객체&lt;/td&gt;
&lt;td&gt;자바스크립트가 실행되는 환경(브라우저 등)이 제공하는 객체&lt;/td&gt;
&lt;td&gt;&lt;code&gt;window&lt;/code&gt;, &lt;code&gt;document&lt;/code&gt;, &lt;code&gt;XMLHttpRequest&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt; 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 정의 객체&lt;/td&gt;
&lt;td&gt;개발자가 직접 정의한 객체&lt;/td&gt;
&lt;td&gt;생성자 함수, 클래스 인스턴스 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;3. 표준 빌트인 객체&lt;/h2&gt;
&lt;p&gt;자주 사용되는 표준 빌트인 객체는 다음과 같다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Array&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Function&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;String&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RegExp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSON&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const now = new Date();
console.log(Math.max(10, 20, 30));
console.log(JSON.stringify({ name: &amp;#39;Alice&amp;#39; }));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이들은 모두 &lt;strong&gt;생성자 함수&lt;/strong&gt;로 제공되며, 인스턴스를 만들거나 정적 메서드를 사용할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 원시값과 래퍼 객체&lt;/h2&gt;
&lt;p&gt;자바스크립트의 기본 데이터 타입인 &lt;strong&gt;문자열, 숫자, 불리언&lt;/strong&gt;은 원시값이다.&lt;/p&gt;
&lt;p&gt;이 원시값들은 임시적으로 객체처럼 동작할 수 있는데, 이 때 &lt;strong&gt;래퍼 객체(Wrapper Object)&lt;/strong&gt;가 생성된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const str = &amp;#39;hello&amp;#39;;
console.log(str.toUpperCase()); // &amp;#39;HELLO&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 코드에서 &lt;code&gt;&amp;#39;hello&amp;#39;&lt;/code&gt;는 문자열 원시값이지만, &lt;code&gt;toUpperCase()&lt;/code&gt;를 호출할 수 있다. 이는 &lt;strong&gt;String 래퍼 객체&lt;/strong&gt;가 임시로 생성되어 메서드를 호출한 뒤 소멸되기 때문이다.&lt;/p&gt;
&lt;p&gt;래퍼 객체의 종류:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const num = 100;
console.log(num.toFixed(2)); // &amp;#39;100.00&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;5. 전역 객체(Global Object)&lt;/h2&gt;
&lt;p&gt;전역 객체는 &lt;strong&gt;코드 어디서든 접근 가능한 전역 범위의 객체&lt;/strong&gt;이다. 실행 환경에 따라 이름은 다르다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;브라우저 환경: &lt;code&gt;window&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Node.js 환경: &lt;code&gt;global&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ES2020 이후 공통으로 사용할 수 있는 전역 객체는 &lt;code&gt;globalThis&lt;/code&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(globalThis === window); // 브라우저에서 true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;전역 객체에는 다음과 같은 속성이 있다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전역 변수 (&lt;code&gt;var&lt;/code&gt;로 선언한 변수)&lt;/li&gt;
&lt;li&gt;전역 함수 (&lt;code&gt;parseInt&lt;/code&gt;, &lt;code&gt;isNaN&lt;/code&gt;, &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;clearInterval&lt;/code&gt; 등)&lt;/li&gt;
&lt;li&gt;표준 빌트인 객체 (&lt;code&gt;Math&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;JSON&lt;/code&gt; 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;6. 빌트인 전역 함수&lt;/h2&gt;
&lt;p&gt;전역 객체의 일부로 제공되는 &lt;strong&gt;빌트인 전역 함수&lt;/strong&gt;는 다음과 같다:&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;isNaN()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;숫자가 아닌 경우 true를 반환&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;isNaN(&amp;#39;abc&amp;#39;); // true&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;&lt;code&gt;parseInt()&lt;/code&gt;, &lt;code&gt;parseFloat()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;문자열을 정수 또는 실수로 변환&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parseInt(&amp;#39;42px&amp;#39;); // 42
parseFloat(&amp;#39;3.14abc&amp;#39;); // 3.14&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;&lt;code&gt;eval()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;문자열을 코드로 실행 (보안상 사용 지양)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eval(&amp;#39;2 + 2&amp;#39;); // 4&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;7. encodeURI / decodeURI (URI / URN)&lt;/h2&gt;
&lt;h3&gt;URI (Uniform Resource Identifier)&lt;/h3&gt;
&lt;p&gt;자원을 식별하는 문자열. URL과 URN이 포함된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URL (Uniform Resource Locator)&lt;/strong&gt;: 자원의 위치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URN (Uniform Resource Name)&lt;/strong&gt;: 자원의 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;encodeURI()&lt;/code&gt; / &lt;code&gt;decodeURI()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;URI 문자열에서 &lt;strong&gt;특수문자를 인코딩/디코딩&lt;/strong&gt;할 때 사용&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const url = &amp;#39;https://example.com/한글&amp;#39;;
const encoded = encodeURI(url); // 인코딩
const decoded = decodeURI(encoded); // 디코딩&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;encodeURIComponent()&lt;/code&gt;는 &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt; 등도 인코딩하는 반면, &lt;code&gt;encodeURI()&lt;/code&gt;는 그렇지 않음&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;8. 암묵적 전역 (Implicit Global)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt; 없이 변수를 선언하면 &lt;strong&gt;암묵적으로 전역 변수가 생성&lt;/strong&gt;된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
  x = 10; // 전역 객체의 프로퍼티로 등록됨
}
foo();
console.log(window.x); // 10&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;문제점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;전역 오염&lt;/li&gt;
&lt;li&gt;의도치 않은 변수 재정의 위험&lt;/li&gt;
&lt;li&gt;모듈 간 충돌 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;&lt;/code&gt;를 사용하여 암묵적 전역 방지&lt;/li&gt;
&lt;li&gt;항상 &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt; 중 하나로 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1&gt;&lt;code&gt;this&lt;/code&gt;&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;1. this 키워드란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;this&lt;/code&gt;는 &lt;strong&gt;실행 컨텍스트에 따라 동적으로 결정되는 특수한 식별자&lt;/strong&gt;이다. 일반적으로 어떤 객체의 메서드를 호출할 때 해당 메서드 내부에서 &lt;code&gt;this&lt;/code&gt;는 &lt;strong&gt;그 메서드를 호출한 객체를 참조&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;하지만 함수가 어떤 방식으로 호출되느냐에 따라 &lt;code&gt;this&lt;/code&gt;가 참조하는 대상은 달라진다. 이 점이 자바스크립트의 &lt;code&gt;this&lt;/code&gt;를 이해하는 데 핵심이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(this); // 브라우저에서 window, Node.js에선 global&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;2. 함수 호출 방식과 this 바인딩&lt;/h2&gt;
&lt;p&gt;자바스크립트는 &lt;strong&gt;함수를 호출하는 방식에 따라 &lt;code&gt;this&lt;/code&gt; 바인딩이 달라진다.&lt;/strong&gt; 크게 4가지 방식으로 나눌 수 있다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;일반 함수 호출&lt;/li&gt;
&lt;li&gt;메서드 호출&lt;/li&gt;
&lt;li&gt;생성자 함수 호출&lt;/li&gt;
&lt;li&gt;&lt;code&gt;call&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;bind&lt;/code&gt;에 의한 명시적 바인딩&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;3. 일반 함수 호출에서의 this&lt;/h2&gt;
&lt;p&gt;함수를 단독으로 호출할 경우, strict mode 여부에 따라 &lt;code&gt;this&lt;/code&gt;가 다르게 바인딩된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function foo() {
  console.log(this);
}

foo(); // 비엄격 모드: window (또는 global), 엄격 모드: undefined&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;엄격 모드에서는 undefined&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;use strict&amp;#39;;
function foo() {
  console.log(this); // undefined
}
foo();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이는 의도하지 않은 전역 객체 접근을 방지하기 위한 설계이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 메서드 호출에서의 this&lt;/h2&gt;
&lt;p&gt;객체의 프로퍼티로 함수를 호출할 경우, &lt;code&gt;this&lt;/code&gt;는 해당 객체를 참조한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const person = {
  name: &amp;#39;KSJ&amp;#39;,
  sayHi: function () {
    console.log(`Hi, I&amp;#39;m ${this.name}`);
  }
};

person.sayHi(); // Hi, I&amp;#39;m KSJ&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 경우 &lt;code&gt;sayHi&lt;/code&gt; 메서드는 &lt;code&gt;person&lt;/code&gt; 객체의 프로퍼티로 호출되므로, &lt;code&gt;this&lt;/code&gt;는 &lt;code&gt;person&lt;/code&gt;을 가리킨다.&lt;/p&gt;
&lt;h3&gt;단 주의할 점&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const fn = person.sayHi;
fn(); // 일반 함수 호출로 간주되어 this는 window 또는 undefined&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;5. 생성자 함수 호출에서의 this&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;new&lt;/code&gt; 키워드와 함께 함수를 호출하면, 새로운 객체가 생성되며 그 객체가 &lt;code&gt;this&lt;/code&gt;로 바인딩된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Person(name) {
  this.name = name;
  this.sayHello = function () {
    console.log(`Hello, I&amp;#39;m ${this.name}`);
  };
}

const p1 = new Person(&amp;#39;Ksj&amp;#39;);
p1.sayHello(); // Hello, I&amp;#39;m Ksj&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이때 생성자 함수 내부의 &lt;code&gt;this&lt;/code&gt;는 &lt;strong&gt;새로 생성된 인스턴스 객체&lt;/strong&gt;를 가리킨다.&lt;/p&gt;
&lt;h3&gt;생성자 호출 시 처리 과정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;빈 객체 생성&lt;/li&gt;
&lt;li&gt;this에 바인딩&lt;/li&gt;
&lt;li&gt;프로퍼티/메서드 정의&lt;/li&gt;
&lt;li&gt;this 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;6. 명시적 바인딩: call, apply, bind&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;call&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;bind&lt;/code&gt; 메서드를 사용하면 &lt;code&gt;this&lt;/code&gt;를 명시적으로 지정할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function greet() {
  console.log(this.name);
}

const user = { name: &amp;#39;Ksj&amp;#39; };
greet.call(user); // Ksj
greet.apply(user); // Ksj

const boundGreet = greet.bind(user);
boundGreet(); // Ksj&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;call&lt;/code&gt;: 첫 번째 인자를 this로 바인딩하고 나머지 인자를 함수에 전달&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apply&lt;/code&gt;: 첫 번째 인자를 this로 바인딩하고 두 번째 인자는 배열로 전달&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bind&lt;/code&gt;: this를 고정한 새로운 함수를 반환 (호출은 나중에)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;자바스크립트의 &lt;code&gt;this&lt;/code&gt;는 &lt;strong&gt;정적이지 않고 동적&lt;/strong&gt;이다. 함수가 호출되는 방식에 따라 &lt;code&gt;this&lt;/code&gt;가 달라지기 때문에, 그 차이를 정확히 이해하는 것이 중요하다. 아래 각 방식에 따라 &lt;code&gt;this&lt;/code&gt; 가 어떻게 지정되는지 정리했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일반 함수 호출: 전역 객체 (또는 undefined)&lt;/li&gt;
&lt;li&gt;메서드 호출: 호출한 객체&lt;/li&gt;
&lt;li&gt;생성자 함수 호출: 새로 생성된 인스턴스&lt;/li&gt;
&lt;li&gt;call/apply/bind: 명시적으로 지정된 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실행 컨텍스트&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;1. 실행 컨텍스트란?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;실행 컨텍스트&lt;/strong&gt;는 자바스크립트 코드가 실행되는 환경을 의미한다. 전역 코드, 함수 코드 등 각각의 실행 단위마다 별도의 실행 컨텍스트가 생성되며, 이 안에 변수, 함수 선언, this 등이 저장된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var x = 1;
function foo() {
  var y = 2;
  console.log(x + y);
}
foo();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 예제에서는 전역 컨텍스트와 &lt;code&gt;foo&lt;/code&gt; 함수 컨텍스트 두 개가 생성된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 소스코드 타입&lt;/h2&gt;
&lt;p&gt;자바스크립트의 실행 단위는 다음과 같은 &lt;strong&gt;소스코드 타입&lt;/strong&gt;으로 분류된다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전역 코드&lt;/li&gt;
&lt;li&gt;함수 코드&lt;/li&gt;
&lt;li&gt;eval 코드&lt;/li&gt;
&lt;li&gt;모듈 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;각 타입은 실행 컨텍스트의 생성 방식과 처리 순서에 영향을 준다.&lt;/p&gt;
&lt;h3&gt;▸ 전역 코드&lt;/h3&gt;
&lt;p&gt;가장 먼저 실행되며, 전역 객체 생성과 전역 변수, 함수 선언을 처리한다.&lt;/p&gt;
&lt;h3&gt;▸ 함수 코드&lt;/h3&gt;
&lt;p&gt;함수가 호출될 때 생성되며, 지역 변수와 arguments 객체 등이 이 컨텍스트에 저장된다.&lt;/p&gt;
&lt;h3&gt;▸ eval 코드&lt;/h3&gt;
&lt;p&gt;eval 함수로 실행된 문자열 내부 코드.&lt;/p&gt;
&lt;h3&gt;▸ 모듈 코드 (ES6)&lt;/h3&gt;
&lt;p&gt;엄격 모드가 자동 적용되며, import/export 키워드를 사용할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 소스코드의 평가와 실행&lt;/h2&gt;
&lt;p&gt;자바스크립트 엔진은 소스코드를 다음의 &lt;strong&gt;2단계&lt;/strong&gt;로 처리한다:&lt;/p&gt;
&lt;h3&gt;▸ 1. 평가(Evaluation) 단계&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;실행 컨텍스트 생성&lt;/li&gt;
&lt;li&gt;변수, 함수, 클래스 선언 등록&lt;/li&gt;
&lt;li&gt;렉시컬 환경 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;▸ 2. 실행(Execution) 단계&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;변수에 값 할당&lt;/li&gt;
&lt;li&gt;함수 호출&lt;/li&gt;
&lt;li&gt;연산 수행 등 실제 코드 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4. 실행 컨텍스트의 역할&lt;/h2&gt;
&lt;p&gt;실행 컨텍스트는 아래와 같은 중요한 역할을 수행한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;식별자(변수, 함수 등) 이름과 값을 매핑&lt;/li&gt;
&lt;li&gt;this 바인딩&lt;/li&gt;
&lt;li&gt;외부 렉시컬 환경 참조 저장&lt;/li&gt;
&lt;li&gt;클로저의 기반 환경 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. 실행 컨텍스트 스택&lt;/h2&gt;
&lt;p&gt;자바스크립트는 &lt;strong&gt;싱글 스레드&lt;/strong&gt; 기반이며, 실행 중인 컨텍스트를 &lt;strong&gt;스택&lt;/strong&gt;(LIFO) 구조로 관리한다.&lt;/p&gt;
&lt;h3&gt;▸ 실행 순서&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;전역 컨텍스트 push&lt;/li&gt;
&lt;li&gt;함수 호출 → 새로운 컨텍스트 push&lt;/li&gt;
&lt;li&gt;함수 종료 → 컨텍스트 pop&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function a() {
  function b() {
    console.log(&amp;#39;b&amp;#39;);
  }
  b();
}
a();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실행 컨텍스트는 &lt;code&gt;[global] → [a] → [b]&lt;/code&gt; 순으로 쌓이고 역순으로 제거된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 렉시컬 환경 (Lexical Environment)&lt;/h2&gt;
&lt;p&gt;실행 컨텍스트 내부에는 &lt;strong&gt;렉시컬 환경&lt;/strong&gt;이라는 구성 요소가 포함된다. 이는 &lt;strong&gt;식별자와 변수 값&lt;/strong&gt;을 실제로 저장하는 공간이다.&lt;/p&gt;
&lt;p&gt;렉시컬 환경은 다시 두 부분으로 나뉜다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;환경 레코드 (Environment Record)&lt;ul&gt;
&lt;li&gt;변수, 함수, 매개변수 정보 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부 렉시컬 환경 참조 (Outer Lexical Environment Reference)&lt;ul&gt;
&lt;li&gt;상위 스코프에 대한 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;7. 실행 컨텍스트 생성과 식별자 검색&lt;/h2&gt;
&lt;p&gt;식별자(변수, 함수)를 검색할 때, 자바스크립트 엔진은 &lt;strong&gt;현재 컨텍스트의 렉시컬 환경 → 상위 컨텍스트 → ... → 전역 컨텍스트&lt;/strong&gt; 순으로 검색한다. 이를 &lt;strong&gt;스코프 체인&lt;/strong&gt;이라고 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function outer() {
  const x = 10;
  function inner() {
    console.log(x); // 외부 렉시컬 환경을 참조하여 검색
  }
  inner();
}
outer();&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;8. 실행 컨텍스트와 블록 레벨 스코프&lt;/h2&gt;
&lt;p&gt;ES6 이전에는 함수 레벨 스코프만 존재했지만, &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt; 도입 이후 &lt;strong&gt;블록 레벨 스코프&lt;/strong&gt;도 생겼다. 실행 컨텍스트 안에서도 이러한 블록 스코프는 &lt;strong&gt;별도의 렉시컬 환경&lt;/strong&gt;으로 관리된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  let x = 1;
  const y = 2;
}
// x, y는 이 블록 밖에서 접근 불가&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;클로저(Closure)&lt;/h2&gt;
&lt;hr&gt;
&lt;h2&gt;1. 클로저란?&lt;/h2&gt;
&lt;p&gt;클로저는 &amp;quot;&lt;strong&gt;함수가 자신이 선언될 당시의 렉시컬 환경(Lexical Environment)을 기억하고 있는 현상&lt;/strong&gt;&amp;quot;을 의미한다. 이 덕분에 함수 외부에서 선언된 변수에 접근할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function outer() {
  const x = 10;
  return function inner() {
    console.log(x); // 외부 스코프 변수 접근
  }
}
const fn = outer();
fn(); // 10&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;2. 렉시컬 스코프와 클로저&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;렉시컬 스코프&lt;/strong&gt;란 함수가 &lt;strong&gt;어디서 선언되었는지&lt;/strong&gt;에 따라 상위 스코프가 결정되는 방식이다. 실행 위치가 아니라 선언 위치가 기준이다.&lt;/p&gt;
&lt;p&gt;클로저는 이러한 렉시컬 스코프를 기반으로 외부 변수에 접근 가능하다. 위 예제에서 &lt;code&gt;inner&lt;/code&gt;는 &lt;code&gt;outer&lt;/code&gt;의 스코프를 참조하고 있으므로 &lt;code&gt;x&lt;/code&gt;에 접근할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 함수 객체의 내부 슬롯&lt;/h2&gt;
&lt;p&gt;자바스크립트의 함수는 객체이며, 내부 슬롯을 가진다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[[Environment]]&lt;/code&gt;: 함수가 생성될 때의 렉시컬 환경 참조&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[[Scopes]]&lt;/code&gt;: V8 엔진 등 일부 구현체에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 내부 슬롯이 존재하기에 함수는 자신이 생성될 당시의 환경을 기억하고 접근할 수 있다. 이게 클로저의 핵심 동작 원리다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 클로저와 렉시컬 환경&lt;/h2&gt;
&lt;p&gt;실행 컨텍스트가 생성되면 그 안에 렉시컬 환경이 포함된다. 클로저는 이 렉시컬 환경을 &lt;strong&gt;함수 내부에 저장&lt;/strong&gt;하여 유지한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function counter() {
  let count = 0;
  return function () {
    count++;
    return count;
  }
}

const inc = counter();
console.log(inc()); // 1
console.log(inc()); // 2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 코드에서 &lt;code&gt;inc&lt;/code&gt; 함수는 &lt;code&gt;count&lt;/code&gt;가 존재하는 렉시컬 환경을 유지하고 있으므로 상태를 보존할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 클로저의 활용&lt;/h2&gt;
&lt;p&gt;클로저는 다음과 같은 패턴에 활용된다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;상태 유지를 위한 private 변수&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 핸들러의 고정 상태 유지&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;콜백 함수 내부에서 외부 변수 참조&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;▸ 예: 상태 유지용 클로저&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function createTimer() {
  let seconds = 0;
  return function () {
    seconds += 1;
    return `${seconds}초 경과`;
  }
}

const timer = createTimer();
timer(); // &amp;#39;1초 경과&amp;#39;
timer(); // &amp;#39;2초 경과&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;6. 캡슐화를 위한 정보 은닉&lt;/h2&gt;
&lt;p&gt;자바스크립트는 접근 제어자(private 등)가 없지만, 클로저를 이용하면 정보 은닉이 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function createUser(name) {
  let _password = &amp;#39;1234&amp;#39;;

  return {
    getName() {
      return name;
    },
    checkPassword(pw) {
      return pw === _password;
    },
  }
}

const user = createUser(&amp;#39;Alice&amp;#39;);
console.log(user.getName()); // Alice
console.log(user.checkPassword(&amp;#39;wrong&amp;#39;)); // false
console.log(user.checkPassword(&amp;#39;1234&amp;#39;)); // true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 예제처럼 외부에서 &lt;code&gt;password&lt;/code&gt;에 직접 접근하지 못하게 하고, &lt;code&gt;checkPassword&lt;/code&gt; 함수로만 확인할 수 있게 설계할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;클로저는 단순한 기술 요소를 넘어서, 자바스크립트의 &lt;strong&gt;스코프&lt;/strong&gt;, &lt;strong&gt;실행 컨텍스트&lt;/strong&gt;, &lt;strong&gt;함수 객체&lt;/strong&gt; 구조까지 깊이 이해할 수 있게 해주는 핵심 개념이다. 렉시컬 환경과의 연결 구조를 이해하면 클로저의 동작 방식을 이해하기 더 쉬울것이다.&lt;/p&gt;
&lt;p&gt;캡슐화, 상태 보존, 은닉 등 실제 개발에서 매우 유용하게 활용되므로, 클로저를 단순한 개념이 아니라 도구로 이해하고 활용하는 것이 중요하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;클래스(Class)&lt;/h1&gt;
&lt;p&gt;자바스크립트에서 클래스는 객체지향 프로그래밍(OOP)을 보다 명확하고 직관적으로 구현하기 위해 도입된 문법이다. ES6에서 도입된 이 클래스 문법은 기존 생성자 함수 기반 객체 생성 방식보다 가독성이 높고 구조적인 코드 작성을 가능하게 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 클래스 정의&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`Hi, I&amp;#39;m ${this.name}`);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;class&lt;/code&gt; 키워드로 클래스를 정의하며, 생성자는 &lt;code&gt;constructor()&lt;/code&gt; 메서드로 정의한다. 생성자 내부에서 프로퍼티를 초기화하고, 나머지 메서드는 프로토타입 메서드로 정의된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 클래스 호이스팅&lt;/h2&gt;
&lt;p&gt;클래스는 &lt;strong&gt;호이스팅은 되지만 초기화 전에 접근할 수 없다.&lt;/strong&gt; 이는 &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;와 같은 &lt;strong&gt;TDZ(Temporal Dead Zone)&lt;/strong&gt;로 동작한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const p = new Person(); // ReferenceError
class Person {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;함수 선언과는 달리 클래스는 선언 전에 사용할 수 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 인스턴스 생성&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;new&lt;/code&gt; 키워드를 통해 클래스로부터 인스턴스를 생성할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const me = new Person(&amp;#39;Alice&amp;#39;);
me.sayHello(); // Hi, I&amp;#39;m Alice&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;me&lt;/code&gt;는 &lt;code&gt;Person&lt;/code&gt; 클래스의 인스턴스이며, &lt;code&gt;Person.prototype&lt;/code&gt;을 상속받는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 메서드&lt;/h2&gt;
&lt;p&gt;클래스 내부에서 정의된 메서드는 프로토타입 메서드다. 클래스 정의 바깥에서 정의된 메서드와 달리, 자동으로 &lt;code&gt;prototype&lt;/code&gt;에 할당된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Counter {
  count = 0;
  increase() {
    this.count++;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;5. 정적 메서드 vs 프로토타입 메서드&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정적 메서드&lt;/strong&gt;: 클래스 자체에 속하며 인스턴스에서는 호출할 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프로토타입 메서드&lt;/strong&gt;: 인스턴스에서 호출 가능한 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MathUtil {
  static add(x, y) {
    return x + y;
  }
}

MathUtil.add(2, 3); // 5&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;6. 클래스의 인스턴스 생성 과정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;빈 객체 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;this&lt;/code&gt; 바인딩&lt;/li&gt;
&lt;li&gt;&lt;code&gt;constructor&lt;/code&gt; 실행&lt;/li&gt;
&lt;li&gt;인스턴스 반환 (명시적 반환이 없으면 &lt;code&gt;this&lt;/code&gt; 반환)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;7. 프로퍼티&lt;/h2&gt;
&lt;p&gt;클래스 필드는 생성자 외부에서도 선언 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User {
  name = &amp;#39;unknown&amp;#39;;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 프로퍼티는 각 인스턴스마다 복사된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;8. private 필드 정의 제안&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;#&lt;/code&gt;을 붙이면 외부에서 접근할 수 없는 private 필드를 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Secret {
  #code = 1234;
  getCode() {
    return this.#code;
  }
}

const s = new Secret();
s.#code; // SyntaxError&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;9. static 필드 정의 제안&lt;/h2&gt;
&lt;p&gt;정적 필드도 &lt;code&gt;static&lt;/code&gt; 키워드를 사용해 클래스 자체에 바인딩할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Counter {
  static count = 0;
  static increase() {
    Counter.count++;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;10. 상속에 의한 클래스 확장&lt;/h2&gt;
&lt;p&gt;클래스는 다른 클래스를 상속받아 확장할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Animal {
  speak() {
    console.log(&amp;#39;Animal sound&amp;#39;);
  }
}

class Dog extends Animal {
  speak() {
    console.log(&amp;#39;Bark&amp;#39;);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;11. extends 키워드&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;extends&lt;/code&gt;는 서브클래스가 슈퍼클래스를 상속하도록 지정한다. 상속받은 서브클래스는 부모 클래스의 메서드를 사용할 수 있으며, 오버라이딩도 가능하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;12. 동적 상속&lt;/h2&gt;
&lt;p&gt;상속 대상은 변수나 함수로 동적으로 지정 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function createParent() {
  return class {
    greet() {
      console.log(&amp;#39;Hello&amp;#39;);
    }
  }
}

class Child extends createParent() {}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;13. 서브클래스의 constructor&lt;/h2&gt;
&lt;p&gt;서브클래스에서 &lt;code&gt;constructor&lt;/code&gt;를 오버라이딩할 경우, 반드시 &lt;code&gt;super()&lt;/code&gt;를 호출해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Dog extends Animal {
  constructor(name) {
    super(); // 반드시 필요
    this.name = name;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;14. super 키워드&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;메서드 내에서 부모 클래스의 메서드를 호출할 때 사용&lt;/li&gt;
&lt;li&gt;생성자 내에서 &lt;code&gt;super()&lt;/code&gt;는 부모의 생성자 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Parent {
  greet() {
    console.log(&amp;#39;Hi from parent&amp;#39;);
  }
}

class Child extends Parent {
  greet() {
    super.greet(); // Hi from parent
    console.log(&amp;#39;Hi from child&amp;#39;);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;p&gt;클래스는 자바스크립트에서 객체지향적 코드 구조를 명확하게 표현하는 도구이다. 상속, 캡슐화, 정적 메서드 등 OOP 개념을 이해하면 더욱 강력한 자바스크립트 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;ES6 클래스 문법은 실제로는 기존 프로토타입 기반 객체 시스템 위에 추상화된 문법적 설탕(syntactic sugar)이라는 점도 함께 기억해두자.&lt;/p&gt;</description>
      <category>개발</category>
      <category>JavaScript</category>
      <category>개발</category>
      <category>자바스크립트</category>
      <category>코딩</category>
      <author>sjindev</author>
      <guid isPermaLink="true">https://sjindev.tistory.com/14</guid>
      <comments>https://sjindev.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 28 Jul 2025 12:19:07 +0900</pubDate>
    </item>
  </channel>
</rss>