Jekyll2023-12-29T08:39:37+00:00https://seungwoo321.github.io/feed.xmlDEV LOG{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com백준 <브루트 포스 - N과 M> 순열과 조합 정리2023-12-29T00:00:00+00:002023-12-29T00:00:00+00:00https://seungwoo321.github.io/ps/2023/12/29/math-permutation-and-combination<script type="text/javascript" async="" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<p>백준의 code.plus 문제집 <브루트 포스 - N과 M>을 풀면서 순열과 조합을 이해하고, 문제를 더 쉽게 해결할 수 있게 되서 그 내용을 정리했습니다.</p>
<!--more-->
<h2 id="순열permutation">순열(Permutation)</h2>
<p>순서대로 나열한다는 것을 의미하며 순서가 중요합니다. 예를 들어 1 2 3과 3 2 1은 서로 다른 순열입니다.</p>
<h3 id="예시">예시</h3>
<p>다섯개의 문자열 A B C D E에서 3개의 문자열을 고른다고 가정해봅시다.</p>
<p>첫 번째 문자로 B를 선택했다면 두 번째로 올 수 있는 문자열은 B를 제외한 A C D E 중에서 선택됩니다. 마찬가지로 3번째 올 수 있는 문자열의 개수는 3개입니다.</p>
<p>5부터 1개씩 줄어들면서 곱하는데 3개를 고르는것이기 때문에 3에서 멈춥니다. 연속해서 진행되기 때문에 총 경우의 수는 5 x 4 x 3이 됩니다.</p>
<h3 id="수식">수식</h3>
<p>이를 수식으로 나타내면 n개 중에서 r개를 고르는 순열은 다음과 같이 표현할 수 있습니다.</p>
\[_nP_r = n(n-1)(n-2)\cdots(n-r+1)\]
<p>위의 예시를 이 식에 대입해봅시다.</p>
<p>\(n - r + 1 = 5 - 3 + 1\) 이므로 3까지 곱하면 됩니다.</p>
\[_5P_3 = {5\times4\times3}\]
<p>이번에는 A B C D E에서 5개의 문자를 고른다고 해봅시다.</p>
\[\begin{align*}
_5P_5 &= 5\times4\times3\times2\times1 \\
&= 5!
\end{align*}\]
<p>즉 n개 중에서 n개를 고르는건 n팩토리얼과 같음을 알 수있습니다.</p>
\[_nP_n = n!\]
<p>이를 다시 순열식으로 나타내면 다음과 같이 됩니다.</p>
\[\begin{align*}
_nP_r &= \frac{n(n-1)(n-2)\cdots(n-r+1)(n-r)\cdots1}{(n-r)(n-r-1)\cdots2\times1} \\
&= \frac{n!}{(n-r)!}
\end{align*}\]
<p>위의 예시 \(_5P_2\) 은 이렇게 정리할 수 있습니다.</p>
\[\begin{align*}
_5P_2 = \frac{5!}{(5-2)!}
\end{align*}\]
<h3 id="특징">특징</h3>
<p>위에서 정리한 식을 사용해서 \(_nP_0\)과 \(0!\)의 값이 무엇인지 살펴보겠습니다.</p>
\[_nP_r = \frac{n!}{(n-r)!}\]
<p>\(_nP_n\)을 위의 식에 대입해보면 0!은 1이 됩니다.</p>
\[\begin{align*}
_nP_n &= \frac{n!}{(n-n)!} \\
&= \frac{n!}{0!} \\
&= \frac{n!}{1} \\
&= n!
\end{align*}\]
<p>마찬가지로 \(_nP_0\)도 대입해보니 1이라고 정리할 수 있습니다.</p>
\[\begin{align*}
_nP_0 &= \frac{n!}{n!} \\
&= 1
\end{align*}\]
<h3 id="중복순열-repetition-permutation">중복순열 (Repetition Permutation)</h3>
<p>순서대로 나열하는데 중복을 허용하는 경우입니다. 주어진 \(n\)개의 원소중에서 \(r\)개를 중복을 허용하여 선택하고 나열하는 경우의 수는 \(n^r\)입니다.</p>
<p>중복순열을 구하는 식은 다음과 같습니다.</p>
\[_n\hat{P}_r = n^r\]
<h2 id="조합combination">조합(Combination)</h2>
<p>순열은 3개를 뽑아서 3개를 줄세우는 겁니다. 여기서 3개를 뽑는것만을 조합이라고 합니다.</p>
<h3 id="예시-1">예시</h3>
<p>A B C D E라는 문자가 있는데 여기서 3개를 고르는것입니다. 순서와 상관이 없습니다. 다음 세개는 모두 동일한 순열입니다.</p>
<p>A C D<br />
A D C<br />
C D A</p>
<h3 id="수식-1">수식</h3>
<p>r개를 뽑기만 하면 조합입니다. 그러면 조합을 한 다음에 연달아 r개를 줄세우면 순열이 됩니다.</p>
\[\text{(조합)}\times{r!} = \text{(순열)}\]
<p>n개 중에서 r을 고르기만 한다고 하여 Combination의 C를 사용해 \(_nC_r\)으로 표현합니다.</p>
\[_nC_r\times{r!} = _nP_r\]
<p>그러면 반대로 조합식은 순열식을 사용해서 이렇게 나타낼 수 있습니다.</p>
\[_nC_r = \frac{_nP_r}{r!}\]
<p>또한, 순열식을 사용해서 이렇게도 쓸 수있습니다.</p>
\[\begin{align*}
_nC_r &= \frac{_nP_r}{r!} \\
&= \frac{n!}{(n-r)!r!}
\end{align*}\]
<h3 id="특징-1">특징</h3>
<p>순열과 마찬가지로 특징을 살펴보겠습니다. \(_nC_0\)을 대입해보면 1이 됩니다.</p>
\[\begin{align*}
_nC_0 &= \frac{n!}{n!0!} \\
&= 0! \\
&= 1
\end{align*}\]
<p>똑같이 대입해보면 다음과 같이 정리할 수 있습니다.</p>
\[\begin{align*}
_nC_n = 1 \\
_nC_1 = n \\
\end{align*}\]
<p>마지막으로 다음식을 살펴보겠습니다. 이 식은 \(_7C_4\)는 \(_7C_3\)과 같다는게 됩니다.</p>
\[\begin{align*}
_nC_r &= _nC_n-_r \\
&= \frac{n!}{(n-(n-r))!(n-r)!} \\
&= \frac{n!}{(n-n+r)!(n-r)!} \\
&= \frac{n!}{r!(n-r)!}
\end{align*}\]
<h3 id="중복조합-repetition-combination">중복조합 (Repetition Combination)</h3>
<p>주어진 요소들을 선택하는데 중복을 허용합니다. n개 중에서 r개를 선택하는 중복조합은 \(n+r-1\)개 중에서 r개를 구하는것과 같습니다.</p>
<p>중복조합을 구하는 식은 다음과 같습니다.</p>
\[\begin{align*}
_n\hat{C}_r = _n+_r-_1C_r &= \frac{(n+r-1)!}{((n+r-1)-r)!r!} \\
&= \frac{(n+r-1)!}{(n-1)!r!}
\end{align*}\]
<h2 id="백준-문제집-브루트-포스---n과-m-풀어보기">백준 문제집 <브루트 포스 - N과 M> 풀어보기</h2>
<p><브루투 포스 - N과 M>은 “코딩 테스트 준비 - 기초” 제목으로 분류되어 있으며 순열과 조합을 활용한 9개의 문제로 구성된 완전탐색(브루트 포스) 문제집입니다. 이 글을 읽고 순열과 조합이 명확하게 이해되었다면 문제 풀이가 정말 쉬워질겁니다.</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/15649">N과 M (1)</a> : 수열을 제공하지 않는 기본적인 순열 문제입니다. 예제 출력2를 보면 1 2와 2 1를 각각 출력하는 순서가 중요한 순열입니다. 제공되는 수열이 없으니 주어진 숫자와 반복문을 활용해서 수열을 만들고 순열을 구해서 출력하면 됩니다.</li>
<li><a href="https://www.acmicpc.net/problem/15650">N과 M (2)</a> : 수열을 제공하지 않는 기본적인 조합 문제입니다. 예제 출력2를 보면 1 2를 출력하고 2 1은 없습니다. 1 2와 2 1은 중복으로 인지하는 조합 문제입니다. 제공되는 수열이 없으니 주어진 숫자와 반복문을 활용해서 수열을 만들고 순열을 구해서 출력하면 됩니다.</li>
<li><a href="https://www.acmicpc.net/problem/15651">N과 M (3)</a> : 수열을 제공하지 않는 기본적인 중복 순열 문제입니다. 예제 출력2를 보면 1 2와 2 1을 각각 출력하는 순서가 중요한 순열입니다. 제공되는 수열이 없으니 주어진 숫자와 반복문을 활용해서 수열을 만들고 순열을 구해서 출력하면 됩니다.</li>
<li><a href="https://www.acmicpc.net/problem/15652">N과 M (4)</a> : 수열을 제공하지 않는 기본적인 중복 조합 문제입니다. 예제 출력2를 보면 1 2를 출력하고 2 1은 없습니다. 1 2와 2 1은 중복으로 인지하는 조합문제입니다. 제공되는 제공되는 수열이 없으니 주어진 숫자와 반복문을 활용해서 수열을 만들고 순열을 구해서 출력하면 됩니다.</li>
<li><a href="https://www.acmicpc.net/problem/15654">N과 M (5)</a> : 수열을 제공하는 순열 문제입니다. 예제 출력2를 보면 1 7과 7 1을 각각 출력하는 순서가 중요한 순열입니다.</li>
<li><a href="https://www.acmicpc.net/problem/15655">N과 M (6)</a> : 수열을 제공하는 기본적인 조합 문제입니다. 예제 출력2를 보면 1 7은 출력하고 7 1은 없습니다. 1 7과 7 1은 중복으로 인지하는 조합문제입니다.</li>
<li><a href="https://www.acmicpc.net/problem/15656">N과 M (7)</a> : 수열을 제공하는 기본적인 중복 순열 문제입니다. 예제 출력2를 보면 1 7과 7 1을 각각 출력하는 순서가 중요한 순열 입니다.</li>
<li><a href="https://www.acmicpc.net/problem/15657">N과 M (8)</a> : 수열을 제공하는 기본적인 중복 조합 문제입니다. 예제 출력2를 보면 1 7은 출력하고 7 1은 없습니다. 1 7과 7 1은 중복으로 인지하는 조합문제입니다.</li>
<li><a href="https://www.acmicpc.net/problem/18290">NM과 K (1)</a> : N x M인 2차원 배열을 제공합니다. 선택한 칸의 인전합 칸은 선택하지 않고 K개를 선택 합니다. 선택한 숫자 K개의 합이 가장 큰 값을 출력합니다. 합을 구하는데 순서는 중요하지 않으니 조합 문제입니다.</li>
</ul>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=458841FPRgo">유튜브 - EBS[수학영역] 미적분과 통계 기본 - 순열과 조합을 정리하면? (고1개념)</a></li>
<li><a href="https://www.acmicpc.net/workbook/view/9371">백준 문제집 <브루트 포스 - N과 M></a></li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com백준의 code.plus 문제집 <브루트 포스 - N과 M>을 풀면서 순열과 조합을 이해하고, 문제를 더 쉽게 해결할 수 있게 되서 그 내용을 정리했습니다.여백 상쇄 (margin collapsing) 이해하기2023-09-01T00:00:00+00:002023-09-01T00:00:00+00:00https://seungwoo321.github.io/css/2023/09/01/css<p>프론트엔드 개발자라면 알고 있어야하는 여백 상쇄 (margin collapsing) 이해하기</p>
<!--more-->
<h2 id="여백-상쇄">여백 상쇄</h2>
<p>여러 블록 요소가 나란히 있을 때 상단과 하단의 바깥 여백은 제일 큰 여백의 크기를 가진 단일 여백으로 결합 됩니다. 이를 여백 상쇄라고 합니다.</p>
<blockquote>
<p>단, 플로팅 요소와 절대 위치를 지정한 요소의 여백은 상쇄되지 않습니다.</p>
</blockquote>
<h2 id="알아야-하는-이유">알아야 하는 이유</h2>
<p>바깥쪽 여백 (margin)을 설정할 때 예기치 못한 결과가 발생할 수 있습니다.</p>
<ul>
<li>각 요소에 바깥쪽 여백을 설정했는데 인접한 두 요소가 바깥쪽 여백 하나를 공유합니다.</li>
<li>부모요소 <code class="language-plaintext highlighter-rouge"><section></code>이 갑자기 <code class="language-plaintext highlighter-rouge"><h1></code> 과 같은 자식 요소의 바깥쪽 여백을 차지합니다.</li>
</ul>
<p>이는 여백 상쇄 현상과 관련 있습니다. 기본적인 사례는 다음 세 가지 입니다.</p>
<h2 id="기본-사례-3가지">기본 사례 3가지</h2>
<h3 id="1-인접-형제">1. 인접 형제</h3>
<p>인접 형제 요소간의 바깥 여백은 더 큰 여백으로 상쇄됩니다.</p>
<p>첫 번째 요소는 바깥 여백이 10px입니다.
<img src="/assets/images/posts/2023/09/01/001.png" alt="margin 10px의 div 요소" /></p>
<p>두 번째 요소는 바깥 여백이 5px 입니다.
<img src="/assets/images/posts/2023/09/01/002.png" alt="margin 5px의 div 요소" /></p>
<p>Demo: <a href="https://jsfiddle.net/seungwoo321/L1u5bc0m/">https://jsfiddle.net/seungwoo321/L1u5bc0m/</a></p>
<h3 id="2-바깥쪽-여백이-설정된-자식-요소를-가진-부모-요소">2. 바깥쪽 여백이 설정된 자식 요소를 가진 부모 요소</h3>
<p>첫째나 마지막의 자식 요소에 바깥쪽 여백이 설정된 경우입니다. 이 경우 부모 요소의 바깥쪽 여백은 자식 요소의 바깥쪽 여백과 함께 상쇄 됩니다. 마찬가지로 크기가 큰 바깥쪽 여백이 이기고 부모 요소에 적용됩니다.</p>
<p>예시 코드 입니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"box"</span> <span class="na">id=</span><span class="s">"box1"</span><span class="nt">></span>
box1
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"box"</span> <span class="na">id=</span><span class="s">"box2"</span><span class="nt">></span>
box2
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.container</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="m">#1dfe2e</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.box</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="m">#e794a9</span><span class="p">;</span>
<span class="p">}</span>
<span class="nf">#box1</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nf">#box2</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">50px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>예시 코드를 실행한 화면입니다.</p>
<p>이전 예제에서 살펴본 div 요소 두 개에 container 클래스를 가진 부모 요소를 추가했습니다.</p>
<p><img src="/assets/images/posts/2023/09/01/003.png" alt="div 요소 두 개에 container 클래스를 가진 부모 요소를 추가" /></p>
<p>첫 번째 자식 요소인 box1은 부모보다 바깥쪽 여백이 작아서 부모의 바깥쪽 여백으로 상쇄되었습니다.</p>
<p><img src="/assets/images/posts/2023/09/01/004.png" alt="첫 번째나 마지막 자식 요소의 여백 보다 부모의 여백이 큰 경우" /></p>
<p>두 번째 자식 요소는 부모보다 바깥쪽 여백이 크기 때문에 부모의 바깥쪽 여백이 상쇄되어서 아래쪽 바깥 여백은 자식요소의 여백으로 상쇄되었습니다.</p>
<p><img src="/assets/images/posts/2023/09/01/005.png" alt="첫 번째나 마지막 자식 요소의 여백이 부모보다 큰 경우" /></p>
<p>부모 요소와 인접한 div요소를 추가하면 확실하게 알 수 있습니다..</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"box"</span> <span class="na">id=</span><span class="s">"box3"</span><span class="nt">></span>
box3
<span class="nt"></div></span>
</code></pre></div></div>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">#box3</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="/assets/images/posts/2023/09/01/006.png" alt="box3 클래스를 가진 블록 요소를 부모 요소와 인접하게 추가" /></p>
<p>box3 클래스를 가진 블록 요소를 부모 요소와 인접하게 그려보면 box2의 바깥쪽 여백만 적용되고 부모 요소의 아랫쪽 바깥 여백 (margin-bottom) 은 상쇄된 것을 볼 수 있습니다.</p>
<p>부모 요소에 안쪽 여백, 인라인 콘텐츠 (자식 요소는 제외) 또는 테두리가 설정된 경우에는 이 동작이 발생하지 않고 자식 요소의 바깥쪽 여백이 콘텐츠를 감싸는 부모 요소에 추가됩니다.</p>
<p>부모 요소에 안쪽 여백 (padding)이 추가되면 바깥 여백은 상쇄되지 않습니다.</p>
<p><img src="/assets/images/posts/2023/09/01/007.png" alt="부모 요소에 안쪽 여백 (padding) 추가" /></p>
<p>부모 요소에 인라인 컨텐츠를 추가하면 바깥 여백은 상쇄되지 않습니다.
<img src="/assets/images/posts/2023/09/01/008.png" alt="부모 요소에 인라인 컨텐츠 추가" /></p>
<p>부모 요소에 테두리가 설정된 경우 바깥 여백은 상쇄되지 않습니다.
<img src="/assets/images/posts/2023/09/01/009.png" alt="부모 요소에 테두리가 설정된 경우 바깥 여백은 상쇄되지 않습니다." /></p>
<p>demo: <a href="https://jsfiddle.net/seungwoo321/av3jrmkL/">https://jsfiddle.net/seungwoo321/av3jrmkL/</a></p>
<h3 id="3-빈-블록">3. 빈 블록</h3>
<p>인라인 콘텐츠, 안쪽 여백, 테두리와 높이(height, min-height, max-height) 가 지정되지 않은 블록 요소의 경우 상단과 하단의 바깥 여백은 하나의 단일한 바깥 여백으로 병합됩니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"></div></span>
</code></pre></div></div>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.container</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="/assets/images/posts/2023/09/01/010.png" alt="빈 블록일 때" /></p>
<p>요소의 margin-top과 margin-bottom이 단일 바깥 여백으로 병합되었습니다.</p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_box_model/Mastering_margin_collapsing">MDN 문서 - 여백 상쇄 정복</a></li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com프론트엔드 개발자라면 알고 있어야하는 여백 상쇄 (margin collapsing) 이해하기러닝 자바스크립트 - ES6 위크맵/위크셋/이터레이터/제너레이터2023-07-06T00:00:00+00:002023-07-06T00:00:00+00:00https://seungwoo321.github.io/javascript/2023/07/06/es6-iterator-generator<p>‘러닝 자바스크립트’를 읽었다. 책만 본다고 개발 실력이 늘지는 않지만 코딩만 한다고 개발 실력이 느는 것 또한 아님을 새삼 느꼈다. ES6를 주로 ES5나 ES3에서 이관된 문법이나 개념에 대해서만 알고 있었던 거다. 그래서 ES6에서도 익숙하지 않은 개념들에 대해서 정리했다..</p>
<!--more-->
<h2 id="위크맵-weakmap">위크맵 (WeakMap)</h2>
<p>ES6 이전에는 키와 값을 연결할 때 객체를 주로 사용해야 했다. 이제는 맵(Map)이 있기 때문에 맵을 사용하면 된다. 그런데 위크맵이란 게 또 있다. 위크맵이 맵과 다른 점은 다음과 같다</p>
<ul>
<li>키는 반드시 객체여야 한다.</li>
<li>키가 가비지 컬렉션 포함될 수 있다.</li>
<li>이터러블이 아니다</li>
<li>clear() 메서드가 없다</li>
</ul>
<p>맵의 키는 맵이 존재하는 한 메모리에 계속 유지한다. 위크맵은 그렇지 않다. (책의 예제를 그대로 가져와서 좀 더 재미있고 이해하기 쉬운 내용으로 변경했다)</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">SalaryMan</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">annualPay</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">WeakMap</span><span class="p">();</span>
<span class="k">return</span> <span class="kd">class</span> <span class="p">{</span>
<span class="nx">setAnnualPay</span> <span class="p">(</span><span class="nx">pay</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">annualPay</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">pay</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">getAnnualPay</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">annualPay</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})();</span>
<span class="kd">const</span> <span class="nx">memberOfn</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SalaryMan</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">memberOfm</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SalaryMan</span><span class="p">();</span>
<span class="nx">memberOfn</span><span class="p">.</span><span class="nx">setAnnualPay</span><span class="p">(</span><span class="mi">6000</span><span class="p">);</span>
<span class="nx">memberOfm</span><span class="p">.</span><span class="nx">setAnnualPay</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span>
<span class="nx">memberOfn</span><span class="p">.</span><span class="nx">getAnnualPay</span><span class="p">()</span> <span class="c1">// 6000</span>
<span class="nx">memberOfm</span><span class="p">.</span><span class="nx">getAnnualPay</span><span class="p">()</span> <span class="c1">// 3000</span>
</code></pre></div></div>
<p>회사원 생성자 함수는 연봉정보를 저장하는 위크맵을 생성하고 IIFE 함수로 감싼다. 그리고 외부에서는 setPay, getPay 메서드로만 연봉 정보에 접근할 수 있는 클래스를 반환한다.</p>
<p>연봉정보를 저장할 때 위크맵을 사용했기 때문에 N사 직원의 연봉과 M사 직원의 연봉정보는 가비지 컬렉션에 포함돼서 더 이상 참조하지 않게 되면 메모리에서 해제된다. 여기서 위크맵 대신 맵을 사용했다면 어떻게 될까. N사 직원과 M사 직원이 더 이상 참조되지 않아도 연봉정보는 계속 메모리에 남아 있게 된다. delete 메서드를 사용해서 명시적으로 제거가 필요하다.</p>
<p>따라서 위크맵과 맵의 선택은 메모리 관리와 객체 생명주기에 따라 결정되어야 한다.</p>
<h2 id="위크셋-weakset">위크셋 (WeakSet)</h2>
<p>셋(Set)은 중복을 허용하지 않는 데이터 집합이다. 그리고 위크셋(WeakSet)이 셋과 다른 점은 다음과 같다</p>
<ul>
<li>위크셋은 객체만 포함할 수 있다.</li>
<li>이 객체들은 가비지 컬렉션의 대상이 된다.</li>
<li>위크맵과 마찬가지로 이터러블이 아니다</li>
</ul>
<p>위크셋의 용도는 매우 적다고 한다. 주어진 객체가 셋 안에 존재하는지 아닌지를 알아보는 것뿐이다. 따라서 나열이 필요하면 셋을 그렇지 않다면 위크셋을 사용하면 되겠다.</p>
<h2 id="이터레이터">이터레이터</h2>
<p>이터레이터는 제너레이터와 함께 ES6에 도입된 매우 중요한 개념이다. 처음 도입된 개념이라서 이전의 자바스크립트에만 익숙하다면 생소할 수 있다. 이번에도 책의 예제 코드 그대로에 내용만 익숙한 애국가 1절로 변경해 보았다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 애국가 1절</span>
<span class="kd">const</span> <span class="nx">nationalAnthem</span> <span class="o">=</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">동해물과 백두산이 마르고 닳도록</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">하느님이 보우 - 하사 우리나라 만세</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">무 -궁화 삼 -천리 화려강- 산</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">대한사람 대한-으로 길이 보전하세</span><span class="dl">"</span>
<span class="p">];</span>
</code></pre></div></div>
<p>다음 코드는 한 줄씩 콘솔에 실행해 보자.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">it</span> <span class="o">=</span> <span class="nx">nationalAnthem</span><span class="p">.</span><span class="nx">values</span><span class="p">();</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '동해물과 백두산이 마르고 닳도록', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '하느님이 보우 - 하사 우리나라 만세', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '무 -궁화 삼 -천리 화려강- 산', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '대한사람 대한-으로 길이 보전하세', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: undefined, done: true}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: undefined, done: true}</span>
</code></pre></div></div>
<p>배열에 <code class="language-plaintext highlighter-rouge">values</code>메서드를 사용해서 이터레이터를 만들 수 있다. 그리고 next() 메서드를 호출하면 노래 한 구절을 부른 게 된다. 이 메서드는 지금 부른 노래 가사를 나타내는 <code class="language-plaintext highlighter-rouge">value</code>프로퍼티와 마지막까지 완창했는지를 나타내는 <code class="language-plaintext highlighter-rouge">done</code>프로퍼티를 가지는 객체를 반환한다. 더 진행할 것이 없으면 <code class="language-plaintext highlighter-rouge">done</code>은 <code class="language-plaintext highlighter-rouge">true</code> 가 되고 <code class="language-plaintext highlighter-rouge">value</code>는 <code class="language-plaintext highlighter-rouge">undefined</code>가 된다. 중요한 점은 여기서 끝이 아니라 <code class="language-plaintext highlighter-rouge">next</code>를 계속 호출할 수 있다는거다.</p>
<p>인덱스 변수를 사용하지 않고 배열을 순회하는 <code class="language-plaintext highlighter-rouge">for ... of</code>의 원리가 이터레이터다. <code class="language-plaintext highlighter-rouge">while</code> 루프를 사용해서 이터레이터를 흉내 내 볼 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">it</span> <span class="o">=</span> <span class="nx">nationalAnthem</span><span class="p">.</span><span class="nx">values</span><span class="p">();</span>
<span class="kd">let</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span>
<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">current</span><span class="p">.</span><span class="nx">done</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">current</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
<span class="nx">current</span> <span class="o">=</span> <span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 실행 결과
동해 물과 백두산이 마르고 닳도록
하느님이 보우 - 하사 우리나라 만세
무 -궁화 삼 -천리 화려강- 산
대한 사람 대한-으로 길이 보전하세
{value: undefined, done: true}
</code></pre></div></div>
<p>이터레이터는 모두 독립적이다. 새 이터레이터를 만들 때마다 처음에서 시작한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">it1</span> <span class="o">=</span> <span class="nx">nationalAnthem</span><span class="p">.</span><span class="nx">values</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">it2</span> <span class="o">=</span> <span class="nx">nationalAnthem</span><span class="p">.</span><span class="nx">values</span><span class="p">();</span>
<span class="c1">// it1로 애국가를 2구절 부른다.</span>
<span class="nx">it1</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '동해물과 백두산이 마르고 닳도록', done: false}</span>
<span class="nx">it1</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '하느님이 보우 - 하사 우리나라 만세', done: false}</span>
<span class="c1">// it2로 애국가를 1구절 부른다.</span>
<span class="nx">it2</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '동해물과 백두산이 마르고 닳도록', done: false}</span>
<span class="c1">// it1로 애국가를 1구절을 더 부른다.</span>
<span class="nx">it1</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '무 -궁화 삼 -천리 화려강- 산', done: false}</span>
</code></pre></div></div>
<h3 id="이터레이션-프로토콜">이터레이션 프로토콜</h3>
<p>이터레이션 프로토콜(Iteration Protocol)은 모든 객체를 이터러블 객체로 바꿀 수 있다. <code class="language-plaintext highlighter-rouge">Symbol.iterator</code> 메서드를 객체에 구현하여 이터레이터를 반환하면 된다. 책에서 소개하는 피보나치 수열을 생성하는 예제를 보면 쉽게 이해할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">FibonacciSequence</span> <span class="p">{</span>
<span class="p">[</span><span class="nb">Symbol</span><span class="p">.</span><span class="nx">iterator</span><span class="p">]()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">b</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">return</span> <span class="p">{</span>
<span class="nx">next</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">ref</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nx">b</span><span class="p">,</span>
<span class="na">done</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">};</span>
<span class="nx">b</span> <span class="o">+=</span> <span class="nx">a</span><span class="p">;</span>
<span class="nx">a</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">ref</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>이런 식으로 이터레이션 프로토콜을 활용하면 임의의 객체를 이터러블 객체로 만들 수 있다. 이제 <code class="language-plaintext highlighter-rouge">for...of</code> 루프나 다른 이터레이션 기능을 사용하여 객체를 순회할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fib</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FibonacciSequence</span><span class="p">();</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">n</span> <span class="k">of</span> <span class="nx">fib</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">++</span><span class="nx">i</span> <span class="o">></span> <span class="mi">9</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 실행 결과
1
1
2
3
5
8
13
21
34
55
</code></pre></div></div>
<h2 id="제너레이터">제너레이터</h2>
<p>제너레이터는 이터레이터를 사용해서 자신의 실행을 제어하는 함수다. 일반 함수와 달리 호출 즉시 실행되지 않고 이터레이터를 반환한다. 그리고 언제든 호출자에게 제어권을 넘길 수 있다.</p>
<p>책의 예제는 무지개색을 반환하는데 대신 추천 책 리스트를 담아봤다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span><span class="o">*</span> <span class="nx">jachungBook</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">나는 4시간만 일한다</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">생각이 돈이 되는 순간</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">타이탄의 도구들 (블랙 에디션)</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">욕망의 진화</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">클루지 (kluge)</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">뇌, 욕망의 비밀을 풀다</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">지능의 역설</span><span class="dl">'</span><span class="p">;</span>
<span class="k">yield</span> <span class="dl">'</span><span class="s1">정리하는 뇌</span><span class="dl">'</span>
<span class="p">}</span>
</code></pre></div></div>
<p>이전에 이터레이터 파트에서 살펴본 것처럼 우선 이터레이터 객체를 얻는다. 제너레이터를 호출하면 이터레이터 객체를 얻을 수 있다. 그 다음 <code class="language-plaintext highlighter-rouge">next</code>를 호출해서 함수를 단계별로 진행할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">it</span> <span class="o">=</span> <span class="nx">jachungBook</span><span class="p">()</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '나는 4시간만 일한다', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '생각이 돈이 되는 순간', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '타이탄의 도구들 (블랙 에디션)', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '욕망의 진화', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '클루지 (kluge)', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '뇌, 욕망의 비밀을 풀다', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '지능의 역설', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '정리하는 뇌', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: undefined, done: true}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">jachungBook</code> 제너레이터는 이터레이터를 반환하므로 <code class="language-plaintext highlighter-rouge">for … of</code> 루프에서 쓸 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">book</span> <span class="k">of</span> <span class="nx">jachungBook</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">book</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 실행 결과
나는 4시간만 일한다
생각이 돈이 되는 순간
타이탄의 도구들 (블랙 에디션)
욕망의 진화
클루지 (kluge)
뇌, 욕망의 비밀을 풀다
지능의 역설
정리하는 뇌
</code></pre></div></div>
<h3 id="yield-표현식과-양방향-통신">yield 표현식과 양방향 통신</h3>
<p>제너레이터와 호출자는 양방향 통신이 가능하다. 통신은 <code class="language-plaintext highlighter-rouge">yield</code> 표현식을 통해 이뤄진다. 표현식은 값으로 평가되고 <code class="language-plaintext highlighter-rouge">yield</code>는 표현식이므로 반드시 어떤 값으로 평가된다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span><span class="o">*</span> <span class="nx">interrogate</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">book</span> <span class="o">=</span> <span class="k">yield</span> <span class="dl">"</span><span class="s2">지금 읽고 있는 책의 제목은 무엇인가요 ?</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">yield</span> <span class="dl">"</span><span class="s2">몇 페이지까지 읽었나요 ?</span><span class="dl">"</span>
<span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">book</span><span class="p">}</span><span class="s2"> 책을 </span><span class="p">${</span><span class="nx">page</span><span class="p">}</span><span class="s2"> 페이지까지 읽으셨군요`</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">yield</code> 표현식의 값은 호출자가 이터레이터에서 <code class="language-plaintext highlighter-rouge">next</code>를 호출할 때 제공한 매개변수다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">it</span> <span class="o">=</span> <span class="nx">interrogate</span><span class="p">();</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span> <span class="c1">// {value: '지금 읽고 있는 책의 제목은 무엇인가요 ?', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="dl">'</span><span class="s1">역행자</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// {value: '몇 페이지까지 읽었나요 ?', done: false}</span>
<span class="nx">it</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="mi">300</span><span class="p">)</span> <span class="c1">// {value: '역행자 책을 300 페이지까지 읽으셨군요', done: true}</span>
</code></pre></div></div>
<p>위 예제와 같이 제너레이터를 활용하면 호출자가 함수의 실행을 제어할 수 있다. 그런데 <code class="language-plaintext highlighter-rouge">yield</code>문은 제너레이터의 마지막 문이더라도 제너레이터를 끝내지 않는다. 그래서 <code class="language-plaintext highlighter-rouge">return</code> 문을 사용하면 그 위치와 관계없이 <code class="language-plaintext highlighter-rouge">done</code> 은 <code class="language-plaintext highlighter-rouge">true</code> 가 되고 <code class="language-plaintext highlighter-rouge">value</code> 프로퍼티는 <code class="language-plaintext highlighter-rouge">return</code> 이 반환하는 값이 된다.</p>
<p>하지만 <code class="language-plaintext highlighter-rouge">for…of</code> 루프에서 사용하면 마지막 값이 절대 출력되지 않기 때문에 주의해야한다. 제너레이터의 첫 예제 코드에서 마지막 책을 <code class="language-plaintext highlighter-rouge">return</code> 문으로 변경하고 실행해보면 “정리하는 뇌”는 출력되지 않는 것을 볼 수 있다.</p>
<p>또한 많이 사용하는 <code class="language-plaintext highlighter-rouge">async/await</code>는 실제 내부적으로 프로미스와 제너레이터를 사용하여 구현된다.</p>
<h3 id="러닝-자바스크립트-책-후기">‘러닝 자바스크립트’ 책 후기</h3>
<p>아주 깊게 다루지는 않지만 ES6를 전반적으로 다룬다는 측면에서 많이 도움이 되었다. 애매하게 알고 있었던 개념들에 대해서 정리가 되었고 특히 더 깊은 지식에 대해서는 추천 자료와 링크들을 제공하고 있다. 이 부분은 모아서 한번 정리해 볼 생각이다.</p>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com‘러닝 자바스크립트’를 읽었다. 책만 본다고 개발 실력이 늘지는 않지만 코딩만 한다고 개발 실력이 느는 것 또한 아님을 새삼 느꼈다. ES6를 주로 ES5나 ES3에서 이관된 문법이나 개념에 대해서만 알고 있었던 거다. 그래서 ES6에서도 익숙하지 않은 개념들에 대해서 정리했다..영원히 변하지 않을 자바스크립트의 초석 개념 3가지2023-07-03T00:00:00+00:002023-07-03T00:00:00+00:00https://seungwoo321.github.io/javascript/2023/07/03/javascript-core-concepts<p>ECMAScript 3판을 다루는 ‘자바스크립트를 깨우치다’란 책을 읽었다. 그리고 이 책에서 말하는 영원히 변하지 않을 자바스크립트 초석 개념이라고 생각하는 3가지에 대해서 정리했다.</p>
<!--more-->
<h2 id="this는-함수가-호출되는-네-가지-방식에-의해서-결정된다">this는 함수가 호출되는 네 가지 방식에 의해서 결정된다</h2>
<p><code class="language-plaintext highlighter-rouge">this</code>의 값은 함수가 호출될 때의 컨텍스트에 따라 결정된다. 호출되는 시점에 결정되기 때문에 동적이라고 할 수 있다. 함수가 호출되는 방식 네가지 (함수, 메서드, 생성자, 간접)만 기억한다면 <code class="language-plaintext highlighter-rouge">this</code>를 아주 쉽게 이해 할 수 있다.</p>
<h3 id="함수-호출">함수 호출</h3>
<p>일반적인 함수로 호출되면 전역 객체 (헤드 객체)를 참조한다. 브라우저에서는 <code class="language-plaintext highlighter-rouge">window</code> 객체이고 Node.js에서는 <code class="language-plaintext highlighter-rouge">global</code>객체다. 엄격모드(strict mode)에서는 <code class="language-plaintext highlighter-rouge">undefined</code>가 된다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">solution</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="메서드-호출">메서드 호출</h3>
<p>메서드로 호출될 때는 해당 메서드를 속성으로 가진 객체를 참조한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">book</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">자바스크립트를 깨우치다</span><span class="dl">'</span><span class="p">,</span>
<span class="na">read</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`'</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="s2">' 책을 읽었다.`</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">read</span><span class="p">())</span> <span class="c1">// '자바스크립트를 깨우치다' 책을 읽었다.</span>
</code></pre></div></div>
<h3 id="생성자-호출">생성자 호출</h3>
<p><code class="language-plaintext highlighter-rouge">new</code> 키워드와 함께 생성자로 호출되면 생성된 인스턴스 객체를 참조한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">Book</span> <span class="p">(</span><span class="nx">title</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">title</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Book</span><span class="p">(</span><span class="dl">'</span><span class="s1">자바스크립트를 깨우치다</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="p">)</span> <span class="c1">// 자바스크립트를 깨우치다</span>
</code></pre></div></div>
<h3 id="간접-호출">간접 호출</h3>
<p><code class="language-plaintext highlighter-rouge">call()</code>, <code class="language-plaintext highlighter-rouge">apply()</code>, <code class="language-plaintext highlighter-rouge">bind()</code> 메서드를 사용하여 간접적으로 호출할 때는 첫 번째 인자로 전달된 객체를 <code class="language-plaintext highlighter-rouge">this</code>로 사용한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">Book</span> <span class="p">(</span><span class="nx">title</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">title</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">read</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`'</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="s2">' 책을 읽었다.`</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">book</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Book</span><span class="p">(</span><span class="dl">'</span><span class="s1">자바스크립트를 깨우치다</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">read</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">book</span><span class="p">)</span> <span class="c1">// '자바스크립트를 깨우치다' 책을 읽었다.</span>
</code></pre></div></div>
<h3 id="그리고-es6-화살표-함수">그리고 ES6 화살표 함수</h3>
<p>ES3에서 <code class="language-plaintext highlighter-rouge">this</code>는 함수 호출 방식에 따라서 생성자 호출, 간접 호출, 메서드 호출, 함수 호출(자유함수)에 따라 <code class="language-plaintext highlighter-rouge">this</code> 가 결정되었다. 그러나 ES6 이후 도입된 화살표 함수는 위 규칙을 완전히 무시하고 주변 스코프를 상속 받는다.</p>
<h2 id="스코프는-함수를-정의할-때-결정된다">스코프는 함수를 정의할 때 결정된다</h2>
<p>말 그대로 어디서 호출하였는지와 상관없이 코드를 작성할 때 정해진다. 이를 정적 스코프 또는 렉시컬 스코프라고 한다.</p>
<h3 id="자바스크립트의-네-가지-스코프">자바스크립트의 네 가지 스코프</h3>
<p>ES3에서는 크게 전역 스코프(global), 함수 스코프(local), eval 스코프 3가지가 있었다. 그리고 ES6에서부터 <code class="language-plaintext highlighter-rouge">let</code>과 <code class="language-plaintext highlighter-rouge">const</code>로 선언된 변수가 가지는 블록 스코프가 도입되었다.</p>
<h3 id="스코프-체인">스코프 체인</h3>
<p>함수 스코프, 블록 스코프, 전역스 코프는 스코프 체인으로 서로 연결 되어 있다. 그래서 변수를 찾을 때 현재 스코프에서 찾지 못하면 상위 스코프로 이동하여 변수를 찾는다.</p>
<p>eval 스코프는 자신과 자신을 감싸는 스코프에는 접근 할 수 있다. 하지만 스코프 체인을 형성하지는 않기 때문에 독립적이다. (그런데 알아도 쓸데는 없다)</p>
<h3 id="클로저">클로저</h3>
<p>이미 반환된 함수가 스코프 체인에 의해서 상위 스코프의 변수에 접근할 수 있는 것을 말한다. 정적 스코프, 즉 코드가 작성될 때 정의된 접근 범위를 벗어나서도 스코프 체인에 의해서 이전 부모 함수의 스코프에 접근할 수 있게 되는 것이다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">readBook</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">자바스크립트를 깨우치다</span><span class="dl">'</span>
<span class="k">return</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`'</span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">' 를 읽었다.`</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">reading</span> <span class="o">=</span> <span class="nx">readBook</span><span class="p">()</span> <span class="c1">// function () { ... } 내부 익명 함수가 반환된다.</span>
<span class="nx">reading</span><span class="p">()</span> <span class="c1">// 외부에서 내부 익명 함수를 실행해도 상위 스코프의 title 변수에 접근해서. '자바스크립트를 깨우치다' 를 읽었다. 가 출력된다.</span>
</code></pre></div></div>
<h2 id="프로토타입">프로토타입</h2>
<ul>
<li>자바스크립트에서 Function 객체로부터 생성된 모든 함수 객체 또는 인스턴스는 prototype 속성을 가진다.</li>
<li>prototype 속성은 상위 객체에 접근하기 위한 체인을 형성하는 역할을 한다.</li>
</ul>
<p>자바스크립트는 모든 것이 객체 처럼 동작한다. 원시 타입도 객체 처럼 동작하기 위해 일시적으로 래퍼 객체(wrapper object)를 생성하여 감싼다.</p>
<p>그리고 모든 객체는 <code class="language-plaintext highlighter-rouge">__proto__</code> 속성을 갖고 있다. 이 속성은 해당 객체의 생성자 함수의 <code class="language-plaintext highlighter-rouge">prototype</code> 속성을 가리킨다. 생성자 함수의 <code class="language-plaintext highlighter-rouge">prototype</code> 속성은 상위 객체의 속성과 메서드를 상속 받고 있다. 객체의 속성이나 메서드를 접근 할 때 해당 객체에 없으면 <code class="language-plaintext highlighter-rouge">prototype</code> 속성을 통해서 상위 객체를 참조한다. 이 과정은 최상위 객체인 <code class="language-plaintext highlighter-rouge">Object.prototype</code>에 도달할 때 까지 반복된다. 이러한 동작방식을 프로토타입 체인이라고 한다.</p>
<h2 id="끝-맺음">끝 맺음</h2>
<p><code class="language-plaintext highlighter-rouge">this</code>는 함수가 호출되는 방식에 따라서 바인딩되는 값이 달라지는 동적인 성격을 갖는다. 반면 <code class="language-plaintext highlighter-rouge">scope</code>는 코드가 정의된 위치에 따라 정적으로 결정된다. 화살표 함수의 <code class="language-plaintext highlighter-rouge">this</code>는 함수가 정의된 위치에 따라 결정된다. ES6 이후의 <code class="language-plaintext highlighter-rouge">this</code>에서는 정적인 성격도 가지게 된것이다. 혹시 아직 <code class="language-plaintext highlighter-rouge">this</code>와 <code class="language-plaintext highlighter-rouge">scope</code>를 말로 설명하는 것이 어렵다면 정적인지 동적인지를 구분해서 생각해보는 것만으로도 도움이 될 수 있다.</p>
<p>프로토타입은 더 정리할게 없다.</p>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.comECMAScript 3판을 다루는 ‘자바스크립트를 깨우치다’란 책을 읽었다. 그리고 이 책에서 말하는 영원히 변하지 않을 자바스크립트 초석 개념이라고 생각하는 3가지에 대해서 정리했다.블로그 마이그레이션(1) 우분투에 Ruby 설치하기2023-05-24T00:00:00+00:002023-05-24T00:00:00+00:00https://seungwoo321.github.io/blog/2023/05/24/jekyll-migration<p>기존의 익숙치 않은 기술 스택을 사용한 블로그 운영에 부담을 느껴 글에만 집중할 수 있도록 jekyll 기반으로 마이그레이션을 진행했습니다. 이번 글에서는 첫 번째 단계로 우분투에서 rvm (Ruby Version Manager) 및 루비 설치 과정에 대해서 기록 했습니다.
<!--more--></p>
<h2 id="루비-설치-mintos">루비 설치 (MintOS)</h2>
<p>맥북과 데스크탑으로는 우분투(Ubunt) 계열인 MintOS를 사용중이라서 우선 MintOS에 설치를 했습니다. 루비로 개발을 할 예정은 아니지만 혹시 모를 버전관리를 위해서 RVM으로 루비를 설치했습니다. 루비 설치 과정 중에는 권한 문제로 인해 에러가 발생했으나 해당 디렉토리에 대한 권한을 수정하면 문제를 해결할 수 있었습니다.</p>
<h3 id="루비-버전-관리자-rvm-설치하기">루비 버전 관리자 (rvm) 설치하기</h3>
<ul>
<li>Install GPG Keys</li>
</ul>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--keyserver</span> keyserver.ubuntu.com <span class="nt">--recv-keys</span> 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
</code></pre></div></div>
<ul>
<li>Basic Install (Ubuntu)</li>
</ul>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. pre-requisites</span>
<span class="nb">sudo </span>apt-get <span class="nb">install </span>software-properties-common
<span class="c"># 2. Add the PPA and install the package</span>
<span class="nb">sudo </span>apt-add-repository <span class="nt">-y</span> ppa:rael-gc/rvm
<span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>rvm
<span class="c"># 3. Add user</span>
<span class="nb">sudo </span>usermod <span class="nt">-a</span> <span class="nt">-G</span> rvm <span class="nv">$USER</span>
<span class="c"># 4. Set up environment</span>
<span class="nb">echo</span> <span class="s1">'source "/etc/profile.d/rvm.sh"'</span> <span class="o">>></span> ~/.bashrc
<span class="nb">source</span> ~/.bashrc <span class="c"># or reboot</span>
</code></pre></div></div>
<h3 id="루비-ruby-설치-하기">루비 (Ruby) 설치 하기</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rvm <span class="nb">install </span>ruby
</code></pre></div></div>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p>루비 설치과정에서 Permission Denied 에러와 requirements_debian_update_system ruby-3.0.0 명령어 실패 오류가 발생했습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Searching <span class="k">for </span>binary rubies, this might take some time.
No binary rubies available <span class="k">for</span>: mint/20.3/x86_64/ruby-3.0.0.
Continuing with compilation. Please <span class="nb">read</span> <span class="s1">'rvm help mount'</span> to get more information on binary rubies.
Checking requirements <span class="k">for </span>mint.
Installing requirements <span class="k">for </span>mint.
<span class="nb">mkdir</span>: <span class="sb">`</span>/usr/share/rvm/log/1677135379_ruby-3.0.0<span class="s1">' 디렉토리를 만들 수 없습니다: 허가 거부
tee: /usr/share/rvm/log/1677135379_ruby-3.0.0/update_system.log: 그런 파일이나 디렉터리가 없습니다
Updating system....
Error running '</span>requirements_debian_update_system ruby-3.0.0<span class="s1">',
please read /usr/share/rvm/log/1677135379_ruby-3.0.0/update_system.log
Requirements installation failed with status: 1.
</span></code></pre></div></div>
<p>이 에러는 <strong><code class="language-plaintext highlighter-rouge">/usr/share/rvm/</code></strong> 디렉토리에 대한 권한 문제로 발생할 수 있습니다. <strong><code class="language-plaintext highlighter-rouge">/usr/share/rvm/</code></strong> 디렉토리는 시스템 루트 권한이 필요한 디렉토리입니다. 해당 오류를 해결하기 위해서 <strong><code class="language-plaintext highlighter-rouge">/usr/share/rvm/</code></strong>디렉토리에 대한 권한을 수정해주기 위해 다음 명령어를 실행해주었습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo chown</span> <span class="nt">-R</span> <span class="nv">$USER</span>:<span class="nv">$USER</span> /usr/share/rvm
</code></pre></div></div>
<h3 id="설치-확인">설치 확인</h3>
<p>루비 설치 과정이 제대로 완료되면 <strong><code class="language-plaintext highlighter-rouge">ruby -v</code></strong> 명령어로 루비 버전을 확인할 수 있습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ruby <span class="nt">-v</span>
ruby 3.0.0p0 <span class="o">(</span>2020-12-25 revision 95aff21468<span class="o">)</span> <span class="o">[</span>x86_64-linux
</code></pre></div></div>
<h3 id="jekyll와-bundler-설치">Jekyll와 Bundler 설치</h3>
<p>이제 다음 위 명령어를 각각 실행하면 RubyGems를 업데이트한 후 정적 사이트 생성 도구인 Jekyll과 Ruby 프로젝트에서 필요한 의존성 패키지를 관리하는 Bundler를 설치할 수 있습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># RubyGems 업데이트</span>
gem update <span class="nt">--system</span>
<span class="c"># Jekyll 설치</span>
gem <span class="nb">install </span>jekyll
<span class="c"># Bundler 설치</span>
gem <span class="nb">install </span>bundler
</code></pre></div></div>
<h3 id="마무리">마무리</h3>
<ul>
<li>jekyll 프로젝트 구성을 위한 기본 설정이 완료되었습니다.</li>
<li>다음 편에서는 직접 기존 블로그의 소스를 포크(fork) 해서 마이그레이션 하고 빌드 하는 과정을 튜토리얼 처럼 다뤄볼 예정입니다.</li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com기존의 익숙치 않은 기술 스택을 사용한 블로그 운영에 부담을 느껴 글에만 집중할 수 있도록 jekyll 기반으로 마이그레이션을 진행했습니다. 이번 글에서는 첫 번째 단계로 우분투에서 rvm (Ruby Version Manager) 및 루비 설치 과정에 대해서 기록 했습니다.자바스크립트 ES6와 호이스팅(Hoisting)2023-05-23T00:00:00+00:002023-05-23T00:00:00+00:00https://seungwoo321.github.io/blog/2023/05/23/what-is-hoisting<p>ES6에서 let과 const는 호이스팅이 발생하지 않는 것처럼 보여서 호이스팅이 발생하지 않는다고 이해할 수 있지만 사실은 그렇지 않다. 이게 무슨 의미인지 정리했다.</p>
<!--more-->
<h2 id="선행되어야-하는-개념">선행되어야 하는 개념</h2>
<ul>
<li>함수 레벨 스코프와 블록 레벨 스코프</li>
<li>실행 컨텍스트에서 변수가 생성되는 과정</li>
</ul>
<h3 id="함수-레벨-스코프와-블록-레벨-스코프">함수 레벨 스코프와 블록 레벨 스코프</h3>
<p>함수 레벨 스코프는 함수 내에서 선언한 변수는 함수 내에서만 유효한 것이고 블록 레벨 스코프는 코드 블록 내에서 선언된 변수는 코드 블록 내에서만 유효한 변수다.</p>
<p>자바스크립트는 함수 레벨 스코프를 따르고 var는 함수 레벨 스코프다. let과 const는 블록 레벨 스코프를 따른다.</p>
<p>어떤 차이가 있는지 아래 예시를 통해서 알 수 있다.</p>
<blockquote>
<p>개발자 도구를 열어서 바로 실행해 보자.</p>
</blockquote>
<p>예제 1.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mi">5</span><span class="p">;</span> <span class="nx">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">cosnole</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span> <span class="c1">// i는 자유변수라서 for 루프가 종료되어도 5가 출력된다.</span>
</code></pre></div></div>
<p>예제 1에서 <code class="language-plaintext highlighter-rouge">var</code> 키워드로 선언된 변수 <code class="language-plaintext highlighter-rouge">i</code>는 함수 레벨 스코프를 가지기 때문에 <code class="language-plaintext highlighter-rouge">i</code>는 전역 변수로 취급된다. 따라서 setTimeout의 콜백 함수에서 <code class="language-plaintext highlighter-rouge">i</code>를 참조할 때는 이미 <code class="language-plaintext highlighter-rouge">for</code> 루프가 끝까지 진행되어 종료된 후의 값인 5가 출력 된다.</p>
<p>예제 2.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span> <span class="o"><</span> <span class="mi">5</span><span class="p">;</span> <span class="nx">j</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">j</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">j</span><span class="p">)</span> <span class="c1">// ReferenceError</span>
</code></pre></div></div>
<p>예제 2에서 <code class="language-plaintext highlighter-rouge">let</code> 키워드로 선언된 변수 <code class="language-plaintext highlighter-rouge">j</code>는 블록 레벨 스코프를 가지기 때문에 <code class="language-plaintext highlighter-rouge">for</code> 루프의 각 반복마다 새로운 <code class="language-plaintext highlighter-rouge">j</code>가 생성되고 해당 반복 블록 내에서만 유효한 지역 변수다. 따라서 setTimeout의 콜백 함수에서 <code class="language-plaintext highlighter-rouge">j</code>는 각 반복에서의 <code class="language-plaintext highlighter-rouge">j</code>를 참조하기 때문에 0, 1, 2, 3, 4가 순서대로 출력 된다.</p>
<h3 id="실행-컨텍스트에서-변수가-생성되는-과정">실행 컨텍스트에서 변수가 생성되는 과정</h3>
<p>변수 선언이란 해당 변수만을 위한 메모리 공간을 할당받는 것이다. 자바스크립트에서 변수는 실행 컨텍스트(Exectuion Context)에서 다음 3단계에 걸쳐 생성된다.</p>
<ol>
<li>선언 단계 (Declaration phase): 실행 컨텍스트의 변수 객체에 등록한다. 스코프가 시작된다.</li>
<li>초기화 단계 (Initialization phase): 변수를 위한 메모리가 할당되고 undefined로 초기화된다.</li>
<li>할당 단계 (Assignment phase) : undefined로 할당된 값에 실제 값을 할당한다.</li>
</ol>
<p>실제 코드를 보면서 살펴보자.</p>
<h4 id="var-키워드로-선언한-변수">var 키워드로 선언한 변수</h4>
<p><code class="language-plaintext highlighter-rouge">var</code> 키워드로 선언한 변수 <code class="language-plaintext highlighter-rouge">foo</code>는 실제로 선언과 초기화 단계가 한 번에 이루어져서 선언과 동시에 <code class="language-plaintext highlighter-rouge">undefined</code>로 초기화되고, 그 후에 ‘bar`로 값이 할당되는 단계로 나누어진다.</p>
<p>예제 3.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<p>위 코드는 이렇게 동작할 것이다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">foo</span><span class="p">,</span> <span class="nx">foo</span> <span class="o">=</span> <span class="kc">undefined</span> <span class="c1">// 선언 및 초기화 단계</span>
<span class="cm">/** ------------------------------------------ */</span>
<span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// 할당</span>
</code></pre></div></div>
<h4 id="let-키워드로-선언한-변수">let 키워드로 선언한 변수</h4>
<p><code class="language-plaintext highlighter-rouge">let</code> 키워드로 선언한 변수는 <strong>선언 단계와 초기화 단계가 분리</strong>되어 있다. 그리고 선언 단계에서 실행 컨텍스트의 변수 객체에 등록이 된 후부터 초기화 전의 시점을 일시적 사각지대(TDZ)라고 한다.</p>
<p>예제 4.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span>
</code></pre></div></div>
<p>위 코드는 이렇게 동작할 것이다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">foo</span><span class="p">;</span> <span class="c1">// 선언</span>
<span class="c1">// ---- ReferenceError ---</span>
<span class="c1">// _____ ___ ____</span>
<span class="c1">// |_ _|| \|_ /</span>
<span class="c1">// | | | |) |/ / </span>
<span class="c1">// |_| |___//___|</span>
<span class="c1">// </span>
<span class="c1">// ---- ReferenceError ---</span>
<span class="nx">foo</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span> <span class="c1">// 초기화</span>
<span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// 할당</span>
</code></pre></div></div>
<p>이렇게 선언 후 초기화가 되기 전의 TDZ에 진입했을 때 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다.</p>
<h4 id="const-키워드로-선언한-변수">const 키워드로 선언한 변수</h4>
<p><code class="language-plaintext highlighter-rouge">const</code> 키워드로 선언하는 변수는 선언과 동시에 값이 할당까지 이루어져야 하며 이후에는 값을 변경할 수 없다.</p>
<p>예제 5.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span>
</code></pre></div></div>
<h2 id="es6와-호이스팅-hoisting">ES6와 호이스팅 (Hoisting)</h2>
<p>호이스팅이란 모든 선언문이 각각이 속한 스코프의 꼭대기로 끌어올려지는 작업이다. ES6에서 <code class="language-plaintext highlighter-rouge">let</code> 키워드로 선언한 변수 또한 블록 레벨 스코프로 호이스팅이 이루어지기 때문에 실행 컨텍스트의 변수 객체에 등록되고 <code class="language-plaintext highlighter-rouge">undefined</code>로 초기화되기 전까지인 TDZ에 진입했을 때 변수에 접근하려면 하면 참조 에러(ReferenceError)가 발생한다. 호이스팅이 발생하지 않는 것처럼 보이는 것뿐이다. <code class="language-plaintext highlighter-rouge">const</code> 키워드도 선언하는 변수도 같지만 선언과 동시에 값이 할당되어야 하는 것이 규칙이기 때문에 문법 오류(SyntaxError)가 발생한다.</p>
<p>예제 6.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">);</span> <span class="c1">// undefined</span>
<span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">a</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">b</span><span class="p">;</span> <span class="c1">// SyntaxError</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">c</span><span class="p">);</span> <span class="c1">// ReferenceError</span>
<span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">);</span> <span class="c1">// ReferenceError</span>
<span class="kd">let</span> <span class="nx">d</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">d</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://poiemaweb.com/es6-block-scope">PoiemaWeb</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Glossary/Hoisting">MDN</a></li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.comES6에서 let과 const는 호이스팅이 발생하지 않는 것처럼 보여서 호이스팅이 발생하지 않는다고 이해할 수 있지만 사실은 그렇지 않다. 이게 무슨 의미인지 정리했다.나의 크롬 북마크, 유용한 링크 모음 tools편2023-05-22T00:00:00+00:002023-05-22T00:00:00+00:00https://seungwoo321.github.io/blog/2023/05/22/my-bookmark<p>며칠전 rvm 및 rbenv로 루비를 설치하면서 만난 오류들을 해결하는 과정이 커맨드 치고 기다리는 시간의 반복이라 이 지루함을 극복하기 위해 크롬의 북마크를 정리했다. 왜 tools 라고 분류하고 남겼는지를 기억하기 위해서 정리 했다.
<!--more--></p>
<h2 id="깃허브-관련">깃허브 관련</h2>
<ul>
<li><a href="https://leviarista.github.io/github-profile-header-generator">Github profile Header Generator</a> : 깃허브 README.md에 사용할 헤더 이미지를 생성</li>
<li><a href="https://www.bannerbear.com/demos/github-social-preview-generator-tool/">Github Social Image Generator</a> : 깃 레포지토리 SNS 프리뷰 이미지 생성</li>
</ul>
<h2 id="그래픽-관련">그래픽 관련</h2>
<ul>
<li><a href="https://okso.app/">Ok! So</a> : 칠판 처럼 사용 가능</li>
<li><a href="https://app.diagrams.net/">diagrams</a> : 다이어그램</li>
<li><a href="https://www.photopea.com">Photopea</a> : 웹 기반 이미지 편집기</li>
<li><a href="https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20">patorjk</a> : 텍스트 아트 생성기 (CLI 만들 때 사용함)</li>
</ul>
<h2 id="기타">기타</h2>
<ul>
<li><a href="https://astexplorer.net/">AST Explorer</a> : 자바스크립트 AST 해석기</li>
<li><a href="https://www.phind.com/">phind</a>: 개발자를 위한 AI 검색엔진</li>
<li><a href="https://jsfiddle.net/">jsfiddle</a> - 라이브 데모용 (vue-pivottable)</li>
<li><a href="http://jsbin.com/?html,output">jsbin</a> - 라이브 데모용2</li>
<li><a href="https://mobaxterm.mobatek.net">mobaxterm</a> : ssh, rdp 등 터미널/원격 다중 접속 툴(Window용)</li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com며칠전 rvm 및 rbenv로 루비를 설치하면서 만난 오류들을 해결하는 과정이 커맨드 치고 기다리는 시간의 반복이라 이 지루함을 극복하기 위해 크롬의 북마크를 정리했다. 왜 tools 라고 분류하고 남겼는지를 기억하기 위해서 정리 했다.LIS (Longest Increasing Subsequence) - 최장 증가 부분 수열2023-05-19T00:00:00+00:002023-05-24T00:00:00+00:00https://seungwoo321.github.io/blog/2023/05/19/what-is-lis-algorithm<p>백준에서 문제 해결 능력을 키우면서 최장 증가 부분 수열문제를 풀게 되었다. 최장 증가 수열이란 주어진 수열의 부분 수열 중에서 숫자가 오름차순으로 정렬 되는 가장 긴 부분 수열을 의미한다. 문제를 풀면서 공부한 내용을 정리했다.
<!--more--></p>
<h2 id="lis-길이-구하기">LIS 길이 구하기</h2>
<p>몇가지 예를 들어보겠다. 주어진 수열이 1, 2, 3, 4, 5, 6, 7, 8, 9 이면 1부터 9까지 연속으로 오름차순 정렬 되어 있어서 최장 증가 부분수열의 길이는 9가 된다. 1, 2, 3, 4, 2, 5, 6, 7, 8 이면 최장 증가부분 수열은 1, 2, 3, 4, 5, 6, 7, 8이 되서 길이는 8이 된다. 조금 더 복잡한 예제를 가지고 살펴보자. 1, 5, 2, 8, 3, 4, 6 이 주어 진다면 최장 부분 증가 수열은 무엇이고 그 길이는 어떻게 될까 ?</p>
<h3 id="on2으로-구하기">O(n^2)으로 구하기</h3>
<p>주어진 수열의 원소들을 순회하면서 순회하는 원소의 위치를 기준으로 했을 때 앞의 숫자들 중에서 가장 긴 LIS의 길이를 새 배열에 저장하고 순회가 끝났을 때 이 배열에서 가장 큰 값이 이 수열의 최장 증가 부분 수열이 된다.</p>
<p>위의 설명을 토대로 1, 5, 2, 8, 3, 4, 6 예제에 적용 해보자.</p>
<ul>
<li>위치가 0일 때 [1] : 최소 1개 이상이므로 기본 값은 항상 1이다.</li>
<li>위치가 1일 때 [1, 2] : 숫자 5까지로 보면 1, 5의 증가부분 수열이 있고 2가 가장 큰 길이가 된다.</li>
<li>위치가 2일 때 [1, 2, 2] : 숫자 2까지로 보면 1, 2의 증가부분 수열이 있고 이전까지와 동일하게 2가 가장 큰 길이가 된다.</li>
<li>위치가 3일 때 [1, 2, 2, 3] : 숫자 8까지로 보면 1, 5, 8 또는 1, 2, 8이라는 증가부분 수열이 추가 되서 이전과 다르게 3이 가장 큰 길이가 된다.</li>
<li>위치가 4일 때 [1, 2, 2, 3, 3] : 숫자 3까지로 보면 1, 2, 3이라는 증가부분 수열이 추가 되었으나 이전까지와 동일하게 가장 큰 길이는 3이다.</li>
<li>위치가 5일 때 [1, 2, 2, 3, 3, 4] : 숫자 4까지로 보면 1, 2, 3, 4라는 증가부분 수열이 추가되어 이전과 다르게 가장 큰 길이는 4가 된다.</li>
<li>위치가 6일 때 [1, 2, 2, 3, 3, 4, 5] : 숫자 6까지로 보면 1, 2, 3, 4, 6이라는 증가부분 수열이 추가되어 이전과 다르게 가장 큰 길이는 5가 된다.</li>
</ul>
<p>자바스크립트 코드로 다음과 같이 구현 할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">nums</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="kd">const</span> <span class="nx">lis</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">7</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">nums</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span> <span class="o"><</span> <span class="nx">i</span><span class="p">;</span> <span class="nx">j</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">nums</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">></span> <span class="nx">nums</span><span class="p">[</span><span class="nx">j</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">lis</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="nx">lis</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">lis</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">lis</span><span class="p">)</span> <span class="c1">// [1, 2, 2, 3, 3, 4, 5]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(...</span><span class="nx">lis</span><span class="p">));</span> <span class="c1">// 5</span>
</code></pre></div></div>
<p>1, 5, 2, 8, 3, 4, 6의 최장 증가 부분 수열은 1, 2, 3, 4, 6 이고 길이는 5가 된다. 이 알고리즘을 그대로 사용해서 <a href="https://www.acmicpc.net/problem/11053">BOJ 11053 - 가장 긴 증가하는 부분 수열</a> 실버2 문제를 해결 할 수 있다.</p>
<h3 id="onlogn으로-구하기">O(nlogn)으로 구하기</h3>
<p>이 알고리즘은 위의 알고리즘을 개량한 형태로 <a href="https://namu.wiki/w/%EC%B5%9C%EC%9E%A5%20%EC%A6%9D%EA%B0%80%20%EB%B6%80%EB%B6%84%20%EC%88%98%EC%97%B4">나무위키</a>를 보고 이해한 내용을 정리 했다. 1줄로 요약하면 <strong>순회하는 원소까지의 숫자로 끝이 가장 작은 수로 끝나면서 가장 긴 증가 부분 수열을 만들면 된다.</strong> 동일하게 1, 5, 2, 8, 3, 4, 6 예제를 가지고 살펴보겠다.</p>
<p>i = 0 일 때 첫 번째 원소를 추가한다. 새 배열은 X라고 하겠다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 1 일 때 5는 앞에서의 부분 수열의 맨 뒤인 1보다 크기 때문에 뒤에 추가한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 5]</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 2 일 때 2는 5를 대신한다면 1, 5보다는 1, 2가 가장 긴 증가부분 수열을 만들기 위해 앞으로 뒤에 붙일 수 있는 숫자가 더 많으니까 5의 인덱스인 1을 구해서 X의 1번째 값 5를 2로 갱신한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 5]</td>
<td>[1, 2]</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 3 일 때 8은 앞에서의 부분 수열의 맨 뒤인 2보다 크기 때문에 뒤에 추가한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 5]</td>
<td>[1, 2]</td>
<td>[1, 2, 8]</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 4 일 때 3은 8을 대신한다면 1, 2, 8보다는 1, 2, 3이 가장 긴 증가부분 수열을 만들기 위해 앞으로 뒤에 붙일 수 있는 숫자가 더 많으니까 8의 인덱스인 2를 구해서 X의 2번째 값 8을 3으로 갱신한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 5]</td>
<td>[1, 2]</td>
<td>[1, 2, 8]</td>
<td>[1, 2, 3]</td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 5 일 때 4는 앞에서의 부분 수열의 맨 뒤인 3보다 크기 때문에 뒤에 추가한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 5]</td>
<td>[1, 2]</td>
<td>[1, 2, 8]</td>
<td>[1, 2, 3]</td>
<td>[1, 2, 3, 4]</td>
<td> </td>
</tr>
</tbody>
</table>
<p>i = 6 일 때 6은 앞에서의 부분 수열의 맨 뒤인 4보다 크기 때문에 뒤에 추가한다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>1</td>
<td>5</td>
<td>2</td>
<td>8</td>
<td>3</td>
<td>4</td>
<td>6</td>
</tr>
<tr>
<td>X</td>
<td>[1]</td>
<td>[1, 2]</td>
<td>[1, 2, 8]</td>
<td>[1, 2, 3]</td>
<td>[1, 2, 3, 4]</td>
<td>[1, 2, 3, 4, 6]</td>
<td> </td>
</tr>
</tbody>
</table>
<p>이렇게 주어진 수열을 순회하면서 최대한 작은 숫자들로 증가 부분 수열을 만들면 그 길이가 최장증가 부분 수열의 길이가 된다. 여기서 주의 할 점이 이 예제만 보고 위 알고리즘으로 만들어진 배열 X가 최장 증가 부분 수열을 만든다고 생각 할 수 있다. 하지만 배열 X는 어디까지나 LIS의 길이를 구하는 배열이다.</p>
<p>이 부분은 수열이 2, 3, 4, 1, 5, 2, 7, 8, 3, 4, 6인 예제를 가지고 확인 해보면 알 수 있다.</p>
<ul>
<li>배열 X는 1, 2, 3, 4, 6, 8 이 되고 최장증가 부분 수열은 2, 3, 4, 5, 7, 8 이 된다.</li>
</ul>
<h2 id="lis-수열-구하기">LIS 수열 구하기</h2>
<p>위의 O(nlogn) 알고리즘으로 구하는 배열 X는 최장증가부분 수열은 아니지만 이 알고리즘을 구현하는 과정에서 원본 인덱스를 저장하는 또 다른 배열을 만들어서 LIS 수열을 구할 수 있다.</p>
<p>먼저 배열 X를 만들기 위한 위치 인덱스를 반환하는 <code class="language-plaintext highlighter-rouge">lowerBound</code> 함수를 이진탐색을 기반으로 구현한다. <code class="language-plaintext highlighter-rouge">lowerBound</code>는 정수 k와 정수 배열이 주어지면 k이상인 수가 처음으로 등장하는 위치를 반환 한다. 위에 알고리즘 설명 글에서 “x(n)의 인덱스인 n을 구해서”라는 설명에 해당 하는 부분이다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">lowerBound</span> <span class="o">=</span> <span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">list</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">left</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">right</span> <span class="o">=</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">left</span> <span class="o"><=</span> <span class="nx">right</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">mid</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">((</span><span class="nx">left</span> <span class="o">+</span> <span class="nx">right</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">list</span><span class="p">[</span><span class="nx">mid</span><span class="p">]</span> <span class="o"><</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">left</span> <span class="o">=</span> <span class="nx">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">right</span> <span class="o">=</span> <span class="nx">mid</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">left</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">lowerBound</code> 함수를 구현하고 배열 X를 <code class="language-plaintext highlighter-rouge">lis</code>라는 이름으로 선언하고 0번째에는 원본 배열의 0번째 값을 추가했다. 그리고 1번째 값부터 순회하면서 어느 위치에 들어갈 수 있는지를 <code class="language-plaintext highlighter-rouge">lowerBound</code> 함수로 구한다. 하나하나 살펴본 예시와 같이 순회하는 값이 가장 커서, 즉 반환된 인덱스와 <code class="language-plaintext highlighter-rouge">lis</code> 배열의 길이가 같다면 뒤에 추가를 하고 그렇지 않으면 갱신을 한다. 이때 또 다른 배열에는 반환된 인덱스 + 1을 저장해서 순회하는 값까지의 최장 증가 부분 수열의 길이를 <code class="language-plaintext highlighter-rouge">indexes</code>에 저장한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">indexes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">lis</span> <span class="o">=</span> <span class="p">[</span><span class="nx">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">]];</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">lowerBound</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">lis</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">index</span> <span class="o">===</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="k">else</span> <span class="nx">lis</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="nx">indexes</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">indexes</span><span class="p">)</span> <span class="c1">// [1, 2, 3, 1, 4, 2, 5, 6, 3, 4, 5]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">lis</span><span class="p">)</span> <span class="c1">// [1, 2, 3, 4, 6, 8]</span>
</code></pre></div></div>
<p>최장 증가 부분 수열을 구하기 위해서 <code class="language-plaintext highlighter-rouge">indexes</code>을 뒤에서부터 가장 큰 길이 부터 1까지 처음 나오는 순간의 인덱스를 가지고 원본 값을 찾아서 저장하고 뒤집는다. <code class="language-plaintext highlighter-rouge">lis</code>의 길이는 6이니까 원소의 길이인 10부터 거꾸로 순회하는데 6부터 1까지 처음 나올 때의 인덱스를 가지고 원본 배열에서 값을 구한다. 6이 처음 나오는 인덱스는 7이므로 <code class="language-plaintext highlighter-rouge">answer</code>에 처음 추가되는 값은 원본 배열의 7번째 원소인 8이 추가된다. 다음에는 5가 처음 나오는 인덱스가 6이므로 원본 배열의 6번째 원소인 7을 추가한다. 이렇게 반복하면 [8, 7, 5, 4, 3, 2] 배열이 완성된다. 뒤집으면 최장증가 부분 수열이다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">answer</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="nx">indexes</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">answer</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">indexes</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span> <span class="o">===</span> <span class="nx">lis</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="nx">answer</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">answer</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="nx">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="nx">j</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">answer</span><span class="p">.</span><span class="nx">reverse</span><span class="p">().</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">))</span> <span class="c1">// 2 3 4 5 7 8</span>
</code></pre></div></div>
<p>이렇게 구현한 O(nlogn) 알고리즘으로는 <a href="https://www.acmicpc.net/problem/14003">BOJ 14003 - 가장 긴 증가하는 부분 수열 5</a> 플래티넘5 문제를 해결 할 수 있다.</p>
<h2 id="마무리">마무리</h2>
<p>이 글은 뒤늦게 PS 공부를 시작한 필자가 LIS (Longest Increasing Subsequence)에 대한 기억이 희미해질 때 쉽게 이해할 수 있도록 작성한 글이다. 관심이 있으신 분들은 이 글의 코드를 참고하여 응용해보시기 바란다.</p>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.com백준에서 문제 해결 능력을 키우면서 최장 증가 부분 수열문제를 풀게 되었다. 최장 증가 수열이란 주어진 수열의 부분 수열 중에서 숫자가 오름차순으로 정렬 되는 가장 긴 부분 수열을 의미한다. 문제를 풀면서 공부한 내용을 정리했다.데이터 정확성을 위한 빅 데시멀 타입과 지수 표기법의 소수점 표시2023-03-28T00:00:00+00:002023-03-28T00:00:00+00:00https://seungwoo321.github.io/blog/2023/03/28/big-decimal-and-e-notation<p>AWS 빌링 데이터를 다루는 프론트엔드 개발자로 일하면서 백엔드에서 전달받은 숫자 데이터가 부동 소수점 연산의 함정과 지수 표기식(e notation)으로 표현되면서 생긴 이슈들을 해결하면서 빅 데시멀과 지수 표기법에 대해 정리해보았습니다.
<!--more--></p>
<h2 id="부동-소수점-타입과-빅-데시멀">부동 소수점 타입과 빅 데시멀</h2>
<h3 id="부동-소수점-타입">부동 소수점 타입</h3>
<p>부동 소수점 타입은 IEEE 754 표준에 따라 일반적으로 내부적으로 2진수로 저장됩니다. 이진수로 저장하면 정확하게 표현할 수 없는 소수점 위치가 발생해 오차가 발생할 수 있습니다. 예를 들어 <code class="language-plaintext highlighter-rouge">0.1</code>을 더블(Double) 타입으로 표현하면 <code class="language-plaintext highlighter-rouge">0.1</code>이 아니라 <code class="language-plaintext highlighter-rouge">0.1000000000000000055511151231257827021181583404541015625</code>와 같이 표현 될 수 있습니다.</p>
<h3 id="부동-소수점의-한계">부동 소수점의 한계</h3>
<p>부동 소수점의 이러한 오차는 일반적으로 작은 범위에서 무시할 만한 수준이지만 계산이 계속해서 이어지면서 쌓이게 되면 결국 오차가 누적되어서 결과값이 크게 차이나게 될 수 있습니다.</p>
<p>특히 큰 값을 다룰 때는 오차가 누적되어 부동소수점의 한계로 인한 계산 문제가 더 많이 발생하기 때문에 정밀한 계산이 필요한 경우에는 부동소수점 타입을 사용하지 않는 것이 좋습니다.</p>
<h3 id="빅-데시멀">빅 데시멀</h3>
<p>자바에서는 부동 소수점의 한계를 극복하기 위해 일반적인 부동 소수점 타입과는 구분되는 Java 표준의 빅 데시멀(Big Decimal)이라는 타입이 제공됩니다. 빅 데시멀은 내부적으로 십진수로 변환하여 저장하기 때문에 0.1은 0.1이라고 저장합니다. 따라서 부동 소수점의 한계로 발생하는 오차를 줄일 수 있어서 정확한 계산을 보장합니다. 하지만 빅 데시멀은 부동소수점에 비해 느리고 메모리 사용량도 더 많기 때문에 큰 데이터셋을 다루는 경우에는 성능상 이슈가 될 수 있습니다.</p>
<blockquote>
<p>빅 데시멀은 자바에서 제공하는 부동 소수점 타입으로, IEEE 754 표준을 따르는 일반적인 부동 소수점 타입과는 구분됩니다.</p>
</blockquote>
<h2 id="지수-표기법">지수 표기법</h2>
<p>너무 크거나 작은 숫자의 데이터를 보면 숫자에 e가 표함된 지수 표기식 데이터를 받는 경우가 있습니다. 이는 “지수 표기법” 또는 “과학적 표기법” 이라고도 하며 보통 숫자 다음에 e와 함께 지수를 표기합니다. 예를 들어, <code class="language-plaintext highlighter-rouge">1,000,000</code>은 <code class="language-plaintext highlighter-rouge">1.0e+6</code>으로 표기될 수 있습니다.</p>
<h3 id="e-지수로-표현되는-기준">e 지수로 표현되는 기준</h3>
<p>일반적으로 IEEE 754 표준을 따르는 더블 타입은 1e-16 이하 또는 1e+16 이상인 대략적으로 16자리 이상인 경우 e 지수 형태로 표기될 가능성이 높습니다. 하지만 이는 숫자 타입의 구현 방식과 사용되는 언어나 프로그램에 따라 다를 수 있으므로 정확한 기준은 없습니다. 빅 데시멀의 경우에는 소수점 이하 6자리 이상일 때 e 지수 형태로 표기됩니다.</p>
<ul>
<li>Java: 더블 타입의 경우 <code class="language-plaintext highlighter-rouge">4.9e-324</code>(10의 -324승) 미만 또는 <code class="language-plaintext highlighter-rouge">1.79769e+308</code>(10의 308승) 초과 이거나 16자리 이상인 경우</li>
<li>Python: 더블 타입의 경우 <code class="language-plaintext highlighter-rouge">2.22507e-308</code>(10의 -308승) 미만 또는 <code class="language-plaintext highlighter-rouge">1.79769e+308</code>(10의 308승) 초과 이거나 16자리 이상인 경우</li>
<li>JavaScript/Node.js: V8엔진을 기반으로 동작하며 <code class="language-plaintext highlighter-rouge">5e-324</code>(10의 -324승) 미만 또는 <code class="language-plaintext highlighter-rouge">1.8e+308</code>(10의 308승) 초과 이거나 16자리 이상인 경우</li>
</ul>
<p>자바에서 e 지수로 표기될 가능성이 있는 더블 타입의 일부 숫자 예시입니다.</p>
<ul>
<li>4.9e-324</li>
<li>1.7976931348623157e+308</li>
<li>1.0000000000000002e-14</li>
<li>9.999999999999998e+15</li>
<li>1.0000000000000001e+16</li>
<li>9.223372036854776e+18</li>
<li>1.2345678901234567e+18</li>
<li>9.999999999999997e+18</li>
<li>1.2345678901234567e+19</li>
<li>9.999999999999998e+19</li>
<li>1.2345678901234567e+20</li>
<li>9.999999999999998e+20</li>
<li>1.2345678901234567e+21</li>
</ul>
<p>다음은 보다 짧은 자릿수에도 e 지수 표기법으로 표현되는 빅 데시멀 타입의 일부 숫자 예시입니다.</p>
<ul>
<li>1.234567e-4</li>
<li>9.8765432e+5</li>
<li>1.23e-8</li>
<li>9.87e-10</li>
<li>7.654321e+5</li>
<li>9.87654e-4</li>
<li>1.23456789e+8</li>
<li>3.21e-9</li>
<li>4.321e+3</li>
<li>7.89e-9</li>
</ul>
<h2 id="결론">결론</h2>
<p>소수점 이하 자릿수가 중요한 분야에서 백엔드가 자바로 작성되어 있다면 빅 데시멀 타입을 주로 사용하게 될 것입니다. 위의 e 지수 표기법으로 표현되는 숫자 예시처럼 빅 데시멀은 더블 타입보다 소수점 이하 자릿수가 적은 숫자에서도 e 지수 형태로 표기되므로 소수점 표기 변환이 필요한 경우가 더 많이 발생 할 것입니다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">rate</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">indexOf</span><span class="p">(</span><span class="dl">'</span><span class="s1">e-</span><span class="dl">'</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">points</span> <span class="o">=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">rate</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">e-</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">let</span> <span class="nx">decimal</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">points</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">decimal</span> <span class="o">+=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">points</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">points</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">).</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">decimal</span> <span class="o">+=</span> <span class="nx">points</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">].</span><span class="nx">length</span>
<span class="p">}</span>
<span class="nx">item</span><span class="p">.</span><span class="nx">rate</span> <span class="o">=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">rate</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="nx">decimal</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">item</span>
</code></pre></div></div>
<p>이 코드는 소수점 이하 자릿수가 큰 데이터에서 e 지수 형태로 표기될 가능성이 있는 숫자를 소수점 형태로 변환하기 위해 작성한 코드입니다. 먼저 indexOf 메소드를 이용하여 해당 숫자가 e-를 포함하고 있는지를 검사하고, split 메소드를 이용하여 e-를 기준으로 문자열을 분리합니다. 분리된 두 번째 요소에서 소수점 이하 자릿수를 추출하고 첫 번째 요소에서 split 메소드를 이용하여 정수부분과 소수점 이하 부분을 분리하여 소수점 이하 자릿수를 계산합니다. 계산된 소수점 이하 자릿수를 toFixed 메소드를 이용하여 소수점 자릿수를 맞춘 숫자로 변환하고 <code class="language-plaintext highlighter-rouge">item.rate</code>에 다시 할당합니다.</p>
<p>예를 들어, <code class="language-plaintext highlighter-rouge">1.234567e-4</code>는 소수점 형태로 변환하면 <code class="language-plaintext highlighter-rouge">0.0001234567</code>입니다. 이렇게 e 지수 형태로 표현된 숫자를 소수점 형태로 변환하여 더욱 직관적으로 사용자가 이해할 수 있도록 했습니다.</p>
<blockquote>
<p>결론적으로, 빅 데시멀과 지수 표기법은 부동 소수점 타입의 한계를 극복하기 위한 대안입니다. 백엔드에서 전달받은 데이터의 타입과 형태를 이해하고 적절한 처리를 수행하여 정확한 데이터 표현과 계산을 보장하는 것이 중요합니다. 프론트엔드 개발자는 이러한 이슈들을 고려하여 데이터 처리 및 표현 방법을 결정해야 하며, 데이터의 정확성과 사용자 경험을 고려하여 적절한 방법을 선택해야 합니다.</p>
</blockquote>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.comAWS 빌링 데이터를 다루는 프론트엔드 개발자로 일하면서 백엔드에서 전달받은 숫자 데이터가 부동 소수점 연산의 함정과 지수 표기식(e notation)으로 표현되면서 생긴 이슈들을 해결하면서 빅 데시멀과 지수 표기법에 대해 정리해보았습니다.오리진이 다른 리소스를 서비스워커로 캐싱하기2023-03-28T00:00:00+00:002023-03-28T00:00:00+00:00https://seungwoo321.github.io/blog/2023/03/28/how-to-pwa-with-vue<p>Vue CLI3의 vue add pwa 명령어를 실행하면 쉽게 PWA를 설정할 수 있습니다. 이것만으로 빌드 시 생성되는 파일이 서비스 워커에 의해 캐싱이 됩니다. 여기에 추가로 교차 오리진에서 호출하는 리소스를 캐싱 해본 경험을 정리했습니다.
<!--more--></p>
<h2 id="왜-서비스-워커를-도입하려는가">왜 서비스 워커를 도입하려는가</h2>
<p>항상 호출되지만 거의 변경되지 않는 JSON 파일이 프로젝트에 포함되어 있어서 빌드 시 크기를 그만큼 차지하고 JSON 파일의 내용 변경 시 전체 서비스를 새로 빌드 해서 배포해야 하는 문제가 있었습니다.</p>
<p>이를 해결하기 위해 JSON 파일을 S3에 업로드하고 이 파일을 수정하는 관리자용 페이지를 개발했습니다. 서비스에서는 새 클라우드 프론트(CloudFront)에 연결된 주소를 사용하여 호출하도록 변경했습니다.</p>
<p>그러나 호출할 때마다 네트워크 호출 비용이 발생하는 것은 이전과 동일합니다.</p>
<p>이를 개선하기 위해 서비스 워커를 사용하여 중간에 네트워크 요청을 가로채고 클라이언트의 캐시 저장 공간에 저장하여 캐시 된 데이터가 있으면 이를 반환하고 없으면 네트워크 요청을 계속하도록 구현하기로 했습니다. 이를 통해 네트워크 호출 비용을 최소화하고 클라이언트의 사용자 경험을 개선하고자 했습니다.</p>
<h2 id="기본-설정-살펴보기">기본 설정 살펴보기</h2>
<p>vue cli3를 사용한 프로젝트에서 앞서 언급한 <code class="language-plaintext highlighter-rouge">vue add pwa</code> 명령어로 뷰 프로젝트에 PWA를 추가했을 때 관련된 내용을 살펴보면 프로젝트 루트에 <code class="language-plaintext highlighter-rouge">register-service-worker</code>를 사용해서 서비스 워커를 등록하는 <code class="language-plaintext highlighter-rouge">registerServiceWorker.js</code>파일이 생성되어 있고 <code class="language-plaintext highlighter-rouge">main.js</code>에는 다음과 같이 추가되어 있을 겁니다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Register Service worker</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">./registerServiceWorker</span><span class="dl">'</span>
</code></pre></div></div>
<p>vue.config.js에 별다른 설정을 하지 않아도 다음과 같이 빌드 된 서비스를 실행해서 개발자 도구의 애플리케이션 탭을 열어 보면 서비스 워커가 등록되어 있고 캐시 저장 공간이 생성된 것을 볼 수 있습니다.</p>
<ul>
<li>서비스 워커</li>
</ul>
<p><img src="/assets/images/posts/2023/04/05/001.png" /></p>
<ul>
<li>캐시 저장공간</li>
</ul>
<p><img src="/assets/images/posts/2023/04/05/002.png" /></p>
<p>캐시를 지우고 서비스 워커를 등록 취소한다음 새로고침 해보면 다시 서비스 워커가 등록되면서 빌드 시 생성되었던 정적 파일들이 캐싱 되는 것을 볼 수 있습니다.</p>
<h2 id="workbox-설정">workbox 설정</h2>
<p>@vue/cli-plugin-pwa의 가이드 문서를 살펴보면 workbox의 옵션을 지원하는 것을 알 수 있는데 주의할 점이 오리진이 다른 경우에는 <code class="language-plaintext highlighter-rouge">path</code>뿐만 아니라 앞부분까지 전체를 입력해 주어야 합니다. urlPattern에 <code class="language-plaintext highlighter-rouge">path</code>에 대한 정규 표현식만 넣으면 오리진이 다른 네트워크 요청에 대해서는 캐싱을 하지 못 합니다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// vue.config.js</span>
<span class="nx">workboxPluginMode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">GenerateSW</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">workboxOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="nl">runtimeCaching</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">urlPattern</span><span class="p">:</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://cdn</span><span class="se">\\</span><span class="s1">.third-party-site</span><span class="se">\\</span><span class="s1">.com.*/styles/.*</span><span class="se">\\</span><span class="s1">.css</span><span class="dl">'</span><span class="p">),</span>
<span class="na">handler</span><span class="p">:</span> <span class="dl">'</span><span class="s1">CacheFirst</span><span class="dl">'</span><span class="p">,</span>
<span class="na">options</span><span class="p">:</span> <span class="p">{</span>
<span class="na">cacheName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">json-cache-v1</span><span class="dl">'</span><span class="p">,</span>
<span class="na">expiration</span><span class="p">:</span> <span class="p">{</span>
<span class="na">maxAgeSeconds</span><span class="p">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">24</span> <span class="c1">// 1 day</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="s3-cors-정책-설정">S3 CORS 정책 설정</h2>
<p>S3 콘솔에서는 선택한 버킷의 권한 탭에 CORS(Cross-origin 리소스 공유)에서 다음과 같이 CORS 정책을 설정할 수 있습니다.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"AllowedHeaders"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"*"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"AllowedMethods"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"GET"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"AllowedOrigins"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"*"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"ExposeHeaders"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"MaxAgeSeconds"</span><span class="p">:</span><span class="w"> </span><span class="mi">3000</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<h2 id="클라우드-프론트-cors-정책-설정">클라우드 프론트 CORS 정책 설정</h2>
<p>클라우드 프론트 콘솔에서는 선택한 배포의 동작 탭에서 편집 화면에 들어가면 <code class="language-plaintext highlighter-rouge">캐시 키 및 원본 요청</code> 의 <code class="language-plaintext highlighter-rouge">응답 헤더 정책 - 선택사항</code>에서 클라이언트에게 내려줄 response의 헤더 정책을 생성하고 선택할 수 있습니다.</p>
<p><img src="/assets/images/posts/2023/04/05/003.png" /></p>
<p>새로운 정책 생성을 선택하면 Access-Control-Allow-Origin 등 CORS 관련 헤더 값을 설정할 수 있습니다</p>
<p>여기서 끝이 아니고 하단에 <code class="language-plaintext highlighter-rouge">사용자 지정 헤더 - 선택사항</code>에서 Service-Worker-Allowed 헤더에 호출하는 도메인 허용까지 해 주고 나서야 CORS로 요청한 리소스를 서비스 워커가 캐싱 할 수 있게 됩니다.</p>
<p><img src="/assets/images/posts/2023/04/05/004.png" /></p>
<h2 id="정리">정리</h2>
<p>오리진이 다른 리소스를 호출해서 서비스 워커로 캐싱하는 과정에서 제대로 캐싱이 되지 않았던 원인은 다음과 같았습니다.</p>
<p>우선, vue.config.js의 pwa 설정을 할 때 URL 패턴에는 path 뿐만 아니라 전체 도메인까지 포함해야 합니다. 또한 클라우드 프론트의 응답 헤더에는 Access-Control-Allow-origin뿐만 아니라 Service-Worker-Allowed 헤더까지 추가해줘야 합니다.</p>
<p>이러한 설정들이 되어 있지 않으면 서비스 워커는 오리진이 다른 리소스를 캐싱하지 못하고 매번 네트워크 요청을 보내기 때문에 캐시 효과를 얻을 수 없습니다.</p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://joshua1988.github.io/vue-camp/pwa/workbox-caching.html#%E1%84%85%E1%85%A5%E1%86%AB%E1%84%90%E1%85%A1%E1%84%8B%E1%85%B5%E1%86%B7-%E1%84%8F%E1%85%A2%E1%84%89%E1%85%B5%E1%86%BC">https://joshua1988.github.io/vue-camp/pwa/workbox-caching.html#%E1%84%85%E1%85%A5%E1%86%AB%E1%84%90%E1%85%A1%E1%84%8B%E1%85%B5%E1%86%B7-%E1%84%8F%E1%85%A2%E1%84%89%E1%85%B5%E1%86%BC</a></li>
<li><a href="https://developer.chrome.com/docs/workbox/reference/workbox-build/#method-generateSW">https://developer.chrome.com/docs/workbox/reference/workbox-build/#method-generateSW</a></li>
<li><a href="https://developer.chrome.com/docs/workbox/modules/workbox-routing/">https://developer.chrome.com/docs/workbox/modules/workbox-routing/</a></li>
</ul>{"avatar"=>"/assets/images/avata.png", "location"=>"Seoul", "email"=>"seungwoo321@gmail.com", "github"=>"Seungwoo321"}seungwoo321@gmail.comVue CLI3의 vue add pwa 명령어를 실행하면 쉽게 PWA를 설정할 수 있습니다. 이것만으로 빌드 시 생성되는 파일이 서비스 워커에 의해 캐싱이 됩니다. 여기에 추가로 교차 오리진에서 호출하는 리소스를 캐싱 해본 경험을 정리했습니다.