<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>PMtHk__</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 06 Apr 2025 15:14:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>PMtHk__</title>
            <url>https://velog.velcdn.com/images/pmthk__/profile/906d3d81-5c46-4e63-bc1e-9b3c2e4b7661/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. PMtHk__. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pmthk__" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[리액트 톺아보기 ➃ - Concurrent Mode]]></title>
            <link>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-4</link>
            <guid>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-4</guid>
            <pubDate>Sun, 06 Apr 2025 15:14:45 GMT</pubDate>
            <description><![CDATA[<p>앞서 React Fiber 아키텍처에 대해 정리했었다. 
이 아키텍처를 활용해 React 18에서는 Concurrent Mode가 도입되었다.
이 글에서는 Concurrent Mode에 대해 조금 더 알아본다.</p>
<h1 id="concurrent-mode">Concurrent Mode</h1>
<p>Concurrent Mode는 React 18에 도입된 새로운 렌더링 모델이다.
기존에는 한 번에 하나의 업데이트만 처리해 <strong>렌더링을 중단할 수 없었다</strong>.
하지만, 이 새로운 모드에서는 <strong>여러 버전의 UI를 동시에 준비</strong>하고 필요에 따라 <strong>작업을 일시 중단하거나 건너뛰는</strong> 것이 가능하다.</p>
<blockquote>
<p><strong>It’s a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time.</strong></p>
</blockquote>
<blockquote>
<p>이 부분은 React Fiber에 내용과 매우 유사하다. 
물론이다. React Fiber를 기반으로 하기 때문이다.</p>
</blockquote>
<p>Concurrent Mode에서는 전체 작업이 완료되기 전까지 실제 DOM 업데이트(Commit 단계)를 지연시킨다.
이를 통해 무거운 연산을 백그라운드에서 처리하면서도 사용자 입력에는 즉시 반응할 수 있게 된다.</p>
<p>즉, React가 사용자 기기 성능에 맞춰 <strong>일정 간격으로 브라우저에 제어권을 넘겨주고</strong>, UI 작업을 자동으로 스케줄링해 <strong>더 나은 응답성을 제공</strong>하는 것이다.</p>
<p>이제 이런 기능이 실제 코드에서 활용되는 방법을 살펴보자.</p>
<h2 id="1-automatic-batching">1. Automatic Batching</h2>
<p>기존에도 상태가 변경되면 React는 컴포넌트를 리렌더링하고, 성능을 위해 상태 변경을 <strong>일괄 처리(batch)</strong>한다.
하지만 기존 React(17 이하)는 <strong>이벤트 핸들러 내부의 상태 변경만 자동으로 일괄 처리 가능했다</strong>.</p>
<p>그 이유는 React가 <strong>SyntheticEvent라는 자체 이벤트 시스템으로 브라우저의 이벤트를 감싸서 처리하면서 이벤트 핸들러의 종료 시점(동기적 종료)</strong>을 명확히 알 수 있었기 때문이다. </p>
<p>반면, 비동기 작업(setTimeout, Promise 등)의 경우 <strong>React가 이를 일괄 처리할 적절한 타이밍을 잡아줄 스케줄링 기능을 갖추지 않았기 때문</strong>에 각각의 상태 업데이트 호출 시마다 렌더링을 즉시 수행했다.</p>
<p>React 18에서는 이를 <strong>Fiber 기반의 아키텍처와 Concurrent 렌더링을 통해 개선했다</strong>.</p>
<ol>
<li>상태 업데이트가 발생하면 React는 이를 <strong>바로 처리하지 않고 업데이트 큐에 담아둔다</strong>.</li>
<li>React의 <strong>Concurrent Scheduler는 브라우저의 이벤트 루프가 현재 수행 중인 모든 마이크로태스크를 마쳤음을 알려주는 순간(적절한 타이밍)</strong>을 기다렸다가 모아둔 상태 업데이트를 일괄 처리한 후 한 번의 렌더링을 수행한다.</li>
</ol>
<p>즉, React 18에서는 렌더링을 즉시 처리하지 않고, Concurrent Scheduler를 통해 <strong>상태 업데이트의 렌더링 시점을 적절히 지연시키고 최적화된 타이밍에 처리하도록 개선된 것</strong>이다.</p>
<p>이 덕분에 React 18부터는 <strong>이벤트 핸들러 내부뿐 아니라 비동기 함수 내부에서도 상태 업데이트의 일괄 처리가 가능</strong>해져, 성능이 크게 개선되었다.</p>
<h2 id="2-transition-usedeferredvalue">2. Transition, useDeferredValue</h2>
<p>다음은 <code>useTransition</code>과 <code>useDeferredValue</code>이다.</p>
<h3 id="transition-usetransition">Transition (useTransition)</h3>
<p>Transition이란 React에서 긴급하지 않은 상태 업데이트의 우선순위를 낮춰 처리하는 기능이다.
사용자가 그 즉시 변경을 확인할 필요가 없는 업데이트를 더 낮은 우선순위로 처리해 UI가 부드럽게 동작하도록 만든다.</p>
<p>기존 React는 상태 변경 시 즉시 렌더링을 수행했기 때문에 입력이 지연되는 문제가 있다.</p>
<p>이제 <code>useTransition</code> 훅을 사용해 이 문제를 해결할 수 있다.</p>
<pre><code class="language-tsx">import { useState, useTransition } from &#39;react&#39;;

function App() {
  const [input, setInput] = useState(&#39;&#39;);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) =&gt; {
    startTransition(() =&gt; {
      setInput(e.target.value);
    });
  };

  return (
    &lt;&gt;
      &lt;input onChange={handleChange} /&gt;
      {isPending &amp;&amp; &lt;div&gt;로딩 중...&lt;/div&gt;}
      &lt;HeavyList filter={input} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p><code>startTransition</code> 으로 감싼 상태 업데이트는 즉시 렌더링 되지 않고, 업데이트 큐에 담겨 대기한다.
그 후, 메인 스레드가 여유가 생기면 이 작업을 렌더링하게 된다.
그 결과로, 입력 지연 문제를 해결해 UI에 반응성을 유지할 수 있다.</p>
<h3 id="usedeferredvalue">useDeferredValue</h3>
<p><code>useDeferredValue</code>는 <code>useTransition</code>과 유사하게 낮은 우선순위를 지정한다.</p>
<p>하지만, 함수 실행의 우선순위를 낮추는 <code>useTransition</code>과 달리, <code>useDeferredValue</code>는 값 자체의 업데이트 우선순위를 낮춘다.</p>
<p>즉, 빠르게 변경되는 상태 값의 업데이트를 지연시켜 렌더링을 줄이는 것이다.
이 역시, 렌더링을 즉시 수행하지 않고, 메인 스레드가 여유 있을 때만 렌더링을 수행하는 것이다.</p>
<p>아래와 같이 useMemo와 함께 사용하면 효과가 더 좋다.</p>
<pre><code class="language-tsx">import { useState, useDeferredValue, useMemo } from &#39;react&#39;;

function SearchList({ items }) {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);

  const filteredItems = useMemo(() =&gt; {
    return items.filter(item =&gt; item.includes(deferredQuery));
  }, [items, deferredQuery]);

  return (
    &lt;&gt;
      &lt;input onChange={(e) =&gt; setQuery(e.target.value)} /&gt;
      &lt;ItemList items={filteredItems} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h2 id="3-suspense">3. Suspense</h2>
<p>Suspense는 React 16에서 처음 등장했다.
이때의 Suspense는 <code>React.lazy</code> 를 이용한 코드 스플리팅에서만 사용 가능했다.
심지어, SSR 환경에서는 사용할 수 없었습니다.</p>
<p>React 18 - Suspense의 변경점은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/56d264ac-6874-42ee-8d75-c0fbc1f67237/image.png" alt=""></p>
<h3 id="1-항상-일관된-트리가-커밋된다">1. 항상 일관된 트리가 커밋된다.</h3>
<p>아래 예시를 보자.</p>
<pre><code class="language-jsx">import { lazy, Suspense } from &#39;react&#39;;
import Panel from &#39;./Panel&#39;;

const LazyComponent = lazy(() =&gt; import(&#39;./LazyComponent&#39;));

export default function App() {
  return (
    &lt;Suspense fallback={&lt;p&gt;Loading...&lt;/p&gt;}&gt;
      &lt;A&gt;
        &lt;LazyComponent /&gt;
      &lt;/A&gt;
    &lt;/Suspense&gt;);
}</code></pre>
<p><code>LazyComponent</code> 가 렌더링 할 준비가 되지 않았다면, 화면에 fallback UI를 표시해야 한다.</p>
<p>우선, React 17 버전을 살펴보자.</p>
<ol>
<li>우선, DOM에 <code>A</code>를 배치한 후, <code>LazyComponent</code>가 준비되지 않았기 때문에 <code>A</code>에 <code>display: none</code> 속성을 부여한다.
즉, 보이지는 않지만 <code>A</code>는 마운트가 되어있고, <code>effect</code>가 실행된다.</li>
<li>그리고, fallback UI를 표시한다.</li>
<li><code>LazyComponent</code>가 준비되면, 렌더링을 시도한다.</li>
<li>fallback UI를 지우고, <code>A</code>의 하위에 <code>LazyComponent</code>를 배치한다.</li>
<li><code>A</code>의 display: none 속성을 제거한다.</li>
</ol>
<p>React 18의 동작은 다음과 같다.</p>
<ol>
<li>DOM 에 <code>A</code>를 배치하지 않는다.</li>
<li>fallback UI를 보여준다.</li>
<li><code>LazyComponent</code>가 준비되면, 렌더링을 시도한다.</li>
<li>fallback UI를 지우고, <code>A</code> 그리고 <code>LazyComponent</code>를 배치한다.</li>
<li><code>A</code>의 effect가 실행된다.</li>
</ol>
<p>즉, 컴포넌트가 완전히 준비되었을 때에만 커밋되고, 항상 동일한 트리가 커밋된다.</p>
<h3 id="2-컨텐츠가-다시-나타날-때-layout-effects-가-재실행된다">2. 컨텐츠가 다시 나타날 때 layout effects 가 재실행된다.</h3>
<p>React 17의 Suspense는 LazyComponent가 아직 준비되지 않은 상태에서도 부모 컴포넌트를 미리 마운트했다. 
즉, 부모 컴포넌트가 DOM에 마운트된 상태였기 때문에 부모 컴포넌트 내부의 layout effect (<code>useLayoutEffect</code>)가 먼저 실행되었다.</p>
<p>이러한 동작으로 인해 LazyComponent가 아직 준비되지 않은 시점에 layout effect가 실행되면서, 
DOM 요소의 실제 크기나 위치 등 정확한 레이아웃 정보를 얻을 수 없었다. </p>
<p>나중에 LazyComponent가 로드되어 실제로 렌더링된 이후에도, 
이미 실행된 layout effect는 재실행되지 않아 여전히 올바른 레이아웃을 계산할 수 없는 문제가 있었다.</p>
<p>하지만 React 18에서는 <strong>Concurrent Rendering</strong>과 개선된 <strong>Suspense</strong>의 도입으로 이 문제가 크게 개선되었다. </p>
<p>React 18의 Suspense는 LazyComponent가 준비될 때까지 부모 컴포넌트의 마운트를 미루고, 
모든 하위 컴포넌트가 준비된 이후에 한꺼번에 DOM에 커밋(commit)된다.</p>
<p>이로 인해 React 18에서는 부모 컴포넌트의 layout effect가 실제로 DOM이 완전히 준비된 이후에 실행된다. 
즉, LazyComponent를 포함한 하위 컨텐츠의 크기나 레이아웃 정보가 정확하게 확보된 상태에서 layout effect가 실행되므로, 
정확한 DOM 정보에 기반하여 레이아웃을 계산할 수 있게 되었다.</p>
<h3 id="3-스트리밍을-사용한-ssr이-지원된다">3. 스트리밍을 사용한 SSR이 지원된다.</h3>
<p>React 17에서는 SSR에서 Suspense를 사용할 수 없었다.
하지만, React 18에서는 HTML 스트리밍을 지원하는 서버 렌더러가 추가되어서 스트림을 생성할 수 있게 되었다.</p>
<h3 id="4-transition을-이용해-fallback-ui를-방지한다">4. Transition을 이용해 fallback UI를 방지한다.</h3>
<p>앞서 다룬 useTransition을 사용해 fallback UI가 이전 컨텐츠를 가리는 경우를 막을 수 있게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 톺아보기 ③ - Hooks 동작 원리]]></title>
            <link>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-3</link>
            <guid>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-3</guid>
            <pubDate>Mon, 03 Mar 2025 15:00:19 GMT</pubDate>
            <description><![CDATA[<p>React Hooks는 함수형 컴포넌트에서도 
상태, 컴포넌트의 생명주기 등 React의 기능들을 사용할 수 있게 만들어준다.</p>
<h2 id="rules-of-hooks">Rules of Hooks</h2>
<p><a href="https://react.dev/reference/rules/rules-of-hooks">React의 공식 문서</a>에 보면, Hooks의 규칙이 소개되어있다.</p>
<h3 id="only-call-hooks-at-the-top-level">Only call Hooks at the top level</h3>
<p>use로 시작하는 함수인 Hook을 최상위 레벨에서만 호출하도록 되어있다.</p>
<ul>
<li>함수형 컴포넌트의 본문 최상위 레벨에서 호출할 것</li>
<li>커스텀 Hook의 본문 최상위 레벨에서 호출할 것</li>
</ul>
<p>특히, 조건문이나 반복문 내부, 그리고 조건부 <code>return</code> 문 이후에 Hook을 호출하는 것은 막혀있다.</p>
<p>이는, React가 렌더링 시에 Hook의 호출 순서를 기반으로 내부 상태를 관리하기 때문이라고 한다.</p>
<p>즉, 조건문이나 반복문 등에서 훅의 호출 순서가 달라지면, 각 렌더링에서 어떤 훅이 어떤 상태와 연결되는지를 예측할 수 없게 되어 문제가 발생할 수 있다는 것이다.</p>
<blockquote>
<p>느낌으로는 알겠지만, 아직 어렵다. 직접 구현해보면서 따라해본다.</p>
</blockquote>
<h2 id="usestate">useState</h2>
<p>일단 useState를 간단하게 만들어보자. <strong>실제와 다르다.</strong></p>
<pre><code class="language-jsx">const ReactX = (() =&gt; {
  const useState = (initialValue) =&gt; {
    let state = initialValue

    const setState = (newValue) =&gt; {
      state = newValue
    }

    return [state, setState] 
  }

  return { useState }
})()</code></pre>
<p>이 useState는 문제가 있다. 이 방식은 상태가 컴포넌트 호출 사이에 유지되지 않는다.
위 ReactX 아래에 다음과 같이 작성했다.</p>
<pre><code class="language-jsx">const { useState } = ReactX

const Component = () =&gt; {
  const [counter, setCounter] = useState(1)

  console.log(counter)

  if (counter !== 2) {
    setCounter(2)
  }
}

Component() // 1차 호출
Component() // 2차 호출</code></pre>
<p>1이 출력된 이후에 2가 출력될 것 같지만, 모두 1이 출력된다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/1288c1d4-db6f-42d3-b710-9433f9a8c01c/image.png" alt=""></p>
<p>이는, useState 내부의 state가 함수가 호출될 때마다 새로 생성되는 지역 변수이기 때문이다.
따라서, 카운터를 2로 변경해도, 함수가 종료되면서 사라지기 때문에, 다시 1로 초기화된다.</p>
<p><code>state</code> 변수를 외부로 이동시키면 이 문제를 해결할 수 있다.</p>
<pre><code class="language-jsx">const ReactX = (() =&gt; {
  let state

  const useState = (initialValue) =&gt; {
    if (state === undefined) {
      state = initialValue
    }

    const setState = (newValue) =&gt; {
      state = newValue
    }

    return [state, setState] 
  }

  return { useState }
})()

const { useState } = ReactX

const Component = () =&gt; {
  const [counter, setCounter] = useState(1)

  console.log(counter)

  if (counter !== 2) {
    setCounter(2)
  }
}

Component() // 1 출력
Component() // 2 출력</code></pre>
<p>하지만, 단 하나의 상태만 존재할 수 있게 되기 때문에 여전히 문제가 존재한다.</p>
<p>실제 React는 상태가 무한히 가능하기 때문에 이를 배열로 관리하고, 
<code>index</code> 를 사용해 접근할 수 있도록 변경한다.</p>
<pre><code class="language-jsx">const ReactX = (() =&gt; {
  let state = []
  let index = 0

  const useState = (initialValue) =&gt; {
    const localIndex = index
    index++

    if (state[localIndex] === undefined) {
      state[localIndex] = initialValue
    }

    const setState = (newValue) =&gt; {
      state[localIndex] = newValue
    }

    return [state[localIndex], setState]
  }

  const resetIndex = () =&gt; {
    index = 0
  }

  return { useState, resetIndex }
})()

const { useState, resetIndex } = ReactX

const Component = () =&gt; {
  const [counter, setCounter] = useState(1)

  console.log(counter)

  if (counter !== 2) {
    setCounter(2)
  }
}

Component() // 1차 호출
resetIndex()
Component() // 2차 호출</code></pre>
<p>이제 React 의 useState와 <strong><em>비슷하게</em></strong> 동작하도록 만들 수 있다.</p>
<blockquote>
<p>React Hook의 상태 관리는 사실 <strong>연결 리스트</strong>를 사용하고 있다.
이 배열로 만든 Hook과 마찬가지로 항상 동일한 순서로 Hook이 호출되어야 하는 것은 당연하다.</p>
</blockquote>
<p>실제로 1, 2 가 차례로 출력된다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/1c428f94-b937-478c-bec3-67fbe3c807ef/image.png" alt=""></p>
<p>이때, 알맞는 상태에 접근하기 위해 <code>localIndex</code> 를 사용하도록 했다.</p>
<p>만약, 조건문이나 반복문에 Hook의 호출이 이루어진다면, 매 렌더링에 이 <code>localIndex</code> 의 값이 달라질 것이고, 그렇다면 의도대로 상태에 접근할 수 없을 것이다.</p>
<h2 id="useeffect">useEffect</h2>
<p>이번엔 useEffect다. </p>
<p><code>state</code> 를 <code>hooks</code> 로 이름만 변경한 후, useEffect를 만들었다.</p>
<pre><code class="language-jsx">const ReactX = (() =&gt; {
  let hooks = []
  let index = 0

  const useState = (initialValue) =&gt; {
      // ...
  }

  const resetIndex = () =&gt; {
    index = 0
  }

  const useEffect = (callbackFn, dependencies) =&gt; {
    let hasChanged = true

    const oldDependencies = hooks[index]

    if (oldDependencies) {
      hasChanged = false

      dependencies.forEach((dependency, index) =&gt; {
        const oldDependency = oldDependencies[index]
        const areSame = Object.is(dependency, oldDependency)
        if (!areSame) {
          hasChanged = true
        }
      });
    }

    if (hasChanged) {
      callbackFn()
    }

    hooks[index] = dependencies
    index++
  }

  return { useState, useEffect, resetIndex }
})()

const { useState, useEffect, resetIndex } = ReactX

const Component = () =&gt; {
  useEffect(() =&gt; {
    console.log(&#39;Effect&#39;)
  }, [])
}

Component() // 1차 호출
resetIndex()
Component() // 2차 호출
resetIndex()
Component() // 3차 호출</code></pre>
<p>그리고, 결과를 보면 최초에만 실행되는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/ab003a60-8613-4e9b-96fa-b94caf4ae9b5/image.png" alt=""></p>
<p>마찬가지로, 컴포넌트를 아래와 같이 바꾸면, 의존성 배열 내의 값이 변경될 때만 동작함을 알 수 있다.</p>
<pre><code class="language-jsx">const Component = () =&gt; {
  const [counter, setCounter] = useState(1)
  const [changed, setChanged] = useState(false)

  console.log(counter)

  useEffect(() =&gt; {
    console.log(&#39;Effect&#39;)
  }, [changed])

  if (counter !== 2) {
    setCounter(2)
  }

  if (!changed &amp;&amp; counter === 2) {
    setChanged(true)
  }
}

Component() // 1차 호출
resetIndex()
Component() // 2차 호출
resetIndex()
Component() // 3차 호출</code></pre>
<p>카운터는 세 번 출력되지만, useEffect는 최초와 changed가 변경될 때만 동작함을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/71242cc0-609e-42ad-9052-76c458f36b66/image.png" alt=""></p>
<p>이렇게 useState와 useEffect의 구조를 보면 Hook의 규칙을 이해하는 것이 쉽다.</p>
<p>이는, React가 상태와 의존성 배열을 <strong>순서로 관리</strong>하기 때문이다.</p>
<p>즉, React는 내부적으로 Hook들을 배열(사실은 연결 리스트인 것 같다)로 관리하는데,
렌더링 시 Hook의 호출 순서가 일정해야 각 Hook과 올바른 상태, 의존성 배열을 연결할 수 있기 때문이다.</p>
<p>다음으로는 Concurrent Mode 에 대해 더 알아본다.</p>
<h2 id="references">References</h2>
<p>모던 리액트 Deep Dive
<a href="https://youtu.be/1VVfMVQabx0?si=l8bKmkuQSOzavx9t">How Do React Hooks Actually Work? React.js Deep Dive #3</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 톺아보기 ② - 컴포넌트 업데이트]]></title>
            <link>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-2</link>
            <guid>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-2</guid>
            <pubDate>Mon, 03 Mar 2025 14:47:56 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1">이전 글</a>에서 <code>render</code>와 <code>commit</code>단계에 대해서 알아봤다.
이번엔 React의 LifeCycle 메서드를 통해 조금 더 자세히 알아보려 한다.</p>
<h2 id="component-update">Component Update</h2>
<p>우리가 setState를 호출하게 되면, 이 변경 정보를 queue에 담고, 이후 하나씩 처리될 것이다.
물론, 변경 사항이 반영되는 것 이전 글에서 다루었듯이 동시에 이루어질 것이다.</p>
<p>그 과정을 자세히 알아보자.</p>
<p><img src="blob:https://velog.io/95625b3a-da93-4eb2-8798-215cafe66ec1" alt=""></p>
<h3 id="getderivedstatefromprops">getDerivedStateFromProps</h3>
<p>컴포넌트의 업데이트 과정에서 가장 먼저 호출되는 메서드는 <code>getDerivedStateFromProps</code>이다.</p>
<p>이 메서드는 부모로부터 전달되는 props의 변화에 따라 내부 state를 업데이트할 수 있도록 해주며,
state가 변경되는 경우에도 역시 호출된다.</p>
<p>즉, props와 state를 매개변수로 받아 새로운 state를 반환한다.</p>
<p>이 메소드는  static으로 정의되어 있다. 즉, 컴포넌트의 인스턴스에는 접근할 수 없다.
(생명주기 메소드 중 유일하게 static으로 선언되어 있다.)</p>
<p>오직, 전달받은 props와 state만을 기반으로 작동한다.
부수 효과 없이 오직 입력 값에 따라 결과를 만들어내는 순수 함수다.</p>
<h3 id="shouldcomponentupdate">shouldComponentUpdate</h3>
<p>React는 state의 변화에 리렌더링을 진행하지만, 경우에 따라 리렌더링을 막기도 한다.</p>
<p>바로 이 <code>shouldComponentUpdate</code> 메서드가 리렌더링을 막는 역할을 하며,
오직 성능 최적화를 위해 존재한다.</p>
<p>업데이트 후 적용될 props와 <code>getDerivedStateFromProps</code> 를 통해 계산된 새로운 state를 인자로 받아서 리렌더링 여부를 반환한다. 
이 역시 순수함수다.</p>
<h3 id="render">render</h3>
<p>만약 <code>shouldComponentUpdate</code> 의 결과가 <code>true</code> 라면,
props와 state를 기반으로 UI를 구성하는 React Element를 반환한다.</p>
<p>다른 메서드와 달리 필수로 구현되어야 한다.</p>
<blockquote>
<p>실제 DOM 조작은 아님을 명심하자!</p>
</blockquote>
<h3 id="getsnapshotbeforeupdate">getSnapshotBeforeUpdate</h3>
<p><code>render</code> 메서드가 호출되어 React Element가 생성된 후,
실제 DOM 조작을 통해 변경 사항을 반영하기 직전에 호출된다.</p>
<p>주로, 업데이트 전의 DOM 상태를 캡처해, 
<code>componentDidUpdate</code> 에서 필요한 경우 사용할 수 있도록 정보를 전달한다. </p>
<p><code>prevProps</code> 와 <code>prevState</code> 를 인자로 받아서 snapshot이라는 값을 반환한다.</p>
<p>이 값은 아래의 <code>componentDidUpdate</code> 의 세 번째 인자로 전달된다.</p>
<p>이때, DOM의 값을 읽어들이는 작업은 할 수 있지만, 
직접적으로 DOM을 조작하거나 상태를 변경하는 등 부수 효과를 발생시키지 않아야 한다.</p>
<h3 id="componentdidupdate">componentDidUpdate</h3>
<p>이전까지의 메서드는 이전 글에서 다뤘던 두 가지 단계 중 <code>render</code> 단계에서 수행되지만 <code>componentDidUpdate</code> 는 <code>commit</code> 단계에서 호출된다.</p>
<p>이 단계에서는 컴포넌트가 최신 상태로 업데이트된 상태이다.</p>
<p>따라서, 부수효과를 처리하기에 가장 좋은 타이밍이다.
즉, DOM 조작, 이벤트 구독/해제, 네트워크 요청 등을 안전하게 수행할 수 있다.</p>
<p>부수 효과를 처리할 최적의 타이밍이다.</p>
<p>즉, DOM 조작, 이벤트 구독, 네트워크 요청들을 처리하면 된다.
심지어, 상태도 변경할 수 있다. (하지만 조건문을 사용해 필요한 경우에만! 아니면 무한 리렌더링!)</p>
<p>다음은 Hooks 의 동작 원리를 더 알아볼 예정이다.</p>
<h2 id="references">References</h2>
<p>모던 리액트 Deep Dive
<a href="https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">react-lifecycle-methods-diagram</a>
<a href="https://youtu.be/2cSijEC_m7g?si=PEwkBFSAkIYLPLMN">How Does React State Actually Work? React.js Deep Dive #4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 톺아보기 ① - 기본 동작 원리]]></title>
            <link>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1</link>
            <guid>https://velog.io/@pmthk__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1</guid>
            <pubDate>Thu, 27 Feb 2025 08:48:55 GMT</pubDate>
            <description><![CDATA[<h2 id="jsx">JSX</h2>
<p>리액트는 JSX를 기본으로 사용한다.
JSX는 JavaScript 파일 내에서 HTML의 태그 등을 사용할 수 있게 만드는 확장 문법이다.</p>
<p>JSX로 작성한 코드가 어떻게 화면에 나타나는가?
JSX를 사용해 React.Element를 리턴하는 컴포넌트를 출력해보면, JavaScript Object가 출력된다.</p>
<details>
  <summary><strong>import React from "react"</strong>가 사라진 이유</summary>

<p>  React 16 이전에는 Babel 이 JSX 문법을 <code>React.createElement</code> 호출로 변환했다.<br/>
  이 과정에서 <code>React.createElement</code> 함수가 사용되기 때문에, 해당 함수의 정의가 있어야 한다.<br/><br/></p>
<p>  따라서, 모든 JSX 파일 상단에 <code>import React from ‘react’</code> 를 명시적으로 추가해야 했다.<br/>
  하지만, React 17 이상에서는 Babel 이나 TypeScript 가 JSX 코드를 트랜스파일링할 때, <code>jsx</code> 나 <code>jsxs</code> 와 같은 헬퍼 함수를 사용하도록 변경되었고, 이 헬퍼 함수들이 <code>react/jsx-runtime</code> 런타임 모듈에서 불러오도록 변경되었다.<br/><br/></p>
<p>  React Native 는 여전히 명시해야 한다.<br/>
  React Native는 Metro 번들러를 사용하며, 기본적으로 제공되는 Babel 프리셋이 새로운 JSX 변환 방식을 기본으로 활성하지 않는 경우가 많다.</p>
</details>

<h2 id="element-component-component-instance">Element, Component, Component Instance</h2>
<h3 id="react-element">React Element</h3>
<p>JSX를 출력하면 나오는 <strong>Object</strong>, 즉 UI의 구성을 설명하는 <strong>객체가 React Element</strong>이다.
클래스형 컴포넌트의 경우 render() 메소드를 통해 나온 결과물이고, 함수형 컴포넌트의 함수의 반환값이다.</p>
<h3 id="react-component">React Component</h3>
<p>어떻게 렌더링할지를 정의하는 함수나 클래스를 의미한다.</p>
<h3 id="react-component-instance">React Component Instance</h3>
<p>실제로 React Component가 렌더링되어 생성된 구체적인 객체를 의미한다.
컴포넌트의 상태, 생명주기가 관리되는 독립적인 객체를 의미한다.</p>
<h2 id="reconciliation">Reconciliation</h2>
<p>React의 목표는 React Element 들의 Tree 구조를 만들어, 실제 DOM에 반영하는 것이다.
React Element는 단순 Object이기 때문에, 이 작업이 매우 빠르게 이루어진다.</p>
<p>이때, 이 React Element들로 이루어진 Tree를 Virtual DOM이라고 하며, 메모리 영역에 저장된다. 
처음에는 해당 트리를 전부 DOM에 그려내지만, 이후에는 그렇지 않다.</p>
<p>DOM에 실제로 그리는 비용은 메모리단에서 JavaScript Object 를 조정하는 비용에 비해 매우 비싸다.
하지만, 특정 부분은 다시 렌더링 해야할 필요도 있다.</p>
<h3 id="diffing">Diffing</h3>
<p>React 는 이렇게 다시 렌더링 해야할 부분을 찾기 위해 아래 두가지 가정을 한다.</p>
<ol>
<li><strong>같은 타입의 Element는 유사한 구조를 가진다.</strong>
즉, 그 Element와 그 하위의 모든 Component Instance를 기존 인스턴스에서 제거(언마운트)하고, 새로운 타입의 Component Instance로 새롭게 생성(마운트)한다.</li>
<li><strong>리스트의 자식들은 key를 통해 고유하게 식별된다.</strong>
<code>key</code> 가 변경되면 React는 해당 Element를 이전과 다른 새로운 요소로 인식하고, 새롭게 마운트한다.
<code>key</code> 의 존재 자체만이 아닌, 같은 위치에서의 <code>key</code> 가 다르다면, 두 요소가 서로 다르다고 판단한다.<br/>
이때, 같은 위치란, 배열 내에서 같은 인덱스가 아닌, 부모 자식 관계 내에서 <code>key</code> 가 어떤 요소를 지칭하는지에 따라 결정된다.<br/>
즉, 리스트나 자식 컴포넌트에서 key가 변경되면, 해당 컴포넌트와 그 하위에 있는 모든 컴포넌트 인스턴스가 기존과는 다른 새로운 것으로 간주되어 언마운트되고, 새로운 인스턴스로 새롭게 마운트한다.
동일하다면, 인스턴스를 재사용하고, 순서를 변경하거나 업데이트한다.</li>
</ol>
<h3 id="batch-update">Batch Update</h3>
<p>위 Diffing 알고리즘을 통해 변경된 부분을 찾아내, 실제 DOM에 최소한의 작업만 적용할 수 있게 한다.
여러 상태 변경이 일어난 후, React는 한번에 변경사항을 찾아내고, 실제 DOM에 반영한다.
즉, DOM 조작 횟수를 줄이는 최적화가 되어 있다.</p>
<h2 id="rendering과-state-change">Rendering과 State Change</h2>
<p>React는 자체적으로 브라우저의 DOM이나 네이티브 UI를 직접 다루지 않는다.
대신, <strong>React Core</strong> 와 <strong>Renderer</strong>(react-dom, react-native)로 역할을 분리해놓았다.</p>
<h3 id="react-core">React Core</h3>
<p>React Core는 UI의 구조, 상태 관리, 그리고 컴포넌트의 생명주기와 관련된 로직을 담고 있다.
순수한 JavaScript 라이브러리로 특정 플랫폼에 의존하지 않는다.</p>
<p>앞서 살펴본, Reconciliation 과정을 통해 가상 DOM을 업데이트한다.
또, 이전 상태와 새로운 상태의 차이를 계산해 어떤 부분이 변경되어야 하는지를 결정한다.</p>
<p>setState, Hooks 등의 요청이 발생하면, React Core에서 여러 업데이트를 모아서 한번에 처리한다.
이를 통해 렌더링을 줄이고 성능을 최적화한다.</p>
<h3 id="renderer">Renderer</h3>
<p>React Core가 생성한 가상 DOM을 실제 사용자 인터페이스로 변환하는 역할을 한다.</p>
<p>브라우저 환경에서는 DOM API를 사용해 실제 HTML 요소를 생성하고 업데이트한다.
모바일 환경이라면, 네이티브 UI 컴포넌트를 생성해, iOS, Android의 네이티브 뷰로 렌더링한다.</p>
<h2 id="react-fiber">React Fiber</h2>
<h3 id="stack-reconciler-legacy">Stack Reconciler (Legacy)</h3>
<p>React 16 이전의 Reconciler 알고리즘은 스택 구조로 이루어져 있었다.
하나의 스택에 작업이 쌓이고, 동기적으로 작업이 이루어진다.</p>
<p>이 스택 기반 Reconciler의 특징은 다음과 같다.</p>
<ul>
<li>Synchronous; <strong>동기로 동작</strong>한다.</li>
<li>스택이 빌 때까지 동작한다.</li>
<li>작업의 중단이 불가하다.</li>
</ul>
<p>이러한 특징으로, 다른 우선순위가 높은 작업을 먼저 처리할 수 없고,
중단하고 싶어도 중단할 수 없다. </p>
<p>즉, 앱이 무반응 상태가 되거나 프레임 드랍이 발생할 수 있다. 아래에서 확인할 수 있다.
<a href="https://claudiopro.github.io/react-fiber-vs-stack-demo/">https://claudiopro.github.io/react-fiber-vs-stack-demo/</a></p>
<p>이를 개선하기 위해 Fiber가 등장한다.</p>
<h3 id="fiber-reconciler">Fiber Reconciler</h3>
<p>React Element가 JavaScript Object인 것 처럼, Fiber 역시 JavaScript Object다.
Fiber는 <strong>애니메이션</strong>과 <strong>반응성</strong>에 초점을 두고 아래와 같은 특징이 있다.</p>
<ul>
<li>작업을 작은 단위(chunk)로 나누고, 우선순위를 지정한다.</li>
<li>작업을 중단하고 재개할 수 있다.</li>
<li>작업을 재사용하거나 불필요하다면 버릴 수 있다.</li>
<li><strong>비동기로 동작한다.</strong></li>
</ul>
<blockquote>
<p>Fiber는 React의 작업 단위로서, 각 Fiber 노드에 대해 렌더링 작업(즉, reconciliation)을 진행한 후, &quot;완료된 작업&quot; 형태로 준비되면 commit 단계에서 실제 DOM에 반영된다.</p>
</blockquote>
<p>즉, React는 두 단계로 작업을 수행한다.</p>
<ol>
<li><p>render 단계 (비동기)
보이지 않는 작업들을 <strong>비동기적으로 처리하는 단계</strong>이다.
작업의 우선순위를 정하고, 그에 따라 작업을 중지하거나 취소할 수도 있다.</p>
</li>
<li><p>commit 단계 (동기)
이 작업은 <strong>동기로 실행되고, 중단될 수 없다</strong>.</p>
</li>
</ol>
<p>Fiber는 작업의 단위다.
상태 변화, 생명주기 메소드의 호출, DOM 조작 등 모두 작업이고, 즉시 혹은 미래에 실행될 것이다.</p>
<p>Time Slicing을 이용해 위 작업을 작은 chunk단위로 나눌 수 있다.
우선순위가 높은 작업은 <code>requestAnimationFrmae()</code> 을 이용해 빠르게 실행하도록 스케줄링할 수 있고,
반대로 낮은 작업은 <code>requestIdleCallback()</code> 을 이용해 스케줄링할 수 있다.</p>
<h3 id="fiber-vs-react-element">Fiber vs React Element</h3>
<p>Fiber와 Element 모두 JavaScript Object 이므로, 굉장히 유사하다.
실제로, Fiber는 Element로부터 생성되는 경우가 많고, 많은 속성을 공유하기도 한다.</p>
<p>하지만 Element가 매번 새로 생성되는 것과 달리, Fiber는 최대한 많이 재사용된다.
Element는 UI를 묘사하지만, Fiber는 상태, 생명주기 메서드, hook 들까지 관리하는 역할을 한다.</p>
<h3 id="fiber-tree">Fiber Tree</h3>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/3ee3e8bd-f948-48ac-89d3-ac1aba8f6787/image.png" alt="Fiber Tree"></p>
<p>두 개의 Tree가 존재한다.
현재 화면에 보이는 <code>current</code> 트리, 즉, DOM 과 동기화 되어 있는 <code>current</code> 트리와
실제 비동기 작업이 반영된 <code>workInProgress</code> 트리가 있다.
비동기 작업들이 모두 완료되면 <code>current</code>와 <code>workInProgress</code> 의 포인터를 변경하는 방식으로 작업을 수행해 나간다.</p>
<p>이때 중요한 점은 일반적인 트리 탐색처럼 재귀로 동작하는 것이 아닌 하나의 While Loop 으로 동작한다.</p>
<p>하지만, 트리를 스왑하는 것만으로는 모든 작업을 해결할 수 없다.
동기적으로 작업을 처리하는 commiit 단계에서도 DOM 조작, 특정 생명주기 메서드의 처리 등이 필요하다.</p>
<p>Render 단계에서는 Fiber Tree만 만들어내는 것이 아니라 <strong>Effect 목록</strong>을 만들어낸다.</p>
<h3 id="effect">Effect</h3>
<p>Effect는 DOM 조작, 특정 생명주기 메소드의 호출등을 의미한다.
이 작업들은 다른 Component에 영향을 줄 수 있기 때문에 render 단계에서 실행될 수 없다.</p>
<p>Commit 단계에서, React는 모든 Effect를 확인하며 Component Instance에 반영한다.
이 변화들은 화면에 반영되어야 하기 때문에, 동기적으로, 하나의 연속적인 변경사항으로 이뤄진다.</p>
<p>즉, render 단계에서 생성한 Effect 목록에 의해 결정된다.</p>
<h3 id="fiber의-alternate">Fiber의 alternate</h3>
<p>Fiber Tree는 두 개다.
<code>alternate</code> 속성은 Fiber Tree의 반대 Fiber 요소를 가리킨다.
이를 통해 Fiber 의 재사용을 극대화한다.</p>
<p>업데이트가 발생하면, React는 <code>current</code> 트리의 각 Fiber 노드에 대응하는 <code>workInProgress</code> Fiber를 준비한다.</p>
<p>이미 <code>alternate</code>가 있고, 변경이 없다면, 해당 Fiber를 그대로 사용하고, 변경이 있다면, 변경이 필요한 부분에 한해 복사를 한다.
항상 깊은 복사가 이루어지지 않는다.</p>
<p>주의할 점은 두 Tree의 Fiber가 서로를 가리킨다는 점이다.
이전 상태를 가리키는 것이 아닌, 두 Fiber가 서로 번갈아가며 <code>current</code>와 <code>workInProgress</code> 역할을 하는 것이다.</p>
<blockquote>
<p>React 소스 코드의 <code>ReactFiber.js</code> 362번째 줄에서 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pmthk__/post/e7236bdd-fd61-4118-a0dd-5a89c73c3347/image.png" alt=""></p>
</blockquote>
<p>이를 통해 Fiber의 재사용을 극대화하면서도, 필요한 경우만 업데이트를 수행하게 된다.</p>
<h3 id="errorboundary-suspense-concurrent-mode">ErrorBoundary, Suspense, Concurrent Mode</h3>
<p>React Fiber의 도입으로 활용할 수 있는 기능들이다.
Concurrent Mode에 대해서는 더 자세히 살펴볼 예정이다.</p>
<p>다음엔 컴포넌트가 업데이트 되는 과정을 조금 더 살펴볼 예정이다.</p>
<h2 id="references">References</h2>
<p><a href="https://www.youtube.com/watch?v=7YhdqIR2Yzo">How Does React Actually Work? React.js Deep Dive #1</a>
<a href="https://www.youtube.com/watch?v=0ympFIwQFJw">How Does React Actually Work? React.js Deep Dive #2</a>
<a href="https://d2.naver.com/helloworld/2690975">React 파이버 아키텍처 분석</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions 로 Vercel 배포 관리하기]]></title>
            <link>https://velog.io/@pmthk__/Github-Actions-%EB%A1%9C-Vercel-%EB%B0%B0%ED%8F%AC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pmthk__/Github-Actions-%EB%A1%9C-Vercel-%EB%B0%B0%ED%8F%AC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 23 Jan 2025 09:44:58 GMT</pubDate>
            <description><![CDATA[<p>Github Actions 와 Vercel 을 활용해 개발하고 있는 블로그의 배포 프로세스를 설정해보려고 한다.</p>
<p>기본적으로 Vercel 은 브랜치에 Push 하면 자동으로 배포가 진행되지만,
나는 내가 설정한 방식으로 동작하는 프로세스를 원했다.</p>
<p>따라서, 브랜치 전략과 Vercel 의 배포 생명주기에 맞는, Github Actions 를 통해 제어되는 배포 프로세스를 만들어보려고 한다.</p>
<h1 id="vercel-의-배포-생명주기">Vercel 의 배포 생명주기</h1>
<p>Vercel 의 <a href="https://vercel.com/docs/deployments/overview#deployment-lifecycle">배포 생명주기</a>를 보면, 총 6단계로 구성되어 있다.</p>
<ol>
<li>Local Development: 개발</li>
<li>Commit and build: Github 등의 저장소에 푸시 → 빌드 시작</li>
<li>Preview: 빌드 성공 → 프리뷰 배포 생성</li>
<li>Production: 프로덕션 배포 생성 ( <code>main</code> 브랜치에 푸시된 경우 )</li>
<li>Retention: 일정 기간 보관</li>
<li>Recovery: 복구 기간 내 복구 가능</li>
</ol>
<p>이 중 나는 빌드하고 배포(Preview, Production)하는 3, 4단계의 동작을 제어해보려고 한다.</p>
<p>실제로, Vercel 설정에서 Preview 와 Production 환경의 환경 변수를 각각 설정할 수 있다.
이를 사용해서 env.local env.production 도 Vercel 과 동일하게 유지할 수 있다.</p>
<h1 id="브랜치-전략">브랜치 전략</h1>
<p>지금은 개인 블로그 만들기 프로젝트이기 때문에 다음과 같은 브랜치 전략을 사용했다.</p>
<ul>
<li>feature/기능명: 새로운 기능 개발</li>
<li>dev (main): 개발 브랜치. feature 를 dev 로 병합</li>
<li>release: 실제 운영 배포</li>
</ul>
<p>이 전략을 다음과 같이 Vercel 의 배포 생명주기와 연계하려 한다.</p>
<ul>
<li>feature 브랜치 → 로컬 환경<ul>
<li>별도의 Vercel 을 통한 배포를 진행하지 않는다.</li>
<li>로컬에서 확인한다.</li>
</ul>
</li>
<li>dev 브랜치 → Preview 배포<ul>
<li>dev 브랜치로 병합된 작업을 기반으로 Preview 배포를 생성한다.</li>
</ul>
</li>
<li>release 브랜치 → Production 배포<ul>
<li>Preview 로 검증이 되면, release 배포로 병합해 Production 배포를 생성한다.</li>
</ul>
</li>
</ul>
<h1 id="vercel-설정하기">Vercel 설정하기</h1>
<p>Vercel 과 Github 를 연동하면 내 커밋 Push 를 기반으로 빌드가 진행되고,
Preview 혹은 Production 배포가 진행된다.</p>
<p>이 자동화를 꺼줄 필요가 있다.</p>
<p>Vercel 프로젝트의 Settings - Git 페이지를 보면, 아래와 같이 Ignored Build Step 이 있다.
이것을 아무것도 하지 않도록 Don&#39;t build anything 으로 바꿔준다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/81b02e61-fd6a-4812-84f7-adf53269415d/image.png" alt=""></p>
<p>그러면, 커밋 Push 를 하더라도 어떤 빌드 과정이나 배포 프로세스도 동작하지 않는다.</p>
<p>그리고, Github Actions 동작을 위해 Vercel 의 토큰을 발급받아야 한다.
Account Settings - Tokens 의 Create Token 으로 이동해 토큰을 발급받는다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/bfef32f1-aa6f-4cb1-895a-a8e2e72e56d8/image.png" alt=""></p>
<h1 id="워크플로우-작성">워크플로우 작성</h1>
<p>배포 동작은 <code>dev</code> 혹은 <code>release</code> 브랜치로 병합되었을 경우 동작하도록 설정하면 된다.
그리고 Vercel CLI 를 이용하면, <code>—prod</code> 로 Production 배포를 설정할 수 있다. ( 없으면 Preview )</p>
<pre><code class="language-yaml">name: Deploy to Vercel

on:
  push:
    branches:
      - dev
      - release

jobs:
  deploy:
    runs-on: ubuntu-latest

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

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: &#39;18&#39;

      - name: Install Vercel CLI
        run: npm install -g vercel

      - name: Deploy to Vercel
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
        run: |
          if [ &quot;${{ github.ref }}&quot; == &quot;refs/heads/release&quot; ]; then
            echo &quot;Deploying Production...&quot;
            vercel deploy --prod --yes --token $VERCEL_TOKEN
          else
            echo &quot;Deploying Preview...&quot;
            vercel deploy --yes --token $VERCEL_TOKEN
          fi</code></pre>
<p>Vercel Action 이 있었는데, <a href="https://vercel.com/docs/cli/deploy">문서</a>를 읽어보면서 CLI 기반으로 작성했다.
이게 더 쉽고 간단한 방법인 것 같다.</p>
<h1 id="마무리">마무리</h1>
<p>생각보다 간단하고 쉽게, 내 입맛에 맞는 배포 프로세스를 설정할 수 있다.</p>
<p>아직은 개발, 프로덕션 별로 다른 환경 변수가 없지만, 점차 필요성이 커질 것이다.
Vercel의 환경 변수 설정을 활용하면 개발 및 운영 환경 간 설정 차이를 관리하고, 배포된 상태에서 <strong>dev</strong>와 <strong>release</strong>를 비교할 수도 있게 됐다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[병렬 처리와 RateLimiter 적용하기]]></title>
            <link>https://velog.io/@pmthk__/%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC%EC%99%80-RateLimiter-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pmthk__/%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC%EC%99%80-RateLimiter-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 07 Jan 2025 16:02:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/pmthk__/post/1e8fcb69-641b-4d05-9b8e-b3b4b3a3f29a/image.png" alt=""></p>
<blockquote>
<p><strong>고로시롤</strong>은 리그 오브 레전드 전적을 직접 조회하는 번거로움을 해소하기 위해 만든 전적 조회 디스코드 봇입니다.</p>
</blockquote>
<p>처음에는 개인적인 사용을 목적으로 개발한 서비스가 현재는 260개 이상 채널에서 사용되고 있다.
사용자가 확대되면서, 서비스의 핵심 기능인 자동 전적 조회의 속도 개선이 필요해졌다.
특히, Riot Games <strong>API Rate Limit을 준수</strong>하면서도 자동 전적 조회의 <strong>속도를 개선</strong>하는 것이 중요했다.
이 글은 속도 개선을 위한 병렬 처리를 도입하고, <strong>RateLimiter</strong> 를 구현해 병렬 처리 간 외부 API 규칙을 준수하는 과정에 대한 글이다.</p>
<h1 id="전적-조회-성능-문제">전적 조회 성능 문제</h1>
<p>고로시롤은 매 정각(0시부터 23시)에 이 봇이 초대된 채널에 등록된 소환사의 전적을 조회하여 전송하고 있다.
초기에는 사용자가 적어, 각 정시에 시작하면 30초 내로 모든 전적 조회를 완료하고 그 정보를 전송할 수 있었다.
하지만 사용자 수가 증가하면서 전적 조회 시간이 약 6분으로 크게 늘어나 서비스 성능 저하가 발생했다.
전적 조회 시간이 길어지면서 이탈하는 사용자도 생기는 것을 확인했다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/19df9b97-6f79-41a5-8865-0d394f821ae1/image.png" alt=""></p>
<p>특히, 기본으로 설정되어 있는 시간인 12시(사진상 3시)에는 전적 조회에 약 7분 가량 걸리는 것을 확인할 수 있었다.</p>
<p>문제의 주요 원인은 이 전적 조회의 모든 구성이 순차 처리 방식이라는 것이다.
초기에는 사용자 수가 적어 이러한 문제가 눈에 띄지 않았지만, 사용자 수의 증가로 성능 저하를 느낄 수 있었다.</p>
<h1 id="병렬-처리-도입하기">병렬 처리 도입하기</h1>
<p>전적 조회 성능 저하 문제를 해결하기 위해 병렬 처리를 도입했다.</p>
<p>기존 전적 조회는 다음과 같은 순서로 처리되고 있었다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/7770847d-99c5-493e-a76d-fb040d95df44/image.png" alt=""></p>
<ol>
<li><p><strong>정각에 작업 시작</strong>: 매 정각(0시부터 23시)에 전적 조회 작업이 이루어진다.</p>
</li>
<li><p><strong>조회할 채널 목록 조회</strong>: 해당 시간에 조회해야할 채널 목록을 조회하고, 각 채널에 등록된 소환사들의 목록을 조회한다.</p>
</li>
<li><p><strong>채널 목록 순회</strong>: 조회한 채널 목록을 하나씩 순차적으로 순회하며 각 채널에 대해 다음 작업을 수행한다.</p>
<p> 3-1. <strong>소환사 전적 조회</strong>: 채널의 소환사 목록을 하나씩 순차적으로 순회하며 각 소환사 전적을 조회한다. 이 과정에서 Riot API 에 요청이 발생한다.</p>
<ul>
<li><p><strong>소환사 랭크 정보 조회</strong> (소환사당 1회)</p>
</li>
<li><p><strong>소환사의 게임 기록 조회</strong> (소환사당 2회)</p>
</li>
<li><p>게임 기록별 소환사 플레이 정보 조회 (게임 기록당 1번, N판 플레이시 N번)</p>
<p>3-2. <strong>채널에 메시지 전송</strong>: 조회된 전적 정보를 채널에 전송한다.</p>
</li>
</ul>
</li>
</ol>
<p>사용자 수 증가에 따라 3번 과정에서 문제가 발생했다.</p>
<p>채널별 여러 소환사의 정보를 순차적으로 조회하던 중 전체 조회 시간이 급격히 늘어났다.
이를 개선하기 위해 병렬 처리를 도입해 동시에 여러 작업을 처리하고 전체 조회 시간을 줄여 성능을 개선하고자 했다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/fc756249-c5ac-4592-ab78-bc4b5009f2bd/image.png" alt=""></p>
<p>그러나 새로운 문제가 있었다.</p>
<p>채널별 소환사 정보 조회와 채널별 조회 기능을 모두 병렬처리하며 외부 API Rate Limit 문제가 있었다.
Riot API 는 10초당 500개의 요청으로 제한되어 있어, 대량 요청이 동시에 발생할 경우 초과할 위험이 컸다.</p>
<p>현재를 기준으로 0시에 약 120명의 소환사를 조회하는 데 소환사당 최소 3회의 요청이 필요했고,
소환사가 한 판 플레이할 때마다 요청 횟수가 1회씩 증가한다.
즉, 평균 2판 이상 플레이할 경우 Rate Limit을 초과하는 것이었다.</p>
<p>이를 해결하기 위해 RateLimiter 을 만들어보고자 했다.</p>
<h1 id="rate-limiter-설계-및-적용하기">Rate Limiter 설계 및 적용하기</h1>
<p>최초 설계는 10초당 500개의 요청을 순차처리하는 방식이었다.
모든 요청 사이에 일정한 딜레이를 두어 API 규칙을 준수하는 것이었다.</p>
<p>하지만, 이 방식은 위의 병렬 처리 도입 목적과 맞지 않았다. 
병렬 처리를 통해 여러 요청을 동시에 처리하면서 성능을 개선하고자 했지만,
순차적으로 처리하는 방식은 결국 병렬 처리의 효율성을 저하시키는 것이었다.</p>
<p>따라서, 두 가지 모드의 RateLimiter 를 생각해봤다.</p>
<ol>
<li><p>일반 모드 Normal Mode
요청이 적다면, 여러 요청을 동시에 처리할 수 있다.</p>
</li>
<li><p>쓰로틀 모드 Throttle Mode
요청이 일정 수준 이상으로 증가하면, 요청을 큐에 담아 순차적으로 실행한다.    </p>
</li>
</ol>
<p>동작 흐름은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/006821fd-f018-4244-b1bd-7f18360c4c2c/image.png" alt=""></p>
<ol>
<li>요청의 양을 모니터링 해 현재 모드를 판단한다. 이를 위해 요청이 시작된 <strong>타임스탬프를 기록</strong>했다.</li>
<li><strong>쓰로틀 모드</strong>에서는 요청을 <strong>큐에 담아 순차적으로 실행</strong>한다.</li>
<li><strong>일반모드</strong>에서는 여러 요청을 동시에 처리해 성능을 극대화 한다.</li>
</ol>
<p>각 모드의 전환은 RateLimiter 인스턴스를 생성할 때 입력한 값을 기준으로, 그리고 임계치를 설정할 수 있도록 했다.</p>
<blockquote>
<p>실제 코드는 <a href="https://github.com/PMtHk/gorosey-lol/blob/main/src/utils/RateLimiter.ts">여기</a>에서 확인할 수 있습니다.</p>
</blockquote>
<h2 id="적용하면서-만난-사소한-문제점">적용하면서 만난 사소한 문제점</h2>
<p>API Rate Limit 을 만족하기 위해 RateLimiter 를 만들어서 적용해도 요청 허용 수를 초과했다는 에러가 발생했다.
이는 각 엔드포인트마다 설정된 Rate Limit 이 달랐기 때문이다.</p>
<p>예를 들어, 내 API 키를 기준으로 각 엔드포인트의 Rate Limit 은 다음과 같다.</p>
<ul>
<li><code>/riot/account</code> 는 1분에 1000개</li>
<li><code>/lol/summoner</code> 는 1분에 1600개</li>
<li><code>/lol/league</code> 는 1분에 100개</li>
<li><code>/lol/match</code> 는 1분에 2000개</li>
</ul>
<p>따라서, 하나의 RateLimiter가 아닌 엔드포인트 별로 다른 인스턴스로 적용해야 했다.</p>
<h1 id="전적-조회-성능-개선">전적 조회 성능 개선</h1>
<p>위 과정을 거쳐 RateLimiter 를 도입했고, 다음과 같은 결과를 얻을 수 있었다.</p>
<ol>
<li><strong>조회 성능 개선</strong>
기존에 약 7분이 걸리던 작업은 이제 약 1분 내외로 처리할 수 있다.
<img src="https://velog.velcdn.com/images/pmthk__/post/44086faf-64fe-4445-b262-65554b5d3691/image.png" alt=""></li>
</ol>
<ol start="2">
<li><strong>API Rate Limit 규칙 준수</strong>
많은 요청이 발생하더라도 외부 API 요청 제한 규칙을 안정적으로 준수할 수 있다.</li>
</ol>
<h1 id="추가-고려사항">추가 고려사항</h1>
<p>외부 API 규칙을 준수하면서 7분이라는 시간을 1분으로 줄인 것은 충분히 좋은 성과다.
하지만, 아직 더 개선할 점들이 있다.</p>
<p><strong>사용자가 많아지면 여전히 문제가 발생한다.</strong>
쓰로틀 모드는 결국 순차 처리가 되고 있기 때문이다.
가장 우려가 되는 부분은 <code>/lol/league</code> 에 대한 요청에 대한 것이다. 1분에 100개 이기 때문에.</p>
<p>저 엔드포인트는 랭크와 티어를 조회하는 요청이다. 
리그 오브 레전드의 게임의 평균 플레이 타임을 고려한다면 20분 이내에는 같은 결과를 조회할 수 있다.
따라서, 크론 작업 사이에 저 부분을 분리해서 순차적으로 실행시킨다면, 쓰로틀 모드로 요청이 순차 처리되는 비율을 줄일 수 있을 것이다.</p>
<p><strong>정시에 발송되지 않는다.</strong>
정시에 전적을 발송해주는 서비스를 만들고 있기 때문에 지금은 그 간격을 줄였을 뿐 아직 정시에 발송되지 않는다.
전적 조회를 미리 해두고 정시에 발송하는 방향으로 개선할 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[42장. 비동기 프로그래밍]]></title>
            <link>https://velog.io/@pmthk__/42%EC%9E%A5.-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@pmthk__/42%EC%9E%A5.-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Thu, 02 Jan 2025 15:53:07 GMT</pubDate>
            <description><![CDATA[<h1 id="동기-처리와-비동기-처리">동기 처리와 비동기 처리</h1>
<p>함수를 호출하면 함수 코드가 평가되어 함수 실행 컨텍스트가 실행된다.</p>
<p>이때 생성된 함수 실행 컨텍스트는 실행 컨텍스트 스택(콜 스택)에 푸시되고 함수 코드가 실행된다.</p>
<p>함수 코드의 실행이 종료되면 콜 스택에서 팝되어 제거된다.</p>
<pre><code class="language-jsx">const foo = () =&gt; {};
const bar = () =&gt; {};

foo();
bar();</code></pre>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/f87d7b50-3529-442f-a86d-203b6c14c4c4/image.png" alt=""></p>
<p><strong>자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖는다.</strong></p>
<p>자바스크립트 엔진은 한 번에 하나의 태스크만 실행할 수 있는 <strong>싱글 스레드 방식</strong>으로 동작한다.</p>
<p>즉, <strong>블로킹이 발생</strong>한다.</p>
<p>현재 실행 중인 태스크가 종료될 때까지 다음 실행될 태스크가 대기하는 방식을 <strong>동기 처리</strong> 방식,</p>
<p>반대로 현재 실행 중인 태스크가 종료되지 않아도 다음 태스크를 바로 실행하는 방식을 <strong>비동기 처리</strong> 방식이라 한다.</p>
<p>비동기 처리는 이벤트 루프와 태스크 큐와 깊은 관계가 있다.</p>
<h1 id="이벤트-루프와-태스크-큐">이벤트 루프와 태스크 큐</h1>
<p>자바스크립트의 특징 중 하나는 싱글 스레드로 동작하는 것이다.</p>
<p>하지만 브라우저는 많은 태스크가 동시에 처리되는 것처럼 느껴진다.</p>
<p>이처럼 자바스크립트의 동시성을 지원하는 것이 바로 이벤트 루프(event loop)다.</p>
<p>이벤트 루프는 브라우저의 내장 기능 중 하나다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/a47739fe-2086-4637-995f-9e4f9ea10d06/image.png" alt=""></p>
<p>구글의 V8 자바스크립트 엔진을 비롯한 대부분의 자바스크립트 엔진은 크게 2개의 영역으로 구분할 수 있다.</p>
<ul>
<li>콜 스택: 소스코드 평가 과정에서 생성된 실행 컨텍스트가 추가되고 제거되는 스택 자료구조인 실행 컨텍스트 스택이다.</li>
<li>힙: 객체가 저장되는 메모리 공간이다.</li>
</ul>
<p>자바스크립트 엔진은 단순히 태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차 실행한다.</p>
<p>비동기 처리에서 소스 코드의 평가와 실행을 제외한 모든 처리는 브라우저 또는 Node.js 가 담당한다.</p>
<p>이를 위해서 브라우저 환경은 태스크 큐와 이벤트 루프를 제공한다.</p>
<ul>
<li>태스크 큐: 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다. 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 보관되는 마이크로태스크 큐도 존재한다.</li>
<li>이벤트 루프: 콜 스택에 실행중인 실행 컨텍스트가 있는지, 태스크 큐(또한 마이크로태스크 큐)에 대기 중인 함수가 있는지를 반복해서 확인하고 이를 순차적으로 콜 스택으로 이동시킨다.</li>
</ul>
<p>자바스크립트는 싱글 스레드 방식으로 동작한다. 이때의 자바스크립트는 <strong>자바스크립트 엔진</strong>을 의미한다.</p>
<p>브라우저는 멀티 스레드로 동작한다.</p>
<blockquote>
<p><strong>45장. 프로미스</strong> 까지 완료한 후 <strong>이벤트 루프</strong> 그리고 <strong>마이크로태스크 큐</strong>, <strong>매크로태스크 큐</strong>에 대해 더 알아보자.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[41장. 타이머]]></title>
            <link>https://velog.io/@pmthk__/41%EC%9E%A5.-%ED%83%80%EC%9D%B4%EB%A8%B8</link>
            <guid>https://velog.io/@pmthk__/41%EC%9E%A5.-%ED%83%80%EC%9D%B4%EB%A8%B8</guid>
            <pubDate>Thu, 02 Jan 2025 15:52:14 GMT</pubDate>
            <description><![CDATA[<h1 id="호출-스케줄링">호출 스케줄링</h1>
<p>함수를 일정 시간이 경과된 이후 호출되도록 함수 호출을 예약하려면 타이머 함수를 사용한다. 이를 호출 스케줄링이라 한다.</p>
<ul>
<li>타이머 생성 함수: setTimeout, setInterval</li>
<li>타이머 제거 함수: clearTimeout, clearInterval</li>
</ul>
<p>타이머 함수는 호스트 객체이다.</p>
<blockquote>
<p>ECMAScript 사양에는 정의되어 있지 않지만 자바스크립트 실행 환경에서 추가로 제공하는 객체를 말한다.
DOM, Canvas, fetch 등이 있다.</p>
</blockquote>
<p>자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖기 때문에 두 가지 이상의 태스크를 동시에 실행할 수 없다. 따라서, 타이머 함수는 비동기 처리 방식으로 동작한다.</p>
<h1 id="디바운스와-스로틀">디바운스와 스로틀</h1>
<p>디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.</p>
<h2 id="디바운스">디바운스</h2>
<p>디바운스는 짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정 시간이 경과한 이후에 이벤트 핸들러가 한 번만 호출되도록 한다.</p>
<p>즉, 디바운스는 짧은 시간 간격으로 발생하는 이벤트를 그룹화하여 마지막에 한 번만 이벤트 핸들러가 호출되도록 한다.</p>
<ul>
<li>resize 이벤트 처리, input 요소에 입력된 값으로 ajax 요청하는 입력 필드 자동완성 UI 구현, 버튼 중복 클릭 방지 처리 등에 유용하게 사용된다.</li>
<li>실무에서는 Underscore의 debounce 함수나 Lodash의 debounce 함수를 사용하는 것을 권장한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/e5a82d9c-c605-4491-8284-f668ee21ca37/image.png" alt=""></p>
<h2 id="스로틀">스로틀</h2>
<p>스로틀은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 한다.</p>
<p>즉, 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다.</p>
<ul>
<li>scroll 이벤트 처리, 무한 스크롤 UI 구현 등에 유용하게 사용된다.</li>
<li>실무에서는 Underscore의 throttle 함수나 Lodash의 throttle 함수를 사용하는 것을 권장한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/3c98cb96-ecfc-4d39-af29-86ecc3d1eb0c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[40장. 이벤트]]></title>
            <link>https://velog.io/@pmthk__/40%EC%9E%A5.-%EC%9D%B4%EB%B2%A4%ED%8A%B8</link>
            <guid>https://velog.io/@pmthk__/40%EC%9E%A5.-%EC%9D%B4%EB%B2%A4%ED%8A%B8</guid>
            <pubDate>Thu, 02 Jan 2025 15:51:06 GMT</pubDate>
            <description><![CDATA[<h1 id="이벤트-드리븐-프로그래밍">이벤트 드리븐 프로그래밍</h1>
<p>이벤트와 그 이벤트에 대응하는 함수를 통해 사용자와 애플리케이션은 상호작용을 할 수 있다.</p>
<p>이와 같이 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식을 이벤트 드리븐 프로그래밍이라 한다.</p>
<p>click, keydown, focus 등 브라우저는 어떤 동작이 발생하면 이를 감지해 이벤트를  발생시킨다.</p>
<p>애플리케이션이 특정 이벤트에 대해 반응해 어떤 일을 하고 싶다면, 호출될 함수를 브라우저에게 알려 호출을 위임해야 한다.</p>
<p>이때, 함수를 <strong>이벤트 핸들러</strong>라 하고 호출을 위임하는 것을 <strong>이벤트 핸들러 등록</strong>이라 한다.</p>
<h1 id="이벤트-핸들러-등록">이벤트 핸들러 등록</h1>
<h2 id="1-이벤트-핸들러-어트리뷰트-방식">1. 이벤트 핸들러 어트리뷰트 방식</h2>
<p>HTML 요소의 어트리뷰트 중 이벤트에 대응하는 이벤트 핸들러 어트리뷰트가 있다.</p>
<p>onclick과 같이 on 접두사와 이벤트 타입으로 이루어져 있다.</p>
<p>이 어트리뷰트의 값으로 함수 호출문 등의 문(statement)을 할당하면 이벤트 핸들러가 등록된다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;button onclick=&quot;sayHi(&#39;Lee&#39;)&quot;&gt;Click me!&lt;/button&gt;
  &lt;script&gt;
    function sayHi(name) {
      console.log(`Hi! ${name}.`);
    }
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이때 주의할점은 이벤트 핸들러 어트리뷰트 값으로 함수 참조가 아닌 함수 호출문 등 문을 할당해야 한다는 것이다.</p>
<p>이벤트 핸들러 등록할 때는 콜백 함수와 마찬가지로 함수 참조를 등록해야 브라우저가 이벤트 핸들러를 호출할 수 있다.</p>
<p>함수 호출문을 등록한다면 함수 호출문의 평가 결과가 이벤트 핸들러로 등록될 것이다.</p>
<p>하지만, 위 예제에서는 이벤트 핸들러 어트리뷰트 값으로 함수 호출문을 할당했다.</p>
<p>이는 이<strong>벤트 핸들러 어트리뷰트 값은 사실 암묵적으로 생성될 이벤트 핸들러의 함수 몸체를 의미</strong>하기 때문이다.</p>
<p>즉, 아래와 같은 함수가 생성되고, 이를 onclick 이벤트 핸들러 프로퍼티에 할당하는 것이다.</p>
<pre><code class="language-jsx">function onclick(event) {
  sayHi(&#39;Lee&#39;) // 함수 몸체
}</code></pre>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/ba6ab8ba-c82b-4591-85ea-19d4736a6ba1/image.png" alt=""></p>
<blockquote>
<p>이처럼 동작하는 이유는 이벤트 핸들러에 인수를 전달하기 위함이다.</p>
</blockquote>
<p>이 방식은 더는 사용하지 않는 것이 좋다고 한다. 
(HTML과 자바스크립트는 관심사가 다르기 때문에)</p>
<p>하지만 모던 자바스크립트에서는 이벤트 핸들러 방식을 사용하는 경우가 있다.</p>
<p>Component Based Development 방식의 Angular, React, Vue 같은 프레임워크나 라이브러리에서는 이 방식으로 이벤트를 처리한다. 
(HTML과 자바스크립트를 뷰를 구성하기 위한 구성 요소로 보기 때문에 관심사가 다르다고 판단하지 않기 때문에)</p>
<h2 id="2-이벤트-핸들러-프로퍼티-방식">2. 이벤트 핸들러 프로퍼티 방식</h2>
<p>이벤트 핸들러 프로퍼티의 키는 이벤트 핸들러 어트리뷰트와 마찬가지로 onclick과 같이 on 접두사와 이벤트 타입으로 이루어져 있다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;button&gt;Click me!&lt;/button&gt;
  &lt;script&gt;
    const $button = document.querySelector(&quot;button&quot;);

    $button.onclick = function () {
      console.log(&quot;button click&quot;);
    };
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이벤트 핸들러를 등록하기 위해서는 이벤트를 발생시킬 객체인 <strong>이벤트 타깃</strong>과 이벤트 종류를 타나내는 문자열인 <strong>이벤트 타입</strong> 그리고 <strong>이벤트 핸들러</strong>를 지정할 필요가 있다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/a9754216-7de2-4a02-a395-727974d52a7b/image.png" alt=""></p>
<p>이 방식은 하나의 이벤트 핸들러만 바인딩할 수 있다는 단점이 있다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
      &lt;button&gt;Click me!&lt;/button&gt;
    &lt;script&gt;
      const $button = document.querySelector(&#39;button&#39;);

      $button.onclick = function () 
          console.log(&#39;button click 1&#39;);
      }

      // 이 이벤트 핸들러만 실행된다.
      $button.onclick = function () {
          console.log(&#39;button click 2&#39;);
      }
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2 id="3-addeventlistener-메서드-방식">3. addEventListener 메서드 방식</h2>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/7bd128b1-a19b-468a-af64-dd1b8f8c6578/image.png" alt=""></p>
<p>addEventListener 메서드는 매개변수로 이벤트 종류를 나타내는 <strong>이벤트 타입</strong>, <strong>이벤트 핸들러</strong>를 전달한다</p>
<p>마지막에 이벤트를 캐치할 이벤트 전파 단계를 지정하거나 생략할 수 있다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;button&gt;Click me!&lt;/button&gt;
  &lt;script&gt;
    const $button = document.querySelector(&quot;button&quot;);

    // $button.onclick = function () {
    //  console.log(&quot;button click&quot;);
    // };

    $button.addEventListener(&quot;click&quot;, function () {
      console.log(&#39;button click&#39;);
    });
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이 addEventListener 메서드 방식은 이벤트 핸들러 프로퍼티에 바인딩된 이벤트 핸들러에 아무런 영향을 주지 않는다.</p>
<p>그리고 동일한 HTML 요소에 대해 하나 이상의 이벤트를 등록할 수 있고, 이벤트 핸들러는 등록된 순서대로 호출된다.</p>
<p>단, 참조가 동일한 이벤트가 중복 등록되면 하나만 등록된다.</p>
<h1 id="이벤트-핸들러-제거">이벤트 핸들러 제거</h1>
<p>addEventListener 메서드로 등록한 이벤트 핸들러를 제거하려면 removeEventListener 메서드를 사용한다.</p>
<p>이때, addEventListener 메서드에 전달한 인수와 일치하지 않으면 제거되지 않는다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
      &lt;button&gt;Click me!&lt;/button&gt;
    &lt;script&gt;
      const $button = document.querySelector(&#39;button&#39;);

      const handleClick = () =&gt; console.log(&#39;button click&#39;);    

      // 이벤트 핸들러 등록
      $button.addEventListener(&#39;click&#39;, handleClick);

      // 이벤트 핸들러 제거 
      $button.removeEventListener(&#39;click&#39;, handleClick, true); // 실패
      $button.removeEventListener(&#39;click&#39;, handleClick); // 성공
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이벤트 핸들러를 제거하려면 이벤트 핸들러의 참조를 변수나 자료구조에 저장하고 있어야 한다.</p>
<p>단, 기명 이벤트 핸들러 내부에서 removeEventListener 메서드를 호출해 이벤트를 제거할 수 있다.</p>
<p>이때 이벤트 핸들러는 단 한 번 호출된다.</p>
<pre><code class="language-jsx">$button.addEventListener(&#39;click&#39;, function foo() {
  console.log(&#39;button click&#39;);
  $button.removeEventListener(&#39;click&#39;, foo);
});</code></pre>
<p>기명 함수를 이벤트 핸들러로 등록할 수 없다면, 자신을 가리키는 arguments.callee 를 사용할 수 있다.</p>
<pre><code class="language-jsx">$button.addEventListener(&#39;click&#39;, function () {
  console.log(&#39;button click&#39;);
  $button.removeEventListener(&#39;click&#39;, arguments.callee);
});</code></pre>
<p>하지만, arguments.callee 는 코드 최적화를 방해하므로 strict mode 에서는 사용이 금지된다.</p>
<p>이벤트 핸들러 프로퍼티 방식은 이벤트 핸들러 프로퍼티에 null 을 할당하면 된다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
      &lt;button&gt;Click me!&lt;/button&gt;
    &lt;script&gt;
      const $button = document.querySelector(&#39;button&#39;);

      const handleClick = () =&gt; console.log(&#39;button click&#39;);    

      // 이벤트 핸들러 프로퍼티 방식으로 이벤트 핸들러 등록
      $button.onclick = handleClick;

      // 이벤트 핸들러 제거 
      $button.onclick = null;
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h1 id="이벤트-객체">이벤트 객체</h1>
<p>이벤트가 발생하면 이벤트에 관련한 다양한 정보를 담고 있는 이벤트 객체가 동적으로 생성되고</p>
<p>생성된 이벤트 객체는 이벤트 핸들러의 첫 인수로 전달된다.</p>
<p>이벤트 핸들러 어트리뷰트 방식의 경우 이벤트 객체를 전달받으려면 이벤트 핸들러의 첫 번째 매개변수 이름이 반드시 event 이어야 한다.</p>
<pre><code class="language-jsx">function onclick(event) [
  showCoords(event)
}</code></pre>
<blockquote>
<p>React는 이벤트 핸들러 어트리뷰트 방식으로 이벤트를 처리한다.
그렇다면 React에서는 왜 e, evt 등 매개변수 이름을 자유롭게 설정할 수 있을까…</p>
</blockquote>
<p>찾아보니 이는 React 는 SyntheticEvent 로 네이티브 이벤트를 감싸서 사용한다.
내부적으로 첫 인자가 이벤트 객체라는 규칙을 가지고 있고, 첫 인자의 이름은 지정하고 있지 않다고 한다.</p>
<p>SyntheticEvent를 알아보자!</p>
<blockquote>
</blockquote>
<h1 id="이벤트-전파">이벤트 전파</h1>
<p>DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파된다.</p>
<p>이를 이벤트 전파 Event Propagation 라고 한다.</p>
<p>이벤트 전파는 이벤트 객체가 전파되는 방향에 따라 3단계로 구분된다.</p>
<ol>
<li>캡처링 단계: 이벤트가 상위에서 하위 요소 방향으로 전파</li>
<li>타긴 단계: 이벤트가 이벤트 타깃에 도달</li>
<li>버블링 단계: 이벤트가 하위에서 상위 요소 방향으로 전파</li>
</ol>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/145b6144-20e0-4a8f-b2fe-263197fe985f/image.png" alt=""></p>
<p>이벤트 핸들러 어트리뷰트나 프로퍼티 방식으로 등록한 이벤트 핸들러는 타깃 단계와 버블링 단계의 이벤트를 캐치할 수 있다.</p>
<p>하지만 addEventListener 메서드 방식으로 등록한 핸들러는 캡처링 단계의 이벤트도 선별적으로 캐치할 수 있다.</p>
<p>대부분의 이벤트는 캡처링과 버블링으로 전파되지만 다음 이벤트들은 버블링을 통해 전파되지 않는다.</p>
<ul>
<li>포커스 이벤트: focus/blur</li>
<li>리소스 이벤트: load/unload/abort/error</li>
<li>마우스 이벤트: mouseenter/mouseleave</li>
</ul>
<blockquote>
<p>캡처링 단계에서 이벤트를 캐치해야 할 경우는 거의 없다.</p>
</blockquote>
<h1 id="이벤트-위임">이벤트 위임</h1>
<p>하위 요소에 개별 이벤트 핸들러를 등록하지 않고 <strong>상위 요소에서 하위 요소의 이벤트를 일괄적으로 제어하는 방식</strong>이다.</p>
<p>버블링 단계가 있기 때문에, 하위 요소에서 발생한 이벤트를 상위 요소에서도 감지할 수 있기 때문이다.</p>
<blockquote>
<p>새로운 요소가 추가되어도 추가적인 이벤트 핸들러 없이 이벤트 감지가 가능해진다.</p>
</blockquote>
<ul>
<li>event.target.closest</li>
<li>Element.prototype.matches</li>
</ul>
<p>위 두 메서드 등을 잘 활용해서 개발자가 의도한 DOM 요소에 접근하도록 주의할 필요가 있다.</p>
<h1 id="커스텀-이벤트">커스텀 이벤트</h1>
<p>개발자의 의도로 생성된 이벤트를 <strong>커스텀 이벤트</strong>라 한다.</p>
<p>기본적으로 CustomEvent 로 생성된 이벤트는 버블링되지 않고 취소할 수 없다.</p>
<h2 id="커스텀-이벤트-디스패치">커스텀 이벤트 디스패치</h2>
<p>커스텀 이벤트는 dispatchEvent 메서드로 이벤트를 발생시킬 수 있다.</p>
<p>일반적인 이벤트 핸들러는 비동기 처리 방식으로 동작하지만 dispatchEvent 메서드는 이벤트를 동기 처리 방식으로 호출한다.</p>
<p>즉, 커스텀 이벤트에 바인딩된 이벤트 핸들러를 직접 호출하는 것과 같다.</p>
<p>또, 이벤트 핸들러 어트리뷰트 혹은 프로퍼티 방식을 사용할 수 없다.</p>
<p>반드시 addEventListener 메서드 방식으로 이벤트 핸들러를 등록해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프를 수료하며 ]]></title>
            <link>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84%EB%A5%BC-%EC%88%98%EB%A3%8C%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84%EB%A5%BC-%EC%88%98%EB%A3%8C%ED%95%98%EB%A9%B0</guid>
            <pubDate>Thu, 19 Dec 2024 14:34:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>끝나지 않을 것 같던 부스트캠프가 끝이 났다.</p>
</blockquote>
<h2 id="들어가기-전에">들어가기 전에</h2>
<p>2월에 대학교를 졸업하고 취업을 준비하며 가장 많이 한 생각은
<em><strong>나는 잘 나아가고 있는 걸까?</strong></em> 였다.</p>
<p>프론트엔드 개발자가 되고자 프로젝트를 해보기도 하고 관련된 기술이나 용어를 공부하는 데에 시간을 쏟았지만, 반복되는 서류 탈락으로 의문은 점점 커졌다.</p>
<p>그렇게 조금이라도 이 의문을 답을 얻고자 부스트캠프에 지원했다.
미리 말하자면, 결국 나는 베이직 · 챌린지 · 멤버십 전 과정을 잘 수료할 수 있었고,
앞으로 내가 어떤 방향으로 나아가야 하는지에 대한 답을 찾은 것 같다.</p>
<p>올해의 반을 함께한 부스트캠프 전체 과정에 대한 후기를 남겨보려 한다.</p>
<hr>
<h2 id="베이직">베이직</h2>
<p>베이직은 부스트캠프의 시작이었다.
지금 돌아보면 베이직은 챌린지 그리고 멤버십의 체험판 같았다.</p>
<p>미션이 주어지면 이를 해결하기 위해 나만의 접근 방법을 설계하고 구현한 뒤,
동료와 공유해 다양한 관점에서 문제 상황과 접근 방법을 살펴보는 경험이었다.</p>
<p>나는 베이직 입과 대상자는 아니었지만, 참여한 것이 잘한 선택이었다고 생각한다.
베이직은 부스트캠프의 문제 해결 방식에 익숙해질 좋은 기회였다.</p>
<p>이전에 작성해둔 후기를 보면, 베이직을 수료하는 시점의 나는 내 생각을 글로 잘 표현하는 것에 관심이 많았던 것 같다. (여전히 그렇다.)
그래서, 캠퍼분들의 글을 보며 어떤 글이 잘 읽히는 글이고, 왜 잘 읽히는지 파악해보았던 것 같다.</p>
<p>그렇게 베이직을 잘 수료했다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/133f887a-a2b2-447c-beb7-2bb32e58c236/image.png" width="800px" alt="베이직 수료 메일 캡처"/>
  <br/>
</div>

<hr>
<h2 id="챌린지">챌린지</h2>
<p>챌린지는 짧은 주기(하루 혹은 이틀)로 미션을 진행하며,
구현을 통해 CS 지식을 배우는 과정이다.</p>
<p>당연히 동료 캠퍼와 이를 공유하고 서로에게 질문을 던지며 함께 성장하는 방법을 익히게 된다.</p>
<p>챌린지에서는 매일같이 수많은 학습 키워드들에 대해 알게 되고, 공부하게 된다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/5416c20e-50a9-446f-8e97-86a02e3f5de2/image.png" width="600px" alt="학습 정리"/>
  <br/>
</div>

<p>코어타임은 10시부터 19시까지 약 9시간이다. 하지만 나는 그 시간으로는 부족했다.
매일 10시부터 시작해 새로 만나게 되는 키워드를 공부하고 미션을 해결하고 나면 자정이 넘는 것이 일상이었다.</p>
<p>(솔직히 부스트캠프를 시작하며 캠퍼들이 평균 13시간 이상을 투자했다는 설명에 조금 의아했다.
하지만 나도 그랬고, 다른 캠퍼분들도 그랬다. 성장하고자 하는 의지가 아니었을까?)</p>
<p>4주는 정말 빠르게 지나갔다.
모든 키워드에 대해 학습할 수 없었다. 또, 몇몇 미션은 해결하지 못하기도 했다.</p>
<p>미션을 해결하지 못하거나 처음 듣는 키워드가 생기면 조급해지기도 했다.
그래서 부스트캠프에는 스터디 그룹이 있다.
혼자서는 막연하고 답답할 때가 많지만, 매일 같이 학습 내용을 공유하고 서로 피드백하며 조급한 마음도 누그러뜨릴 수 있었다.</p>
<p>챌린지를 통해 내가 더 잘해야 해, 나 혼자 잘하면 돼 라는 생각은 사라졌다.
오히려 함께해야 더 좋은 방향으로 더 멀리 나아갈 수 있다는 것을 깨닫게 됐다.</p>
<p>정말 힘든 4주였지만, 함께이기에 잘 마무리할 수 있었다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/35da011c-1122-4df8-be99-05c90fd3488c/image.png" width="400px" alt="챌린지 수료증"/>
  <br/>
</div>

<hr>
<h2 id="멤버십">멤버십</h2>
<p>멤버십은 8주간의 학습 스프린트와 6주간의 그룹 프로젝트로 구성되어 있다.
학습 스프린트는 또 4주간의 풀 스택 과정과 FE · BE 중 선택한 분야에 대한 4주간의 심화 학습 과정으로 이루어져 있다.</p>
<p>멤버십에 들어오며 내 목표는 명확했다.
더 많은 것들을 캠퍼분들과 공유하고, 더 많이 공유 받으며 배우는 것이었다.
이를 위해 최대한 많은 코드를 살펴보고 질문을 던졌고, 내가 아는 것들을 공유했다.
그러기 위해 근거를 제시하고, 내 생각을 더 쉽고 명료하게 전달하고자 했다.</p>
<p>그렇게 열심히 학습 스프린트 과정에 참여했다.
많이 부족한 나였지만, 캠퍼분들께 내 노력이 조금은 닿았을까?
많은 캠퍼분들께서 긍정적인 피드백을 주셨다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/ba5ebf1a-2089-4e3d-9ec9-2db858255a5f/image.png" width="500px" alt="캠퍼분들의 피드백"/>
  <br/>
</div>

<p>추가로, 멤버십에는 현업 개발자분들로 구성된 멘토님들로부터 리뷰를 받을 기회가 있었다.
나는 이를 놓치고 싶지 않아, 멘토님들의 답변과 질문을 토대로 추가 질문을 던지고, 다시 답변을 받는 과정을 반복하며 가능한 다양한 지식을, 인사이트를 얻어내고자 했다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/3328ed5e-e7bc-4ab8-a0a9-65271b730be7/image.png" width="800px" alt="멘토님과의 티키타카"/>
  <br/>
</div>

<p>그렇게 많은 캠퍼분들, 멘토님들의 도움과 함께 성공적으로 학습 스프린트를 마무리할 수 있었다.</p>
<br/>

<p>사담이지만 나는 그동안 내가 만든 화면을 바로 확인할 수 있다는 점에서 매력을 느껴 프론트엔드 개발을 공부했다.
하지만, 이번 기회에는 더 다양하게 배우고 학습하고자 백엔드 분야를 선택하게 되었다.
고민하던 나에게 한 줄기 빛 같은 운영진의 글이 있었다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/e69b9250-e4b4-4386-a73e-fbe58b6bcaec/image.png" width="800px" alt="운영진분의 글"/>
  <br/>
</div>

<p>백엔드 과정에서는 다른 캠퍼분들에 비해 부족한 점이 많았다.
새로운 내용이 너무 많았지만, 데이터베이스, 인프라 등 웹 서비스를 전반적으로 파악할 수 있었다.
그리고, 풀스택 개발자라는 꿈을 꾸게 되었다. 둘 다 너무 재밌다.
(하지만 우선 하나에 집중해볼 생각이다.)</p>
<br/>

<p>그리고 그룹 프로젝트가 시작됐다.
그룹 프로젝트는 임의로 구성된 4명 혹은 5명의 캠퍼가 한 팀이 되어 진행된다.</p>
<p>팀과 함께 그동안 배운 내용을 바탕으로 주제 선정, 기획, 개발까지, 하나의 결과물을 만들어내는 과정이다.</p>
<p>생각보다 6주라는 시간을 짧았다.
매일 진행되는 스크럼과 회의, 그리고 개발과 그 과정에서의 문서화를 반복하다보니 순식간에 끝났다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/7f85bbe9-a264-4629-8641-1788ba02d882/image.png" width="800px" alt="문서들"/>
  <br/>
</div>

<p>나에게 그룹 프로젝트는 단순히 함께 결과물을 만들어내는 것 이상이었다.
협업의 중요성을 깨닫고, 효과적인 협업 방식을 고민하고 직접 적용해볼 기회였다.</p>
<p>협업 관점에서 팀의 문제점을 찾고 이를 개선하기 위해 다른 팀들의 협업 방식을 관찰하며,
우리 팀에 적용할 수 있는 요소를 찾아보기도 했다.</p>
<p>예시로, 어떤 팀은 문서화를 통해 효율을 높였고, 다른 팀은 프로토타이핑을 통해 기술 선택 과정에서의 불필요한 자원 소모를 줄이기도 했다. 또 다른 팀은 적극적으로 짝 프로그래밍을 진행해 팀원 간의 이해도를 맞추기도 했다.
이렇게 다양한 접근 방식을 경험하며, 협업에 대한 시야를 넓히게 되었다.</p>
<p>물론, 모든 것을 다 해보지는 못했다.
하고 싶은 것은 많았지만, 시간은 빠르게 흘러갔다.</p>
<p>하고 싶은 건 많았지만, 뚝딱거리고 삐걱거리던 나와 함께 프로젝트를 잘 마무리 해준 팀에게 다시 한번 감사를 전하고 싶다.</p>
<p>그렇게 멤버십도 마무리됐다.
<img src="https://velog.velcdn.com/images/pmthk__/post/c8ac459f-abcc-4471-a190-985791fe0398/image.png" alt=""></p>
<hr>
<h2 id="마무리하며">마무리하며</h2>
<p>받아놓은 날짜는 오기 마련이다.
여름에 시작한 부스트캠프는 겨울이 오며 끝이 났다.</p>
<p>처음에는 단지 방황하지 않으려는 마음으로 참여했던 부스트캠프에서,
나는 공부하는 방법과 지식을 나누며 함께 성장하는 법을 깨닫게 되었다.</p>
<p>정말 다양한 감정을 느꼈고, 많은 것을 얻었다.
때론 너무 힘들어 좌절하기도 했고, 때론 마냥 즐거웠다.
그래도 여기까지 올 수 있었던 건 이 경험을 함께한 캠퍼분들 덕분이지 않을까?</p>
<p>베이직, 챌린지, 멤버십 과정 전반에 걸쳐 학습과 구현에 아낌없이 도움을 주시고,
힘든 순간에도 “잘하고 있다”는 말로 용기를 북돋아 주신 분들 덕에 마무리할 수 있었다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/pmthk__/post/7048f108-b645-436a-9ead-9d96eba9ee9f/image.png" width="600px" alt="마무리"/>
  <br/>
</div>

<p>부디 몇몇 분들에게라도 내가 작은 도움이 되었길 바란다.
직접적이든 간접적이든 함께하며 도움을 주신 모든 캠퍼분들께 감사의 마음을 전하고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[29, 30장. Math / Date]]></title>
            <link>https://velog.io/@pmthk__/29-30%EC%9E%A5.-Math-Date</link>
            <guid>https://velog.io/@pmthk__/29-30%EC%9E%A5.-Math-Date</guid>
            <pubDate>Tue, 13 Aug 2024 12:52:20 GMT</pubDate>
            <description><![CDATA[<h1 id="math">Math</h1>
<p><code>Math</code> 는 생성자 함수가 아니다. </p>
<p>표준 빌트인 객체인 <code>Math</code> 정적 프로퍼티와 정적 메소드만 제공한다. </p>
<h1 id="date">Date</h1>
<p><code>Date</code> 는 빌트인 객체이면서 동시에 생성자 함수다.</p>
<p><strong>UTC(협정 세계시)</strong>를 기점으로 시간을 나타낸다. 단위는 <code>ms</code> 밀리초이다.</p>
<h2 id="date-생성자-함수">Date 생성자 함수</h2>
<p><code>Date</code> 생성자 함수로 생성한 <code>Date</code> 객체는 내부적으로 날짜와 시간을 나타내는 정수값을 가지게 된다.</p>
<p>이 값은 1970년 1월 1일 00:00:00(UTC)을 기점으로 밀리초를 나타낸다.</p>
<h3 id="new-date"><code>new Date()</code></h3>
<p>인자 없이 호출하면 현재 날짜와 현재 시간을 가지는 <code>Date</code> 객체를 반환한다.</p>
<p>만약 <code>new</code> 연산자 없이 호출하면 객체가 아닌 <strong>날짜와 시간 정보를 나타내는 문자열을 반환</strong>한다.</p>
<h3 id="new-datemilliseconds"><code>new Date(milliseconds)</code></h3>
<p>밀리초를 인자로 전달하면  1970년 1월 1일 00:00:00(UTC)를 기점으로 해당 밀리초가 경과한 날짜와 시간을 나타내는 <code>Date</code> 객체를 반환한다.</p>
<h3 id="new-datedatestring"><code>new Date(dateString)</code></h3>
<p>날짜와 시간을 나타내는 문자열을 전달해 <code>Date</code> 객체를 생성할 수도 있다.</p>
<p>이때 전달한 문자열은 <code>Date.parse()</code> 메서드에 의해 해석 가능한 형식이어야 한다.</p>
<h3 id="new-dateyear-month-day-hour-minute-second-millisecond"><code>new Date(year, month[, day, hour, minute, second, millisecond])</code></h3>
<p><code>Date</code> 생성자 함수에 연, 월, 시, … 를 인수로 전달해 지정된 날짜와 시간을 나타내는 <code>Date</code> 객체를 반환할 수 있다.</p>
<p>이때 연, 월은 반드시 지정해야 하며 <code>month</code> 는 0 부터 11까지임에 주의하자. (0 = 1월, 1 = 2월, …)</p>
<pre><code class="language-jsx">new Date(2024, 7, 13, 10, 00, 00, 0);
new Date(&#39;2024/8/13/10:00:00:00&#39;);</code></pre>
<h2 id="date-정적-메서드">Date 정적 메서드</h2>
<h3 id="datenow"><code>Date.now()</code></h3>
<p> 1970년 1월 1일 00:00:00(UTC)을 기점으로 현재 시간까지 경과한 밀리초를 반환한다.</p>
<h3 id="dateparsedatestring"><code>Date.parse(dateString)</code></h3>
<p> 1970년 1월 1일 00:00:00(UTC)을 기점으로 인수로 전달된 지정 시간까지의 밀리초를 숫자로 반환한다.</p>
<h3 id="dateutcyear-month-day-hour-minute-second-millisecond"><code>Date.UTC(year, month[, day, hour, minute, second, millisecond])</code></h3>
<p> 1970년 1월 1일 00:00:00(UTC)을 기점으로 인수로 전달된 지정 시간까지의 밀리초를 숫자로 반환한다.</p>
<h3 id="dateprototypegettimezoneoffset"><code>Date.prototype.getTimezoneOffset()</code></h3>
<p>UTC와 Date 객체에 지정된 locale 시간과의 차이를 분 단위로 반환한다.</p>
<pre><code class="language-jsx">const now = new Date();  // now의 지정 로케일은 KST이다.

now.getTimezoneOffset() // -540</code></pre>
<h3 id="dateprototypetodatestring"><code>Date.prototype.toDateString()</code></h3>
<p>사람이 읽을 수 있는 형식의 문자열로 Date 객체의 날짜를 반환한다.</p>
<pre><code class="language-jsx">const now = new Date(&#39;2024/8/13/10:00&#39;);

now.toString() // → Tue Aug 13 2024 10:00:00 GMT+0900 (대한민국 표준시)
now.toDateString()// → Tue Aug 13 2024</code></pre>
<h3 id="dateprototypetotimestring"><code>Date.prototype.toTimeString()</code></h3>
<p>사람이 읽을 수 있는 형식의 문자열로 Date 객체의 시간을 표현한 문자열을 반환한다.</p>
<pre><code class="language-jsx">const now = new Date(&#39;2024/8/13/10:00&#39;);

now.toString() // → Tue Aug 13 2024 10:00:00 GMT+0900 (대한민국 표준시)
now.toTimeString()// → 10:00:00 GMT+0900 (대한민국 표준시)</code></pre>
<h3 id="dateprototypetoisostring"><code>Date.prototype.toISOString()</code></h3>
<p>ISO 8601 형식으로 날짜와 시간을 표현한 문자열을 반환한다.</p>
<pre><code class="language-jsx">const now = new Date(&#39;2024/8/13/10:00&#39;);

now.toString() // → Tue Aug 13 2024 10:00:00 GMT+0900 (대한민국 표준시)
now.toISOString()// → 2024-08-13T01:00:00.000Z</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 챌린지 - 4주차 회고]]></title>
            <link>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EB%B0%8F-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%EB%B0%8F-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 10 Aug 2024 16:52:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>4주 동안의 챌린지 과정이 마무리 되었습니다.
4주차의 회고는, 전체 과정을 돌아보며 작성했습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/f990d4fa-52a1-4986-a273-ca6edece90ab/image.png" alt=""></p>
<h2 id="growth--나를-알고-성장하기">GROWTH : 나를 알고 성장하기</h2>
<p>챌린지 과정 이전의 저는 책 혹은 검색을 통해 혼자 학습하고, 혼자 개발하는 것에 익숙했습니다. 그리고, 정답이 존재하는 문제에서 고득점을 받는 것에만 집중해왔습니다. 하지만, 챌린지 과정은 정답이 없어서 혼란스러웠습니다.</p>
<p>따라서, 1주차에는 이 챌린지 과정이 단순히 멤버십을 가기 위한 하나의 평가라고 생각했고, 주어지는 미션을 모두 마무리해야 한다는 강박이 있었습니다. 피어 세션에서 동료 캠퍼의 결과물을 보며, 적지 않게 질투심도 느끼고, 경계하기도 했던 것 같습니다.</p>
<p>하지만, 4주가 지난 지금, 챌린지 과정의 의미를 조금은 깨닫게 되었습니다. “나를 알고 성장하기”라는 말의 핵심은 “나의 속도를 파악해라”였던 것 같습니다.</p>
<p>단순히 해결을 위해 기계적으로 코드를 작성하는 것이 아닌, 나에게 알맞은 속도로 필요한 부분은 학습하고, 실현 가능한 목표를 설정하고 달성하는 과정에서 더 근거 있는 설득력 있는 개발자로 성장하는 방법을 배운 것 같습니다.</p>
<p>또, 피어 세션을 통해 남을 부러워하고 질투하는 것이 아니라 내가 어떤 부분이 남들에 비해 부족한지를 알 수 있었고, 그 점을 개선해 나갈 수 있는 기회를 얻은 것 같습니다.</p>
<p>→ 앞으로도 제 속도에 맞는 학습 - 분석 - 설계 - 구현의 과정을 유지할 것 같습니다. 무리한 목표를 세우고 기계적인 코드 작성을 하는 개발자가 아닌 챌린지 과정에서 발전시켜 온 실현 가능한 목표를 설정하고 그 목표를 달성하는 개발자가 되보려 합니다.</p>
<hr>
<h2 id="initiative--자기-주도적으로-행동하기">INITIATIVE : 자기 주도적으로 행동하기</h2>
<p>챌린지의 미션에는 정답이 없습니다. 그 미션을 해결하는 정해진 방법도 없습니다. 무엇을 할지, 어떻게 할지는 모두 제가 스스로 결정하고 행동해야 했습니다. 마치 제 속도를 제가 찾아냈듯이, 얼마나 학습하고 어떤 목표를 세워 해결해 나갈지는 오롯이 제 몫이었습니다.</p>
<p>따라서, 저는 주차가 진행될수록 미션을 해결하는 것과 함께 새로운 목표를 세우는 것에도 집중해 보았습니다. 두 번째 주차에는 전체 설계에 대한 그림을 그리고, 피어 세션을 위한 발표를 준비했습니다. 세 번째 주차에는 “소프트웨어 장인”이라는 책에서 인상 깊었던 “낮은 사기는 개발자들의 주된 실패 요인”의 “낮은 사기”를 해결하기 위해 가벼운 질문을 통해 분위기를 끌어 올리고, 더 많은 피드백 질문을 하려고 노력했습니다. 마지막 주차에는 3주차 동료분에게 영감을 얻어 피어 세션에서 더 적극적으로 질문하고 답변하기 위해 모든 그룹 캠퍼들의 코드를 여러 테스트 케이스로 실행해 보는 도전을 했습니다.</p>
<p>물론, 이러한 도전이 매일 성공적이지는 않았습니다. 하지만 새로운 도전에서의 작은 성공이 도전 자체를 즐거움으로 바꾸어주었고, 매주 새로운 도전을 할 수 있는 계기가 되었던 것 같습니다.</p>
<p>사실 저는 새로운 것에 잘 도전하는 개발자는 아니었던 것 같습니다. 하지만, 챌린지에서의 경험을 통해 제 한계를 조금씩 갱신해 나갈 수 있었던 것 같습니다. </p>
<p>→ 사실 저는 새로운 것에 잘 도전하는 개발자는 아니었던 것 같습니다. 하지만 이제는 새로운 도전을 통해 제 한계를 갱신할 수 있는, 그리고 여러 방법을 시도해보며 저의 성장을 위해 최선의 선택을 할 수 있는 개발자가 되보려 합니다.</p>
<hr>
<h2 id="professional--기술적-전문성-기르기">PROFESSIONAL : 기술적 전문성 기르기</h2>
<p>네이버 부스트캠프를 시작하기 전에는, 개발하면서 생긴 궁금한 점이나 공부한 내용을 노션이나 블로그에 간단히 정리하는 정도에 그쳤습니다. 필요한 정보를 그때그때 찾다 보니, 지식들을 체계적으로 정리하지는 못했던 것 같습니다. 하지만 챌린지 과정을 진행하면서, 단순히 지식을 학습하고 정리하는 데 그치지 않고, 이를 도식화하는 시도를 해보았습니다. 도식화를 통해 각 용어와 개념들 사이의 연결고리를 시각적으로 확인할 수 있었고, 이를 통해 더 체계적으로 지식을 정리하고 활용할 수 있게 되었습니다.</p>
<p>또한, 피어세션을 준비하면서 제가 설계하고 구현한 내용을 명확하게 표현할 필요성을 느꼈습니다. 잘 동작하는 코드가 항상 다른 사람들이 이해하기 쉬운 것은 아니었기 때문에, 설계 과정이나 동작 흐름을 더 잘 설명하기 위해 다이어그램을 그려 첨부했습니다. 이 부분은 2주차에 동료 캠퍼분의 <code>README</code> 를 보고 배워야 겠다고 생각한 부분이었습니다. 저도 동료분의 이해가 쉬웠듯, 제가 그림을 통해 동료분들이 더 쉽게 이해할 수 있을 것이라 생각했습니다. 그 결과, 캠퍼분들이 이를 통해 내용을 더 쉽게 이해할 수 있었다고 말해주셨던 기억이 납니다. </p>
<p>→ 그럼에도 불구하고, 저는 여전히 “왜”라는 질문을 충분히 던지지 못한 것 같습니다. 배경 지식에 대한 기존의 이해도를 파악하고, 어떤 부분을 더 공부해야겠다는 생각은 많이 했지만, 그 지식이 왜 필요한지에 대해서는 깊이 고민하지 않았던 것 같습니다. 앞으로는 단순히 지식을 학습하는 것에 그치지 않고, 그 지식이 왜 필요한지, 그리고 그것이 어떤 맥락에서 활용될 수 있는지를 더 깊이 생각해보고 정리하는 습관을 들이고 싶습니다.</p>
<hr>
<h2 id="together--함께-자라기">TOGETHER : 함께 자라기</h2>
<blockquote>
<p>“나의 성장이 온전히 내 힘만으로 이뤄진 것이 아니다”</p>
</blockquote>
<p>챌린지 과정 동안, 저는 동료들과 함께 성장하는 것이 얼마나 즐거운 일인지, 그리고 얼마나 중요한지 깊이 깨닫게 되었습니다. 처음에는 혼자 문제를 해결하는 데만 집중했지만, 시간이 지날수록 동료 캠퍼분들과의 함께하는 것이 문제 해결과 학습에 얼마나 큰 도움이 되는지 알게 되었습니다.</p>
<p>처음에는 동료 캠퍼분들을 경쟁 상대로 보았던 것 같습니다. 그래서 좋은 설계나 구현 내용을 보면 궁금한 점을 질문하기보다는 속으로 질투를 느꼈던 것 같습니다. 하지만 이런 태도는 저에게도 동료 캠퍼분들에게도 도움이 되지 않았다는 것을 깨달았습니다. </p>
<p>그래서 2주차부터는 스터디 그룹의 캠퍼분들의 장점을 나도 따라 해보는 시도를 했습니다. 2주차에는 설계를 그림으로 나타내기, 3주차에는 피어 세션의 분위기를 밝게 유지하기, 4주차에는 다양한 방법으로 코드를 테스트해보고 질문하기 등을 시도했습니다.이렇게 다른 분들의 장점을 흡수하려고 하다 보니, 그것들을 제 강점 중 하나로 만들어 낼 수 있었고, 이 강점으로 다시 다른 캠퍼분들에게 영향을 줄 수 있었다고 생각합니다. </p>
<p>→ 개발자로서 동료와의 협력, 특히 동료와의 소통은 매우 중요한 요소라고 생각합니다. 동료들과의 소통을 통해 제가 몰랐던 것을 배우고, 또 제가 아는 것을 공유하며 정리하는 과정에서 더 큰 성장의 기회를 얻을 수 있다는 확신이 생겼습니다. 동료와 함께 성장할 수 있는, 장점을 흡수하고 제 강점을 전달할 수 있는 개발자가 되고 싶습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 챌린지 - 3주차 회고]]></title>
            <link>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 03 Aug 2024 05:24:30 GMT</pubDate>
            <description><![CDATA[<h1 id="keep-🟢">Keep 🟢</h1>
<blockquote>
<p><strong>“개발자들의 낮은 사기는 프로젝트 실패의 주된 이유 중 하나다.”</strong>
산드로 만쿠소, 『소프트웨어 장인』 (길벗, 2015), 229.</p>
</blockquote>
<p><strong>저번 주에도 느꼈지만, 좋은 분위기에서의 피어 세션은 피어 세션의 한계를 없애는 것 같다.</strong></p>
<p>더 많은, 더 예리한 질문들을 받고 내 생각을 논리적으로 설명할 수 있게 되는 것 같다. 사기를 올려서 다 같이 즐겁게 챌린지의 피어 세션을 하다 보면, 질문받는 게 즐거워지고, 내 의견을 공유하는 것이 더 재미있어지는 것 같다.</p>
<p>→ 2주차에도, 3주차에도 좋은 캠퍼분들과 좋은 분위기를 만들어 왔듯이, 4주차에도 좋은 분위기를 유지할 수 있도록 만들자!</p>
<br/>

<p><strong>이번 주에는 남이 읽기 좋은 글을 써보려고 하면서, 글로 표현하는 것의 한계를 느끼고, 항상 하나 이상의 다이어그램을 그렸다.</strong></p>
<p>이렇게 다이어그램으로 표현하는 것의 장점은 내 설계의 의도를 쉽게 파악할 수 있다는 것이다. 캠퍼분들께서도 더 쉽게 의도를 파악할 수 있다고 말씀해주셨다. 또, 내 머리 속의 설계를 그림으로 표현하면서 내가 놓치는 부분을 확인할 수 있게 되고, 더 견고한 설계를 만들기에도 좋았던 것 같다.</p>
<p>→ 4주차 미션을 진행하면서도 나를 위해, 그리고 내 글을 볼 캠퍼분들을 위해 시간을 투자해서 다이어그램을 그리자!</p>
<hr>
<h1 id="problem-⚠️">Problem ⚠️</h1>
<p><strong>이번 주는 짝과의 활동이 많았다.</strong></p>
<p>저번 주 회고에서 Slack에서 더 많은 의견을 주고받지 못한 점이 아쉽다고 작성했는데, 이번 주는 스터디 그룹 Slack에서 몇몇 의견을 주고받은 것을 제외하면, 몇 개의 답변에 그친 것 같다. 이 챌린지 과정에 400여 명의 캠퍼분들의 의견을 얻을 수 있는 좋은 장소임을 망각한 점이 저번 주에 이어서 아쉬운 점이다.</p>
<br/>

<p>*<em>피어 세션을 통해 질문하는 법과 제대로 답변하는 법을 점점 배워나가는 것 같다. *</em></p>
<p>하지만, “내가 잘 들어주는 캠퍼인가?”라고 생각해보면, 반쯤은 맞고 반쯤은 아닐 수도 있다는 생각이 들었다. 내가 말하는 사람의 의도를 단번에 잡아낼 수 있는 사람은 아닌 것 같다. 조금 더 경청해서, 짝분이나 스터디 그룹 캠퍼분들의 의도를 파악해 답변할 수 있는 사람이 되지 못한 점은 아쉽다.</p>
<hr>
<h1 id="try-🚀">Try 🚀</h1>
<p>미션을 진행하며 생긴 고민을 하나 이상을
충분한 상황 설명과 왜 고민하는 지에 대해 작성해 Slack 에 올려보자!</p>
<br/>

<p>경청하는 동료 캠퍼가 되어보자! 
즉답이 아닌, 의도에 맞는 답변을 해보자!</p>
<br/>

<blockquote>
<p>항상 80 ~ 90점의 소프트웨어를 개발할 수 있는지가 중요하다.</p>
</blockquote>
<p>더 견고한 설계를 고민하며 시간을 많이 투자해 구현의 시간이 부족했던 것 같다.
마지막 주차이니 만큼, 구현을 시작하기 충분한 설계가 나오면, 일단 구현을 시작해보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 챌린지 - 2주차 회고]]></title>
            <link>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 03 Aug 2024 05:22:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 주는 예비군으로 월요일과 목요일 첫 미션을 제외하곤 아직 해보지 못했다. 
이는 금요일 저녁과 주말을 이용해 해볼 예정이다.</p>
</blockquote>
<h1 id="keep-🟢">Keep 🟢</h1>
<p>월요일 미션을 진행하며, 내 기준에 내가 오후 7시까지 진행할 수 있다고 생각하는 정도의 목표를 세우고 진행했다.
물론, 목표를 모두 해결한 것은 아니지만, 이 과정에서 내 속도를 점점 알아가고 있는 것 같아서 좋다.
→ 계속해 단기 목표를 세우고 완료해가자!</p>
<h3 id="피어세션-👥">피어세션 👥</h3>
<p>나를 포함해서 모두 이제 이 분위기에 적응해 나가는 느낌이다.</p>
<p>모르는 것에 혼자 고민하는 것이 아닌, 더 자유롭고 한결 편안한 분위기에서 질문과 피드백을 나눌 수 있었다.
1주차 보다 다들 적극적으로 진행할 수 있어서 좋았다.
→ 이런 분위기, 환경을 3주차에도, 4주차에도 만들어 나가자!</p>
<blockquote>
<p>좋은 팀원과 함께하니 단 하루 밖에 피어 세션을 못한 것이 아쉽다…</p>
</blockquote>
<hr>
<h1 id="problem-⚠️">Problem ⚠️</h1>
<p>피어 세션을 통해 여럿이 어떤 주제에 대해 이야기할 때 더 많은 정보를 얻을 수 있는 것은 매일 느낀다. 
피어 세션이 아니어도, 다른 캠퍼분들과 저장소의 댓글이나 Slack 을 통해 의견을 나누고 질문하는 것이 가능하다.
이를 적극적으로 활용하지 못한 점은 아쉽다.</p>
<hr>
<h1 id="try-🚀">Try 🚀</h1>
<p>스터디 그룹 혹은 랜덤한 다른 캠퍼분들의 <code>README.md</code> 를 꾸준히 보며 정말 다양한 접근 방식과 문제 해결 방식을 알 수 있고, 남이 읽기 좋은 글을 쓰는 법, 즉 조금이라도 더 좋은 <code>README.md</code> 를 파악해 갈 수 있는 것 같다.</p>
<p>→ 무작정 글을 쓰는게 아닌 나의 진짜 고민과, 문제 해결과정을 작성하자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 챌린지 - 1주차 회고]]></title>
            <link>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@pmthk__/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 03 Aug 2024 05:20:04 GMT</pubDate>
            <description><![CDATA[<h1 id="keep-🟢">Keep 🟢</h1>
<p>어려운 미션이 많았다. CS 지식을 요구사항 분석 - 설계 - 구현을 하며 배우는 과정이다.
일주일간 힘들어도, 포기하지 않고 최선을 다해 잘 해결해왔다.
→ 점점 더 어려워질 것이다. 내 템포로 최선을 다하자!</p>
<hr>
<h1 id="problem-⚠️">Problem ⚠️</h1>
<p><strong>학습과 설계, 구현의 적절한 균형을 찾지 못했다.</strong></p>
<p>배경지식에 대한 충분한 학습이 되지 않아서 미션을 해결하면서 모르는 것이 생길 때 마다 이 것을 찾아보느라 어느 하나에 집중하지 못했던 것 같다. 그러다보니 점차 미션 해결에만 집중해서, 코드만 작성하는 기계가 된 나를 발견한 것 같다.</p>
<p><strong>어색한 피어세션에서 더 소극적이었다.</strong></p>
<p>첫 주차라 모두 어색했고, 적극적이지 못했다. 피어 세션을 통해 서로의 문제 해결 과정, 고민을 나누지 않고, 서로의 코드만을 바라보며 각자의 코드에 대한 설명만 장황하게 늘어놓았다. </p>
<hr>
<h1 id="try-🚀">Try 🚀</h1>
<blockquote>
<p>챌린지 과정은 도전적인 미션을 통해 CS 지식을 배우고 한정된 시간 내 최선의 결과를 만드는 연습을 하는 과정이다.</p>
</blockquote>
<p>나만의 속도와 방식으로 학습하고 설계하고 구현하자. 완성에 집착하지 말자!</p>
<p>6명의 스터디 그룹이 모두 같은 과정으로 문제를 해결하지 않는다.
동료 캠퍼로부터 더 다양한 시각과 해결 방식을 얻어갈 수 있도록 하자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[부스트캠프 웹 · 모바일 9기 베이직 후기]]></title>
            <link>https://velog.io/@pmthk__/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9-%EB%AA%A8%EB%B0%94%EC%9D%BC-9%EA%B8%B0-%EB%B2%A0%EC%9D%B4%EC%A7%81-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@pmthk__/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9-%EB%AA%A8%EB%B0%94%EC%9D%BC-9%EA%B8%B0-%EB%B2%A0%EC%9D%B4%EC%A7%81-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 14 Jul 2024 08:08:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/pmthk__/post/dfa859a4-c59e-4854-b1a2-8812a846859f/image.png" alt=""></p>
<h1 id="베이직">베이직?</h1>
<p>베이직 과정은 개발자에게 중요한 역량인 문제 해결력이 무엇인지 경험할 수 있도록 만들어진 과정이다. </p>
<p>2주 동안 문제를 정의하고 설계하는 연습을 하며, 문제를 해결하며 성장할 수 있는 기회를 제공한다.</p>
<p>1차 문제 해결력 테스트 결과에 따라 베이직 과정을 수료하면 2차 테스트 응시 기회가 주어지는 <em>“베이직 입과 대상자”</em>와 바로 2차 테스트 응시 기회가 주어지는 <em>“2차 문제 해결력 테스트 대상자”</em>로 나뉘게 된다.</p>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/6a7023d2-7cab-47bd-966a-5bda528afc86/image.png" alt=""></p>
<p>나는 <em>“2차 문제 해결력 테스트 대상자”</em> 였지만, 최근에 진행한 연습 프로젝트에서도 설계의 부족함을 느꼈기 때문에 베이직 과정에서 나아갈 방향을 얻을 수 있을 것 같아서 입과를 선택했다.</p>
<p>베이직을 입과하면 2주간 개인 미션과 그룹 미션을 해결하게 된다.</p>
<h1 id="그래서">그래서?</h1>
<p>베이직 과정은 아주 짧았지만, 얻어가는 게 많은 과정이었다.
<img src="https://velog.velcdn.com/images/pmthk__/post/d194b558-9901-43c3-b45d-9d9db5c81b9a/image.png" alt=""></p>
<h2 id="글을-잘-읽자">글을 잘 읽자!</h2>
<p>나는 알고리즘 문제를 풀거나 간단한 구현 문제를 만나면, 예시 코드나 요구하는 결과물을 기준으로 바로 코드를 작성하는 경향이 있다.</p>
<p>베이직 과정의 미션에는 요구사항과 예시 코드가 있지만, 중간중간 모호한 부분도 존재했기 때문에 기존 방식대로 코드를 작성하는 데만 집중하게 되면 요구사항의 일부분을 놓치는 경우가 발생했다. </p>
<p>또, 미션의 정답이 존재하지 않기 때문에 그렇게 작성한 내 코드가 정답임을 알 수도 없다.</p>
<p>따라서, 우선 주어지는 글, 즉 요구사항을 잘 읽고 분석하며 내 생각을 잘 정리하여 그 내용을 토대로 미션을 해결하는 것이 중요함을 깨닫게 되었다. </p>
<p>즉, 우선 글을 잘 읽고 상황 및 문제를 잘 이해하는 것이 중요함을 깨달았다.</p>
<h2 id="글을-잘-쓰자">글을 잘 쓰자!</h2>
<p>베이직 과정에서는 글을 읽고 분석한 결과물, 구현을 위해 설계한 내용들, 그리고 구현에 성공했다면 어떻게 구현했는지에 대한 내용을 모두 글로 작성하기로 했다. (필수는 아니지만 나는 그렇게 했다.)</p>
<p>분석한 내용과 구현하기 위해 설계한 내용을 글로 작성하다 보면 내가 어떤 부분을 놓쳤는지 확인할 수 있었다. </p>
<p>또한, 구현 과정에서 내가 작성한 설계를 보고도 모호한 경우가 발생하기도 해 최대한 구체적으로 글을 작성하는 게 중요하다고 느껴졌다.</p>
<p>또 마지막으로, 다른 캠퍼분들의 저장소를 확인하다 보니 내가 작성한 글이 가독성이 좋지 않다는 점을 알게 되었다.</p>
<p>따라서, 내가 아닌 다른 사람이 읽었을 때에도 모호함이 남지 않도록, 그리고 글의 내용을 잘 파악할 수 있도록 글을 작성하는 것이 개발자로서도 중요한 역량임을 알게 되었다.</p>
<h2 id="항상-정답이-있지-않다">항상 정답이 있지 않다.</h2>
<p>베이직 과정의 미션에는 정답이 없다. </p>
<p>기존의 알고리즘 문제나 과제에서는 정해진 정답을 찾는 데에 집중했다면, 베이직 과정에서는 다양한 접근 방식과 다양한 해결 방법으로 미션을 해결하는 것이 가능했다.</p>
<p>다른 캠퍼분들, 함께 이 과정을 진행한 친구, 그리고 수료생의 접근법까지 많은 접근 방식과 많은 해결 방법을 접할 수 있었고, 나와 같은 방식으로 해결하신 분들도 있지만, 전혀 다른 방식으로 해결하신 분들도 많았다.</p>
<p>다양한 해결 방법을 접하면서 나의 방법이 아닌 여러 방법으로 시도해보는 것은 내 좁았던 시야를 넓히고 사고의 유연성을 조금이라도 키울 수 있었던 것 같다.</p>
<p>또, 짧은 시간이었지만 그룹 미션에서 여러 캠퍼분들과 아이디어를 공유하고, 하나의 결과를 설계하기 위해 토론했던 과정에서 동료의 중요성, 즉 협력하는 것이 얼마나 더 많은 일을 할 수 있는 지 다시 한번 깨닫게 되었다.</p>
<h1 id="앞으로">앞으로?</h1>
<p>베이직은 2주라는 짧은 기간이었지만, 좋은 동료가 되고 좋은 개발자로 성장하는 데에 필요한 하나의 밑거름이었다. </p>
<p>챌린지 과정을 진행하면서도 지속적으로 학습하고, 다양한 관점에서 문제를 바라보고 여러 가지 접근 방식을 시도해보며 시야를 넓히는 것에 집중해야 겠다.</p>
<p>또, 구현보다는 문제를 분석하고 정의하는 과정, 그리고 설계하는 과정에 더 많은 노력을 기울일 예정이다. </p>
<p>이를 통해 도출한 결과물을 명확하게 정리하고 다른 사람도 쉽게 이해할 수 있도록 설명할 수 있는 능력도 기르고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js Circular dependency]]></title>
            <link>https://velog.io/@pmthk__/Circular-dependency</link>
            <guid>https://velog.io/@pmthk__/Circular-dependency</guid>
            <pubDate>Tue, 02 Jul 2024 08:41:20 GMT</pubDate>
            <description><![CDATA[<h1 id="순환-종속성이란">순환 종속성이란?</h1>
<blockquote>
<p><a href="https://docs.nestjs.com/fundamentals/circular-dependency">A circular dependency occurs when two classes depend on each other.</a></p>
</blockquote>
<p><strong>순환 종속성</strong>은 두 클래스가 각자에 의존할 때 발생한다.</p>
<p>Nest.js 에서는 <strong>모듈과 모듈사이</strong> 혹은 <strong>프로바이더와 프로바이더 사이</strong>에서 발생할 수 있다.</p>
<h1 id="문제">문제</h1>
<p><code>ProductModule</code> 그리고 <code>OrderModule</code> 이 있다.</p>
<p><code>ProductModule</code> 내에는 <code>ProductService</code> 가, <code>OrderModule</code> 내에는 <code>OrderService</code> 가 있다.</p>
<pre><code class="language-tsx">// product.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Product]),
    OrderModule, // ← 문제 발생 지점
  ],
  providers: [ProductService],
  controllers: [ProductController],
  exports: [ProductService],
})
export class ProductModule {}

// order.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Order]),
    Product Module, // ← 문제 발생 지점
  ],
  providers: [OrderService],
  controllers: [OrderController],
  exports: [OrderService],
})
export class OrderModule {}</code></pre>
<h2 id="무엇이-문제인가">무엇이 문제인가?</h2>
<p><code>ProductService</code> 의 구매 로직은 아래와 같다.</p>
<pre><code class="language-tsx">@Injectable
export class ProductService {
  constructor(
    private orderService: OrderService,
    @InjectRepository(Product) private productRepository: Repository&lt;Product&gt;,
  ) {}

    buyProduct = async (buyer: User, productId: number) =&gt; {
        const product = await this.productRepository.findOne({
      where: { id: productId },
      relations: [&#39;seller&#39;],
    })

    // ...

    const newOrder = await this.orderService.createOrder(...)
    // ProductService 내에서 OrderService 를 사용해야 한다.

        // ...
    } 
}</code></pre>
<p>구매자가 어떤 제품을 구매하면, 해당 제품의 상태를 <code>예약중</code> 으로 변경하고, 새로운 주문을 만들어야 한다. </p>
<p>따라서 <code>OrderService</code> 를 <code>ProductService</code> 내에서 사용해야 한다.</p>
<pre><code class="language-tsx">@Injectable()
export class OrderService {
  constructor(
      private productService: ProductService,
    @InjectRepository(Order) private orderRepository: Repository&lt;Order&gt;,
  ) {}

  approveOrder = async (orderId: number) =&gt; {
    // ...

    await this.productService.updateOrder(...)

    // ...
  }
}</code></pre>
<p>판매자가 위에서 생성된 어떤 주문을 승인하는 상황이 발생한다고 가정하자.</p>
<p><code>OrderService</code> 내의 <code>AcceptOrder</code> 가 호출될 것이다.</p>
<p>주문을 승인하면 해당 주문의 상태를 <code>대기중</code> 에서 <code>승인됨</code> 으로 변경하고, 해당 제품의 상태를 판매완료 로 변경해야 하므로, <code>OrderService</code> 내에서 <code>ProductService</code> 를 사용하게 된다.</p>
<p>→ 이렇게 순환 종속성이 발생하게 되었다.</p>
<p>`[Nest] 64392  -  ERROR [ExceptionHandler] Nest cannot create the OrderModule instance.
The module at index [1] of the OrderModule &quot;imports&quot; array is undefined.</p>
<p>Potential causes:</p>
<ul>
<li>A circular dependency between modules. Use forwardRef() to avoid it. Read more: <a href="https://docs.nestjs.com/fundamentals/circular-dependency">https://docs.nestjs.com/fundamentals/circular-dependency</a></li>
<li>The module at index [1] is of type &quot;undefined&quot;. Check your import statements and the type of the module.`</li>
</ul>
<h1 id="해결하기">해결하기</h1>
<h2 id="forwardref">forwardRef</h2>
<p>위의 에러와 <a href="https://docs.nestjs.com/fundamentals/circular-dependency">공식 문서</a>에 제시된 방법이다. </p>
<p><strong>전방 참조, 선행 참조</strong>라는 이 유틸리티 기능을 이용해 아직 정의되지 않은 class 를 참조할 수 있게 한다.</p>
<p>나는 현재 <code>OrderModule</code> 내의 <code>OrderService</code> 
그리고 <code>ProductModule</code> 내의 <code>ProductService</code> 구조이므로 </p>
<p>Module forward reference 항목 또한 적용해야 한다.</p>
<p>이를 적용해 순환 종속성 문제를 해결해보자.</p>
<pre><code class="language-tsx">// product.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Product]),
    forwardRef(() =&gt; OrderModule),
  ],
  providers: [ProductService],
  controllers: [ProductController],
  exports: [ProductService],
})
export class ProductModule {}

// product.service.ts
@Injectable
export class ProductService {
  constructor(
    @Inject(forwardRef(() =&gt; OrderService))
    private orderService: OrderService,
    @InjectRepository(Product) private productRepository: Repository&lt;Product&gt;,
  ) {}

    // ...
}</code></pre>
<pre><code class="language-tsx">// order.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Order]),
    forwardRef(() =&gt; ProductModule),
  ],
  providers: [OrderService],
  controllers: [OrderController],
  exports: [OrderService],
})
export class OrderModule {}

// order.service.ts
@Injectable()
export class OrderService {
  constructor(
    @Inject(forwardRef(() =&gt; ProductService))
      private productService: ProductService,
    @InjectRepository(Order) private orderRepository: Repository&lt;Order&gt;,
  ) {}

    // ...
}</code></pre>
<p>위와 같이 작성하면 <strong>Circular Dependency</strong> 문제를 해결할 수 있다.</p>
<h2 id="moduleref">ModuleRef</h2>
<p><a href="https://docs.nestjs.com/fundamentals/module-ref">공식문서</a>에 추가로 <code>ModuleRef</code> 를 이용해 <code>DI container</code> 로 부터 직접 프로바이더 인스턴스를 받아오는 방법이 소개되어 있다.</p>
<p>이 방법도 적용해보자.</p>
<pre><code class="language-tsx">// product.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Product]),
    // forwardRef(() =&gt; OrderModule),
  ],
  providers: [ProductService],
  controllers: [ProductController],
  // exports: [ProductService],
})
export class ProductModule {}

// product.service.ts
@Injectable
export class ProductService implements OnMoudleInit {
    private orderService: OrderService

  constructor(
      private moduleRef: ModuleRef,
    @InjectRepository(Product) private productRepository: Repository&lt;Product&gt;,
  ) {}

  onMoudleInit() {
      this.orderService = this.moduleRef.get(OrderService, { strict: false });
    }

    // ...
}</code></pre>
<pre><code class="language-tsx">// order.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([Order]),
    // forwardRef(() =&gt; ProductModule),
  ],
  providers: [OrderService],
  controllers: [OrderController],
  // exports: [OrderService],
})
export class OrderModule {}

// order.service.ts
@Injectable
export class OrderService implements OnMoudleInit {
    private productService: productService

  constructor(
      private moduleRef: ModuleRef,
    @InjectRepository(Order) private productRepository: Repository&lt;Order&gt;,
  ) {}

  onMoudleInit() {
      this.productService = this.moduleRef.get(ProductService, { strict: false });
    }

    // ...
}</code></pre>
<p>코드를 이와 같이 수정해도 순환 종속성 문제를 해결할 수 있다.</p>
<blockquote>
<p>위의 경우 두 프로바이더가 다른 모듈 내에 존재하기 때문에 <code>moduleRef.get()</code> 의 두 번째 인자로 <code>{ strict: false }</code> 를 전달해 주어야 합니다.</p>
</blockquote>
<h2 id="공통-모듈">공통 모듈</h2>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/a6aba1f4-c364-4e67-b24a-066606d6a354/image.png" alt=""></p>
<p>Nest 는 하위 모듈을 합쳐서 새로운 모듈을 생성한다. <code>Product</code> 와 <code>Order</code> 모듈을 함께 사용해서 새로운 <code>CommonModule</code> (공통 모듈)을 만들어서 사용할 수 있다.</p>
<pre><code class="language-tsx">// common.module.ts
@Module({
  imports: [
      OrderModule,
      ProductModule
  ],
  providers: [CommonService],
  controllers: [],
})
export class OrderModule {}

// common.service.ts
@Injectable
export class CommonService{
  constructor(
      private orderService: OrderService
        private productService: ProductService
  ) {}

    // ...
}</code></pre>
<h1 id="결론-진짜-문제">결론 (진짜 문제)</h1>
<p><code>forwardRef</code> 와 <code>ModuleRef</code> 의 경우는  간단하고 빠르게 적용할 수 있지만, 결국 코드 복잡도를 높히게 되어 유지보수가 어려워진다고 한다. Nest.js 문서에는 하나의 테크닉으로 소개되고 있다.<br>리팩토링을 통해 공통 모듈을 작성하고 순환 종속성 문제를 해결하는 방식이 장기적으로는 더 좋은 방식이라고 한다.</p>
<p>나의 진짜 문제는 <code>buyProduct</code> 가 <code>ProductService</code> 내부에 있던 것이다.
하나의 관심사, 하나의 도메인을 기준으로 모듈을 구성하는 것이 맞다.
처음에는 구매는 제품 구매이니 <code>ProductService</code> 내에서 구현했지만, 실제 구매하는 행위는 주문을 생성하는 행위이다. 
따라서,  <code>OrderModule</code> 내에서 <code>ProductModule</code> 를 사용하는 것이 맞았을 거라는 생각이 들었다.</p>
<p>아니면, <code>TypeORM</code> 도 모듈을 참조한 것이니, <code>OrderService</code> 에서 직접 orderRepo 를 사용하면, 
여러 모듈을 상위 모듈에서 사용한 위 이미지와 같은 구조라는 생각도 하게되었다.</p>
<p>어떤 방식으로 이를 해결해야 하나 오래 고민했다. 더 나은 방식을 사용해보려고 노력했다.
<strong>진짜 결론은, 설계가 더 중요하다. 구현보다는 설계에 더 투자해보자…</strong></p>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://docs.nestjs.com/fundamentals/circular-dependency">순환 종속성 - Nest.js Docs</a></li>
<li><a href="https://docs.nestjs.com/fundamentals/module-ref">Module Reference - Nest.js Docs</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[28장. Number]]></title>
            <link>https://velog.io/@pmthk__/28%EC%9E%A5.-Number</link>
            <guid>https://velog.io/@pmthk__/28%EC%9E%A5.-Number</guid>
            <pubDate>Sun, 30 Jun 2024 10:34:40 GMT</pubDate>
            <description><![CDATA[<h1 id="number-생성자-함수">Number 생성자 함수</h1>
<p>표준 빌트인 객체인 Number 객체는 생성자 함수 객체다. </p>
<p>따라서 <code>new</code> 연산자와 함께 호출하여 <code>Number</code> 인스턴스를 생성할 수 있다.</p>
<p>인수를 전달하지 않으면 <code>[[NumberData]]</code> 내부 슬롯에 0 을 할당한 <code>Number</code> 래퍼 객체를 생성한다.</p>
<pre><code class="language-jsx">const numObj = new Number();
console.log(numObj); // Number {[[PrimitiveValue]]: 0}</code></pre>
<p>인수로 숫자가 아닌 값을 전달하면 인수를 숫자로 강제 변환한 후 <code>[[NumberData]]</code> 내부 슬롯에 변환된 숫자를 할당한 <code>Number</code> 래퍼 객체를 생성한다. </p>
<p>숫자로 변환이 불가능 하다면 <code>NaN</code> 을 할당한다.</p>
<pre><code class="language-jsx">let numObj = new Number(&#39;10&#39;);
console.log(numObj); // Number {[[PrimitiveValue]]: 10}

numObj = new Number(&#39;Hello&#39;);
console.log(numObj); // Number {[[PrimitiveValue]]: NaN}</code></pre>
<p><code>new</code> 연산자를 사용하지 않고 <code>Number</code> 생성자 함수를 호출하면 인스턴스를 생성하지 않고 숫자를 반환한다.</p>
<p>이를 이용하여 명시적으로 타입을 변환하기도 한다.</p>
<pre><code class="language-jsx">// 문자열 타입 =&gt; 숫자 타입
Number(&#39;0&#39;);     // -&gt; 0
Number(&#39;-1&#39;);    // -&gt; -1
Number(&#39;10.53&#39;); // -&gt; 10.53

// 불리언 타입 =&gt; 숫자 타입
Number(true);  // -&gt; 1
Number(false); // -&gt; 0</code></pre>
<h1 id="number-프로퍼티">Number 프로퍼티</h1>
<blockquote>
<p>새로 알게된 내용 위주로만 정리했습니다. 프로퍼티와 메서드는 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number</a> 에서 확인할 수 있습니다.</p>
</blockquote>
<h2 id="numberepsilon">Number.EPSILON</h2>
<p>ES6 에서 도입된 <strong><code>Number.EPSILON</code></strong> 은 <code>Number</code> 형으로 표현될 수 있는 1과 1보다 큰 값 중에서 가장 작은 값의, 차이다. 대략 <code>2^-52</code>의 값을 갖는다.</p>
<p>아래와 같이 부동 소수점 산술 연산은 정확한 결과를 기대하기 어렵다. 이 오차를 해결하기 위해 사용된다.</p>
<pre><code class="language-jsx">0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3 // false</code></pre>
<pre><code class="language-jsx">function isEqual(a, b) {
    return Math.abs(a-b) &lt; Number.EPSILON;
}

isEqual(0.1 + 0.2, 0.3); // true</code></pre>
<h1 id="number-메서드">Number 메서드</h1>
<h2 id="numberisnan">Number.isNaN</h2>
<p>ES6에서 도입된 <code>Number.isNaN</code> 정적 메서드는 인수로 전달된 숫자값이 <code>NaN</code> 인지 검사하여 그 결과를 불리언으로 반환한다.</p>
<p>빌트인 전역함수인 isNaN 은 전달받은 인수를 숫자로 암묵적 타입 변환한다.</p>
<p>하지만, Number.isNaN 메서드는 전달받은 인수를 숫자로 암묵적으로 타입을 변환하지 않는다.</p>
<pre><code class="language-jsx">// Number.isNaN은 인수를 숫자로 암묵적 타입 변환하지 않는다.
Number.isNaN(undefined); // -&gt; false

// isFinite는 인수를 숫자로 암묵적 타입 변환한다. undefined는 NaN으로 암묵적 타입 변환된다.
isNaN(undefined); // -&gt; true</code></pre>
<h2 id="numberprototypetofixed">Number.prototype.toFixed</h2>
<p><code>toFixed</code> 메서드는 숫자를 반올림하여 문자열로 반환한다.</p>
<pre><code class="language-jsx">// 소수점 이하 반올림. 인수를 생략하면 기본값 0이 지정된다.
(12345.6789).toFixed(); // -&gt; &quot;12346&quot;
// 소수점 이하 1자리수 유효, 나머지 반올림
(12345.6789).toFixed(1); // -&gt; &quot;12345.7&quot;
// 소수점 이하 2자리수 유효, 나머지 반올림
(12345.6789).toFixed(2); // -&gt; &quot;12345.68&quot;
// 소수점 이하 3자리수 유효, 나머지 반올림
(12345.6789).toFixed(3); // -&gt; &quot;12345.679&quot;</code></pre>
<h2 id="numberprototypetostring">Number.prototype.toString</h2>
<p>toString 메서드는 숫자를 문자열로 변환하여 반환한다.</p>
<p>진법을 나타내는 2 ~ 36 사이의 정수값을 인수로 전달할 수 있다. 기본값은 10이다.</p>
<pre><code class="language-jsx">// 인수를 생략하면 10진수 문자열을 반환한다.
(10).toString(); // -&gt; &quot;10&quot;
// 2진수 문자열을 반환한다.
(16).toString(2); // -&gt; &quot;10000&quot;
// 8진수 문자열을 반환한다.
(16).toString(8); // -&gt; &quot;20&quot;
// 16진수 문자열을 반환한다.
(16).toString(16); // -&gt; &quot;10&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[27장. 배열]]></title>
            <link>https://velog.io/@pmthk__/27%EC%9E%A5.-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@pmthk__/27%EC%9E%A5.-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Sun, 30 Jun 2024 10:33:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>배열은 자주 사용해서 익숙하다고 생각했지만 의외로 모르는 내용이 많았습니다.
내용은 제가 처음 알게 되었거나, 잘 모르고 있었던 내용 위주로 정리했습니다.</p>
</blockquote>
<h1 id="배열">배열</h1>
<p>배열은 순차적으로 나열한 자료구조다.</p>
<p><strong>자바스크립트에는 배열이라는 타입은 존재하지 않는다. 배열은 객체 타입이다.</strong></p>
<pre><code class="language-jsx">typeof arr // → object</code></pre>
<p>하지만, 일반 객체와 구별되는 특징이 있다.</p>
<ul>
<li>값의 참조를 프로퍼티 키가 아닌 인덱스로 참조한다.</li>
<li>값의 순서가 존재한다.</li>
<li><code>length</code> 프로퍼티가 존재한다.</li>
</ul>
<h1 id="자바스크립트-배열은-배열이-아니다">자바스크립트 배열은 배열이 아니다.</h1>
<p>자료구조에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 구조를 말한다.</p>
<p>즉, 하나의 데이터 타입으로 통일되어 있고, 서로 연속적으로 인접해 있는 자료구조이다.</p>
<p><strong>⇒ 이러한 배열을 밀집 배열이라고 한다.</strong></p>
<p>하지만, 자바스크립트의 배열은 이러한 배열의 의미와는 조금 다르다.</p>
<p>배열을 위한 동일한 크기의 메모리 공간도 필요하지 않고, 연속적으로 있지 않을 수도 있다.</p>
<p><strong>⇒ 이러한 배열을 희소 배열이라고 한다.</strong></p>
<p><strong>→ 자바스크립트의 배열은 일반적인 배열을 흉내 낸 특수한 객체이다.</strong></p>
<pre><code class="language-jsx">console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));
/*
{
    &#39;0&#39;: {value: 1, writable: true, enumerable: true, configurable: true}
    &#39;1&#39;: {value: 2, writable: true, enumerable: true, configurable: true}
    &#39;2&#39;: {value: 3, writable: true, enumerable: true, configurable: true}
    length: {value: 3, writable: true, enumerable: false, configurable: false}
}
*/</code></pre>
<p>자바스크립트 배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며, <code>length</code> 프로퍼티를 갖는 특수한 객체이다.</p>
<blockquote>
<p>자바스크립트 배열은 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느릴수밖에 없는 구조적인 단점이 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 일반적인 배열보다 빠른 성능을 기대할 수 있다.</p>
</blockquote>
<h1 id="length-프로퍼티와-희소-배열">length 프로퍼티와 희소 배열</h1>
<p><code>length</code> 프로퍼티의 값은 배열에 요소를 추가하거나 삭제하면 자동으로 갱신된다. </p>
<p><code>length</code> 프로퍼티 값은 배열의 길이를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당할 수 있다.</p>
<p>현재 <code>length</code> 프로퍼티 값보다 작은 숫자를 할당하면 배열의 길이가 줄어들고,</p>
<p>더 큰 값을 할당하는 경우에는 <code>length</code> 프로퍼티 값은 변경되지만, 실제로 배열의 길이가 늘어나진 않는다.</p>
<pre><code class="language-jsx">const arr = [1];

arr.length = 3;

console.log(arr.length); // 3
console.log(arr) // [1, empty x 2]</code></pre>
<p>위의 출력에서의 empty x 2 는 실제로 추가된 배열의 요소가 아니다.</p>
<p>이처럼 현재 <code>length</code> 프로퍼티의 값보다 큰 숫자 값을 할당하면 <code>length</code> 프로퍼티의 값은 변경되지만 실제 배열에는 아무 변함이 없다. </p>
<p>심지어, 메모리 공간을 보호하지도 않으며 빈 요소를 생성하지도 않는다.</p>
<p>자바스크립트는 문법적으로 희소 배열을 허용하지만 희소 배열은 사용하지 않는 것이 좋다.</p>
<p>모던 자바스크립트 엔진은 요소의 타입이 일치하는 배열을 생성할 때 일반적인 의미의 배열처럼 연속된 메모리 공간을 확보하는 것으로 알려져 있다. </p>
<p><strong>→ 반드시 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선이다.</strong></p>
<h1 id="배열-생성">배열 생성</h1>
<h2 id="배열-리터럴">배열 리터럴</h2>
<p>가장 간단한 방식이다.</p>
<pre><code class="language-jsx">const arr = [1, 2, 3];</code></pre>
<h2 id="array-생성자-함수">Array 생성자 함수</h2>
<pre><code class="language-jsx">const arr = new Array()&#39; // []
// 전달된 인수가 없으면 빈 배열을 생성한다.

const arr = new Array(10); 
// 전달된 인수가 1개이고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성한다.
// 길이 10인 희소 배열이 생성된다. 배열의 요소는 없다.
// 4_294_967_295 이하인 양의 정수여야 한다.

const arr = new Array(1, 2, 3) // [1, 2, 3]
const arr = new Array({}) // [{}]
// 전달된 인수가 2개 이상이거나 숫자가 아닌 경우 인수를 요소로 갖는다.</code></pre>
<h2 id="arrayof">Array.of</h2>
<p>ES6에서 도입된 <code>Array.of</code> 메서드는 전달된 인수를 요소로 갖는 배열을 생성한다.</p>
<p><code>Array.of</code>는 Array 생성자 함수와 다르게 전달된 인수가 1개이고 숫자이더라도 인수를 갖는 배열을 생성한다.</p>
<pre><code class="language-jsx">Array.of(1); // -&gt; [1]
Array.of(1, 2, 3); // -&gt; [1, 2, 3]
Array.of(&#39;string&#39;); // -&gt; [&#39;string&#39;]</code></pre>
<h2 id="arrayfrom">Array.from</h2>
<p>ES6에서 도입된 <code>Array.from</code> 메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.</p>
<pre><code class="language-jsx">// 유사 배열 객체를 변환하여 배열을 생성한다.
Array.from({ length: 2, 0: &#39;a&#39;, 1: &#39;b&#39; }); // ➔ [&#39;a&#39;, &#39;b&#39;]

// 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다.
Array.from(&#39;Hello&#39;); // ➔ [&#39;H&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]</code></pre>
<p><code>Array.from</code> 을 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 값을 만들면서 요소를 채울 수 있다.</p>
<pre><code class="language-jsx">Array.from({ length: 3 }, (_, i) =&gt; i); // [0, 1, 2]</code></pre>
<h1 id="배열-요소의-참조">배열 요소의 참조</h1>
<p>배열의 요소를 참조할 때에는 대괄호 <code>[]</code> 를 사용한다.</p>
<p>유효한 인덱스가 아니면 <code>undefined</code> 를 반환한다.</p>
<h1 id="배열-요소의-추가와-갱신">배열 요소의 추가와 갱신</h1>
<p>인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수(또는 정수 형태의 문자열)를 사용해야 한다.</p>
<p>만약 정수 이외의 값을 인덱스처럼 사용하면 프로퍼티가 생성된다.</p>
<p>이때 추가된 프로퍼티는 <code>length</code> 프로퍼티 값에 영향을 주지 않는다.</p>
<pre><code class="language-jsx">const arr = [];

// 배열 요소의 추가
arr[0] = 1;
arr[&#39;1&#39;] = 2;

// 프로퍼티 추가
arr[&#39;foo&#39;] = 3;
arr.bar = 4;
arr[1.1] = 5;
arr[-1] = 6;

console.log(arr); // [1, 2, foo: 3, bar: 4, &#39;1.1&#39;: 5, &#39;-1&#39;: 6]
console.log(arr.length) // 2</code></pre>
<h1 id="배열-요소의-삭제">배열 요소의 삭제</h1>
<p>배열은 사실 객체이므로 <code>delete</code> 연산자를 사용할 수 있다.</p>
<pre><code class="language-jsx">const arr = [1, 2, 3];

// 배열 요소의 삭제
delete arr[1];
console.log(arr); // [1, empty, 3]

// length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
console.log(arr.length) // 3 </code></pre>
<p>희소 배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 Array.prototype.splice() 메서드를 사용한다.</p>
<blockquote>
<p>메서드와 고차 함수 중 제 생각에 중요한, 자주 사용하는, 잊어버리기 쉬운 친구들만 작성해보았습니다.</p>
</blockquote>
<h1 id="arrayprototypemap">Array.prototype.map</h1>
<p><code>map</code> 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.</p>
<p><strong>그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다. (원본 배열은 유지된다.)</strong></p>
<p><code>map</code> <strong>메서드가 생성한 새로운 배열의</strong> <code>length</code> <strong>프로퍼티의 값은 기존 배열의</strong> <code>length</code> <strong>프로퍼티 값과 반드시 일치한다.</strong></p>
<h1 id="arrayprototypereduce">Array.prototype.reduce</h1>
<p><code>reduce</code> 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다.</p>
<p><strong>그리고 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다. (원본 배열은 유지된다.)</strong></p>
<pre><code class="language-jsx">// [1, 2, 3, 4]의 모든 요소의 누적을 구한다.
const sum = [1, 2, 3, 4]
    .reduce((accumulator, currentValue, index, array) =&gt; accumulator + currentValue, 0);

console.log(sum); // 10</code></pre>
<p><img src="https://velog.velcdn.com/images/pmthk__/post/7d47a553-9d15-43a2-8218-96d0788a0d0c/image.png" alt=""></p>
<p><code>reduce</code> <strong>메서드를 호출할 때는 언제나 초기값을 전달하는 것이 안전하다.</strong></p>
<h1 id="arrayprototypeflat">Array.prototype.flat</h1>
<p>ES10 에서 도입된 <code>flat</code> 메서드는 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화 한다.</p>
<pre><code class="language-jsx">// 중첩 배열을 평탄화하기 위한 깊이 값의 기본값은 1이다.
[1, [2, [3, [4]]]].flat();  // -&gt; [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(1); // -&gt; [1, 2, [3, [4]]]

// 중첩 배열을 평탄화하기 위한 깊이 값을 2로 지정하여 2단계 깊이까지 평탄화한다.
[1, [2, [3, [4]]]].flat(2); // -&gt; [1, 2, 3, [4]]
// 2번 평탄화한 것과 동일하다.
[1, [2, [3, [4]]]].flat().flat(); // -&gt; [1, 2, 3, [4]]

// 중첩 배열을 평탄화하기 위한 깊이 값을 Infinity로 지정하여 중첩 배열 모두를 평탄화한다.
[1, [2, [3, [4]]]].flat(Infinity); // -&gt; [1, 2, 3, 4]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[26장. ES6 함수의 추가 기능]]></title>
            <link>https://velog.io/@pmthk__/26%EC%9E%A5.-ES6-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EA%B0%80-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@pmthk__/26%EC%9E%A5.-ES6-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EA%B0%80-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Thu, 27 Jun 2024 12:30:09 GMT</pubDate>
            <description><![CDATA[<h1 id="함수의-구분">함수의 구분</h1>
<p>ES6 이전까지 자바스크립트의 함수는 별다른 구분 없이 사용되었다.</p>
<p><strong>즉, ES6 이전의 모든 함수는 일반 함수로서 호출할 수 있으면서, 생성자 함수로서 호출할 수 있다.</strong></p>
<p>ES6 에서는 함수를 사용 목적에 따라 세가지 종류로 명확히 구분한다.</p>
<table>
<thead>
<tr>
<th>ES6 함수의 구분</th>
<th>constructor</th>
<th>prototype</th>
<th>super</th>
<th>arguments</th>
</tr>
</thead>
<tbody><tr>
<td>일반 함수</td>
<td>O</td>
<td>O</td>
<td>X</td>
<td>O</td>
</tr>
<tr>
<td>메서드</td>
<td>X</td>
<td>X</td>
<td>O</td>
<td>O</td>
</tr>
<tr>
<td>화살표 함수</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
</tbody></table>
<h1 id="메서드">메서드</h1>
<p><strong>ES6 사양에서 메서드는 메서드 축약 표현으로 정의된 함수만을 의미한다.</strong></p>
<pre><code class="language-tsx">const obj = {
  x: 1,
  // foo는 메서드이다.
  foo() { return this.x; },
  // bar에 바인딩된 함수는 메서드가 아닌 일반 함수이다.
  bar: function() { return this.x; }
};

console.log(obj.foo()); // 1
console.log(obj.bar()); // 1</code></pre>
<p>이러한 메서드는 인스턴스를 생성할 수 없는 <code>non-constructor</code> 이다.</p>
<p>인스턴스를 생성할 수 없으므로 <code>prototype</code> 프로퍼티가 없고, 프토토타입도 생성하지 않는다.</p>
<pre><code class="language-tsx">new obj.foo(); // -&gt; TypeError: obj.foo is not a constructor
new obj.bar(); // -&gt; bar {}</code></pre>
<pre><code class="language-tsx">// obj.foo는 constructor가 아닌 ES6 메서드이므로 prototype 프로퍼티가 없다.
obj.foo.hasOwnProperty(&#39;prototype&#39;); // -&gt; false

// obj.bar는 constructor인 일반 함수이므로 prototype 프로퍼티가 있다.
obj.bar.hasOwnProperty(&#39;prototype&#39;); // -&gt; true</code></pre>
<p>ES6 메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯 <code>[[HomeObject]]</code> 를 갖는다.</p>
<p><code>super</code> 참조는 내부 슬롯 <code>[[HomeObject]]</code> 를 사용하여 수퍼클래스의 메서드를 참조하므로 ES6 메서드는 <code>super</code> 키워드를 사용할 수 있다.</p>
<h1 id="화살표-함수">화살표 함수</h1>
<p>화살표 함수는 <code>function</code> 키워드 대신 화살표를 사용하여 함수를 정의할 수 있다.</p>
<p>몇 가지 주의할 점이 있다.</p>
<ul>
<li>화살표 함수는 함수 표현식으로 정의해야 한다.</li>
<li>매개변수가 여러 개인 경우 소괄호 <code>()</code> 안에 매개변수를 선언한다.</li>
<li>매개변수가 한 개인 경우 소괄호는 생략 가능하다.</li>
<li>함수 몸체 내부의 문이 값으로 평가될 수 있는 표현식인 문이라면 생략 가능하다.</li>
<li>객체를 반환하기 위해서는 소괄호 <code>()</code> 로 감싸야 한다.</li>
<li>즉시 실행 함수로 사용할 수 있다.</li>
</ul>
<p>화살표 함수의 특징은 다음과 같다.</p>
<ul>
<li><strong>화살표 함수는 인스턴스를 생성할 수 없는 non-constructor 다.</strong></li>
<li><strong>중복된 매개변수 이름을 선언할 수 없다.</strong></li>
<li>화살표 함수는 함수 자체의 <code>this</code>, <code>arguments</code>, <code>super</code>, <code>new.target</code> 바인딩을 갖지 않는다.</li>
</ul>
<h2 id="this">this</h2>
<p>화살표 함수가 일반 함수와 구별되는 가장 큰 특징은 바로 <code>this</code> 다.</p>
<p>화살표 함수는 함수 자체의 <code>this</code> 바인딩이 없고, 상위 스코프의 <code>this</code> 를 그대로 참조한다. </p>
<p>화살표 함수는 다른 함수의 인수로 전달되어 콜백 함수로 사용되는 경우가 많다.</p>
<p>콜백 함수 내부의 <code>this</code> 가 외부 함수의 <code>this</code> 와 다르기 때문에 발생하는 문제, 즉 <strong>“콜백 함수 내부의 <code>this</code> 문제”</strong> 를 해결하기 위해 의도적으로 설계된 것이다.</p>
<pre><code class="language-jsx">class Prefixer {
    constructor(prefix) {
        this.prefix = prefix;
    }

    add(arr) {
        // add 메서드는 배열 arr 을 순회하며 모든 요소에 prefix를 추가한다.
        // 1
        return arr.map(function (item) {
            return this.prefix + item; // 2
            // TypeError 발생
        }
    }
}

const prefixer = new Prefixer(&#39;-webkit-&#39;);
console.log(prefixer.add([&quot;transition&quot;, &quot;user-select&quot;]);</code></pre>
<p>위 예제를 실행할 때 기대하는 결과는 앞에 <code>-webkit-</code> 이 추가된 배열일 것이다. </p>
<p>하지만 <code>TypeError</code> 가 발생한다.</p>
<p>프로토타입 메서드 내부인 <code>1</code> 번 위치에서의 <code>this</code> 는 메서드를 호출한 객체, 즉 <code>prefixer</code> 객체를 가리킨다. 하지만, <code>map()</code> 메서드의 인수로 전달한 콜백 함수의 내부인 <code>2</code> 번 위치에서의 <code>this</code> 는 <code>undefined</code> 를 가리키게 된다. </p>
<blockquote>
<p>22장에서, 일반 함수로서 호출되는 모든 함수 내부의 this 는 전역 객체를 가리킨다고 설명했다. 
그런데 클래스 내부의 모든 코드에는 암묵적으로 <code>strict mode</code> 가 적용되어 있다. 
따라서, 위의 <code>map()</code> 메서드의 콜백 함수에도 <code>strict mode</code> 가 적용된다. 
<code>strict mode</code> 에서 일반 함수로 호출되는 모든 내부 함수의 <code>this</code> 는 <code>undefined</code> 가 바인딩 된다.
<strong>→ 그 결과로, <code>2</code> 번 위치에서의 <code>this</code> 가 <code>undefined</code> 를 가리키게 된다.</strong>
<strong>→</strong> <strong>이 문제가 바로 “콜백 함수 내부에서의 <code>this</code> 문제” 이다.</strong></p>
</blockquote>
<p>화살표 함수는 함수 자체의 <code>this</code> 바인딩을 갖지 않고 화살표 함수 내부에서의 <code>this</code> 는 상위 스코프의 <code>this</code> 를 그대로 참조한다.</p>
<p><strong>→ 이를 <code>lexical this</code> 라 한다. 이는 렉시컬 스코프와 같이 화살표 함수의 this 가 함수가 정의된 위치에 의해 결정된다는 것을 의미한다.</strong></p>
<h2 id="super">super</h2>
<p>화살표 함수는 함수 자체의 <code>super</code> 바인딩을 갖지 않는다.</p>
<p>-&gt;<code>this</code> 와 마찬가지로 화살표 함수 내에서 <code>super</code> 를 참조하면 상위 스코프의 <code>super</code> 를 참조하게 된다.</p>
<pre><code class="language-jsx">class Base {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hi! ${this.name}`;
  }
}

class Derived extends Base {
  // 화살표 함수의 super는 상위 스코프인 constructor의 super를 가리킨다.
  sayHi = () =&gt; `${super.sayHi()}. how are you doing?`;
}

const derived = new Derived(&#39;Son&#39;);
console.log(derived.sayHi());
//Hi! Son. how are you doing?</code></pre>
<h2 id="arguments">arguments</h2>
<p>화살표 함수는 함수 자체의 <code>arguments</code> 바인딩을 갖지 않는다.</p>
<p>→ 화살표 함수 내부에서 <code>arguments</code> 를 참조하면 <code>this</code> 와 마찬가지로 상위 스코프의 <code>arguments</code> 를 참조한다.</p>
<pre><code class="language-jsx">(function() {
  // 화살표 함수 foo의 argumetns는 상위 스코프인 즉시 실행 함수의 arguments를 가리킨다.
  const foo = () =&gt; console.log(arguments);
  foo(3, 4);
})(1, 2);

// 전역에는 arguments 객체가 존재하지 않는다.
const foo = () =&gt; console.log(arguments);
foo(1, 2); // ReferenceError</code></pre>
<h1 id="rest-파라미터">Rest 파라미터</h1>
<p>매개변수의 이름 앞에 <code>...</code> 을 붙여 정의한 매겨변수이다.</p>
<p>Rest 파라미터를 통해 전달된 인수들의 목록을 배열로 전달 받을 수 있다.</p>
<pre><code class="language-jsx">function foo(...rest) {
  // 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터다.
  console.log(rest);
}

foo(1, 2, 3, 4, 5);
// [ 1, 2, 3, 4, 5 ]</code></pre>
<h2 id="주의사항">주의사항</h2>
<ul>
<li>반드시 마지막 파라미터여야 한다.</li>
<li>단 하나만 선언할 수 있다.</li>
<li>Rest 파라미터는 함수 정의 시 선언한 매개변수 개수를 나타내는 함수 객체의 <code>length</code> 프로퍼티에 영향을 주지 않는다.</li>
<li>화살표 함수는 <code>arguments</code> 객체를 갖지 않는다. 반드시 Rest 파라미터를 사용해야 한다.</li>
</ul>
<h1 id="매개변수-기본값">매개변수 기본값</h1>
<p>인수가 전달되지 않은 매개변수의 값은 <code>undefined</code> 이다. </p>
<p>ES6 에서 도입된 매개변수 기본값을 사용하면 이를 방지할 수 있다.</p>
<pre><code class="language-jsx">function sum(x = 0, y = 0) {
  return x + y;
}

console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1</code></pre>
<p>매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우와, <code>undefined</code> 가 전달된 경우에만 유효하다.</p>
<pre><code class="language-jsx">function logName(name = &#39;Son&#39;) {
  console.log(name);
}

logName(); // Son
logName(undefined); // Son
logName(null); // null</code></pre>
<p>Rest 파라미터에는 기본값을 지정할 수 없다.</p>
<pre><code class="language-jsx">function foo(...rest = []) {
    console.log(rest);
}
// SyntaxError</code></pre>
]]></description>
        </item>
    </channel>
</rss>