<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_diver.log</title>
        <link>https://velog.io/</link>
        <description>더깊이</description>
        <lastBuildDate>Tue, 07 Feb 2023 02:48:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. dev_diver.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_diver" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[22. 02. 07 자바스크립트) 프로미스와 async, await]]></title>
            <link>https://velog.io/@dev_diver/22.-02.-07-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%EB%AF%B8%EC%8A%A4%EC%99%80-async-await</link>
            <guid>https://velog.io/@dev_diver/22.-02.-07-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%EB%AF%B8%EC%8A%A4%EC%99%80-async-await</guid>
            <pubDate>Tue, 07 Feb 2023 02:48:54 GMT</pubDate>
            <description><![CDATA[<h1 id="콜백callback">콜백(callback)</h1>
<p>브라우저나 node 등 자바스크립트 호스트 환경이 제공하는 여러 함수를 사용하면 <code>비동기(asynchronous)</code> 동작을 스케줄링할 수 있다. 즉, 원하는 때에 동작이 시작되도록 할 수 있다.</p>
<p><code>setTimeout</code>은 스케줄링에 사용되는 가장 대표적인 함수이다.</p>
<p>실무에서 만날 수 있는 비동기 동작은 다양하다. 스크립트나 모듈을 로딩하는 것 또한 비동기 동작 중 하나 이다. <code>src</code>에 있는 스크립트를 읽어오는 함수를 예시로 비동기 동작 처리가 어떻게 일어나는지 살펴보자.</p>
<p>아래 예시에서 함수 <code>loadScript(src)</code>는 <code>&lt;script src=&quot;...&quot;&gt;</code>를 동적으로 만들고, 문서에 추가한다. 브라우저는 자동으로 태그에 있는 스크립트를 불러오고, 로딩이 완료되면 스크립트를 실행한다.</p>
<pre><code class="language-javascript">function loadScript(src) {
  // &lt;script&gt; 태그를 만들고 페이지에 태그를 추가한다.
  // 태그가 페이지에 추가되면 src에 있는 스크립트를 로딩하고 실행한다.
  let script = document.createElement(&#39;script&#39;);
  script.src = src;
  document.head.append(script);
}

// 해당 경로에 있는 스크립트를 불러오고 실행한다.
loadScript(&#39;/my/script.js&#39;);</code></pre>
<p>이 때 스크립트는 비동기적으로 실행된다. 로딩은 지금 당장 시작되더라도, 실행은 함수가 끝난 후에야 되기 때문이다.</p>
<p>따라서 <code>loadScript</code> 아래에 있는 코드들은 스크립트 로딩이 종료되는 걸 기다리지 않는다.</p>
<pre><code class="language-javascript">loadScript(&#39;/my/script.js&#39;);
// 아래의 코드는 스크립트 로딩이 끝날 때가지 기다리지 않는다.
// ...</code></pre>
<p>스크립트 로딩이 끝나자마자 해당 스크립트를 사용해서 무언가를 해야한다고 가정해보자. 스크립트 안에는 다양한 함수가 정의되어 있고, 이 함수들을 실행하길 원하는 상황이다. 그런데 <code>loadScript</code>를 호출하자마자 내부 함수를 호출하면 원하는 대로 작동하지 않는다.</p>
<pre><code class="language-javascript">loadScript(&#39;/my/script.js&#39;); // script.js엔 &quot;function newFunction() {…}&quot;이 있다.

newFunction(); // 함수가 존재하지 않는다는 에러가 발생한다!</code></pre>
<p>에러는 브라우저가 스크립트를 읽어올 수 있는 시간을 충분히 확보하지 못했기 때문에 발생한다. 그런데 현재로서는 <code>loadScript</code>에서 스크립트 로딩이 완료되었는지 알 방법이 없다. 언젠가 스크립트가 로드되고 실행도 되겠지만, 그게 다이다. 원하는 대로 스크립트 안의 함수나 변수를 사용하려면 스크립트 로딩이 끝났는지 여부를 알 수 있어야 한다.</p>
<p>따라서, <code>loadScript</code>의 두 번째 인수로 스크립트 로딩이 끝난 후 실행될 함수인 <code>콜백</code> 함수를 추가해보자. 콜백 함수는 나중에 호출할 함수를 의미한다.</p>
<pre><code class="language-javascript">function loadScript(src, callback) {
  let script = document.createElement(&#39;script&#39;);
  script.src = src;

  script.onload = () =&gt; callback(script);

  document.head.append(script);
}</code></pre>
<p>새롭게 불러온 스크립트에 있는 함수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트 안의 함수를 사용할 수 있다. 이렇게 두 번째 인수로 전달된 함수는 예시처럼 외부 스크립트를 불러오는 것이 완료되었을 때 등 원하는 동작이 완료되었을 때 실행된다.</p>
<pre><code class="language-javascript">loadScript(&#39;/my/script.js&#39;, function(script) {
  // 콜백 함수는 스크립트 로드가 끝나면 실행된다.
  newFunction(); // 이제 함수 호출이 제대로 동작한다.
  //...
});</code></pre>
<p>이런 방식을 콜백 기반 비동기 프로그래밍이라고 한다. 무언가를 비동기적으로 수행하는 함수는 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어갈 <code>콜백</code>을 인수로 반드시 제공해야 한다.</p>
<h3 id="콜백-속-콜백">콜백 속 콜백</h3>
<p>만약 로드해야 될 스크립트가 두 개 있는 경우에는 어떻게 하면 두 스크립트를 순차적으로 불러올 수 있을까? 두 번째 스크립트 로딩은 첫 번째 스크립트의 로딩이 끝난 이후가 되길 원한다면?</p>
<p>가장 자연스러운 해결 방법은 아래와 같이 콜백함수 안에서 두 번째 스크립트를 호출하는 것이다.</p>
<pre><code class="language-javascript">loadScript(&#39;/my/script.js&#39;, function(script) {
  alert(`${script.src}을 로딩했습니다. 이젠, 다음 스크립트를 로딩합시다.`);

  loadScript(&#39;/my/script2.js&#39;, function(script) {
    alert(`두 번째 스크립트를 성공적으로 로딩했습니다.`);
  });
});</code></pre>
<p>이렇게 중첩 콜백을 만들면 바깥에 위치한 <code>loadScript</code>가 완료된 후, 안 쪽 <code>loadScript</code>가 실행된다. 그런데 이렇게 콜백 안에 콜백을 넣는 것은 수행하려는 동작이 단 몇 개라면 괜찮지만, 동작이 많은 경우에는 유지보수 등 좋지 않다.</p>
<h3 id="에러-핸들링">에러 핸들링</h3>
<p>스크립트 로딩이 실패할 경우, 콜백함수에서 에러를 핸들링할 수 있게 구현해보자.</p>
<pre><code class="language-javascript">function loadScript(src, callback) {
  let script = document.createElement(&#39;script&#39;);
  script.src = src;

  script.onload = () =&gt; callback(null, script);
  script.onerror = () =&gt; callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));

  document.head.append(script);
}

loadScript(&#39;/my/script.js&#39;, function(error, script) {
  if (error) {
    // 에러 처리
  } else {
    // 스크립트 로딩이 성공적으로 끝남
  }
});</code></pre>
<p>이제 <code>loadScript</code>는 스크립트 로딩에 성공하면 <code>callback(null, script)</code>을, 실패하면 <code>callback(error)</code>를 호출한다.</p>
<p>이렇게 에러를 처리하는 방식은 오류 우선 콜백이라 부르고, 흔히 사용되는 패턴이다.</p>
<p>오류 우선 콜백은 다음 관례를 따른다.</p>
<ul>
<li><code>callback</code>의 첫 번째 인수는 에러를 위해 남겨둔다. 에러가 발생하면 이 인수를 이용해서 <code>callback(err)</code>이 호출된다.</li>
<li>두 번째 인수 혹은 더 많은 인수는 에러가 발생하지 않았을 때를 위해 남겨둔다. 원하는 동작이 성공한 경우에는 <code>callback(null, result1, result2, ...)</code>이 호출된다.</li>
</ul>
<h3 id="콜백-헬callback-hell">콜백 헬(callback hell)</h3>
<p>콜백 기반 비동기 처리는 한 개 혹은 두 개의 중첩 호출이 있을 경우에는 쓸만하다. 하지만 비동기 동작이 많아지면 아래와 같은 코드 작성이 불가피해진다.</p>
<pre><code class="language-javascript">loadScript(&#39;1.js&#39;, function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript(&#39;2.js&#39;, function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript(&#39;3.js&#39;, function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // 모든 스크립트가 로딩된 후, 실행 흐름이 이어진다. (*)
          }
        });

      }
    })
  }
});</code></pre>
<p>보기만해도 어지럽다. 이렇게 깊은 중첩 코드가 만들어내는 패턴을 <code>콜백 지옥(callback hell)</code> 혹은 <code>멸망의 피라미드(pyramid of doom)</code>이라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/b2cb2d01-d1ae-4666-87ac-301fffac9e63/image.png" alt=""></p>
<p>비동기 동작이 하나씩 추가될 때마다 중첩 호출이 만들어내는 피라미드는 오른쪽으로 점점 커진다. 따라서 이런 코딩 방식은 좋지 않다.</p>
<p>이제 각 동작을 독립적인 함수로 만들어 위와 같은 문제를 완화해 보자.</p>
<pre><code class="language-javascript">loadScript(&#39;1.js&#39;, step1);

function step1(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript(&#39;2.js&#39;, step2);
  }
}

function step2(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript(&#39;3.js&#39;, step3);
  }
}

function step3(error, script) {
  if (error) {
    handleError(error);
  } else {
    // 모든 스크립트가 로딩되면 다른 동작을 수행한다. (*)
  }
};</code></pre>
<p>새롭게 작성한 코드는 각 동작을 분리해 최상위 레벨의 함수로 만들었기 때문에 깊은 중첩이 없다. 이렇게 작성하면 동작상의 문제는 없다. 하지만 코드의 연결성이 떨어져 보이고, 가독성이 떨어진다.</p>
<hr>
<h1 id="프로미스promise">프로미스(promise)</h1>
<ul>
<li><code>제작 코드(producing code)</code> : 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 한다.</li>
<li><code>소비 코드(consuming code)</code> : 제작 코드의 결과를 기다렸다가 이를 소비한다. 이 때 소비 주체인 함수는 여럿이 될 수 있다.</li>
<li><code>프로미스(promise)</code> : 제작 코드와 소비 코드를 연결해 주는 특별한 자바스크립트 객체이다. 프로미스는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 제작 코드가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 잇도록 해준다.</li>
</ul>
<p><code>promise</code> 객체는 아래와 같은 문법으로 만들 수 있다.</p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  // excutor 제작 코드
});</code></pre>
<p><code>new Promise</code>에 전달되는 함수는 <code>executor(실행자, 실행 함수)</code>라 부른다. <code>executor</code>는 <code>new Promise</code>가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다.</p>
<p><code>executor</code>의 인수 <code>resolve</code>와 <code>reject</code>는 자바스크립트에서 자체 제공하는 콜백이다. 따라서, 개발자는 <code>resolve</code>와 <code>reject</code>를 신경 쓰지 않고, <code>executor</code> 안의 코드만 작성하면 된다.</p>
<p>대신 <code>executor</code>에서는 결과를 즉시 얻든지 늦게 얻든지 상관없이, 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다.</p>
<ul>
<li><code>resolve(value)</code> : 일이 성공적으로 끝난 경우 그 결과를 나타내는 <code>value</code>와 함께 호출</li>
<li><code>reject(error)</code> : 에러 발생 시 에러 객체를 나타내는 <code>error</code>와 함께 호출</li>
</ul>
<p>요약하면 다음과 같다. <code>executor</code>는 자동으로 실행되는데 여기서 원하는 일이 처리된다. 처리가 끝나면 <code>executor</code>는 처리 성공 여부에 따라 <code>resolve</code>나 <code>reject</code>를 호출한다.</p>
<p><code>new Promise</code> 생성자가 반환하는 프로미스 객체는 다음과 같은 숨김 프로퍼티를 갖는다.</p>
<ul>
<li><code>state</code> : 처음에는 <code>pending</code> 상태였다가 <code>resolve</code>가 호출되면 <code>fulfilled</code>, <code>reject</code>가 호출되면 <code>rejected</code>로 변한다.</li>
<li><code>result</code> : 처음에는 값이 <code>undefined</code> 였다가 <code>resolve(value)</code>가 호출되면 <code>value</code>로, <code>reject(error)</code>가 호출되면 <code>error</code>로 변한다.</li>
</ul>
<p>정리하면 <code>executor</code>는 아래 그림과 같이 프로미스 객체의 상태를 둘 중 하나로 변화시킨다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c72d124d-72dc-4722-8c98-aa2fc94418f3/image.png" alt=""></p>
<p>프로미스 생성자와 간단한 <code>executor</code> 함수로 만든 예시를 살펴보자. <code>setTimeout</code>을 이용해서 <code>executor</code> 함수는 약간의 시간이 걸리도록 구현했다.</p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  // 프로미스가 만들어지면 executor 함수는 자동으로 실행된다.

  // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 프로미스의 result 프로퍼티는 완료가 된다.
  setTimeout(() =&gt; resolve(&quot;완료&quot;), 1000);
});</code></pre>
<p><code>executor</code>가 실행된 지 1초 후, <code>resolve(&quot;완료&quot;)</code>가 호출되고 결과가 만들어진다. 이 때 프로미스 객체의 상태는 아래와 같이 변한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c6da86c2-7784-47f8-9f42-c2374cb6c8a3/image.png" alt=""></p>
<p>위 예시를 통해서 우리가 알 수 있는 것은 두 가지이다.</p>
<ul>
<li><code>executor</code>는 <code>new Promise</code>에 의해 자동으로 그리고 즉각적으로 호출된다.</li>
<li><code>executor</code>는 인자로 <code>resolve</code>와 <code>reject</code> 함수를 받는다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 개발자가 따로 만들 필요가 없다. 다만, <code>resolve</code>나 <code>reject</code> 중 하나는 반드시 호출해야 한다.</li>
</ul>
<p>이처럼 일이 성공적으로 처리되었을 때의 프로미스는 <code>fulfilled promise(약속이 이행된 프로미스)</code>라고 부른다.</p>
<p>이번에는 <code>executor</code>가 에러와 함께 약속한 작업을 거부하는 경우에 대해 살펴보자.</p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보낸다.
  setTimeout(() =&gt; reject(new Error(&quot;에러 발생&quot;)), 1000);
});</code></pre>
<p>1초 후 <code>reject</code>가 호출되면 프로미스의 상태가 <code>rejected</code>로 변한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/7418f177-c34f-4169-95aa-058a38ae3ae4/image.png" alt=""></p>
<p>지금까지 배운 내용을 요약해보자. <code>executor</code>는 보통 시간이 걸리는 일을 수행한다. 일이 끝나면 <code>resolve</code>나 <code>reject</code> 함수를 호출하는데, 이 때 프로미스 객체의 상태가 변화한다. <code>이행(resolved</code> 혹은 <code>거부(rejected)</code> 상태의 프로미스는 <code>처리된(settled)</code> 프로미스라고 부른다. 반면, 처리되지 않은 프로미스는 <code>대기 상태의(pending)</code> 프로미스라고 부른다.</p>
<p>프로미스는 성공 또는 실패 둘 중 하나만 한다. <code>executor</code>는 <code>resolve</code>나 <code>reject</code> 중 하나를 반드시 호출해야 한다. 이때 변경된 상태는 더 이상 변하지 않는다. 즉, 처리가 끝난 프로미스에 <code>resolve</code>와 <code>reject</code>를 호출하면 무시된다.</p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  resolve(&quot;완료&quot;);

  reject(new Error(&quot;…&quot;)); // 무시됨
  setTimeout(() =&gt; resolve(&quot;…&quot;)); // 무시됨
});</code></pre>
<p>이렇게 <code>executor</code>에 의해 처리가 끝난 프로미스는 결과 혹은 에러만 가질 수 있다. 또, <code>resolve</code>와 <code>reject</code>는 인수를 1개만 받거나 아무것도 받지 않고, 그 이외의 인수는 무시한다.</p>
<h3 id="then-catch-finally">then, catch, finally</h3>
<p>프로미스 객체는 <code>executor</code>와 소비 함수를 이어주는 역할을 한다. 소비 함수는 <code>then</code>, <code>catch</code>, <code>finally</code> 메서드를 사용해서 등록되고, 결과나 에러를 받을 수 있다.</p>
<p>또, 프로미스 객체의 <code>state</code>, <code>result</code> 프로퍼티는 숨김 프로퍼티이므로 개발자가 직접 접근할 수 없다. 대신에 <code>then</code>, <code>catch</code>, <code>finally</code> 메서드를 사용해서 접근 가능하다.</p>
<p><code>then</code> 메서드의 문법은 다음과 같다. <code>then</code> 메서드의 첫 번째 인수는 프로미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받는다. <code>then</code> 메서드의 두 번째 인수는 프로미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.</p>
<pre><code class="language-javascript">promise.then(
  function(result) { ... }
  function(result) { ... }
);</code></pre>
<p>아래 예시는 성공적으로 이행된 프로미스와 프로미스가 거부된 경우에 따라 어떻게 반응하는지 보여준다.</p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  setTimeout(() =&gt; resolve(&quot;완료&quot;), 1000);
});

// resolve 함수는 then의 첫 번째 함수를 실행한다.
promise.then(
  result =&gt; alert(result), // 1초 후 &quot;완료!&quot;를 출력
  error =&gt; alert(error) // 실행되지 않음
);

let promise = new Promise(function(resolve, reject) {
  setTimeout(() =&gt; reject(new Error(&quot;에러 발생!&quot;)), 1000);
});

// reject 함수는 .then의 두 번째 함수를 실행한다.
promise.then(
  result =&gt; alert(result), // 실행되지 않음
  error =&gt; alert(error) // 1초 후 &quot;Error: 에러 발생!&quot;을 출력
);</code></pre>
<p>작업이 성공적으로 처리된 경우만 다루고 싶다면 <code>then</code>에 인수를 하나만 전달하면 된다.</p>
<pre><code class="language-javascript">let promise = new Promise(resolve =&gt; {
  setTimeout(() =&gt; resolve(&quot;완료!&quot;), 1000);
});

promise.then(alert); // 1초 뒤 &quot;완료!&quot; 출력</code></pre>
<p>에러가 발생한 경우만 다루고 싶다면 <code>catch</code> 메서드를 사용하면 된다.</p>
<pre><code class="language-javascript">let promise = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; reject(new Error(&quot;에러 발생!&quot;)), 1000);
});

// .catch(f)는 promise.then(null, f)과 동일하게 작동한다
promise.catch(alert); // 1초 뒤 &quot;Error: 에러 발생!&quot; 출력</code></pre>
<p><code>finally</code> 메서드를 사용해서 이행이나 거부 상태에 상관없이 프로미스가 처리되면 항상 함수를 실행하게 할 수 있다. 쓸모 없어진 로딩표시를 멈추는 경우와 같이 결과 어떻든 마무리가 필요할 때 유용하다.</p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */
})
  // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
  .finally(() =&gt; 로딩 인디케이터 중지)
  .then(result =&gt; result 혹은 err =&gt; error)</code></pre>
<p>또, <code>finally</code> 메서드는 프로미스의 성공이나 실패 여부를 몰라도 되기 때문에 인수가 없고, 자동으로 다음 핸들러(메서드)에 결과와 에러를 전달한다.</p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&quot;결과&quot;), 2000)
})
  .finally(() =&gt; alert(&quot;프라미스가 준비되었습니다.&quot;))
  .then(result =&gt; alert(result)); // &lt;-- .then에서 result를 다룰 수 있음

new Promise((resolve, reject) =&gt; {
  throw new Error(&quot;에러 발생!&quot;);
})
  .finally(() =&gt; alert(&quot;프라미스가 준비되었습니다.&quot;))
  .catch(err =&gt; alert(err)); // &lt;-- .catch에서 에러 객체를 다룰 수 있음</code></pre>
<p>프로미스가 대기 상태 일 때, <code>then / catch / finally</code> 메서드는 프로미스가 처리되길 기다린다. 프로미스가 처리 상태가 된다면 핸들러가 즉각 실행된다.</p>
<h3 id="프로미스로-어떻게-비동기-동작을-처리하는지-예시">프로미스로 어떻게 비동기 동작을 처리하는지 예시</h3>
<p>앞 서 콜백으로 구현한 스크립트 로딩에 사용되는 함수를 프로미스로 구현해보자.</p>
<pre><code class="language-javascript">function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement(&#39;script&#39;);
    script.src = src;

    script.onload = () =&gt; resolve(script);
    script.onerror = () =&gt; reject(new Error(`${src}를 불러오는 도중에 에러가 발생함`));

    document.head.append(script);
  });
}

let promise = loadScript(&quot;https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js&quot;);

promise.then(
  script =&gt; alert(`${script.src}을 불러왔습니다!`),
  error =&gt; alert(`Error: ${error.message}`)
);

promise.then(script =&gt; alert(&#39;또다른 핸들러...&#39;));</code></pre>
<p>프로미스를 사용한 코드와 콜백 기반 코드의 차이점은 아래와 같다.</p>
<ul>
<li><code>프로미스</code><ul>
<li>흐름이 자연스럽고 유연한 코드를 작성할 수 있다. <code>loadScript(src)</code>로 스크립트를 읽고, 결과에 따라 그 다음 무엇을 하지에 대한 코드를 작성하면 된다<code>(then)</code>.</li>
<li>프로미스에 원하는 만큼 <code>then</code>을 호출할 수 있다.</li>
</ul>
</li>
<li><code>콜백</code><ul>
<li><code>loadScript(src, callback)</code>를 호출할 때, 함께 호출할 <code>callback</code> 함수가 준비되어 있어야 한다. <code>loadScript</code>를 호출하기 이전에 호출 결과로 무엇을 할지 미리 알고 있어야 한다.</li>
<li>콜백은 하나만 가능하다.</li>
</ul>
</li>
</ul>
<hr>
<h1 id="프로미스-체이닝promise-chaining">프로미스 체이닝(promise chaining)</h1>
<p>프로미스 체이닝은 아래와 같이 생겼다. <code>result</code>가 <code>then</code> 메서드의 체인을 통해 전달된다.</p>
<pre><code class="language-javascript">new Promise(function(resolve, reject) {
  setTimeout(() =&gt; resolve(1), 1000);
}).then(function(result) {
  alert(result); // 1
  return result * 2;
}).then(function(result) {
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/d65142ca-e8e3-423a-9ba4-31cc3ec7f400/image.png" alt=""></p>
<p>프로미스 체이닝이 가능한 이유는 <code>promise.then(handler)</code>을 호출하면 프로미스가 반환되기 때문이다. 반환된 프로미스에는 당연히 <code>then(handler)</code> 메서드를 또 호출할 수 있다. <code>then(handler)</code> 메서드의 인수인 핸들러가 값을 반환하면 해당 반환값이 <code>then</code> 메서드가 반환한 프로미스의 <code>result</code>가 된다.</p>
<p>참고로 아래 예시는 프로미스 체이닝이 아니므로 주의해야 한다. 아래 예시의 <code>then</code> 메서드들은 <code>result</code>를 순차적으로 전달하지 않고 독립적으로 처리한다. </p>
<pre><code class="language-javascript">let promise = new Promise(function(resolve, reject) {
  setTimeout(() =&gt; resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});</code></pre>
<h3 id="프로미스-반환하기">프로미스 반환하기</h3>
<p><code>then(handler)</code> 메서드에 사용되는 핸들러가 프로미스를 생성하거나 반환하는 경우도 있다. 이 경우에 이어지는 핸들러는 프로미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받는다.</p>
<pre><code class="language-javascript">new Promise(function(resolve, reject) {
  setTimeout(() =&gt; resolve(1), 1000);
}).then(function(result) {
  alert(result); // 1
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; reslove(result * 2), 1000);
  });
}).then(function(result) { // (**)
  alert(result); // 2
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(result * 2), 1000);
  });
}).then(function(result) {
  alert(result); // 4
});</code></pre>
<p>위 예시에서 첫 번째 <code>then</code> 메서드는 1을 출력하고 프로미스를 반환한다. 1초 후에 해당 프로미스가 이행되고, 그 결과는 두 번째 <code>then</code> 메서드로 전달된다. 이후 반복된다.</p>
<h3 id="loadscript-예시-개선하기">loadScript 예시 개선하기</h3>
<p>지금까지 배운 기능을 사용해서 프로미스를 사용해 정의한 <code>loadScript</code>를 개선해보자.</p>
<pre><code class="language-javascript">loadScript(&quot;/article/promise-chaining/one.js&quot;)
  .then(script =&gt; loadScript(&quot;/article/promise-chaining/two.js&quot;))
  .then(script =&gt; loadScript(&quot;/article/promise-chaining/three.js&quot;))
  .then(script =&gt; {
    // 스크립트를 정상적으로 불러왔기 때문에 스크립트 내의 함수를 호출할 수 있다.
    one();
    two();
    three();
  });</code></pre>
<p>위 예시에서 <code>loadScript</code>를 호출할 때마다 프로미스가 반환되고, 다음 <code>then</code>은 해당 프로미스가 이행되었을 때 실행된다. 스크립트들은 이런 과정을 거쳐 순차적으로 로드된다.</p>
<p>프로미스 체인에 더 많은 비동기 동작을 추가할 수도 있는데, 추가 작업이 많아져도 코드가 오른쪽으로 길어지지 않고 아로래만 증가해서 멸망의 피라미드가 만들어지지 않는다.</p>
<p>아래와 같이 각 <code>loadScript</code>에 <code>then</code>을 바로 붙일 수도 있다.</p>
<pre><code class="language-javascript">loadScript(&quot;/article/promise-chaining/one.js&quot;).then(script1 =&gt; {
  loadScript(&quot;/article/promise-chaining/two.js&quot;).then(script2 =&gt; {
    loadScript(&quot;/article/promise-chaining/three.js&quot;).then(script3 =&gt; {
      // 여기서 script1, script2, script3에 정의된 함수를 사용할 수 있다.
      one();
      two();
      three();
    });
  });
});</code></pre>
<p>위 방식을 사용해도 동일한 동작을 수행하지만, 코드가 오른쪽으로 길어져 멸망의 피라미드가 만들어진다. 위 방식이 단점만 있는 것은 아니다. 중첩 함수에서 외부 스코프에 접근할 수 있기 때문에, 가장 깊은 곳에 있는 중첩 콜백에서 <code>script1</code>, <code>script2</code>, <code>script3</code> 변수 모두에 접근할 수 있다.</p>
<h3 id="fetch와-프로미스-체이닝-응용하기">fetch와 프로미스 체이닝 응용하기</h3>
<p>프론트엔드단에서는 네트워크 요청 시에 프로미스를 자주 사용한다.</p>
<p><code>fetch</code> 메서드를 사용해서 원격 서버에서 사용자 정보를 가져오는 예시를 살펴보자.</p>
<pre><code class="language-javascript">let promise = fetch(url);</code></pre>
<p>위 코드를 실행하면 <code>url</code>에 네트워크 요청을 보내고 프로미스를 반환한다. 원격 서버가 헤더와 함께 응답을 보내면 프로미스는 <code>response</code> 객체와 함께 이행된다. 그런데 이때 <code>response</code> 전체가 완전히 다운로드되기 전에 프로미스는 이행 상태가 된다.</p>
<p>응답이 완전히 종료되고, 응답 전체를 읽으려면 <code>response.text()</code> 메서드를 호출해야 한다. <code>response.text()</code> 메서드는 원격 서버에서 전송한 텍스트 전체가 다운로드되면, 해당 텍스트를 <code>result</code> 값으로 갖는 이행된 프로미스를 반환한다.</p>
<pre><code class="language-javascript">fetch(&#39;/article/promise-chaining/user.json&#39;)
  // 원격 서버가 응답하면 .then 아래 코드가 실행된다.
  .then(function(response) {
    // response.text()는 응답 텍스트 전체가 다운로드되면
    // 응답 텍스트를 새로운 이행 프라미스로 만들고, 이를 반환한다.
    return response.text();
  })
  .then(function(text) {
    // 원격에서 받아온 파일의 내용
    alert(text); // {&quot;name&quot;: &quot;Violet-Bora-Lee&quot;, &quot;isAdmin&quot;: true}
  });</code></pre>
<p>또, <code>response.json()</code> 메서드를 쓰면 원격에서 받아온 데이터를 읽고 JSON으로 파싱할 수 있다.</p>
<pre><code class="language-javascript">// 위 코드와 동일한 기능을 하지만, response.json()은 원격 서버에서 불러온 내용을 JSON으로 변경한다.
fetch(&#39;/article/promise-chaining/user.json&#39;)
  .then(response =&gt; response.json())
  .then(user =&gt; alert(user.name)); // Violet-Bora-Lee, 이름만 성공적으로 가져옴</code></pre>
<p>예시를 더 살펴보자. 아래 예시는 깃허브에 요청을 보내서 사용자 프로필을 불러오고 아바타를 출력한다.</p>
<pre><code class="language-javascript">// user.json에 요청을 보낸다.
fetch(&#39;/article/promise-chaining/user.json&#39;)
  // 응답받은 내용을 json으로 불러온다.
  .then(response =&gt; response.json())
  // GitHub에 요청을 보낸다.
  .then(user =&gt; fetch(`https://api.github.com/users/${user.name}`))
  // 응답받은 내용을 json 형태로 불러온다,
  .then(response =&gt; response.json())
  // 3초간 아바타 이미지(githubUser.avatar_url)를 보여준다.
  .then(githubUser =&gt; {
    let img = document.createElement(&#39;img&#39;);
    img.src = githubUser.avatar_url;
    img.className = &quot;promise-avatar-example&quot;;
    document.body.append(img);

    setTimeout(() =&gt; img.remove(), 3000); // (*)
  });</code></pre>
<p><code>(*)</code>로 표시한 줄에서 아바타가 사라지고 무언가를 더 하고 싶을 때는 어떻게 할까? 프로미스 체인을 확장하려면 아바타가 사라질 때 이행 프로미스를 반환해주어야 한다.</p>
<pre><code class="language-javascript">fetch(&#39;/article/promise-chaining/user.json&#39;)
  .then(response =&gt; response.json())
  .then(user =&gt; fetch(`https://api.github.com/users/${user.name}`))
  .then(response =&gt; response.json())
  .then(githubUser =&gt; new Promise(function(resolve, reject) { // (*)
    let img = document.createElement(&#39;img&#39;);
    img.src = githubUser.avatar_url;
    img.className = &quot;promise-avatar-example&quot;;
    document.body.append(img);

    setTimeout(() =&gt; {
      img.remove();
      resolve(githubUser); // (**)
    }, 3000);
  }))
  // 3초 후 동작함
  .then(githubUser =&gt; alert(`${githubUser.name}의 이미지를 성공적으로 출력하였습니다.`));</code></pre>
<p><code>(*)</code> 로 표시한 곳의 <code>then</code> 메서드의 핸들러는 이제 <code>setTimeout</code> 안의 <code>resolve</code>를 호출했을 때만 처리상태가 되는 <code>new Promise</code>를 반환한다. 체인의 다음 <code>then</code> 메서드는 해당 프로미스의 처리를 기다린다.</p>
<p>비동기 동작은 항상 프로미스를 반환하도록 하는 것이 좋다. 지금은 당장 체인을 확장할 계획이 없더라도 이렇게 구현해 놓으면 나중에 체인 확장이 필요한 경우에 손쉽게 체인을 확장할 수 있다.</p>
<p>위에서 살펴본 예시를 재사용 가능한 함수 단위로 분리한 코드를 살펴보자.</p>
<pre><code class="language-javascript">function loadJson(url) {
  return fetch(url)
    .then(response =&gt; response.json());
}

function loadGithubUser(name) {
  return fetch(`https://api.github.com/users/${name}`)
    .then(response =&gt; response.json());
}

function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement(&#39;img&#39;);
    img.src = githubUser.avatar_url;
    img.className = &quot;promise-avatar-example&quot;;
    document.body.append(img);

    setTimeout(() =&gt; {
      img.remove();
      resolve(githubUser);
    }, 3000);
  });
}

// 함수를 이용하여 다시 동일 작업 수행
loadJson(&#39;/article/promise-chaining/user.json&#39;)
  .then(user =&gt; loadGithubUser(user.name))
  .then(showAvatar)
  .then(githubUser =&gt; alert(`Finished showing ${githubUser.name}`));
  // ...</code></pre>
<hr>
<h1 id="프로미스와-에러-핸들링">프로미스와 에러 핸들링</h1>
<p>프로미스가 거부되면 제어 흐름이 제일 가까운 <code>rejection</code> 핸들러로 넘어간다. 따라서, 프로미스 체인을 사용하면 에러를 쉽게 처리할 수 있다.</p>
<p>존재하지 않는 주소를 <code>fetch</code>에 넘겨주는 예시를 살펴보자.<code>catch</code> 메서드에서 에러를 처리한다.</p>
<pre><code class="language-javascript">fetch(&#39;https://no-such-server.blabla&#39;) // 거부
  .then(response =&gt; response.json())
  .catch(err =&gt; alert(err)) // TypeError: failed to fetch (출력되는 내용은 다를 수 있음)</code></pre>
<p><code>catch</code> 메서드는 첫 번째 핸들러일 필요가 없고, 하나 혹은 여러 개의 <code>then</code> 메서드 뒤에 올 수 있다.</p>
<pre><code class="language-javascript">fetch(&#39;/article/promise-chaining/user.json&#39;)
  .then(response =&gt; response.json())
  .then(user =&gt; fetch(`https://api.github.com/users/${user.name}`))
  .then(response =&gt; response.json())
  .then(githubUser =&gt; new Promise((resolve, reject) =&gt; {
    let img = document.createElement(&#39;img&#39;);
    img.src = githubUser.avatar_url;
    img.className = &quot;promise-avatar-example&quot;;
    document.body.append(img);

    setTimeout(() =&gt; {
      img.remove();
      resolve(githubUser);
    }, 3000);
  }))
  .catch(error =&gt; alert(error.message));</code></pre>
<p>정상적인 경우라면 <code>catch</code>는 트리거 되지 않는다. 그런데 네트워크 문제나 잘못된 형식의 JSON 등으로 위쪽 프로미스 중 하나라도 거부되면 <code>catch</code>에서 에러를 잡게 된다.</p>
<h3 id="암시적-try-catch">암시적 try catch</h3>
<p>프로미스 <code>executor</code> 주위에는 암시적인 <code>try catch</code>가 있다. 예외가 발생하면 <code>암시적 try catch</code>에서 예외를 잡고, 이를 <code>reject</code>처럼 다룬다.</p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  throw new Error(&quot;에러 발생!&quot;);
}).catch(alert); // Error: 에러 발생!

// 위 예시는 아래와 같은 동작을 한다.
new Promise((resolve, reject) =&gt; {
  reject(new Error(&quot;에러 발생!&quot;));
}).catch(alert); // Error: 에러 발생!</code></pre>
<p><code>executor</code> 뿐만 아니라 프로미스 핸들러에서도 발생한다. <code>then</code> 핸들러 안에서 <code>throw</code>를 사용해서 에러를 던지면, 제어 흐름이 가장 가까운 에러 핸들러로 넘어간다.</p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  resolve(&quot;OK&quot;);
}).then((result) =&gt; {
  throw new Error(&quot;에러 발생!&quot;); // 프로미스가 거부됨
}).catch(alert); // Error: 에러 발생!</code></pre>
<p><code>throw</code> 문이 만든 에러뿐만 아니라 모든 종류의 에러가 <code>암시적 try catch</code>에서 처리된다.</p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  resolve(&quot;OK&quot;);
}).then((result) =&gt; {
  blabla(); // 존재하지 않는 함수
}).catch(alert); // ReferenceError: blabla is not defined</code></pre>
<h3 id="다시-던지기">다시 던지기</h3>
<p>프로미스 체인 마지막의 <code>catch</code>는 <code>try catch</code>와 유사한 역할을 한다. <code>then</code> 메서드를 원하는 만큼 사용하다가 마지막에 <code>catch</code> 메서드 하나만 붙이면 앞선 <code>then</code>  메서드에서 발생한 모든 에러를 처리할 수 있다.</p>
<p>일반적으로 <code>try catch</code>에서는 에러를 분석하고, 처리할 수 없는 에러로 판단되면 에러를 다시 던질 때가 있다. 프로미스에도 유사한 일을 할 수 있다.</p>
<p><code>catch</code>에서 에러가 성공적으로 처리되면 가장 가까운 곳에 있는 <code>then</code> 메서드로 제어 흐름이 넘어가서 실행이 이어진다.</p>
<pre><code class="language-javascript">// 실행 순서: catch -&gt; then
new Promise((resolve, reject) =&gt; {

  throw new Error(&quot;에러 발생!&quot;);

}).catch(function(error) {

  alert(&quot;에러가 잘 처리되었습니다. 정상적으로 실행이 이어집니다.&quot;);

}).then(() =&gt; alert(&quot;다음 핸들러가 실행됩니다.&quot;));</code></pre>
<p><code>catch</code> 안에서 에러를 처리할 수 없어서 <code>throw</code>를 사용하면 제어 흐름이 가장 가까운 곳에 있는 에러 핸들러로 에러를 다시 던진다.</p>
<pre><code class="language-javascript">// 실행 순서: catch -&gt; catch
new Promise((resolve, reject) =&gt; {

  throw new Error(&quot;에러 발생!&quot;);

}).catch(function(error) { // (*)

  if (error instanceof URIError) {
    // 에러 처리
  } else {
    alert(&quot;처리할 수 없는 에러&quot;);

    throw error; // 에러 다시 던지기
  }

}).then(function() {
  /* 여기는 실행되지 않는다. */
}).catch(error =&gt; { // (**)

  alert(`알 수 없는 에러가 발생함: ${error}`);
  // 반환값이 없음 =&gt; 실행이 계속됨

});</code></pre>
<h3 id="처리되지-못한-거부">처리되지 못한 거부</h3>
<p>아래 예시처럼 프로미스 체인 끝에 <code>catch</code>가 없어 에러를 처리하지 못하면 무슨 일이 발생할까?</p>
<pre><code class="language-javascript">new Promise(function() {
  noSuchFunction(); // 존재하지 않는 함수를 호출하기 때문에 에러가 발생함
})
  .then(() =&gt; {
    // 성공상태의 프라미스를 처리하는 핸들러. 한 개 혹은 여러 개가 있을 수 있음
  }); // 끝에 .catch가 없음!</code></pre>
<p>에러가 발생하면 프로미스는 거부상태가 되고, 실행 흐름은 가장 가까운 <code>rejection</code> 핸들러로 넘어간다. 그런데 위 예시에서는 예외를 처리할 핸들러가 없어서 에러가 갇혀버린다. 이런 식으로 코드에 처리하지 못한 에러가 남게 되면 실무에서는 끔찍한 일이 발생할 수 있다.</p>
<p>자바스크립트 엔진은 프로미스 <code>rejection</code>을 추적하다가 위와 같은 상황이 발생하면 전역 에러를 생성한다. 브라우저 환경에서는 이런 에러를 <code>unhandledrejection</code> 이벤트로 처리할 수 있다.</p>
<pre><code class="language-javascript">window.addEventListener(&#39;unhandledrejection&#39;, function(event) {
  // unhandledrejection 이벤트엔 두 개의 특수 프로퍼티가 있다.
  alert(event.promise); // [object Promise] - 에러를 생성하는 프라미스
  alert(event.reason); // Error: 에러 발생! - 처리하지 못한 에러 객체
});

new Promise(function() {
  throw new Error(&quot;에러 발생!&quot;);
}); // 에러를 처리할 수 있는 .catch 핸들러가 없음</code></pre>
<p>브라우저 환경에서 에러가 발생했는데 <code>catch</code>가 없으면 <code>unhandledrejection</code> 핸들러가 트리거 된다. <code>unhandledrejection</code> 핸들러는 에러 정보가 담긴 <code>event</code> 객체를 받기 때문에 이 핸들러 안에서 원하는 작업을 할 수 있다.</p>
<p>대개 이런 에러는 회복할 수 없다. 개발자로서 할 수 있는 최선의 방법은 사용자에게 문제 상황을 알리고, 가능하다면 서버에 에러 정보를 보내는 것이다.</p>
<hr>
<h1 id="프로미스-api">프로미스 API</h1>
<p><code>Promise</code> 클래스에는 5가지 정적 메서드가 있다.</p>
<h3 id="promiseall">Promise.all</h3>
<p>여러 개의 프로미스를 동시에 실행시키고 모든 프로미스가 준비될 때까지 기다린다고 해보자.</p>
<p>복수의 URL로 동시에 요청을 보내고, 다운로드가 모두 완료된 후에 콘텐츠를 처리할 때 사용할 수 있다.</p>
<pre><code class="language-javascript">let promise = Promise.all(promisesArr);</code></pre>
<p><code>Promise.all</code>은 요소 전체가 프로미스인 이터러블 객체(대개는 배열임)을 받고, 새로운 프로미스를 반환한다.</p>
<p>배열 안의 프로미스가 모두 처리되면 새로운 프로미스가 이행되는데, 배열 안 프로미스의 결과값을 담은 배열이 새로운 프로미스의 <code>result</code>가 된다.</p>
<p>아래 예시에서 <code>Propmise.all</code>은 3초 후에 처리되고, 반환된는 프로미스의 <code>result</code>는 <code>[1, 2, 3]</code> 이다.</p>
<pre><code class="language-javascript">Promise.all([
  new Promise(resolve =&gt; setTimeout(() =&gt; resolve(1), 3000)), // 1
  new Promise(resolve =&gt; setTimeout(() =&gt; resolve(2), 2000)), // 2
  new Promise(resolve =&gt; setTimeout(() =&gt; resolve(3), 1000))  // 3
]).then(alert); // 프라미스 전체가 처리되면 1, 2, 3이 반환.</code></pre>
<p>프로미스의 결과인 <code>result</code> 배열의 요소 순서는 <code>Promise.all</code>에 전달되는 프로미스 순서와 상응한다. 즉, 첫 번째 프로미스가 가장 늦게 이행되더라도 처리 결과는 배열의 첫 번째 요소에 저장된다.</p>
<p>작업해야 할 데이터가 담긴 배열을 프로미스 배열로 맵핑하고, 이 배열을 <code>Promise.all</code>로 감싸는 때에 많이 사용한다.</p>
<p>URL이 담긴 배열을 <code>fetch</code> 메서드로 처리하는 예시를 살펴보자.</p>
<pre><code class="language-javascript">let urls = [
  &#39;https://api.github.com/users/iliakan&#39;,
  &#39;https://api.github.com/users/Violet-Bora-Lee&#39;,
  &#39;https://api.github.com/users/jeresig&#39;
];

// fetch를 사용해 url을 프라미스로 매핑
let requests = urls.map(url =&gt; fetch(url));

// Promise.all은 모든 작업이 이행될 때까지 기다림
Promise.all(requests)
  .then(responses =&gt; responses.forEach(
    response =&gt; alert(`${response.url}: ${response.status}`)
  ));</code></pre>
<p>깃허브 유저네임이 담긴 배열을 사용해서 사용자 정보를 가져오는 예시를 살펴보자. 참고로,<code>id</code>를 기준으로 장바구니 목록을 불러올 때도 사용할 수 있다. </p>
<pre><code class="language-javascript">let names = [&#39;iliakan&#39;, &#39;Violet-Bora-Lee&#39;, &#39;jeresig&#39;];

let requests = names.map(name =&gt; fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
  .then(responses =&gt; {
    // 모든 응답이 성공적으로 이행됨
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`);
    }

    return responses;
  })
  // 응답 메시지가 담긴 배열을 response.json()로 매핑해, 내용을 읽음
  .then(responses =&gt; Promise.all(responses.map(r =&gt; r.json())))
  // JSON 형태의 응답 메시지는 파싱 되어 배열 &#39;users&#39;에 저장됨
  .then(users =&gt; users.forEach(user =&gt; alert(user.name)));</code></pre>
<p><code>Promise.all</code>에 전달되는 프로미스 중 하나라도 거부되면, <code>Promise.all</code>이 반환하는 프로미스는 에러와 함께 바로 거부된다. </p>
<p>아래 예시에서 2초 후 두 번째 프라미스가 거부되면서 Promise.all 전체가 거부되고, .catch가 실행된다. 거부 에러는 Promise.all 전체의 결과가 된다.</p>
<pre><code class="language-javascript">Promise.all([
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; resolve(1), 1000)),
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; reject(new Error(&quot;에러 발생!&quot;)), 2000)),
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; resolve(3), 3000))
]).catch(alert); // Error: 에러 발생!</code></pre>
<p><code>Promise.all</code>은 대개 프로미스가 요소인 이터러블 객체를 받지만, 요소가 프로미스가 아닐 경우에는 그대로 결과 배열에 전달된다. 이미 결과를 알고 있는 값은 이런 특징을 이용해서 <code>Promise.all</code>에 그냥 전달하면 된다.</p>
<pre><code class="language-javascript">Promise.all([
  new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(1), 1000)
  }),
  2,
  3
]).then(alert); // 1, 2, 3</code></pre>
<h3 id="promiseallsettled">Promise.allSettled</h3>
<p><code>Promise.all</code>은 프로미스가 하나라도 거절되면 전체를 거절한다. 반면, <code>Promise.allSettled</code>는 모든 프로미스가 처리될 때까지 기다린다. 반환되는 배열은 다음과 같은 요소들을 갖는다.</p>
<ul>
<li>응답이 성공할 경우 : <code>{ status: &quot;fulfilled&quot;, value: result }</code></li>
<li>에러가 발생한 경우 : <code>{ status: &quot;rejected&quot;, reason: error }</code></li>
</ul>
<p><code>fetch</code>를 사용해서 여러 사람의 정보를 가져온다고 해보자. 여러 요청 중 하나가 실패해도 다른 요청 결과는 여전히 필요하다. 이럴 때 <code>Promise.allSettled</code>를 사용한다.</p>
<pre><code class="language-javascript">let urls = [
  &#39;https://api.github.com/users/iliakan&#39;,
  &#39;https://api.github.com/users/Violet-Bora-Lee&#39;,
  &#39;https://no-such-url&#39;
];

Promise.allSettled(urls.map(url =&gt; fetch(url)))
  .then(results =&gt; { // (*)
    results.forEach((result, num) =&gt; {
      if (result.status == &quot;fulfilled&quot;) {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == &quot;rejected&quot;) {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });

// 위의 (*)는 아래와 같다.
[
  {status: &#39;fulfilled&#39;, value: ...응답...},
  {status: &#39;fulfilled&#39;, value: ...응답...},
  {status: &#39;rejected&#39;, reason: ...에러 객체...}
]</code></pre>
<h3 id="promiserace">Promise.race</h3>
<p><code>Promise.all</code>과 비슷하다. 다만 가장 먼저 처리되는 프로미스의 결과나 에러를 반환한다.</p>
<pre><code class="language-javascript">Promise.race([
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; resolve(1), 1000)),
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; reject(new Error(&quot;에러 발생!&quot;)), 2000)),
  new Promise((resolve, reject) =&gt; setTimeout(() =&gt; resolve(3), 3000))
]).then(alert); // 1</code></pre>
<hr>
<h1 id="프로미스화promisification">프로미스화(promisification)</h1>
<p>콜백을 받는 함수를 프로미스를 반환하는 함수로 바꾸는 것을 <code>프로미스화</code>라고 한다.</p>
<p>기능을 구현 하다 보면 콜백보다는 프로미스가 더 편리하기 때문에 콜백 기반 함수와 라이브러리를 프로미스를 반환하는 함수로 바꾸는 게 좋은 경우가 종종 생긴다.</p>
<p><code>loadScript</code> 예시를 프로미스화 해보자.</p>
<pre><code class="language-javascript">function loadScript(src, callback) {
  let script = document.createElement(&#39;script&#39;);
  script.src = src;

  script.onload = () =&gt; callback(null, script);
  script.onerror = () =&gt; callback(new Error(`${src}를 불러오는 도중에 에러가 발생함`));

  document.head.append(script);
}

// 사용법:
// loadScript(&#39;path/script.js&#39;, (err, script) =&gt; {...})

// 프로미스화
let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) =&gt; {
    loadScript(src, (err, script) =&gt; {
      if (err) reject(err)
      else resolve(script);
    });
  })
}

// 사용법:
// loadScriptPromise(&#39;path/script.js&#39;).then(...)</code></pre>
<p><code>loadScriptPromise</code>는 <code>loadScript</code>에 모든 일을 위임한다. <code>loadScript</code>의 콜백은 스크립트 로딩 상태에 따라 <code>이행</code> 혹은 <code>거부</code> 상태의 프로미스를 반환한다.</p>
<p>실무에서는 여러 개의 함수를 프로미스화할 일이 생길 수 있으므로, 헬퍼 함수를 만들어보자.</p>
<pre><code class="language-javascript">function promisify(f) {
  return function (...args) { // 래퍼 함수를 반환함
    return new Promise((resolve, reject) =&gt; {
      function callback(err, result) { // f에 사용할 커스텀 콜백
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback); // 위에서 만든 커스텀 콜백을 함수 f의 인수 끝에 추가한다.

      f.call(this, ...args); // 기존 함수를 호출한다.
    });
  };
};

// 사용법:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);</code></pre>
<p>이러한 방식들도 콜백을 완전히 대체하지는 못한다. 프로미스는 하나의 결과만 가질 수 있지만, 콜백은 여러 번 호출할 수 있기 때문이다. 따라서 프로미스화는 콜백을 단 한 번 호출하는 함수에만 적용하자.</p>
<hr>
<h1 id="마이크로태스크">마이크로태스크</h1>
<p>프로미스 핸들러 <code>then</code>, <code>catch</code>, <code>finally</code>는 항상 비동기적으로 실행된다. 프로미스가 즉시 이행되더라도, 해당 메서드들 보다 아래에 있는 코드들이 먼저 실행된다.</p>
<pre><code class="language-javascript">let promise = Promise.resolve();

promise.then(() =&gt; alert(&quot;프라미스 성공!&quot;));

alert(&quot;코드 종료&quot;); </code></pre>
<p>위 예시를 실행하면 프로미스가 즉시 실행되어도 코드 종료가 가장 먼저 출력되고, 프로미스 성공이 출력된다. 이는 마이크로태스크 큐 방식 때문이다.</p>
<h3 id="마이크로태스크-큐">마이크로태스크 큐</h3>
<p>비동기 작업을 처리할 때 ECMA에서는 <code>PromiseJobs</code>라는 내부 큐를 명시한다. V8 엔진에서는 이를 마이크로태스크 큐라 부른다.</p>
<p>실행할 것이 아무것도 남아있지 않을 때만 마이크로태스크 큐에 있는 작업이 실행되기 시작한다. 마이크로태스크 큐는 먼저 들어온 작업을 먼저 실행한다.</p>
<p>앞 선 예시에서 코드 종료가 먼저 출력되는 이유가 바로 여기에 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c2484cbc-5127-4cca-bad0-09c1b2a0d4d4/image.png" alt=""></p>
<p>프로미스 성공을 먼저 출력시키려면 어떻게 해야 할까? 방법은 간단하다. <code>then</code>을 사용해서 큐에 넣으면 된다. 즉, 어떤 코드를 <code>then, catch, finally</code> 메서드가 호출된 이후에 실행하고 싶다면 <code>then</code>을 체인에 추가하고 이 안에 코드를 넣으면 된다.</p>
<pre><code class="language-javascript">Promise.resolve()
  .then(() =&gt; alert(&quot;프라미스 성공!&quot;))
  .then(() =&gt; alert(&quot;코드 종료&quot;));</code></pre>
<hr>
<h1 id="async와-await">async와 await</h1>
<h3 id="async-함수">async 함수</h3>
<p>함수 앞에 <code>async</code>를 붙이면 해당 함수는 항상 프로미스를 반환한다. 프로미스가 아닌 값을 반환하더라도 이행 상태의 프로미스로 값을 감싸 이행된 프로미스가 반환되도록 한다.</p>
<p>아래 예시의 함수를 호출하면 <code>result</code>가 1인 이행 프로미스가 반환된다.</p>
<pre><code class="language-javascript">async function f() {
  return 1;
}

f().then(alert); // 1</code></pre>
<p>명시적으로 프로미스를 반환하는 것도 가능하다. 결과는 동일하다.</p>
<pre><code class="language-javascript">async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1</code></pre>
<h3 id="await">await</h3>
<p><code>await</code> 키워드는 <code>async</code> 함수 안에서만 동작한다. 자바스크립트는 <code>await</code> 키워드를 만나면 프로미스가 처리될 때까지 기다린다. 결과는 그 이후 반환된다.</p>
<pre><code class="language-javascript">let value = await promise;</code></pre>
<p>1초 후 이행되는 프로미스를 예시로 사용하여 <code>await</code>의 동작을 살펴보자.</p>
<pre><code class="language-javascript">async function f() {
  let promise = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(&quot;완료!&quot;), 1000)
  });

  let result = await promise;    // 프로미스가 이행될 때까지 기다림

  alert(result);    // 완료!
}

f();</code></pre>
<p>함수를 호출하고, 함수 본문이 실행되는 도중에 <code>(*)</code>로 표시한 줄에서 실행이 잠시 중단되었다가 프로미스가 처리되면 실행이 재개된다. 이때 프로미스 객체의 <code>result</code> 값이 변수 <code>result</code>에 할당된다.</p>
<p><code>await</code>은 말 그대로 프로미스가 처리될 때까지 함수 실행을 기다리게 만든다. 프로미스가 처리되면 그 결과와 함께 실행이 재개된다.</p>
<p>프로미스가 처리되길 기다리는 동안에 엔진이 다른 일을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.</p>
<p><code>await</code>은 <code>promise.then</code> 보다 좀 더 세련되게 프로미스의 <code>result</code> 값을 얻을 수 있다. 또, 가독성이 좋고 쓰기도 쉽다.</p>
<p>일반 함수에는 <code>await</code>을 사용할 수 없다.</p>
<pre><code class="language-javascript">function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}</code></pre>
<p>프로미스 체이닝으로 구현한 <code>showAvatar</code> 함수를 <code>async / await</code>로 다시 작성해보자.</p>
<ul>
<li>먼저 <code>then</code> 호출을 <code>await</code>로 바꾼다.</li>
<li><code>function</code> 앞에 <code>async</code>를 붙인다.</li>
</ul>
<pre><code class="language-javascript">async function showAvatar() {

  // JSON 읽기
  let response = await fetch(&#39;/article/promise-chaining/user.json&#39;);
  let user = await response.json();

  // github 사용자 정보 읽기
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // 아바타 보여주기
  let img = document.createElement(&#39;img&#39;);
  img.src = githubUser.avatar_url;
  img.className = &quot;promise-avatar-example&quot;;
  document.body.append(img);

  // 3초 대기
  await new Promise((resolve, reject) =&gt; setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();</code></pre>
<p>코드가 깔끔해지고 읽기도 쉬워졌다. 프로미스를 사용한 예시보다 훨씬 낫다.</p>
<p><code>await</code>은 최상위 레벨 코드에서는 작동하지 않는다.</p>
<pre><code class="language-javascript">// 최상위 레벨 코드에선 문법 에러가 발생함
let response = await fetch(&#39;/article/promise-chaining/user.json&#39;);
let user = await response.json();</code></pre>
<p>하지만 익명 <code>async</code> 함수로 코드를 감싸면 최상위 레벨 코드에도 <code>await</code>을 사용할 수 있다.</p>
<pre><code class="language-javascript">(async () =&gt; {
  let response = await fetch(&#39;/article/promise-chaining/user.json&#39;);
  let user = await response.json();
  ...
})();</code></pre>
<p>또, 클래스의 메서드 이름 앞에 <code>async</code>를 추가하면 <code>async</code> 클래스 메서드를 선언할 수 있다.</p>
<pre><code class="language-javascript">class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1</code></pre>
<h3 id="에러-핸들링-1">에러 핸들링</h3>
<p>프로미스가 정상적으로 이행되면 <code>await promise</code>는 프로미스 객체의 <code>result</code>에 저장된 값을 반환한다. 반면 프로미스가 거부되면 마치 <code>throw</code>문처럼 에러가 던져진다.</p>
<pre><code class="language-javascript">async function f() {
  await Promise.reject(new Error(&quot;에러 발생!&quot;));
}

// 아래 코드와 동일하게 동작한다.
async function f() {
  throw new Error(&quot;에러 발생!&quot;);
}</code></pre>
<p>실제 상황에서는 프로미스가 거부 되기 전에 약간의 시간이 지체되는 경우가 있다. 이런 경우에는 <code>await</code>가 에러를 던지기 전에 지연이 발생한다.</p>
<p><code>await</code>가 던진 에러는 <code>throw</code>가 던진 에러를 잡을 때처럼 <code>try catch</code>를 사용해서 잡을 수 있다.</p>
<pre><code class="language-javascript">async function f() {
  try {
    let response = await fetch(&#39;http://유효하지-않은-주소&#39;);
  } catch (err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();</code></pre>
<p>에러가 발생하면 제어 흐름이 <code>catch</code> 블록으로 넘어간다. 여러 줄의 코드를 <code>try</code>로 감싸는 것도 가능하다.</p>
<pre><code class="language-javascript">async function f() {

  try {
    let response = await fetch(&#39;http://유효하지-않은-주소&#39;);
    let user = await response.json();
  } catch(err) {
    // fetch와 response.json에서 발행한 에러 모두를 여기서 잡는다.
    alert(err);
  }
}

f();</code></pre>
<p><code>try catch</code>가 없으면 <code>async</code> 함수를 호출해서 만든 프로미스가 거부 상태가 된다. <code>catch</code>를 추가하면 거부된 프로미스를 처리할 수 있다.</p>
<pre><code class="language-javascript">async function f() {
  let response = await fetch(&#39;http://유효하지-않은-주소&#39;);
}

// f()는 거부 상태의 프라미스가 됩니다.
f().catch(alert); // TypeError: failed to fetch // (*)</code></pre>
<p><code>async / await</code>을 사용하면 <code>await</code>가 대기 상태를 처리해주기 때문에 <code>then</code>이 거의 필요하지 않다. 또, <code>catch</code> 대신 <code>try catch</code>를 사용할 수 있다는 장점도 생긴다.</p>
<p>항상 그런것은 아니지만, <code>promise.then</code> 보다 <code>async / await</code>을 사용하는 것이 대개는 더 편리하다. 그런데 문법적 제약 때문에 <code>async</code> 함수 바깥의 최상위 레벨 코드에서는 <code>await</code>을 사용할 수 없다. 그렇기 때문에 관행처럼 <code>then / catch</code> 를 추가해서 최종 결과나 처리되지 못한 에러를 다룬다. 위 예시의 <code>(*)</code>가 그 예이다.</p>
<p>여러 개의 프로미스가 모두 처리되길 기다려야 하는 상황이라면, <code>await</code>은 <code>Promise.all</code>과도 함께 쓸 수 있다.</p>
<pre><code class="language-javascript">// 프라미스 처리 결과가 담긴 배열을 기다린다.
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);</code></pre>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/async">https://ko.javascript.info/async</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 1. 25 자바스크립트) 에러 핸들링]]></title>
            <link>https://velog.io/@dev_diver/22.-1.-25-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@dev_diver/22.-1.-25-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Wed, 25 Jan 2023 09:57:50 GMT</pubDate>
            <description><![CDATA[<h1 id="try-catch와-에러-핸들링">try catch와 에러 핸들링</h1>
<p>에러가 발생하면 스크립트는 즉시 중단되고, 콘솔에 에러가 출력된다.</p>
<p>그러나 <code>try catch</code> 문법을 사용하면 스크립트가 중단되는 것을 방지하고, 에러를 잡아서 처리할 수 있다.</p>
<h3 id="try-catch-문법">try catch 문법</h3>
<p><code>try</code>와 <code>catch</code>라는 두 개의 주요 블록으로 구성된다.</p>
<pre><code class="language-javascript">try {
  // ...
} catch (err) {
  // ...
}</code></pre>
<p>동작 알고리즘은 다음과 같다.</p>
<ol>
<li>먼저, <code>try { ... }</code> 안의 코드가 실행된다.</li>
<li>에러가 없다면 <code>try { ... }</code> 안의 코드만 실행되고, <code>catch</code> 블록은 건너뛴다.</li>
<li>에러가 있다면 <code>try { ... }</code> 안 코드의 실행이 중단되고, <code>catch(err) { ... }</code> 블록으로 제어 흐름이 넘어간다. <code>err</code>에는 무슨 일이 발생했는지에 대한 설명이 담긴 에러 객체르 포함한다.</li>
</ol>
<p>이렇게 <code>try</code> 블록 안에서 에러가 발생해도, <code>catch</code>에서 에러를 처리하기 때문에 스크립트는 중단되지 않는다.</p>
<p><code>try catch</code>는 오직 유효한 코드에서 발생하는 에러만 처리할 수 있다. 이런 에러를 런타임 에러 혹은 예외라고 부른다.</p>
<p>또, <code>try catch</code>는 동기적으로 동작한다. <code>setTimeout</code>처럼 스케줄 된 코드에서 발생한 예외는 <code>try catch</code>에서 잡아낼 수 없다. 아래 예시에서 <code>setTimeout</code>에 넘겨진 익명 함수는 엔진이 <code>try catch</code>를 떠난 다음에서야 실행된다.</p>
<pre><code class="language-javascript">try {
  setTimeout(function() {
    noSuchVariable; // 스크립트는 여기서 죽는다.
  }, 1000);
} catch (e) {
  alert( &quot;작동 멈춤&quot; );
}</code></pre>
<p>따라서, 스케줄 된 함수 내부의 예외를 잡으려면 <code>try catch</code>를 반드시 함수 내부에 구현해야 한다.</p>
<pre><code class="language-javascript">setTimeout(function() {
  try {
    noSuchVariable; // 이제 try..catch에서 에러를 핸들링 할 수 있다.
  } catch {
    alert( &quot;에러를 잡았습니다!&quot; );
  }
}, 1000);</code></pre>
<h3 id="에러-객체">에러 객체</h3>
<p>에러가 발생하면 자바스크립트는 에러 상세내용이 담긴 객체를 생성한다. 그 후, <code>catch</code> 블록에 해당 객체를 인수로 전달한다.</p>
<pre><code class="language-javascript">try {
  // ...
} catch(err) { // &lt;-- &#39;에러 객체&#39;, err 대신 다른 이름으로도 쓸 수 있음
  // ...
}</code></pre>
<p>에러 객체는 아래의 주요 프로퍼티를 가진다.</p>
<ul>
<li><code>name</code> : 에러 이름</li>
<li><code>mesage</code> : 에러 상세 내용을 담고 있는 메시지</li>
<li><code>stack</code> : 현재 호출 스택. 에러를 유발한 중첩 호출들의 순서 정보를 가진 문자열로, 디버깅 목적으로 사용된다.</li>
</ul>
<pre><code class="language-javascript">try {
  lalala; // 에러, 변수가 정의되지 않음!
} catch(err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at ... (호출 스택)

  // 에러 전체를 보여줄 수도 있다.
  // 이때, 에러 객체는 &quot;name: message&quot; 형태의 문자열로 변환된다.
  alert(err); // ReferenceError: lalala is not defined
}</code></pre>
<p>에러에 대한 자세한 정보가 필요하지 않으면, <code>err</code> 인수를 생략할 수도 있다.</p>
<pre><code class="language-javascript">try {
  // ...
} catch { // &lt;-- (err) 없이 쓸 수 있음
  // ...
}</code></pre>
<h3 id="try-catch를-실무에서-사용하는-방법">try catch를 실무에서 사용하는 방법</h3>
<p>앞 서 JSON으로 인코딩된 값을 읽을 수 있도록 하는 <code>JSON.parse(str)</code> 메서드에 대해 배운 바 있다. 이 메서드는 주로 서버 등에서 네트워크를 통해 전달받은 데이터를 디코딩하는 데 사용한다.</p>
<p>이 때 잘못된 형식의 json이 들어온 경우, <code>JSON.parse</code>는 에러를 만들기 때문에 스크립트가 중단된다. 서버에서 전달받은 데이터가 잘못되어 스크립트가 죽는 경우, 사용자는 개발자 콘손을 열지 않는 이상 원인을 알 수 없다. 이를 처리하기 위해 <code>try catch</code>를 사용해보자.</p>
<pre><code class="language-javascript">let json = &quot;{ bad json }&quot;;

try {

  let user = JSON.parse(json); // &lt;-- 여기서 에러가 발생하므로
  alert( user.name ); // 이 코드는 동작하지 않는다.

} catch (e) {
  // 에러가 발생하면 제어 흐름이 catch 문으로 넘어온다.
  alert( &quot;데이터에 에러가 있어 재요청을 시도합니다.&quot; );
  alert( e.name );
  alert( e.message );
}</code></pre>
<p>위 예시에서는 에러가 발생했다는 것만 보여주기 위해 간단히 예외처리 했지만, <code>catch</code> 블록 안에서 새로운 네트워크 요청 보내기, 대안 제안하기, 에러 정보 로깅 등 다양한 일을 할 수 있다. 그냥 스크립트가 죽도록 놔두는 것보다 훨씬 나은 대응이다.</p>
<h3 id="throw-연산자로-직접-에러를-만들어서-던지기">throw 연산자로 직접 에러를 만들어서 던지기</h3>
<p>json이 문법적으로 잘못되진 않았지만, 스크립트 내에서 사용 중인 필수 프로퍼티 등을 가지고 있지 않다면 무슨 일이 발생할까?</p>
<pre><code class="language-javascript">let json = &#39;{ &quot;age&quot;: 30 }&#39;; // 불완전한 데이터

try {

  let user = JSON.parse(json); // &lt;-- 에러 없음
  alert( user.name ); // 에러 발생

} catch (e) {
  alert( &quot;실행되지 않습니다.&quot; );
}</code></pre>
<p>위 예시에서 <code>JSON.parse</code>는 정상적으로 실행되었지만 <code>name</code> 프로퍼티가 없기 때문에 에러가 발생한다. 이 때 <code>throw</code> 연산자를 사용해서 직접 에러 처리를 할 수 있다.</p>
<p><code>throw</code> 연산자는 에러를 생성한다.</p>
<pre><code class="language-javascript">throw &lt;error object&gt;</code></pre>
<p>이론적으로 숫자, 문자열 같은 원시형 자료를 포함한 어떤 것이든 에러 객체로 사용할 수 있다. 하지만 내장 에러와의 호환을 위해 되도록 에러 객체에 <code>name</code>과 <code>message</code> 프로퍼티를 넣어주는 것을 권장한다.</p>
<p>자바스크립트는 <code>Error</code>, <code>SyntaxError</code>, <code>ReferenceError</code>, <code>TypeError</code> 등의 표준 에러 객체에 관련한 생성자를 지원한다. 이 생성자들을 이용해서 에러 객체를 만들 수 있다.</p>
<pre><code class="language-javascript">let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...</code></pre>
<p>일반 객체가 아닌 내장 생성자를 사용해서 만든 내장 에러 객체의 <code>name</code> 프로퍼티는 생성자 이름과 동일한 값을 갖는다. 프로퍼티 <code>message</code>의 값은 인수에서 가져온다.</p>
<pre><code class="language-javascript">let error = new Error(&quot;이상한 일이 발생했습니다. o_O&quot;);

alert(error.name); // Error
alert(error.message); // 이상한 일이 발생했습니다. o_O</code></pre>
<p>다시 원래 예시로 돌아가서 잘못된 데이터를 받았을 때, <code>throw</code> 연산자를 사용해서 에러를 던져보자.</p>
<pre><code class="language-javascript">let json = &#39;{ &quot;age&quot;: 30 }&#39;;    // 불완전한 데이터

try {
  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError(&quot;불완전한 데이터: 이름 없음&quot;);    // 에러 생성 (*)
  }

  alert( user.name);
} catch(e) {
  alert( &quot;JSON Error: &quot; + e.message ); // JSON Error: 불완전한 데이터: 이름 없음</code></pre>
<p><code>(*)</code>로 표시한 줄에서 <code>throw</code> 연산자는 <code>message</code>를 이용해서 <code>SyntxError</code>를 생성한다. 에러 생성 방식은 자바스크립트가 자체적으로 에러를 생성하는 방식과 동일하다. 에러가 발생했으므로 <code>try</code>의 실행은 즉시 중단되고, 제어 흐름이 <code>catch</code>로 넘어간다.</p>
<h3 id="에러-다시-던지기">에러 다시 던지기</h3>
<p>위 예시에서는 불완전한 데이터를 <code>try catch</code>로 처리했다. 그런데 또 다른 예기치 않은 에러가 <code>try</code> 블록 안에서 발생할 수도 있다.</p>
<pre><code class="language-javascript">let json = &#39;{ &quot;age&quot;: 30 }&#39;; // 불완전한 데이터

try {
  user = JSON.parse(json); // &lt;-- user 앞에 let을 붙이는 걸 잊었다.

  // ...
} catch(err) {
  alert(&quot;JSON Error: &quot; + err); // JSON Error: ReferenceError: user is not defined
  // (실제론 JSON Error가 아니다.)
}</code></pre>
<p>에러는 어떤 상황에서도 발생할 수 있다. 위 예시에서는 불완전한 데이터를 다루려는 목적으로 <code>try catch</code>를 썼다. 그런데, <code>catch</code>는 원래 <code>try</code> 블록에서 발생한 모든 에러를 잡으려는 목적으로 만들어졌다. 그런데 위 예시에서는 에러 종류와 관계없이 <code>&quot;JSON Error&quot;</code> 메시지를 보여준다. 이렇게 에러 종류와 관계없이 동일한 방식으로 에러를 처리하는 것은 디버깅을 어렵게 만들기 때문에 좋지 않다.</p>
<p>이런 문제를 피하고자 <code>다시 던지기(rethrowing)</code> 기술을 사용한다. 즉, <code>catch</code>는 알고 있는 에러만 처리하고 나머지는 다시 던져야 한다.</p>
<ol>
<li><code>catch</code>가 모든 에러를 받는다.</li>
<li><code>catch</code> 블록 안에서 에러 객체를 분석한다.</li>
<li>에러 처리 방법을 알지 못하면 <code>throw</code> 구문을 실행한다.</li>
</ol>
<p>에러를 다시 던져서 <code>catch</code> 블록에서는 <code>SyntaxError</code>만 처리되도록 해보자. 보통 에러 타입을 <code>instanceof</code> 명령어로 체크한다. </p>
<pre><code class="language-javascript">let json = &#39;{ &quot;age&quot;: 30 }&#39;; // 불완전한 데이터
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError(&quot;불완전한 데이터: 이름 없음&quot;);
  }

  blabla(); // 예상치 못한 에러

  alert( user.name );

} catch(e) {

  if (e instanceof SyntaxError) {
    alert( &quot;JSON Error: &quot; + e.message );
  } else {
    throw e; // 에러 다시 던지기 (*)
  }

}</code></pre>
<p><code>catch</code> 블록 안의 <code>(*)</code>로 표시한 줄에서 다시 던져진 에러는 <code>try catch</code> 밖으로 던져진다. 이 때 바깥에 <code>try catch</code>가 있다면 여기서 에러를 잡는다. 아니면 스크립트는 중단된다. 이렇게 하면 <code>catch</code> 블록에서는 어떻게 다룰지 알고 있는 에러만 처리하고, 알 수 없는 에러는 건너뛸 수 있다.</p>
<p>이제 <code>try catch</code>를 하나 더 만들어, 다시 던져진 예상치 못한 에러를 처리해 보자. 아래 예시에서 <code>readData</code>는 <code>SyntaxError</code>만 처리할 수 있지만, 함수 바깥의 <code>try catch</code>에서는 예상치 못한 에러도 처리할 수 있게 된다.</p>
<pre><code class="language-javascript">function readData() {
  let json = &#39;{ &quot;age&quot;: 30 }&#39;;

  try {
    // ...
    blabla(); // 에러!
  } catch (e) {
    // ...
    if (!(e instanceof SyntaxError)) {
      throw e; // 알 수 없는 에러 다시 던지기
    }
  }
}

try {
  readData();
} catch (e) {
  alert( &quot;External catch got: &quot; + e ); // 에러를 잡음
}</code></pre>
<h3 id="try-catch-finally">try catch finally</h3>
<p><code>finally</code> 안의 코드는 다음과 같은 상황에서 실행된다.</p>
<ul>
<li>에러가 없는 경우. 즉, <code>try</code> 실행이 끝난 후</li>
<li>에러가 있는 경우. 즉, <code>catch</code> 실행이 끝난 후</li>
</ul>
<p><code>finally</code>를 사용하면 <code>try catch</code>를 다음과 같이 확장할 수 있다.</p>
<pre><code class="language-javascript">try {
   ... 코드를 실행 ...
} catch(e) {
   ... 에러 핸들링 ...
} finally {
   ... 항상 실행 ...
}</code></pre>
<p><code>finally</code> 절은 무언가를 실행하고, 실행 결과(에러 여부)에 상관없이 어떤 코드를 실행하고 싶을 경우 사용된다.</p>
<p>아래 예시는 피보나치 함수의 연산 시간을 측정하는 함수이다. <code>finally</code> 절을 사용해서 에러가 발생했든 아니든 관계없이 연산 시간 측정을 할 수 있다.</p>
<pre><code class="language-javascript">let num = +prompt(&quot;양의 정수를 입력해주세요.&quot;, 35)

let diff, result;

function fib(n) {
  if (n &lt; 0 || Math.trunc(n) != n) {
    throw new Error(&quot;음수나 정수가 아닌 값은 처리할 수 없습니다.&quot;);
  }
  return n &lt;= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (e) {
  result = 0;
} finally {
  diff = Date.now() - start;
}

alert(result || &quot;에러 발생&quot;);

alert( `연산 시간: ${diff}ms` );</code></pre>
<p><code>catch</code> 절이 없는 <code>try finally</code> 구문도 사용할 수 있다. <code>try finally</code> 안에서는 에러를 처리하고 싶지 않지만, 시작한 프로세스가 마무리되었는지 확실히 하고 싶은 경우에 사용한다.</p>
<pre><code class="language-javascript">function func() {
  // 무언가를 측정하는 경우와 같이 끝맺음이 있어야 하는 프로세스
  try {
    // ...
  } finally {
    // 스크립트가 죽더라도 완료됨
  }
}</code></pre>
<hr>
<h1 id="커스텀-에러와-에러-확장">커스텀 에러와 에러 확장</h1>
<p>개발을 하다 보면 자체 에러 클래스가 필요한 경우가 종종 생긴다.</p>
<p>예를 들어 네트워크 관련 작업 중 에러가 발생하면 <code>HttpError</code>, 데이터베이스 관련 작업 중 에러가 발생했다면 <code>DbError</code> 등을 사용하는 것이 직관적이기 때문이다.</p>
<p>직접 에러 클래스를 만든 경우, 이 에러들은 <code>name</code>, <code>message</code>, <code>stack</code> 프로퍼티 등을 지원해야 한다. 이 뿐만 아니라 다양한 프로퍼티를 지워할 수도 있다. 예를 들어 <code>HttpError</code> 클래스의 객체는 <code>statusCode</code> 프로퍼티를 만들고 <code>404</code> 등의 값을 가질 수 있다.</p>
<p>앞서 배운 바와 같이 <code>throw</code>의 인수에는 아무런 제약이 없기 때문에 커스텀 에러 클래스는 반드시 <code>Error</code>를 상속할 필요가 없다. 하지만, <code>Error</code>를 상속받아 커스텀 에러 클래스를 만들게 되면 <code>obj instanceof Error</code>를 사용해서 에러 객체를 식별할 수 있다는 장점이 생긴다. 이런 장점 때문에 맨땅에서 커스텀 에러 객체를 만드는 것보다 <code>Error</code>를 상속받아 에러 객체를 만드는 것이 낫다.</p>
<p>또, 애플리케이션 크기가 점점 커지면 <code>HttpTimeoutError</code>는 <code>HttpError</code>를 상속받는 것처럼 커스텀 에러 클래스들은 자연스레 계층 구조를 형성하게 된다. </p>
<h3 id="에러-확장하기">에러 확장하기</h3>
<p>사용자 데이터가 저장된 JSON을 읽는 함수 <code>readUser(json)</code>가 있다고 하자.</p>
<pre><code class="language-javascript">let json = `{ &quot;name&quot;: &quot;John&quot;, &quot;age&quot;: 30 }`;</code></pre>
<p><code>readUser</code> 내부에서는 <code>JSON.parse</code>를 이용하게 된다. 따라서 잘못된 형식의 json이 들어오면 <code>SyntaxError</code>가 발생한다. 그런데 인수로 받은 데이터가 형식은 맞지만, 유효한 사용자가 아니면 어떡할까? 필수적인 <code>name</code>이나 <code>age</code>가 누럭되었을 수도 있다.</p>
<p>따라서, <code>readUser</code>는 JSON 형식의 데이터를 읽을 수 있을 뿐만 아니라, 데이터를 검증할 수도 있어야 한다. 그런데 이 때 발생하는 에러는 <code>SyntaxError</code>가 아니다. 지금부터 이 에러를 <code>ValidationError</code>라고 부르자. 이제 <code>ValidationError</code>를 위한 클래스를 만들어보자.</p>
<p><code>ValidationError</code> 클래스에는 문제가 되는 필드 정보가 저장되어야 한다. 내장 클래스 <code>Error</code>를 상속받아 만들어 보자.</p>
<pre><code class="language-javascript">// 자바스크립트 자체 내장 에러 클래스 Error의 &#39;슈도 코드&#39;
class Error {
  constructor(message) {
    this.message = message;
    this.name = &quot;Error&quot;; // (name은 내장 에러 클래스마다 다르다.)
    this.stack = &lt;call stack&gt;;  // stack은 표준은 아니지만, 대다수 환경이 지원한다.
  }
}

class ValidationError extends Error {
  constructor(message) {
    super(message);    // (1)
    this.name = &quot;ValidationError&quot;;    // (2)
  }
}

function test() {
  throw new ValidationError(&quot;에러 발생!&quot;);
}

try {
  test();
} catch(err) {
  alert(err.message); // 에러 발생!
  alert(err.name); // ValidationError
  alert(err.stack); // 각 행 번호가 있는 중첩된 호출들의 목록
}</code></pre>
<p><code>(1)</code>에서 부모 생성자를 호출하고 있다. 자바스크립트에서는 자식 생성자 안에서 <code>super</code>를 반드시 호출해야 한다. 또, 부모 생성자에서는 <code>name</code> 프로퍼티를 설정하기 때문에 <code>(2)</code> 에서 원하느 값으로 재설정한다.</p>
<p>이제, <code>readUser</code> 안에서 <code>ValidationError</code>를 사용해보자.</p>
<pre><code class="language-javascript">class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = &quot;ValidationError&quot;;
  }
}

// 사용법
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError(&quot;No field: age&quot;);
  }
  if (!user.name) {
    throw new ValidationError(&quot;No field: name&quot;);
  }

  return user;
}

// try..catch와 readUser를 함께 사용한 예시

try {
  let user = readUser(&#39;{ &quot;age&quot;: 25 }&#39;);
} catch (err) {
  if (err instanceof ValidationError) {
    alert(&quot;Invalid data: &quot; + err.message); // Invalid data: No field: name
  } else if (err instanceof SyntaxError) { // (*)
    alert(&quot;JSON Syntax Error: &quot; + err.message);
  } else {
    throw err; // 알려지지 않은 에러는 재던지기 한다. (**)
  }
}</code></pre>
<p>이제 <code>try catch</code> 블록에서 커스텀 에러 <code>ValidationError</code>와 <code>JSON.parse</code>에서 발생하는 내장 에러 <code>SyntaxError</code> 둘 다 처리할 수 있다. 이 과정에서 <code>instanceof</code>로 에러 유형을 확인했다<code>(*)</code>. 에러 유형 확인은 <code>instaceof</code> 말고도 아래와 같이 <code>err.name</code>을 사용해도 가능하다.</p>
<pre><code class="language-javascript">// ...
// (err instanceof SyntaxError) 대신 사용 가능
} else if (err.name == &quot;SyntaxError&quot;) { // (*)
// ...</code></pre>
<p>그런데 에러 유형 확인은 <code>err.name</code> 보다는 <code>instanceof</code>를 사용하는 게 훨씬 좋다. <code>ValidationError</code>를 확장하여 새로운 에러를 만들수도 있는데, <code>instanceof</code>는 새로운 상속 클래스에서도 동작하기 때문이다.</p>
<p>또, <code>catch</code>에 알려지지 않을 에러가 있을 때 이 에러는 재 던지기 된다<code>(**)</code>. 위 예시의 <code>catch</code> 블록에서는 유효성 검사와 문법 오류만 처리하고, 다른 종류의 에러는 밖으로 던져야 한다.</p>
<h3 id="더-깊게-상속하기">더 깊게 상속하기</h3>
<p>앞 서 만든 <code>ValidationError</code> 클래스는 너무 포괄적이어서, 꼭 필요한 프로퍼티가 누락되거나 <code>age</code>에 문자열 값이 들어가는 것처럼 형식이 잘못된 경우를 처리할 수 없어 잘못될 확률이 있다. 따라서, 필수 프로퍼티가 없는 경우에 대응할 수 있도록 좀 더 구체적인 클래스 <code>PropertyRequiredError</code>를 만들어 보자. 해당 클래스에는 누락된 프로퍼티에 대한 추가 정보가 담겨야 한다.</p>
<pre><code class="language-javascript">class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = &quot;ValidationError&quot;;
  }
}

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super(&quot;No property: &quot; + property);
    this.name = &quot;PropertyRequiredError&quot;;
    this.property = property;
  }
}

// 사용법
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError(&quot;age&quot;);
  }
  if (!user.name) {
    throw new PropertyRequiredError(&quot;name&quot;);
  }

  return user;
}

// try..catch와 readUser를 함께 사용하면 다음과 같다.

try {
  let user = readUser(&#39;{ &quot;age&quot;: 25 }&#39;);
} catch (err) {
  if (err instanceof ValidationError) {
    alert(&quot;Invalid data: &quot; + err.message); // Invalid data: No property: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
  } else if (err instanceof SyntaxError) {
    alert(&quot;JSON Syntax Error: &quot; + err.message);
  } else {
    throw err; // 알려지지 않은 에러는 재던지기 한다.
  }
}</code></pre>
<p>여기서 주목할 점은 <code>PropertyRequiredError</code> 생성자 안에서 <code>this.name</code>을 수동으로 할당해 주었다. 그런데 이렇게 매번 커스텀 에러 클래스의 생성자 안에서 <code>this.name</code>을 수동으로 할당해 주는 것은 귀찮은 작업이다.</p>
<p>이런 번거로운 작업은 베이스가 되는 에러 클래스를 만들고 커스텀 에러들이 이 클래스를 상속받게 하면 피할 수 있다. 베이스 에러 생성자에 <code>this.name = this.constructor.name</code>을 추가하면 된다.</p>
<p>베이스가 되는 에러 클래스 <code>MyError</code>를 만들어 보자.</p>
<pre><code class="language-javascript">class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

class ValidationError extends MyError { }

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super(&quot;No property: &quot; + property);
    this.property = property;
  }
}

// 제대로 된 이름이 출력된다.
alert( new PropertyRequiredError(&quot;field&quot;).name ); // PropertyRequiredError</code></pre>
<h3 id="예외-감싸기">예외 감싸기</h3>
<p>함수 <code>readUser</code>가 기능이 많아지면서 에러 종류가 많아지면 그 때마다 에러 종류에 따라 에러 처리 분기문을 매번 추가해야 하는가? 라는 의문이 생길 수 있다.</p>
<p>보통은 그렇지 않다. 실제로 우리가 필요로 하는 정보는 데이터를 읽을 때 에러가 발생했는지에 대한 여부이다. 왜 에러가 발생했는지와 자세한 설명은 대부분의 경우 필요하지 않다. 이런 에러 처리 기술은 <code>예외 감싸기(wrapping exception)</code>라고 부른다. <code>예외 감싸기</code>는 다음과 같은 순서로 진행된다.</p>
<ol>
<li>데이터 읽기와 같은 포괄적인 에러를 대변하는 새로운 클래스 <code>ReadError</code>를 만든다.</li>
</ol>
<ul>
<li>함수 <code>readUser</code>가 발생한 <code>ValidationError</code>, <code>SyntaxError</code> 등의 에러는 <code>readUser</code> 내부에서 잡고 이 때 <code>ReadError</code>를 생성한다.</li>
</ul>
<ol start="3">
<li><code>ReadError</code> 객체의 <code>cause</code> 프로퍼티에는 실제 에러에 대한 참조가 저장된다.</li>
</ol>
<p>이렇게 예외 감싸기를 사용하면 <code>readUser</code>를 호출하는 코드에서는 <code>ReadError</code>만 확인하면 된다. 데이터를 읽을 때 발생하는 에러 종류 전체를 확인하지 않아도 된다. 추가 정보가 필요한 경우에는 <code>cause</code> 프로퍼티를 확인하면 된다.</p>
<p>실제로 <code>ReadError</code>를 정의하고 이를 사용해보자.</p>
<pre><code class="language-javascript">class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = &#39;ReadError&#39;;
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError(&quot;age&quot;);
  }

  if (!user.name) {
    throw new PropertyRequiredError(&quot;name&quot;);
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError(&quot;Syntax Error&quot;, err);
    } else {
      throw err;
    }
  }

  try {
    validateUser(user);
  } catch (err) {
    if (err instanceof ValidationError) {
      throw new ReadError(&quot;Validation Error&quot;, err);
    } else {
      throw err;
    }
  }

}

try {
  readUser(&#39;{잘못된 형식의 json}&#39;);
} catch (e) {
  if (e instanceof ReadError) {
    alert(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert(&quot;Original error: &quot; + e.cause);
  } else {
    throw e;
  }
}</code></pre>
<p>이제 <code>readUser</code>를 사용했을 때, <code>Syntax</code> 에러나 <code>Validation</code> 에러가 발생하면 해당 에러 자체를 다시 던지기 하지 않고, <code>ReadError</code>를 던지게 된다.</p>
<p>이렇게 되면, <code>readUser</code>를 호출하는 바깥 코드에서는 <code>instanceof ReadError</code> 딱 하나만 확인하면 된다. 에러 처리 분기문을 여러 개 만들 필요가 없다.</p>
<p>정리하자면 <code>예외 감싸기</code>는 종류별 에러를 좀 더 추상적인 에러 <code>ReadError</code> 등에 하나로 모아 처리하는 것을 뜻한다. 이런 기법은 객체 지향 프로그래밍에서 널리 쓰이는 패턴이다.</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/error-handling">https://ko.javascript.info/error-handling</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[23. 01. 25 알고리즘) 재귀 ]]></title>
            <link>https://velog.io/@dev_diver/23.-01.-25-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9E%AC%EA%B7%80</link>
            <guid>https://velog.io/@dev_diver/23.-01.-25-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9E%AC%EA%B7%80</guid>
            <pubDate>Wed, 25 Jan 2023 04:56:27 GMT</pubDate>
            <description><![CDATA[<h1 id="재귀recursion">재귀(Recursion)</h1>
<p>어떠한 것을 정의할 때 자기 자신을 참조하는 것을 뜻한다.</p>
<p>따라서, 재귀 함수는 함수 자기 자신을 계속 호출하는 함수를 뜻한다.</p>
<p>재귀 함수의 두 가지 조건을 다음과 같다.</p>
<ul>
<li><code>종료 조건</code></li>
<li><code>재귀적인 호출</code></li>
</ul>
<h1 id="재귀-함수의-예시">재귀 함수의 예시</h1>
<p>아래 예시는 음수가 아닌 정수를 입력 받았을 때, 팩토리얼을 재귀 함수로 구현한 예시이다.</p>
<pre><code class="language-javascript">function factorial(num) {
  if ( num &lt;= 0 ) return 1;    // 종료 조건

  return num * factorial(--num); // 재귀적인 호출
}</code></pre>
<h1 id="재귀-함수가-될-수-없는-것">재귀 함수가 될 수 없는 것</h1>
<p>아래 조건들을 지키지 않으면 콜스택은 오버플로우되고 에러가 발생한다.</p>
<ul>
<li>종료 조건이 없는 것</li>
<li>재귀 호출에 올바른 인수를 전달하지 않는 것(올바른 반환값이 아니게 됨)</li>
<li>반환값이 없는 것</li>
</ul>
<h1 id="헬퍼-메소드-재귀">헬퍼 메소드 재귀</h1>
<p>재귀에 헬퍼 함수를 구현해보자.</p>
<p>기본적인 틀은 아래와 같다.</p>
<pre><code class="language-javascript">function outer(input) {

  let outerScopedVariable = [];

  function helper(helperInput) {
    // modify the outerScopedVariable
    helper(helperInput--)
  }

  helper(input)

  return outerScopedVariable;
}</code></pre>
<p>헬퍼 함수는 아래의 두 가지 구성요소로 이루어진다.</p>
<ul>
<li>outer 함수</li>
<li>inner 함수 (재귀 함수)</li>
</ul>
<p>배열에서 모든 홀수값을 수집하는 간단한 예시를 헬퍼 함수로 구현해보자.</p>
<pre><code class="language-javascript">function collectOddValues(arr) {
  let result = [];

  function helper(helperInput) {
    if(helperInput.length === 0) {
      return;
    }

    if (helperInput[0] % 2 !== 0) {
      result.push(helperInput[0]);
    }

    helper(helperInput.slice(1));
  }

  helper(arr);

  return result;
}</code></pre>
<h1 id="헬퍼-함수가-아닌-순수-재귀">헬퍼 함수가 아닌 순수 재귀</h1>
<p>배열에서 모든 홀수값을 수집하는 예시를 헬퍼 함수가 아닌 순수한 재귀 함수로 구현해보자.</p>
<pre><code class="language-javascript">function collectOddValues(arr) {
  let newArr = [];

  if(arr.length === 0) {
    return newArr;
  }

  if(arr[0] % 2 !== 0) {
    newArr.push(arr[0]);
  }

  newArr = newArr.concat(collectOddValues(arr.slice(1)));

  return newArr;
}</code></pre>
<p>순수 재귀 팁</p>
<ul>
<li>배열을 사용하고 헬퍼 함수 없이 순수 재귀함수를 작성하는 경우에 배열을 복사하는 slice, spread 연산자, concat 같은 메서드를 사용할 수 있다. 그러면 배열을 변경할 필요가 없다.</li>
<li>문자열은 변경할 수 없다. 다라서 slice, substr, substring 등의 메서드를 사용해야 한다.</li>
<li>객체의 경우에는 Object.assign이나 spread 연산자를 사용하는 게 유용하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 1. 20 자바스크립트) 클래스]]></title>
            <link>https://velog.io/@dev_diver/22.-1.-20-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@dev_diver/22.-1.-20-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Fri, 20 Jan 2023 11:43:25 GMT</pubDate>
            <description><![CDATA[<h1 id="클래스와-기본-문법">클래스와 기본 문법</h1>
<h3 id="클래스-정의">클래스 정의</h3>
<p>클래스는 객체 지향 프로그래밍에서 특정 객체를 생성하기 위해 멤버 변수와 메서드를 정의하는 일종의 틀(템플릿)로, 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다.</p>
<p>본래 과거의 자바스크립트는 클래스가 없는 프로토타입 기반의 객체 지향 언어지만, 모던 자바스크립트에 도입된 클래스 문법을 사용해서 클래스 기반 프로그래밍을 할 수 있게 되었다.</p>
<p>참고로, 도입된 클래스 문법은 사실 일종의 프로로타입 기반 Syntatic sugar이다.</p>
<h3 id="기본-문법">기본 문법</h3>
<pre><code class="language-javascript">class MyClass {
  constructor() { ... }

  method1() { ... }
  methodN() { ... }
}</code></pre>
<p>위와 같은 문법으로 클래스를 만들고, <code>new MyClass()</code>를 호출하면 내부에서 정의한 메서드가 들어 있는 객체가 생성된다.</p>
<p>객체의 초기 상태를 설정해주는 생성자 메서드 <code>constructor()</code>는 <code>new</code>에 의해 자동으로 호출되어 객체가 초기화된다.</p>
<pre><code class="language-javascript">class User {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }
}

let user = new User(&quot;Yong&quot;);
user.sayHi();    // Yong</code></pre>
<p>위 예시에서 <code>new User(&quot;Yong&quot;)</code>를 호출하면 아래와 같은 일이 수행된다.</p>
<ol>
<li>새로운 객체가 생성된다.</li>
<li>넘겨받은 인수와 함께 <code>constructor</code>가 자동으로 실행된다.</li>
<li>객체에서 <code>user.sayHi()</code>와 같은 클래스 내에 구현된 메서드를 호출할 수 있다.</li>
</ol>
<h3 id="자바스크립트에서-클래스는-정확히-무엇일까">자바스크립트에서 클래스는 정확히 무엇일까?</h3>
<p>자바스크립트에서 클래스는 사실 함수의 한 종류이다.</p>
<pre><code class="language-javascript">class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

alert(typeof User);    // function</code></pre>
<p><code>class User { ... }</code> 문법 구조가 진짜 하는 일은 아래와 같다.</p>
<ol>
<li><code>User</code>라는 이름을 가진 함수를 만든다. 함수 본문은 생성자 메서드 <code>constructor</code>에서 가져온다. 생성자 메서드가 없으면 본문이 비워진 함수가 만들어진다.</li>
<li><code>sayHi</code> 같은 클래스 내에서 정의한 메서드를 앞 서 만들었던 함수의 <code>User.prototype</code>에 저장한다.</li>
</ol>
<p><code>class User</code>의 선언 결과를 그림으로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/dcc19cd5-e76a-463c-adaa-68b6c116f5f6/image.png" alt=""></p>
<pre><code class="language-javascript">class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// 클래스는 함수입니다.
alert(typeof User); // function

// 정확히는 생성자 메서드와 동일합니다.
alert(User === User.prototype.constructor); // true

// 클래스 내부에서 정의한 메서드는 User.prototype에 저장됩니다.
alert(User.prototype.sayHi); // alert(this.name);

// 현재 프로토타입에는 메서드가 두 개입니다.
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi</code></pre>
<h3 id="클래스는-단순한-syntatic-sugar편의-문법가-아니다">클래스는 단순한 Syntatic sugar(편의 문법)가 아니다</h3>
<pre><code class="language-javascript">// class User와 동일한 기능을 하는 순수 함수를 만들어보겠습니다.

// 1. 생성자 함수를 만듭니다.
function User(name) {
  this.name = name;
}
// 모든 함수의 프로토타입은 &#39;constructor&#39; 프로퍼티를 기본으로 갖고 있기 때문에
// constructor 프로퍼티를 명시적으로 만들 필요가 없습니다.

// 2. prototype에 메서드를 추가합니다.
User.prototype.sayHi = function() {
  alert(this.name);
};

// 사용법:
let user = new User(&quot;John&quot;);
user.sayHi();</code></pre>
<p>위 예시처럼 순수 함수로 클래스 역할을 하는 생성자 함수를 선언하는 방법과 <code>class</code> 키워드를 사용하는 방법의 결과는 거의 같다. 그래서 혹자는 <code>class</code>가 단순한 Syntatic sugar라고 생각한다.</p>
<p>하지만 두 방법에는 중요한 차이가 몇 가지 있다.</p>
<ol>
<li><code>class</code>로 만든 함수에는 특수한 숨김 프로퍼티인 <code>[[IsClassConstructor]]: true</code>가 이름표처럼 붙어있다. 자바스크립트는 다양한 경우에 <code>[[IsClassConstructor]]: true</code>를 활용한다. 클래스 생성자를 <code>new</code>와 함께 호출하지 않으면 에러가 발생하는데, 바로 이때 사용된다.</li>
</ol>
<pre><code class="language-javascript">class User {
  constructor() {}
}

alert(typeof User); // User의 타입은 함수이긴 하지만 그냥 호출할 수 없다.
User(); // TypeError: Class constructor User cannot be invoked without &#39;new&#39;</code></pre>
<ol start="2">
<li><p>클래스에 정의된 메서드는 열거할 수 없다. 즉, 클래스의 <code>prototype</code> 프로퍼티에 추가된 메서드의 <code>enumerable</code> 플래그는 <code>false</code>이다.</p>
</li>
<li><p>클래스는 항상 <code>strict mode</code>로 실행된다. 클래스 생성자 안 코드 전체에는 자동으로 <code>strict mode</code>가 적용된다. 등</p>
</li>
</ol>
<h3 id="클래스-표현식">클래스 표현식</h3>
<p>클래스도 함수처럼 다른 표현식 내부에서 정의, 전달, 반환, 할당할 수 있다.</p>
<pre><code class="language-javascript">let User = class {
  sayHi() {
    alert(&quot;안녕하세요.&quot;);
  }
};</code></pre>
<p>기명 함수 표현식과 유사하게 클래스 표현식에도 이름을 붙일 수 있다. 클래스 표현식에 이름을 붙이면, 오직 클래스 내부에서만 사용할 수 있다.</p>
<pre><code class="language-javascript">// 기명 클래스 표현식(Named Class Expression)
// (명세서엔 없는 용어이지만, 기명 함수 표현식과 유사하게 동작한다.)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass라는 이름은 오직 클래스 안에서만 사용할 수 있다.
  }
};

new User().sayHi(); // 원하는대로 MyClass의 정의를 보여준다.

alert(MyClass); // ReferenceError: MyClass is not defined, MyClass는 클래스 밖에서 사용할 수 없다.</code></pre>
<p>아래와 같이 필요에 따라 클래스를 동적으로 생성하는 것도 가능하다.</p>
<pre><code class="language-javascript">function makeClass(phrase) {
  // 클래스를 선언하고 이를 반환함
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}

// 새로운 클래스를 만듦
let User = makeClass(&quot;안녕하세요.&quot;);

new User().sayHi(); // 안녕하세요.</code></pre>
<h3 id="getter와-setter">getter와 setter</h3>
<p>클래스도 <code>getter</code>나 <code>setter</code>를 지원한다. 이들 역시 <code>prototype</code>에 정의된다.</p>
<p><code>get</code>과 <code>set</code>을 이용해서 <code>user.name</code>을 조작해보자.</p>
<pre><code class="language-javascript">class User {
  constructor(name) {
    // setter 활성화
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length &lt; 1) {
      alert(&quot;이름이 너무 짧습니다.&quot;);
      return;
    }
    this._name = value;
  }
}

let user = new User(&quot;보라&quot;);
alert(user.name);    // 보라
user = new User(&quot;&quot;);    // 이름이 너무 짧습니다.</code></pre>
<h3 id="계산된-메서드-이름-">계산된 메서드 이름 <code>[]</code></h3>
<p><code>computed property name</code>으로 객체의 프로퍼티 이름을 동적으로 지정했던 것처럼, 클래스에서도 대괄호를 이용해서 <code>computed method name</code>을 만들 수 있다.</p>
<pre><code class="language-javascript">class User {
  [&#39;say&#39; + &#39;Hi&#39;]() {
    alert(&quot;Hello&quot;);
  }
}

new User().sayHi();</code></pre>
<h3 id="클래스-필드">클래스 필드</h3>
<p>클래스 필드 문법을 사용해서 어떤 종류의 프로퍼티도 클래스에 추가할 수 있다.</p>
<pre><code class="language-javascript">class User {
  name = &quot;보라&quot;;    // 클래스 필드

  sayHi() {
    alert(`${this.name}님 안녕하세요!`);
  }
}

new User().sayHi();    // 보라님 안녕하세요</code></pre>
<p>클래스 필드의 중요한 특징 중 하나는 <code>User.prototype</code>이 아닌 개별 객체에서만 클래스 필드가 프로퍼티로 설정된다는 점이다.</p>
<pre><code class="language-javascript">class User {
  name = &quot;보라&quot;;
}

let user = new User();
alert(user.name);    // 보라
alert(User.prototype.name);    // undefined</code></pre>
<p>또, 클래스 필드에는 복잡한 표현식이나 함수 호출 결과를 사용할 수도 있다.</p>
<pre><code class="language-javascript">class User {
  name = prompt(&quot;이름을 알려주세요.&quot;, &quot;보라&quot;);
}

let user = new User();
alert(user.name);    // 보라</code></pre>
<h3 id="클래스-필드로-바인딩-된-메서드-만들기">클래스 필드로 바인딩 된 메서드 만들기</h3>
<p>자바스크립트에서 <code>this</code>는 동적으로 결정된다.</p>
<p>따라서 객체 메서드를 여기저기 전달해서 전혀 다른 컨텍스트에서 호출하게 되면, <code>this</code>는 메서드가 정의된 객체를 참조하지 않는다.</p>
<pre><code class="language-javascript">class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button(&quot;안녕하세요.&quot;);

setTimeout(button.click, 1000);    // undefined</code></pre>
<p>이렇게 <code>this</code>의 컨텍스트를 알 수 없게 되는 문제를 <code>잃어버린 this(losing this)</code>라고 한다.</p>
<p>이러한 문제는 두 가지 방법을 사용해서 해결할 수 있다.</p>
<ol>
<li><code>setTimeout(() =&gt; button.click(), 1000)</code> 같이 래퍼 함수를 전달하기</li>
<li>생성자 안에서 메서드를 객체에 바인딩하기</li>
</ol>
<p>위 두 가지 방법 말고 클래스 필드를 사용해도 문제를 해결할 수 있다. </p>
<pre><code class="language-javascript">class Button {
  constructor(value) {
    this.value = value;
  }

  click = () =&gt; {
    alert(this.value);
  }
}

let button = new Button(&quot;안녕하세요.&quot;);

setTimeout(button.click, 1000);    // 안녕하세요</code></pre>
<p>클래스 필드 <code>click = () =&gt; { ... }</code>는 클래스 <code>Button</code>으로 만든 각 객체마다 독립적인 함수를 만들어주고, 이 함수의 <code>this</code>를 해당 객체에 바인딩시켜준다. 따라서 아무곳에나 <code>button.click</code>을 전달할 수 있고, <code>this</code>에는 항상 의도한 값이 들어가게 된다.</p>
<p>클래스 필드의 이런 기능은  브라우저 환경에서 메서드를 이벤트 리스너로 설정해야 할 때 특히 유용하다.</p>
<hr>
<h1 id="클래스-상속">클래스 상속</h1>
<p>클래스 상속을 사용하면 클래스를 다른 클래스로 확장하여 기존에 존재하던 기능을 토대로 새로운 기능을 만들 수 있다.</p>
<h3 id="extends-키워드">extends 키워드</h3>
<p>동물과 관련된 객체를 만들 수 있는 <code>Animal</code> 클래스를 만들어보자.</p>
<pre><code class="language-javascript">class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} 은/는 속도 ${this.speed}로 달립니다.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} 이/가 멈췄습니다.`);
  }
}

let animal = new Animal(&quot;동물&quot;);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/fb3a1fbc-988d-45ee-810f-0b4fd275def2/image.png" alt=""></p>
<p>또 다른 클래스 <code>Rabbit</code>을 만들어보자. 토끼는 동물이므로 <code>Rabbit</code>은 동물 관련 메서드가 담긴 <code>Animal</code>을 상속받아 만들 수 있다. 이렇게 상속받은 토끼는 동물이 할 수 있는 일반적인 동작을 수행할 수 있다.</p>
<pre><code class="language-javascript">class Rabbit extends Animal {
  hide() {
    alert(`${this.name} 이/가 숨었습니다!`);
  }
}

let rabbit = new Rabbit(&quot;흰 토끼&quot;);

rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼 이/가 숨었습니다!</code></pre>
<p>키워드 <code>extends</code>는 프로토타입을 기반으로 동작한다. <code>extends</code>는 <code>Rabbit.prototype</code>의 <code>[[Prototype]]</code>를 <code>Animal.prototype</code>으로 설정한다. 따라서, <code>Rabbit.prototype</code>에서 메서드를 찾지 못하면 <code>Animal.prototype</code>에서 메서드를 가져온다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/efacee4d-19ad-4a62-9c73-f89637e6a6af/image.png" alt=""></p>
<p>자바스크립트 엔진은 아래와 같은 절차를 따라서 객체의 메서드 <code>rabbit.run</code>의 존재를 확인한다.</p>
<ol>
<li>객체 <code>rabbit</code>에 <code>run</code>이 있나 확인한다. 없다면, 2번을 수행한다.</li>
<li><code>rabbit</code>의 프로토타입인 <code>Rabbit.prototype</code>에 메서드가 있나 확인한다. <code>hide</code>는 있으나, <code>run</code>은 없다. 없다면, 3번을 수행한다.</li>
<li><code>extends</code>를 통해 관계가 만들어진 <code>Rabbit.prototype</code>의 프로토타입, <code>Animal.prototype</code>에 <code>run</code>이 있나 확인한다. 찾았다!</li>
</ol>
<p>자바스크립트의 내장 객체는 프로토타입을 기반으로 상속 관계를 맺는다. <code>Date.prototype</code>의 <code>[[Prototype]]</code>이 <code>Object.prototype</code>인 것처럼 말이다.</p>
<p>클래스 문법은 <code>extends</code> 뒤에 표현식이 와도 처리해준다. 아래 예시처럼 <code>extends</code> 뒤에서 부모 클래스를 만들어주는 함수를 호출할 수도 있다. </p>
<pre><code class="language-javascript">function f(phrase) {
  return class {
    sayHi() { alert(phrase) }
  }
}

class User extends f(&quot;Hello&quot;) {}

new User().sayHi();    // Hello</code></pre>
<p>이러한 방법은 조건에 따라 다른 클래스를 상속받고 싶을 때 유용하다. 조건에 따라 다른 클래스를 반환하는 함수를 만들고, 함수 호출 결과를 상속받게 하면 된다.</p>
<h3 id="메서드-오버라이딩">메서드 오버라이딩</h3>
<p>특별한 사항이 없으면 <code>class Rabbit</code>은 <code>class Animal</code>에 있는 메서드를 그대로 상속받는다.</p>
<p>그런데 <code>Rabbit</code>에서 <code>stop</code> 등의 메서드를 자체적으로 정의하면, 상속받은 메서드가 아닌 자체적으로 정의한 메서드가 사용된다.</p>
<pre><code class="language-javascript">class Rabbit extends Animal {
  stop() {
    // rabbit.stop()을 호출할 때
    // Animal의 stop()이 아닌, 이 메서드가 사용된다.
  }
}</code></pre>
<p>개발을 하다 보면 부모 메서드 전체를 교체하지 않고, 부모 메서드를 토대로 일부 기능만 변경하고 싶을 때가 있다. 또는 부모 메서드의 기능을 확장하고 싶을 때도 있다. 이럴 때 자체적으로 메서드를 만들어서 작업하게 되는데, 이미 자체적으로 메서드를 만들었더라도 부모 메서드도 호출하고 싶을 때가 있다.</p>
<p>키워드 <code>super</code>는 이럴 때 사용한다.</p>
<ul>
<li><code>super.methodName()</code>은 부모 클래스에 정의된 메서드를 호출한다.</li>
<li><code>super(...)</code>는 부모 생성자를 호출하는데, 자식 생성자 내부에서만 사용할 수 있다.</li>
</ul>
<p>이런 특징을 이용해서 토끼가 멈추면 자동으로 숨도록 하는 코드를 만들어보자.</p>
<pre><code class="language-javascript">class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name}가 속도 ${this.speed}로 달립니다.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name}가 멈췄습니다.`);
  }
}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name}가 숨었습니다!`);
  }

  stop() {
    super.stop();    // 부모 클래스의 stop을 호출한다.
    this.hide();
  }
}

let rabbit = new Rabbit(&quot;흰 토끼&quot;);

rabbit.run(5); // 흰 토끼가 속도 5로 달립니다.
rabbit.stop(); // 흰 토끼가 멈췄습니다. 흰 토끼가 숨었습니다!</code></pre>
<h3 id="생성자-메서드-오버라이딩">생성자 메서드 오버라이딩</h3>
<p>지금까지 클래스 <code>Rabbit</code>에는 자체 <code>constructor</code>가 없었다.</p>
<p>클래스가 다른 클래스를 상속받고 <code>constructor</code>가 없는 경우에는 아래처럼 비어있는 <code>constructor</code>가 만들어진다.</p>
<pre><code class="language-javascript">class Rabbit extends Animal {
  // 자체 생성자가 없는 클래스를 상속받으면 자동으로 만들어진다.
  constructor(...args) {
    super(...args);
  }
}</code></pre>
<p>보다시피 생성자는 기본적으로 부모 <code>constructor</code>를 호출한다. 이 때 부모 <code>constructor</code>에도 인수를 모두 전달한다. 클래스에 자체 생성자가 없는 경우에는 이런 일이 모두 자동으로 일어난다.</p>
<p>이제 클래스 <code>Rabbit</code>에 자체적으로 생성자를 추가해보자.</p>
<pre><code class="language-javascript">class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {
  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLenth;
  }

  // ...
}

// 동작하지 않는다!
let rabbit = new Rabbit(&quot;흰 토끼&quot;, 10); // ReferenceError: Must call super constructor in derived class before accessing &#39;this&#39; or returning from derived constructor</code></pre>
<p>위 예시와 같이 에러가 발생한다. 무엇이 잘못됐을까?</p>
<p>이유는 다음과 같다.</p>
<blockquote>
</blockquote>
<p>상속받은 클래스의 생성자에서는 반드시 <code>super(...)</code>를 호출해야 하는데, <code>super(...)</code>를 호출하지 않아서 에러가 발생했다. <code>super(...)</code>는 <code>this</code>를 사용하기 전에 반드시 호출해야 한다.</p>
<p>왜 <code>super(...)</code>를 호출해야 할까?</p>
<p>당연한 이유가 있다. 상속받은 클래스의 생성자가 호출될 때 어떤 일이 발생하는지 살펴보면서 이유를 찾아보자.</p>
<p>자바스크립트는 <code>상속 클래스의 생성자 함수(derived constructor)</code>와 <code>그렇지 않은 생성자 함수</code>를 구분한다. 상속 클래스의 생성자 함수에는 특수한 숨김 프로퍼티인 <code>[[ConstructorKind]]: &quot;derived&quot;</code>가 이름표처럼 붙는다.</p>
<p>일반 클래스의 생성자 함수와 상속 클래스의 생성자 함수 간의 차이는 <code>new</code>와 함께 드러난다.</p>
<ul>
<li>일반 클래스가 <code>new</code>와 함께 실행되면, 빈 객체가 만들어지고 <code>this</code>에 해당 객체를 할당한다.</li>
<li>반면에 상속 클래스의 생성자 함수가 실행되면, 상속 클래스의 생성자 함수는 빈 객체를 만들고 <code>this</code>에 해당 객체를 할당하는 일을 부모 클래스의 생성자가 처리해주길 기대한다.</li>
</ul>
<p>이런 차이 때문에 상속 클래스의 생성자에서는 <code>super</code>를 호출해서 부모 생성자를 실행해 주어야 한다. 그렇지 않으면 <code>this</code>가 될 객체가 만들어지지 않아 에러가 발생한다.</p>
<p>아래 예시와 같이 <code>this</code>를 사용하기 전에 <code>super()</code>를 호출하면 <code>Rabbit</code>의 생성자가 제대로 동작한다.</p>
<pre><code class="language-javascript">class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {
  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// 이제 에러 없이 동작한다.
let rabbit = new Rabbit(&quot;흰 토끼&quot;, 10);
alert(rabbit.name); // 흰 토끼
alert(rabbit.earLength); // 10</code></pre>
<h3 id="클래스-필드-오버라이딩">클래스 필드 오버라이딩</h3>
<p>오버라이딩은 메서드뿐만 아니라 클래스 필드를 대상으로도 적용할 수 있다.</p>
<p>부모 클래스의 생성자 안에 있는 필드에 오버라이딩해서 접근하려고 할 때 자바스크립트는 다른 언어와 다르게 조금 까다롭다. 예시를 살펴보자.</p>
<pre><code class="language-javascript">class Animal {
  name = &quot;animal&quot;;

  constructor() {
    alert(this.name);    // (*)
  }
}

class Rabbit extends Animal {
  name = &quot;rabbit&quot;;
}

new Animal();    // animal
new Rabbit();    // animal</code></pre>
<p><code>Animal</code>을 상속받는 <code>Rabbit</code>에서 <code>name</code> 필드를 오버라이딩 했다. <code>Rabbit</code>에는 따로 생성자가 정의되어 있지 않기 때문에 <code>Rabbit</code>을 사용해서 인스턴스를 만들면 <code>Animal</code>의 생성자가 호출된다.</p>
<p>흥미로운 점은 <code>new Animal()</code>과 <code>new Rabbit()</code>을 실행한 경우가 동일하다는 것이다. 이를 통해 부모 생성자는 자식 클래스에서 오버라이딩한 값이 아닌, 부모 클래스 안의 필드 값을 사용한다는 사실을 알 수 있다.</p>
<p>상속을 받고 필드 값을 오버라이딩했는데 새로운 값 대신 부모 클래스 안에 있는 기존 필드 값을 사용한 점이 이상한데, 이해를 돕기 위해 이 상황을 메서드와 비교해서 생각해보자.</p>
<p>아래 예시에서는 필드 <code>this.name</code> 대신에 메서드 <code>this.showName()</code>을 사용했다.</p>
<pre><code class="language-javascript">class Animal {
  showName() {  // this.name = &#39;animal&#39; 대신 메서드 사용
    alert(&#39;animal&#39;);
  }

  constructor() {
    this.showName(); // alert(this.name); 대신 메서드 호출
  }
}

class Rabbit extends Animal {
  showName() {
    alert(&#39;rabbit&#39;);
  }
}

new Animal(); // animal
new Rabbit(); // rabbit</code></pre>
<p>필드를 오버라이딩한 예시와 결과가 다르게 나왔다. 위와 같이 자식클래스에서 부모 생성자를 호출하면 오버라이딩한 메서드가 실행되어야 한다. 그런데 클래스 필드는 자식 클래스에서 필드를 오버라이딩해도 부모 생성자가 오버라이딩한 필드 값을 사용하지 않는다. 부모 생성자는 항상 부모 클래스에 있는 필드의 값을 사용한다. 왜 이런 차이가 있을까?</p>
<p>이유는 필드 초기화 순서 때문이다. 클래스 필드는 다음과 같은 규칙에 따라 초기화하는 방식이 다르다.</p>
<ul>
<li>아무것도 상속받지 않은 클래스는 생성자 실행 이전에 초기화된다.</li>
<li>부모 클래스가 있는 경우에는 <code>super()</code> 실행 직후에 초기화된다.</li>
</ul>
<p>위 예시에서 <code>Rabbit</code>은 자식 클래스이고 <code>contructor()</code>가 정의되어 있지 않다. 이런 경우 앞서 설명한 바와 같이 생성자는 비어있는데 그 안에 <code>super(...args)</code>만 있다고 보면 된다. 따라서, <code>new Rabbit()</code>을 실행하면 <code>super()</code>가 호출되고 그 결과 부모 생성자가 실행된다. </p>
<p>그런데 이때 클래스 필드 초기화 순서에 따라 자식 클래스 <code>Rabbit</code>의 필드는 <code>super()</code> 실행 후에 초기화된다. 부모 생성자가 실행되는 시점에 <code>Rabbit</code>의 필드는 아직 존재하지 않는다. 이런 이유로 필드를 오버라이딩 했을 때 <code>Animal</code>에 있는 필드가 사용된 것이다.</p>
<p>이렇게 자바스크립트는 오버라이딩시 필드와 메서드의 동작 방식이 미묘하게 다르다.</p>
<p>다행히도 이런 문제는 오버라이딩한 필드를 부모 생성자에서 사용할 때만 발생한다. 이런 차이가 왜 발생하는지 모르면 결과를 해석할 수 없기 때문에 별도의 공간을 만들어 필드 오버라이딩시 내부에서 벌어지는 일에 대해 자세히 알아보았다.</p>
<p>개발하다가 필드 오버라이딩이 문제가 되는 상황이 발생하면 필드 대신 메서드를 사용하거나 <code>getter</code>나 <code>setter</code>를 사용하면 해결된다.</p>
<h3 id="super-키워드와-homeobject">super 키워드와 <code>[[HomeObject]]</code></h3>
<p><code>super</code>의 내부 메커니즘에 대해 알아보자.</p>
<p>객체의 메서드가 실행되면 현개 객체가 <code>this</code>가 된다. 이 상태에서 <code>super.method()</code>를 호출하면 자바스크립트 엔진은 현재 객체의 프로토타입에서 <code>method</code>를 찾아야 한다. 이런 과정은 어떻게 일어날까?</p>
<p>쉬워 보이는 질문 같지만 실제로는 그렇지 않다. 엔진은 현재 객체 <code>this</code>를 알기 때문에 <code>this.__proto__.method</code>를 통해 부모 객체의 <code>method</code>를 찾는다라는 나이브한 생각은 틀렸다!</p>
<p>구체적인 코드와 함께 문제를 재현해보자. 간결성을 위해 클래스가 아닌 일반 객체를 사용해서 예시를 구성해보자. 아래 예시의 <code>rabbit.__proto__</code>은 <code>animal</code>이다. <code>rabbit.eat()</code>에서 <code>this.__proto__</code>를 사용해서 <code>animal.eat()</code>을 호출해보자.</p>
<pre><code class="language-javascript">let animal = {
  name: &quot;동물&quot;,
  eat() {
    alert(`${this.name} 이/가 먹이를 먹습니다.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: &quot;토끼&quot;,
  eat() {
    // 예상대로라면 super.eat()이 동작해야 한다.
    this.__proto__.eat.call(this); // (*)
  }
};

rabbit.eat(); // 토끼 이/가 먹이를 먹습니다.</code></pre>
<p><code>(*)</code>로 표시한 줄에서는 <code>eat</code>을 프로토타입에서 가져오고, 현재 객체의 컨텍스트에 기반하여 <code>eat</code>을 호출한다. 여기서 주의해야할 부분은 <code>.call(this)</code>이다. <code>this.__proto__.eat()</code>만 있으면 현재 객체가 아닌 프로토타입의 컨텍스트에서 <code>eat</code>을 실행하기 때문에 <code>.call(this)</code>가 있어야한다.</p>
<p>위 예시를 실행하면 예상한 내용이 출력된다.</p>
<p>이제 프로토타입 체인에 객체를 하나 더 추가해보자. 이제 문제가 발생하기 시작한다.</p>
<pre><code class="language-javascript">let animal = {
  name: &quot;동물&quot;,
  eat() {
    alert(`${this.name} 이/가 먹이를 먹습니다.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    // call을 사용해 컨텍스트를 옮겨가며 부모(animal) 메서드를 호출한다.
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    // longEar를 가지고 무언가를 하면서 부모(rabbit) 메서드를 호출한다.
    this.__proto__.eat.call(this); // (**)
  }
};

longEar.eat(); // RangeError: Maximum call stack size exceeded</code></pre>
<p>예상과 달리 에러가 발생한다! 왜 이런일이 발생하는지 천천히 살펴보자.</p>
<p>먼저 살펴봐야 할 것은 <code>(*)</code>과 <code>(**)</code>로 표시한 줄이다. 이 두 줄에서 <code>this</code>는 현재 객체인 <code>longEar</code>가 된다. 여기에 핵심이 있다. 모든 객체 메서드는 현재 객체를 <code>this</code>로 갖기 때문이다.</p>
<p>따라서 <code>(*)</code>과 <code>(**)</code>로 표시한 줄의 <code>this.__proto__</code>에는 둘 다 모두 <code>rabbit</code>이 할당된다. 프로토타입 체인 위로 올라가지 않고, 양쪽 모두에서 <code>rabbit.eat</code>을 호출하기 때문에 자기 자신은 계속 호출해서 무한루프에 빠지게 된다.</p>
<p>이를 그림으로 나타내면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/7f5ecb12-60af-4f94-b275-c33930fcba44/image.png" alt=""></p>
<p>이런 문제는 <code>this</code>만으로는 해결할 수 없다. 대신 자바스크립트에는 이런 문제를 해결할 수 있는 함수 전용 특수한 숨김 프로퍼티인 <code>[[HomeObject]]</code>가 있다.</p>
<p>클래스이거나 객체 메서드인 함수의 <code>[[HomeObject]]</code> 프로퍼티는 해당 객체가 저장된다.</p>
<p><code>super</code>는 <code>[[HomeObject]]</code>를 이용해서 부모 프로토타입과 메서드를 찾는다.</p>
<p>예시를 통해서 <code>[[HomeObject]]</code>가 어떻게 동작하는지 살펴보자. 먼저 일반 객체를 이용해보자.</p>
<pre><code class="language-javascript">let animal = {
  name: &quot;동물&quot;,
  eat() {    // animal.eat.[[HomeObject]] == animal
    alert(`${this.name} 이/가 먹이를 먹습니다.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: &quot;토끼&quot;,
  eat() {    // rabbit.eat.[[HomeObject]] == rabbit
    super.eat();
  }
};

let longEar = {
  __proto__: rabbit,
  name: &quot;귀가 긴 토끼&quot;,
  eat() {    // longEar.eat.[[HomeObject]] == longEar
    super.eat();
  }
};

// 이제 제대로 동작한다.
longEar.eat();  // 귀가 긴 토끼 이/가 먹이를 먹습니다.</code></pre>
<p><code>[[HomeObject]]</code>의 메커니즘 덕분에 메서드가 의도한 대로 동작하는 것을 확인할 수 있다. 이렇게 <code>longEar.eat</code>같은 객체 메서드는 <code>[[HomeObject]]</code>를 알고 있기 때문에 <code>this</code> 없이도 프로토타입으로부터 부모 메서드를 가져올 수 있다.</p>
<h3 id="메서드는-자유롭지-않다">메서드는 자유롭지 않다.</h3>
<p>자바스크립트에서 함수는 대개 객체이 묶이지 않고 자유롭다. 이런 자유성 때문에 <code>this</code>가 달라도 객체 간에 메서드를 복사하는 것이 가능하다.</p>
<p>그런데 <code>[[HomeObject]]</code>는 함수의 자유도를 파괴한다. 메서드가 객체를 기억하기 때문이다. 개발자가 <code>[[HomeObject]]</code>를 변경할 방법은 없기 때문에 한 번 바인딩 된 함수는 더 이상 변경되지 않는다.</p>
<p>다행인 점은 <code>[[HomeObject]]</code>는 오직 <code>super</code> 내부에서만 유효하다. 그렇기 때문에 메서드에서 <code>super</code>를 사용하지 않는 경우에는 메서드의 자유성이 보장된다. 객체 간에 복사 역시 가능하다.</p>
<p>하지만 메서드에서 <code>super</code>를 사용하면 이야기가 달라진다. 객체 간에 메서드를 잘못 복사한 경우에 <code>super</code>가 제대로 동작하지 않는 경우를 살펴보자.</p>
<pre><code class="language-javascript">let animal = {
  sayHi() {
    console.log(`나는 동물입니다.`);
  }
};

// rabbit은 animal을 상속받는다.
let rabbit = {
  __proto__: animal,
  sayHi() {
    super.sayHi();
  }
};

let plant = {
  sayHi() {
    console.log(&quot;나는 식물입니다.&quot;);
  }
};

// tree는 plant를 상속받는다.
let tree = {
  __proto__: plant,
  sayHi: rabbit.sayHi // (*)
};

tree.sayHi();  // 나는 동물입니다. (?!?)</code></pre>
<p>위 예시는 예상한 결과와 다르다. 원인은 단순하다.</p>
<ul>
<li><code>(*)</code>로 표시한 줄에서 메서드 <code>tree.sayHi</code>는 중복 코드를 방지하기 위해서 <code>rabbit</code>에서 메서드를 복사해왔다.</li>
<li>그런데 복사해온 메서드는 <code>rabbit</code>에서 생성했기 때문에, 해당 메서드의 <code>[[HomeObject]]</code>는 <code>rabbit</code>이다.</li>
<li><code>tree.sayHi()</code>의 내부 코드에는 <code>super.sayHi()</code>가 있다. <code>rabbit</code>의 프로토타입은 <code>animal</code>이므로 <code>super</code>는 프로토타입 체인 위에 있는 <code>animal</code>로 올라가 <code>sayHi</code>를 찾는다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/11ff9879-99a9-43b0-92e8-970cb8a66ea8/image.png" alt=""></p>
<h3 id="함수-프로퍼티가-아닌-메서드-사용하기">함수 프로퍼티가 아닌 메서드 사용하기</h3>
<p><code>[[HomeObject]]</code>는 클래스와 일반 객체의 메서드에서 정의된다. 그런데 객체 메서드의 경우 <code>[[HomeObject]]</code>가 제대로 동작하게 하려면 메서드를 반드시 <code>methodName()</code> 형태로 정의해야 한다. <code>methodName: function()</code> 형태로 정의하면 안된다.</p>
<p>메서드 문법이 아닌 함수 프로퍼티를 사용해서 예시를 작성하면 다음과 같다. <code>[[HomeObject]]</code> 프로퍼티가 설정되지 않기 때문에 상속이 제대로 동작하지 않는 것을 확인할 수 있다.</p>
<pre><code class="language-javascript">let animal = {
  eat: function() {
    // ...
  }
};

let rabbit = {
  __proto__: animal,
  eat: function() {
    super.eat();
  }
};

rabbit.eat();  // SyntaxError: &#39;super&#39; keyword unexpected here ([[HomeObject]]가 없어서 에러가 발생함)</code></pre>
<hr>
<h1 id="정적-메서드와-정적-프로퍼티">정적 메서드와 정적 프로퍼티</h1>
<p><code>prototype</code>이 아닌 클래스 함수 자체에 메서드를 설정할 수 있다. 이런 메서드를 <code>정적(static) 메서드</code>라고 한다.</p>
<p>정적 메서드는 아래와 같이 클래스 안에서 <code>static</code> 키워드를 붙여서 만들 수 있다.</p>
<pre><code class="language-javascript">class User {
  static staticMethod() {
    alert(this === User);
  }
}

User.staticMethod();    // true</code></pre>
<p>정적 메서드는 메서드를 프로퍼티 형태로 직접 할당하는 것과 동일한 일을 한다.</p>
<pre><code class="language-javascript">class User {}

User.staticMethod = function() {
  alert(this === User);
};

User.staticMethod();    // true</code></pre>
<p>정적 메서드는 어떤 특정한 객체가 아닌 클래스에 속한 함수를 구현하고자 할 때 주로 사용된다.</p>
<p>객체 <code>article</code>이 여러 개 있고, 이들을 비교할 함수가 필요하다고 가정하자. 가장 먼저 아래와 같이 <code>Article.compare</code> 메서드를 추가하는 방법이 있다.</p>
<pre><code class="language-javascript">class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

let articles = [
  new Article(&quot;HTML&quot;, new Date(2019, 1, 1)),
  new Article(&quot;CSS&quot;, new Date(2019, 0, 1)),
  new Article(&quot;JavaScript&quot;, new Date(2019, 11, 1))
];

articles.sort(Article.compare);

alert( articles[0].title );    // CSS</code></pre>
<p>여기서 <code>Article.compare</code>는 <code>article</code>을 비교해주는 수단으로, 글 전체를 위에서 바라보면 비교를 수행한다. <code>Article.compare</code>이 글 하나의 메서드가 아닌 클래스의 메서드여야 하는 이유가 여기에 있다.</p>
<p>이번에 살펴볼 예시는 팩토리 메서드를 구현한 코드이다. 다양한 방법을 사용해서 조건에 맞는 <code>article</code> 인스턴스를 만들어야 한다고 가정하자.</p>
<ul>
<li>매개변수(<code>title</code>, <code>date</code> 등)를 이용해서 관련 정보가 담긴 <code>article</code> 생성</li>
<li>오늘 날짜를 기반으로 비어있는 <code>article</code> 생성 등</li>
</ul>
<p>첫 번째 방법은 생성자를 사용해서 구현할 수 있고, 두 번째 방법은 클래스에 정적 메서드를 만들어 구현할 수 있다.</p>
<pre><code class="language-javascript">class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    return new this(&quot;Today&#39;s digest&quot;, new Date());
  }
}

let article1 = new Article(&quot;첫 번째 article&quot;, new Date());
alert( article1.title ); // 첫 번째 article

let article2 = Article.createTodays();
alert( article2.title ); // Today&#39;s digest</code></pre>
<p>이제 <code>Today&#39;s digest</code>라는 글이 필요할 때마다 <code>Article.createTodays()</code>를 호출하면 된다. 여기서도 마찬가지로 <code>Article.createTodays()</code>는 인스턴스의 메서드가 아닌 전체 클래스의 메서드이다.</p>
<p>정적 메서드는 항목 검색, 저장, 삭제 등을 수행하는 데이터베이스 관련 클래스에도 사용된다.</p>
<pre><code class="language-javascript">// Article은 article을 관리해주는 특별 클래스라고 가정하자
// article 삭제에 쓰이는 정적 메서드
Article.remove({id: 12345});</code></pre>
<h3 id="정적-프로퍼티">정적 프로퍼티</h3>
<p>정적 프로퍼티는 일반 클래스 프로퍼티와 유사하지만, <code>static</code>이 붙는다는 점이 다르다.</p>
<pre><code class="language-javascript">class Article {
  static publisher = &quot;Ilya Kantor&quot;;
}

alert( Article.publisher ); // Ilya Kantor</code></pre>
<p>위 예시는 <code>Article</code>에 프로퍼티를 직접 할당한 것과 동일하게 동작한다.</p>
<pre><code class="language-javascript">Article.publisher = &quot;Ilya Kantor&quot;;</code></pre>
<h3 id="정적-프로퍼티와-정적-메서드-상속">정적 프로퍼티와 정적 메서드 상속</h3>
<p>정적 프로퍼티와 정적 메서드는 상속된다.</p>
<p>아래 예시에서 <code>Animal.compare</code>와 <code>Animal.planet</code>은 상속되어서 각각 <code>Rabbit.compare</code>와 <code>Rabbit.planet</code>에서 접근할 수 있다.</p>
<pre><code class="language-javascript">class Animal {
  static planet = &quot;지구&quot;;

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name}가 속도 ${this.speed}로 달립니다.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }
}

// Animal을 상속받음
class Rabbit extends Animal {
  hide() {
    alert(`${this.name}가 숨었습니다!`);
  }
}

let rabbits = [
  new Rabbit(&quot;흰 토끼&quot;, 10),
  new Rabbit(&quot;검은 토끼&quot;, 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // 검은 토끼가 속도 5로 달립니다.

alert(Rabbit.planet); // 지구</code></pre>
<p>위 예시에서 <code>Rabbit.compare</code>을 호출하면 <code>Animal.compare</code>가 호출된다. 이게 가능한 이유는 프로토타입 때문이다. <code>extends</code> 키워드는 <code>Rabbit</code>의 <code>[[Prototype]]</code>이 <code>Animal</code>을 참조하도록 한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/3a57192c-df0a-4775-b6ee-b9e4ab69e04f/image.png" alt=""></p>
<p>따라서 <code>class Rabbit extends Animal</code>은 두 개의 <code>[[Prototype]]</code> 참조를 만들어 낸다.</p>
<ul>
<li>함수 <code>Rabbit</code>은 프로토타입을 통해 함수 <code>Animal</code>을 상속받는다.</li>
<li><code>Rabbit.prototype</code>은 프로토타입을 통해 <code>Animal.prototype</code>을 상속받는다.</li>
</ul>
<p>이런 과정이 있기 때문에 인스턴스의 일반 메서드 상속과 클래스의 정적 메서드 상속이 가능하다.</p>
<pre><code class="language-javascript">class Animal {}
class Rabbit extends Animal {}

// 정적 메서드
alert(Rabbit.__proto__ === Animal); // true

// 일반 메서드
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true</code></pre>
<hr>
<h1 id="private-protected-프로퍼티와-메서드">private, protected 프로퍼티와 메서드</h1>
<p>객체 지향 프로그래밍에서 가장 중요한 원리 중 하나는 내부 인터페이스와 외부 인터페이스를 구분 짓는 <code>캡슐화</code>이다.</p>
<h3 id="내부-인터페이스와-외부-인터페이스">내부 인터페이스와 외부 인터페이스</h3>
<p>객체 지향 프로그래밍에서 프로퍼티와 메서드는 두 그룹으로 분류된다.</p>
<ul>
<li><code>내부 인터페이스(internal interface)</code> : 동일한 클래스 내의 다른 메서드에서는 접근할 수 있지만, 클래스 밖에서는 접근할 수 없는 프로퍼티와 메서드</li>
<li><code>외부 인터페이스(external interface)</code> : 클래스 밖에서도 접근 가능한 프로퍼티와 메서드</li>
</ul>
<p>내부 인터페이스의 세부사항들은 서로의 정보를 이용해서 객체를 동작시킨다. 밖에서는 세부 요소를 알 수 없고, 접근도 불가능하다. 내부 인터페이스의 기능은 외부 인터페이스를 통해야만 사용할 수 있다.</p>
<p>이런 특징 때문에 외부 인터페이스만 알아도 객체를 가지고 무언가를 할 수 있다. 객체 안이 어떻게 동작하는지 알지 못해도 괜찮다는 점은 큰 장점으로 작용한다.</p>
<p>자바스크립트에는 아래와 같은 두 가지 타입의 객체 필드(프로퍼티와 메서드)가 있다.</p>
<ul>
<li><code>public</code> : 클래스 내/외부 어디서든지 접근할 수 있으며 외부 인터페이스를 구성한다.</li>
<li><code>private</code> : 클래스 내부에서만 접근할 수 있으며 내부 인터페이스를 구성할 때 쓰인다.</li>
</ul>
<p>자바스크립트 이외의 언어들에서는 클래스 자신과 자식 클래스에서만 접근을 허용하는 <code>protected</code> 필드를 지원한다. <code>protected</code> 필드는 <code>private</code> 필드와 비슷하지만, 자식 클래스에서도 접근이 가능하다는 점이 다르다.</p>
<p><code>protected</code> 필드도 내부 인터페이스를 만들 때 유용하다. 자식 클래스의 필드에 접근해야 하는 경우가 많기 때문에, <code>protected</code> 필드는 <code>private</code> 필드보다 조금 더 광범위하게 사용된다.</p>
<p>자바스크립트는 <code>protected</code> 필드를 지원하지 않지만, <code>protected</code>를 사용하면 편리한 점이 많기 때문에 이를 모방해서 사용하는 경우가 많다.</p>
<p>지금까지 배운 프로퍼티 타입을 사용해서 커피머신을 구현해보자.</p>
<h3 id="protected-프로퍼티로-프로퍼티-보호하기">protected 프로퍼티로 프로퍼티 보호하기</h3>
<p>먼저, 간단한 커피 머신 클래스를 만들어보자.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  waterAmount = 0;    // 물통에 차 있는 물의 양

  constructor(power) {
    this.power = power;
    alert( `전력량이 ${power}인 커피머신을 만듭니다.` );
  }
}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);

// 물 추가
coffeeMachine.waterAmount = 200;</code></pre>
<p>현재 프로퍼티 <code>waterAmount</code>와 <code>power</code>는 <code>public</code>이다. 이들은 손쉽게 읽고 원하는 값으로 변경할 수 있는 상태이다.</p>
<p>이제 <code>waterAmount</code>를 <code>protected</code>로 바꿔서 통제해보자. 예시로 <code>waterAmount</code>를 0 미만의 값으로는 설정하지 못하도록 만들어 보자.</p>
<p>**protected 프로퍼티 명 앞에는 밑줄 <code>_</code>이 붙는다.</p>
<p>자바스크립트에서 강제한 사항은 아니지만, 밑줄은 개발자들 사이에서 외부 접근이 불가능한 프로퍼티나 메서드를 나타낼 때 쓴다.</p>
<p><code>waterAmount</code>에 밑줄을 붙여 <code>protected</code> 프로퍼티로 만들어주자.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  _waterAmount = 0;

  set waterAmount(value) {
    if (value &lt; 0) throw new Error(&quot;물의 양은 음수가 될 수 없다.&quot;);
    this._waterAmount = value;
  }

  get waterAmount() {
    return this._waterAmount;
  }

  constructor(power) {
    this._power = power;
  }
}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);

// 물 추가
coffeeMachine.waterAmount = -10; // Error: 물의 양은 음수가 될 수 없습니다.</code></pre>
<h3 id="읽기-전용-프로퍼티">읽기 전용 프로퍼티</h3>
<p><code>power</code> 프로퍼티를 읽기만 가능하도록 만들어보자. 프로퍼티를 생성할 때만 값을 할당할 수 있고, 그 이후에는 값을 절대 수정하지 말아야 하는 경우가 있는데, 이럴 때 <code>읽기 전용 프로퍼티</code>를 활용할 수 있다.</p>
<p>읽기 전용 프로퍼티를 만들려면 <code>setter</code>는 만들지 않고, <code>getter</code>만 만들어야 한다.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  // ...

  constructor(power) {
    this._power = power;
  }

  get power() {
    return this._power;
  }

}

// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);

alert(`전력량이 ${coffeeMachine.power}인 커피머신을 만듭니다.`); // 전력량이 100인 커피머신을 만듭니다.

coffeeMachine.power = 25; // strict mode에서 Error (setter 없음)</code></pre>
<p>참고로 위에서는 <code>get</code>, <code>set</code> 문법을 사용해서 <code>getter</code>와 <code>setter</code> 함수를 만들었다. 하지만 대부분은 아래와 같이 <code>get...</code>, <code>set...</code> 형식의 함수가 선호된다.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  _waterAmount = 0;

  setWaterAmount(value) {
    if (value &lt; 0) throw new Error(&quot;물의 양은 음수가 될 수 없습니다.&quot;);
    this._waterAmount = value;
  }

  getWaterAmount() {
    return this._waterAmount;
  }
}

new CoffeeMachine().setWaterAmount(100);</code></pre>
<p>다소 길어보이지만, 이렇게 함수를 선언하면 다수의 인자를 받을 수도 있고, 이름에서 어떤 동작인지 유추할 수 있다는 장점이 있다. 반면 <code>get</code>, <code>set</code> 문법은 코드가 짧아진다는 장점이 있다. 어떤걸 사용해야 한다는 규칙은 없으므로 유동적이게 사용하자.</p>
<p>또, <code>protected</code> 필드는 상속된다. <code>class MegaMachine extends CoffeeMachine</code>로 클래스를 상속받으면, 새로운 클래스의 메서드에서 <code>this._waterAmount</code>나 <code>this._power</code>를 사용해서 프로퍼티에 접근할 수 있다.</p>
<h3 id="private-프로퍼티">private 프로퍼티</h3>
<p><code>private</code> 프로퍼티와 메서드는 <code>#</code>으로 시작하고, 클래스 안에서만 접근할 수 있다.</p>
<p>물 용량 한도를 나타내는 <code>private</code> 프로퍼티 <code>#waterLimit</code>과 남아있는 물의 양을 확인해주는 <code>private</code> 메서드인 <code>#checkWater</code>를 구현해보자.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  #waterLimit = 200;

  #checkWater(value) {
    if (value &lt; 0) throw new Error(&quot;물의 양은 음수가 될 수 없습니다.&quot;);
    if (value &gt; this.#waterLimit) throw new Error(&quot;물이 용량을 초과합니다.&quot;);
  }
}

let coffeeMachine = new CoffeeMachine();

// 클래스 외부에서 private에 접근할 수 없음
coffeeMachine.#checkWater(200);    // Error
coffeeMachine.#waterLimit = 1000;    // Error</code></pre>
<p><code>#</code>은 자바스크립트에서 지원하는 문법으로, <code>private</code> 필드를 의미한다. <code>private</code> 필드는 클래스 외부나 자식 클래스에서 접근할 수 없다.</p>
<p><code>private</code> 필드는 <code>public</code> 필드와 상충하지 않는다. 클래스는 <code>private</code> 프로퍼티 <code>#waterAmount</code>와 <code>public</code> 프로퍼티 <code>waterAmount</code>를 동시에 가질 수 있다.</p>
<p><code>#waterAmount</code>의 접근자 <code>waterAmount</code>를 만들어보자.</p>
<pre><code class="language-javascript">class CoffeeMachine {
  #waterAmount = 0;

  get waterAmount() {
    return this.#waterAmount;
  }

  set waterAmount(value) {
    if (value &lt; 0) throw new Error(&quot;물의 양은 음수가 될 수 없습니다.&quot;);
    this.#waterAmount = value;
  }
}

let machine = new CoffeeMachine();

machine.waterAmount = 100;
alert( machine.#waterAmount );    // Error</code></pre>
<p><code>protected</code> 필드와 달리, <code>private</code> 필드는 언어 자체에 의해 강제된다는 점이 장점이다.</p>
<p>그런데 <code>CoffeeMachine</code>을 상속받는 자식 클래스에서는 <code>#waterAmount</code>에 직접 접근할 수 없다. <code>#waterAmount</code>에 접근하려면 <code>waterAmount</code>의 <code>getter</code>와 <code>setter</code>를 통해야 한다.</p>
<pre><code class="language-javascript">class MegaCoffeeMachine extends CoffeeMachine {
  method() {
    alert( this.#waterAmount ); // Error: CoffeeMachine을 통해서만 접근할 수 있습니다.
  }
}</code></pre>
<p>참고로, 보통 <code>this[name]</code>으로 클래스 필드에 접근할 수 있지만, <code>private</code> 필드는 <code>this[name]</code>로 사용할 수 없다.</p>
<pre><code class="language-javascript">class User {
  ...
  sayHi() {
    let fieldName = &quot;name&quot;;
    alert(`Hello, ${this[fieldName]}`);
  }
}</code></pre>
<h3 id="캡슐화의-장점">캡슐화의 장점</h3>
<p>앞 서 설명했듯이 객체 지향 프로그래밍에서 내부 인터페이스와 외부 인터페이스를 구분하는 것을 <code>캡슐화</code>라고 한다고 했다.</p>
<p>캡슐화는 여러 장점을 가진다.</p>
<ul>
<li>외부에서 의도치 않게 클래스를 조작하여, 비정상적인 결과를 내는 상황을 방지할 수 있다.</li>
<li>내부 인터페이스를 엄격하게 구분하면, 사용자에게 알리지 않고도 자유롭게 내부 프로퍼티와 메서드를 수정할 수 있다. 사용자는 새로운 버전이 출시되면서 내부적으로 변경된 사항들을 외부 인터페이스만 똑같다면 무리없이 같은 기능 혹은 업그레이드된 기능을 사용할 수 있다.</li>
<li>복잡성을 은닉할 수 있다. 구현 세부 사항이 숨겨져 있으면 간단하고 편리해진다. 외부 인터페이스에 대한 설명도 문서화하기 쉬워진다.</li>
</ul>
<hr>
<h1 id="내장-클래스-확장하기">내장 클래스 확장하기</h1>
<p>배열, 맵 같은 내장 클래스도 확장 가능하다.</p>
<p>아래 예시에서 <code>PowerArray</code>는 내장 클래스 <code>Array</code>를 상속받는다.</p>
<pre><code class="language-javascript">class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert( arr.isEmpty() );    // false

let filteredArr = arr.filter(item =&gt; item &gt;= 10);
alert( filteredArr );    // 10, 50
alert( filteredArr.isEmpty() );    // false</code></pre>
<p><code>filter</code>, <code>map</code> 등의 내장 메서드는 상속받은 클래스인 <code>PowerArray</code>의 인스턴스를 반환한다. 이 객체를 구현할 때 내부에서는 객체의 <code>constructor</code> 프로퍼티를 사용한다.</p>
<p>따라서 아래와 같은 관계를 갖는다.</p>
<pre><code class="language-javascript">arr.constructor === PowerArray</code></pre>
<p><code>arr.filter()</code>가 호출될 때, 내부에서는 내장 클래스 <code>Array</code>가 아닌 <code>arr.constructor</code>를 기반으로 새로운 배열이 만들어지고 여기에 필터 후 결과가 담긴다. 이렇게 되면 <code>PowerArray</code>에 구현된 메서드를 사용할 수 있다는 장점이 생긴다.</p>
<p>물론 동작 방식을 변경할 수 있다.</p>
<p>특수한 정적 <code>getter</code>인 <code>Symbol.species</code>를 클래스에 추가할 수 있는데. <code>Symbol.species</code>가 있으면 <code>map</code>, <code>filter</code> 등의 메서드를 호출할 때 반환되어 만들어지는 객체의 생성자를 지정할 수 있다.</p>
<p><code>map</code>이나 <code>filter</code> 같은 내장 메서드가 일반 배열을 반환하도록 하려면 아래 예시처럼 <code>Symbol.species</code>가 <code>Array</code>를 반환하도록 하면 된다.</p>
<pre><code class="language-javascript">class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }

  // 클래스 내장 메서드는 반환 값에 명시된 클래스를 생성자로 사용한다
  static get [Symbol.species]() {
    return Array;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter는 arr.constructor[Symbol.species]를 생성자로 사용해 새로운 배열을 만든다.
let filteredArr = arr.filter(item =&gt; item &gt;= 10);

// filteredArr는 PowerArray가 아닌 Array의 인스턴스이다.
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function</code></pre>
<p>이런 방식으로 더는 확장 기능이 전달되지 않는다.</p>
<h3 id="내장-객체와-정적-메서드-상속">내장 객체와 정적 메서드 상속</h3>
<p>내장 객체는 <code>Object.keys</code>, <code>Array.isArray</code> 등의 자체 정적 메서드를 갖는다.</p>
<p>내장 클래스들은 서로 상속 관계를 맺는다. <code>Array</code>는 <code>Object</code>를 상속받는다.</p>
<p>일반적으로 한 클래스가 다른 클래스를 상속받으면 정적 메서드와 그렇지 않은 메서드 모두를 상속받는다. 그런데 내장 클래스는 다르다. 내장 클래스는 정적 메서드를 상속받지 못한다.</p>
<p>예를 들어보자. <code>Array</code>와 <code>Date</code>는 모두 <code>Object</code>를 상속받기 때문에 두 클래스의 인스턴스에서는 <code>Object.prototype</code>에 구현된 메서드를 사용할 수 있다. 그런데 내장 클래스 <code>Array</code>의 <code>[[Prototype]]</code>과 내장 클래스 <code>Date</code>의 <code>[[Prototype]]</code>는 <code>Object</code>를 참조하지 않기 때문에 <code>Array.keys()</code>나 <code>Date.keys()</code> 같은 정적 메서드를 인스턴스에서 사용할 수 없다.</p>
<p>아래는 <code>Date</code>와 <code>Object</code>의 관계를 나타낸 그림이다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/0b7e2f2f-a21a-4e6b-96ff-25279afd6a86/image.png" alt=""></p>
<p>보다시피 클래스 <code>Date</code>와 클래스 <code>Object</code>를 직접 이어주는 링크(<code>[[Prototype]]</code>)이 없다. <code>Date</code>와 <code>Object</code>는 독립적이다. 오직, <code>Date.protoype</code>만 <code>Object.protoype</code>을 상속받는다.</p>
<hr>
<h1 id="instanceof로-클래스-확인하기">instanceof로 클래스 확인하기</h1>
<p><code>instanceof</code> 연산자를 사용하면 객체가 특정 클래스에 속하는지 아닌지 확인할 수 있다. 또, 상속 관계도 확인할 수 있다.</p>
<p>이러한 특징으로 <code>intanceof</code>를 사용해서 매개변수의 타입에 따라 이를 다르게 처리하는 다형적인 함수를 만들어 객체 지향 프로그래밍의 특징인 <code>다형성</code>을 구현할 수 있다.</p>
<h3 id="instanceof-연산자">instanceof 연산자</h3>
<pre><code class="language-javascript">obj instanceof Class</code></pre>
<p>객체 <code>obj</code>가 클래스 <code>Class</code>에 속하거나, <code>Class</code>를 상속받는 클래스에 속하면 <code>true</code>가 반환된다.</p>
<pre><code class="language-javascript">class Rabbit {}
let rabbit = new Rabbit();

alert( rabbit instanceof Rabbit );    // true</code></pre>
<p><code>instanceof</code>는 생성자 함수에서도 사용할 수 있다.</p>
<pre><code class="language-javascript">// 클래스가 아닌 생성자 함수
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit );    // true</code></pre>
<p>물론 <code>Array</code> 같은 내장 클래스에도 사용할 수 있다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 3];

alert( arr instanceof Array );    // true
alert( arr instanceof Object );     // true</code></pre>
<p>위 예시처럼 <code>instanceof</code> 연산자는 보통 프로토타입 체인을 거슬러 올라가면서 인스턴스 여부나 상속 여부를 확인한다. 그런데 정적 메서드 <code>Symbol.hasInstance</code>를 사용하면 직접 확인하는 로직을 구현할 수도 있다.</p>
<p><code>obj instanceof Class</code>는 대략 아래와 같은 알고리즘으로 동작한다.</p>
<ol>
<li>클래스에 정적 메서드 <code>Symbol.hasInstance</code>가 구현되어 있으면, <code>obj instanceof Class</code> 문이 실행될 때, <code>Class[Symbol.hasInstance](obj)</code>가 호출된다. 호출 결과는 <code>true</code>나 <code>false</code> 이어야 한다. 이런 규칙을 기반으로 <code>instanceof</code>의 동작을 커스터마이징 할 수 있다.</li>
</ol>
<pre><code class="language-javascript">// canEat 프로퍼티가 있으면 animal이라고 판단할 수 있도록
// instanceof의 로직을 직접 설정한다.
class Animal {
  static [Symbol.hasInstance](obj) {
    if (obj.canEat) return true;
  }
}

let obj = { canEat: true };

alert(obj instanceof Animal);    // true</code></pre>
<ol start="2">
<li>그런데, 대부분의 클래스에는 정적 메서드 <code>Symbol.hasInstance</code>가 구현되어있지 않다. 이럴 때는 일반적인 로직이 사용된다. <code>obj instanceof Class</code>는 <code>Class.prototype</code>이 객체 <code>obj</code>의 프로토타입 체인 상의 프로토타입 중 하나와 일치하는지 확인한다. 비교는 차례 차례 거슬러 올라가면 확인한다.</li>
</ol>
<pre><code class="language-javascript">obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
obj.__proto__.__proto__.__proto__ === Class.prototype?
...
// 이 중 하나라도 true라면 true를 반환한다.
// 그렇지 않고 체인의 끝에 도달하면 false를 반환한다.</code></pre>
<hr>
<h1 id="믹스인mixin">믹스인(mixin)</h1>
<p>자바스크립트는 단일 상속만을 허용하는 언어이다. 객체에는 단 하나의 <code>[[Prototype]]</code>만 있을 수 있고, 클래스는 하나의 클래스만 상속받을 수 있다.</p>
<p>그런데 이런 제약사항이 한계처럼 느껴질 때가 있다. 예를 들어, 클래스 <code>User</code>와 이벤트를 생성해준느 코드가 담긴 클래스 <code>EventEmitter</code>가 있는데, <code>EventEmitter</code>의 기능을 <code>User</code>에 추가해서 사용자가 이벤트를 emit할 수 있게 해주고 싶다고 가정하자. 이럴 때 <code>믹스인</code>이라는 개념을 사용하면 도움이 된다.</p>
<p>위키피디아에서 <code>믹스인</code>은 다른 클래스를 상속받을 필요 없이, 이들 클래스에 구현되어있는 메서드를 담고 있는 객체(또는 클래스)라고 정의한다. </p>
<p>다시 말해, <code>믹스인</code>은 특정 행동을 실행해주는 메서드를 가지고있는데 단독으로는 쓰이지 않고 다른 클래스에 행동을 더해주는 용도로 사용된다.</p>
<p>객체 지향 언어에서 주로 다른 클래스들의 메서드 조합을 포함하는 클래스를 의미한다.</p>
<h3 id="믹스인-예시">믹스인 예시</h3>
<p>자바스크립트에서 믹스인을 구현할 수 있는 가장 쉬운 방법은 유용한 메서드 여러 개가 담긴 객체를 하나 만드는 것이다.</p>
<p>이렇게 하면 다수의 메서드를 원하는 클래스의 프로토타입에 쉽게 병할할 수 있다.</p>
<pre><code class="language-javascript">// 믹스인
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// 사용법
// 클래스 생성
class User {
  constructor(name) {
    this.name = name;
  }
}

// 메서드 복사
Object.assign(User.prototype, sayHiMixin);

// 이제 User 클래스로 생성한 인스턴스는 인사를 할 수 있다.
new User(&quot;Dude&quot;).sayHi();    // Hello Dude</code></pre>
<p>위 예시는 상속 없이 <code>User</code> 클래스에 믹스인을 사용해서 메서드만 간단히 복사했다. 믹스인을 활용하면 <code>User</code>가 아래 예시처럼 다른 클래스를 상속받는 동시에, 믹스인에 구현된 추가 메서드도 사용할 수 있다.</p>
<pre><code class="language-javascript">class User extends Person {
  // ...
}

Object.assign(User.prototype, sayHiMixin);</code></pre>
<p>믹스인 안에서 믹스인 상속을 사용하는 것도 가능하다. 아래 예시에서 <code>sayHiMixin</code>은 <code>sayMixin</code>을 상속받는다.</p>
<pre><code class="language-javascript">let sayMixin = {
  say(phrase) {
    alert(phrase);
  }
};

let sayHiMixin = {
  __proto__: sayMixin, // (Object.create를 사용해 프로토타입을 설정할 수도 있다.)

  sayHi() {
    // 부모 메서드 호출
    super.say(`Hello ${this.name}`); // (*)
  },
  sayBye() {
    super.say(`Bye ${this.name}`); // (*)
  }
};

class User {
  constructor(name) {
    this.name = name;
  }
}

// 메서드 복사
Object.assign(User.prototype, sayHiMixin);

// 이제 User가 인사를 할 수 있다.
new User(&quot;Dude&quot;).sayHi(); // Hello Dude!</code></pre>
<p>아래 그림처럼 객체 <code>sayHiMixin</code>에서 부모 메서드 <code>super.say()</code>를 호출하면, 클래스가 아닌 <code>sayHiMixin</code>의 프로토타입에서 메서드를 찾는다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c8140487-a95a-4f41-93c6-08542ec6937b/image.png" alt=""></p>
<p>이는 <code>sayHi</code>와 <code>sayBye</code>가 생성된 곳이 <code>sayHiMixin</code>이기 때문이다. 따라서 메서드를 복사했더라도, 이 메서드들의 숨김 프로퍼티인 <code>[[HomeObject]]</code>는 위 그림처럼 <code>sayHiMixin</code>을 참조한다.</p>
<p>메서드의 <code>super</code>가 <code>[[HomeObject]]</code>가 가리키는 객체의 <code>[[Prototype]]</code> 내에서 부모 메서드를 찾기 때문에, 메서드는 클래스 <code>User</code> <code>[[Prototype]]</code>이 아닌 객체 <code>sayHiMixin</code>의 <code>[[Prototype]]</code>을 검색한다.</p>
<h3 id="이벤트-믹스인">이벤트 믹스인</h3>
<p>실제로 사용할 수 있는 믹스인을 만들어보자.</p>
<p>상당수의 브라우저 객체는 이벤트 생성이라는 중요한 기능을 가지고 있다.</p>
<p>이벤트는 정보를 필요로 하는 곳에 정보를 널리 알리는(broadcast) 훌륭한 수단이다. </p>
<p>아래 예시에서 클래스나 객체에 이벤트 관련 함수를 쉽게 추가할 수 있도록 해주는 믹스인을 만들어 보자. 믹스인은 다음의 메서드를 가질 것이다.</p>
<ul>
<li>뭔가 중요한 일이 발생했을 때 이벤트를 생성하는 메서드 <code>.trigger(name, [...data])</code>를 제공한다. 인수 <code>name</code>은 이벤트 이름이고, 뒤따르는 조건부 인수는 이벤트 데이터 정보를 담는다.</li>
<li>메서드 <code>.on(name, handler)</code>은 <code>name</code>에 해당하는 이벤트에 리스너로 <code>handler</code> 함수를 추가한다. <code>.on()</code>은 이벤트(<code>name</code>)가 트리거 될 때 호출되고, <code>.trigger</code> 호출에서 인수를 얻는다.</li>
<li>메서드 <code>.off(name, handler)</code>는 <code>handler</code> 리스너를 제거한다.</li>
</ul>
<p>위 믹스인을 추가하면 아래 예시들에 적용할 수있다.</p>
<ul>
<li>사용자가 로그인할 때 객체 <code>user</code>가 <code>&quot;login&quot;</code>이라는 이벤트를 생성할 수 있게 된다.</li>
<li>객체 <code>calendar</code>는 <code>user</code>가 생성한 이벤트인 <code>&quot;login&quot;</code>을 듣고 사용자에 맞는 달력을 보여줄 수 있다.</li>
<li>메뉴의 항목을 선택했을 때 객체 <code>menu</code>가 <code>&quot;select&quot;</code>라는 이벤트를 생성하고, 어떤 객체는 <code>&quot;select&quot;</code>에 반응하는 이벤트 핸들러를 할당할 수 있다.</li>
</ul>
<p>이벤트 믹스인을 구현해보자.</p>
<pre><code class="language-javascript">let eventMixin = {
  /**
   *    이벤트 구독
   *    사용패턴: menu.on(&quot;select&quot;, function(item) { ... }
   */
  on(eventName, handler) {
    if (!this._eventHandlers) this._eventHandler = {};
    if (!this._eventHandlers[eventName]) {
      this._eventHandlers[eventName] = [];
    }
    this._eventHandlers[eventName].push(handler);
  },

  /**
   *    구독 취소
   *    사용패턴: menu.off(&quot;select&quot;, handler)
   */
  off(eventName, handler) {
    let handlers = this._eventHandlers?.[eventName];
    if (!handlers) return;
    for (let i = 0; i &lt; handlers.length; i++) {
      if (handlers[i] === handler) {
        handlers.splice(i--, 1);
      }
    }
  },

  /**
   *    주어진 이름과 데이터를 기반으로 이벤트 생성
   *    사용패턴: this.trigger(&quot;select&quot;, data1, data2);
   */
  trigger(eventName, ...args) {
    if (!this._eventHandlers?.[eventName]) {
      return;    // no handlers for that event name
    }

    // 핸들러 호출
    this._eventHandlers[eventName].forEach(handler =&gt; handler.apply(this, args));
  }
};</code></pre>
<p>위 믹스인은 아래와 같이 동작한다.</p>
<ul>
<li><code>on(eventName, handler)</code> 메서드 : <code>eventName</code>에 해당하는 이벤트가 발생하면 실행시킬 함수 <code>handler</code>를 할당한다. 한 이벤트에 대응하는 핸들러가 여러 개 있을 때, 프로퍼티 <code>_eventHandlers</code>는 핸들러가 담긴 배열을 저장한다.</li>
<li><code>off(eventName, handler)</code> 메서드 : 핸들러 리스트에서 <code>handler</code>를 제거한다.</li>
<li><code>trigger(eventName, ...args)</code> 메서드 : 이벤트를 생성한다. <code>_eventHandlers[eventName]</code>에 있는 모든 핸들러가 <code>...args</code>와 함께 호출된다.</li>
</ul>
<p>아래는 사용 예시다.</p>
<pre><code class="language-javascript">// 클래스 생성
class Menu {
  choose(value) {
    this.trigger(&quot;select&quot;, value);
  }
}

// 이벤트 관련 메서드가 구현된 믹스인 추가
Object.assign(Menu.prototype, eventMixin);

let menu = new Menu();

// 메뉴 항목을 선택할 때 호출될 핸들러 추가

// 메뉴 항목을 선택할 때 호출될 핸들러 추가
menu.on(&quot;select&quot;, value =&gt; alert(`선택된 값: ${value}`));

// 이벤트가 트리거 되면 핸들러가 실행되어 얼럿창이 뜸
menu.choose(&quot;123&quot;);    // 얼럿창 메시지: Value selected: 123</code></pre>
<p>이제 <code>menu.on(...)</code>을 사용해서 메뉴 선택이라는 이벤트를 들을 수 있고, 이에 반응하는 코드를 추가할 수 있다.</p>
<p>위와 같은 믹스인 <code>eventMixin</code>을 사용하면 특정 동작을 구현한 메서드들을 상속 체이닝에 포함시키지 않고도 원하는 클래스 모두에 추가할 수 있다.</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/classes">https://ko.javascript.info/classes</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 1. 17 자바스크립트) 객체 프로퍼티 설정 / 프로토타입과 프로토타입 상속
]]></title>
            <link>https://velog.io/@dev_diver/22.-1.-17-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%84%A4%EC%A0%95-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EA%B3%BC-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85-%EC%83%81%EC%86%8D</link>
            <guid>https://velog.io/@dev_diver/22.-1.-17-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%84%A4%EC%A0%95-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EA%B3%BC-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85-%EC%83%81%EC%86%8D</guid>
            <pubDate>Tue, 17 Jan 2023 13:41:46 GMT</pubDate>
            <description><![CDATA[<h1 id="프로퍼티-플래그와-설명자">프로퍼티 플래그와 설명자</h1>
<h3 id="프로퍼티-플래그">프로퍼티 플래그</h3>
<p>객체의 프로퍼티는 값 뿐만 아니라 <code>플래그(flag)</code>라 불리는 특별한 속성 세 가지를 갖는다.</p>
<ul>
<li><code>writable</code> : <code>true</code>이면 프로퍼티의 값을 수정할 수 있다.</li>
<li><code>enumerable</code> : <code>true</code>이면 반복문을 사용해서 프로퍼티를 나열할 수 있다.</li>
<li><code>configurable</code> : <code>true</code>이면 프로퍼티를 삭제하거나 플래그 수정이 가능하다.</li>
</ul>
<p>지금까지 해왔던 방식으로 객체에 프로퍼티를 만들면 해당 프로퍼티의 플래그는 모두 <code>true</code>가 된다.</p>
<p>먼저 플래그를 얻는 방법을 알아보자. <code>Object.getOwnPropertyDescriptor</code> 메서드를 사용하면 특정 프로퍼티에 대한 정보를 모두 얻을 수 있다.</p>
<pre><code class="language-javascript">let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);</code></pre>
<p>위 메서드를 호출하면 <code>프로퍼티 설명자(descriptor)</code>라 불리는 객체가 반환되는데, 여기에 프로퍼티 값과 세 가지 플래그에 대한 정보가 모두 담겨져있다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;
};

let descriptor = Object.getOwnPropertyDescriptor(user, &#39;name&#39;);

alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
  &quot;value&quot;: &quot;John&quot;,
  &quot;writable&quot;: true,
  &quot;enumerable&quot;: true,
  &quot;configurable&quot;: true
}
*/</code></pre>
<p><code>Object.defineProperty</code> 메서드를 사용하면 프로퍼티의 플래그를 변경할 수 있다. 객체에 해당 프로퍼티가 있으면 인수로 넘겨받은 정보대로 플래그를 변경해준다. 만약 프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해서 새로운 프로퍼티를 만든다. 이 때, 플래그 정보가 없으면 플래그값은 자동으로 <code>false</code>가 된다.</p>
<pre><code class="language-javascript">Object.defineProperty(obj, propertyName, descriptor)</code></pre>
<p>아래 예시를 보면 새로운 프로퍼티 <code>name</code>이 만들어지고, 모든 플래그 값이 <code>false</code>가 된 것을 확인할 수 있다.</p>
<pre><code class="language-javascript">let user = {};

Object.defineProperty(user, &quot;name&quot;, {
  value: &quot;John&quot;
});

let descriptor = Object.getOwnPropertyDescriptor(user, &#39;name&#39;);

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  &quot;value&quot;: &quot;John&quot;,
  &quot;writable&quot;: false,
  &quot;enumerable&quot;: false,
  &quot;configurable&quot;: false
}
 */</code></pre>
<h3 id="writable-플래그">writable 플래그</h3>
<p><code>writable</code> 플래그를 사용해서 프로퍼티에 값을 쓰거나, 못쓰게 만들 수 있다.</p>
<pre><code class="language-javascript">// 이미 존재하는 프로퍼티 변경
let user = {
  name: &quot;John&quot;
};

Object.defineProperty(user, &quot;name&quot;, {
  writable: false
});

user.name = &quot;Pete&quot;; // Error: Cannot assign to read only property &#39;name&#39;

// 새로운 프로퍼티에 적용
let user = { };

Object.defineProperty(user, &quot;name&quot;, {
  value: &quot;John&quot;,
  // defineProperty를 사용해 새로운 프로퍼티를 만들 땐, 어떤 플래그를 true로 할지 명시해주어야 한다.
  enumerable: true,
  configurable: true
});

alert(user.name); // John
user.name = &quot;Pete&quot;; // Error</code></pre>
<h3 id="enumerable-플래그">enumerable 플래그</h3>
<p><code>enumerable</code> 플래그 값을 이용해서 프로퍼티가 반복문에 나타나게(열거 가능) 혹은 나타나지 않게(열거 불가능) 만들 수 있다.</p>
<p>객체 내장 메서드 <code>toString</code>은 열거가 불가능하지만, 커스텀으로 만든 <code>toString</code> 메서드는 반복문에 나타난다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  toString() {
    return this.name;
  }
};

//커스텀 toString은 for...in을 사용해 열거할 수 있습니다.
for (let key in user) alert(key); // name, toString</code></pre>
<p>위에서 설명한 <code>enumerable</code> 플래그 값을 <code>false</code>로 설정해서 <code>for in</code> 반복문에 나타나지 않게 할 수 있다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  toString() {
    return this.name;
  }
};

Object.defineProperty(user, &quot;toString&quot;, {
  enumerable: false
});

// 이제 for...in을 사용해 toString을 열거할 수 없게 되었습니다.
for (let key in user) alert(key); // name</code></pre>
<h3 id="configurable-플래그">configurable 플래그</h3>
<p><code>configurable</code> 플래그를 통해 해당 프로퍼티를 객체에서 지우거나 지울 수 없게, 플래그 설정을 가능하게하거나 가능하지 않게 만들 수 있다.</p>
<p>기본적으로 <code>false</code>로 설정된 몇몇 내장 객체의 프로퍼티가 있다. 예를 들어 내장 객체<code>Math</code>의 <code>PI</code> 프로퍼티가 대표적인 예이다. 이 프로퍼티는 쓰기와 열거, 구성이 불가능하다.</p>
<pre><code class="language-javascript">let descriptor = Object.getOwnPropertyDescriptor(Math, &#39;PI&#39;);

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  &quot;value&quot;: 3.141592653589793,
  &quot;writable&quot;: false,
  &quot;enumerable&quot;: false,
  &quot;configurable&quot;: false
}
*/

Math.PI = 3; // Error

// 수정도 불가능하지만 지우는 것 역시 불가능하다</code></pre>
<p><code>configurable</code> 플래그를 <code>false</code>로 설정하면 돌이킬 방법이 없다. <code>defineProperty</code>를 써도 값을 <code>true</code>로 돌릴 수 없다.</p>
<p><code>configurable</code> 플래그를 <code>false</code>로 설정했을 때 만들어내는 구체적인 제약사항은 아래와 같다.</p>
<ul>
<li><code>configurable</code> 플래그를 수정할 수 없다.</li>
<li><code>enumerable</code> 플래그를 수정할 수 없다.</li>
<li><code>writable</code> 플래그의 값을 <code>false</code>에서 <code>true</code>로 바꿀 수 없다. 단, <code>true</code>에서 <code>false</code>로의 변경은 가능하다.</li>
<li>접근자 프로퍼티 <code>get / set</code>을 변경할 수 없다. 단, 새롭게 만드는 것은 가능하다.</li>
</ul>
<pre><code class="language-javascript">let user = { };

Object.defineProperty(user, &quot;name&quot;, {
  value: &quot;John&quot;,
  writable: false,
  configurable: false
});

// user.name 프로퍼티의 값이나 플래그를 변경할 수 없습니다.
// 아래와 같이 변경하려고 하면 에러가 발생합니다.
//   user.name = &quot;Pete&quot;
//   delete user.name
//   Object.defineProperty(user, &quot;name&quot;, { value: &quot;Pete&quot; })
Object.defineProperty(user, &quot;name&quot;, {writable: true}); // Error</code></pre>
<h3 id="objectdefineproperties">Object.defineProperties</h3>
<p><code>Object.defineProperties(obj, descriptors)</code> 메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있다.</p>
<pre><code class="language-javascript">Object.defineProperties(obj, {
  propName1: descriptor1,
  propName2: descriptor2
  // ...
});

Object.defineProperties(user, {
  name: { value: &quot;John&quot;, writable: false },
  surname: { value: &quot;Smith&quot;, writable: false },
  // ...
});</code></pre>
<h3 id="objectgetownpropertydescriptors">Object.getOwnPropertyDescriptors</h3>
<p><code>Object.getOwnPropertyDescriptors(obj)</code> 메서드를 사용하면 모든 프로퍼티 설명자를 한 번에 가져올 수 있다.</p>
<p>해당 메서드와 <code>Object.definedProperties</code> 메서드를 함께 사용하면 객체 복제 시 플래그도 함께 복사할 수 있다.</p>
<pre><code class="language-javascript">let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));</code></pre>
<p>위 방법을 사용하면 프로퍼티 뿐만 아니라 <code>for in</code> 반복문 방식으로는 복제 못하는 플래그 정보, 심볼형 프로퍼티도 복제할 수 있다. </p>
<hr>
<h1 id="프로퍼티-getter와-setter">프로퍼티 getter와 setter</h1>
<p>객체의 프로퍼티는 두 종류로 나뉜다.</p>
<ul>
<li><code>데이터 프로퍼티(data property)</code> : 지금까지 사용한 모든 일반적인 프로퍼티이다.</li>
<li><code>접근자 프로퍼티(accessor property)</code> : 본질은 함수이며, 값을 get하거나 set하는 역할을 담당한다. 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 동작한다.</li>
</ul>
<h3 id="getter와-setter">getter와 setter</h3>
<p>접근자 프로퍼티는 <code>getter</code>와 <code>setter</code> 메서드로 표현된다. 객체 리터럴 안에서 <code>get</code>과 <code>set</code>으로 나타낼 수 있다.</p>
<pre><code class="language-javascript">let obj = {
  get propName() {
    // getter, obj.propName을 실행할 때 실행되는 코드
  },

  set propName(value) {
    // setter, obj.propName = value를 실행할 때 실행되는 코드
  }
};</code></pre>
<p><code>getter</code> 메서드는 <code>obj.propName</code>을 사용해서 프로퍼티를 읽으려고 할 때 실행되고, <code>setter</code> 메서드는 <code>obj.propName = value</code>으로 프로퍼티에 값을 할당하려 할 때 실행된다.</p>
<p>프로퍼티 <code>name</code>의 값이 <code>&quot;John&quot;</code>이고 <code>surname</code>의 값이 <code>&quot;Smith&quot;</code>인 객체 <code>user</code>를 만들어보자. </p>
<p>이 객체에 <code>fullName</code>이라는 프로퍼티를 추가할 때, 기존 값을 복사-붙여넣기 하지 않고 <code>fullName</code>이 <code>John Smith</code>가 되도록 접근자 프로퍼티를 구현하자.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  surname: &quot;Smith&quot;,

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName);    // John Smith</code></pre>
<p>객체 외부에서 프로퍼티를 호출할 때, 접근자 프로퍼티를 사용하면 함수처럼 호출하지 않고 일반 프로퍼티에서 값에 접근하는 것처럼 프로퍼티를 사용할 수 있다. 나머지 작업은 <code>getter / setter</code> 메서드 뒷단에서 처리해준다.</p>
<p>이어서, 위 예시의 <code>fullName</code>은 <code>getter</code> 메서드만 가지고 있기 때문에 <code>user.fullName = value</code> 처럼 사용해서 값을 할당하려고 하면 에러가 발생한다.</p>
<pre><code class="language-javascript">let user = {
  get fullName() {
    return `...`;
  }
};

user.fullName = &quot;Test&quot;; // Error (프로퍼티에 getter 메서드만 있어서 에러가 발생한다.)</code></pre>
<p><code>user.fullName</code>에 <code>setter</code> 메서드를 추가해서 에러가 발생하지 않도록 고쳐보자.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  surname: &quot;Smith&quot;,

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(&quot; &quot;);
  }
};

// 주어진 값을 사용해 set fullName이 실행된다.
user.fullName = &quot;Alice Cooper&quot;;

alert(user.name); // Alice
alert(user.surname); // Cooper</code></pre>
<p>이렇게 <code>getter</code>와 <code>setter</code> 메서드를 구현하면 객체에는 <code>fullName</code>이라는 가상의 프로퍼티가 생긴다. 가상의 프로퍼티는 읽고 쓸 수는 있지만, 실제로 존재하지 않는다.</p>
<h3 id="접근자-프로퍼티-설명자">접근자 프로퍼티 설명자</h3>
<p>데이터 프로퍼티의 설명자와 접근자 프로퍼티의 설명자는 다르다. 접근자 프로퍼티에는 설명자 <code>value</code>와 <code>writable</code>이 없는 대신에 <code>get</code> 과 <code>set</code>이라는 함수가 있다.</p>
<ul>
<li><code>get</code> : 인수가 없는 함수로, 프로퍼티를 읽을 때 동작한다.</li>
<li><code>set</code> : 인수가 하나인 함수로, 프로퍼티에 값을 쓸 때 호출된다.</li>
<li><code>enumerable</code> : 데이터 프로퍼티와 동일하다.</li>
<li><code>configurable</code> : 데이터 프로퍼티와 동일하다.</li>
</ul>
<p>데이터 프로퍼티를 정의할 때 처럼 접근자 프로퍼티도 아래와 같이 <code>defineProperty</code>에 설명자 <code>get</code>과 <code>set</code>을 전달하면 접근자 프로퍼티를 설정할 수 있다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  surname: &quot;Smith&quot;
};

Object.defineProperty(user, &#39;fullName&#39;, {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(&quot; &quot;);
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname</code></pre>
<p>객체의 프로퍼티는 접근자 프로퍼티나 데이터 프로퍼티 중 한 종류에만 속할 수 있다. 즉, <code>get / set</code> 메서드만 가지거나 <code>value</code>만 가질 수 있다. 한 프로퍼티에 <code>get / set</code>과 <code>value</code>를 동시에 설정하면 에러가 발생한다.</p>
<pre><code class="language-javascript">// Error: Invalid property descriptor.
Object.defineProperty({}, &#39;prop&#39;, {
  get() {
    return 1
  },

  value: 2
});</code></pre>
<h3 id="getter와-setter-똑똑하게-활용하기">getter와 setter 똑똑하게 활용하기</h3>
<p><code>getter</code>와 <code>setter</code>를 실제 프로퍼티 값을 감싸는 래퍼처럼 사용하면 실제 프로퍼티 값을 원하는 대로 통제할 수 있다.</p>
<p>아래 예시에서는 <code>name</code> 프로퍼티를 위한 <code>setter</code>를 만들어서 이름이 짧아지는 것을 방지하고 있다. 실제 값은 별도의 프로퍼티 <code>_name</code>에 저장된다.</p>
<pre><code class="language-javascript">let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length &lt; 4) {
      alert(&quot;입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.&quot;);
      return;
    }
    this._name = value;
  }
};

user.name = &quot;Pete&quot;;
alert(user.name); // Pete

user.name = &quot;&quot;; // 너무 짧은 이름을 할당하려 함</code></pre>
<p>객체 <code>user</code>의 이름은 <code>_name</code>에 저장되고, 프로퍼티에 접근하는 것은 <code>getter</code>와 <code>setter</code>를 통해 이루어진다. 기술적으로 외부 코드에서 <code>user._name</code>을 사용해서 바로 접근할 수 있다. 하지만 <code>_</code>로 시작하는 프로퍼티는 객체 내부에서만 활용하고 외부에서는 건드리지 않는 것이 관습이다.</p>
<h3 id="호환성을-위해-사용하기">호환성을 위해 사용하기</h3>
<p>접근자 프로퍼티는 어느 때나 <code>getter</code>와 <code>setter</code>를 사용해서 데이터 프로퍼티의 행동과 값을 원하는 대로 조정할 수 있다는 점에서 유용하다.</p>
<p>데이터 프로퍼티 <code>name</code>과 <code>age</code>를 사용해서 사용자를 나타내는 객체를 구현한다고 가정해보자.</p>
<pre><code class="language-javascript">function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User(&quot;John&quot;, 25);

alert( john.age ); // 25</code></pre>
<p>그런데, 요구사항이 바뀌어서 <code>age</code> 대신에 <code>birthday</code>를 저장해야 한다고 하자.</p>
<pre><code class="language-javascript">function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let john = new User(&quot;John&quot;, new Date(1992, 6, 1));</code></pre>
<p>위 방식처럼 생성자 함수를 수정하면 기존 코드 중 프로퍼티 <code>age</code>를 사용하고 있는 코드를 모두 수정해야 한다. <code>age</code>가 사용되는 부분을 모두 찾아서 수정하는 것도 가능하지만, 시간이 오래 걸린다. 게다가 여러 사람이 <code>age</code>를 사용하고 있다면 모두 찾아 수정하는 것 자체가 힘들다.</p>
<p><code>age</code> 프로퍼티는 그대로 있어도 나쁠 것이 없다. 그래서 기존 코드들은 그대로 두고 코드를 수정해보자. 대신 <code>age</code> 프로퍼티를 위한 <code>getter</code>를 추가해서 문제를 해결해 보자.</p>
<pre><code class="language-javascript">function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // age는 현재 날짜와 생일을 기준으로 계산된다.
  Object.defineProperty(this, &quot;age&quot;, {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User(&quot;John&quot;, new Date(1992, 6, 1));

alert( john.birthday ); // birthday를 사용할 수 있다.
alert( john.age );      // age 역시 사용할 수 있습니다다.</code></pre>
<hr>
<h1 id="프로토타입-상속">프로토타입 상속</h1>
<p>기존에 있는 기능을 가져와 확장해야 하는 경우에는 어떻게 할까? 예시를 살펴보자.</p>
<p>사람에 관한 프로퍼티와 메서드를 가진 <code>user</code>라는 객체가 있는데, <code>user</code>와 유사하지만 약간의 차이가 있는 <code>admin</code>과 <code>guest</code>라는 객체를 만들어야 한다고 가정하자.</p>
<p>이 때 프로토타입 상속을 이용하면 <code>user</code>의 메서드를 복사/붙여넣거나 다시 구현하지 않고, <code>user</code>에 약간의 기능을 얹어 <code>admin</code>과 <code>guest</code> 객체를 만들 수 있다.</p>
<h3 id="prototype과-__proto__"><code>[[Prototype]]</code>과 <code>__proto__</code></h3>
<p>자바스크립트의 객체는 <code>[[Prototype]]</code>이라는 숨김 프로퍼티를 갖는다. 이 숨김 프로퍼티 값은 <code>null</code>이거나 다른 객체를 참조하는데, 참조 대상을 <code>프로토타입</code>이라 부른다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/574eb398-e581-44ad-8707-f7bf9224a086/image.png" alt=""></p>
<p>어떤 객체에서 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 객체 내에 없으면 자바스크립트는 자동으로 프로토타입에서 프로퍼티를 찾는다. 이러한 방식을 통해 <code>프로토타입 상속</code>을 구현할 수 있다.</p>
<p><code>[[Prototype]]</code> 프로퍼티는 숨김 프로퍼티이지만 <code>__proto__</code>를 사용해서 개발자가 값을 설정할 수 있다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true
};

let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;</code></pre>
<p><code>__proto___</code>는 <code>[[Prototype]]</code>과 다르다. <code>__proto__</code>는 <code>[[Prototype]]</code>의 getter이자 setter이다. 하위 호환성 때문에 여전히 <code>__proto__</code>를 사용할 수는 있지만, 최신 스크립트에서는 <code>__proto__</code> 대신 함수 <code>Object.getPrototypeOf</code>나 <code>Object.setPrototypeOf</code>를 써서 프로토타입을 get하거나 set한다.</p>
<p>다시 예시로 돌아가면 객체 <code>rabbit</code>에서 프로퍼티를 얻고싶은데 해당 프로퍼티가 없다면, 자바스크립트는 자동으로 프로토타입인 <code>animal</code> 객체에서 프로퍼티를 얻는다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// 프로퍼티 eats과 jumps를 rabbit에서도 사용할 수 있게 되었습니다.
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/bc1da6f3-e60f-4ad0-afb9-c4391523f82c/image.png" alt=""></p>
<p>이제 <code>rabit</code>의 프로토타입은 <code>animal</code>이다. 그리고, <code>rabbit</code>은 <code>animal</code>을 상속받는다. 이렇게 프로토타입에서 상속받은 프로퍼티를 <code>상속 프로퍼티</code>라고 한다.</p>
<p>상속 프로퍼티를 사용해서 <code>animal</code>에 정의된 메서드를 <code>rabbit</code>에서 호출해 보자.</p>
<pre><code class="language-javascript">let animal = {
  eats: true,
  walk() {
    alert(&quot;동물이 걷습니다.&quot;);
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// 메서드 walk는 rabbit의 프로토타입인 animal에서 상속받았습니다.
rabbit.walk(); // 동물이 걷습니다.</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/9ddcbd9a-fda2-4bad-a2bf-454648460abf/image.png" alt=""></p>
<p>프로토타입 체인은 계속 연결될 수 있다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true,
  walk() {
    alert(&quot;동물이 걷습니다.&quot;);
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// 메서드 walk는 프로토타입 체인을 통해 상속받았습니다.
longEar.walk(); // 동물이 걷습니다.
alert(longEar.jumps); // true (rabbit에서 상속받음)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/e4a8a95f-942d-40b7-8657-31a9179033d4/image.png" alt=""></p>
<p>프로토타입 체이닝에는 두 가지 제약사항이 있다.</p>
<ul>
<li>순환 참조는 허용되지 않는다. 즉, <code>__proto__</code>를 이용해서 닫힌 형태로 다른 객체를 참조하면 에러가 발생한다.</li>
<li><code>__proto__</code>의 값은 <code>객체</code>나 <code>null</code>만 가능하다. 다른 자료형은 무시된다.</li>
</ul>
<h3 id="프로토타입은-읽기-전용이다">프로토타입은 읽기 전용이다</h3>
<p>프로토타입은 프로퍼티를 읽을 때만 사용한다. 프로퍼티를 추가, 수정하거나 지우는 연산은 객체에 직접 해야 한다.</p>
<p>객체 <code>rabbit</code>에 메서드 <code>walk</code>를 직접 할당해보자. <code>rabbit.walk()</code>를 호출하면 프로토타입에 있는 메서드가 실행되지 않고, <code>rabbit</code>에 직접 추가한 메서드가 실행된다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true,
  walk() {
    /* rabbit은 이제 이 메서드를 사용하지 않습니다. */
  }
};

let rabbit = {
  __proto__: animal
};

rabbit.walk = function() {
  alert(&quot;토끼가 깡충깡충 뜁니다.&quot;);
};

rabbit.walk(); // 토끼가 깡충깡충 뜁니다.</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/66218db5-fff8-4649-ae05-ca6bbfb3098f/image.png" alt=""></p>
<h3 id="this는-프로토타입에-영향을-받지-않는다">this는 프로토타입에 영향을 받지 않는다</h3>
<p>메서드를 객체에서 호출하던 프로토타입에서 호출하던지 상관없이 <code>this</code>는 언제나 <code>.</code> 앞에 있는 객체이다.</p>
<p>상속받은 메서드를 사용하더라도 객체는 프로토타입이 아닌 자신의 상태를 수정한다. 예시를 살펴보자. 메서드 저장소 역할을 하는 객체 <code>animal</code>을 <code>rabbit</code>이 상속받았다고 하자. </p>
<pre><code class="language-javascript">// animal엔 다양한 메서드가 있습니다.
let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`동물이 걸어갑니다.`);
    }
  },
  sleep() {
    this.isSleeping = true;
  }
};

let rabbit = {
  name: &quot;하얀 토끼&quot;,
  __proto__: animal
};

// rabbit에 새로운 프로퍼티 isSleeping을 추가하고 그 값을 true로 변경합니다.
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (프로토타입에는 isSleeping이라는 프로퍼티가 없습니다.)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/d8a296e1-0a6b-44df-ad44-cc42c8b8dc3a/image.png" alt=""></p>
<p><code>rabbit.sleep()</code>을 호출하면 객체 <code>rabbit</code>에 <code>isSleeping</code> 프로퍼티가 추가된다. 이 때 상속받은 메서드의 <code>this</code>는 <code>animal</code>이 아닌 실제 메서드가 호출되는 시점의 <code>.</code> 앞에 있는 객체가 된다. 따라서 <code>this</code>에 데이터를 쓰면 <code>animal</code>이 아닌 해당 객체의 상태가 변화한다. 즉, 메서드는 공유되지만, 객체의 상태는 공유되지 않는다!</p>
<h3 id="for-in-반복문에-객체-사용하기">for in 반복문에 객체 사용하기</h3>
<p><code>for in</code>은 객체 자신의 프로퍼티 키 뿐만 아니라 상속받은 프로퍼티들의 키도 순회대상에 포함시킨다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// Object.keys는 객체 자신의 키만 반환합니다.
alert(Object.keys(rabbit)); // jumps

// for..in은 객체 자신의 키와 상속 프로퍼티의 키 모두를 순회합니다.
for(let prop in rabbit) alert(prop); // jumps, eats</code></pre>
<p><code>Object.hasOwnProperty(key)</code>를 이용하면 상속 프로퍼티를 순회 대상에서 제외할 수 있다. 이 메서드는 <code>key</code>에 대응하는 프로퍼티가 상속 프로퍼티가 아니고 객체에 직접 구현되어있는 프로퍼티라면 <code>true</code>를 반환한다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`객체 자신의 프로퍼티: ${prop}`); // 객체 자신의 프로퍼티: jumps
  } else {
    alert(`상속 프로퍼티: ${prop}`); // 상속 프로퍼티: eats
  }
}</code></pre>
<p>위 예시의 상속 관계를 그림으로 나타내면 아래와 같다. 그림을 보면 <code>for in</code> 안에서 <code>rabbit</code>에 사용한 메서드 <code>hasOwnProperty</code>가 상속받은 메서드인 것을 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c69c2754-6e19-41d3-889e-78ca230f01b8/image.png" alt=""></p>
<p>근데 상속 프로퍼티인 <code>eats</code>는 출력되지만, <code>hasOwnProperty</code>는 출력되지 않았다. 왜일까? 이유는 간단하다. <code>Object.prototype</code>에 있는 모든 메서드의 <code>enumerable</code> 플래그는 <code>false</code>이고, <code>for in</code>은 열거 가능한 프로퍼티만 순회 대상에 포함하기 때문이다.</p>
<hr>
<h1 id="함수의-prototype-프로퍼티">함수의 prototype 프로퍼티</h1>
<p>생성자 함수에 <code>new</code> 연산자를 사용해 만든 객체는 생성자 함수의 프로토타입 정보를 사용한다.</p>
<p>생성자 함수 <code>F</code>의 프로토타입을 의미하는 <code>F.prototype</code>에서 <code>prototype</code>은 <code>F</code>에 정의된 <strong>일반 프로퍼티</strong>이다. 다시 말하면 <code>F.prototype</code>에서 <code>prototype</code>은 앞 서 배운 숨김 프로퍼티인 <code>[[Prototype]]</code>과 다른 일반적인 프로퍼티이다. </p>
<p><code>F.prototype</code> 프로퍼티는 <code>new F</code>를 호출할 때만 사용된다. <code>new F</code>를 호출할 때 만들어지는 새로운 객체의 <code>[[Prototype]]</code>을 할당해 준다.</p>
<p>아래 예시를 살펴보자. </p>
<pre><code class="language-javascript">let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit(&quot;흰 토끼&quot;); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true</code></pre>
<p><code>Rabbit.protoype = animal</code>은 <code>new Rabbit</code>을 호출해서 만든 새로운 객체의 <code>[[Prototype]]</code>을 <code>animal</code>로 설정하라는 것을 의미한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/39ab9401-dc40-4889-aef8-da21d338d025/image.png" alt=""></p>
<p>위 그림에서 세로 화살표가 객체 <code>rabbit</code>이 <code>animal</code>을 상속받았다는 것을 의미한다.</p>
<h3 id="함수의-prototype-프로퍼티와-constructor-프로퍼티">함수의 prototype 프로퍼티와 constructor 프로퍼티</h3>
<p>모든 함수는 특별히 할당하지 않더라도 기본적으로 <code>prototype</code>을 갖는다.</p>
<p><code>prototype</code> 프로퍼티는 <code>constructor</code> 프로퍼티 하나만 가지고 있는 객체를 가리키는데, 여기서 <code>constructor</code> 프로퍼티는 함수 자신을 가리킨다.</p>
<pre><code class="language-javascript">// 함수를 만들기만 해도 디폴트 프로퍼티인 prototype이 설정된다.
function Rabbit() {}    // Rabbit.prototype = { constructor: Rabbit };

alert( Rabbit.prototype.constructor == Rabbit ); // true</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/c933123b-9514-4c58-945d-5bdd4bea0359/image.png" alt=""></p>
<p>특별한 조작을 가하지 않았다면 <code>new Rabbit</code>을 실행해서 만든 모든 객체는 <code>constructor</code> 프로퍼티를 사용할 수 있는데, 이 때 <code>[[Prototype]]</code>을 거쳐간다.</p>
<pre><code class="language-javascript">function Rabbit() {}    // Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // {constructor: Rabbit}을 상속받음

alert(rabbit.constructor == Rabbit); // true ([[Prototype]]을 거쳐 접근함)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/f95fe3ea-d2f8-40de-8f54-989d6965e807/image.png" alt=""></p>
<p>생성자 함수로 만들어진 객체의 <code>constructor</code> 프로퍼티를 이용하면 새로운 객체를 만들 때 사용할 수 있다. 이런 방법은 객체를 만들 때 어떤 생성자가 사용되었는지 알 수 없을 때 유용하게 쓸 수 있다.</p>
<pre><code class="language-javascript">function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit(&quot;흰 토끼&quot;);

let rabbit2 = new rabbit.constructor(&quot;검정 토끼&quot;);</code></pre>
<p><code>constructor</code>에 가장 중요한 점은 <code>constructor</code>와 관련해서 벌어지는 모든 일은 전적으로 개발자에게 달려있다는 점이다.</p>
<p>함수에 기본으로 설정되는 <code>prototype</code> 프로퍼티 값을 다른 객체로 바꾸면 무슨일이 일어나는지 살펴보자.</p>
<pre><code class="language-javascript">function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false</code></pre>
<p>이런 상황을 방지하고 <code>constructor</code>의 기본 성질을 제대로 활용하려면 <code>prototype</code>에 뭔가를 하고 싶을 때 <code>prototype</code> 전체를 덮어쓰지 말고, <code>prototype</code>에 원하는 프로퍼티만 추가 및 제거 해야 한다.</p>
<pre><code class="language-javascript">function Rabbit() {}

// Rabbit.prototype 전체를 덮어쓰지 말고
// 원하는 프로퍼티가 있으면 그냥 추가하자
Rabbit.prototype.jumps = true
// 이렇게 하면 디폴트 프로퍼티 Rabbit.prototype.constructor가 유지된다.</code></pre>
<p>만약 실수로 <code>prototype</code>을 덮어썼다 하더라도 <code>constructor</code> 프로퍼티를 수동으로 다시 만들어주면 <code>constructor</code>를 다시 사용할 수 있다.</p>
<pre><code class="language-javascript">Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};</code></pre>
<hr>
<h1 id="내장-객체의-프로토타입">내장 객체의 프로토타입</h1>
<p><code>prototype</code> 프로퍼티는 자바스크립트 내부에서도 광범위하게 사용된다. 모든 내장 생성자 함수에서 <code>prototype</code> 프로퍼티를 사용한다.</p>
<h3 id="objectprototype">Object.prototype</h3>
<p>빈 객체가 있다고 가정하자.</p>
<pre><code class="language-javascript">let obj = {};
alert( obj );    // &quot;[object Object]&quot;</code></pre>
<p><code>obj</code>는 비어있는데, <code>&quot;[object Object]&quot;</code> 문자열을 생성하는 코드는 어디에 있을까? <code>obj = {}</code>는 <code>obj = new Object()</code>와 같다. 여기서 <code>Object</code>는 내장 객체 생성자 함수인데, 이 생성자 함수의 <code>prototype</code>은 <code>toString</code>을 비롯한 다양한 메서드가 구현되어있는 객체를 참조한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/bd785e71-089d-4dac-8305-8cb53043b7ce/image.png" alt=""></p>
<p><code>new Object()</code>를 호출하거나 리터럴 문법을 사용해서 객체를 만들 때, 새롭게 생성된 객체의 <code>[[Prototype]]</code>은 <code>Object.prototype</code>을 참조한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/bd6cc60d-c74d-4b98-ba80-13ad6d58124c/image.png" alt=""></p>
<p>따라서, <code>obj.toString()</code>이 호출되면 <code>Object.prototype</code>에서 해당 메서드를 가져오게 된다.</p>
<pre><code class="language-javascript">let obj = {};

alert(obj.__proto__ === Object.prototype); // true

alert(obj.toString === obj.__proto__.toString); //true
alert(obj.toString === Object.prototype.toString); //true</code></pre>
<h3 id="다양한-내장-객체의-프로토타입">다양한 내장 객체의 프로토타입</h3>
<p><code>Array</code>, <code>Date</code>, <code>Function</code>을 비롯한 내장 객체들 역시 프로토타입에 메서드를 저장해 놓는다.</p>
<p>예를 들어, 배열 <code>[1, 2, 3]</code>을 만들면 <code>new Array()</code>의 내부 동작에 의하여 <code>Array.prototype</code>이 배열 <code>[1, 2, 3]</code>의 프로토타입이 되고, <code>Array.prototype</code>을 통해 배열 메서드를 사용할 수 있다. 이러한 내부 동작은 메모리 효율을 높여주는 장점이 있다.</p>
<p>모든 내장 프로토타입의 상속 트리 꼭대기에는 <code>Object.prototype</code>이 있다.
<img src="https://velog.velcdn.com/images/dev_diver/post/710123dd-91f1-4f7a-8ed8-8a74a7342a72/image.png" alt=""></p>
<pre><code class="language-javascript">let arr = [1, 2, 3];

// arr은 Array.prototype을 상속받았나요?
alert( arr.__proto__ === Array.prototype ); // true

// arr은 Object.prototype을 상속받았나요?
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// 체인 맨 위엔 null이 있습니다.
alert( arr.__proto__.__proto__.__proto__ ); // null</code></pre>
<p>프로토타입 체인 상의 중복 메서드가 있을 때는 체인에 가장 가까운 곳에 있는 메서드가 사용된다. 예를 들어, <code>Object.prototype</code>에도 <code>toString</code>이 있고, <code>Array.prototype</code>에도 <code>toString</code>이 있는데, 가장 가까운 <code>Array.prototype</code>의 <code>toString</code> 메서드가 사용된다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/427cccb1-cb5d-4e5f-8d95-6c1c6f376b55/image.png" alt=""></p>
<pre><code class="language-javascript">let arr = [1, 2, 3]
alert(arr); // 1,2,3 &lt;-- Array.prototype.toString의 결과</code></pre>
<p>배열뿐만 아니라 함수 같은 다른 내장 객체들 또한 같은 방식으로 동작한다.</p>
<pre><code class="language-javascript">function f() {}

alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true, 객체에서 상속받음</code></pre>
<h3 id="원시형">원시형</h3>
<p>문자열과 숫자, 불린은 객체가 아니기에 다루기 까다롭다. 이런 원시 타입 값의 프로퍼티에 접근하려고 하면 내장 생성자 <code>String</code>, <code>Number</code>, <code>Boolean</code>을 사용하는 임시 래퍼(wrapper) 객체가 생성된다. 임시 래퍼 객체는 메서드를 제공한 후 사라진다.</p>
<p>명세서에는 각 자료형에 해당하는 래퍼 객체의 메서드를 프로토타입 안에 구현해 놓고 <code>String.prototype</code>, <code>Number.prototype</code>, <code>Boolean.prototype</code>을 사용해서 쓰도록 규정한다.</p>
<p><code>null</code>과 <code>undefined</code>에 대응하는 래퍼 객체는 없다. 따라서, 메서드와 프로퍼티뿐만 아니라 프로토타입도 사용할 수 없다.</p>
<h3 id="내장-프로토타입-변경하기">내장 프로토타입 변경하기</h3>
<p>내장 프로토타입은 수정할 수 있다. 하지만, 프로토타입은 전역으로 영향을 미치기 때문에 프로토타입을 조작하면 기존 코드와 충돌이 날 가능성이 크다. 따라서, 내장 프로토타입을 수정하는 것은 최대한 지양한다. 모던 자바스크립트에서 내장 프로토타입의 변경을 허용하는 경우는 폴리필을 만들 때 딱 하나뿐이다.</p>
<h3 id="내장-프로토타입에서-메서드-빌려오기">내장 프로토타입에서 메서드 빌려오기</h3>
<p>개발을 하다 보면 내장 프로토타입에 구현된 메서드를 빌려야 하는 경우가 생긴다.</p>
<p>유사 배열 객체를 만들고 <code>Array.prototype</code>의 메서드를 복사해보자. <code>join</code>의 내부 알고리즘은 인덱스와 <code>length</code> 프로퍼티만 확인하기 때문에 아래 코드는 에러없이 동작한다.</p>
<pre><code class="language-javascript">let obj = {
  0: &quot;Hello&quot;,
  1: &quot;world!&quot;,
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(&#39;,&#39;) ); // Hello,world!</code></pre>
<p>이처럼 메서드 빌리기는 여러 객체에서 필요한 기능을 가져와 섞는 것을 가능하게 해주기 때문에 유연한 개발을 가능하게 한다.</p>
<hr>
<h1 id="프로토타입-메서드와-__proto__가-없는-객체">프로토타입 메서드와 <code>__proto__</code>가 없는 객체</h1>
<p><code>__proto___</code>는 브라우저를 대상으로 개발하고 있다면 다소 구식이기 때문에 더는 사용하지 않는 것이 좋다. 대신 아래와 같은 모던한 메서드들을 사용하는 것이 좋다.</p>
<ul>
<li><code>Object.create(proto, [descriptors])</code> : <code>[[Prototype]]</code>이 매개변수 <code>proto</code>를 참조하는 빈 객체를 만든다. 프로퍼티 설명자를 추가로 넘길 수 있다.</li>
<li><code>Object.getPrototypeOf(obj)</code> : <code>obj</code>의 <code>[[Prototype]]</code>을 반환한다.</li>
<li><code>Object.setPrototypeOf(obj, proto)</code> : <code>obj</code>의 <code>[[Prototype]]</code>이 매개변수 <code>proto</code>가 되도록 설정한다.</li>
</ul>
<pre><code class="language-javascript">let animal = {
  eats: true
};

// 프로토타입이 animal인 새로운 객체를 생성합니다.
let rabbit = Object.create(animal);

alert(rabbit.eats); // true

alert(Object.getPrototypeOf(rabbit) === animal); // true

Object.setPrototypeOf(rabbit, {}); // rabbit의 프로토타입을 {}으로 바꿉니다.</code></pre>
<p>프로퍼티 설명자를 이용해 새 객체에 프로퍼티를 추가할 수도 있다.</p>
<pre><code class="language-javascript">let animal = {
  eats: true
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true
  }
});

alert(rabbit.jumps); // true</code></pre>
<p><code>Object.create</code>를 사용하면 <code>for in</code>을 사용해서 프로퍼티를 복사하는 것보다 더 효과적으로 객체를 복제할 수 있다. 객체의 모든 프로퍼티를 포함한 완벽한 사본이 만들어진다. 사본에는 열거 가능한 프로퍼티와 불가능한 프로퍼티, 데이터 프로퍼티, getter, setter, <code>[[Prototype]]</code> 등 모든 프로퍼티가 복제된다. 단, 복제된 객체는 얕은 복사본이다.</p>
<pre><code class="language-javascript">let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));</code></pre>
<p>참고로, 원한다면 언제나 <code>[[Prototype]]</code>에 접근하거나 설정할 수 있다. 하지만 대개는 객체를 생성할 때만 설정하고 이후에는 수정하지 않는다. 자바스크립트 엔진은 이런 시나리오를 토대로 최적화되어 있다. 프로토타입을 그때그때 바꾸는 연산은 객체 프로퍼티 접근 관련 최적화를 망치기 때문에 성능에 나쁜 영향을 미친다. 따라서, <code>[[Prototype]]</code>을 바꾸는 것이 어떤 결과를 초래할지 확실히 알거나 속도가 전혀 중요하지 않은 경우라면 프로토타입을 바꾸지 말자.</p>
<h3 id="왜-proto-사용을-지양할까">왜 <strong>proto</strong> 사용을 지양할까?</h3>
<p>아래 예시를 살펴보자. 프롬프트 창에 <code>__proto__</code>를 입력하면 값이 제대로 할당되지 않는다. <code>__proto__</code> 프로퍼티는 특별한 프로퍼티라는 것을 이미 알고 있기 때문이다. 참고로 <code>__proto__</code>는 항상 객체이거나 <code>null</code>이어야 한다.</p>
<pre><code class="language-javascript">let obj = {};

let key = prompt(&quot;입력하고자 하는 key는 무엇인가요?&quot;, &quot;__proto__&quot;);
obj[key] = &quot;...값...&quot;;

alert(obj[key]); // &quot;...값...&quot;이 아닌 [object Object]가 출력됩니다.</code></pre>
<p>개발자가 위 예시와 같은 코드를 작성할 때는 이런 결과를 의도하면서 구현하지는 않는다. 다만 키가 무엇이 되었든, 키를 저장하는데 키가 <code>__proto__</code>일 때 값이 제대로 저장되지 않는 건 명백한 버그이다.</p>
<p>위 예시에서는 이 버그가 그리 치명적이지는 않다. 그런데 할당 값(<code>obj[key]</code>의 right side)이 객체 일 때는 프로토타입이 바뀔 수 있다는 치명적인 버그가 발생할 수 있다. 프로토타입이 바뀌면 예상치 못한 일이 발생할 수 있기 때문이다.</p>
<p>개발자들은 대개 프로토타입이 중간에 바뀌는 시나리오는 배제한 채 개발을 진행한다. 이런 고정관념 때문에 프로토타입이 중간에 바뀌면서 발생한 버그는 그 원인을 쉽게 찾지 못한다. 서버 사이드에서 자바스크립트를 사용할 때는 이런 버그가 취약점이 되기도 한다.</p>
<p>이러한 문제를 어떻게 예방할 수 있을까? 객체 대신 맵을 사용하면 된다. 또, 간단하게 그냥 객체를 사용할 수도 있다.</p>
<p>앞 서 봤듯이 <code>__proto__</code>는 객체의 프로퍼티가 아니라 <code>Object.prototype</code>의 접근자 프로퍼티이다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/67c1575e-1404-4a58-a2f7-c6abc9e00866/image.png" alt=""></p>
<p>그렇기 떄문에 <code>obj.__proto__</code>를 읽거나 쓸때는 이에 대응하는 getter, setter가 프로토타입에서 호출되고 <code>obj</code>는 <code>[[Prototype]]</code>을 통해 getter와 setter에 접근한다. 다시말해, <code>__proto__</code>는 <code>[[Prototype]]</code>에 접근하기 위한 수단이지 <code>[[Prototype]]</code> 그 자체가 아니다.</p>
<p><code>Object.create(null)</code>을 사용해서 프로토타입이 없는 빈 객체를 만들면 해결할 수 있다.</p>
<pre><code class="language-javascript">let obj = Object.create(null);

let key = prompt(&quot;입력하고자 하는 key는 무엇인가요?&quot;, &quot;__proto__&quot;);
obj[key] = &quot;...값...&quot;;

alert(obj[key]); // &quot;...값...&quot;이 제대로 출력됩니다.</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/db6ae260-4233-4cbe-a9ea-de1b268f547f/image.png" alt=""></p>
<p>위 방법을 사용하면 객체는 더 이상 <code>__proto___</code>의 getter와 setter를 상속받지 않는다. 이제 <code>__proto__</code>는 평범함 데이터 프로퍼티처럼 처리되므로 버그 없이 예시가 잘 동작하게 된다.</p>
<p>이렇게 프로토타입이 없는 빈 객체는 아주 단순한 혹은 순수 사전식 객체라고 부른다. 이런 객체는 <code>toString</code> 같은 내장 메서드가 없다는 단점이 있다.</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/object-properties">https://ko.javascript.info/object-properties</a>
<a href="https://ko.javascript.info/prototypes">https://ko.javascript.info/prototypes</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 1. 15 자바스크립트) 함수 심화학습]]></title>
            <link>https://velog.io/@dev_diver/22.-1.-13-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94%ED%95%99%EC%8A%B5</link>
            <guid>https://velog.io/@dev_diver/22.-1.-13-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94%ED%95%99%EC%8A%B5</guid>
            <pubDate>Sun, 15 Jan 2023 12:52:18 GMT</pubDate>
            <description><![CDATA[<h1 id="재귀recursion">재귀(recursion)</h1>
<p>문제 해결을 하다 보면 함수에서 다른 함수를 호출해야할 때가 있다. 이 때 함수가 함수 자기 자신을 호출할 수도 있는데, 이를 재귀라고 한다.</p>
<p>재귀는 큰 목표 작업 하나를 동일하면서 간단한 작업 여러 개로 나눌 수 있을 때 유용한 프로그래밍 패턴이다.</p>
<p>목표 작업을 간단한 동작 하나와 목표 작업을 변형한 작업으로 단순화시킬 수 있을 때도 재귀를 사용할 수 있다.</p>
<p>재귀의 예시를 살펴보자.</p>
<pre><code class="language-javascript">function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n-1);
  }
}

pow(2, 3);    // 8</code></pre>
<p><code>n == 1</code>일 때 : 모든 절차가 간단해진다. 명확한 결과값을 즉시 도출하므로, 이를 재귀의 베이스(base)라 한다.</p>
<p><code>n != 1</code> 일 때 : <code>pow(x, n)</code>은 <code>x * pow(x, n - 1)</code>로 표현할 수 있다. 이를 재귀 단계(recursive step)이라 부른다. 목표 작업 <code>pow(x, n)</code>을 간단한 동작(<code>x를 곱하기</code>)과 목표 작업을 변형한 작업(<code>pow(x, n - 1)</code>)으로 분할 하였다.</p>
<p>이렇게 재귀를 이용하면 함수 호출의 결과가 명확해질 때까지 함수 호출을 더 간단한 함수 호출로 계속 줄일 수 있다.</p>
<p>가장 처음하는 호출을 포함한 중첩 호출의 최대 개수는 재귀 깊이(recursion depth)라고 한다. 위 예시에서 <code>pow(x, n)</code>의 재귀 깊이는 <code>n</code>이다.</p>
<p>자바스크립트 엔진은 최대 재귀 깊이를 제한한다. 만개 정도까지는 확실히 허용하고, 엔진에 따라 이보다 더 많은 깊이를 허용하는 경우도 있다. 하지만 대다수의 엔진이 십만까지는 다루지 못한다.</p>
<p>이런 제한을 완화하려고 엔진 내부에서 자동으로 <code>tail calls optimization</code>이라는 최적화를 수행하긴 하지만, 모든 곳에 적용되는 것은 아니고 간단한 경우에만 적용된다.</p>
<p>재귀 깊이 제한때문에 재귀를 실제 적용하는데에는 제약이 있긴 하지만, 재귀로 간결하고 유지보수가 쉬운 코드를 만들 수 있기 때문에 여전히 광범위하게 사용되고 있다.</p>
<h3 id="실제-재귀-호출시-동작-방식---실행-컨텍스트와-스택">실제 재귀 호출시 동작 방식 - 실행 컨텍스트와 스택</h3>
<p>실제 재귀 호출이 어떻게 동작하는지 알아보자. 이를 위해서는 함수의 내부 동작에 대해 살펴보아야 한다.</p>
<p>실행 중인 함수의 실행 절차에 대한 정보는 해당 함수의 실행 컨텍스트(execution context)에 저장된다.</p>
<p>실행 컨텍스트는 함수 실행에 대한 세부 정보를 담고 있는 내부 데이터 구조이다. 제어 흐름의 현재 위치, 변수의 현재 값, <code>this</code>의 값 등 상세 내부 정보가 실행 컨텍스트에 저장된다.</p>
<p>함수 호출 1 회당 하나의 실행 컨텍스트가 생성된다.</p>
<p>함수 내부에 중첩 호출이 있을 때는 아래와 같은 절차가 수행된다.</p>
<blockquote>
</blockquote>
<ul>
<li>현재 함수의 실행이 일시 중지된다.</li>
<li>중지된 함수와 연관된 실행 컨텍스트는 실행 컨텍스트 스택에 저장된다.</li>
<li>중첩 호출이 실행된다.</li>
<li>중첩 호출 실행이 끝난 이후 실행 컨텍스트 스택에서 일시 중단한 함수의 실행 컨텍스트를 꺼내오고, 중단한 함수의 실행을 다시 이어간다.</li>
</ul>
<p>재귀 깊이는 스택에 들어가는 실행 컨텍스트 수의 최댓값과 같다. 실행 컨텍스트는 메모리를 차지하므로 재귀를 사용할 때에는 메모리 요구사항에 유의해야 한다.</p>
<h3 id="재귀적-순회recursive-traversal">재귀적 순회(recursive traversal)</h3>
<p>한 회사 내에 모든 임직원의 임금의 합을 구해보자.</p>
<p>두 가지 경우로 나누어 생각할 수 있다.</p>
<blockquote>
</blockquote>
<ol>
<li>베이스 : 임직원 배열을 가진 단순한 부서. 간단한 반복문으로 급여 합계를 구할 수 있다.</li>
<li>재귀 단계 : N개의 하위 부서가 있는 객체. 각 하위 부서에 속한 임직원의 급여 합계를 얻기 위해서 N번의 재귀 호출을 하고, 최종적으로 모든 하위부서 임직원의 급여를 더한다.</li>
</ol>
<pre><code class="language-javascript">let company = { 
  sales: [{name: &#39;John&#39;, salary: 1000}, {name: &#39;Alice&#39;, salary: 1600 }],
  development: {
    sites: [{name: &#39;Peter&#39;, salary: 2000}, {name: &#39;Alex&#39;, salary: 1800 }],
    internals: [{name: &#39;Jack&#39;, salary: 1300}]
  }
};

// 급여 합계를 구해주는 함수
function sumSalaries(department) {
  if (Array.isArray(department)) { // 첫 번째 경우
    return department.reduce((prev, current) =&gt; prev + current.salary, 0); // 배열의 요소를 합함
  } else { // 두 번째 경우
    let sum = 0;
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // 재귀 호출로 각 하위 부서 임직원의 급여 총합을 구함
    }
    return sum;
  }
}

alert(sumSalaries(company)); // 7700</code></pre>
<h3 id="재귀적-자료-구조">재귀적 자료 구조</h3>
<p>재귀적 자료 구조는 자기 자신의 일부를 복제하는 형태의 자료 구조이다.</p>
<p>위에서 살펴본 회사 구조가 재귀적 자료 구조의 한 예이다. 회사는 부서를 가지는 객체로 이루어져 있고, 각 부서도 하위 부서를 가지는 객체 구조이다.</p>
<h4 id="연결-리스트linked-list">연결 리스트(linked list)</h4>
<p>연결리스트도 재귀적 자료 구조 중 하나이다.</p>
<p>객체를 정렬하여 어딘가에 저장하고 싶다면 가장 먼저 떠오르는 구조가 배열일 것이다. 하지만 배열은 요소 삭제와 삽입에 들어가는 비용이 많이 든다. 앞 쪽 요소에 무언가를 할 때 <code>shift</code>, <code>unshift</code>를 사용하면 모든 요소의 번호를 다시 매겨야 하므로 배열은 꽤 느리다.</p>
<p>빠르게 삽입 혹은 삭제를 해야 할 때는 배열 대신 연결 리스트를 사용할 수 있다.</p>
<p>연결 리스트는 객체 안에 아래 프로퍼티들을 조합해서 정의할 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>value</code> : 연결 리스트 노드가 가지는 값 프로퍼티</li>
<li><code>next</code> : 다음 연결 리스트 노드를 참조하는 프로퍼티. 다음 요소가 없으면 <code>null</code>이 된다.</li>
</ul>
<pre><code class="language-javascript">let linkedList = {
  value: 1,
  next: {
    value: 2,
    next: {
      value: 3,
      next: {
        value: 4,
        next: null
      }
    }
  }
};</code></pre>
<p>연결 리스트의 <code>next</code> 프로퍼티를 이용해서 이어지는 객체 어디에든 도달할 수 있다.</p>
<pre><code class="language-javascript">linkedList.next;    // { value: 2, next: ... }
linkedList.next.next;    // { value 3, next: ... }</code></pre>
<p>전체 리스트를 여러 부분으로 쉽게 나눌 수도 있고, 다시 합치는 것도 가능하다.</p>
<pre><code class="language-javascript">// 나누기
let secondLinkedList = list.next.next;
list.next.next = null;

// 합치기
list.next.next = secondLinkedList;</code></pre>
<p>쉽게 요소를 추가하거나 삭제할 수도 있다.</p>
<pre><code class="language-javascript">let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };

// 연결 리스트 맨 앞에 노드 추가
list = { value: &quot;new item&quot;, next: list };

// 중간 요소 제거. 이전 요소의 next를 변경 해준다.
list.next = list.next.next;</code></pre>
<p>이처럼 연결 리스트는 배열과 달리 대량으로 요소 번호를 재할당하지 않고도 요소를 쉽게 재배열할 수 있다는 장점이 있다.</p>
<p>다만, 연결 리스트의 가장 큰 단점은 인덱스만 사용해서 요소에 쉽게 접근할 수 없다는 점이다. N번 째 값을 얻기 위해서 배열처럼 <code>arr[N]</code>로 원하는 요소에 접근할 수 없고, 첫 번째 항목부터 시작해서 N번 <code>next</code>로 이동해야 한다.</p>
<hr>
<h1 id="나머지-매개변수와-스프레드-문법에-사용되는-">나머지 매개변수와 스프레드 문법에 사용되는 ...</h1>
<p><code>...</code>은 나머지 매개변수와 스프레드 문법에 사용될 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>...</code>이 함수 매개변수의 끝에 있으면 인수 목록의 나머지를 배열로 모아주는 나머지 매개변수이다.</li>
<li><code>...</code>이 함수 호출 시 사용되거나 기타 경우에는 배열을 목록으로 확장해주는 스프레드 문법이다.</li>
</ul>
<h3 id="나머지-매개변수-">나머지 매개변수 ...</h3>
<p>상당수의 자바스크립트 내장 함수는 인수의 개수에 제약을 두지 않습니다. 또, 함수 정의 방법과 상관없이 함수에 넘겨주는 인수의 개수에는 제약이 없다.</p>
<p>함수에서 임의의 개수의 인수를 받는 방법과 매개변수에 배열을 전달하는 방법에 대해 알아보자.</p>
<pre><code class="language-javascript">function sum(a, b) {
  return a + b;
}

alert( sum(1, 2, 3, 4, 5) );</code></pre>
<p>함수를 정의할 때는 인수를 두 개만 받고, 실제 함수를 호출할 때는 이보다 많은 여분의 인수를 전달했지만, 에러가 발생하지 않았다. 반환값은 처음 두 개의 인수만을 사용해 계산된다.</p>
<p>값들을 담은 배열 이름 앞에 마침표 세 개 <code>...</code>를 붙여서 함수 선언부에 매개변수로 포함시킬 수 있다. 아래 <code>...args</code>는 매개변수들을 한데 모은 배열이다.</p>
<pre><code class="language-javascript">function sumAll(...args) {
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

sumAll(1)</code></pre>
<p>앞 부분의 매개변수는 변수로, 남아있는 매개변수들은 배열에 모을 수도 있다.</p>
<pre><code class="language-javascript">function showName(firstName, lastName, ...titles) {
  alert( firstName + &#39; &#39; + lastName );
  alert( titles[0] );
  alert( titles[1] );
  alert( titles.length );
}

showName(&quot;Bora&quot;, &quot;Lee&quot;, &quot;Software Engineer&quot;, &quot;Researcher&quot;);</code></pre>
<p>나머지 매개변수 <code>...</code>는 남아있는 인수를 모으는 역할을 하므로 항상 마지막에 있어야 한다. </p>
<h3 id="유사-배열-객체-arguments">유사 배열 객체 arguments</h3>
<p>유사 배열 객체 <code>arguments</code>에 인덱스를 사용해서 인수에 접근할 수 있다.</p>
<pre><code class="language-javascript">function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );
}

// 2, Bora, Lee가 출력됨
showName(&quot;Bora&quot;, &quot;Lee&quot;);

// 1, Bora, undefined가 출력됨(두 번째 인수는 없음)
showName(&quot;Bora&quot;);</code></pre>
<p><code>arguments</code>는 이터러블이지만 배열은 아니다. 따라서, 배열 메서드를 사용할 수 없는 단점이 있다. 또, 인수 전체를 담기 때문에 나머지 매개변수처럼 인수의 일부만 사용할 수 없다.</p>
<h3 id="스프레드-문법spread-syntax-">스프레드 문법(spread syntax) ...</h3>
<p>지금까지는 매개변수 목록을 배열로 가져오는 방법에 대해 살펴보았다.</p>
<p>그런데 개발을 하다 보면 배열을 통째로 매개변수에 넘겨주는 것처럼 반대되는 기능이 필요할 때가 생긴다.</p>
<p>스프레드 문법은 이럴 때 사용하기 위해서 만들어졌다. <code>...</code>를 사용하지만, 나머지 매개변수와는 반대되는 역할을 한다.</p>
<pre><code class="language-javascript">let arr = [3, 5, 1];

alert( Math.max(...arr) );    // 5

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) );    // 8
alert( Math.max(1, ...arr1, 2, ...arr2, 25) );    // 25</code></pre>
<p>스프레드 문법은 배열을 합칠 때도 활용할 수 있다.</p>
<pre><code class="language-javascript">let arr1 = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [0, ...arr1, 2, ...arr2];

alert(merged);    // [0, 3, 5, 1, 2, 8, 9, 15]</code></pre>
<p>배열이 아니더라도 이터러블이면 스프레드 문법을 사용할 수 있다. 스프레드 문법을 사용해서 문자열을 문자 배열로 변환시켜 보자.</p>
<pre><code class="language-javascript">let str = &quot;Hello&quot;;

alert( [...str] );    // [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;]</code></pre>
<p>스프레드 문법은 <code>for of</code>와 같은 방식으로 내부에서 <code>iterator</code>를 사용해서 요소를 수집한다.</p>
<p>참고로, <code>Array.from()</code>은 이터러블을 배열로 바꿔줄 수 있다.</p>
<pre><code class="language-javascript">let str = &quot;Hello&quot;;

alert( Array.from(str) );    // [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;]</code></pre>
<p>스프레드 문법과 동일한 결과가 출력되는 것을 확인할 수 있다. 그런데 <code>Array.from(obj)</code>와 <code>[...obj]</code>는 다음과 같은 미묘한 차이가 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Array.from()</code>은 유사 배열 객체와 이터러블 둘 다 사용할 수 있다.</li>
<li>스프레드 문법은 이터러블에만 사용할 수 있다.</li>
</ul>
<p>이런 이유 때문에 무언가를 배열로 바꿀 때에는 스프레드 문법보다 <code>Array.from()</code>이 보편적으로 사용된다.</p>
<h3 id="배열과-객체의-복사본-만들기">배열과 객체의 복사본 만들기</h3>
<p><code>Object.assign()</code> 말고도 스프레드 문법을 사용해서 배열과 객체를 복사할 수 있다.</p>
<pre><code class="language-javascript">// 배열 복사
let arr = [1, 2, 3];
let arrCopy = [...arr];

JSON.stringify(arr) === JSON.stringify(arrCopy);    // true

arr === arrCopy;    // false (참조가 다름)

// 객체 복사
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; 

JSON.stringify(obj) === JSON.stringify(objCopy); // true

alert(obj === objCopy); // false (참조가 다름)</code></pre>
<hr>
<h1 id="변수의-유효범위scope와-클로저">변수의 유효범위(scope)와 클로저</h1>
<p>자바스크립트는 함수형 언어이다. 함수를 동적으로 생성할 수 있고, 생성한 함수를 다른 함수에 인수로 넘길 수 있으며, 생성된 곳이 아닌 곳에서 함수를 호출할 수도 있다.</p>
<p>함수 내부에서 함수 외부에 있는 변수를 접근할 수 있다. 그런데, 함수가 생성된 이후에 외부 변수가 변경되면 어떤 일이 발생할까? 함수는 새로운 값을 가져올까 아니면 생성 시점 이전의 값을 가져올까? </p>
<p>또, 매개변수를 통해 함수를 넘기고 이 함수를 멀리 떨어진 코드에서 호출할 때는 어떤 일이 발생할까? 함수는 호출되는 곳을 기준으로 외부 변수에 접근할까?</p>
<h3 id="코드-블록-">코드 블록 {...}</h3>
<p>코드 블록 안에서 선언한 변수는 블록 안에서만 사용할 수 있다.</p>
<pre><code class="language-javascript">{
  let message = &quot;Hello&quot;;

  alert(message);    // Hello
}

alert(message);    // ReferenceError: message is not defined</code></pre>
<p>이런 특징은 특정 작업을 수행하는 코드를 한데 묶어두는 용도로 활용할 수 있다.</p>
<pre><code class="language-javascript">{
  // 메시지 출력
  let message = &quot;안녕하세요.&quot;;
  alert(message);
}

{
  // 또 다른 메시지 출력
  let message = &quot;안녕히 가세요.&quot;;
  alert(message);
}</code></pre>
<p><code>if</code>, <code>for</code>, <code>while</code> 등에서도 마찬가지로 코드 블록 안에서 선언한 변수는 오직 블록 안에서만 접근 가능하다.</p>
<pre><code class="language-javascript">if (true) {
  let phrase = &quot;Hello&quot;

  alert(phrase);    // Hello
}

alert(phrase);    // ReferenceError: phrase is not defined</code></pre>
<h3 id="중첩-함수">중첩 함수</h3>
<p>함수 내부에서 선언한 함수를 중첩 함수라고 부른다. 중첩 함수는 아래와 같이 코드를 정돈하는데에 사용할 수 있다.</p>
<pre><code class="language-javascript">function sayHiBye(firstName, lastName) {
  function getFullName() {
    return firstName + &quot; &quot; + lastName;
  }

  alert( &quot;Hello, &quot; + getFullName() );
  alert( &quot;Bye, &quot; + getFullName() );
}</code></pre>
<p>중첩 함수는 새로운 객체의 프로퍼티 형태나 중첩 함수 그 자체로 반환될 수 있다. 이렇게 반환된 중첩 함수는 어디서든 호출해 사용할 수 있다.</p>
<pre><code class="language-javascript">function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

alert( counter() );    // 0
alert( counter() );    // 1</code></pre>
<p>좀 더 복잡한 내용을 살펴보자. 위 예시에서 <code>counter</code>를 여러 개 만들었을 때, 이 함수들은 서로 독립적일까? 함수와 중첩 함수 내 <code>count</code> 변수에는 어떤 값이 할당될까?</p>
<h3 id="렉시컬-환경lexical-environment">렉시컬 환경(lexical environment)</h3>
<p>명확한 이해를 돕기 위해 설명을 몇 개의 단계로 나눠서 진행해보자.</p>
<h4 id="단계-1-변수">단계 1. 변수</h4>
<p>자바스크립트에서 <code>실행 중인 함수</code>, <code>코드 블록</code>, <code>스크립트 전체</code>는 렉시컬 환경이라 불리는 <code>내부 숨김 연관 객체(internal hidden associcated object)</code>를 갖는다.</p>
<p>렉시컬 환경 객체는 두 부분으로 구성된다.</p>
<blockquote>
</blockquote>
<ol>
<li><code>환경 레코드(environment record)</code> : 모든 지역 변수를 프로퍼티로 저장하고 있는 객체이다. <code>this</code> 값과 같은 기타 정보도 여기에 저장된다.</li>
<li><code>외부 렉시컬 환경(outer lexical environment)</code>에 대한 참조 : 외부 코드와 연관된다.</li>
</ol>
<p>변수는 특수 내부 객체인 <code>환경 레코드</code>의 프로퍼티일 뿐이다. 다시 말해, 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경함을 의미한다.</p>
<p>아래 두 줄짜리 코드에는 렉시컬 환경이 하나만 존재한다.
<img src="https://velog.velcdn.com/images/dev_diver/post/453a9294-1249-4573-a0a8-f401d72a06e9/image.png" alt=""></p>
<p>이렇게 스크립트 전체와 관련된 렉시컬 환경은 <code>전역 렉시컬 환경(global lexical environment)</code>이라 한다.</p>
<p>위 그림에서 <code>네모 상자</code>는 변수가 저장되는 <code>환경 레코드</code>이고, <code>phrase</code>는 <code>환경 레코드의 프로퍼티(변수)</code>를 나타낸다. <code>화살표</code>는 <code>외부 렉시컬 환경에 대한 참조</code>를 나타낸다. 전역 렉시컬 환경이 외부 참조를 갖지 않기 때문에 화살표가 <code>null</code>을 가리킨다.</p>
<p>코드가 실행되고 실행 흐름이 이어져 나가면서 렉시컬 환경은 변화한다.
<img src="https://velog.velcdn.com/images/dev_diver/post/e3657f14-95f4-435f-bc3c-48ef436b5e35/image.png" alt=""></p>
<p>우측의 네모 상자들은 코드가 한 줄씩 실행될 때마다 전역 렉시컬 환경이 어떻게 변화하는지 보여준다.</p>
<blockquote>
</blockquote>
<ol>
<li>스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다.<ul>
<li>이 때 변수의 상태는 특수 내부 상태인 <code>uninitialized</code>가 된다.</li>
<li>자바스크립트 엔진은 <code>uninitialized</code> 상태의 변수를 인지하긴 하지만, <code>let</code>을 만나기 전까지는 이 변수를 참조할 수 없다.</li>
</ul>
</li>
<li><code>let phrase</code>가 나타났다. 아직 값을 할당하기 전이기 때문에 프로퍼티(변수) 값은 <code>undefined</code>이다. <code>phrase</code>는 이 시점 이후부터 사용할 수 있다.</li>
<li>환경 레코드의 프로퍼티 <code>phrase</code>에 값이 할당되었다.</li>
<li>환경 레코드의 프로퍼티 <code>phrase</code>의 값이 변경되었다.</li>
</ol>
<p>렉시컬 환경은 명세서에서 자바스크립트가 어떻게 동작하는지 설명하는 데 쓰이는 이론상의 객체이다. 따라서 코드를 사용해서 직접 렉시컬 환경을 얻거나 조작하는 것은 불가능하다. 자바스크립트 엔진들은 명세서에 언급된 사항을 준수하면서, 사용하지 않는 변수를 버려 메모리를 절약하거나 등의 엔진 고유의 방법을 사용해서 렉시컬 환경을 최적화한다. </p>
<h4 id="단계-2-함수-선언문">단계 2. 함수 선언문</h4>
<p>함수는 변수와 마찬가지로 값이다.</p>
<p>다만 함수 선언문으로 선언한 함수는 일반 변수와는 달리 바로 초기화한다는 점이 다르다.</p>
<p>함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다. 선언되기 전에도 함수를 사용할 수 있는 것은 바로 이 이유 때문이다.</p>
<p>아래 그림은 스크립트에 함수를 추가했을 때, 전역 렉시컬 환경 초기 상태가 어떻게 변하는지 보여준다.
<img src="https://velog.velcdn.com/images/dev_diver/post/b6e11d37-6d31-4bce-ad77-0cb4631a2cb9/image.png" alt=""></p>
<p>이런 동작 방식은 함수 선언문으로 정의한 함수에만 적용된다. 함수를 변수에 할당한 함수 표현식은 해당하지 않는다.</p>
<h4 id="단계-3-내부와-외부-렉시컬-환경">단계 3. 내부와 외부 렉시컬 환경</h4>
<p>함수를 호출해서 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다. 이 렉시컬 환경에는 함수 호출 시에 넘겨받은 매개변수와 함수의 지역 변수가 저장된다.</p>
<p>아래 그림에서 <code>say(&quot;John&quot;)</code>을 호출하면 아래와 같은 내부 변화가 일어난다.
<img src="https://velog.velcdn.com/images/dev_diver/post/1837eb2c-35ec-4ec5-a6ed-659f48891aec/image.png" alt=""></p>
<p>함수가 호출 중인 동안에는 호출 중인 함수를 위한 내부 렉시컬 환경과 내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 가지게 된다.</p>
<blockquote>
</blockquote>
<ul>
<li>예시의 내부 렉시컬 환경은 현재 실행 중인 함수인 <code>say</code>에 상응한다. 내부 렉시컬 환경에는 함수의 매개변수인 <code>name</code>으로부터 유래한 프로퍼티 하나만 있다. <code>say(&quot;John&quot;)</code>을 호출했기 때문에, <code>name</code>의 값은 <code>&quot;John&quot;</code>이 된다.</li>
<li>예시의 외부 렉시컬 환경은 전역 렉시컬 환경이다. 전역 렉시컬 환경은 변수 <code>phrase</code>와 함수 <code>say</code>를 프로퍼티로 갖는다.</li>
</ul>
<p>그리고 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다.</p>
<p>코드에서 변수에 접근할 때는, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장한다. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복된다.</p>
<p>전역 렉시컬 환경에 도달할 때까지 변수를 찾지 못하면 strict mode에서는 에러가 발생한다.</p>
<p>아래 그림에서 변수 검색이 어떻게 진행되는지 확인해보자.
<img src="https://velog.velcdn.com/images/dev_diver/post/08df2dcd-1774-438c-abf7-6e29162c8997/image.png" alt=""></p>
<blockquote>
</blockquote>
<ul>
<li>함수 <code>say</code> 내부의 <code>alert</code>에서 변수 <code>name</code>에 접근할 때는 먼저 내부 렉시컬 환경을 살펴본다. 내부 렉시컬 환경에서 변수 <code>name</code>을 찾았다.</li>
<li><code>alert</code>에서 변수 <code>phrase</code>에 접근하려는데, <code>phrase</code>에 상응하는 프로퍼티가 내부 렉시컬 환경에 없다. 따라서 검색 범위는 외부 렉시컬 환경으로 확장된다. 외부 렉시컬 환경에서 <code>phrase</code>를 찾았다.</li>
</ul>
<h4 id="단계-4-함수를-반환하는-함수">단계 4. 함수를 반환하는 함수</h4>
<pre><code class="language-javascript">function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();</code></pre>
<p><code>makeCounter()</code>를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어지고, 여기에 <code>makeCounter</code>를 실행하는데 필요한 변수들이 저장된다.
<img src="https://velog.velcdn.com/images/dev_diver/post/26415070-9f35-42b8-8f79-3f892b5e1878/image.png" alt=""></p>
<p><code>makeCounter()</code>가 실행되는 도중에 본문(<code>return count++</code>)이 한 줄 짜리인 중첩 함수가 만들어진다. 현재는 중첩 함수가 생성되기만 하고 실행은 되지 않은 상태이다.</p>
<p>모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다. 함수는 <code>[[Environment]]</code>라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된다.
<img src="https://velog.velcdn.com/images/dev_diver/post/36fc6618-9a65-4b8f-a4ab-d851919058eb/image.png" alt=""></p>
<p>따라서 <code>counter.[[Environment]]</code>에는 <code>{count: 0}</code>이 있는 렉시컬 환경에 대한 참조가 저장된다. 호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 것은 바로 <code>[[Environment]]</code> 프로퍼티 덕분이다.</p>
<p><code>counter()</code>를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성된다. 그리고 이 렉시컬 환경은 <code>counter.[[Environment]]</code>에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조한다.
<img src="https://velog.velcdn.com/images/dev_diver/post/985e2592-e182-4ff9-a4a4-accc552d3b5b/image.png" alt=""></p>
<p>실행 흐름이 중첩 함수의 본문으로 넘어오면 <code>count</code> 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾는다. 익명 중첩 함수에는 지역변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황이다(<code>&lt;empty&gt;</code>). 이제 <code>counter()</code>의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 <code>count</code>를 찾아보자. <code>count</code>를 찾았다.</p>
<p>이제 <code>count++</code>가 실행되면서 count 값이 1 증가해야하는데, 변수값 갱신은 변수가 저장된 렉시컬 환경에서 이루어진다.</p>
<p>따라서 실행이 종료된 후의 상태는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev_diver/post/b8a67ff4-d28a-4bc4-9709-b1bdca0389ae/image.png" alt=""></p>
<p><code>counter()</code>를 여러 번 호출하면 <code>count</code> 변수가 증가하는 이유가 바로 여기에 있다.</p>
<h3 id="클로저closure">클로저(closure)</h3>
<p>외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다.</p>
<p>자바스크립트에서는 모든 함수가 자연스럽게 클로저가 된다.</p>
<p>자바스크립트의 함수는 숨김 프로퍼티인 <code>[[Environment]]</code>를 이용해서 자신이 어디서 만들어졌는지를 기억한다. 함수 본문에서는 <code>[[Environment]]</code> 프로퍼티를 사용해서 외부 변수에 접근한다.</p>
<p>아래는 클로저를 활용한 <code>sum</code> 함수 구현 예시이다.</p>
<pre><code class="language-javascript">function sum(a) {
  return function(b) {
    return a + b;
  };
}

sum(1)(2);    // 3
sum(5)(-1);    // 4</code></pre>
<p>프론트엔드 개발자 채용 인터뷰에서 <code>&quot;클로저가 무엇입니까?&quot;</code>라는 질문을 받으면, 클로저의 정의를 말하고 자바스크립트에서 왜 모든 함수가 클로저인지에 관해 설명하면 된다. 이 때, <code>[[Environment]]</code> 프로퍼티와 렉시컬 환경이 어떤 방식으로 동작하는지에 대한 설명을 덧붙이면 좋다.</p>
<h3 id="가비지-컬렉션">가비지 컬렉션</h3>
<p>함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거된다. 함수와 관련된 변수들은 이 때 모두 사라진다.</p>
<p>함수 호출이 끝나면 관련 변수를 참조할 수 없는 이유가 바로 여기에 있다. 자바스크립트에서 모든 객체는 도달 가능한 상태일 때만 메모리에 유지된다.</p>
<p>그런데 호출이 끝난 후에도 여전히 도달 가능한 중첩 함수가 있을 수 있다. 이때는 이 중첩함수의 <code>[[Environment]]</code> 프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장된다. 다시 말해, 도달 가능한 상태가 되는 것이다.</p>
<p>함수 호출은 끝났지만 렉시컬 환경이 메모리에 유지되는 이유는 바로 이 때문이다.</p>
<pre><code class="language-javascript">function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f();    // g.[[Environment]]에
                // f() 호출 시 만들어지는 렉시컬 환경 정보가 저장된다.</code></pre>
<p>그런데 이렇게 중첩 함수를 사용할 때는 주의할 점이 있다. <code>f()</code>를 여러 번 호출하고 그 결과를 어딘가에 저장하는 경우, 호출 시에 만들어지는 각 렉시컬 환경 모두가 메모리에 유지된다는 점이다. 각 렉시컬 환경은 메모리에서 삭제되지 않는다.</p>
<pre><code class="language-javascript">function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 배열 안의 세 함수는 각각 f()를 호출할 때 생성된
// 렉시컬 환경과 연관 관계를 맺는다.
let arr = [f(), f(), f()];</code></pre>
<p>렉시컬 환경 객체는 다른 객체와 마찬가지로 도달할 수 없을 때, 메모리에서 삭제된다. 해당 렉시컬 환경 객체를 참조하는 중첩 함수가 하나라도 있으면 사라지지 않는다.</p>
<p>아래 예시 같이 중첩 함수가 메모리에서 삭제되고 난 후에야, 이를 감싸는 렉시컬 환경도 메모리에서 제거된다.</p>
<pre><code class="language-javascript">function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g가 살아있는 동안엔 연관 렉시컬 환경도 메모리에 살아있다.

g = null; // 도달할 수 없는 상태가 되었으므로 메모리에서 삭제된다.</code></pre>
<hr>
<h1 id="오래된-var">오래된 var</h1>
<p><code>var</code>로 선언한 변수는 <code>let</code>으로 선언한 변수와 유사하다. 하지만 초기 자바스크립트 구현 방식 때문에 <code>var</code>는 <code>let</code>과 <code>const</code>로 선언한 변수와는 다른 방식으로 동작한다.</p>
<h3 id="var는-블록-스코프가-없다">var는 블록 스코프가 없다</h3>
<p><code>var</code>로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프이다. 블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근이 가능하다.</p>
<pre><code class="language-javascript">if (true) {
  var test = true;
}

alert(test);    // true. if 문이 끝나도 변수에 접근할 수 있음</code></pre>
<p>위 예시를 살펴보면 <code>var</code>는 코드 블록을 무시하기 때문에 <code>test</code>는 전역 변수가 된다. 다시 말해, 전역 스코프에서 이 변수에 접근할 수 있다.</p>
<p>또 다른 예시를 살펴보자. <code>var</code>는 블록이나 루프 수즌의 스코프를 형성하지 않기 때문에 여전히 반복문 밖인 전역 스코프에서도 변수에 접근할 수 있다.</p>
<pre><code class="language-javascript">for (var i = 0; i &lt; 10; i++) {
  // ...
}

alert(i);    // 10</code></pre>
<p>코드 블록이 함수 안에 있다면, <code>var</code>는 함수 레벨 변수가 된다.</p>
<pre><code class="language-javascript">function sayHi() {
  if (true) {
      var phrase = &quot;Hello&quot;;
  }

  alert(phrase);    // Hello
}

sayHi();
alrt(phrase);    // Error</code></pre>
<h3 id="var는-변수의-중복-선언을-허용한다">var는 변수의 중복 선언을 허용한다</h3>
<p>한 스코프에서 같은 변수를 <code>let</code>으로 중복 선언하면 에러가 발생한다.</p>
<p>반면에 <code>var</code>는 같은 변수를 여러 번 중복 선언할 수 있다.</p>
<pre><code class="language-javascript">let user;
let user;    // Error

var user = &quot;Pete&quot;;
var user = &quot;John&quot;;

alert(user);    // John</code></pre>
<h3 id="선언하기-전에-사용할-수-있는-var">선언하기 전에 사용할 수 있는 var</h3>
<p><code>var</code> 선언은 전역에서 선언한 변수라면 스크립트가 시작될 때 처리되고, 함수 내에서 선언한 변수라면 함수가 시작될 때 처리된다. </p>
<p>함수 본문 내에서 <code>var</code>로 선언한 변수는 선언 위치와 상관없이 함수 본문이 시작되는 지점에서 정의된다. 따라서 아래 두 예제는 동일하게 동작한다.</p>
<pre><code class="language-javascript">function sayHi() {
  phrase = &quot;Hello&quot;;

  alert(phrase);

  var phrase;
}

sayHi();    // Hello</code></pre>
<pre><code class="language-javascript">function sayHi() {
  var phrase;

  phrase = &quot;Hello&quot;;

  alert(phrase);
}

sayHi();    // Hello</code></pre>
<p>또, 코드 블록은 무시되기 때문에 아래 코드 역시 동일하게 동작한다.</p>
<pre><code class="language-javascript">function sayHi() {
  phrase = &quot;Hello&quot;;

  if (false) {
    var phrase;
  }

  alert(phrase);
}

sayHi();    // Hello</code></pre>
<p>이렇게 변수가 끌어올려 지는 현상을 호이스팅이라 한다. 주의해야할 점은 변수 선언은 호이스팅 되지만 할당은 호이스팅 되지 않는다.</p>
<pre><code class="language-javascript">function sayHi() {
  alert(phrase);

  var phrase = &quot;Hello&quot;;
}

sayHi();</code></pre>
<p>위 예시는 실제로 다음과 같이 동작한다. 변수 선언은 함수 실행이 시작될 때 호이스팅되지만, 할당은 호이스팅 되지 않기 때문에 예상한 결과와 다르게 동작한다.</p>
<pre><code class="language-javascript">function sayHi() {
  var phrase;

  alert(phrase);

  phrase = &quot;Hello&quot;;
}

sayHi();    // undefined</code></pre>
<p>이처럼 모든 <code>var</code> 선언은 함수 시작 시 처리되기 때문에, <code>var</code>로 선언한 변수는 어디서든 참조할 수 있다. 하지만 변수에 무언가를 할당하기 전까지는 값이 <code>undefined</code>이다.</p>
<h3 id="var도-블록-레벨-스코프를-가질-수-있다">var도 블록 레벨 스코프를 가질 수 있다</h3>
<p>과거에는 변수 선언에 <code>var</code>만 사용할 수 있었다. 하지만 <code>var</code>는 블록 레벨 스코프를 가질 수 없었기 때문에 개발자들은 <code>var</code>도 블록 레벨 스코프를 가질 수 있는 방안을 고려하게 된다. 이 때 만들어진 것이 <code>즉시 실행 함수 표현식(IIFE)</code>이다.</p>
<p><code>IIFE</code>는 다음과 같이 사용한다. 함수 선언식을 소괄호로 감싸고, 바로 호출하여 실행한다. 함수를 괄호로 감싸면 자바스크립트가 함수를 함수 선언문이 아닌 표현식으로 인식하도록 속일 수 있다. 함수 표현식은 이름이 없어도 괜찮고, 즉시 호출도 가능하다.</p>
<pre><code class="language-javascript">(function() {
  var message = &quot;Hello&quot;;

  alert(message);
})();    // Hello</code></pre>
<p>위 예시에서 <code>var</code>로 선언된 변수는 블록 레벨 스코프를 가지게 되므로 블록 외부에서 접근할 수 없다.</p>
<hr>
<h1 id="전역-객체">전역 객체</h1>
<p>전역 객체를 사용하면 어디서나 사용 가능한 변수나 함수를 만들 수 있다. 전역 객체는 언어 자체나 호스트 환경에 기본 내장되어 있는 경우가 많다. 예를 들어 브라우저 환경에서는 전역 객체를 <code>window</code>, Node 환경에서는 <code>global</code>이라고 부른다.</p>
<p>전역 객체에는 <code>Array</code>와 같은 내장 객체나 <code>window.innerHeight(뷰포트의 높이를 반환)</code> 같은 브라우저 환경 전용 변수 등이 저장되어 있다.</p>
<p>전역 객체의 모든 프로퍼티는 아래와 같이 직접 접근할 수 있다.</p>
<pre><code class="language-javascript">alert(&quot;Hello&quot;);

window.alert(&quot;Hello&quot;);    // 위와 동일하게 동작한다</code></pre>
<p>브라우저에서 <code>let</code>이나 <code>const</code>가 아닌 <code>var</code>로 선언한 전역 함수나 전역 변수는 전역 객체의 프로퍼티가 된다.</p>
<pre><code class="language-javascript">var gVar = 5;

alert(window.gVar);    // 5

let gLet = 5;

alert(window.gLet);    // undefined</code></pre>
<p>하위 호환성 때문에 위와 같은 방식으로 전역 객체를 사용해도 동작은 하지만, 모듈을 사용하는 모던 자바스크립트는 이러한 방식을 지원하지 않기 때문에 사용을 지양해야한다.</p>
<p>중요한 변수라서 모든 곳에서 사용할 수 있게 하려면, 아래와 같이 전역 객체에 직접 프로퍼티를 추가시킨다.</p>
<pre><code class="language-javascript">// 모든 스크립트에서 접근할 수 있게 프로퍼티 선언
window.currentUser = {
  name: &quot;Yong&quot;
};

alert(currentUser.name);    // Yong
alert(window.currentUser.name);    // Yong</code></pre>
<p>전역 변수는 되도록 사용하지 않는 것이 좋다. 함수를 만들때에도 외부 변수나 전역 변수를 사용하는 것보다 입력으로 변수를 받고 이를 이용해서 출력을 하는 방식으로 만들어야 테스트도 쉽고, 에러도 덜 발생한다. 따라서 프로젝트 전체에서 꼭 필요한 변수만 전역 객체에 저장하고, 전역 변수는 가능한 한 최소한으로 사용한다.</p>
<hr>
<h1 id="객체로서의-함수와-기명-함수-표현식">객체로서의 함수와 기명 함수 표현식</h1>
<p>자바스크립트에서 함수는 값으로 취급되며 자료형은 객체이다. 다시 말해 함수를 호출이 가능한 active한 &quot;객체&quot;라고 이해하면 된다. 함수를 호출 할 수 있을 뿐만 아니라 객체처럼 함수에 프로퍼티를 추가하거나 제거, 참조를 통해 전달할 수도 있다.</p>
<h3 id="name-프로퍼티">name 프로퍼티</h3>
<p>함수 객체의 <code>name</code> 프로퍼티를 사용하면 함수 이름을 가져올 수 있다.</p>
<pre><code class="language-javascript">function sayHi() {
  alert(&quot;Hi&quot;);
}

alert(sayHi.name);    // sayHi

// 익명 함수에도 자동으로 이름이 할당된다
let sayHi = function() {
  alert(&quot;Hi&quot;);
};

alert(sayHi.name);    // sayHi</code></pre>
<h3 id="length-프로퍼티">length 프로퍼티</h3>
<p>함수 객체의 <code>length</code> 프로퍼티는 매개변수의 개수를 반환한다. 단, 나머지 매개변수는 개수에 포함되지 않는다.</p>
<pre><code class="language-javascript">function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length);    // 1
alert(f2.length);    // 2
alert(many.length);    // 2</code></pre>
<p><code>length</code> 프로퍼티를 활용하면 객체지향의 중요한 개념 중 하나인 다형성을 구현할 수 있다. 아래 예시를 살펴보자. 파라미터의 종류에 따라(아래 예시에서는 <code>length</code> 프로퍼티의 값) 함수를 처리하는 방식이 다양한 것을 바로 다형성이라 한다.</p>
<pre><code class="language-javascript">function ask(question, ...handlers) {
  let isYes = confirm(question);

  for (let handler of handlers) {
    if (handler.length == 0) {
      if (isYes) handler();
    } else {
      handler(isYes);
    }
  }
}

ask(&quot;질문 있으신가요?&quot;, () =&gt; alert(&#39;OK를 선택하셨습니다.&#39;), result =&gt; alert(result));</code></pre>
<h3 id="함수에-프로퍼티-추가하기">함수에 프로퍼티 추가하기</h3>
<p>함수에는 자체적으로 만든 프로퍼티를 추가할 수 있다.</p>
<pre><code class="language-javascript">function sayHi() {
  alert(&quot;Hi&quot;);

  sayHi.counter++;
}

sayHi.counter = 0; // 초기화

sayHi();    // Hi
sayHi();    // Hi

alert(sayHi.counter);    // 2</code></pre>
<h3 id="기명-함수-표현식">기명 함수 표현식</h3>
<p>기명 함수 표현식(Named Function Expression, NFE)은 이름이 있는 함수 표현식을 나타낸다.</p>
<pre><code class="language-javascript">// 일반적인 함수 표현식
let sayHi = function(who) {
  alert(`Hello, ${who}`);
};

// 기명 함수 표현식
let sayHi = function func(who) {
  alert(`Hello, ${who}`);
}</code></pre>
<p>기명 함수 표현식을 사용하면 두 가지 변화가 생긴다.</p>
<ul>
<li>이름을 사용해서 함수 표현식 내부에서 자기 자신을 참조할 수 있다.</li>
<li>기명 함수 표현식 외부에서는 그 이름을 사용할 수 없다.</li>
</ul>
<pre><code class="language-javascript">let sayHi = function func(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    func(&quot;Guest&quot;);
  }
};

sayHi();    // Hello, Guest
func();        // Error</code></pre>
<p>위 예시에서 왜 중첩 호출을 할 때 <code>sayHi</code> 대신 <code>func</code>를 사용할까? 그 이유는 다음과 같다. 대부분의 개발자는 아래와 같이 코드를 작성하곤 한다.</p>
<pre><code class="language-javascript">let sayHi = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    sayHi(&quot;Guest&quot;);
  }
};</code></pre>
<p>하지만 이렇게 코드를 작성하면 외부 코드에 의해 <code>sayHi</code>가 변경될 수 있다는 문제가 생긴다.</p>
<pre><code class="language-javascript">let sayHi = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    sayHi(&quot;Guest&quot;); // TypeError: sayHi is not a function
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Error</code></pre>
<p>이러한 문제를 해결할 때 사용하는 것이 바로 기명 함수 표현식이다.</p>
<pre><code class="language-javascript">let sayHi = function func(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    func(&quot;Guest&quot;);
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Hello, Guest </code></pre>
<hr>
<h1 id="new-function-문법">new Function 문법</h1>
<p>함수 표현식과 함수 선언문 이외에도 함수를 만들 수 있는 방법이 있다.</p>
<h3 id="문법">문법</h3>
<p>다음과 같이 <code>new Function</code> 문법을 사용하면 함수를 만들 수 있다. 매개변수와 함수 본문은 문자열 형태로 전달한다.</p>
<pre><code class="language-javascript">let func = new Function([arg1, ...], functionBody);</code></pre>
<pre><code class="language-javascript">let sum = new Function(&#39;a&#39;, &#39;b&#39;, &#39;return a + b&#39;);

alert( sum(1, 2) );    // 3

let sayHi = new Function(&#39;alert(&quot;Hello&quot;)&#39;);

sayHi();    // Hello</code></pre>
<p>기존에 사용하던 함수 선언문, 함수 표현식 방법과의 가장 큰 차이점은 <code>new Function</code> 문법은 런타임에 받은 문자열을 사용해서 함수를 만들 수 있다는 점이다. 예로 서버에서 코드를 받거나 템플릿을 사용해서 함수를 동적으로 컴파일해야 하는 경우와 같은 때에 사용할 수 있다.</p>
<h3 id="클로저">클로저</h3>
<p>함수는 <code>[[Environment]]</code>에 저장된 정보를 이용해서 함수가 만들어진 곳을 기억한다. 즉, <code>[[Environment]]</code>는 함수가 만들어진 렉시컬 환경을 참조한다.</p>
<p>그런데 <code>new Function</code>을 이용해서 함수를 만들면 함수의 <code>[[Environment]]</code> 프로퍼티가 현재 렉시컬 환경이 아닌 전역 렉시컬 환경을 참조하게 된다. 따라서, 외부 변수에 접근할 수 없고, 오직 전역 변수에만 접근할 수 있다.</p>
<pre><code class="language-javascript">// new Function 문법
function getFunc() {
  let value = &quot;test&quot;;

  let func = new Function(&#39;alert(value)&#39;);

  return func;
}

getFunc()(); // ReferenceError: value is not defined

// 함수 표현식
function getFunc() {
  let value = &quot;test&quot;;

  let func = function() { alert(value); };

  return func;
}

getFunc()(); // getFunc의 렉시컬 환경에 있는 값 &quot;test&quot;가 출력됨</code></pre>
<hr>
<h1 id="settimeout과-setinterval을-이용한-호출-스케줄링">setTimeout과 setInterval을 이용한 호출 스케줄링</h1>
<p>일정 시간이 지난 후에 원하는 함수를 예약 실행할 수 있게 하는 것을 호출 스케줄링이라고 한다.</p>
<p>호출 스케줄링을 구현하는 방법은 두 가지가 있다.</p>
<ul>
<li><code>setTimeout</code>을 이용해서 일정 시간이 지난 후에 함수를 실행하는 방법</li>
<li><code>setInterval</code>을 이용해서 일정 시간 간격을 두고 함수를 실행하는 방법</li>
</ul>
<h3 id="settimeout">setTimeout</h3>
<pre><code class="language-javascript">let timer = setTimeout(func|code, [delay], [arg1], ...);</code></pre>
<p><code>func|code</code>는 일정 시간이 지난 후 실행하고자 하는 코드로, 함수 또는 문자열 형태이다. 대개는 이 자리에 함수가 들어간다. 하위 호환성을 위해서 문자열도 받을 수 있다.</p>
<p><code>delay</code>는 실행 전 대기 시간으로, 단위는 밀리초이며 기본값은 0이다.</p>
<p><code>arg1, ...</code>는 함수에 전달할 인수들이다.</p>
<p>아래 예시를 살펴보자.</p>
<pre><code class="language-javascript">// 함수에 인수가 없을 때
function sayHi() {
  alert(&#39;Hello&#39;);
}

setTimeout(sayHi, 1000);    // 1초 후에 Hello

// 함수에 인수를 전달할 때
function sayHi(who, phrase) {
  alert( who + &quot;, &quot; + phrase );
}

setTimeout(sayHi, 1000, &quot;Yong&quot;, &quot;Hello&quot;);    // 1초 후에 Yong, Hello

// 익명 함수 전달
setTimeout(() =&gt; alert(&#39;Hello&#39;), 1000);    // 1초 후에 Hello</code></pre>
<p><code>setTimeout</code>을 호출하면 타이머 식별자가 반환된다. 스케줄링을 취소하고 싶을 때는 해당 식별자를 사용하면 된다.</p>
<pre><code class="language-javascript">let timerId = setTimeout(...);
clearTimeout(timerId);</code></pre>
<h3 id="setinterval">setInterval</h3>
<p><code>setInterval</code>은 <code>setTimeout</code>과 동일한 문법을 사용한다. 단, <code>setTimeout</code>은 전달받은 함수를 단 한 번만 실행하는 것과 달리 <code>setInterval</code>은 함수를 주기적으로 실행한다.</p>
<p>마찬가지로, <code>clearTimeout</code>을 통해 함수 호출을 중단할 수 있다.</p>
<hr>
<h1 id="callapply와-데코레이터-포워딩">call/apply와 데코레이터, 포워딩</h1>
<p>자바스크립트에서 함수는 이곳저곳 전달될 수 있고, 객체로도 사용될 수 있다. 함수 간에 호출을 어떻게 포워딩하는지, 함수를 어떻게 데코레이팅하는지 알아보자.</p>
<p>데코레이터는 함수를 감싸는 래퍼로 함수의 행동을 변화시킨다. 함수의 주요 작업은 여전히 함수에서 처리한다.</p>
<h3 id="함수의-코드-변경-없이-캐싱-기능-추가하기">함수의 코드 변경 없이 캐싱 기능 추가하기</h3>
<p>함수 안에 캐싱 관련 코드를 추가하는 대신, 래퍼 함수를 만들어서 캐싱 기능을 추가해보자.</p>
<pre><code class="language-javascript">function slow(x) {
  // CPU 집약적인 작업이 여기에 올 수 있습니다.
  alert(`slow(${x})을/를 호출함`);
  return x;
}

function cachingDecorator(func) {
  let cache = new Map();

  return function(x) {
    if (cache.has(x)) {    // cache에 해당 키가 있으면
      return cache.get(x); // 대응하는 값을 cache에서 읽어옵니다.
    }

    let result = func(x);  // 그렇지 않은 경우엔 func를 호출하고,

    cache.set(x, result);  // 그 결과를 캐싱(저장)합니다.
    return result;
  };
}

slow = cachingDecorator(slow);

alert( slow(1) ); // slow(1)이 저장되었습니다.
alert( &quot;다시 호출: &quot; + slow(1) ); // 동일한 결과

alert( slow(2) ); // slow(2)가 저장되었습니다.
alert( &quot;다시 호출: &quot; + slow(2) ); // 윗줄과 동일한 결과</code></pre>
<p>위 예시에서 <code>cachingDecorator</code> 같이 인수로 받은 함수의 행동을 변경시켜주는 함수를 <code>데코레이터(decorator)</code>라고 한다. </p>
<p>함수에 <code>cachingDecorator</code>를 적용하기만 하면 캐싱이 가능한 함수를 구현할 수 있기 때문에 데코레이터 함수는 유용하다. 또, 캐싱 관련 코드를 함수 로직과 분리할 수 있기 때문에 함수의 코드가 간결해진다는 장점이 있다.</p>
<p>아래 그림에서 볼 수 있듯이 <code>cachingDecorator(func)</code>를 호출하면 래펴 함수가 반환된다. 래퍼 함수 <code>function(x)</code>는 <code>func(x)</code>의 호출 결과를 캐싱 로직으로 감싼다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/8a122847-039e-4339-ba4f-d9921a0ca464/image.png" alt=""></p>
<p>바깥 코드에서 봤을 때, 함수 <code>slow</code>는 래퍼로 감싼 이전이나 이후나 동일한 일을 수행한다. 단지, 캐싱 기능이 추가된 것 뿐이다.</p>
<p>함수의 본문을 수정하는 것 보다 독립된 래퍼 함수를 사용할 때 생기는 이점을 정리하면 다음과 같다.</p>
<ul>
<li>데코레이터를 재사용할 수 있다.</li>
<li>추가하고자하는로직이 분리되어 함수의 복잡성이 증가하지 않는다.</li>
<li>필요하다면 여러 개의 데코레이터를 조합해서 사용할 수도 있다.</li>
</ul>
<h3 id="call-메서드를-사용해서-컨텍스트-지정하기">call 메서드를 사용해서 컨텍스트 지정하기</h3>
<p>위처럼 구현한 데코레이터는 객체의 메서드에는 사용하기 적합하지 않다.</p>
<pre><code class="language-javascript">// worker.slow에 캐싱 기능을 추가해봅시다.
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    // CPU 집약적인 작업이라 가정
    alert(`slow(${x})을/를 호출함`);
    return x * this.someMethod(); // (*)
  }
};

// 이전과 동일한 코드
function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func(x); // (**)
    cache.set(x, result);
    return result;
  };
}

alert( worker.slow(1) ); // 기존 메서드는 잘 동작합니다.

worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용

alert( worker.slow(2) ); // 에러 발생!, Error: Cannot read property &#39;someMethod&#39; of undefined</code></pre>
<p>원인은 <code>**</code>로 표시한 줄에서 래퍼가 기존 함수 <code>func(x)</code>를 호출하면 <code>this</code>가 <code>undefined</code>가 되기 때문이다. 즉, <code>this</code>의 컨텍스트가 사라졌기 때문에 에러가 발생한다.</p>
<p>이를 해결하기 위해서는 <code>this</code>를 명시적으로 고정해서 함수를 호출할 수 있게 해주는 내장 함수 메서드를 사용해야 한다.</p>
<pre><code class="language-javascript">func.call(context, arg1, ...)</code></pre>
<p>위 메서드를 호출하면 메서드의 첫 번째 인수가 <code>this</code>, 이어지는 인수가 <code>func</code>의 인수가 된 후, <code>func</code>가 호출된다.</p>
<pre><code class="language-javascript">// 컨텍스트만 고정
function sayHi() {
  alert(this.name);
}

let user = { name: &quot;John&quot; };
let admin = { name: &quot;Admin&quot; };

// call을 사용해 원하는 객체가 &#39;this&#39;가 되도록 합니다.
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin

// 컨텍스트 및 함수의 인수 전달
function say(phrase) {
  alert(this.name + &#39;: &#39; + phrase);
}

let user = { name: &quot;John&quot; };

// this엔 user가 고정되고, &quot;Hello&quot;는 메서드의 첫 번째 인수가 됩니다.
say.call( user, &quot;Hello&quot; ); // John: Hello</code></pre>
<p>다시 처음으로 돌아가서 데코레이터를 객체의 메서드에 적용할 때 <code>call</code>을 사용하면 컨텍스트를 원본 함수로 전달하면서 에러가 발생하지 않는다.</p>
<pre><code class="language-javascript">let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    alert(`slow(${x})을/를 호출함`);
    return x * this.someMethod(); // (*)
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func.call(this, x); // 이젠 &#39;this&#39;가 제대로 전달됩니다.
    cache.set(x, result);
    return result;
  };
}

worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용

alert( worker.slow(2) ); // 제대로 동작합니다.
alert( worker.slow(2) ); // 제대로 동작합니다. 다만, 원본 함수가 호출되지 않고 캐시 된 값이 출력됩니다.</code></pre>
<h3 id="여러-인수-전달하기">여러 인수 전달하기</h3>
<p>여러 인수를 전달할 방법은 다양하다.</p>
<ul>
<li>복수 키를 지원하는 맵과 유사한 자료 구조 구현하기</li>
<li>중첩 맵 사용하기</li>
<li>해싱 함수로 두 값을 하나로 합치기</li>
</ul>
<p>세 번째 방법을 사용해보자</p>
<pre><code class="language-javascript">let worker = {
  slow(min, max) {
    alert(`slow(${min},${max})을/를 호출함`);
    return min + max;
  }
};

function cachingDecorator(func, hash) {
  let cache = new Map();
  return function() {
    let key = hash(arguments); // (*)
    if (cache.has(key)) {
      return cache.get(key);
    }

    let result = func.call(this, ...arguments); // (**)

    cache.set(key, result);
    return result;
  };
}

function hash(args) {
  return args[0] + &#39;,&#39; + args[1];
}

worker.slow = cachingDecorator(worker.slow, hash);

alert( worker.slow(3, 5) ); // 제대로 동작합니다.
alert( &quot;다시 호출: &quot; + worker.slow(3, 5) ); // 동일한 결과 출력(캐시된 결과)</code></pre>
<h3 id="funcapply">func.apply</h3>
<p>위에서 사용한 <code>func.call(this, ...arguments)</code> 대신, <code>func.apply</code>를 사용해도 된다.</p>
<p><code>apply</code> 메서드는 <code>func</code>의 <code>this</code>를 첫 번째 인수 <code>context</code>로 고정하고, 유사 배열 객체인 <code>args</code>를 함수의 인수로 사용한다.</p>
<pre><code class="language-javascript">func.apply(context, args)</code></pre>
<p><code>call</code> 메서드와 <code>apply</code> 메서드의 차이는 다음과 같다.</p>
<ul>
<li><code>call</code> 메서드는 복수 인수를 따로 따로 받는다.</li>
<li><code>apply</code> 메서드는 유사 배열 형태의 인수만 받는다.</li>
</ul>
<p>이 차이만 빼면 두 메서드는 완전히 동일하게 동작한다. 인수가 이터러블 형태라면 <code>call</code>을, 유사 배열 형태라면 <code>apply</code>를 사용하면 된다.</p>
<p>이렇게 컨텍스트와 함께 인수 전체를 다른 함수에 전달하는 것을 <code>콜 포워딩(call fowarding)</code>이라 한다.</p>
<h3 id="메서드-빌리기">메서드 빌리기</h3>
<p>위에서 구현한 해싱 함수를 개선해보자. <code>args</code>의 요소 개수에 상관없이 요소들을 합칠 수 있게 하자. 가장 자연스러운 해결책은 배열 메서드 <code>join</code>을 사용하는 것이다.</p>
<pre><code class="language-javascript">function hash(args) {
  return args.join();
}</code></pre>
<p>하지만 이 방법은 동작하지 않는다. 인수로 넘어오는 <code>args</code>는 진짜 배열이 아니고 이터러블 객체나 유사 배열 객체이기 때문이다. 배열이 아니기 때문에 <code>join</code>을 호출하면 에러가 발생한다.</p>
<p>아래와 같이 <code>메서드 빌리기(method borrowing)</code> 기법을 통해서 배열 메서드 <code>join</code>을 사용할 수 있다.</p>
<pre><code class="language-javascript">function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);</code></pre>
<p>일반 배열에서 <code>join</code> 메서드를 빌려오고(<code>[].join</code>), <code>[].join.call</code>를 사용해서 <code>arguments</code>를 컨텍스트로 고정한 후 <code>join</code> 메서드를 호출한다.</p>
<h3 id="데코레이터와-함수-프로퍼티">데코레이터와 함수 프로퍼티</h3>
<p>함수 또는 메서드를 데코레이터로 감싸는 것은 대체적으로 안전하다. 그런데 원본 함수에 <code>calledCount</code> 등의 프로퍼티가 있으면 데코레이터를 적용한 함수에서는 프로퍼티를 사용할 수 없으므로 안전하지 않다.</p>
<p>함수에 프로퍼티가 있는 경우에는 데코레이터 사용에 주의해야 한다.</p>
<p>함수 프로퍼티에 접근할 수 있게 해주는 데코레이터를 만들 때에는 <code>Proxy</code>라는 특별한 객체를 사용해서 함수를 감싸야 한다.</p>
<hr>
<h1 id="함수-바인딩">함수 바인딩</h1>
<p>객체 메서드를 콜백으로 전달할 때 <code>this</code> 정보가 사라지는 문제가 생긴다. 이를 해결하는 방법을 알아보자.</p>
<h3 id="사라진-this">사라진 this</h3>
<p>앞 서 다양한 예제를 통해 <code>this</code> 정보가 사라지는 문제를 경험했다. 객체 메서드가 객체 내부가 아닌 다른 곳에 전달되어 호출되면 <code>this</code>가 사라진다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;,
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!</code></pre>
<p>이는 <code>setTimeout</code>에 객체에서 분리된 함수인 <code>user.sayHi</code>가 전달되기 때문이다. 위 예시의 마지막 줄은 아래와 같다.</p>
<pre><code class="language-javascript">let f = user.sayHi;
setTimeout(f, 1000); // user 컨텍스트를 잃어버림</code></pre>
<p>브라우저 환경에서 <code>setTimeout</code> 메서드는 인수로 전달받은 함수를 호출할 때 <code>this</code>에 <code>window</code>를 할당한다. 따라서 위 예시에서 <code>this.firstName</code>은 <code>window.firstName</code>가 되는데, <code>window</code> 객체에는 <code>firstName</code> 프로퍼티가 없으므로 <code>undefined</code>가 출력된다.</p>
<p>메서드를 전달할 때, 컨텍스트를 유지하려면 어떻게 해야 할까?</p>
<h3 id="방법-1-래퍼">방법 1. 래퍼</h3>
<p>가장 간단한 해결책은 래퍼 함수를 사용하는 것이다.</p>
<pre><code class="language-javascript">// 함수 선언 방식 래퍼
let user = {
  firstName: &quot;John&quot;,
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.sayHi(); // Hello, John!
}, 1000);

// 화살표 함수 방식 래퍼
setTimeout(() =&gt; user.sayHi(), 1000); // Hello, John!</code></pre>
<p>위 예시가 의도한 대로 동작하는 이유는 외부 렉시컬 환경에서 <code>user</code>를 받아서 보통 때처럼 메서드를 호출하기 때문이다. 이러한 방식은 <code>setTimeout</code>이 트리거 되기 전에 <code>user</code>가 변경되면, 변경된 객체의 메서드를 호출하는 취약점이 있다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;,
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() =&gt; user.sayHi(), 1000);

// 1초가 지나기 전에 user의 값이 바뀜
user = { sayHi() { alert(&quot;또 다른 사용자!&quot;); } };

// setTimeout에 또 다른 사용자!</code></pre>
<p>두 번째 방법을 사용하면 이런 일이 발생하지 않는다.</p>
<h3 id="방법-2-bind">방법 2. bind</h3>
<p>모든 함수는 <code>this</code>를 수정할 수 있는 내장 메서드 <code>bind</code> 를 가지고 있다.</p>
<p>기본 문법은 다음과 같다.</p>
<pre><code class="language-javascript">let boundFunc = func.bind(context);</code></pre>
<p><code>bind</code> 메서드는 함수처럼 호출 가능한 특수 객체를 반환한다. 이 객체를 호출하면 <code>this</code>가 <code>context</code>로 고정된 함수 <code>func</code>가 반환된다. 따라서, <code>boundFunc</code>를 호출하면 <code>this</code>가 고정된 <code>func</code>를 호출하는 것과 동일한 효과를 볼 수 있다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John</code></pre>
<p>바인딩된 함수의 인수도 원본 함수의 인수로 그대로 전달된다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;
};

function func(phrase) {
  alert(phrase + &#39;, &#39; + this.firstName);
}

// this를 user로 바인딩합니다.
let funcUser = func.bind(user);

funcUser(&quot;Hello&quot;); // Hello, John (인수 &quot;Hello&quot;가 넘겨지고 this는 user로 고정됩니다.)</code></pre>
<p>이제 객체 메서드에 <code>bind</code> 메서드를 적용해 보자.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;,
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

let sayHi = user.sayHi.bind(user); // (*)

// 이제 객체 없이도 객체 메서드를 호출할 수 있습니다.
sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

// 1초 이내에 user 값이 변화해도
// sayHi는 기존 값을 사용합니다.
user = {
  sayHi() { alert(&quot;또 다른 사용자!&quot;); }
};</code></pre>
<p>마찬가지로 인수도 전달할 수 있다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;John&quot;,
  say(phrase) {
    alert(`${phrase}, ${this.firstName}!`);
  }
};

let say = user.say.bind(user);

say(&quot;Hello&quot;); // Hello, John (인수 &quot;Hello&quot;가 say로 전달되었습니다.)
say(&quot;Bye&quot;); // Bye, John (&quot;Bye&quot;가 say로 전달되었습니다.)</code></pre>
<h3 id="부분-적용partial-application">부분 적용(partial application)</h3>
<p><code>bind</code> 메서드의 문법은 다음과 같다.</p>
<pre><code class="language-javascript">let bound = func.bind(context, [arg1] , ...);</code></pre>
<p><code>bind</code>는 컨텍스트를 <code>this</code>로 고정하는 것 뿐만 아니라 함수의 인수도 고정한다.</p>
<p>곱셉을 하는 함수에 인수를 고정해서 <code>double</code> 함수를 만들어보자.</p>
<pre><code class="language-javascript">function mul(a, b) {
  return a * b;
}

let double = mul.bind(null, 2);

alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10</code></pre>
<p>이러한 방식을 <code>부분 적용</code>이라 한다. 부분 적용을 사용하면 기존 함수의 매개변수를 고정하여 새로운 함수를 만들 수 있다.</p>
<p>기존 함수의 인수 몇 개를 고정한 함수, <code>부분 함수(부분 적용 함수)</code>를 만드는 이유는 다음과 같다.</p>
<ul>
<li>가독성이 좋은 이름을 가진 독립 함수를 만들 수 있다. ex) <code>double</code></li>
<li>인수를 고정할 수 있어서, 매번 같은 인수를 전달할 필요가 없다. ex) <code>mul(2, 3), mul(2, 4)</code></li>
<li>이외에도 베이스 함수를 기반으로 더 특정된 기능을 가진 변형 함수를 만들 수 있다.</li>
</ul>
<h3 id="컨텍스트-없는-부분-적용">컨텍스트 없는 부분 적용</h3>
<p><code>bind</code> 메서드는 컨텍스트를 생략하고 인수만 고정할 수 없다.</p>
<p>인수는 고정하면 컨텍스트는 고정하고 싶지 않을 때는 다음과 같이 구현할 수 있다. </p>
<pre><code class="language-javascript">function partial(func, ...argsBound) {
  return function(...args) { // (*)
    return func.call(this, ...argsBound, ...args);
  }
}

// 사용법:
let user = {
  firstName: &quot;John&quot;,
  say(time, phrase) {
    alert(`[${time}] ${this.firstName}: ${phrase}!`);
  }
};

// 시간을 고정한 부분 메서드를 추가함
user.sayNow = partial(user.say, new Date().getHours() + &#39;:&#39; + new Date().getMinutes());

user.sayNow(&quot;Hello&quot;);
// 출력값 예시:
// [10:00] John: Hello!</code></pre>
<p><code>partial</code>을 호출하면 래퍼가 반환된다. 래퍼를 호출하면 <code>func</code>는 다음과 같이 동작한다.</p>
<ul>
<li>동일한 <code>this</code>를 받는다.</li>
<li><code>partial</code>을 호출할 때 받은 인수(<code>&quot;HH:MM&quot;</code>)는 <code>...argsBound</code>에 전달된다.</li>
<li>래퍼에 전달된 인수(<code>&quot;Hello&quot;</code>)는 <code>...args</code>가 된다.</li>
</ul>
<hr>
<h1 id="화살표-함수-다시-살펴보기">화살표 함수 다시 살펴보기</h1>
<p>화살표 함수는 단순히 함수를 짧게 쓰기 위한 용도가 아니다. 몇 가지 유용한 기능을 제공한다.</p>
<h3 id="화살표-함수에는-this가-없다">화살표 함수에는 this가 없다.</h3>
<p>화살표 함수 본문에서 <code>this</code>에 접근하면, 외부 컨텍스트에서 값을 가져온다. </p>
<p>이런 특징은 객체의 메서드 안에서 동일 객체의 프로퍼티를 대상으로 순회를 할 때 사용할 수 있다.</p>
<pre><code class="language-javascript">let group = {
  title: &quot;1모둠&quot;,
  students: [&quot;보라&quot;, &quot;호진&quot;, &quot;지민&quot;],

  showList() {
    this.students.forEach(
      student =&gt; alert(this.title + &#39;: &#39; + student)
    );
  }
};

group.showList();</code></pre>
<p>예시의 <code>forEach</code>에서 화살표 함수를 사용했기 때문에 화살표 함수 본문에 있는 <code>this.title</code>은 화살표 함수 바깥에 있는 메서드인 <code>showList</code>가 가리키는 대상과 동일해진다.</p>
<p>위 예시에서 화살표 함수 대신 일반 함수를 사용한다면 에러가 발생한다.</p>
<pre><code class="language-javascript">let group = {
  title: &quot;1모둠&quot;,
  students: [&quot;보라&quot;, &quot;호진&quot;, &quot;지민&quot;],

  showList() {
    this.students.forEach(function(student) {
      // TypeError: Cannot read property &#39;title&#39; of undefined
      alert(this.title + &#39;: &#39; + student)
    });
  }
};

group.showList();</code></pre>
<p><code>forEach</code>에 전달되는 함수의 <code>this</code>가 <code>undefined</code>이기 때문에 에러가 발생한다. 그런데 화살표 함수는 <code>this</code> 자체가 없기 때문에 이런 에러가 발생하지 않는다.</p>
<p>참고로, 화살표 함수는 <code>this</code>가 없기 때문에 생성자 함수로 사용할 수 없다.  <code>new</code>와 함께 호출 할 수 없다.</p>
<h3 id="화살표-함수에는-유사-배열-객체-arguments가-없다">화살표 함수에는 유사 배열 객체 arguments가 없다</h3>
<p>화살표 함수는 일반 함수와 다르게 모든 인수에 접근할 수 있는 유사 배열 객체 <code>arguments</code>를 지원하지 않는다.</p>
<p>이런 특징은 현재 <code>this</code> 값과 <code>arguments</code> 정보를 함께 실어 호출을 포워딩 해주는 데코레이터를 만들 때 유용하게 사용된다.</p>
<pre><code class="language-javascript">function defer(f, ms) {
  return function() {
    setTimeout(() =&gt; f.apply(this, arguments), ms)
  };
}

function sayHi(who) {
  alert(&#39;안녕, &#39; + who);
}

let sayHiDeferred = defer(sayHi, 2000);
sayHiDeferred(&quot;철수&quot;); // 2초 후 &quot;안녕, 철수&quot;가 출력됩니다.</code></pre>
<p>화살표 함수와 일반 함수의 다른 점을 요약하면 다음과 같다.</p>
<ul>
<li><code>this</code>를 가지지 않는다.</li>
<li><code>arguments</code>를 지원하지 않는다.</li>
<li><code>new</code>와 함께 호출할 수 없다.</li>
</ul>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/advanced-functions">https://ko.javascript.info/advanced-functions</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 01. 09 프론트엔드 기본 지식) 객체 지향 프로그래밍(OOP)과 자바스크립트]]></title>
            <link>https://velog.io/@dev_diver/22.-01.-09-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8DOOP%EA%B3%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@dev_diver/22.-01.-09-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8DOOP%EA%B3%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Mon, 09 Jan 2023 03:42:15 GMT</pubDate>
            <description><![CDATA[<h1 id="객체-지향-프로그래밍objected-oriented-programming-oop이란">객체 지향 프로그래밍(Objected-Oriented Programming, OOP)이란?</h1>
<p>객체 지향 프로그래밍은 실세계에 존재하는 <code>객체(Object)</code>를 소프트웨어의 세계에서 표현하기 위해, 객체의 핵심적인 개념 또는 기능만을 추출하는 <code>추상화(abstraction)</code>를 통해 모델링하려는 프로그래밍 패러다임을 의미한다.</p>
<p>다시 말해, 프로그램을 여러 개의 독립된 단위, 즉 <strong>&quot;객체&quot;</strong> 들의 관계성있는 집합이라는 관점으로 접근하는 방식으로 볼 수 있다.</p>
<h1 id="객체-지향-프로그래밍의-기본-구성-요소">객체 지향 프로그래밍의 기본 구성 요소</h1>
<blockquote>
</blockquote>
<ul>
<li><code>클래스(Class)</code><ul>
<li>같은 종류의 집단이 가지는 <code>속성</code>과 행위인 <code>메서드</code>를 정의한 것이다. </li>
<li>객체 지향 프로그램의 기본적인 사용자 정의 데이터형(추상 자료형)이라고 할 수 있다.</li>
<li>객체 생성에 사용되는 청사진일뿐이며, 실질적으로 사용되는 객체를 생성하기위해서는 <code>new</code> 연산자를 통한 인스턴스화 과정이 필요하다.</li>
</ul>
</li>
<li><code>객체(Object)</code> <ul>
<li>클래스의 인스턴스이다.</li>
<li>객체는 자신 고유의 속성을 가지며, 클래스에서 정의한 메서드를 수행할 수 있다.</li>
<li>모든 인스턴스는 클래스에서 정의된 범위 내에서만 작동하며, 런타임에 그 구조를 변경할 수 없다.</li>
</ul>
</li>
</ul>
<h1 id="객체-지향-프로그래밍의-4원칙">객체 지향 프로그래밍의 4원칙</h1>
<h3 id="캡슐화정보-은닉">캡슐화(정보 은닉)</h3>
<p>외부로 노출해야 하는 값과 내부에서만 사용하는 값을 구분하여, 내부 데이터에 바로 접근을 하지 못하게 하고 필요한 메서드만 열어두는 특성을 말한다.</p>
<p>속성과 메서드를 클래스와 같은 하나의 틀 안에 담고 외부에 공개될 필요가 없는 정보는 private 변수 등으로 숨길 수 있다.</p>
<p>이러한 방법은 객체 외부에는 제한된 접근 권한을 제공하여 내부를 보호해서 객체의 안정성을 높이고, 필요한 메서드만 열어둠으로써 객체의 재사용성을 높일 수 있다.</p>
<pre><code class="language-javascript">// class
class Character {
  name = &quot;Yong&quot;
  #hp = 300
  #mp = 500

  attck() {...}
  useSkill() {... this.#mp -= 50; ... }
  moveTo(toX, toY) {...}
}

// object
var character = new Character();
character.name = &quot;Yong&quot; // public한 필드는 외부에서 수정이 가능하여 문제를 일으킬 수 있다.

// private을 이용하면 mp를 외부에서 함부로(?) 수정할 수 없다.
character.mp = 3000 // Error TS18013: Property &#39;#mp&#39; is not accessible outside class &#39;Human&#39; because it has a private identifier.</code></pre>
<h3 id="상속">상속</h3>
<p>객체에 공통된 부분만 따로 만들고 그 코드를 같이 물려 받아서 같은 부분은 재사용하고, 나머지 다른 부분들은 각각 코딩하는 방식을 바로 상속이라 한다. 즉, 객체의 일부분을 재사용하는 방법이다. </p>
<p>예를 들어, 다음과 같은 저글링 클래스를 구현했다고 해보자.</p>
<pre><code class="language-javascript">// class
class Zergling {
  name = &quot;저글링&quot;
  hp = 35

  die() {...}
  attack() {...}
  moveTo(toX, toY) {...}
}</code></pre>
<p>이제 히드라 클래스를 만드려고하니 저글링 클래스와 동일한 로직이 많다는 것을 알게 되었다. 또, 모션이나 공격 방식 등 다르게 구현해야 하는 로직도 존재한다는 것을 알게 되었다.</p>
<pre><code class="language-javascript">class Hydra {
  name = &quot;히드라&quot;
  hp = 75

  die() {...}
  attack() {...}
  moveTo(toX, toY) {...}
}</code></pre>
<p>이처럼 한 객체에 공통된 부분만 따로 만들고 그 코드를 같이 상속 받아서 같은 부분은 재사용하고, 나머지 다른 부분들은 각각 코딩할 수 있다.</p>
<p>상속을 통해 새로운 클래스가 기존의 클래스의 속성과 메서드를 물려받을 수 있다.</p>
<p>이 때, 상속을 받는 새로운 클래스를 하위 클래스 혹은 자식 클래스라고 하고, 새로운 클래스가 상속하는 기존의 클래스를 상위 클래스 혹은 부모 클래스라고 한다. </p>
<p>상속은 코드 재사용의 관점에서 매우 유용하다. 새롭게 정의할 클래스가 기존에 있는 클래스와 유사하다면 상속을 통해서 다른 점만 구현하면 된다.</p>
<h3 id="추상화">추상화</h3>
<p>추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현하는 것이다. 위 상속의 예시에서 살펴본 것처럼 공통적인 부분을 모아서 상위의 개념으로 새롭게 이름을 붙이는 것도 추상화의 예시라고 할 수 있다.</p>
<pre><code class="language-javascript">// 상속과 추상화 예시
class Unit {
  name = &quot;&quot;
  hp = 0

  die() {...}
  attack() {...}
  moveTo(toX, toY) {...}
}

class Zergling extends Unit {
  name = &quot;저글링&quot;
  hp = 35

  attack() {...}
}

class Hydralisk extends Unit {
  name = &quot;히드라리스크&quot;
  hp = 70

  attack() {...}
}</code></pre>
<p>추상화를 통해 정의된 자료형을 추상 자료형이라고 한다. 추상 자료형은 자료형의 자료 표현과 자료형의 연산을 캡슐화한 것으로 자료형의 정보를 은닉할 수 있다.</p>
<p>객체 지향 프로그래밍에서 일반적으로 추상 자료형을 <code>클래스</code>, 추상 자료형의 인스턴스를 <code>객체</code>, 추상 자료형에서 정의된 연산을 <code>메소드</code>라고 한다.</p>
<h3 id="다형성">다형성</h3>
<p>프로그램의 각 요소들(상수, 변수, 식, 객체, 함수, 메서드 등)이 다양한 자료형(type)에 속할 수 있는 성질을 가리킨다.</p>
<p>쉽게 설명하면 저글링, 히드라, 오버로드 등이 함께 섞인 유닛들이 각자 이동하는 속도나 방법이 다르지만, 이들을 하나의 이동이 가능한 유닛으로 취급하여 같은 타입으로 취급할 수 있다.</p>
<p>이처럼 추상화된 Unit 이라는 타입이 저글링, 히드라, 오버로드 등 하위 타입인 여러 가지 타입으로 참조할 수 있다는 개념이 바로 다형성이다.</p>
<pre><code class="language-javascript">let zergling1 = new Zergling()
let zergling2 = new Zergling()
let hydralisk1 = new Hydralisk()
let hydralisk2 = new Hydralisk()
let mutalisk = new Mutalisk()
let overload = new Overload()

let units = [zergling1, zergling2, hydralisk1, hydralisk2, mutalisk, overload]

// 모두 같은 이름의 moveTo 메소드를 호출하지만 각자의 방식대로 동작한다.
units.forEach(unit =&gt; unit.moveTo(300, 400))</code></pre>
<p>객체 지향 프로그래밍에서는 다음과 같은 방식으로 다형성을 구현할 수 있다.</p>
<ul>
<li><p><code>오버라이딩(overriding)</code> : 같은 이름의 메서드가 여러 클래스에서 다른 기능을 하는 것을 뜻한다. 즉, 부모 클래스로부터 상속받은 메서드를 자식 클래스에서 재정의하는 것을 뜻한다.</p>
</li>
<li><p><code>오버로딩(overloading)</code> : 하나의 클래스 내에서 같은 이름의 메서드가 매개변수의 개수나 자료형에 따라서 다른 기능을 하는 것을 뜻한다.</p>
</li>
</ul>
<hr>
<h1 id="자바스크립트의-객체-지향">자바스크립트의 객체 지향</h1>
<h3 id="자바스크립트는-프로토타입-기반-객체-지향-언어이다">자바스크립트는 프로토타입 기반 객체 지향 언어이다</h3>
<p>자바스크립트는 멀티 패러다임 언어로 함수형, 프로토타입 기반 객체 지향 언어이다. 그 중 프로토타입 기반 객체 지향 언어의 특징을 살펴보자. 프로토타입 객체는 메소드와 속성들을 상속받기 위한 템플릿 객체로, 모든 객체들이 가지고 있다.</p>
<p>자바스크립트는 클래스가 없는 프로토타입 기반 객체 지향 언어이다. 이 때문에 별도의 객체 생성 방법이 존재한다.</p>
<ul>
<li><code>객체 리터럴</code></li>
<li><code>Object() 생성자 함수</code></li>
<li><code>생성자 함수</code></li>
</ul>
<pre><code class="language-javascript">// 객체 리터럴
let obj1 = {};
obj1.name = &quot;Yong&quot;;

// Object() 생성자 함수
let obj2 = new Object();
obj2.name = &quot;Yong&quot;;

// 생성자 함수
function F() {}
let obj3 = new F();
obj3.name = &quot;Yong&quot;;</code></pre>
<p>자바스크립트는 클래스 기반 객체 지향 언어와 다르게, 이미 생성된 객체의 자료구조와 기능을 동적으로 변경할 수 있다는 특징이 있다. 즉, 자바스크립트는 동적인 언어이다. </p>
<p>프로토타입 기반 객체 지향에서 객체 지향의 상속, 캡슐화 등의 개념은 프로토타입 체인과 클로저 등으로 구현할 수 있다.</p>
<blockquote>
<p>참고로, ES6에서 새롭게 Class가 도입되었다. 다만, 자바스크립트의 새로운 객체지향 모델을 제공하는 것은 아니다. 새로 도입된 Class도 사실 함수이고, 기존 프로토타입 기반 패턴의 Syntatic sugar이다.</p>
</blockquote>
<h3 id="객체의-생성">객체의 생성</h3>
<p>본래는 클래스 기반 언어에서는 <code>class</code>와 <code>new</code> 연산자를 통해서 객체를 생성하지만, 자바스크립트는 클래스 대신 생성자 함수를 사용해서 객체를 생성할 수 있다. 이 때, 생성자 함수는 클래스이자 생성자의 역할을 한다.</p>
<pre><code class="language-javascript">// 생성자 함수
function Person(name) {
  this.name = name;

  this.setName = function (name) {
    this.name = name;
  };

  this.getName = function () {
    return this.name;
  };
}

// 객체 생성
let me = new Person(&quot;Yong&quot;);

// 객체의 메서드 호출
me.getName();    // Yong</code></pre>
<p>위와 같이 생성자 함수 방식으로 생성한 객체 자체는 문제가 없지만, 여러 개의 객체를 생성한다면 문제가 발생할 수 있다. 아래 예시를 보면 각각의 객체마다 중복되어 생성된 메서드를 볼 수 있다. 즉, 각 객체가 동일한 메서드를 각자 소유하게 된다. 이는 메모리 낭비 문제로 귀결될 수 있다.</p>
<pre><code class="language-javascript">let me  = new Person(&#39;Yong&#39;);
let you = new Person(&#39;Kim&#39;);
let him = new Person(&#39;Choi&#39;);

console.log(me);  // Person { name: &#39;Yong&#39;, setName: [Function], getName: [Function] }
console.log(you); // Person { name: &#39;Kim&#39;, setName: [Function], getName: [Function] }
console.log(him); // Person { name: &#39;Choi&#39;, setName: [Function], getName: [Function] }</code></pre>
<p>이같은 문제를 해결하기 위해서는 자바스크립트의 특징인 프로토타입을 사용할 수 있다.</p>
<h3 id="프로토타입-체인과-메서드-정의">프로토타입 체인과 메서드 정의</h3>
<p>자바스크립트의 모든 객체는 프로토타입이라고 하는 객체를 가리키는 내부적인 링크를 가지고 있다. 즉, 프로토타입을 통해 객체들을 연결할 수 있는데, 이를 <code>프로토타입 체인</code>이라고 한다.</p>
<p>생성자 함수 내부에 작성했던 메서드를 생성자 함수의 <code>prototype 프로퍼티</code>가 가리키는 프로토타입 객체로 이동시키면, 생성자 함수에 의해 생성된 모든 객체는 프로토타입 체인을 통해 프로토타입 객체의 메서드를 참조할 수 있다.</p>
<pre><code class="language-javascript">function Person(name) {
  this.name = name;
}

// 프로토타입 객체에 메소드 정의
Person.prototype.setName = function (name) {
  this.name = name;
};

// 프로토타입 객체에 메소드 정의
Person.prototype.getName = function () {
  return this.name;
};

let me  = new Person(&#39;Yong&#39;);
let you = new Person(&#39;Kim&#39;);
let him = new Person(&#39;choi&#39;);

console.log(Person.prototype);
// Person { setName: [Function], getName: [Function] }

console.log(me);  // Person { name: &#39;Yong&#39; }
console.log(you); // Person { name: &#39;Kim&#39; }
console.log(him); // Person { name: &#39;choi&#39; }

me.getName();    // Yong</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/3bafe27e-1eff-485f-85d3-ad31961236df/image.png" alt=""></p>
<h3 id="자바스크립트의-상속과-추상화-그리고-다형성">자바스크립트의 상속과 추상화 그리고 다형성</h3>
<p>자바스크립트는 클래스가 없기 때문에 일반적으로는 클래스의 변수나 메서드들을 물려받을 수 없다. </p>
<p>대신 프로토타입 객체를 통해 객체들을 연결한다. 즉, 프로토타입을 통해 객체가 다른 객체로 직접 상속된다. 현재 객체에 원하는 속성이 없으면 프로토타입 객체에서 찾는 방식으로 상속을 구현하고, 프로토타입 객체에도 있고 현재 객체에도 있는 속성은 현재 객체에 있는걸 사용해서 다형성을 구현한다.</p>
<p>다시 말하면 프로토타입 객체가 부모의 역할을 하고, 이들로부터 물려받는 객체가 자식의 역할을 한다.</p>
<pre><code class="language-javascript">var animal = {
  name: &quot;&quot;,
  age: 0
  sleep: function() { console.log(&quot;sleep... zzz&quot;, this.name) }
  speak: function() { console.log(&quot;...&quot;, this.name) }
}

var cat = {
  name: &quot;냥이&quot;,
  age: 1
  speak: function() { console.log(&quot;야옹~~~&quot;, this.name, this.age) }
}

// 상속을 받는(척 하지만 prototype 연결)
cat.__proto__ = animal

cat.sleep() // cat에는 sleep없으니 연결된 animal를 찾아가서 sleep을 호출.. 이걸로 마치 상속 해결!
cat.speak() // cat에 있는 속성이니 본인의 speak호출! 이걸로 다형성 해결!</code></pre>
<p>자바스크립트의 상속 구현 방식은 크게 두 가지이다.</p>
<ul>
<li>클래스 기반 언어의 상속 방식을 흉내 낸 <code>의사 클래스 상속(Pseudo-classical Inheritance)</code></li>
<li>프로토타입으로 구현한 <code>프로토타입 패턴 상속(Prototypal Inheritance)</code></li>
</ul>
<p>의사 클래스 상속은 자식 생성자 함수의 <code>prototype 프로퍼티</code>를 부모 생성자 함수의 인스턴스로 교체하여 상속을 구현하는 방식이다. 다시 말해, 자식 생성자 함수의 프로토타입 객체를 부모 생성자 함수의 인스턴스로 교체한다. 다만 의사 클래스 상속 방식은 여러 단점을 가지고 있기 때문에 프로토타입 패턴 상속만 알아보겠다.</p>
<p>프로토타입 패턴 상속은 <code>Object.create</code> 함수를 사용하여 객체에서 다른 객체로 직접 상속을 구현하는 방식이다. <code>Object.create</code> 함수는 매개변수에 프로토타입으로 설정할 객체를 전달하고, 이를 상속하는 새로운 객체를 생성한다.</p>
<pre><code class="language-javascript">// 부모 생성자 함수
let Parent = (function () {
  // Constructor
  function Parent(name) {
    this.name = name;
  }

  // method
  Parent.prototype.sayHi = function () {
    console.log(&#39;Hi! &#39; + this.name);
  };

  // return constructor
  return Parent;
}());

// create 함수의 인수는 프로토타입이다.
let child = Object.create(Parent.prototype);
child.name = &#39;child&#39;;

child.sayHi();  // Hi! child

console.log(child instanceof Parent); // true</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/5c5ba1a1-25b0-47f0-91b0-047d37a98d28/image.png" alt=""></p>
<h3 id="자바스크립트의-캡슐화정보-은닉와-모듈-패턴">자바스크립트의 캡슐화(정보 은닉)와 모듈 패턴</h3>
<p>자바스크립트는 <code>public</code> 또는 <code>private</code> 등의 키워드를 제공하지 않지만, 정보 은닉이 불가능한 것은 아니다.</p>
<pre><code class="language-javascript">let Person = function(arg) {
  const name = arg ? arg : &#39;&#39;; // ①

  this.getName = function() {
    return name;
  };

  this.setName = function(arg) {
    name = arg;
  };
}

let me = new Person(&#39;Yong&#39;);

let name = me.getName();

console.log(name);    // Yong

me.setName(&#39;Kim&#39;);
name = me.getName();

console.log(name);    // Kim</code></pre>
<p>위 예시를 살펴 보면, 함수 레벨의 스코프를 제공하므로 함수 내부의 변수는 외부에서 참조할 수 없다. 즉, 위 Person 생성자 함수 내부에서 선언된 name은 private 변수가 된다. </p>
<p>private 변수는 외부에서 직접 접근할 수 없고, 위처럼 생성자 함수 내에 생성된 getName 메서드 같은 클로저를 통해 접근할 수 있다. 이러한 방식이 기본적인 자바스크립트의 캡슐화이다.</p>
<p>위 예시를 조금 더 정리해보자. person 함수는 객체를 반환하고, 이 객체 내의 메서드는 클로저로서 private 변수에 접근할 수 있다. 이러한 방식을 모듈 패턴이라고 하며 캡슐화와 정보 은닉을 제공한다.</p>
<pre><code class="language-javascript">let person = function(arg) {
  let name = arg ? arg : &#39;&#39;;

  return {
    getName: function() {
      return name;
    },
    setName: function(arg) {
      name = arg;
    }
  }
}

let me = person(&#39;Yong&#39;); /* or let me = new person(&#39;Yong&#39;); */

let name = me.getName();

console.log(name);

me.setName(&#39;Kim&#39;);
name = me.getName();

console.log(name);</code></pre>
<p>다만 이러한 모듈 패턴은 다음과 같은 주의할 점이 있다.</p>
<ul>
<li>private 변수가 원시형이 아닌 객체나 배열일 경우, 반환된 해당 멤버의 변경이 가능하다. 객체를 반환하는 경우 반환값은 얕은 복사로 인해 private 변수의 참조값을 반환하게 된다. 따라서, 외부에서 private 변수의 값을 변경할 수 있다. 이를 회피하기 위해서는 객체를 그대로 반환하지 않고, 반환해야 할 객체의 정보를 새로운 객체에 담아 반환해야 한다.</li>
<li>person 함수가 반환한 객체는 person 함수 객체의 프로토타입에 접근할 수 없다. 위에서 살펴본 모듈 패턴은 생성자 함수가 아니며 단순히 메소드를 담은 객체를 반환한다. 반환된 객체는 객체 리터럴 방식으로 생성된 객체로 함수 person의 프로토타입에 접근할 수 없다. 반환된 객체가 함수 person의 프로토타입에 접근할 수 없다는 것은 person을 부모 객체로 상속할 수 없다는 것을 의미한다. 함수 person을 부모 객체로 상속할 수 없다는 것은 함수 person이 반환하는 객체에 모든 메소드를 포함시켜야한다는 것을 의미한다. 이 문제를 해결하기 위해서는 객체를 반환하는 것이 아닌 함수를 반환해야 한다.</li>
</ul>
<pre><code class="language-javascript">let Person = function() {
  let name;

  let F = function(arg) { name = arg ? arg : &#39;&#39;; };

  F.prototype = {
    getName: function() {
      return name;
    },
    setName: function(arg) {
      name = arg;
    }
  };

  return F;
}();

let me = new Person(&#39;Yong&#39;);

console.log(Person.prototype === me.__proto__);

console.log(me.getName());
me.setName(&#39;Kim&#39;)
console.log(me.getName());</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/4ba1e635-ed7c-4230-b274-083d5070520f/image.png" alt=""></p>
<p>캡슐화를 구현하는 패턴은 다양하며 각각의 패턴에는 장단점이 있다. 다양한 패턴의 장단점을 분석하고 파악하는 것이 보다 효율적인 코드를 작성하는데 중요하다.</p>
<hr>
<h1 id="자바스크립트에서의-객체지향의-의미">자바스크립트에서의 객체지향의 의미</h1>
<p>자바스크립트는 멀티 패러다임 언어이다. 객체지향을 배제할 필요도, 맹목적으로 객체지향스럽게 설계해야 할 필요도 없다. 객체지향의 개념으로 데이터와 메서드를 가지는 객체를 통해 독립적이고 작은 모듈로 만들어 편리하게 재사용하는 장점을 취하면서, 객체들의 결합이 높아져 프로그램이 복잡해질 때는 함수형으로 만들어서 사용하면 된다. 결국 자바스크립트는 자바스크립트스럽게 사용하면 된다.</p>
<hr>
<h1 id="참고문헌">참고문헌</h1>
<p><a href="https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D">https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</a>
<a href="https://poiemaweb.com/js-object-oriented-programming">https://poiemaweb.com/js-object-oriented-programming</a>
<a href="https://yozm.wishket.com/magazine/detail/1396/">https://yozm.wishket.com/magazine/detail/1396/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 01. 03) styled-components override 이슈]]></title>
            <link>https://velog.io/@dev_diver/22.-01.-03-2%EC%A3%BC%EC%B0%A8.-styled-components-override-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@dev_diver/22.-01.-03-2%EC%A3%BC%EC%B0%A8.-styled-components-override-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Tue, 03 Jan 2023 08:18:23 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-발견">문제 발견</h1>
<p>일반 컴포넌트를 <code>styled-components</code>로 override할 때, override한 스타일 코드들이 적용되지 않는 문제가 발생했다.</p>
<pre><code class="language-tsx">// 예시. Button.tsx
import React from &#39;react&#39;;
import styled from &quot;styled-components&quot;

interface Props {
  label: string;
  onClick?: (event: React.MouseEvent) =&gt; void;
}

export const Button = ({ label, onClick }: Props) =&gt; (
  &lt;button onClick={onClick}&gt;
    {label}
  &lt;/button&gt;
);

const StyledButton = styled(Button)`
    background-color: blue;
`;</code></pre>
<p>위와 같이 구현한 예시 코드로 버튼을 렌더링해도 <code>class</code>가 갱신되지 않고, 스타일도 적용되지 않았다.</p>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/659b54c2-b6f6-4eb2-9202-cbc384e5d3b4/image.png" alt=""></p>
<hr>
<h1 id="원인-분석">원인 분석</h1>
<p>새로 도입한 <code>vite</code>의 번들링 방식이 문제인지, <code>storybook</code>이나 <code>styled-components</code>의 버전 문제인지 등 이것저것 시도해보았지만 답이 아니었다.</p>
<p>차근 차근 <code>styled-components</code>의 공식 문서를 살펴본 결과 <code>styled-components</code>로 스타일링 된 컴포넌트가 아닌 일반적인 컴포넌트는 <code>className</code>를 <code>props</code>로 직접 명시해주어야 <code>className</code>이 override 된다는 해결 방법을 찾았다.</p>
<hr>
<h1 id="문제-해결">문제 해결</h1>
<p>따라서 앞 서 구현한 코드에서 단순히, <code>className</code> props를 지정해주고 문제를 해결했다.</p>
<pre><code class="language-tsx">// 예시. Button.tsx
import React from &#39;react&#39;;
import styled from &quot;styled-components&quot;

interface Props {
  className?: string;
  label: string;
  onClick?: (event: React.MouseEvent) =&gt; void;
}

export const Button = ({ className, label, onClick }: Props) =&gt; (
  &lt;button className={className} onClick={onClick}&gt;
    {label}
  &lt;/button&gt;
);

const StyledButton = styled(Button)`
    background-color: blue;
`;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_diver/post/1a1917b8-8b8b-48b2-b512-bed32821b5d0/image.png" alt=""></p>
<hr>
<h1 id="회고">회고</h1>
<p>필자가 개발하면서 겪는 이슈 혹은 에러들은 십중팔구 공식 문서에 친절히 나와있다. (<del>지엽적일 수는 있지만..</del>)</p>
<p>조급하게 문제를 해결할 생각보다는 천천히 원인을 분석하고, 공식 문서를 먼저 살펴보는 방법도 좋을 것 같다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 11. 18 GDSC 프론트 스터디) 테스팅 에러 해결 ]]></title>
            <link>https://velog.io/@dev_diver/22.-11.-18-GDSC-%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@dev_diver/22.-11.-18-GDSC-%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Fri, 18 Nov 2022 08:26:56 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-원인">문제 원인</h1>
<p>라우팅 관련 테스트 코드를 작성하면서 두 가지 문제를 발견했다.</p>
<p>첫 번째는 <code>&lt;Router&gt;</code> 컴포넌트를 사용할 때, <code>history</code> props가 없는 문제</p>
<p>두 번째는 페이지 이동 테스트를 할 때 컴포넌트가 리렌더링이 되지 않아, <code>element</code>가 업데이트되지 않는 문제이다.</p>
<h1 id="문제-분석">문제 분석</h1>
<p>첫 번째 문제는 <code>history</code> prop이 <code>deprecated</code> 된게 원인으로 파악됐다.</p>
<p>두 번째 문제는 <code>render</code> 함수가 컴포넌트를 렌더링할 때, 딱 한 번만 렌더링함으로 다른 페이지로 이동하는 테스트를 했을 때 컴포넌트가 업데이트되지 않는점이 문제인 것 같다.</p>
<h1 id="문제-해결">문제 해결</h1>
<p>첫 번째 문제부터 살펴보자. 기존 테스트 코드는 아래와 같이 사용했다.</p>
<pre><code class="language-javascript">import { Router } from &quot;react-router-dom&quot;;

const history = createMemoryHistory();

render (&lt;Router history={history} /&gt;);</code></pre>
<p>위 코드에서 <code>&lt;Router&gt;</code> 컴포넌트에 <code>history</code> prop 대신 <code>location</code> 과 <code>navigator</code> props를 사용했다.</p>
<pre><code class="language-javascript">import { Router } from &quot;react-router-dom&quot;;

const history = createMemoryHistory();

render (
  &lt;Router location={history.location} navigator={history} /&gt;
);</code></pre>
<p>두 번째 문제를 살펴보자. 기존 테스트 코드는 아래와 같다. 렌더링을 하고 이벤트를 발생시켜 페이지를 이동하는 테스트를 했다.</p>
<pre><code class="language-javascript">render (
  render (
      &lt;Router location={history.location} navigator={history}&gt;
          &lt;App /&gt;
      &lt;/Router&gt;
  );
)

fireEvent.click(...);

...
</code></pre>
<p>위 코드는 한 번만 렌더링 하므로 <code>render</code> 함수가 반환하는 <code>rerender</code> 함수를 사용했다. 이벤트를 발생 시키고 컴포넌트를 리렌더링했다.</p>
<pre><code class="language-javascript">
const { rerender } = render (
  render (
      &lt;Router location={history.location} navigator={history}&gt;
          &lt;App /&gt;
      &lt;/Router&gt;
  );
)

fireEvent.click(...);

rerender(render (
  render (
      &lt;Router location={history.location} navigator={history}&gt;
          &lt;App /&gt;
      &lt;/Router&gt;
  );
));

...
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 24 자바스크립트)  자료형 심화와 자료구조]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-18-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9E%90%EB%A3%8C%ED%98%95-%EC%8B%AC%ED%99%94</link>
            <guid>https://velog.io/@dev_diver/22.-10.-18-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9E%90%EB%A3%8C%ED%98%95-%EC%8B%AC%ED%99%94</guid>
            <pubDate>Mon, 24 Oct 2022 08:18:17 GMT</pubDate>
            <description><![CDATA[<h1 id="원시값에-객체처럼-메서드-사용하기">원시값에 객체처럼 메서드 사용하기</h1>
<p>자바스크립트는 원시값을 다루는 작업에 객체처럼 메서드를 사용하여 작업을 수월하게 하고자 했다. 단, 원시값은 객체처럼 무겁지 않고, 가능한 한 빠르고 가볍게 유지해야한다는 조건이 필요했다.</p>
<p>이를 해결하기 위해서 원시값에도 객체처럼 메서드를 호출 할 수 있는 방법을 아래와 같이 만들었다.</p>
<blockquote>
</blockquote>
<ul>
<li>원시값은 그대로 남겨두어 단일 값 형태를 유지한다.</li>
<li><code>문자열</code>, <code>숫자</code>, <code>불린</code>, <code>심볼</code>의 메서드와 프로퍼티에 접근할 수 있도록 언어 차원에서 허용했다.</li>
<li>원시값이 앞 서 말한 타입의 메서드나 프로퍼티에 접근하려 하면 추가 기능을 제공해주는 특수한 객체, <code>원시 래퍼 객체(object wrapper)</code>를 만들어 준다.</li>
<li>래퍼 객체는 작업이 끝나면 삭제된다.</li>
</ul>
<p>래퍼 객체는 원시값의 타입에 따라 종류가 다양하고, 래퍼 객체 마다 제공하는 메서드 역시 다르다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>String</code></li>
<li><code>Number</code></li>
<li><code>Boolean</code></li>
<li><code>Symbol</code></li>
</ul>
<p>예를 들어, 인수로 받은 문자열의 모든 글자를 대문자로 바꿔주는 <code>str.toUpperCase()</code> 메서드를 살펴보자.</p>
<pre><code class="language-javascript">let strExample = &quot;Hello&quot;;

alert( strExample.toUpperCase() );    // HELLO</code></pre>
<p>해당 메서드가 호출될 때 내부적으로 아래와 같이 동작한다.</p>
<blockquote>
</blockquote>
<ol>
<li>인수로 받은 문자열은 원시값이므로 원시값의 프로퍼티(toUpperCase)에 접근하는 순간, 래퍼 객체가 만들어진다. 래퍼 객체는 문자열의 값을 알고 있고, <code>toUpperCase()</code>와 같은 메서드를 가지고 있다.</li>
<li>메서드가 실행되고, 새로운 문자열이 반환된다.</li>
<li>래퍼 객체는 파괴되고, 원시값만 남는다.</li>
</ol>
<p>이러한 내부 프로세스를 통해 원시값을 가볍게 유지하면서 메서드를 호출하여 작업을 할 수 있다.</p>
<p>참고로, 자바스크립트 엔진은 위 프로세스의 최적화에 많은 신경을 쓴다. 래퍼 객체를 만들지 않고도 마치 래퍼 객체를 생성한 것처럼 동작하게끔 해준다.</p>
<hr>
<h1 id="숫자형과-다양한-메서드와-함수들">숫자형과 다양한 메서드와 함수들</h1>
<p>모던 자바스크립트는 숫자를 나타내는 두 가지 자료형을 지원한다.</p>
<blockquote>
</blockquote>
<ul>
<li>일반적인 숫자형은 64비트 형식의 IEEE-754에 저장된다.</li>
<li>2^53 이상이거나 -2^53 이하의 임의의 길이를 가진 정수는 BigInt형 숫자로 나타낼 수 있다.</li>
</ul>
<h3 id="숫자를-입력하는-다양한-방법">숫자를 입력하는 다양한 방법</h3>
<p>숫자는 일반적인 방식과 지수형, 두 가지 방식으로 변수에 할당할 수 있다.</p>
<p>큰 숫자와 작은 숫자를 입력하는 예시를 살펴보자.</p>
<pre><code class="language-javascript">// 큰 숫자 일반적인 방식
let billion = 1000000000;

// 큰 숫자 지수표현 방식
let billion = 1e9;

// 작은 숫자 일반적인 방식
let ms = 0.000001;

// 작은 숫자 지수표현 방식
let ms = 1e-6;</code></pre>
<p>또, 16진수와 2진수, 8진수로 변수에 할당할 수도 있다.</p>
<pre><code class="language-javascript">// 16진수
let hexNum = 0xff;
let hexNum = 0xFF;

// 2진수
let biNum = 0b11111111

// 8wlstn
let ocNum = 0o377;</code></pre>
<h3 id="진법-변환-메서드">진법 변환 메서드</h3>
<p>숫자값이 할당된 변수에 <code>num.toString(base)</code> 메서드를 사용하면 해당 값을 base진법으로 표현한 후, 이를 문자열로 반환해준다. base는 기본값이 10으로 2에서 36까지 지정할 수 있다.</p>
<pre><code class="language-javascript">let num = 255;

alert( num.toString(16) );    // ff
alert( num.toString(2) );    // 11111111</code></pre>
<h3 id="어림수-관련-내장-함수">어림수 관련 내장 함수</h3>
<p>어림수 관련 내장 함수들도 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Math.floor</code> : 소숫점 첫 째 자리에서 내린다. <ul>
<li>ex) 3.1 =&gt; 3, -1.1 =&gt; -2</li>
</ul>
</li>
<li><code>Math.ceil</code> : 소숫점 첫 째 자리에서 올린다. <ul>
<li>ex) 3.1 =&gt; 4, -1.1 =&gt; -1</li>
</ul>
</li>
<li><code>Math.round</code> : 소숫점 첫 째 자리에서 반올림한다. <ul>
<li>ex) 3.1 =&gt; 3, 3.6 =&gt; 4, -1.1 =&gt; -1</li>
</ul>
</li>
<li><code>Math.trunc</code> : 소수부를 무시한다. <ul>
<li>ex) 3.1 =&gt; 3, -1.1 =&gt; -1 </li>
</ul>
</li>
</ul>
<p>이를 활용해 원하는 숫자를 소숫점 자릿수까지 남길 수 있다.</p>
<p>예를 들어, 1.23456을 1.23로 만들어보자. 두 가지 방식을 사용할 수 있다.</p>
<ol>
<li><p>곱하고 나누기</p>
<pre><code class="language-javascript">let num = 1.23456;

Math.floor( num * 100) / 100 );    // 1.23</code></pre>
</li>
<li><p><code>toFixed(n)</code> 메서드 사용하기. 이 메서드는 소숫점 n번 째 자리까지 가장 가까운 값으로 올리거나 내리고 문자열을 반환해준다. 또, 소수부의 길이가 인수보다 작으면 끝에 0이 추가된다.</p>
<pre><code class="language-javascript">let num = 1.23456;
num.toFixed(2);    // &quot;1.23&quot;
num.toFixed(8); // &quot;1.23456000&quot;

// 단항 연산자 +로 문자열을 숫자형으로 바꾸기
+num.toFixed(2);    // 1.23</code></pre>
</li>
</ol>
<h3 id="부정확한-계산">부정확한 계산</h3>
<p>자바스크립트에서 숫자는 내부적으로 64비트 형식의 IEEE-754로 표현되므로, 숫자를 저장하려면 정확히 64비트가 필요하다. 64비트 중 52비트는 숫자를 저장하는데 사용되고, 11비트는 소숫점 위치를, 1비트는 부호를 저장하는데 사용된다.</p>
<p>숫자가 너무 커져서 64비트 공간을 넘치면 <code>Infinity</code>로 처리된다.</p>
<pre><code class="language-javascript">alert( 1e500 );    // Infinity</code></pre>
<p>또, 정밀도 손실(loss of precision)도 일어난다.</p>
<pre><code class="language-javascript">alert( 0.1 + 0.2 == 0.3 ); // false</code></pre>
<p>왜 이러한 일들이 발생할까?</p>
<p>이유는 숫자가 0과 1로 이루어진 이진수로 변환되어 연속된 메모리 공간에 저장되기 때문이다. 예를 들어, 0.1과 0.2같은 분수는 이진법으로 표현하면 무한 소수가 된다.</p>
<p>이를 해결하기 위해, IEEE-754에서는 가능한 가장 가까운 숫자로 반올림하는 방법을 사용한다. 반올림을 사용하면 어쩔 수 없이 작은 정밀도 손실이 일어나게 된다.</p>
<p>정밀도 손실로 숫자 계산에서 오차가 발생하는 상황을 해결하는 방법은 없을까? 가장 신뢰할 수 있는 방법은 앞 서 배운 <code>toFixed()</code> 메서드를 사용하여 어림수를 만드는 것이다.</p>
<pre><code class="language-javascript">let sum = 0.1 + 0.2;
alert( +sum.toFixed(1) == 0.3 ); // true</code></pre>
<h3 id="isnan과-isfinite">isNaN과 isFinite</h3>
<p>자바스크립트에는 두 종류의 특수 숫자 값이 존재한다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Infinity</code>, <code>-Infinity</code> : 그 어떤 숫자보다 크거나 작은 값</li>
<li><code>NaN</code> : 숫자가 아님을 나타내는 값</li>
</ul>
<p>두 특수 숫자는 숫자형에 속하지만 정상적인 숫자가 아니기 때문에, 특별한 함수 <code>isNaN()</code>과 <code>isFinite()</code>가 존재한다.</p>
<pre><code class="language-javascript">// isNaN(value)    : 인수를 숫자로 변환한 다음 NaN인지 테스트한다.
alert( isNaN(NaN) );    // true
alert( isNaN(&quot;Hi&quot;) );    // true

// isFinite(value) : 인수를 숫자로 변환하고 변환한 숫자가 NaN, Infinity, -Infinity가 아닌, 일반 숫자인 경우 true를 반환한다.
alert( isFinite(&quot;15&quot;) );    // true
alert( isFinite(&quot;Hi&quot;) );    // false
alert( isFinite(Infinity) );    // false</code></pre>
<h3 id="문자열에서-숫자-추출하기">문자열에서 숫자 추출하기</h3>
<p>단항 덧셈 연산자 <code>+</code>와 <code>Number()</code>를 사용하여 숫자형으로 변형할 때, 피연산자가 숫자가 아니면 형 변환이 실패한다.</p>
<pre><code class="language-javascript">alert( +&quot;100px&quot; ); // NaN</code></pre>
<p>실무에서는 100px, 12pt와 같이 숫자와 단위를 함께 쓰는 경우가 흔하다. 따라서, 숫자만 추출하는 방법이 필요한데, 이 때 쓸 수 있는 내장함수가 바로 <code>parseInt</code>와 <code>parseFloat</code>이다.</p>
<p>두 함수는 불가능할 때까지 문자열에서 숫자를 읽는다. 왼쪽부터 숫자를 읽는 도중 오류(숫자가 아닌 값을 부분을 만나면)가 발생하면, 이미 수집된 숫자를 <code>parseInt</code>는 정수로, <code>parseFloat</code>는 부동 소숫점 숫자로 반환한다.</p>
<pre><code class="language-javascript">parseInt(&quot;100px&quot;);    // 100
parseFloat(&quot;12.5em&quot;);    // 12.5
parseInt(&quot;12.3&quot;);    // 12
parseFloat(&quot;12.3.4&quot;);    // 12.3

parseInt(&quot;a123&quot;);    // NaN</code></pre>
<p><code>parseInt(str, radix)</code>의 두 번째 매개변수 <code>radix</code>에 원하는 진수를 지정해 줄 수 있다. 이를 통해, 16진수 문자열, 2진수 문자열 등을 파싱할 수 있다.</p>
<pre><code class="language-javascript">parseInt(&quot;0xff&quot;, 16);    // 255
parseInt(&quot;ff&quot;, 16);    // 255
parseInt(&quot;2n9c&quot;, 36);    // 123456</code></pre>
<h3 id="기타-수학-함수">기타 수학 함수</h3>
<p>자바스크립트에서 제공하는 내장 객체 Math에는 다양한 수학 관련 함수와 상수 프로퍼티가 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Math.random()</code> : 0과 1 사이의 난수를 반환한다.(1은 제외)</li>
<li><code>Math.max(a, b, c, ...)</code>, <code>Math.min(a, b, c, ...)</code> : 인수 중 최대값 혹은 최소값을 반환한다.</li>
<li><code>Math.pow(x, n)</code> : x를 n번 거듭제곱한 값을 반환한다.</li>
</ul>
<pre><code class="language-javascript">Math.random();    // 0.3123123

Math.max(3, 5, -10, 0, 1);    // 5
Math.min(1, 2);    // 1

Math.pow(2, 10);    // 1024</code></pre>
<p>이 외에도 다양한 함수가 있다.</p>
<hr>
<h1 id="문자형과-다양한-메서드와-함수들">문자형과 다양한 메서드와 함수들</h1>
<p>자바스크립트에서 텍스트 형식의 데이터는 길이에 상관없이 문자열 형태로 저장된다.</p>
<p>문자열은 페이지 인코딩 방식과 상관없이 항상 UTF-16 형식을 따른다.</p>
<h3 id="문자열을-감싸는-따옴표의-종류">문자열을 감싸는 따옴표의 종류</h3>
<blockquote>
</blockquote>
<ul>
<li><code>&quot;&quot;</code> : 큰 따옴표</li>
<li><code>&#39;&#39;</code> : 작은 따옴표</li>
<li><code>``</code>: 백틱</li>
</ul>
<p>위의 따옴표들은 모두 문자열을 만들 수 있다. 다만, 백틱은 조금 더 특별한 기능이 추가되어 있다. 백틱은 표현식을 <code>${}</code>로 감싸서 문자열 안에 삽입할 수 있게 해준다. 또, 문자열을 여러 줄에 걸쳐서 작성할 수 있다. 이러한 방식을 템플릿 리터럴(template literal)이라 한다.</p>
<pre><code class="language-javascript">// 백틱 문자열 중간에 표현식 넣기
alert( `1 + 2 = ${1 + 2}` );    // 1 + 2 = 3

// 백틱을 사용해서 문자열을 여러 줄에 작성하기
let guestList = `손님:
    John
    Pete
    Mary
`;</code></pre>
<h3 id="제어-문자">제어 문자</h3>
<p>자바스크립트에는 이스케이프 문자<code>\</code>로 시작하는 다양한 제어 문자들이 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>\n</code> : 줄 바꿈</li>
<li><code>\r</code> : 캐리지 리턴. 단독으로 사용하는 경우는 없음.</li>
<li><code>\&#39;, \&quot;</code> : 따옴표</li>
<li><code>\\</code> : 역슬래시</li>
<li><code>\t</code> : 탭</li>
<li><code>\xXX</code> : 16진수 유니코드 <code>XX</code>로 표현한 유니코드 글자</li>
<li><code>\uXXXX</code> : UTF-16 인코딩 규칙을 사용하는 16진수 코드 <code>XXXX</code>로 표현한 유니코드 기호</li>
<li><code>\u{XXXXX...}</code> : UTF-32로 표현한 유니코드 기호</li>
</ul>
<p>위 제어문자들의 예시를 살펴 보자.</p>
<pre><code class="language-javascript">// 줄 바꿈 제어문자 예시
let str1 = &quot;Hello\nWorld&quot;;    // Hello
                            // World

// 유니코드 제어문자 예시
alert( &quot;\u00A9&quot; );    // ©
alert( &quot;\u{1F60D}&quot; );    // 😍

// 따옴표 제어 문자
alert( &#39;I\&#39;m the Walrus&#39; );    // I&#39;m the Walrus</code></pre>
<h3 id="문자열의-길이를-나타내는-프로퍼티">문자열의 길이를 나타내는 프로퍼티</h3>
<pre><code class="language-javascript">alert( `My\n`.length );    // 3</code></pre>
<h3 id="인덱스를-활용해-문자열의-특정-글자에-접근하기">인덱스를 활용해 문자열의 특정 글자에 접근하기</h3>
<p>문자열의 특정 글자에는 인덱스와 대괄호 혹은 <code>str.charAt()</code> 메서드로 접근할 수 있다. 위치는 0부터 시작한다.</p>
<pre><code class="language-javascript">let str = &quot;Hello&quot;;

// 대괄호 사용
str[0];    // H
str[1000];    // 반환할 글자가 없으면 undefined 반환

// 메서드 사용
str.charAt(0);    // H
str.charAt(1000);    // 반환할 글자가 없으면 &quot;&quot; 빈 문자열 반환</code></pre>
<p><code>for of</code> 반복문을 통해 문자열을 구성하는 글자들을 대상으로 반복 작업을 할 수 있다.</p>
<pre><code class="language-javascript">for (let char of &quot;hello&quot;) {
  alert(char);    // h, e, l, l, o
}</code></pre>
<h3 id="문자열의-불변성">문자열의 불변성</h3>
<p>문자열은 수정할 수 없다. 수정을 하려고 하면 에러가 발생한다.</p>
<p>수정하고싶다면 수정하고 싶은 부분을 포함한 새로운 문자열을 만들어 사용할 수 있다.</p>
<pre><code class="language-javascript">let str = &quot;Hi&quot;;

str[0] = &quot;h&quot;;    // Cannot assign to read only property &#39;0&#39; of string &#39;Hi&#39;

str = &quot;h&quot; + str[1];
alert( str );    // hi</code></pre>
<h3 id="문자열의-대소문자를-변경하는-메소드">문자열의 대소문자를 변경하는 메소드</h3>
<p><code>toLowerCase()</code>와 <code>toUpperCase()</code> 메소드를 사용해서, 문자열의 대소문자를 변경할 수 있다. 인덱스를 활용해 지정한 글자 하나만 바꾸어 해당 글자를 반환할 수도 있다.</p>
<pre><code class="language-javascript">&quot;Interface&quot;.toUpperCase();    // INTERFACE
&quot;Interface&quot;.toLowerCase();    // interface
&quot;Interface&quot;[0].toLowerCase();    // i</code></pre>
<h3 id="부분-문자열substring-찾기">부분 문자열(substring) 찾기</h3>
<p>자바스크립트에서 부분 문자열을 찾는 방법은 여러 가지이다.</p>
<h4 id="indexof-메서드">indexOf() 메서드</h4>
<p>첫 번째 방법은 <code>str.indexOf(substr)</code> 메서드이다. 해당 메서드는 부분 문자열<code>substr</code>이 어디에 위치하는지 찾아준다. 원하는 부분 문자열을 찾으면 위치를 반환하고, 못찾으면 -1을 반환한다. 원하는 부분 문자열이 여러 개이면 가장 먼저 발견한 위치를 반환한다.</p>
<p>또, 두 번째 매개변수 <code>pos</code>를 사용하여, 검색을 해당 <code>pos</code>위치 부터 시작할 수도 있다.</p>
<pre><code class="language-javascript">let str = &quot;Widget with id&quot;;

str.indexOf(&quot;Widget&quot;);    // 0
str.indexOf(&quot;widget&quot;);    // -1
str.indexOf(&quot;id&quot;);    // 1

str.indexOf(&quot;id&quot;, 2);    // 12. 반환되는 인덱스는 처음부터이다.</code></pre>
<p>전체 부분 문자열을 대상으로 무언가를 하고 싶다면 반복문 안에서 <code>indexOf</code>를 사용하면 된다.</p>
<pre><code class="language-javascript">let str = &quot;As sly as a fox, as strong as an ox&quot;;

let target = &quot;as&quot;;

let pos = 0;
while (true) {
  let foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( `위치: ${foundPos}` );

  pos = foundPos + 1;
}

// 짧은 코드 버전
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
  alert( `위치: ${pos}` );
}</code></pre>
<p><code>str.lastIndexOf(substr, pos)</code> 메서드도 있다. 위 메소드와 동일하게 동작하지만, 문자열 끝에서부터 부분 문자열을 찾는다는 점만 다르다.</p>
<h4 id="includes-메서드">includes() 메서드</h4>
<p><code>str.includes(substr, pos)</code> 메서드는 문자열에 부분 문자열 <code>substr</code>이 있으면 <code>true</code>, 없으면 <code>false</code>를 반환한다.</p>
<p>부분 문자열의 위치 정보보단, 포함여부를 알고 싶을 때 사용하는 메서드이다.</p>
<pre><code class="language-javascript">&quot;Widget with id&quot;.includes(&quot;Widget&quot;);    // true
&quot;Hello&quot;.includes(&quot;Bye&quot;);    // false

// 인덱스를 두 번째 매개변수 pos에 넘겨, 해당 위치부터 부분 문자열을 검색
&quot;Widget&quot;.includes(&quot;id&quot;, 3);    // false</code></pre>
<h4 id="startswith와-endswith-메서드">startsWith()와 endsWith() 메서드</h4>
<p><code>str.startsWith()</code>와 <code>str.endsWith()</code>는 메서드 이름 그대로, 문자열이 매개변수로 넘긴 부분 문자열로 시작하는지 혹은 끝나는지 여부를 확인할 때 사용한다.</p>
<pre><code class="language-javascript">&quot;Widget&quot;.startsWith(&quot;Wid&quot;);    // true
&quot;Widget&quot;.endsWith(&quot;get&quot;);    // true</code></pre>
<h3 id="부분-문자열-추출하기">부분 문자열 추출하기</h3>
<p>자바스크립트에는 부분 문자열 추출과 관련된 세 가지 메서드가 있다.</p>
<h4 id="slice-메서드">slice() 메서드</h4>
<p><code>str.slice(start, [, end])</code>는 인덱스로 지정한 <code>start</code>부터 <code>end-1</code> 까지의 부분 문자열을 반환한다.</p>
<p>두 번째 인수가 생략된 경우, 명시한 위치부터 문자열 끝까지를 반환한다.</p>
<pre><code class="language-javascript">let str = &quot;stringfy&quot;;
str.slice(0, 5);    // strin
str.slice(0, 1);    // s

str.slice(2);    // ringfy</code></pre>
<h4 id="substring-메서드">substring() 메서드</h4>
<p><code>str.substring(start, [, end]</code> 메서드는 <code>start</code>와 <code>end</code>사이에 있는 문자열을 반환한다. <code>start</code>가 <code>end</code> 보다 커도 된다.</p>
<pre><code class="language-javascript">let str = &quot;stringfy&quot;;

str.substring(2, 6);    // ring
str.substring(6, 2);    // ring</code></pre>
<h3 id="문자열-비교하기">문자열 비교하기</h3>
<p>문자열을 비교할 때는 알파벳 순서를 기준으로 글자끼리 비교가 이뤄지며, 다음의 규칙들을 따른다.</p>
<blockquote>
</blockquote>
<ul>
<li>소문자는 대문자보다 항상 크다.</li>
<li>발음 구별 기호가 붙은 문자는 알파벳 순서 기준을 따르지 않는다.</li>
</ul>
<p>모든 문자열은 UTF-16을 사용해서 인코딩되는데, 모든 문자가 숫자 형식의 코드와 매칭된다. </p>
<p><code>str.codePointAt(pos)</code> 메서드를 문자열에 사용해서 인덱스에 위치한 문자의 코드를 반환한다.</p>
<pre><code class="language-javascript">&quot;z&quot;.codePointAt(0);    // 122
&quot;Z&quot;.codePointAt(0);    // 90</code></pre>
<p>반대로, <code>fromCodePoint(code)</code> 메서드를 사용해서 코드에 대응하는 글자를 만들 수 있다.</p>
<pre><code class="language-javascript">String.fromCodePoint(90);    // Z</code></pre>
<hr>
<h1 id="배열array">배열(array)</h1>
<blockquote>
</blockquote>
<p>데이터를 순서대로 저장할 때 쓰는 자료구조이다.</p>
<h3 id="배열-선언">배열 선언</h3>
<p>두 가지 문법을 사용해서 배열을 만들 수 있다.</p>
<pre><code class="language-javascript">// 배열 생성자를 사용해서 빈 배열 선언
let arr = new Array();

// 배열 생성자를 사용해서 배열 선언 및 초기화
let arr = new Array(&quot;사과&quot;, &quot;배&quot;, &quot;기타&quot;);

// 인수로 숫자를 넣으면 해당 길이를 가지는 빈 배열을 만든다.
let arr = new Array(2);

alert( arr[0] );    // undefined
alert( arr.length );    // 2

// 대괄호를 사용해서 빈 배열 선언
let arr = [];

// 대괄호를 사용해서 배열 선언 및 초기화
let fruits = [&quot;사과&quot;, &quot;오렌지&quot;, &quot;자두&quot;];</code></pre>
<h3 id="인덱스를-사용한-배열-사용-접근-수정-추가">인덱스를 사용한 배열 사용 접근, 수정, 추가</h3>
<p>각 배열 요소에는 0부터 시작하는 인덱스가 매겨져 있다. 이 인덱스를 통해서 다음과 같은 동작을 할 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>배열 내 특정 요소에 접근</li>
<li>특정 요소 수정</li>
<li>배열에 새로운 요소 추가</li>
</ul>
<pre><code class="language-javascript">let fruits = [&quot;사과&quot;, &quot;오렌지&quot;, &quot;자두&quot;];

// 요소 접근
fruits[0];    // 사과
fruits[1];    // 오렌지
fruits[2];    // 자두

// 요소 수정
fruits[2] = &quot;배&quot;;

// 새로운 요소 추가
fruits[3] = &quot;레몬&quot;;</code></pre>
<h3 id="length-프로퍼티">length 프로퍼티</h3>
<p><code>length</code> 프로퍼티를 통해 배열의 길이를 알아낼 수 있다. 배열의 가장 큰 인덱스에 1을 더하면 된다.</p>
<pre><code class="language-javascript">let fruits = [&quot;사과&quot;, &quot;오렌지&quot;, &quot;자두&quot;];

fruits.length;    // 3

let fruits = [];

fruits[123] = &quot;사과&quot;;
fruits.length;    // 124</code></pre>
<p>배열에 무언가 조작을 하면 <code>length</code> 프로퍼티가 자동으로 갱신된다.</p>
<p><code>length</code> 프로퍼티를 활용해서 배열을 초기화할 수 있다. <code>length</code>  값을 수동으로 증가시키면 아무 일도 일어나지 않지만, 값을 감소시키면 배열이 잘린다. 잘려진 배열은 다시 되돌릴 수 없다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];

arr.length = 2;
arr;    // [1, 2]

arr.length = 5;
arr[3];    // undefined

// length 프로퍼티를 활용한 배열 초기화
arr.length = 0;</code></pre>
<h3 id="배열-요소의-자료형에는-제약이-없다">배열 요소의 자료형에는 제약이 없다</h3>
<p>문자열, 숫자, 불리언, 객체, 배열, 함수 등 다양한 데이터를 담을 수 있다.</p>
<pre><code class="language-javascript">let arr = [ &quot;사과&quot;, { name: &quot;용&quot; }, true, function() { alert(&quot;안녕하세요&quot;); } ];

alert( arr[1].name );    // 용

arr[3]();    // 안녕하세요</code></pre>
<h3 id="배열에-요소를-추가-혹은-삭제하는-pop-push-shift-unshift-메서드">배열에 요소를 추가 혹은 삭제하는 pop(), push(), shift(), unshift() 메서드</h3>
<blockquote>
</blockquote>
<ul>
<li><code>pop()</code> : 배열 끝의 요소를 제거하고, 제거한 요소를 반환한다.</li>
<li><code>push()</code> : 배열 끝의 요소를 추가한다. 요소 여러 개를 한 번에 추가할 수 있다.</li>
<li><code>shift()</code> : 배열 앞 요소를 제거하고, 제거한 요소를 반환한다.</li>
<li><code>unshift()</code> : 배열 앞에 요소를 추가한다. 요소 여러 개를 한 번에 추가할 수 있다.</li>
</ul>
<pre><code class="language-javascript">let fruits = [&quot;사과&quot;, &quot;오렌지&quot;, &quot;배&quot;];

// pop
fruits.pop();    // 배
fruits    // 사과, 오렌지

// push
fruits.push(&quot;배&quot;);
fruits    // 사과, 오렌지, 배

// shift
fruits.shift();    // 사과
fruits    // 오렌지, 배

// unshift
fruits.unshift(&quot;사과&quot;);
fruits    // 사과, 오렌지, 배

// 여러 개 요소 push, unshift
let fruits = [&quot;사과&quot;];

fruits.push(&quot;오렌지&quot;, &quot;배&quot;);
fruits.unshift(&quot;파인애플&quot;, &quot;레몬&quot;);
fruits    // 파인애플, 레몬, 사과, 오렌지, 배</code></pre>
<h3 id="배열의-내부-동작-원리">배열의 내부 동작 원리</h3>
<p>배열은 특별한 종류의 객체이다. 배열에 <code>typeof</code> 연산자를 사용하면 객체형을 반환한다. </p>
<p>객체에 대괄호와 키를 사용해서 프로퍼티에 접근하는 것처럼, 배열도 대괄호와 인덱스를 사용해서 요소에 접근할 수 있다.</p>
<p>또, 객체가 참조에 의해 복사를 하는 것처럼 배열도 참조에 의한 복사를 한다.</p>
<pre><code class="language-javascript">let fruits = [&quot;바나나&quot;];

let arr = fruits;

arr === fruits;    // true

arr.push(&quot;배&quot;);
alert( fruits ); // 바나나, 배</code></pre>
<p>자바스크립트 엔진은 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높인다. 이러한 점이 일반 객체와 다른다.</p>
<h3 id="배열-순회">배열 순회</h3>
<p><code>for</code> 반복문은 배열을 순회할 때 쓰는 가장 기본적인 방법이다.</p>
<pre><code class="language-javascript">let arr = [&quot;사과&quot;, &quot;오렌지&quot;, &quot;배&quot;];

for (let i = 0; i &lt; arr.length; i++) {
  alert( arr[i] );
}</code></pre>
<p><code>for of</code> 반복문으로도 순회할 수 있다.</p>
<pre><code class="language-javascript">for (let fruit of fruits) {
  alert(fruit);
}</code></pre>
<h3 id="다차원-배열">다차원 배열</h3>
<p>배열의 요소로 또 다른 배열을 넣을 수 있다. 이러한 배열을 다차원 배열이라 부른다. 보통, 행렬을 저장하는 용도로 쓰인다.</p>
<pre><code class="language-javascript">let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

alert( matrix[1][1] );    // 5</code></pre>
<h3 id="tostring-메서드">toString 메서드</h3>
<p>배열에는 <code>toString</code> 메서드가 구현되어 있어, 이를 호출하면 요소를 쉼표로 구분한 문자열이 반환된다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 3];

alert( String(arr) );    // &quot;1,2,3&quot;</code></pre>
<h3 id="배열-요소-추가--삭제-메서드-심화">배열 요소 추가 / 삭제 메서드 심화</h3>
<p><code>push</code>, <code>pop</code>, <code>shift</code>, <code>unshift</code> 메서드 이외에 배열 요소 추가와 삭제 메서드가 있다.</p>
<h4 id="splice-메서드">splice() 메서드</h4>
<p><code>arr.splice(index, [deleteCount, elem1, ...])</code> 메서드는 기본적으로 요소를 삭제하는 메서드이지만, 요소 추가와 교체가 모두 가능한 메서드 이다.</p>
<p>첫 번째 매개변수 <code>index</code>는 조작을 가할 첫 번째 요소를 가리키는 인덱스이다. 두 번째 매개변수 <code>deletecount</code>는 제거하고자 하는 요소의 개수를 나타낸다. <code>elem1, ...</code>는 배열에 추가할 요소를 나타낸다.</p>
<p><code>splice</code>는 삭제된 요소로 구성된 배열을 반환한다.</p>
<p><code>deleteCount</code>를 0으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있다.</p>
<pre><code class="language-javascript">// 요소 한 개 제거
let arr = [&quot;I&quot;, &quot;study&quot;, &quot;JavaScript&quot;];
arr.splice(1, 1);
arr;    // [&quot;I&quot;, &quot;JavaScript&quot;]

// 요소 여러 개 제거 후 요소 대체
let arr = [&quot;I&quot;, &quot;study&quot;, &quot;JavaScript&quot;, &quot;right&quot;, &quot;now&quot;];
arr.splice(0, 3, &quot;Let&#39;s&quot;, &quot;dance&quot;);
arr;    // [&quot;Let&#39;s&quot;, &quot;dance&quot;, &quot;right&quot;, &quot;now&quot;]

// 삭제된 요소 반환
let arr = [&quot;I&quot;, &quot;study&quot;, &quot;JavaScript&quot;, &quot;right&quot;, &quot;now&quot;];
let removed = arr.splice(0, 2);
removed;    // [&quot;I&quot;, &quot;study&quot;]

// 요소 제거하지 않고, 요소 추가
let arr = [&quot;I&quot;, &quot;study&quot;, &quot;JavaScript&quot;];
arr.splice(2, 0, &quot;complex&quot;, &quot;language&quot;);
arr;    // [&quot;I&quot;, &quot;study&quot;, &quot;complex&quot;, &quot;language&quot;, &quot;JavaScript&quot;]</code></pre>
<h4 id="slice-메서드-1">slice() 메서드</h4>
<p><code>arr.slice([start], [end])</code> 메서드는 <code>start</code> 인덱스부터 <code>end - 1</code> 인덱스까지의 요소를 복사한 새로운 배열을 반환한다.</p>
<p>인수를 넘기지 않으면, 배열의 복사본을 만들 수 있다.</p>
<pre><code class="language-javascript">let arr = [&quot;t&quot;, &quot;e&quot;, &quot;s&quot;, &quot;t&quot;];

alert( arr.slice(1, 3) );    // [&quot;e&quot;, &quot;s&quot;]
alert( arr.slice(-2) );    // [&quot;s&quot;, &quot;t&quot;]
alert( arr.slice() );    // [&quot;t&quot;, &quot;e&quot;, &quot;s&quot;, &quot;t&quot;]</code></pre>
<h4 id="concat-메서드">concat() 메서드</h4>
<p><code>arr.concat(arg1, ...)</code> 메서드는 기존 배열에 요소를 추가해서 새로운 배열을 반환한다. 인수에는 배열이나 값이 올 수 있다.</p>
<pre><code class="language-javascript">let arr = [1, 2];

arr.concat([3, 4]);    // [1, 2, 3, 4]
arr.concat([3, 4], [5, 6]);    // [1, 2, 3, 4, 5, 6]
arr.concat([3, 4], 5, 6);    // [1, 2, 3, 4, 5, 6]</code></pre>
<p><code>concat()</code> 메서드는 객체가 인자로 넘어오면 객체는 분해되지 않고 통으로 복사된다. 그런데 인자로 받은 객체에 <code>Symbol.isConcatSpreadable</code> 프로퍼티가 있으면 이 객체를 배열처럼 취급한다. 따라서, 객체 전체가 아닌 객체 프로퍼티의 값이 더해진다.</p>
<pre><code class="language-javascript">let arr = [1, 2];

let arrayLike = {
  0: &quot;something&quot;,
  length: 1
};

arr.concat(arrayLike);    // [1, 2, {0: &quot;something&quot;, length: 1}]

let arrayLike = {
  0: &quot;something&quot;,
  1: &quot;else&quot;,
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

arr.concat(arrayLike);    // [1, 2, &quot;something&quot;, &quot;else&quot;]</code></pre>
<h3 id="배열-요소에-반복작업-하기">배열 요소에 반복작업 하기</h3>
<h4 id="foreach-메서드">forEach() 메서드</h4>
<p><code>arr.forEach(callback)</code>는 콜백함수를 사용해서, 배열 요소 각각에 대해 해당 콜백의 동작을 실행한다.</p>
<pre><code class="language-javascript">arr.forEach(function(item, index, array) {
  // ...
});

// 배열 각 요소에 대해 alert
[&quot;Bilbo&quot;, &quot;Gandalf&quot;, &quot;Nazgul&quot;].forEach(alert);

// 인덱스와 배열에 대한 정보까지 더해서 출력
[&quot;Bilbo&quot;, &quot;Gandalf&quot;, &quot;Nazgul&quot;].forEach((item, index, array) =&gt; {
  alert(`${item} is at index ${index} in ${array}`);
});</code></pre>
<p>참고로 인수로 넘겨준 콜백 함수의 반환값은 무시된다.</p>
<h4 id="map-메서드">map() 메서드</h4>
<p><code>map(callback)</code> 메서드는 배열 요소 전체를 대상으로 함수를 호출하고, 콜백 함수 호출 결과를 배열로 반환해준다.</p>
<pre><code class="language-javascript">// 문법
let result = arr.map(function(item, index, array) {});

// 각 요소의 길이를 출력하는 예시
let lengths = [&quot;Bilbo&quot;, &quot;Gandalf&quot;, &quot;Nazgul&quot;].map(
  item =&gt; item.length
);
lengths;    // [5, 7, 6]</code></pre>
<h4 id="reduce와-reduceright-메서드">reduce()와 reduceRight() 메서드</h4>
<p>배열의 각 요소를 대상으로 반복 작업을 해서 값 하나를 도출할 때 주로 사용된다.</p>
<pre><code class="language-javascript">let value = arr.reduce(function(accumulator, item, index, array) {
  // accumulater : 이전 함수 호출 결과
  // item : 현재 배열 요소
  // index : 요소의 위치
  // array : 배열
}, [initial]);
  // initial(옵션) : 함수 최초 호출 시 사용되는 accumulator의 초기값</code></pre>
<p>인수로 넘겨주는 콜백 함수는 배열의 모든 요소를 대상으로 차례차례 적용되는데, 적용 결과는 다음 함수 호출 시에도 사용된다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) =&gt; sum + current, 0);
result;    // 15</code></pre>
<p><code>arr.reduceRight</code> 메서드는 동일한 기능을 하지만, 배열의 오른쪽부터 연산을 수행한다.</p>
<h3 id="배열-탐색하기">배열 탐색하기</h3>
<p>배열 내에서 무언가를 찾고 싶을 때 쓰는 메서드를 알아보자.</p>
<h4 id="indexof-lastindexof-includes-메서드">indexOf(), lastIndexOf(), includes() 메서드</h4>
<blockquote>
</blockquote>
<ul>
<li><code>arr.indexOf(item, from)</code> : 배열에 인덱스 <code>from</code> 부터 시작해서 <code>item</code>을 찾는다. 찾으면 해당 요소의 인덱스를 반환하고, 발견하지 못하면 -1을 반환한다.</li>
<li><code>arr.lastIndexOf(item, from)</code> : 검색을 배열 끝에서부터 시작한다.</li>
<li><code>arr.includes(item, from)</code> : 배열에 인댁스 <code>from</code>부터 시작해서 <code>item</code>이 있는지 검사하고, 해당 요소를 발견하면 true를 반환한다.</li>
</ul>
<p>위 메서드들은 요소를 찾을 때 일치 연산자 <code>===</code>를 사용한다.</p>
<pre><code class="language-javascript">let arr = [1, 0, false];

arr.indexOf(0);    // 1
arr.indexOf(false);    // 2
arr.indexOf(null);    // -1

arr.includes(1);    // true</code></pre>
<h4 id="find-findindex-메서드">find(), findIndex() 메서드</h4>
<pre><code class="language-javascript">arr.find(function(item, index, array) {
      // item : 함수를 호출하는 요소
      // index : 요소의 인덱스
      // array : 메서드를 호출한 배열
      // ...
});</code></pre>
<p>배열의 각 요소마다 콜백을 호출한다. 콜백 결과로 true가 반환되면 반복이 멈추고, 해당 요소를 반환한다. 조건에 해당하는 요소가 없으면 undefined를 반환한다.</p>
<pre><code class="language-javascript">let users = [
  {id: 1, name: &quot;John&quot;},
  {id: 2, name: &quot;Pete&quot;},
  {id: 3, name: &quot;Mary&quot;}
];

let user = users.find(item =&gt; item.id == 1);
user.name;    // John</code></pre>
<p><code>arr.findIndex()</code> 메서드는 동일한 일을 하나, 조건에 맞는 요소를 발견하면 해당 요소의 인덱스를 반환한다. 조건에 맞는 요소가 없으면 -1이 반환된다.</p>
<h4 id="filter-메서드">filter() 메서드</h4>
<p><code>filter(callback)</code> 메서드는 조건을 충족하는 요소를 여러 개 찾아서 배열로 반환해준다.</p>
<pre><code class="language-javascript">let results = arr.filter(function(item, index, array) {});</code></pre>
<p>조건을 충족하는 요소가 배열에 순차적으로 더해진다. 조건을 충족하는 요소가 없으면 빈 배열이 반환된다.</p>
<pre><code class="language-javascript">let users = [
  {id: 1, name: &quot;John&quot;},
  {id: 2, name: &quot;Pete&quot;},
  {id: 3, name: &quot;Mary&quot;}
];

let someUsers = users.filter(item =&gt; item.id &lt; 3);
someUsers;    // [ {id: 1, name: &quot;John&quot;}, {id: 2, name: &quot;Pete&quot;} ]</code></pre>
<h3 id="배열을-변형하거나-요소를-재정렬하는-메서드">배열을 변형하거나 요소를 재정렬하는 메서드</h3>
<h4 id="sort-메서드">sort() 메서드</h4>
<p>배열의 요소를 정렬해주는 메서드이다. 배열 자체가 변경된다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 15];

arr.sort();
arr;    // [1, 15, 2];</code></pre>
<p>오름차순 결과를 기대했지만, 결과가 예상과 다르게 나왔다. 이유는 뭘까?</p>
<p><code>sort()</code> 메서드에서 요소는 문자열로 취급되어 재정렬되기 때문이다.</p>
<p>기본적으로 정렬되는 기준 대신 새로운 정렬 기준을 만들려면 <code>arr.sort([callback])</code> 콜백 함수로 정렬 기준이 되는 함수를 넘겨주면 된다. 인수로 넘겨주는 함수는 반드시 값 두개를 비교해야 하고, 반환값도 있어야 한다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 15];
arr.sort((a, b) =&gt; {
  if (a &gt; b) return 1;
  if (a == b) return 0;
  if (a &lt; b) return -1;
});

arr;    // [1, 2, 15]</code></pre>
<p>콜백 함수의 반환 값에는 제약이 없다. 첫 번째 인수가 두 번째 인수보다 크다를 나타내려면 양수를, 첫 번째 인수가 두 번째 인수보다 작다를 나타내려면 음수를 반환하면 된다. 이 점을 활용해서 위 콜백 함수를 더 간결하게 만들 수 있다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 15];
arr.sort((a, b) =&gt; a - b);
arr;    // [1, 2, 15]</code></pre>
<h4 id="reverse-메서드">reverse() 메서드</h4>
<p><code>arr.reverse()</code> 메서드는 배열의 요소를 역순으로 정렬시켜주는 메서드이다.</p>
<pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
arr.reverse();
arr;    // [5, 4, 3, 2, 1]</code></pre>
<h4 id="spilt과-join-메서드">spilt()과 join() 메서드</h4>
<p><code>str.split(구분자)</code> 메서드를 이용하면 구분자를 기준으로 문자열을 쪼개고 각각 배열의 요소를 집어넣어, 배열을 반환해준다.</p>
<p>두 번째 인수로 숫자를 전달해서, 반환받는 배열의 길이를 제한할 수 있다.</p>
<pre><code class="language-javascript">let names = &quot;Bilbo, Gandalf, Nazgul&quot;;
let arr = names.split(&quot;, &quot;);

arr;    // [&quot;Bilbo&quot;, &quot;Gandalf&quot;, &quot;Nazgul&quot;]

let arr = &#39;Bilbo, Gandalf, Nazgul, Saruman&#39;.split(&#39;, &#39;, 2);
arr;    // [&quot;Bilbo&quot;, &quot;Gandalf&quot;]</code></pre>
<p><code>arr.join(구분자)</code> 메서드를 사용하면 구분자를 사용해서, 배열 요소를 모두 합친 후 하나의 문자열로 반환한다.</p>
<pre><code class="language-javascript">let arr = [&quot;Bilbo&quot;, &quot;Gandalf&quot;, &quot;Nazgul&quot;];
let str = arr.join(&quot;;&quot;);
str;    // Bilbo;Gandalf;Nazgul</code></pre>
<h3 id="arrayisarray-메서드로-배열-여부-알아내기">Array.isArray() 메서드로 배열 여부 알아내기</h3>
<p>자바스크립트에서 배열은 객체형에 속하므로, <code>typeof</code> 연산자로는 일반 객체인지 배열인지 구분할 수 없다.</p>
<p>이 때, 사용할 수 있는 메서드가 <code>Array.isArray(value)</code>이다. <code>value</code>가 배열이면 <code>true</code>, 배열이 아니면 <code>false</code>를 반환해준다.</p>
<pre><code class="language-javascript">Array.isArray({});    // false
Array.isArray([]);    // true</code></pre>
<h3 id="배열-메서드와-thisarg">배열 메서드와 thisArg</h3>
<p><code>sort</code>를 제외한 <code>find</code>, <code>filter</code>, <code>map</code> 등 함수를 호출하는 대부분의 배열 메서드는 <code>thisArg</code>라는 매개변수를 옵션으로 받을 수 있다.</p>
<p><code>thisArg</code>는 콜백 함수의 <code>this</code>가 된다.</p>
<pre><code class="language-javascript">let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age &gt;= this.minAge &amp;&amp; user.age &lt; this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

// army.canJoin 호출 시 참을 반환해주는 user를 찾음
let soldiers = users.filter(army.canJoin, army);

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23</code></pre>
<hr>
<h1 id="반복가능한-객체-이터러블iterable">반복가능한 객체, 이터러블(iterable)</h1>
<p>이터러블은 배열을 일반화한 객체이다.</p>
<p>이터러블이라는 개념을 사용하면, 어떤 객체에든 <code>for of</code> 반복문을 적용할 수 있다. 반대로 말해서 <code>for of</code>를 사용할 수 있는 객체는 이터러블이다.</p>
<p>배열이 대표적인 이터러블이다. 배열 외에도 다수의 내장 객체가 반복 가능하다. 문자열 역시 이터러블의 한 예이다.</p>
<p>배열이 아닌 객체가 있는데, 이 객체가 어떤 것들의 컬렉션을 나타내고 있는 경우에는 <code>for of</code> 문법을 적용할 수만 있다면 해당 컬렉션을 순회하는데 유용할 것이다. </p>
<p>따라서, 해당 객체를 이터러블로 만들 수 있는 방법을 살펴볼 것이다.</p>
<h3 id="객체를-이터러블로-만드는-과정">객체를 이터러블로 만드는 과정</h3>
<p>객체를 이터러블로 만드는 과정을 다음과 같다.</p>
<blockquote>
</blockquote>
<ol>
<li><code>for of</code>를 적용하기에 적합해 보이는 객체를 만든다.</li>
<li>해당 객체에 <code>Symbol.iterator</code> 메서드(심볼형 프로퍼티)를 추가한다.</li>
</ol>
<p><code>Symbol.iterator</code> 메서드를 추가한 이터러블에 <code>for of</code>를 호출했을 때 발생하는 내부 동작을 살펴보자.</p>
<blockquote>
</blockquote>
<ol>
<li><code>for of</code>가 시작되면, <code>for of</code>는 객체의 <code>Symbol.iterator</code> 메서드를 호출한다. <ul>
<li>객체에 해당 특수 내장 심볼이 없으면 에러가 발생한다. </li>
<li><code>Symbol.iterator</code> 메서드는 반드시 <code>next</code> 메서드가 있는 객체 <code>iterator</code>를 반환해야 한다.</li>
</ul>
</li>
<li>이후 <code>for of</code>는 객체 <code>iterator</code>만을 대상으로 동작한다.</li>
<li><code>for of</code>에 다음 값이 필요하면, <code>for of</code>는 <code>iterator</code>의 <code>next</code> 메서드를 호출한다.</li>
<li><code>next</code> 메서드의 반환 값은 <code>{done: Boolean, value: any}</code>와 같은 형태여야 한다. <code>done</code>이 <code>true</code>이면 반복이 종료되었음을 의미하고, <code>false</code>이면 <code>value</code>에 다음 값이 저장된다.</li>
</ol>
<p>직접 이터러블을 만들어보자. 이터러블이 아닌 객체 <code>range</code>를 이터러블로 만들어주는 코드는 다음과 같다.</p>
<pre><code class="language-javascript">let range = {
  from: 1,
  to: 5
};

// 1. for of 최초 호출 시, Symbol.iterator가 호출된다.
range[Symbol.iterator] = functuon() {
  // Symbol.iterator는 객체 iterator를 반환한다.
  // 2. 이후 for of는 반환된 iterator만을 대상으로 동작한다
  return { 
    current: this.from,
    last: this.to,

    // 3. for of 반복문에 의해 반복마다 next() 메서드가 호출된다.
    next() {
      // 4. next() 메서드는 값을 객체 {done:boolean, value:any} 형태로 반환한다.
      if (this.current &lt;= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

for (let num of range) {
  alert(num);    // 1, 2, 3, 4, 5
}</code></pre>
<p>이터러블의 핵심은 <code>관심사의 분리(Separation of Concern, SoC)</code>에 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>이터러블에는 메서드 <code>next()</code> 가 없다.</li>
<li>대신에 <code>Symbol.iterator()</code>를 호출해서 만든 객체 <code>iterator</code>와 해당 객체의 메서드 <code>next()</code>에서 반복에 사용될 값을 만들어낸다.</li>
</ul>
<p>이러한 방식으로 <code>iterator</code> 객체와 이터러블을 분리할 수 있다.</p>
<p><code>iterator</code> 객체와 이터러블을 합쳐서 이터러블 자체를 <code>iterator</code>로 만들면 코드가 더 간단해진다.</p>
<pre><code class="language-javascript">let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.form;
    return this;
  },

  next() {
    if (this.current &lt;= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num);    // 1, 2, 3, 4, 5
}</code></pre>
<h3 id="문자열은-이터러블이다">문자열은 이터러블이다.</h3>
<p>문자열은 배열과 마찬가지로 가장 광범위하게 쓰이는 내장 이터러블이다.</p>
<p><code>for of</code>는 문자열의 각 글자를 순회한다.</p>
<pre><code class="language-javascript">for (let char of &quot;test&quot;) {
  alert(char);    // t, e, s, t
}</code></pre>
<h3 id="iterator를-명시적으로-호출하기">iterator를 명시적으로 호출하기</h3>
<p><code>for of</code>를 사용했을 때와 동일한 작업을 하지만, 문자열에 <code>iterator</code>를 직접 호출해서 순회해보자. </p>
<p><code>iterator</code>를 명시적으로 호출하는 경우는 거의 없지만, 반복 과정을 더 잘 통제할 수 있다는 장점이 있다. </p>
<p>예를 들어, 반복을 시작했다가 잠시 멈춰 다른 작업을 하다가 다시 반복을 시작하는 것과 같이 반복 과정을 여러 개로 쪼개는 것이 가능하다.</p>
<pre><code class="language-javascript">let str = &quot;Hello&quot;;

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value);
}</code></pre>
<h3 id="이터러블iterable과-유사-배열array-like-객체">이터러블(iterable)과 유사 배열(array-like) 객체</h3>
<p>아래 두 객체는 서로 같은 의미가 아니다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>이터러블</code> : 메서드 <code>Symbol.iterator</code>가 구현된 객체이다.</li>
<li><code>유사 배열 객체</code> : 인덱스와 <code>length</code> 프로퍼티가 있어서 배열처럼 보이는 객체이다.</li>
</ul>
<p>또, 이터러블과 유사 배열은 대개 배열이 아니기 때문에 <code>push</code>, <code>pop</code> 등의 메서드를 지원하지 않는다. 이러한 객체들을 배열처럼 다루고 싶을 때는 어떻게 할까?</p>
<p><code>Array.from(obj, [mapFn, thisArg])</code> 메서드는 이터러블 혹은 유사 배열을 받아 새로운 배열을 만들고, 객체의 모든 요소를 새롭게 만든 배열로 복사한다.</p>
<p>두 번째 인수로 매핑함수를 넘겨주면 새로운 배열에 요소를 추가하기 전에 각 요소를 대상으로 연산을 수행할 수 있다.</p>
<pre><code class="language-javascript">let arrayLike = {
  0: &quot;Hello&quot;,
  1: &quot;World&quot;,
  length: 2
};

let arr = Array.from(arrayLike);
alert( arr.pop() );    // World

let arr = Array.from(range);
arr;    // [1, 2, 3, 4, 5]

let arr = Array.from(range, num =&gt; num * num);
arr;    // [1, 4, 9, 16, 25]</code></pre>
<hr>
<h1 id="map과-set-자료구조">Map과 Set 자료구조</h1>
<h3 id="맵map">맵(map)</h3>
<p>맵은 키가 있는 데이터를 저장한다는 점에서 객체와 유사하지만, 객체와 달리 키를 문자형으로 변환하지 않아 자료형에 제약이 없다는 점이 다르다.</p>
<p>맵에는 다음과 같은 주요 메서드와 프로퍼티가 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>new Map()</code> : 맵을 생성한다.</li>
<li><code>map.set(key, value)</code> : <code>key</code>를 이용해서 <code>value</code>를 저장한다.</li>
<li><code>map.get(key)</code> : <code>key</code>에 해당하는 값을 반환한다. <code>key</code>가 존재하지 않으면 <code>undefined</code>를 반환한다.</li>
<li><code>map.has(key)</code> : 맵에 <code>key</code>가 존재하면 <code>true</code>, 존재하지 않으면 <code>false</code>를 반환한다.</li>
<li><code>map.delete(key)</code> : 맵에서 <code>key</code>에 해당하는 값을 삭제한다.</li>
<li><code>map.clear()</code> : 맵 안의 모든 요소를 제거한다.</li>
<li><code>map.size</code> : 맵이 가진 요소의 개수를 반환한다.</li>
</ul>
<pre><code class="language-javascript">let map = new Map();

map.set(&quot;1&quot;, &quot;str1&quot;);
map.set(1, &quot;num1&quot;);
map.set(true, &quot;bool1&quot;);

map.get(1);    // num1
map.get(&quot;1&quot;);    // str1

map.size;    // 3

// 맵은 키로 객체도 허용한다.
let john = { name: &quot;John&quot; };

let visitsCountMap = new Map();
visitsCountMap.set(john, 123);
visitsCountMap.get(john);    // 123</code></pre>
<p>맵에는 일반 객체처럼 대괄호 표기법을 사용해 키에 접근하거나, 프로퍼티를 추가하는 것은 좋지 않다. 대신에 맵 전용 메서드 <code>set</code>, <code>get</code>을 사용해야한다.</p>
<p>맵은 <code>set</code> 메서드를 호출할 때마다 맵 자신이 반환된다. 이를 활용해서 체이닝을 할 수도 있다.</p>
<pre><code class="language-javascript">map.set(&quot;1&quot;, &quot;str1&quot;).set(1, &quot;num1&quot;).set(true, &quot;bool1&quot;);</code></pre>
<h3 id="배열이나-객체를-맵으로-바꾸기">배열이나 객체를 맵으로 바꾸기</h3>
<p>각 요소가 키-값 쌍인 배열이나 이터러블을 맵에 전달해서 초기화할 수 있다.</p>
<pre><code class="language-javascript">// 배열로 초기화
let map = new Map([
  [&quot;1&quot;, &quot;str1&quot;],
  [1, &quot;num1&quot;],
  [true, &quot;bool1&quot;]
]);

map.get(&quot;1&quot;);    // str1

// 객체로 초기화.
// Object.entries(obj)은 객체를 키-값 쌍을 요소로 가지는 배열로 반환
let obj = {
  name: &quot;John&quot;,
  age: 30
};

let map = new Map(Object.entries(obj));

map.get(&quot;name&quot;);    // John</code></pre>
<h3 id="맵의-요소에-반복-작업하기">맵의 요소에 반복 작업하기</h3>
<p>다음의 메서드를 사용해서 맵의 각 요소에 반복 작업을 할 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>map.keys()</code> : 각 요소의 키를 모아서 이터러블을 반환한다.</li>
<li><code>map.values()</code> : 각 요소의 값을 모아서 이터러블을 반환한다.</li>
<li><code>map.entries()</code> : 각 요소의 키와 값을 한쌍으로 배열로 만들고, 이를 모아서 이터러블로 반환한다.</li>
</ul>
<p>맵은 값이 삽입된 순서대로 순회를 한다.</p>
<p>또, 맵은 배열과 유사하게 내장 메서드 <code>forEach()</code>를 지원한다.</p>
<pre><code class="language-javascript">let recipeMap = new Map([
  [&quot;cucumber&quot;, 500],
  [&quot;tomatoes&quot;, 350],
  [&quot;onion&quot;, 50]
]);

// 키를 대상으로 순회
for (let vegetable of recipeMap.keys()) {
  alert(vegetable);    // cucumber, tomatoes, onion
}

// 값을 대상으로 순회
for (let amount of recipeMap.values()) {
  alert(amount);    // 500, 350, 50
}

// 키-값 쌍을 대상으로 순회
for (let entry of recipeMap.entries()) {
  alert(entry);    // [cucumber, 500] ...
}

// 맵 자체의 순회는 map.entries()와 같은 동작을 한다.
for (let entry of recipeMap) {
  alert(entry);    // [cucumber, 500] ...
}

recipeMap.forEach( (value, key, map) =&gt; {
  alert(`${key}: ${value}`);    // cucumber: 500 ...
});</code></pre>
<h3 id="맵을-객체로-바꾸기">맵을 객체로 바꾸기</h3>
<p>위에서 객체를 맵으로 바꾸는 방법을 알아보았다. </p>
<p>반대로, <code>Object.fromEntries()</code> 메서드를 사용해서 맵을 객체로 바꾸어 보자. 이 메서드는 각 요소가 키-값 쌍인 배열을 객체로 바꾸어준다.</p>
<pre><code class="language-javascript">let map = new Map();
map.set(&quot;banana&quot;, 1);
map.set(&quot;orange&quot;, 2);
map.set(&quot;meat&quot;, 4);

let obj = Object.fromEntries(map.entries());

obj;    // { banana: 1, orange: 2, meat: 4 }

// 같은 동작
let obj = Object.fromEntries(map);

obj;    // { banana: 1, orange: 2, meat: 4 }</code></pre>
<h3 id="셋set">셋(set)</h3>
<p>셋은 중복을 허용하지 않는 값을 모아놓은 컬렉션이다. 셋에는 키가 없고, 값만 저장된다.</p>
<p>셋의 주요 메서드와 프로퍼티는 다음과 같다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>new Set(iterable)</code> : 셋을 만든다. 이터러블을 전달받으면, 그 안의 값을 복사해서 셋에 넣어준다.</li>
<li><code>set.add(value)</code> : 값을 추가하고, 셋 자신을 반환한다. 셋 내에 동일한 값이 있으면 <code>set.add(value)</code>를 호출해도 동작하지 않는다. 셋은 중복값을 허용하지 않기 때문이다.</li>
<li><code>set.delete(value)</code> : 값을 제거한다. 셋에 값이 있어서 제거에 성공하면 <code>true</code>, 실패하면 <code>false</code>를 반환한다.</li>
<li><code>set.has(value)</code> : 셋 내에 <code>value</code>가 존재하면 <code>true</code>, 존재하지 않으면 <code>false</code>를 반환한다.</li>
<li><code>set.clear()</code> : 셋을 비운다.</li>
<li><code>set.size</code> : 셋의 요소 갯수를 반환한다.</li>
</ul>
<pre><code class="language-javascript">let set = new Set();

let john = { name: &quot;John&quot; };
let pete = { name: &quot;Pete&quot; };
let mary = { name: &quot;Mary&quot; };

set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

set.size;    // 3

for (let user of set) {
  alert(user.name);    // John, Pete, Mary
}</code></pre>
<h3 id="셋의-요소에-반복-작업하기">셋의 요소에 반복 작업하기</h3>
<p><code>for of</code>나 <code>forEach</code>를 사용해서 셋의 값을 대상으로 반복 작업을 수행할 수 있다.</p>
<pre><code class="language-javascript">let set = new Set([&quot;oranges&quot;, &quot;apples&quot;, &quot;bananas&quot;]);

for (let value of set) {
  alert(value);    // oranges, apples, bananas
}

set.forEach((value, valueAgain, set) =&gt; {
  alert(value);
});</code></pre>
<p>위 <code>forEach()</code> 메서드에서 콜백 함수에 쓰인 두 번째 인자는 첫 번째 인자와 값이 같다. 이는 맵과의 호환성 때문이다. 이렇게 구현해 놓으면 맵을 셋으로 혹은 셋을 맵으로 교체하기 쉽다.</p>
<p>셋도 맵과 마찬가지로 반복 작업을 위한 메서드들이 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>set.keys()</code> : 셋 내의 모든 값을 포함하는 이터러블을 반환한다.</li>
<li><code>set.values()</code> : 셋 내의 모든 값을 포함하는 이터러블을 반환한다. 맵과의 호환성을 위해 만들어진 메서드이다.</li>
<li><code>set.entries()</code> : 셋 내의 각 값을 이용해 만든 값-값 쌍 배열을 포함하는 이터러블을 반환한다. 맵과의 호환성을 위해 만들어진 메서드이다.</li>
</ul>
<hr>
<h1 id="위크맵과-위크셋">위크맵과 위크셋</h1>
<p>맵에서 객체를 키로 사용한 경우에 맵이 메모리에 있는 한 객체를 참조하는 것이 아무것도 없어도, 객체가 가비키 컬렉터의 대상이 되지 않는다. 이는 사용하지 않는 메모리의 낭비가 발생한다.</p>
<p>하지만 위크맵과 위크셋을 사용하면 메모리를 절약할 수 있다.</p>
<h3 id="위크맵weakmap">위크맵(weakmap)</h3>
<p>위크맵의 키는 반드시 객체여야 한다. 원시값은 위크맵의 키가 될 수 없다.</p>
<p>위크맵의 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 위크맵에서 자동으로 삭제된다.</p>
<pre><code class="language-javascript">// 위크맵 선언 예시
let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, &quot;ok&quot;);

// 위크맵에서 객체 삭제
let john = { name: &quot;John&quot; };

let weakMap = new WeakMap();
weakMap.set(john, &quot;...&quot;);

john = null;

// john을 나타내는 객체는 메모리에서 지워짐</code></pre>
<p>위 예시에서 john을 나타내는 객체는 오로지 위크맵의 키로만 사용되고 있으므로, 참조를 덮어쓰게 되면 이 객체는 위크맵과 메모리에서 자동으로 삭제된다.</p>
<p>위크맵은 반복 작업과 <code>keys()</code>, <code>values()</code>, <code>entries()</code> 메서드를 지원하지 않는다. 따라서, 위크맵에서는 키나 값 전체를 얻는 것이 불가능하다.</p>
<p>위크맵이 지원하는 메서드는 다음과 같다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>weakMap.get(key)</code></li>
<li><code>weakMap.set(key, value)</code></li>
<li><code>weakMap.delete(key)</code></li>
<li><code>weakMap.has(key)</code></li>
</ul>
<h3 id="위크맵의-활용">위크맵의 활용</h3>
<h4 id="추가-데이터를-저장">추가 데이터를 저장</h4>
<p>외부 코드에 속한 객체를 가지고 작업해야 한다고 가정해보자. 이 객체에 데이터를 추가해줘야 하는데, 추가해 줄 데이터는 객체가 살아있는 동안에만 유효한 상황이다. 이럴 때, 위크맵을 사용할 수 있다.</p>
<p>위크맵에 원하는 데이터를 저장하고, 키로 객체를 사용한다. 이런 방식을 통해 객체가 가비지 컬렉션의 대상이 될 때, 데이터도 함께 사라지게 된다.</p>
<pre><code class="language-javascript">weakMap.set(john, &quot;비밀문서&quot;);
// john이 제거되면, 비밀문서는 자동으로 파기된다.</code></pre>
<p>좀 더 구체적인 예시로, 사용자의 방문 횟수를 세어주는 기능을 구현해보자.</p>
<pre><code class="language-javascript">// 맵을 사용한 구현
let visitsCountMap = new Map(); // 맵에 사용자의 방문 횟수를 저장함

// 사용자가 방문하면 방문 횟수를 늘려줍니다.
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}

let john = { name: &quot;John&quot; };

countUser(john); // John의 방문 횟수를 증가시킵니다.

// John의 방문 횟수를 셀 필요가 없어지면 아래와 같이 john을 null로 덮어씁니다.
john = null;</code></pre>
<p>이처럼 맵을 사용해서 구현하면 객체를 가리키는 참조가 없어져도 가비지 컬렉터에 의해 자동으로 삭제되지 않으므로, 데이터를 손수 지워줘야 한다. 이렇게 수동으로 데이터를 비워주는 방식은 비효율적이다.</p>
<p>위크맵을 사용하면 객체가 도달 가능하지 않은 상태가 되면 자동으로 메모리에서 삭제되기 때문에, 수동으로 데이터를 지워줄 필요가 없다. 키에 대응하는 값이 자동으로 가비지 컬렉션의 대상이 되기 때문이다.</p>
<pre><code class="language-javascript">// 위크맵을 사용한 구현
let visitsCountMap = new WeakMap(); // 위크맵에 사용자의 방문 횟수를 저장함

// 사용자가 방문하면 방문 횟수를 늘려줍니다.
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}</code></pre>
<h4 id="캐싱caching">캐싱(caching)</h4>
<p>위크맵은 캐싱이 필요할 때 유용하다. 캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 다음 번의 연산 시간과 비용을 절약해주는 기법이다.</p>
<p>아래에서 맵과 위크맵을 사용한 캐싱 예시를 비교해보자.</p>
<pre><code class="language-javascript">// 맵을 사용한 구현
let cache = new Map();

// 연산을 수행하고 그 결과를 맵에 저장
function process(obj) {
  if (!cache.has(obj)) {
    let result = obj;
    cache.set(obj, result);
  }

  reurn cache.get(obj);
}

let obj = {
  // ...
};

let result1 = process(obj);

let result2 = process(obj);    // 두 번 째 호출 때는 맵에 저장된 결과를 사용

// 객체가 쓸모없어지면 null로 덮어 씀
obj = null;

// 데이터가 자동으로 삭제되지 않아 메모리 누수
alert(cache.size);    // 1

// 위크맵을 사용한 구현
let cache = new WeakMap();

// 연산을 수행하고 그 결과를 위크맵에 저장합니다.
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* 연산 수행 */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

let obj = {/* ... 객체 ... */};

let result1 = process(obj);
let result2 = process(obj);

// 객체가 쓸모없어지면 아래와 같이 null로 덮어쓴다.
obj = null;

// 위크맵에서는 obj가 가비지 컬렉션의 대상이 되므로, 캐싱된 데이터 역시 메모리에서 삭제됨
// 삭제가 진행되면 cache엔 그 어떤 요소도 남아있지 않음</code></pre>
<h3 id="위크셋weakset">위크셋(weakset)</h3>
<p>위크셋은 셋과 유사하지만 객체만 저장할 수 있다는 점이 다르다. 원시값은 저장할 수 없다.</p>
<p>위크셋안의 객체는 도달 가능하지 않으면 메모리에서 삭제된다.</p>
<p>위크셋도 마찬가지로 반복 작업 관련 메서드를 사용할 수 없다.</p>
<p>위크셋이 지원하는 메서드는 다음과 같다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>weakSet.add()</code></li>
<li><code>weakSet.delete()</code></li>
<li><code>weakSet.has()</code></li>
</ul>
<p>위크맵과 유사하게 위크셋도 부차적인 데이터를 저장할 때 사용할 수 있다. 다만, 위크셋에는 위크맵처럼 복잡한 데이터를 저장하지 않는다. 대신 예 혹은 아니오 같은 간단한 답변을 얻는 용도로 사용된다.</p>
<p>위크셋을 사용해서 사용자의 사이트 방문 여부를 추적하는 예시를 살펴보자.</p>
<pre><code class="language-javascript">let visitedSet = new WeakSet();

let john = { name: &quot;John&quot; };
let pete = { name: &quot;Pete&quot; };
let mary = { name: &quot;Mary&quot; };

visitedSet.add(john);    // John이 방문
visitedSet.add(pete);    // Pete가 방문
visitedSet.add(john);    // John이 다시 방문

// 방문 여부 확인
visitedSet.has(john);    // true
visitedSet.has(mary);    // false

// John의 방문 여부가 삭제됨
john = null;</code></pre>
<hr>
<h1 id="일반-객체를-대상으로-순회하기">일반 객체를 대상으로 순회하기</h1>
<p><code>keys()</code>, <code>values()</code>, <code>entries()</code> 메서드들은 맵과 셋, 배열에서 순회에 사용하는 메서드이다.</p>
<p>일반 객체에도 해당 메서드들이 있긴하지만, 문법에 차이가 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Object.keys(obj)</code> : 객체의 키만 담은 배열을 반환한다.</li>
<li><code>Object.values(obj)</code> : 객체의 값만 담은 배열을 반환한다.</li>
<li><code>Object.entries(obj)</code> : 키-값 쌍 배열을 담은 배열을 반환한다.</li>
</ul>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  age: 30
};

Object.keys(user);    // [&quot;name&quot;, &quot;age&quot;]
Object.values(user);    // [&quot;John&quot;, 30]
Object.entries(user);    // [ [&quot;name&quot;, &quot;John&quot;], [&quot;age&quot;, 30] ]

// 값을 순회하는 예시
for (let value of Object.values(user)) {
  alert(value);    // Violet, 30
}</code></pre>
<p>위 메서드들은 <code>for in</code> 반복문처럼 키가 심볼형인 프로퍼티를 무시한다.</p>
<p>객체에는 <code>map()</code>과 <code>filter()</code> 같은 배열 전용 메서드 역시 사용할 수 없다.</p>
<p>하지만 <code>Object.entries()</code>와 <code>Object.fromEntries()</code>를 순차적으로 적용하면 객체에도 배열 전용 메서드를 사용할 수 있다.</p>
<pre><code class="language-javascript">let prices = {
  banana: 1,
  orange: 2,
  meat: 4
};

let doublePrices = Object.fromEntries( // 배열을 다시 객체로 되돌림
  // 객체를 배열로 변환해서 map 적용
  Object.entries(prices).map(([key, value]) =&gt; [key, value * 2])
);

doublePrices.meat;    // 8</code></pre>
<hr>
<h1 id="구조-분해-할당destructing-assigment">구조 분해 할당(destructing assigment)</h1>
<p>개발을 하다 보면 함수에 객체나 배열을 전달해야 할 때가 있다. 또, 가끔은 객체나 배열에 저장된 데이터 중 일부만 필요한 경우가 생긴다.</p>
<p>이럴 때 객체나 배열을 변수로 분해할 수 있게 해주는 특별한 문법인 <code>구조 분해 할당</code>을 사용할 수 있다.</p>
<h3 id="배열-분해하기">배열 분해하기</h3>
<pre><code class="language-javascript">// 배열 구조 분해 할당
let arr = [&quot;Bora&quot;, &quot;Lee&quot;]

let [firstName, surname] = arr;

firstName;    // Bora
surname;    // Lee

// 문자열을 배열로 변환하고, 구조 분해 할당
let [firstName, surname] = &quot;Bora Lee&quot;.split(&quot; &quot;);</code></pre>
<p>쉼표를 사용하여 필요하지 않은 배열의 요소를 무시할 수 있다.</p>
<pre><code class="language-javascript">let [firstName, , title] = [&quot;Julius&quot;, &quot;Caesar&quot;, &quot;Consul&quot;, &quot;of the Roman Republic&quot;];

title;    // Consul</code></pre>
<p>배열, 문자열 뿐만 아니라 할당 연산자 우측에는 모든 이터러블이 올 수 있다.</p>
<pre><code class="language-javascript">let [a, b, c] = &quot;abc&quot;;
let [one, two, three] = new Set([1, 2, 3]);</code></pre>
<p>또, 할당 연산자 좌측에는 변수뿐만 아니라, 객체의 프로퍼티가 같이 할당할 수 있는 어떤 것이든 올 수 있다.</p>
<pre><code class="language-javascript">let user = {};
[user.name, user.surname] = &quot;Bora Lee&quot;.split(&quot; &quot;);

user.name;    // Bora</code></pre>
<p>구조 분해 할당을 이용해서 변수 교환 트릭도 할 수 있다.</p>
<pre><code class="language-javascript">let guest = &quot;Jane&quot;;
let admin = &quot;Pete&quot;;

[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`);    // Pete Jane</code></pre>
<p><code>...</code>로 나머지 요소를 가져올 수 있다.
rest는 나머지 배열 요소들이 저장된 새로운 배열이 된다.</p>
<pre><code class="language-javascript">let [name1, name2, ...rest] = [&quot;Julius&quot;, &quot;Caesar&quot;, &quot;Consul&quot;, &quot;of the Roman Republic&quot;];

name1;    // Julius
name2;    // Caesar

// rest는 배열이다.
rest[0];    // Consul
rest[1];    // of the Roman Republic</code></pre>
<p>할당할 값이 없을 때 기본으로 할당해 줄 기본값을 설정할 수 있다. 기본값에는 복잡한 표현식이나 함수 호출도 올 수 있다.</p>
<pre><code class="language-javascript">let [firstName, surname] = [];
firstName;    // undefined
surname;    // undefined

let [name = &quot;Guest&quot;, surname = &quot;Anonymous&quot;] = [&quot;Julius&quot;];

name;    // Julius
surname;    // Anonymous</code></pre>
<h3 id="객체-분해하기">객체 분해하기</h3>
<pre><code class="language-javascript">// 좌측의 할당 받는 부분의 이름은 객체의 프로퍼티와 같아야 한다.
let {var1, var2} = {var1: ..., var2: ...};

let options = {
  title: &quot;Menu&quot;,
  width: 100,
  height: 200
};

let {title, width, height} = options;

title;    // Menu
width;    // 100
height;    // 200</code></pre>
<p>객체의 구조 분해 할당은 순서가 중요하지 않다.</p>
<pre><code class="language-javascript">let {height, width, title} = { title: &quot;Menu&quot;, height: 200, width: 100 }</code></pre>
<p>콜론 <code>:</code>을 사용해서 프로퍼티 키와 다른 이름을 가진 변수를 저장할 수도 있다.</p>
<pre><code class="language-javascript">let options = {
  title: &quot;Menu&quot;,
  width: 100,
  height: 200
};

let {width: w, height: h, title} = options;

title; // Menu
w;    // 100
h;    // 200</code></pre>
<p>프로퍼티가 없는 경우에 대비해서 기본값을 설정할 수 있다. 기본값에는 표현식이나 함수 호출이 올 수도 있다.</p>
<pre><code class="language-javascript">let options = {
  title: &quot;Menu&quot;
};

let {width = 100, height = 200, title} = options;

title;    // Menu
width;    // 100
height;    // 200</code></pre>
<p>객체에서 원하는 정보만 뽑아올 수도 있다. 배열과 달리 콤마로 구별하지 않아도 된다.</p>
<pre><code class="language-javascript">let options = {
  title: &quot;Menu&quot;,
  width: 100,
  height: 200
};

let { title } = options;

title;    // Menu</code></pre>
<p><code>...</code> 패턴을 사용해서, 변수에 할당받지 않는 나머지 프로퍼티들을 객체로 받을 수 있다.</p>
<pre><code class="language-javascript">let options = {
  title: &quot;Menu&quot;,
  height: 200,
  width: 100
};

let {title, ...rest} = options;

// rest = { height: 200, width: 100 }
rest.height;    // 200
rest.width;    // 100</code></pre>
<p><code>let</code>으로 새로 변수를 생성하여 구조 분해 할당을 하지 않고, 기존의 존재하는 변수에 구조 분해 할당할 수 있다. 다만, 자바스크립트에서는 표현식 안에 있지 않은 <code>{ }</code>를 코드 블록으로 인식하므로, 구조 분해 할당문을 괄호 <code>()</code>로 감싸서 자바스크립트가 표현식으로 해석하게 하면 된다.</p>
<pre><code class="language-javascript">let title, width, height;

({title, width, height}) = {title: &quot;Menu&quot;, width: 200, height: 100}});

title;    // Menu</code></pre>
<h3 id="중첩-구조-분해">중첩 구조 분해</h3>
<p>객체나 배열이 다른 객체나 배열을 중첩으로 포함하는 경우, 좀 더 복잡한 패턴을 사용해서 중첩 배열이나 객체의 정보를 추출할 수 있다.</p>
<pre><code class="language-javascript">let options = {
  size: {
    width: 100,
    height: 200
  },
  items: [&quot;Cake&quot;, &quot;Donut&quot;],
  extra: true
};

let {
  size: {
    width,
    height
  },
  items: [item1, item2],
  title = &quot;Menu&quot;
} = options;

title;    // Menu
width;    // 100
height;    // 200
item1;    // Cake
item2;    // Donut</code></pre>
<h3 id="함수-매개변수에-구조-분해-할당-활용">함수 매개변수에 구조 분해 할당 활용</h3>
<p>지저분한 여러 개의 매개변수를 하나의 객체로 모아 함수에 전달하고, 함수가 인수로 전달받은 객체를 구조 분해하여 변수에 할당하고 원하는 작업을 하여 효율적인 코드를 작성할 수 있다.</p>
<pre><code class="language-javascript">// 구조 분해 활용 전
function showMenu(title = &quot;Untitled&quot;, width = 200, height = 100, items = []) {
 // ...
}

// 매개 변수의 순서도 틀리지 않고, 매개변수도 모두 전달해주어야 에러가 발생하지 않는다.
// 기본값이 있어 불필요한 인수를 전달 해준다. 
showMenu(&quot;My Menu&quot;, undefined, undefined, [&quot;Item1&quot;, &quot;Item2&quot;]);


// 구조 분해 활용
let options = {
  title: &quot;My menu&quot;,
  items: [&quot;Item1&quot;, &quot;Item2&quot;]
};

function showMenu({title = &quot;Untitled&quot;, width = 200, height = 100, items = []}) {
  alert( `${title} ${width} ${height}` );    // My Menu 200 100
  alert( items );    // Item1, Item2
}

showMenu(options);</code></pre>
<p>참고로, 구조 분해 변수에 콜론 <code>:</code>으로 변수 이름을 따로 지정하지 않는 경우에는 구조 분해 할당 문법에 따라 프로퍼티 이름에 맞춰야 한다.</p>
<pre><code class="language-javascript">function({ propertyName: varName }) {
  // ...
}</code></pre>
<hr>
<h1 id="json과-메서드">JSON과 메서드</h1>
<h3 id="json">JSON</h3>
<p>JSON(JavaScript Object Notation)은 값이나 객체를 나타내주는 범용 포맷이다. 본래 자바스크립트에서 사용할 목적으로 만들어졌다. 그런데 라이브러리를 사용하면 자바스크립트가 아닌 언어에서도 JSON을 다룰 수 있어서, JSON을 데이터 교환 목적으로 사용하는 경우가 많다.</p>
<p>네트워크를 통해 객체를 어딘가에 보내거나 받을 때, 로깅 목적으로 객체를 출력해야 한다면 객체를 문자열로 전환해야 한다.</p>
<p>또, 전환된 문자열에는 객체 프로퍼티가 모두 포함되어 있어야한다.</p>
<p>이 때, 사용할 수 있는 내장 함수가 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>JSON.stringify(obj)</code> : 객체를 JSON으로 바꿔준다.</li>
<li><code>JSON.parse(json)</code> : JSON을 객체로 바꿔준다.</li>
</ul>
<h3 id="jsonstringify-메서드">JSON.stringify() 메서드</h3>
<pre><code class="language-javascript">// JSON.stringify 예시
let student = {
  name: &quot;John&quot;,
  age: 30,
  isAdmin: false,
  course: [&quot;html&quot;, &quot;css&quot;, &quot;js&quot;],
  wife: null
};

let json = JSON.stringify(student);

alert(json);
/*
{
  &quot;name&quot;: &quot;John&quot;,
  &quot;age&quot;: 30,
  &quot;isAdmin&quot;: false,
  &quot;courses&quot;: [&quot;html&quot;, &quot;css&quot;, &quot;js&quot;],
  &quot;wife&quot;: null
}
*/</code></pre>
<p>객체는 이렇게 문자열로 변환된 후에 네트워크를 통해 전송하거나 저장소에 저장할 수 있다.</p>
<p>JSON으로 인코딩된 문자열은 일반 객체와 다른 특징을 보인다.</p>
<blockquote>
</blockquote>
<ul>
<li>문자열은 큰따옴표로만 감싸야한다. 작은따옴표나 백틱은 사용할 수 없다.</li>
<li>객체 프로퍼티 이름은 큰따옴표로 감싸야 한다.</li>
</ul>
<p><code>JSON.stringify()</code>는 객체뿐만 아니라 원시값에도 적용할 수 있다. 적용할 수 있는 자료형은 아래와 같다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>객체</code></li>
<li><code>배열</code></li>
<li><code>문자형</code></li>
<li><code>숫자형</code></li>
<li><code>불린형</code></li>
<li><code>null</code></li>
</ul>
<pre><code class="language-javascript">JSON.stringify(1);    // &#39;1&#39;
JSON.stringify(&#39;test&#39;);    // &#39;&quot;test&quot;&#39;
JSON.stringify(true);    // &#39;true&#39;
JSON.stringify([1, 2, 3]);    // &#39;[1,2,3]&#39;</code></pre>
<p>JSON은 데이터 교환을 목적으로 만들어진, 언어에 종속되지 않는 포맷이다. 따라서, 자바스크립트만의 객체 프로퍼티는 <code>JSON.stringify()</code>가 처리할 수 없다. <code>JSON.stringify()</code> 호출 시 무시되는 프로퍼티는 아래와 같다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>함수 프로퍼티(메서드)</code></li>
<li><code>심볼형 프로퍼티(키가 심볼인 프로퍼티)</code></li>
<li><code>값이 undefined인 프로퍼티</code></li>
</ul>
<pre><code class="language-javascript">let user = {
  sayHi() { // 무시
    alert(&quot;Hello&quot;);
  },
  [Symbol(&quot;id&quot;)]: 123, // 무시
  something: undefined // 무시
};

alert( JSON.stringify(user) ); // {} (빈 객체가 출력됨, 자료형은 string임)</code></pre>
<p><code>JSON.stringify()</code>는 중첩 객체도 알아서 문자열로 바꿔준다.</p>
<pre><code class="language-javascript">let meetup = {
  title: &quot;Conference&quot;,
  room: {
    number: 23,
    participants: [&quot;john&quot;, &quot;ann&quot;]
  }
};

alert( JSON.stringify(meetup) );
/* 객체 전체가 문자열로 변환되었습니다.
{
  &quot;title&quot;:&quot;Conference&quot;,
  &quot;room&quot;:{&quot;number&quot;:23,&quot;participants&quot;:[&quot;john&quot;,&quot;ann&quot;]},
}
*/</code></pre>
<p>다만, <code>JSON.stringify()</code>는 순환 참조가 있으면 원하는 대로 객체를 문자열로 바꿀 수 없다.</p>
<pre><code class="language-javascript">let room = {
  number: 23
};

let meetup = {
  title: &quot;Conference&quot;,
  participants: [&quot;john&quot;, &quot;ann&quot;]
};

meetup.place = room;       // meetup은 room을 참조합니다.
room.occupiedBy = meetup; // room은 meetup을 참조합니다.

JSON.stringify(meetup); // Error: Converting circular structure to JSON</code></pre>
<h3 id="replacer로-원하는-프로퍼티만-직렬화하기">replacer로 원하는 프로퍼티만 직렬화하기</h3>
<p><code>JSON.stringify()</code>의 전체 문법은 아래와 같다.</p>
<pre><code class="language-javascript">let json = JSON.stringify(value, [replacer, space))

// value : 인코딩하려는 값
// replacer : 인코딩 하길 원하는 프로퍼티가 담긴 배열 또는 매핑 함수 function(key, value)
// space : 서식 변경 목적으로 사용할 공백 문자 수</code></pre>
<p>JSON으로 변환하길 원하는 프로퍼티가 담긴 배열을 두 번째 인수로 넘겨주면 해당 프로퍼티들만 인코딩할 수 있다. 순환 참조를 발생시키는 프로퍼티 <code>room.occupiedBy</code>만 제외하고 모든 프로퍼티를 배열에 넣어보자.</p>
<pre><code class="language-javascript">let room = {
  number: 23
};

let meetup = {
  title: &quot;Conference&quot;,
  participants: [{name: &quot;John&quot;}, {name: &quot;Alice&quot;}],
  place: room // meetup references room
};

room.occupiedBy = meetup; // room references meetup

alert( JSON.stringify(meetup, [&#39;title&#39;, &#39;participants&#39;, &#39;place&#39;, &#39;name&#39;, &#39;number&#39;]) );
/*
{
  &quot;title&quot;:&quot;Conference&quot;,
  &quot;participants&quot;:[{&quot;name&quot;:&quot;John&quot;},{&quot;name&quot;:&quot;Alice&quot;}],
  &quot;place&quot;:{&quot;number&quot;:23}
}
*/</code></pre>
<p>배열이 길게 느껴지면 배열 대신 함수를 전달할 수 있다. <code>replacer</code>에 전달되는 함수는 프로퍼티 키-값 쌍 전체를 대상으로 호출되는데, 반드시 기존 프로퍼티 값을 대신하여 사용할 값을 <code>return</code>문으로 반환해야 한다.  특정 프로퍼티를 직렬화에서 누락시키려면 반환값을 undefined로 만들면 된다.</p>
<pre><code class="language-javascript">alert( JSON.stringify(meetup, function replacer(key, value) {
  alert(`${key}: ${value}`);
  return (key == &#39;occupiedBy&#39;) ? undefined : value;
}));

/* replacer 함수에서 처리하는 키:값 쌍 목록
:             [object Object]
title:        Conference
participants: [object Object],[object Object]
0:            [object Object]
name:         John
1:            [object Object]
name:         Alice
place:        [object Object]
number:       23
*/</code></pre>
<p><code>replacer</code> 함수가 중첩 객체와 배열의 요소까지 포함한 모든 키-값 쌍을 처리하고 있다. <code>replacer</code>함수는 재귀적으로 키-값 쌍을 처리하는데, 함수 내에서 <code>this</code>는 현재 처리하고 있는 프로퍼티가 위치한 객체를 가리킨다.</p>
<p>첫 줄에 예상치 못한 문자열<code>:[object Object]</code>가 뜨는 것을 볼 수 있는데, 이는 함수가 최초로 호출될 때 <code>{&quot;&quot;: meetup}</code> 형태의 래퍼 객체가 만들어지기 때문이다. <code>replacer</code> 함수가 가장 처음으로 처리해야하는 키-값 쌍에서 키는 빈 문자열, 값은 변환하고자 하는 객체 전체가 되는 것이다.</p>
<p>이렇게 <code>replacer</code> 함수를 사용하면 중첩 객체 등을 포함한 객체 전체에서 원하는 프로퍼티만 선택해 직렬화 할 수 있다.</p>
<h3 id="space로-가독성-높이기">space로 가독성 높이기</h3>
<p><code>JSON.stringify(value, [replacer, space])</code>의 세 번째 인수 <code>space</code>는 가독성을 높이기 위해 중간에 삽입해 줄 공백 문자 수를 나타낸다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;John&quot;,
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

alert(JSON.stringify(user, null, 2));
/* 공백 문자 두 개를 사용하여 들여쓰기함:
{
  &quot;name&quot;: &quot;John&quot;,
  &quot;age&quot;: 25,
  &quot;roles&quot;: {
    &quot;isAdmin&quot;: false,
    &quot;isEditor&quot;: true
  }
}
*/

/* JSON.stringify(user, null, 4)라면 아래와 같이 좀 더 들여써짐
{
    &quot;name&quot;: &quot;John&quot;,
    &quot;age&quot;: 25,
    &quot;roles&quot;: {
        &quot;isAdmin&quot;: false,
        &quot;isEditor&quot;: true
    }
}
*/</code></pre>
<h3 id="tojson-메서드">toJSON() 메서드</h3>
<p>객체에 <code>toJSON</code>이라는 메서드가 구현되어 있으면, 객체를 해당 메서드에서 구현한 코드대로 JSON으로 바꿀 수 있다.</p>
<pre><code class="language-javascript">let room = {
  number: 23,
  toJSON() {
    return this.number;
  }
};

let meetup = {
  title: &quot;Conference&quot;,
  room
};

JSON.stringify(room);    // &#39;23&#39;</code></pre>
<h3 id="jsonparse">JSON.parse</h3>
<p><code>JSON.parse()</code> 메서드를 사용하면 JSON으로 인코딩된 객체를 다시 객체로 디코딩할 수 있다.</p>
<pre><code class="language-javascript">let value = JSON.parse(str, [reviver]);

// str : JSON 형식의 문자열
// reviver : 모든 키-값 쌍을 대상으로 호출되는 함수로 값을 변경시킬 수 있다. function(key,value)</code></pre>
<p>문자열로 변환된 배열을 다시 배열로 디코딩해보자.</p>
<pre><code class="language-javascript">let numbers = &quot;[0, 1, 2, 3]&quot;;

numbers = JSON.parse(numbers);

numbers;    // [0, 1, 2, 3]</code></pre>
<p>아래와 같이 중첩 객체에도 사용할 수 있다.</p>
<pre><code class="language-javascript">let userData = &#39;{ &quot;name&quot;: &quot;John&quot;, &quot;age&quot;: 35, &quot;isAdmin&quot;: false, &quot;friends&quot;: [0,1,2,3] }&#39;;

let user = JSON.parse(userData);

user.friends[1];    // 1</code></pre>
<p>참고로, JSON은 주석을 지원하지 않는다.</p>
<h3 id="reviver-사용하기">reviver 사용하기</h3>
<p>서버로부터 문자열로 변환된 객체를 전송받았다고 가정하자. 이제 이 문자열을 역직렬화해서 자바스크립트 객체를 만들어보자.</p>
<pre><code class="language-javascript">let str = &#39;{&quot;title&quot;:&quot;Conference&quot;,&quot;date&quot;:&quot;2017-11-30T12:00:00.000Z&quot;}&#39;;

let meetup = JSON.parse(str);

alert( meetup.date.getDate() ); // 에러</code></pre>
<p><code>meetup.date</code>의 값은 <code>Date</code> 객체가 아닌, 문자열이기 때문에 발생한 에러이다. 그렇다면 문자열을 <code>Date</code>로 전환해줘야 한다는 것을 어떻게 <code>JSON.parse()</code> 메서드에 알릴 수 있을까?</p>
<p>이 때 사용하는 것이 두 번째 인수 <code>reviver</code>이다.</p>
<pre><code class="language-javascript">let str = &#39;{&quot;title&quot;:&quot;Conference&quot;,&quot;date&quot;:&quot;2017-11-30T12:00:00.000Z&quot;}&#39;;

let meetup = JSON.parse(str, function(key, value) {
  if (key == &#39;date&#39;) return new Date(value);
  return value;
});

alert( meetup.date.getDate() ); // 2017-11-30T12:00:00.000Z</code></pre>
<hr>
<h1 id="date-객체와-날짜">Date 객체와 날짜</h1>
<p><code>Date</code>는 날짜를 저장할 수 있고, 날짜와 관련된 메서드를 제공해주는 내장 객체이다.</p>
<p><code>Date</code> 객체를 활용하면 생성 및 수정 시간을 저장하거나 시간을 측정할 수 있고, 현재 날짜를 출력하는 용도 등으로 활용할 수 있습니다.</p>
<h3 id="date-객체-생성하기">Date 객체 생성하기</h3>
<p><code>new Date()</code>를 인수없이 호출하면 현재 날짜와 시간이 저장된 <code>Date</code> 객체가 반환된다. </p>
<p>1970년 1월 1일 0시 0분 0초를 기준으로 흘러간 밀리초를 나타내는 정수는 <code>타임스탬프(timestamp)</code>라고 부른다. <code>new Date(timestamp)</code>로 호출하면 UTC 기준으로 1970년 1월 1일 0시 0분 0초부터 <code>timestamp</code> 후의 시점이 저장된 <code>Date</code> 객체가 반환된다.</p>
<pre><code class="language-javascript">let now = new Date();
alert( now );    // 현재 날짜 및 시간이 출력됨
                // Mon Oct 24 2022 15:59:40 GMT+0900 (한국 표준시)

let Jan01_1970 = new Date(0);
alert( Jan01_1970 ); // Thu Jan 01 1970 09:00:00 GMT+0900 (한국 표준시)

let Jan02_1970 = new Date(24 * 3600 * 1000);
alert( Jan02_1970 ); // Fri Jan 02 1970 09:00:00 GMT+0900 (한국 표준시)

let Dec31_1969 = new Date(-24 * 3600 * 1000);
alert( Dec31_1969 );    // Wed Dec 31 1969 09:00:00 GMT+0900 (한국 표준시)</code></pre>
<p><code>new Date(datestring)</code>는 인수로 받은 문자열을 자동으로 구문 분석하여 날짜를 저장한다.</p>
<pre><code class="language-javascript">let date = new Date(&quot;2017-01-26&quot;);
alert(date);
// 인수로 시간은 지정하지 않았기 때문에 GMT 자정이라고 가정하고
// 코드가 실행되는 시간대(timezone)에 따라 출력 문자열이 바뀜.
// 따라서 얼럿 창엔
// Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)
// 혹은
// Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)등이 출력됨</code></pre>
<p><code>new Date(year, month, date, hours, seconds, ms)</code>는 주어진 인수를 조합해서 만들 수 있는 날짜가 저장된 객체를 반환한다. 해당 호출 방식은 다음의 규칙을 따른다.</p>
<blockquote>
</blockquote>
<ul>
<li>첫 번째와 두 번째 인수는 필수값이다.</li>
<li><code>year</code>는 반드시 네 자리 숫자여야 한다. <code>2013</code>은 괜찮지만 <code>98</code>은 안된다.</li>
<li><code>month</code>는 0(1월)부터 11(12월) 사이의 숫자여야 한다.</li>
<li><code>date</code>는 일을 나타내는데, 값이 없는 경우에는 1일로 처리된다.</li>
<li><code>hours / minutes / seconds / ms</code>에 값이 없는 경우에는 0으로 처리된다.</li>
</ul>
<pre><code class="language-javascript">new Date(2011, 0, 1, 0, 0, 0, 0);    // Sat Jan 01 2011 00:00:00 GMT+0900 (한국 표준시)</code></pre>
<h3 id="date-객체에서-날짜-구성요소-얻기">Date 객체에서 날짜 구성요소 얻기</h3>
<p><code>Date</code> 객체의 메서드를 사용해서 연, 월, 일 등의 값을 얻을 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>getFullYear()</code> : 네 자릿수 연도를 반환한다.</li>
<li><code>getMonth()</code> : 0 이상 11 이하의 숫자로 월을 반환한다. </li>
<li><code>getDate()</code> : 1 이상 31 이하의 일을 반환한다.</li>
<li><code>getHours()</code>,<code>getMinutes()</code>, <code>getSeconds()</code>, </li>
<li><code>getMilliseconds()</code> : 시, 분, 초, 밀리초를 반환한다.</li>
<li><code>getDay()</code> : 일요일을 나타내는 0부터 토요일을 나타내는 6 까지의 숫자 중 하나를 반환한다.</li>
<li><code>getTime()</code> : 주어진 일시와 1970년 1월 1일 00시 00분 00초 사이의 간격인 타임스탬프를 밀리세컨드 단위로 반환한다.</li>
<li><code>getTimezoneOffset()</code> : 현지 시간과 표준 시간의 차이를 분 단위로 반환한다.</li>
</ul>
<p>위 메서드는 모두 현지 시간 기준으로 값을 반환한다. <code>get</code> 다음에 <code>UTC</code>를 붙여주면 표준시(UTC+0) 기준의 날짜 구성 요소를 반환해주는 메서드를 만들 수 있다. 단, <code>getTime()</code>과 <code>getTimezoneOffset()</code> 메서드는 표준시 기준의 메서드가 없다.</p>
<pre><code class="language-javascript">let date = new Date();

// 현지 시간 기준
alert( date.getHours() ); 

// 표준시간대 기준
alert( date.getUTCHours() );

alert( new Date().getTimezoneOffset() );</code></pre>
<h3 id="date-객체에-날짜-구성요소-설정하기">Date 객체에 날짜 구성요소 설정하기</h3>
<p>아래 메서드를 사용해서 날짜 구성요소를 설정할 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>setFullYear(year, [month], [date])</code></li>
<li><code>setMonth(month, [date])</code></li>
<li><code>setDate(date)</code></li>
<li><code>setHours(hour, [min], [sec], [ms])</code></li>
<li><code>setMinutes(min, [sec], [ms])</code></li>
<li><code>setSeconds(sec, [ms])</code></li>
<li><code>setMillisecons(ms)</code></li>
<li><code>setTime(milliseconds)</code> : 1970년 1월 1일 00:00:00 UTC부터 <code>milliseconds</code> 이후를 나타내는 날짜를 설정한다.</li>
</ul>
<p><code>setTime()</code>을 제외한 모든 메서드는 <code>set</code> 다음에 <code>UTC</code>를 붙여서 표준시에 따라 날짜 구성 요소를 설정해주는 메서드가 있다.</p>
<pre><code class="language-javascript">let today = new Date();

// 날짜는 변경되지 않고 시만 0 으로 변경된다.
today.setHours(0);
alert(today);    // Mon Oct 24 2022 00:48:37 GMT+0900 (한국 표준시)

// 날짜는 변경되지 않고, 시, 분, 초가 모두 변경된다.
today.setHours(0, 0, 0, 0);
alert(today);    // Mon Oct 24 2022 00:00:00 GMT+0900 (한국 표준시)

today.setUTCMinutes(42);
alert(today); // Mon Oct 24 2022 00:42:00 GMT+0900 (한국 표준시)</code></pre>
<h3 id="자동-고침autocorrection">자동 고침(autocorrection)</h3>
<p><code>Date</code> 객체에 범위를 벗어나는 값을 설정하려고 하면 자동 고침 기능이 활성화되면서 값이 자동으로 수정된다. </p>
<p>입력받은 날짜 구성 요소가 범위를 벗어나면 초과분은 자동으로 다른 날짜 구성 요소에 배분된다.</p>
<pre><code class="language-javascript">let date = new Date(2013, 0, 32);    // 1월 32일은 없다.
alert(date);    // 2월 1일로 자동 고침

let date = new Date(2016, 2, 28);
date.setDate(date.getDate() + 2);
alert ( date );    // 윤년이면 2016년 3월 1일, 아니면 2016년 3월 2일로 자동 고침</code></pre>
<h3 id="date-객체를-숫자로-변경해서-시간차-측정하기">Date 객체를 숫자로 변경해서 시간차 측정하기</h3>
<p><code>Date</code> 객체를 숫자형으로 변경하면, 타임스탬프가 된다.</p>
<pre><code class="language-javascript">let date = new Date();
alert(+date);    // == date.getTime()을 호출하는 것과 같다.</code></pre>
<p>이를 응용하면 날짜에 마이너스 연산자를 적용해서 밀리초 기준 시차를 구할 수 있다.</p>
<pre><code class="language-javascript">let start = new Date();

for (let i = 0; i &lt; 100000; i++) {
  // do something
}

let end = new Date();

alert( `반복문의 연산 시간은 ${end - start} 밀리초 입니다.` );</code></pre>
<h3 id="datenow">Date.now()</h3>
<p>현재 타임스탬프를 반환하는 <code>Date.now()</code> 메서드를 사용해서, <code>Date</code> 객체를 만들지 않고도 시차를 측정할 수 있다.</p>
<p><code>Date.now()</code> 메서드는 <code>new Date().getTime()</code>과 의미론적으로 동일하지만, <code>Date</code> 객체를 만들지 않는다는 점이 다르다. 따라서 <code>new Date().getTime()</code>을 사용하는 것보다 빠르고, 가비지 컬렉터의 일을 덜어준다는 장점이 있다.</p>
<pre><code class="language-javascript">let start = Date.now();

for (let i = 0; i &lt; 100000; i++) {
  // do something
}

let end = Date.now();

alert( `반복문의 연산 시간은 ${end - start} 밀리초 입니다.` );</code></pre>
<h3 id="dateparse-메서드">Date.parse() 메서드</h3>
<p><code>Date.parse(str)</code> 메서드를 사용하면 문자열에서 날짜를 읽어올 수 있다.
단, 문자열의 형식은 <code>YYYY-MM-DDTHH:mm:ss.sssZ</code> 여야 한다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>YYYY-MM-DD</code> : 연-월-일</li>
<li><code>T</code> : 구분 기호</li>
<li><code>HH:mm:ss.sss</code> : 시:분:초.밀리초</li>
<li><code>Z</code> : 옵션으로, <code>+-hh:mm</code> 형식의 시간대를 나타낸다. <code>Z</code>가 한 글자인 경우에는 UTC+0을 나타낸다.</li>
</ul>
<p><code>YYYY-MM-DD</code>, <code>YYYY-MM</code>, <code>YYYY</code>와 같이 더 짦은 문자열 형식도 가능하다.</p>
<p>위 조건을 만족하는 문자열을 대상으로 <code>Date.parse(str)</code>를 호출하면 문자열과 대응하는 날짜의 타임스탬프가 반환된다. 문자열의 형식이 조건에 맞지 않은 경우에는 <code>NaN</code>이 반환된다.</p>
<pre><code class="language-javascript">let ms = Date.parse(&quot;2012-01-26T13:51:50.417-07:00&quot;);

alert(ms);    // 1327611110417

// Date.parse(str) 메서드를 사용해서 반환받은 타임스탬프로 새로운 Date 객체 생성
let date = new Date( Date.parse(&#39;2012-01-26T13:51:50.417-07:00&#39;) );

alert(date);    // Fri Jan 27 2012 05:51:50 GMT+0900 (한국 표준시)</code></pre>
<hr>
<h1 id="참고문헌">참고문헌</h1>
<p><a href="https://ko.javascript.info/data-types">https://ko.javascript.info/data-types</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 18 코테) 알고리즘 코딩 스타일]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-18-%EC%BD%94%ED%85%8C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9-%EC%8A%A4%ED%83%80%EC%9D%BC</link>
            <guid>https://velog.io/@dev_diver/22.-10.-18-%EC%BD%94%ED%85%8C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9-%EC%8A%A4%ED%83%80%EC%9D%BC</guid>
            <pubDate>Tue, 18 Oct 2022 01:46:31 GMT</pubDate>
            <description><![CDATA[<p>코딩 테스트 혹은 알고리즘 풀이를 기준으로</p>
<p>같은 로직을 구현했을 때, 좀 더 직관적인 코드가 좋을까, 아니면 함축적인 코드가 좋을까</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 16 자바스크립트) 객체 기본]]></title>
            <link>https://velog.io/@dev_diver/22.-10-14-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@dev_diver/22.-10-14-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Sat, 15 Oct 2022 18:15:32 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트엔 여덟 가지 자료형이 있고, 이 중 일곱 개는 오직 하나의 데이터만 담을 수 있어 원시형(primitive type)이라 부른다.</p>
<hr>
<h1 id="객체-기본">객체 기본</h1>
<p>객체형은 원시형과 달리 키로 구분된 데이터 집합이나 복잡한 객체 등 다양한 데이터를 담을 수있다.</p>
<h3 id="객체-생성하기">객체 생성하기</h3>
<p>객체를 만드는 방법은 두 가지가 있다.</p>
<pre><code class="language-javascript">// 객체 생성자 문법
let user = new Object();

// 객체 리터럴 문법
let user = {};</code></pre>
<h3 id="객체-리터럴과-프로퍼티">객체 리터럴과 프로퍼티</h3>
<p>객체 리터럴의 중괄호 안에는 <code>키:값</code> 쌍으로 구성된 프로퍼티가 들어간다. 키에는 문자형과 심볼형, 값에는 모든 자료형이 허용된다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  age: 27
};</code></pre>
<p>프로퍼티는 점 표기법을 이용해 값을 읽거나 추가, 삭제할 수 있다.</p>
<pre><code class="language-javascript">// 프로퍼티 값 읽기
user.name;  // Yong
user.age; // 27

// 프로퍼티 추가하기
user.isAdmin = true;

// 프로퍼티 삭제하기. delete 연산자를 사용.
delete user.age;

// 여러 단어를 조합해 프로퍼티 만들기. 복수의 단어는 따옴표로 묶어야 한다.
let user = {
  name: &quot;Yong&quot;,
  age: 27,
  &quot;likes dog&quot;: true
};</code></pre>
<p>여러 단어를 조합해 프로퍼티 키를 만든 경우에는 점 표기법을 사용해서 프로퍼티 값을 읽을 수 없다. 대신 대괄호 표기법을 사용해서 읽을 수 있다. 마찬가지로 대괄호 표기법을 사용해서 프로퍼티 추가와 삭제도 가능하다. 대괄호 표기법 안의 프로퍼티 키는 따옴표로 감싸야 한다. 또, 프로퍼티 키를 변수에 할당하여 접근할 수도 있다.</p>
<pre><code class="language-javascript">let user = {};

// 대괄호 표기법을 사용해 프로퍼티 추가하기
user[&quot;likes dog&quot;] = true;

// 대괄호 표기법을 사용해 프로퍼티 읽기
user[&quot;likes dog&quot;]); // true

// 키를 변수에 할당하여 프로퍼티에 접근하기
let key = &quot;likes dog&quot;;
user[key]; // true

// 대괄호 표기법을 사용해 프로퍼티 삭제하기
delete user[&quot;likes dog&quot;];</code></pre>
<p>객체 리터럴 방식으로 객체를 만들 때, 프로퍼티 키가 대괄호로 둘러싸여 있는 경우, 해당 프로퍼티 키를 <code>계산된 프로퍼티(computed property)</code>라고 부른다.</p>
<pre><code class="language-javascript">let fruit = prompt(&quot;어떤 과일을 구매하시겠습니까?&quot;, &quot;apple&quot;);

let bag = {
  [fruit]: 5, // 변수 fruit에서 프로퍼티 이름을 동적으로 받아 옵니다.
};

alert( bag.apple ); // fruit에 &quot;apple&quot;이 할당되었다면, 5가 출력됩니다.</code></pre>
<p>위 예시에서 <code>[fruit]</code>는 프로퍼티 이름을 변수 <code>fruit</code>에서 가져오겠다는 것을 의미한다.</p>
<h3 id="const-let과-객체-선언">const, let과 객체 선언</h3>
<p>앞 선 내용에서 <code>const</code>로 선언된 상수는 수정할 수 없다고 배웠다. </p>
<p>하지만, <code>const</code>로 선언된 객체는 수정할 수 있다. 엄밀히 말하면, 객체 안의 프로퍼티만 수정할 수 있다.</p>
<pre><code class="language-javascript">const user = {
  name: &quot;Yong&quot;
};

user.name = &quot;Dean&quot;;

user.name; // Dean</code></pre>
<p>물론, <code>let</code>으로 선언한 객체는 객체 자체도 수정할 수 있다.</p>
<h3 id="단축-프로퍼티">단축 프로퍼티</h3>
<p>프로퍼티 값을 매개변수에서 받아와 사용하는 경우가 종종 있다.</p>
<pre><code class="language-javascript">function makeUser(name, age) {
  return {
    name: name,
    age: age
  };
}

let user = makeUser(&quot;Yong&quot;, 27);
user.name; // Yong</code></pre>
<p>위 예시의 프로퍼티들은 키와 값의 이름이 매개변수와 동일하다. 이렇게 매개변수를 사용해 프로퍼티를 만드는 경우에는 <code>프로퍼티 값 단축 구문(property value shorthand)</code>을 사용해서 코드를 짧게 줄일 수 있다.</p>
<pre><code class="language-javascript">function makeUser(name, age) {
  return {
    name,    // == name: name
    age,    // == age: age
  };
}</code></pre>
<h3 id="프로퍼티-존재-여부-확인하기">프로퍼티 존재 여부 확인하기</h3>
<p>자바스크립트 객체는 존재하지 않는 프로퍼티에 접근해도 에러가 발생하지 않고, <code>undefined</code>가 반환된다.</p>
<p>이런 특징을 응용해서 프로퍼티 존재 여부를 쉽게 확인할 수 있다.</p>
<pre><code class="language-javascript">let user = {};

user.noSuchProperty === undefined;    // true</code></pre>
<p>다만 이러한 방식은 프로퍼티는 존재하지만 값이 없을 경우에도 존재하지 않는다고 출력되므로, 존재 여부 확인이 정확하지 않을 수 있다.</p>
<p>이런 문제점은 <code>in</code> 연산자를 사용해 해결할 수 있다.</p>
<pre><code class="language-javascript">// in 연산자 사용하기
let user = { name: &quot;Yong&quot;, age: 27 };

&quot;age&quot; in user;    // true
&quot;blabla&quot; in user;    // false</code></pre>
<h3 id="객체-순회하기">객체 순회하기</h3>
<p><code>for .. in</code> 반복문을 사용해서 객체의 모든 키를 순회할 수 있다.</p>
<pre><code class="language-javascript">for (let key in object) {
  // 각 프로퍼티 키를 이용해서 본문을 실행한다.
}</code></pre>
<h3 id="객체의-프로퍼티-정렬-방식">객체의 프로퍼티 정렬 방식</h3>
<p>정수 프로퍼티는 자동으로 정렬되고, 그 외의 프로퍼티는 객체에 추가한 순서대로 정렬된다.</p>
<pre><code class="language-javascript">// 정수 프로퍼티 예시
let codes = {
  &quot;49&quot;: &quot;독일&quot;,
  &quot;41&quot;: &quot;스위스&quot;,
  &quot;1&quot;: &quot;미국&quot;
};

for (let code in codes) {
  code;    // 1, 41, 49
}

// 정수가 아닌 프로퍼티 예시
let user = {
  name: &quot;Yong&quot;,
  surname: &quot;JunHyun&quot;
};
user.age = 27;

for (let prop in user) {
  prop;    // name, surname, age
}</code></pre>
<hr>
<h1 id="객체는-참조에-의해-복사된다">객체는 참조에 의해 복사된다</h1>
<p>객체형과 원시형의 근본적인 차이 중 하나는 변수를 다른 변수에 할당할 때, 원시형은 값 자체가 복사되어 저장되고, 객체는 참조에 의해 복사되고 저장된다는 것이다. </p>
<p>쉽게 말해 값 자체가 아닌, 값을 저장하고 있는 객체에 접근할 수 있는 메모리 주소가 복사된다.</p>
<p>이러한 특징을 활용하면 여러 변수를 사용하여 하나의 객체에 접근하거나 조작할 수 있다.</p>
<pre><code class="language-javascript">// 원시형 예시
let message = &quot;Hello!&quot;;
let phrase = message;

phrase = &quot;bye!&quot;;

alert(message);    // Hello!

// 객체형 예시
let user = { name: &quot;John&quot; };
let admin = user;

admin.name = &quot;Dean&quot;;

user.name    // Dean</code></pre>
<p>참조에 의해 복사된 객체는 동일하다. 따라서 객체 비교 시에 동등 연산자 <code>==</code>와 일치 연산자 <code>===</code>는 동일하게 동작한다.</p>
<p>비교 시 피연산자인 두 객체가 동일한 객체인 경우에 참을 반환한다.</p>
<pre><code class="language-javascript">let a = {};
let b = a;

a == b    // true;
a === b // true;</code></pre>
<h3 id="객체-복제와-병합">객체 복제와 병합</h3>
<p>객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어지는 것을 살펴보았다.</p>
<p>그런데 객체를 복제하고 싶다면 즉, 기존에 있던 객체와 똑같지만 독립적인 객체를 만들고 싶다면 어떻게 할까?</p>
<p>새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회하여 프로퍼티를 복사하면 된다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  age: 27
};

let clone = {};

for (let key in user) {
  clone[key] = user[key];
}

clone.name = &quot;Dean&quot;;

user.name;    // Yong</code></pre>
<p>또는 <code>Object.assign</code> 메소드를 사용해도 된다.</p>
<blockquote>
</blockquote>
<pre><code class="language-javascript">Object.assign(dest, [src1, src2, ...]);</code></pre>
<ul>
<li>첫 번째 인수 <code>dest</code> : 목적지 객체이다.</li>
<li>이어지는 인수들 <code>src1, src2, ...</code> : 복사하고자 하는 객체이다.</li>
<li>복사하고자하는 객체의 프로퍼티를 dest에 복사한다.<ul>
<li>목적지 객체에 동일한 이름을 가진 프로퍼티가 있는 경우에는 기존 값이 덮어씌워 진다.</li>
</ul>
</li>
<li>dest를 반환한다.</li>
</ul>
<pre><code class="language-javascript">let user = { name: &quot;Yong&quot; };
let permission1 = { canView: true };
let permission2 = { canEdit: true };

Object.assign(user, permission1, permission2);

// user = { name: &quot;Yong&quot;, canView: true, canEdit: true }</code></pre>
<p>위 메소드를 사용하여, 순회해서 복제했던 객체를 다시 만들어보자. 훨씬 간단하다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  age: 27
};

let clone = Object.assign({}, user);</code></pre>
<p>위 방식들은 중첩된 객체까지는 복사하지 못하므로, <strong>얕은 복사</strong>라 한다.</p>
<h3 id="중첩-객체-복제">중첩 객체 복제</h3>
<p>프로퍼티가 중첩 객체일 때는 어떻게 할까?</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  sizes: {
    height: 190,
    width: 100
  }
};</code></pre>
<p>단순히 <code>clone.sizes = user.sizes</code>로는 객체를 복제할 수 없다. 왜냐하면 중첩된 <code>user.sizes</code>는 객체이기 때문에 참조 값이 복사되어, <code>clone</code>과 <code>user</code>는 같은 <code>sizes</code> 객체를 공유하게 된다.</p>
<p>이 문제를 해결하려면 <code>user</code>의 각 키를 검사하면서, 그 키가 객체인 경우에는 해당 객체의 구조도 순회하는 반복문을 사용해야 한다.</p>
<p>이런 방식을 <strong>깊은 복사</strong>라 한다.</p>
<hr>
<h1 id="메서드method">메서드(method)</h1>
<p>객체는 사용자, 주문 등과 같이 실제 존재하는 개체를 표현하고자 할 때 생성된다.</p>
<p>사용자는 현실에서 장바구니에서 물건 선택하기, 로그인하기 등의 행동을 한다.</p>
<p>이와 마찬가지로 사용자를 나타내는 객체도 특정한 행동을 할 수 있다.</p>
<p>자바스크립트에선 객체의 프로퍼티에 함수를 할당하여 객체에 행동을 할 수 있는 기능을 부여한다.</p>
<h3 id="메서드-생성하기">메서드 생성하기</h3>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  age: 27
};

user.sayHi = function() {
  alert(&quot;Hello!&quot;);
};

user.sayHi();    // Hello!</code></pre>
<p>함수 표현식으로 함수를 만들고, 객체 프로퍼티에 함수를 할당해 주었다.</p>
<p>이렇게 객체 프로퍼티에 할당된 함수를 <strong>메서드</strong>라 한다.</p>
<p>이미 정의된 함수를 이용해 메서드를 만들 수도 있다.</p>
<pre><code class="language-javascript">let user = {
  ...
};

function sayHi() {
  alert(&quot;Hello!&quot;);
}

user.sayHi = sayHi;

user.sayHi();    // Hello!</code></pre>
<h3 id="메서드-단축-구문">메서드 단축 구문</h3>
<p>객체 리터럴 안에 메서드를 단축 구문으로 선언할 수 있다.</p>
<pre><code class="language-javascript">// 일반적인 메서드 선언
user = {
  sayHi: function() {
    alert(&quot;Hello&quot;);
  }
};

// 단축 구문을 이용한 메서드 선언
user = {
  sayHi() {
    alert(&quot;Hello&quot;);
  }
};</code></pre>
<h3 id="메서드와-this">메서드와 this</h3>
<p>메서드는 객체에 저장된 정보(프로퍼티)에 접근할 수 있어야 제 역할을 할 수 있다.</p>
<p>메서드 내부에서 <code>this</code> 키워드를 사용하면 메서드를 호출한 객체에 접근할 수 있다.</p>
<p><code>this</code>는 점(.) 앞의 객체를 참조한다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  age: 27,

  sayHi() {
    alert(this.name);
  }

};

user.sayHi()  // Yong</code></pre>
<h3 id="자유로운-this">자유로운 this</h3>
<p>자바스크립트에선 모든 함수에 <code>this</code> 키워드를 사용할 수 있다.</p>
<p><code>this</code> 값은 런타임에 결정된다. 즉, 런타임의 컨텍스트에 따라 가리키는 객체가 달라진다.</p>
<p>동일한 함수라도 다른 객체에서 호출했다면 <code>this</code>가 참조하는 값이 달라진다.</p>
<pre><code class="language-javascript">let user = { name: &quot;Yong&quot; };
let admin = { name: &quot;Dean&quot; };

function sayHi() {
  alert( this.name ) ;
}

user.f = sayHi;
admin.f = sayHi;

user.f();  // Yong
admin.f();  // Dean</code></pre>
<h3 id="this가-없는-화살표-함수">this가 없는 화살표 함수</h3>
<p>화살표 함수는 일반 함수와 달리 고유한 <code>this</code>를 가지지 않는다.</p>
<p>화살표 함수에서 <code>this</code>를 참조하면, 화살표 함수가 아닌 외부 함수에서 <code>this</code> 값을 가져온다.</p>
<p>아래 예시에서 함수 <code>arrow()</code>의 <code>this</code>는 외부 함수 <code>sayHi()</code>의 <code>this</code>가 된다.</p>
<pre><code class="language-javascript">let user = {
  firstName: &quot;준현&quot;,
  sayHi() {
    let arrow = () =&gt; alert(this.firstName);
  }
};

user.sayHi();    // 준현</code></pre>
<p>이러한 특징은 별개의 <code>this</code>가 만들어지는 건 원하지 않고 외부 컨텍스트에 있는 <code>this</code>를 이용하고 싶은 경우에 유용하다.</p>
<hr>
<h1 id="new-연산자와-생성자-함수">new 연산자와 생성자 함수</h1>
<p>객체 리터럴을 사용하면 객체를 쉽게 만들 수 있지만, 개발을 하다 보면 유사한 객체를 여러 개 만들어야 할 때가 생길 수 있다.</p>
<p>그럴 때 <code>new</code> 연산자와 <code>생성자 함수</code>를 사용하면 유사한 객체 여러 개를 쉽게 만들 수 있다.</p>
<h3 id="생성자-함수constructor-function">생성자 함수(constructor function)</h3>
<p>생성자 함수는 두 가지 관례를 따른다.</p>
<blockquote>
</blockquote>
<ul>
<li>함수 이름의 첫 글자는 대문자로 시작한다.</li>
<li><code>new</code> 연산자를 붙여 실행한다.</li>
</ul>
<p>생성자 함수를 실행하면 다음과 같은 알고리즘이 동작한다.</p>
<blockquote>
</blockquote>
<ol>
<li>빈 객체를 만들어 <code>this</code>에 할당한다.</li>
<li>함수 본문을 실행한다. <code>this</code>에 새로운 프로퍼티를 추가해 <code>this</code>를 수정한다.</li>
<li><code>this</code>를 반환한다.</li>
</ol>
<pre><code class="language-javascript">function User(name) {
  // this = {};        빈 객체가 내부적으로 만들어짐

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;    this가 내부적으로 반환됨
}

let user = new User(&quot;준현&quot;);

user.name;    // 준현
user.isAdmin;    // false</code></pre>
<p>위 방식은 객체 리터럴 문법으로 일일이 객체를 만드는 방법보다 간단하게 여러 객체를 만들 수 있다. 생성자 함수의 의의는 바로 여기에 있다. <strong>재사용할 수 있는 객체 생성 코드를 구현하는 것이다.</strong></p>
<p>재사용할 필요가 없는 복잡한 객체를 만들어야 할 때는 어떻게 할까? 이럴 때는 익명 생성자 함수로 감싸주는 방식을 사용할 수 있다.</p>
<pre><code class="language-javascript">let user = new function() {
  this.name = &quot;Yong&quot;;
  this.isAdmin = false;
};</code></pre>
<p>위 생성자 함수는 익명 함수이기 때문에 어디에도 저장되지 않는다. 만들 때부터 단 한 번만 호출되기 때문에 재사용도 불가능하다. 이렇게 익명 생성자 함수를 이용하면, 재사용은 막으면서 코드를 캡슐화할 수 있다.</p>
<h3 id="생성자-함수와-return문">생성자 함수와 return문</h3>
<p>생성자 함수엔 보통 <code>return</code>문이 없다. 반환해야 할 것들은 모두 <code>this</code>에 저장되고, <code>this</code>는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없다.</p>
<p>만약 <code>return</code> 문이 있다면 아래와 같은 규칙이 적용된다.</p>
<blockquote>
</blockquote>
<ul>
<li>객체를 <code>return</code> 한다면 <code>this</code> 대신에 명시한 객체가 반환된다.</li>
<li>원시형을 <code>return</code>한다면 <code>return</code>문이 무시되고, <code>this</code>가 반환된다.</li>
</ul>
<pre><code class="language-javascript">// 객체를 리턴할 때
function BigUser() {

  this.name = &quot;원숭이&quot;;

  return { name: &quot;고릴라&quot; };  // &lt;-- this가 아닌 새로운 객체를 반환함
}

alert( new BigUser().name );  // 고릴라

// 직접 명시하여 아무것도 리턴하지 않을 때
function SmallUser() {

  this.name = &quot;원숭이&quot;;

  return; // &lt;-- this를 반환함
}

alert( new SmallUser().name );  // 원숭이</code></pre>
<h3 id="생성자-함수-내-메서드">생성자 함수 내 메서드</h3>
<p>생성자 함수를 사용하면 매개변수를 이용해 객체 내부를 자유롭게 구성할 수 있다.</p>
<pre><code class="language-javascript">function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( &quot;제 이름은 &quot; + this.name + &quot;입니다.&quot; );
  };
}

let yong = new User(&quot;용준현&quot;);

yong.sayHi();    // 제 이름은 용준현입니다.</code></pre>
<hr>
<h1 id="옵셔널-체이닝optional-chaining-">옵셔널 체이닝(optional chaining) ?.</h1>
<p>옵셔널 체이닝을 사용하여 프로퍼티가 없는 객체를 에러 없이 안전하게 접근할 수 있다.</p>
<h3 id="옵셔널-체이닝이-필요한-이유">옵셔널 체이닝이 필요한 이유</h3>
<p>브라우저에서 동작하는 코드를 개발할 때, 다음과 같은 문제가 발생할 때가 있다.</p>
<p>자바스크립트를 사용해 페이지에 존재하지 않는 요소에 접근해서 요소의 정보를 가져오려 하면 문제가 발생한다.</p>
<pre><code class="language-javascript">// querySelector 호출 결과가 null인 경우에 에러 발생
let html = document.querySelector(&quot;.my-element&quot;).innerHTML;</code></pre>
<p>명세서에 옵셔널 체이닝이 추가되기 전에 이런 문제들을 해결하기 위해 첫 번째 falsy를 찾는 <code>&amp;&amp;</code> 연산자를 사용하곤 했다.</p>
<pre><code class="language-javascript">let user = {}; // 주소 정보가 없는 사용자

alert( user &amp;&amp; user.address &amp;&amp; user.address.street ); // undefined, 에러가 발생하지 않습니다.</code></pre>
<p>중첩 객체의 특정 프로퍼티에 접근하기 위해 거쳐야 할 요소들을 <code>AND</code>로 연결해 실제 해당 객체나 프로퍼티가 있는지 확인하는 방법을 사용했다. 이런 방식은 코드가 길어진다는 단점이 있다.</p>
<h3 id="옵셔널-체이닝의-등장">옵셔널 체이닝의 등장</h3>
<p><code>?.</code>은 앞의 평가 대상이 <code>undefined</code>나 <code>null</code>이면 평가를 멈추고, <code>undefined</code>를 반환한다.</p>
<p>옵셔널 체이닝을 사용해 <code>user.address.street</code>에 안전하게 접근해보자.</p>
<pre><code class="language-javascript">let user = {};

alert( user?.address?.street );    // undefined. 에러가 발생하지 않는다.</code></pre>
<p>옵셔널 체이닝을 사용하면 아예 객체가 존재하지 않더라도 에러가 발생하지 않는다.</p>
<pre><code class="language-javascript">let user = null;

alert( user?.address );    // undefined
alert( user?.address.street );    // undefined</code></pre>
<p><code>?.</code>은 앞 평가 대상에만 동작하고, 확장은 되지 않는다. </p>
<p>쉽게 말해, 위 예시에서 <code>user?.</code>는 <code>user</code>가 <code>null</code>이나 <code>undefined</code>인 경우만 처리할 수 있다. <code>user</code>가 실제 값이 존재할 때, <code>user.address</code> 프로퍼티가 반드시 존재해야 한다. 그렇지 않으면, 위 경우에서 옵셔널 체이닝을 사용하지 않은 두 번째 점 연산자에서 에러가 발생한다.</p>
<h3 id="옵셔널-체이닝을-남용하지-말자">옵셔널 체이닝을 남용하지 말자</h3>
<p><code>?.</code>는 존재하지 않아도 괜찮은 대상에만 사용해야 한다.</p>
<p>사용자 주소를 다루는 위 예시에서 논리상 <code>user</code>는 반드시 존재해야 하는데, <code>address</code>는 필수값이 아니다.</p>
<p>그러니 <code>user.address?.street</code>를 사용하는 것이 바람직하다.</p>
<p>이유는 <code>user</code>에 값을 할당하지 않았다면 바로 알아낼 수 있어야 하기 때문이다. 그렇지 않으면 에러를 조기에 발견하지 못해 디버깅이 어려워진다.</p>
<h3 id="-앞의-변수는-꼭-선언되어-있어야--한다">?. 앞의 변수는 꼭 선언되어 있어야  한다.</h3>
<p>옵셔널 체이닝은 선언이 완료된 변수를 대상으로만 동작한다.</p>
<h3 id="옵셔널-체이닝과-단락-평가">옵셔널 체이닝과 단락 평가</h3>
<p><code>?.</code>는 왼쪽 평가대상이 <code>undefined</code>이거나 <code>null</code>이면 즉시 평가를 멈춘다.</p>
<p>이런 평가 방법을 단락 평가라고 부른다.</p>
<p>이러한 특성 때문에 <code>?.</code> 오른쪽에 있는 부가 동작은 <code>?.</code>의 평가가 멈췄을 때 더는 일어나지 않는다.</p>
<pre><code class="language-javascript">let user = null;
let x = 0;

user?.sayHi(x++);    // 아무 일도 일어나지 않는다.
alert(x);    // 0, x는 증가하지 않는다.</code></pre>
<h3 id="와-">?.()와 ?.[]</h3>
<p><code>?.</code>은 연산자가 아니고, 함수나 대괄호와 함께 동작하는 특별한 문법 구조체이다.</p>
<p>존재 여부가 확실치 않은 함수를 호출할 때의 활용 방법을 살펴보자.</p>
<p>한 객체엔 <code>admin</code> 메서드가 있지만 다른 객체에는 없는 상황이다.</p>
<pre><code class="language-javascript">let user1 = {
  admin() {
    alert(&quot;관리자 계정입니다.&quot;);
  }
}

let user2 = {};

user1.admin?.();    // 관리자 계정입니다.
user2.admin?.();    //</code></pre>
<p><code>user1</code>에는 <code>admin</code>이 정의되어 있기 때문에 메서드가 제대로 호출되었다.</p>
<p>반면 <code>user2</code>에는 <code>admin</code>이 정의되어 있지 않았음에도, 메서드를 호출했을 때 에러 없이 평가가 멈추었다.</p>
<p>또, 점 표기법 대신 대괄호 표기법<code>[]</code>를 사용하여 객체 프로퍼티에 접근하는 경우에도 <code>?.[]</code>를 사용할 수 있다.</p>
<p>옵셔널 체이닝을 사용하면 객체의 존재 여부가 확실치 않은 경우에도 대괄호 표기법을 사용해 프로퍼티를 읽을 수 있다.</p>
<pre><code class="language-javascript">let user1 = {
  firstName: &quot;Violet&quot;
};

let user2 = null;

let key = &quot;firstName&quot;;

alert( user1?.[key] );    // violet
alert( user2?.[key] );    // undefined 에러가 발생하지 않음</code></pre>
<h3 id="옵셔널-체이닝을-이용한-프로퍼티-삭제">옵셔널 체이닝을 이용한 프로퍼티 삭제</h3>
<p>옵셔널 체이닝과 <code>delete</code>를 조합해 객체의 프로퍼티를 삭제할 수도 있다.</p>
<pre><code class="language-javascript">delete user?.name;    // user가 존재하면 user.name을 삭제한다.</code></pre>
<h3 id="옵셔널-체이닝을-이용한-프로퍼티-쓰기">옵셔널 체이닝을 이용한 프로퍼티 쓰기</h3>
<p>당연하게도 옵셔널 체이닝은 읽기나 삭제를 할 순 있지만, 쓸 수는 없다.</p>
<p>옵셔널 체이닝의 앞 쪽이 <code>undefined</code>나 <code>null</code> 인 곳에 값을 쓸 수는 없기 때문이다.</p>
<hr>
<h1 id="심볼형">심볼형</h1>
<p>자바스크립트는 객체 프로퍼티 키로 오직 문자형과 심볼형만을 허용한다.</p>
<h3 id="심볼symbol">심볼(symbol)</h3>
<p>심볼은 원시형 데이터로, 유일한 식별자(unique identifier)를 만들고 싶을 때 사용한다.</p>
<p><code>Symbol()</code>을 사용하여 심볼값을 만들 수 있다.</p>
<p>심볼 이름이라 불리는 설명을 붙일 수도 있다. 심볼 이름은 디버깅 시 유용하다.</p>
<pre><code class="language-javascript">// 심볼 선언
let id = Symbol();


// 심볼이름이 붙은 심볼 선언
let id = Symbol(&quot;id&quot;);</code></pre>
<p>심볼은 유일성이 보장되는 자료형이기 때문에, 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값은 다르다. 심볼 이름은 심볼에 붙이는 설명일 뿐, 어떤 것에도 영향을 주지 않는 이름표 역할만 한다.</p>
<pre><code class="language-javascript">let id1 = Symbol(&quot;id&quot;);
let id2 = Symbol(&quot;id&quot;);

id1 == id2;    // false</code></pre>
<h3 id="심볼은-문자형으로-자동-형-변환-되지-않는다">심볼은 문자형으로 자동 형 변환 되지 않는다.</h3>
<p>자바스크립트에선 문자형으로의 내부적 형 변환이 비교적 자유롭게 일어난다.</p>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);
alert(id);    // TypeError: Cannot convert a Symbol value to a string</code></pre>
<p>문자열과 심볼은 근본이 다르기 때문에 우연히라도 서로의 타입으로 변환돼선 안된다.</p>
<p>자바스크립트에선 언어 차원의 보호장치(language guard)를 마련해 심볼이 다른 형으로 변환되지 않게 막아준다.</p>
<p><code>description</code> 프로퍼티를 이용해서 심볼 이름만 보여주는 것도 가능하다.</p>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);
alert( id.description );    // id</code></pre>
<h3 id="숨김-프로퍼티">숨김 프로퍼티</h3>
<p>심볼을 이용하면 숨김 프로퍼티를 만들 수 있다. </p>
<p>숨김 프로퍼티는 외부 코드에서 접근이 불가능하고, 갚도 덮어쓸 수 없는 프로퍼티이다. 이를 활용해 현재 스크립트에서 외부 스크립트에 속한 객체에 새로운 프로퍼티를 추가할 수 있다.</p>
<p>서드파티 코드에서 가지고 온 <code>user</code>라는 객체가 여러 개 있고, <code>user</code>를 이용해 어떤 작업을 해야 하는 하는 상황이라고 가정하자. <code>user</code>에 심볼을 이용한 식별자를 붙일 수 있다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;
};

let id = Symbol(&quot;id&quot;);

user[id] = 1;

// 심볼을 키로 사용해 데이터에 접근
alert( user[id] );    // 1 </code></pre>
<p>문자열 <code>&quot;id&quot;</code>를 키로 사용하지 않고, <code>Symbol(&quot;id&quot;)</code>를 사용한 이유가 무엇일까?</p>
<p><code>user</code>는 서드파티 코드에서 가지고 온 객체이므로 함부로 새로운 프로퍼티를 추가할 수 없다.</p>
<p>그런데 심볼은 서드파티 코드에서 접근할 수 없기 때문에, 심볼을 사용하면 서드파티 코드가 모르게 <code>user</code>에 식별자를 부여할 수 있다.</p>
<p>또 다른 상황을 살펴보자.</p>
<p>제 3의 스크립트에서 <code>user</code>를 식별해야 하는 상황이 벌어졌다고 가정하자. <code>user</code>의 원천인 서드파티 코드, 현재 작성 중인 스크립트, 제 3의 스크립트가 각자 서로의 코드도 모른 채 <code>user</code>를 식별해야 하는 상황이 벌어졌다.</p>
<p>제 3의 스크립트에서도 아래와 같이 <code>Symbol(&quot;id&quot;)</code>를 이용해 전용 식별자를 만들어 사용할 수 있다.</p>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);

user[id] = &quot;어떤 스크립트 id 값&quot;;</code></pre>
<p>심볼은 유일성이 보장되므로 이름이 같더라도 우리가 만든 식별자와 어떤 스크립트에서 만든 식별자가 충돌하지 않는다.</p>
<p>만약 심볼 대신 문자열 <code>&quot;id&quot;</code>를 사용해 식별자를 만들었다면 충돌이 발생하고 덮어씌어질 가능성이 있다.</p>
<h3 id="리터럴에서의-심볼">리터럴에서의 심볼</h3>
<p>객체 리터럴을 사용해 객체를 만든 경우, 대괄호를 사용해 심볼형 키를 만들어야 한다.</p>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);

let user = {
  name: &quot;John&quot;,
  [id]: 123
};</code></pre>
<h3 id="키가-심볼인-프로퍼티는-for-in-반복문에서-배제된다">키가 심볼인 프로퍼티는 for in 반복문에서 배제된다.</h3>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);

let user = {
  name: &quot;Yong&quot;,
  age: 27,
  [id]: 123
};

for (let key in user) alert(key);    // name, age</code></pre>
<p><code>Object.keys(user)</code> 에서도 키가 심볼인 프로퍼티는 배제된다.</p>
<p><code>심볼형 프로퍼티 숨기기(hiding symbolic property)</code>라 불리는 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못한다.</p>
<p>하지만 <code>Object.assgin</code>은 키가 심볼이 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사한다.</p>
<pre><code class="language-javascript">let id = Symbol(&quot;id&quot;);
let user = {
  [id]: 123
};

let clone = Object.assgin({}, user);

alert( clone[id] );    // 123</code></pre>
<h3 id="전역-심볼">전역 심볼</h3>
<p>심볼은 이름이 같더라도 모두 별개로 취급된다. 하지만 이름이 같은 심볼이 같은 개체를 가리키기를 원할 때도 있다.</p>
<p><code>전역 심볼 레지스트리(global symbol registry)</code>는 이런 경우를 위해 만들어졌다.</p>
<p>전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환해준다.</p>
<p>전역 심볼 레지스트리 안에 있는 심볼을 읽거나, 새로운 심볼을 생성하려면 <code>Symbol.for(key)</code>를 사용하면 된다.</p>
<p>이 메서드를 호출하면 심볼 이름이 <code>key</code>인 심볼을 반환한다. 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 <code>Symbol(key)</code>을 만들고 레지스트리 안에 저장한다.</p>
<pre><code class="language-javascript">// 전역 심볼 레지스트리에서 심볼 읽기. 심볼이 존재하지 않으면 새로운 심볼을 만듦
let id = Symbol.for(&quot;id&quot;);

// 동일한 이름을 이용해 심볼을 다시 읽기
let idAgain = Symbol.for(&quot;id&quot;);

alert( id === idAgain) );    // true</code></pre>
<p>전역 심볼 레지스트리 안에 있는 심볼을 <code>전역 심볼</code>이라 한다. 애플리케이션에서 광범위하게 사용해야 하는 심볼일 경우 전역 심볼을 사용하면 된다.</p>
<p>전역 심볼을 찾을 때 사용되는 <code>Symbol.for(key)</code>에 반대되는 메서드도 있다.</p>
<p><code>Symbol.keyFor(심볼)</code>를 사용하면 심볼을 사용하여 심볼 이름을 얻을 수 있다.</p>
<pre><code class="language-javascript">let sym = Symbol.for(&quot;name&quot;);
let sym2 = Symbol.for(&quot;id&quot;);

alert( Symbol.keyFor(sym) );    // name
alert( Symbol.keyFor(sym2) );    // id</code></pre>
<p><code>Symbol.keyFor</code>는 전역 심볼 레지스트리를 검색해서 해당 심볼의 이름을 얻어낸다. 검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없다. 전역 심볼이 아닌 인자가 넘어오면 <code>undefined</code>를 반환한다.</p>
<p>전역 심볼이 아닌 모든 심볼은 <code>description</code> 프로퍼티가 있다. 일반 심볼에서 심볼 이름을 얻고 싶으면 해당 프로퍼티를 사용하면 된다.</p>
<pre><code class="language-javascript">let globalSymbol = Symbol.for(&quot;name&quot;);
let localSymbol = Symbol(&quot;name&quot;);

alert ( Symbol.keyFor(globalSymbol) );    // name
alert ( Symbol.keyFor(localSymbol) );    // undefined

alert ( localSymbol.description );    // name</code></pre>
<h3 id="시스템-심볼system-symbol">시스템 심볼(system symbol)</h3>
<p>자바스크립트 내부에서 사용되는 심볼로, 이를 활용하여 객체를 미세 조정할 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>Symbol.hasInstance</code></li>
<li><code>Symbol.isConcatSpreadable</code></li>
<li><code>Symbol.iterator</code></li>
<li><code>Symbol.toPrimitive</code></li>
<li>etc ...</li>
</ul>
<h3 id="사실-심볼을-완전히-숨길-방법은-없다">사실 심볼을 완전히 숨길 방법은 없다.</h3>
<p><code>Object.getOwnPropertySymbols(obj)</code>를 사용하면 모든 심볼을 볼 수 있고, <code>Reflect.ownKeys(obj)</code>는 심볼형 키를 포함한 객체의 모든 키를 반환해준다.</p>
<p>하지만 대부분의 라이브러리, 내장 함수 등은 이런 메서드를 사용하지 않는다.</p>
<hr>
<h1 id="객체를-원시형으로-변환하기">객체를 원시형으로 변환하기</h1>
<p>객체에 원시값을 기대하는 내장 함수나 연산자를 사용할 때, 객체-원시형으로의 형 변환이 자동으로 일어난다.</p>
<blockquote>
</blockquote>
<ul>
<li>불리언으로의 형 변환에서 객체는 논리 평가 시 항상 <code>true</code>를 반환한다.</li>
<li>숫자형으로의 형 변환은 객체끼리 빼는 연산을 할 때나 수학 관련 함수를 적용할 때 일어난다. 예를 들어, 객체 <code>Date</code> 끼리 빼는 연산을 수행하면, 두 날짜의 시간 차이가 반환된다.</li>
<li>문자형으로의 형 변환은 대개 <code>alert(obj)</code> 같이 객체를 출력하려고 할 때 일어난다.</li>
</ul>
<h3 id="특수-객체-메서드를-사용한-객체의-형-변환">특수 객체 메서드를 사용한 객체의 형 변환</h3>
<p>특수 객체 메서드를 사용하면 숫자형이나 문자형으로의 형 변환을 원하는 대로 조절할 수 있다.</p>
<p>객체 형 변환은 세 종류로 구분되는데, <code>&quot;hint&quot;</code>라 불리는 값이 구분 기준이 된다. <code>&quot;hint&quot;</code>는 목표로 하는 자료형 정도로 이해하면 된다.</p>
<ul>
<li><code>&quot;string&quot;</code></li>
</ul>
<p><code>alert</code> 함수 같이 문자열을 기대하는 연산을 수행할 때는, hint가 <code>string</code>이 된다.</p>
<pre><code class="language-javascript">// 객체 출력
alert(obj);

// 객체를 프로퍼티 키로 사용
anotherObj[obj] = 123;</code></pre>
<ul>
<li><code>&quot;number&quot;</code>
수학 연산을 적용하려 할 때, hint는 <code>number</code>가 된다.</li>
</ul>
<pre><code class="language-javascript">// 명시적 형 변환
let num = Number(obj);

// 수학 연산(이항 덧셈 연산 제외)
let n = +obj;
let delta = date1 - date2;

// 크기 비교
let greater = user1 &gt; user2;</code></pre>
<ul>
<li><code>&quot;default&quot;</code>
연산자가 기대하는 자료형이 확실치 않을 때, hint는 <code>default</code>가 된다. 이런 상황은 아주 드물게 발생한다.</li>
</ul>
<p>hint가 <code>&quot;number</code>일 때 이항 덧셈 연산은 제외하였는데, 이항 덧셈 연산자 <code>+</code>는 피연산자의 자료형에 따라 문자열을 합치는 연산일 수도 있고, 숫자를 더해주는 연산을 할 수도 있기 때문이다.</p>
<p>동등 연산자 <code>==</code>를 사용해 객체와 원시형을 비교할 때도, 객체를 어떤 자료형으로 바꿔야 할지 확신이 안 서므로 hint는 <code>default</code>가 된다.</p>
<pre><code class="language-javascript">// 이항 덧셈 연산
let total = obj1 + obj2;

// 동등 연산
if (user == 1) { ... };</code></pre>
<p><code>Date</code> 객체를 제외한 모든 내장 객체는 hint가 <code>default</code>인 경우와 <code>number</code>인 경우를 동일하게 처리한다. 따라서, 커스텀 객체를 만들 때 이러한 규칙을 따르면 된다.</p>
<p>결론적으로 객체와 원시형의 형 변환은 두 종류만 남게된다.</p>
<blockquote>
</blockquote>
<ul>
<li>객체 - 문자형</li>
<li>객체 - 숫자형</li>
</ul>
<p>자바스크립트는 형 변환이 필요할 때, 아래와 같은 알고리즘에 따라 원하는 메서드를 찾고 호출한다.</p>
<blockquote>
</blockquote>
<ol>
<li>객체에 <code>obj[Symbol.toPrimitive](hint)</code> 메서드가 있는지 찾는다. 있다면 메서드를 호출한다.<ul>
<li><code>Symbol.toPrimitive</code>는 시스템 심볼로, 심볼형 키로 사용된다.</li>
</ul>
</li>
<li>1에 해당하지 않고 hint가 <code>&quot;string&quot;</code> 이라면,<ul>
<li><code>obj.toString()</code> 이나 <code>obj.valueOf()</code>를 호출한다.</li>
</ul>
</li>
<li>1과 2에 해당하지 않고 hint가 <code>&quot;number&quot;</code>나 <code>&quot;default&quot;</code> 라면<ul>
<li><code>obj.valueOf()</code> 나 <code>obj.toString()</code> 을 호출한다.</li>
</ul>
</li>
</ol>
<h4 id="symboltoprimitive">Symbol.toPrimitive</h4>
<p>자바스크립트에는 <code>Symbol.toPrimitive</code>라는 내장 심볼이 존재한다.</p>
<p>이 심볼은 아래와 같이 hint를 명명하는 데 사용된다.</p>
<pre><code class="language-javascript">obj[Symbol.toPrimitive] = function(hint) {
  // 반드시 원시값을 반환해야 함
  // hint는 &quot;string&quot;, &quot;number&quot;, &quot;default&quot; 중 하나가 될 수 있음
};

// 실제 예시
let user = {
  name: &quot;Yong&quot;,
  money: 1000,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == &quot;string&quot; ? `{name: &quot;${this.name}&quot;}` : this.money;
  }
};

alert(user);    // hint: string =&gt; {name: &quot;Yong&quot;}
alert(+user);    // hint: number =&gt; 1000
alert(user + 500);    // hint: default =&gt; 1500</code></pre>
<p>이렇게 메서드를 구현해 놓으면 객체 <code>user</code>는 hint에 따라 문자열로 변환되기도 하고, 숫자로 변환되기도 한다.</p>
<h4 id="tostring과-valueof">toString과 valueOf</h4>
<p><code>toString</code>과 <code>valueOf</code>는 심볼이 생기기 이전부터 존재했던 메서드이다.</p>
<p>이 메서드를 이용하면 구식이긴 하지만 형 변환을 직접 구현할 수 있다.</p>
<p>객체에 <code>Symbol.toPrimitive</code>가 없으면 자바스크립트는 아래 규칙에 따라 <code>toString</code>이나 <code>valueOf</code>를 호출한다.</p>
<blockquote>
</blockquote>
<ul>
<li>hint가 <code>&quot;string&quot;</code>인 경우 : <code>toString, valueOf</code> 순으로 호출</li>
<li>hint가 <code>&quot;number, &quot;default&quot;</code>인 경우 : <code>valueOf, toString</code> 순으로 호출</li>
</ul>
<p>이 메서드들은 반드시 원시값을 반환해야 한다. 객체를 반환하면 그 결과는 무시된다.</p>
<p>일반 객체는 기본적으로 다음 규칙을 따른다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>toString</code>은 문자열 <code>&quot;[object Object]&quot;</code>을 반환한다.</li>
<li><code>valueOf</code>는 객체 자신을 반환한다.</li>
</ul>
<pre><code class="language-javascript">let user = {name: &quot;Yong&quot;};

alert(user);    // [object Object]
alert(user.valueOf() === user);    // true</code></pre>
<p>이런 이유 때문에 <code>alert</code>에 객체를 넘기면 <code>[object Object]</code>가 출력되는 것이다.</p>
<p><code>valeOf</code>는 객체 자신을 반환하기 때문에 그 결과가 무시된다. 우리는 그냥 이 메서드가 존재하지 않는다고 생각하면 된다.</p>
<p>이제 직접 이 메서드들을 사용한 예시를 구현해보자.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,
  money: 1000,

  // hint가 &quot;string&quot;인 경우
  toString() {
    return `{name: &quot;${this.name}&quot;}`;
  },

  // hint가 &quot;number&quot; 나 &quot;default&quot; 인 경우
  valueOf() {
    return this.money;
  }

};

alert(user);    // {name: &quot;Yong&quot;}
alert(+user);    // 1000
alert(user + 500);    // 1500</code></pre>
<p>출력 결과가 <code>Symbol.toPrimitive</code>를 사용한 예제와 완전히 동일하다.</p>
<p>간혹 모든 형 변환을 한 곳에서 처리해야 하는 경우도 생긴다.</p>
<p>이럴 땐 아래와 같이 <code>toString</code>만 구현하면된다.</p>
<pre><code class="language-javascript">let user = {
  name: &quot;Yong&quot;,

  toString() {
    return this.name;
  }
};

alert(user);    // Yong
alert(user + 500);    // Yong500</code></pre>
<p>객체에 <code>Symbol.toPrimitive</code>와 <code>valueOf</code>가 없으면, <code>toString</code>이 모든 형 변환을 처리한다.</p>
<h3 id="반환-타입">반환 타입</h3>
<p>위에서 살펴 본 세 개의 메서드는 hint에 명시된 자료형으로의 형 변환을 보장해주지 않는다.</p>
<p>확신할 수 있는 단 한 가지는 객체가 아닌 원시값을 반환해 준다는 것 뿐이다.</p>
<h3 id="추가-형-변환">추가 형 변환</h3>
<p>상당수의 연산자와 함수가 피연산자의 형을 변환시킨다.</p>
<p>객체가 피연산자일 때는 다음과 같은 단계를 거쳐 형 변환이 일어난다.</p>
<blockquote>
</blockquote>
<ol>
<li>객체는 원시형으로 변환된다.</li>
<li>변환 후 원시간이 원하는 형이 아닌 경우에 또 다시 형 변환이 일어난다.</li>
</ol>
<p><code>obj.toString()</code>만 사용해도 모든 변환을 다 다룰 수 있기 때문에, 실무에선 <code>obj.toString()</code>만 구현해도 충분한 경우가 많다. 반환값도 사람이 읽고 이해할 수 있는 형식이기 때문에 실용성 측면에서 다른 메서드에 뒤처지지 않는다. </p>
<p>또, <code>obj.toString()</code>은 로깅이나 디버깅 목적으로도 자주 사용된다.</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/object-basics">https://ko.javascript.info/object-basics</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 14 자바스크립트) 트랜스파일과 폴리필]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-14-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EA%B3%BC-%ED%8F%B4%EB%A6%AC%ED%95%84</link>
            <guid>https://velog.io/@dev_diver/22.-10.-14-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EA%B3%BC-%ED%8F%B4%EB%A6%AC%ED%95%84</guid>
            <pubDate>Fri, 14 Oct 2022 06:12:39 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트는 끊임없이 진화하는 언어로, 명세서에 계속해서 기능들이 추가된다.</p>
<p>등록된 지 얼마 안 된 기능을 사용해 코드를 작성하다 보면 특정 엔진에서 해당 기능들을 지원하지 않을 수도 있다.</p>
<p>이 때 사용할 수 있는 것이 바벨이다.</p>
<h1 id="바벨babel">바벨(Babel)</h1>
<p>바벨의 주요 역할은 다음과 같다.</p>
<h3 id="트랜스파일transpile">트랜스파일(transpile)</h3>
<p>명세서엔 새로운 문법이나 기존에 없던 내장 함수에 대한 정의가 추가되곤 한다.</p>
<p>새로운 문법을 사용해 코드를 작성하면 트랜스파일러는 이를 구 표준을 준수하는 코드로 변경해준다.</p>
<p>바벨은 이렇게 코드를 재작성해주는 트랜스파일러 프로그램이다.</p>
<p>변경된 코드는 웹사이트 형태로 사용자에게 전달된다.</p>
<p>참고로, 웹팩같은 모던 프로젝트 빌드 시스템은 코드가 수정될 때마다 자동으로 트랜스파일러를 동작시켜준다.</p>
<h3 id="폴리필polyfill">폴리필(polyfill)</h3>
<p>반면, 새롭게 표준에 추가된 함수는 명세서 내의 정의를 읽고, 이에 맞게 직접 함수를 구현해야 사용할 수 있다.</p>
<p>자바스크립트는 매우 동적인 언어라서 원하면 어떤 함수라도 스크립트에 추가할 수 있다.</p>
<p>물론 기존 함수를 수정하는 것도 가능하다.</p>
<p>개발자는 스크립트에 새로운 함수를 추가하거나 수정해서 스크립트가 최신 표준을 준수할 수 있게 작업할 수 있다.</p>
<p>이렇게 변경된 표준을 준수할 수 있게 기존 함수의 동작 방식을 수정하거나, 새롭게 구현한 함수의 스크립트를 <strong>폴리필</strong>이라 부른다.</p>
<p>쉽게 말해, 웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드가 폴리필이다.</p>
<p>말 그대로 구현이 누락된 새로운 기능을 메꿔주는(fill in) 역할을 한다.</p>
<p>예시를 들자면, map 함수를 지원하지 않는 브라우저에서 다른 방법으로 map 함수의 기능을 구현하는 것을 말한다. </p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/code-quality">https://ko.javascript.info/code-quality</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 14 자바스크립트) 테스트]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-13-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@dev_diver/22.-10.-13-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 14 Oct 2022 05:55:49 GMT</pubDate>
            <description><![CDATA[<h1 id="테스트는-왜-해야-하는가">테스트는 왜 해야 하는가?</h1>
<p>개발자들은 보통 콘솔 창 등을 사용하며, 실행한 결과가 기대했던 결과와 같은지 계속 비교하면서 원하는 기능이 잘 구현되는지 확인한다.</p>
<p>실제 실행 결과가 기대했던 결과와 다르면, 코드를 수정하고 다시 실행하여 기대했던 결과와 비교하는 과정을 원하는 기능을 완성할 때까지 반복한다.</p>
<p>이렇게 코드를 수동으로 재실행하면서 테스트하는 방식은 불완전하다.</p>
<p>예를 들어, 함수 f를 구현한다고 가정하자. </p>
<p>코드를 작성하고 f(1)이 제대로 동작하는지 확인한다. 제대로 동작하는 것을 확인하고, f(2)를 테스트하지만 제대로 동작하지 않는다. 코드를 수정하고 f(2)를 재확인한다. 이번엔 제대로 동작한다. 지금 시점에 f(1)이 제대로 동작할까? 테스트하지 않았으므로 불분명하다.</p>
<p>이렇게 수동으로 테스트를 하면 에러가 발생할 확률이 생긴다.</p>
<p>이러한 문제들은 테스트 자동화를 통해 다양한 조건에서 코드를 실행하여, 실행 결과와 기대 결과를 비교할 수 있다.</p>
<hr>
<h1 id="behavior-driven-developmentbdd">Behavior Driven Development(BDD)</h1>
<p><code>테스트(test)</code>, <code>문서(documentation)</code>, <code>예시(example)</code>를 한데 모아놓은 개념이다.</p>
<h3 id="명세서specification-spec">명세서(specification, spec)</h3>
<p>코드를 작성하기 전에, 코드가 무슨 일을 하는지 상상한 후 이를 자연어로 표현해야 한다.</p>
<p>이 때, 만들어진 산출물을 BDD에선 <code>명세서</code> 또는 <code>스펙</code>이라 부른다.</p>
<p>스펙엔 아래와 같이 유스 케이스에 대한 자세한 설명과 테스트가 담겨있다.</p>
<pre><code class="language-javascript">describe(&quot;pow&quot;, function() {

  it(&quot;주어진 숫자의 n 제곱&quot;, function() {
    assert.equal(pow(2, 3), 8);
  });

});</code></pre>
<p>스펙은 세 가지 주요 구성 요소로 이루어진다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>describe(&quot;title&quot;, function() { ... })</code> : 구현하고자 하는 기능에 대한 설명이 들어간다.    </li>
<li><code>it(&quot;유스 케이스 설명&quot;, function() { ... })</code> : it의 첫 번째 인수에는 특정 유스 케이스에 대한 설명이 들어간다. 누구나 읽을 수 있고, 이해할 수 있는 자연어로 적어준다. 두 번째 인수에는 해당 유스 케이스에 대한 테스트 함수가 들어간다.</li>
<li><code>assert.equal(value1, value2)</code> : 기능을 제대로 구현했다면, <code>it</code> 블록 내의 코드 <code>assert</code>문이 에러 없이 실행된다. 함수 <code>assert.*</code>는 구현하고자 하는 기능이 제대로 동작하는지 확인해 준다. 위 예시에서 사용된 <code>assert.equal</code>는 인수끼리 동등 비교를 했을 때, 다르다고 판단되면 에러를 반환한다.</li>
</ul>
<p>스펙은 실행 가능하다. 스펙을 실행하면 <code>it</code> 블록 안의 테스트가 실행된다.</p>
<h3 id="다양한-assertion">다양한 assertion</h3>
<p>Chai는 다양한 assertion을 지원한다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>assert.equal(value1, value2)</code> : value1과 value2의 동등성을 확인한다.</li>
<li><code>assert.strictEqual(value1, value2)</code> : value1과 value2의 일치성을 확인한다.</li>
<li><code>assert.notEqual</code> : 비동등성을 확인한다.</li>
<li><code>assert.notStrictEqual</code> : 비일치성을 확인한다.</li>
<li><code>assert.isTrue(value)</code> : value가 true인지 확인한다.</li>
<li><code>assert.isFalse(value)</code> : value가 false인지 확인한다.</li>
<li>이외에도 다양한 assertion을 확인할 수 있다. <a href="https://www.chaijs.com/api/assert/">https://www.chaijs.com/api/assert/</a></li>
</ul>
<h3 id="bdd를-적용한-기능-개발-프로세스">BDD를 적용한 기능 개발 프로세스</h3>
<p>실제 개발에 착수하면 아래와 같은 순서로 개발이 진행된다.</p>
<blockquote>
</blockquote>
<ol>
<li>기본적인 테스트가 포함된 스펙 초안을 작성한다.</li>
<li>스펙 초안을 보고 코드를 작성한다. 코드가 작동하는지 확인하기 위해 테스트 프레임워크를 사용해 스펙을 실행한다. 이 때, 코드가 잘못 작성되었다면 에러가 출력된다. </li>
<li>개발자는 테스트를 모두 통과해, 에러가 더는 출력되지 않을 때까지 코드를 수정한다.</li>
<li>모든 테스트를 통과하는 코드 초안이 완성된다.</li>
<li>스펙에 지금까지 고려하지 않았던 유스 케이스 몇 가지를 추가한다. 테스트에 실패한다.</li>
<li>네 번째 단계로 돌아가 테스트를 모두 통과할 때까지 코드를 수정한다.</li>
<li>기능이 완성될 때까지 3~6단계를 반복한다.</li>
</ol>
<p>위 프로세스를 따라서 거듭제곱 함수를 구현하는 예시로 BDD를 알아보자.</p>
<h4 id="1-스펙-초안-작성">1) 스펙 초안 작성</h4>
<p><code>pow</code> 기능을 구현해보자.</p>
<p>아래 HTML에는 <code>pow</code>의 스펙 초안, 테스팅 라이브러리가 모두 들어있다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;!-- 결과 출력에 사용되는 mocha css를 불러옵니다. --&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css&quot;&gt;
  &lt;!-- Mocha 프레임워크 코드를 불러옵니다. --&gt;
  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js&quot;&gt;&lt;/script&gt;
  &lt;script&gt;
    mocha.setup(&#39;bdd&#39;); // 기본 셋업
  &lt;/script&gt;
  &lt;!-- chai를 불러옵니다 --&gt;
  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js&quot;&gt;&lt;/script&gt;
  &lt;script&gt;
    // chai의 다양한 기능 중, assert를 전역에 선언합니다.
    let assert = chai.assert;
  &lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;

  &lt;script&gt;
    function pow(x, n) {
      /* 코드를 여기에 작성합니다. 지금은 빈칸으로 남겨두었습니다. */
    }
  &lt;/script&gt;

  &lt;!-- 테스트(describe, it...)가 있는 스크립트를 불러옵니다. --&gt;
  &lt;script src=&quot;test.js&quot;&gt;&lt;/script&gt;

  &lt;!-- 테스트 결과를 id가 &quot;mocha&quot;인 요소에 출력하도록 합니다.--&gt;
  &lt;div id=&quot;mocha&quot;&gt;&lt;/div&gt;

  &lt;!-- 테스트를 실행합니다! --&gt;
  &lt;script&gt;
    mocha.run();
  &lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<pre><code class="language-javascript">// test.js
describe(&quot;pow&quot;, function() {

  it(&quot;주어진 숫자의 n 제곱&quot;, function() {
    assert.equal(pow(2, 3), 8);
  });

});</code></pre>
<p>위 HTML을 웹페이지에 불러와 함수 <code>pow</code> 본문에 아무 코드도 없기 때문에 테스트에 실패한다. 결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev_diver/post/a3da5586-f6a8-4230-b548-5eebd68f6146/image.png" alt=""></p>
<h4 id="24-기능-코드-추가하고-테스트하기">2~4) 기능 코드 추가하고 테스트하기</h4>
<p>테스트 통과를 위해 코드를 간단하게 추가해보자.</p>
<pre><code class="language-javascript">function pow(x, n) {
  return 8;
}</code></pre>
<p>결과는 다음과 같다. 에러가 발생하지 않는다.
<img src="https://velog.velcdn.com/images/dev_diver/post/f09d49e7-d59d-4b43-b065-b8d0dee32372/image.png" alt=""></p>
<h4 id="5-스펙에-유스-케이스-추가하기">5) 스펙에 유스 케이스 추가하기</h4>
<p>스펙에 테스트를 추가하는 방법은 두 가지가 있다.</p>
<ol>
<li><p>기존 <code>it</code> 블록에 <code>assert</code>문을 추가하기</p>
<pre><code class="language-javascript">describe(&quot;pow&quot;, function() {

it(&quot;주어진 숫자의 n 제곱&quot;, function() {
 assert.equal(pow(2, 3), 8);
 assert.equal(pow(3, 4), 81);
});
</code></pre>
</li>
</ol>
<p>});</p>
<pre><code>2. 새로운 ```it``` 블록 추가하기(테스트 추가하기)
```javascript
describe(&quot;pow&quot;, function() {

  it(&quot;2를 세 번 곱하면 8입니다.&quot;, function() {
    assert.equal(pow(2, 3), 8);
  });

  it(&quot;3을 네 번 곱하면 81입니다.&quot;, function() {
    assert.equal(pow(3, 4), 81);
  });

});</code></pre><p>두 방법에는 근본적인 차이가 있다. </p>
<p><code>assert</code>에서 에러가 발생하면 <code>it</code> 블록은 즉시 종료된다. 따라서 기존 <code>it</code> 블록에 <code>assert</code>를 추가하면, 앞의 <code>assert</code>가 실패했을 때 다음 <code>assert</code>의 결과를 알 수 없다. 그렇기 때문에 두 번째 방법으로 테스트를 분리하는 것을 추천한다.</p>
<p>또, 테스트를 추가할 땐 다음 규칙도 따르는게 좋다. <code>테스트 하나에선 한 가지만 확인하기.</code> </p>
<p>위 테스트의 결과는 다음과 같다. 
<img src="https://velog.velcdn.com/images/dev_diver/post/c9f272cb-47e2-4a46-acf4-0c48d99eb628/image.png" alt=""></p>
<h4 id="6-코드-개선하기">6) 코드 개선하기</h4>
<p>두 번째 테스트도 통과할 수 있게 코드를 개선해보자.</p>
<pre><code class="language-javascript">function pow(x, n) {
  let result = 1;

  for (let i = 0; i &lt; n; i++) {
    result *= x;
  }

  return result;
}</code></pre>
<p>함수가 제대로 작동하는지 확인하기 위해 더 많은 테스트를 추가하자.</p>
<pre><code class="language-javascript">describe(&quot;pow&quot;, function() {
  function makeTest(x) {
    let expected = x * x * x;
    it(`${x}를 세 번 곱하면 ${expected}입니다.`, function() {
      assert.equal(pow(x, 3), expected);
    });
  }

  for (let x = 1; x &lt;= 5; x++) {
    makeTest(x);
  }

});</code></pre>
<p>결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev_diver/post/5adfdc6a-7229-4508-b05c-6524ec8ae13c/image.png" alt=""></p>
<h4 id="7-스펙-더-확장하기">7) 스펙 더 확장하기</h4>
<p>함수 <code>pow</code>의 매개변수 <code>n</code>은 양의 정수여야 한다.</p>
<p>자바스크립트에선 수학 연산을 수행하다 에러가 발생하면 NaN을 반환한다.</p>
<p><code>pow</code>도 해당 에러에 대한 테스트를 추가해보자.</p>
<pre><code class="language-javascript">describe(&quot;pow&quot;, function() {

  // ...

  it(&quot;n이 음수일 때 결과는 NaN입니다.&quot;, function() {
    assert.isNaN(pow(2, -1));
  });

  it(&quot;n이 정수가 아닐 때 결과는 NaN입니다.&quot;, function() {
    assert.isNaN(pow(2, 1.5));
  });

});</code></pre>
<p>결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/dev_diver/post/2317aa64-93e7-455c-8385-21b8586a5025/image.png" alt=""></p>
<p>기존에 <code>n</code>이 음수이거나 정수가 아닌 경우를 생각하지 않고 코드를 구현했기 때문에, 새롭게 추가한 테스트는 실패할 수 밖에 없다.</p>
<blockquote>
</blockquote>
<p><strong>BDD의 핵심은 여기에 있다. 실패할 수 밖에 없는 테스트를 추가하고, 테스트를 통과할 수 있게 코드를 개선하는 것이다.</strong></p>
<p>새롭게 추가한 테스트도 통과할 수 있도록 함수 <code>pow</code>에 코드를 추가해보자.</p>
<pre><code class="language-javascript">function pow(x, n) {
  if (n &lt; 0) return NaN;
  if (Math.round(n) != n) return NaN;

  let result = 1;

  for (let i = 0; i &lt; n; i++) {
    result *= x;
  }

  return result;
}</code></pre>
<p>결과는 다음과 같다. 에러 없이 모든 테스트를 통과한다.
<img src="https://velog.velcdn.com/images/dev_diver/post/0e98afaa-e112-4731-8dbc-1972c4bfaa8c/image.png" alt=""></p>
<p>이제 원하는 기능을 완성할 때까지 위 과정들을 계속 반복하면 된다.</p>
<h4 id="확장-중첩-describe를-사용해-테스트-추가하기">확장) 중첩 describe를 사용해 테스트 추가하기</h4>
<pre><code class="language-javascript">describe(&quot;pow&quot;, function() {

  describe(&quot;x를 세 번 곱합니다.&quot;, function() {

    function makeTest(x) {
      let expected = x * x * x;
      it(`${x}을/를 세 번 곱하면 ${expected}입니다.`, function() {
        assert.equal(pow(x, 3), expected);
      });
    }

    for (let x = 1; x &lt;= 5; x++) {
      makeTest(x);
    }

  });

  // describe와 it을 사용해 이 아래에 더 많은 테스트를 추가할 수 있습니다.
});</code></pre>
<p>중첩 describe는 새로운 테스트 하위 그룹을 정의할 때 사용된다.</p>
<h4 id="확장-함수-before--after--beforeeach--aftereach">확장) 함수 before / after / beforeEach / afterEach</h4>
<blockquote>
</blockquote>
<ul>
<li><code>before</code> : 전체 테스트가 실행되기 전에 실행된다.</li>
<li><code>after</code> : 전체 테스트가 실행된 후에 실행된다.</li>
<li><code>beforeEach</code> : 매 <code>it</code>이 실행되기 전에 실행된다.</li>
<li><code>afterEach</code> : 매 <code>it</code>이 실행된 후에 실행된다.</li>
</ul>
<pre><code class="language-javascript">describe(&quot;test&quot;, function() {

  before(() =&gt; alert(&quot;테스트를 시작합니다 - 테스트가 시작되기 전&quot;));
  after(() =&gt; alert(&quot;테스트를 종료합니다 - 테스트가 종료된 후&quot;));

  beforeEach(() =&gt; alert(&quot;단일 테스트를 시작합니다 - 각 테스트 시작 전&quot;));
  afterEach(() =&gt; alert(&quot;단일 테스트를 종료합니다 - 각 테스트 종료 후&quot;));

  it(&#39;test 1&#39;, () =&gt; alert(1));
  it(&#39;test 2&#39;, () =&gt; alert(2));

});</code></pre>
<p>위 테스트의 실행 순서는 아래와 같다.</p>
<pre><code>테스트를 시작합니다 - 테스트가 시작되기 전          (before)
단일 테스트를 시작합니다 - 각 테스트 시작 전         (beforeEach)
1
단일 테스트를 종료합니다 - 각 테스트 종료 후         (afterEach)
단일 테스트를 시작합니다 - 각 테스트 시작 전         (beforeEach)
2
단일 테스트를 종료합니다 - 각 테스트 종료 후         (afterEach)
테스트를 종료합니다 - 테스트가 종료된 후            (after)</code></pre><p>위 함수들은 대개 초기화 용도로 사용된다. </p>
<p>카운터 변수를 0으로 만들거나, 테스트가 바뀔 때마다 해줘야하는 작업이 있을 때 활용할 수 있다.</p>
<h3 id="bdd를-적용함으로써-얻는-이점">BDD를 적용함으로써 얻는 이점</h3>
<p>BDD에선 스펙을 먼저 작성하고 난 후에 구현을 시작한다. 따라서, 구현이 종료된 시점에 스펙과 코드를 둘 다 확보할 수 있다.</p>
<p>스펙의 용도는 세 가지이다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>테스트</code> : 함수가 의도하는 동작을 제대로 수행하고 있는지 보장한다.</li>
<li><code>문서</code> : 함수가 어떤 동작을 수행하고 있는지 설명해준다.</li>
<li><code>예시</code> : 실제 동작하는 예시를 이용해 함수를 어떻게 사용할 수 있는지 알려준다.</li>
</ul>
<p>스펙이 있기 때문에 개발자는 안전하게 함수를 개선하거나 변경할 수 있다.</p>
<p>코드가 바뀌어도 기존에 구현된 기능에 영향을 주지 않게 하는 건 매우 중요하다. 프로젝트 규모가 커지면 함수 하나를 이곳저곳에서 사용하는데, 변경된 함수가 이 함수를 사용하는 모든 곳에서 제대로 동작하는지 수동으로 확인하는 건 불가능하기 때문이다.</p>
<p>테스트를 하지 않고 코드를 작성해왔다면 개발자들은 둘 중 한 갈래의 길로 빠져버린다.</p>
<ul>
<li>아무 대책 없이 코드를 변경한다. 부작용을 생각하지 않고 함수를 수정했기 때문에 어디선가 버그가 발생한다.</li>
<li>수정이나 개선을 기피하게 된다. 버그의 대가가 가혹하기 때문에, 코드가 구식이 되어도 그 누구도 코드를 건드리려 하지 않는다.</li>
</ul>
<p><code>테스트 자동화</code>는 이런 문제를 피하게 도와준다. 코드에 변화가 있어도 스펙을 실행하여 테스트를 진행하면 에러 발생 여부를 확인할 수 있다.</p>
<p>또, 잘 테스트 된 코드는 더 나은 아키텍처를 만든다. 테스트를 작성하려면 함수가 어떤 동작을 하는지, 입력값은 무엇이고 출력값은 무엇인지 정의하고 난 후에 구현을 시작한다. 코드는 정의된 사항을 뒷받침 할 수 있게 작성해야한다. 따라서 구현을 시작하는 순간부터 이미 좋은 아키텍처가 보장된다.</p>
<p>매 번 이런 절차를 따라 구현하는게 쉽지는 않다. 함수가 어떻게 동작해야하는지 확신이 서지 않는 상황에서 코드를 작성하기전에 명세서를 작성해야하므로 익숙하지 않을 수 있다. 그렇지만 테스트를 작성하면 일반적으로 개발 속도가 빨라지고 이전보다 코드를 더 안정적으로 작성할 수 있다.</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/code-quality">https://ko.javascript.info/code-quality</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 13 자바스크립트) 자바스크립트 기본]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-11-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@dev_diver/22.-10.-11-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Thu, 13 Oct 2022 11:58:55 GMT</pubDate>
            <description><![CDATA[<h1 id="스크립트를-작성하는-장소">스크립트를 작성하는 장소</h1>
<h3 id="내부-스크립트">내부 스크립트</h3>
<p><code>&lt;script&gt;</code> 태그를 이용하여 자바스크립트 코드를 HTML 문서에 삽입할 수 있다.</p>
<pre><code class="language-javascript">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;

&lt;body&gt;

  &lt;p&gt;스크립트 전&lt;/p&gt;

  &lt;script&gt;
    alert( &#39;Hello, world!&#39; );
  &lt;/script&gt;

  &lt;p&gt;스크립트 후&lt;/p&gt;

&lt;/body&gt;

&lt;/html&gt;</code></pre>
<h3 id="외부-스크립트">외부 스크립트</h3>
<p><code>&lt;script&gt;</code>태그에 <code>src</code>속성을 사용하여 외부에서 작성한 자바스크립트 코드를 HTML에 삽입할 수 있다.</p>
<pre><code class="language-javascript">// 상대 경로 방식 예시
&lt;script src=&quot;/path/to/script.js&quot;&gt;&lt;/script&gt;

// 절대 경로 방식 예시
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js&quot;&gt;&lt;/script&gt;
</code></pre>
<hr>
<h1 id="strict-mode">Strict mode</h1>
<p>ES5 이후 기존 기능 중 일부가 변경되어, 하위 호환성에 문제가 생겼다.</p>
<p>기본 모드에선 변경사항의 대부분이 활성화되지 않지만, 특별 지시자를 사용해 strict mode를 활성화하면 변경사항을 활성화할 수 있다.</p>
<h3 id="use-strict-지시자">use strict 지시자</h3>
<p>스크립트 최상단에 use strict 지시자가 오면, 스크립트 전체가 &quot;모던&quot;하게 동작한다.</p>
<pre><code class="language-javascript">&quot;use strict&quot;;

...</code></pre>
<p>함수 본문 맨앞에 use strict 지시자가 오면, 해당 함수만 strict mode로 실행된다.</p>
<pre><code class="language-javascript">function func() {
  &quot;use strict&quot;;
  ...
}</code></pre>
<p>참고로, 클래스와 모듈을 사용한 모던 자바스크립트는 use strict가 자동으로 적용되므로, 스크립트에 붙일 필요가 없다.</p>
<hr>
<h1 id="변수와-상수">변수와 상수</h1>
<h3 id="변수variable">변수(Variable)</h3>
<blockquote>
</blockquote>
<p>값을 저장할 때 쓰이는, 쉽게 말해 이름이 붙은 저장소이다.
변수는 메모리 영역에 연결되고, 값이 해당 메모리 영역에 저장된다. 그 후 변수 이름을 통해 값에 접근할 수 있다.</p>
<h3 id="변수-생성">변수 생성</h3>
<p><code>let</code> 키워드를 사용해 변수를 생성한다.</p>
<pre><code class="language-javascript">// 변수 선언(생성)
let message;

// 변수 안에 데이터 할당(저장)
message = &quot;hello&quot;;

// 변수명을 이용해 저장된 데이터에 접근
alert(message);

// 변수 선언과 할당을 한 번에 하기
let message = &quot;hello&quot;;</code></pre>
<p>변수는 한 번만 선언해야 한다. 같은 이름의 변수를 여러 번 선언하면 에러가 발생한다.</p>
<p>참고로, <code>var</code>키워드는 <code>let</code>과 거의 동일하게 동작한다. 하지만 호이스팅 문제 때문에 모던 자바스크립트에는 쓰지 않는 것을 권장한다.</p>
<p><code>자바스크립트와 함수형 언어 내용 추가 예정</code></p>
<h3 id="변수명을-짓는-규칙">변수명을 짓는 규칙</h3>
<p>변수명을 지을 때는 두 가지 제약 사항이 있다.</p>
<blockquote>
</blockquote>
<ol>
<li>변수명에는 <code>문자</code>와 <code>숫자</code>, 기호 <code>$</code>와 <code>_</code>만 들어 갈 수 있다.</li>
<li>숫자는 변수명의 첫 글자에 올 수 없다.</li>
</ol>
<p>그리고 제약 사항은 아니지만 개발자들 사이의 암묵적인 변수명 규칙들이 있다.</p>
<blockquote>
</blockquote>
<ol>
<li>변수가 담고있는 것이 무엇인지 잘 설명해주는 이름이어야 한다. 
ex) <code>userName</code></li>
<li>여러 단어를 조합한 변수명은 <code>camelCase</code> 혹은 <code>snake_case</code> 를 사용해 명명한다.</li>
<li><code>let</code>, <code>class</code> 등 예약어는 변수명으로 사용할 수 없다.</li>
</ol>
<h3 id="상수constant">상수(Constant)</h3>
<p>저장한 데이터를 변경하지 않는 변수를 선언할 때에는, <code>const</code> 키워드를 사용한다. 상수를 변경하려고 하면 에러가 발생한다.</p>
<pre><code class="language-javascript">const myBirthDay = &quot;1996.06.26&quot;;</code></pre>
<p>기억하기 힘든 값을 대문자 상수로 선언하는 것은 널리 사용되는 관습이다. 이런 상수는 대개 대문자와 밑줄로 명명한다.</p>
<pre><code class="language-javascript">const COLOR_RED = &quot;#F00&quot;;</code></pre>
<hr>
<h1 id="자료형">자료형</h1>
<p>자바스크립트는 동적 타입 언어로, 변수에 저장되는 값의 타입은 언제든 바꿀 수 있다.</p>
<h3 id="숫자형number-type">숫자형(number Type)</h3>
<p>숫자형은 정수 및 부동소수점 숫자를 나타낸다.</p>
<pre><code class="language-javascript">let n = 123;</code></pre>
<p><code>1, -1, 0.1</code> 뿐만 아니라 <code>Infinity, -Infinity, NaN</code> 같은 특수 숫자 값도 포함된다. </p>
<h3 id="bigint형bigint-type">BigInt형(bigInt Type)</h3>
<p>높은 정밀도로 작업을 해야 할 때 다루는 숫자형과 비슷한 타입이다. </p>
<p>정수 리터럴 끝에 n을 붙여서 만들 수 있다.</p>
<pre><code class="language-javascript">const bigInt = 1234567890123456789012345678901234567890n;</code></pre>
<h3 id="문자형string-type">문자형(string Type)</h3>
<p><code>&quot;&quot;</code>, <code>&#39;&#39;</code>, <code>``</code> 세 가지 따옴표로 문자열을 만들 수 있다.</p>
<p>백틱으로 감싼 문자열안에는 <code>${}</code> 안에 변수나 표현식을 넣을 수 있다.</p>
<pre><code class="language-javascript">let str = &quot;hello&quot;;
let str2 = &#39;Single quotes are ok too&#39;;
let phrase = `can embed another ${str}`;</code></pre>
<h3 id="불리언형boolean-type">불리언형(boolean Type)</h3>
<p><code>true</code> 나 <code>false</code> 값을 나타내는 자료형이다.</p>
<pre><code class="language-javascript">let isGreater = 4 &gt; 1;

alert( isGreater ); // true</code></pre>
<h3 id="null형null-type">null형(null Type)</h3>
<p><code>null</code> 값만을 가지는 자료형이다. </p>
<p>존재하지 않는 값, 비어 있는 값, 알 수 없는 값 등을 나타내는 데 사용한다.</p>
<pre><code class="language-javascript">let age = null;</code></pre>
<h3 id="undefined형undefined-type">undefined형(undefined Type)</h3>
<p><code>undefined</code> 값만을 가지는 자료형이다. </p>
<p>값이 할당되지 않은 상태를 나타낸다. 변수는 선언했지만 값을 할당하지 않았을 때, 해당 변수에 <code>undefined</code>가 자동으로 할당된다.</p>
<pre><code class="language-javascript">let age;

alert(age); // undefined</code></pre>
<h3 id="객체형object-type">객체형(object Type)</h3>
<p>문자열이나 숫자, null 등 한 가지만 표현할 수 있는 원시(primitive) 자료형과 달리, 데이터 컬렉션이나 복잡한 개체를 표현할 수 있는 자료형이다.</p>
<h3 id="심볼형symbol-type">심볼형(Symbol Type)</h3>
<p>객체의 고유한 식별자(unique identifier)를 만들 때 사용된다.</p>
<h3 id="typeof-연산자">typeof 연산자</h3>
<p>해당 변수의 자료형을 알고 싶을 때 사용하는 연산자이다.</p>
<p><code>typeof ...</code> 혹은 <code>typeof(...)</code> 두 가지 방식으로 사용할 수 있다.</p>
<pre><code class="language-javascript">typeof undefined // undefined
typeof 0 // number
typeof(&quot;foo&quot;) // string
typeof null // object  =&gt; null 타입은 객체형과 별도의 자료형이지만 하위 호환성을 위해 object 타입을 나타낸다.
typeof alert // function =&gt; 함수형으로 표현되지만 함수형은 따로 없고, 함수는 객체형에 속한다. 이 또한 하위 호환성 유지를 위해 남겨진 표현이다.</code></pre>
<h3 id="형-변환type-conversion">형 변환(Type Conversion)</h3>
<p>함수와 연산자에 전달되는 값이 적절한 자료형으로 자동 변환되는 일이나, 전달받은 값을 의도적으로 원하는 타입으로 변환해 주는 경우를 말한다.</p>
<pre><code class="language-javascript">// 문자형으로 변환
let value = true; // true
value = String(value) // &quot;true&quot;

// 숫자형으로 변환
let num = &quot;123&quot;; // &quot;123&quot;
num = Number(num) // 123

// 불린형으로 변환
// 0, 빈 문자열, null, undefined, NaN은 false.
// 그 외의 값은 true로 변환된다.
Boolea(1) // true
Boolean(0) // false
Boolean(&quot;hi&quot;) // true
Boolean(&quot;&quot;) // false</code></pre>
<hr>
<h1 id="연산자">연산자</h1>
<h3 id="피연산자의-갯수에-따른-연산자-분류">피연산자의 갯수에 따른 연산자 분류</h3>
<p><code>단항 연산자(unary operator)</code> : 피연산자를 하나만 받는 연산자</p>
<pre><code class="language-javascript">// 단항 연산자(unary operator)
let x = 1;
x = -x; // -1

// 단항 연산자 +는 피연산자가 숫자형이 아닌 경우 숫자형으로 변환한다.
+true // 1
+&quot;&quot; // 0</code></pre>
<p><code>이항 연산자(binary operator)</code> : 두 개의 피연산자를 받는 연산자</p>
<pre><code class="language-javascript">// 숫자 덧셈
let x = 1, y = 3;
y - x // 2

// 문자열 붙이기
let s = &quot;my&quot; + &quot;string&quot;; // &quot;mystring&quot;

// 피연산자가 하나라도 문자열이면 결과는 문자열을 반환
2 + &quot;1&quot; // &quot;21&quot;</code></pre>
<h3 id="연산자-우선순위precedence">연산자 우선순위(Precedence)</h3>
<p>하나의 표현식에 둘 이상의 연산자가 있는 경우, 실행 순서는 연산자의 우선순위에 의해 결정된다.</p>
<p>우선순위가 같으면 왼쪽에서 오른쪽으로 연산이 수행된다.</p>
<p>괄호안의 연산은 모든 우선순위를 무시하고 가장 먼저 수행된다.</p>
<h3 id="수학-연산자">수학 연산자</h3>
<blockquote>
</blockquote>
<ul>
<li>덧셈 연산자 <code>+</code></li>
<li>뺄셈 연산자 <code>-</code></li>
<li>곱셈 연산자 <code>*</code></li>
<li>나눗셈 연산자 <code>/</code></li>
<li>나머지 연산자 <code>%</code></li>
<li>거듭제곱 연산자 <code>**</code></li>
</ul>
<h3 id="할당-연산자">할당 연산자</h3>
<p>무언가를 할당할 때 쓰이는 연산자이다.</p>
<pre><code class="language-javascript">// 값 할당
let x = 2 * 2 + 1; // 5

// 값을 반환
let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

a // 3
c // 0

// 체이닝 =&gt; 할당 연산자는 우측부터 평가가 진행된다.
let a, b, c;

a = b = c = 2 + 2;

a // 4
b // 4
c // 4</code></pre>
<h3 id="복합-할당-연산자">복합 할당 연산자</h3>
<p>변수에 연산자를 적용하고, 그 결과를 같은 변수에 저장할 때 쓰이는 연산자이다.</p>
<pre><code class="language-javascript">let n = 2;
n += 5; // 7
n *= 2; // 14
n -= 8; // 6
n /= 3; // 2</code></pre>
<h3 id="증가--감소-연산자">증가 / 감소 연산자</h3>
<pre><code class="language-javascript">let counter = 2;

// 증가 연산자 : 변수를 1 증가시킨다.
counter++; // 3

// 감소 연산자 : 변수를 1 감소시킨다.
counter--; // 2</code></pre>
<p>증감 연산자는 피연산자의 뒤에 올 때를 <code>후위형(postfix)</code>, 피연산자 앞에 올 때는 <code>전위형(prefix)</code>이라 부른다.</p>
<p>전위형은 증감 후의 새로운 값을 반환한다.
후위형은 증감 전의 기존 값을 반환하고, 증감한다.</p>
<h3 id="비트-연산자">비트 연산자</h3>
<p>피연산자를 32비트 정수로 변환하여 이진 연산을 수행한다.</p>
<blockquote>
</blockquote>
<ul>
<li>AND <code>&amp;</code></li>
<li>OR <code>|</code></li>
<li>XOR <code>^</code></li>
<li>NOT <code>~</code></li>
<li>왼쪽 시프트 <code>&lt;&lt;</code></li>
<li>오른쪽 시프트 <code>&gt;&gt;</code></li>
<li>부호 없는 오른쪽 시프트 <code>&gt;&gt;&gt;</code></li>
</ul>
<h3 id="쉼표-연산자">쉼표 연산자</h3>
<p>여러 표현식을 코드 한 줄에서 평가할 수 있게 한다.</p>
<p>표현식 각각이 모두 평가되지만, 마지막 표현식의 평가 결과만 반환된다.</p>
<pre><code class="language-javascript">let a = (1 + 2, 3 + 4);

a; // 7 (3 + 4의 결과만 할당됨)

// 한 줄에서 세 개의 연산이 수행됨
for (a = 1, b = 3, c = a * b; a &lt; 10; a++) {
 ...
}</code></pre>
<h3 id="비교-연산자">비교 연산자</h3>
<blockquote>
</blockquote>
<ul>
<li>부등호 연산자 <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code></li>
<li>동등 연산자 <code>==</code></li>
<li>부등 연산자 <code>!=</code></li>
<li>일치 연산자 <code>===</code></li>
<li>불일치 연산자 <code>!==</code></li>
</ul>
<p>비교 연산자는 기본적으로 불린형을 반환한다.</p>
<pre><code class="language-javascript">2 &gt; 1 // true
2 == 1 // false
2 != 1 // true</code></pre>
<p>비교 연산자를 사용하여 유니코드순으로 문자열을 비교할 수 있다.</p>
<ul>
<li>유니코드 뒤쪽의 문자열은 유니코드 앞쪽의 문자열보다 크다.</li>
<li>문자열의 첫 글자부터 차례대로 비교한다.</li>
<li>비교가 종료되었을 때, 문자열이 긴 쪽이 더 크다고 결론내린다.<pre><code class="language-javascript">&quot;Z&quot; &gt; &quot;A&quot; // true
&quot;A&quot; &gt; &quot;a&quot; // false
&quot;Bee&quot; &lt; &quot;Be&quot; // false</code></pre>
</li>
</ul>
<p>일치와 불일치 연산자는 형 변환 없이 피연산자의 자료형까지 비교하여 검사하기 때문에 더 엄격하다.</p>
<pre><code class="language-javascript">0 == false // true
0 === false // false</code></pre>
<h3 id="논리-연산자">논리 연산자</h3>
<blockquote>
</blockquote>
<ul>
<li>OR <code>||</code></li>
<li>AND <code>&amp;&amp;</code></li>
<li>NOT <code>!</code></li>
</ul>
<p><code>OR 연산자</code>는 피연산자가 모두 false인 경우를 제외하고 연산 결과는 항상 true이다. </p>
<pre><code class="language-javascript">true || true;   // true
false || true;  // true
true || false;  // true
false || false; // false</code></pre>
<p>또, <code>첫 번째 truthy</code>를 찾는데에도 쓰인다.</p>
<pre><code class="language-javascript">result = value1 || value2 || value3;</code></pre>
<ol>
<li>왼쪽 피연산자부터 시작해 평가한다.</li>
<li>각 피연산자를 불린형으로 변환하고, 변환값이 true이면 연산을 멈추고 해당 피연산자의 변환 전 원래 값을 반환한다.</li>
<li>피연산자를 모두 평가해도 false인 경우 가장 마지막 피연산자를 반환한다.</li>
</ol>
<p>이러한 기능은 다양한 용도로 활용할 수 있다.</p>
<ol>
<li>변수 또는 표현식들에서 첫 번째 truthy 값 얻기<pre><code class="language-javascript">let firstName = &quot;&quot;;
let lastName = &quot;&quot;;
let nickName = &quot;바이올렛&quot;;
</code></pre>
</li>
</ol>
<p>alert( firstName || lastName || nickName || &quot;익명&quot;); // 바이올렛</p>
<pre><code>
2. 단락 평가(short circuit evaluation)
논리 연산자 (&amp;&amp; , ||) 를 사용하여 연산을 진행 할 때 좌측 식의 값에 따라 우측 식의 실행 여부를 판단하는 동작. ```||``` 단락 평가는 연산자 왼쪽 조건이 falsy일 때만 명령어를 실행하고자 할 때 자주 쓰인다.
```javascript
true || alert(&quot;not printed&quot;);
false || alert(&quot;printed&quot;);</code></pre><p><code>AND 연산자</code>는 두 피연산자가 모두 참일 때만 true를 반환한다. 또, 첫 번째 falsy를 찾는데에도 쓰인다.</p>
<pre><code class="language-javascript">result = value1 &amp;&amp; value2 &amp;&amp; value3;</code></pre>
<ol>
<li>왼쪽부터 피연산자를 평가한다.</li>
<li>각 피연산자는 불린형으로 변환되고, 변환후 값이 false이면 평가를 멈추고 해당 피연산자의 원래 값을 반환한다.</li>
<li>피연산자 모두가 true로 평가되는 경우 마지막 피연산자를 반환한다.<pre><code class="language-javascript">1 &amp;&amp; 0; // 0
1 &amp;&amp; 5; // 5
null &amp;&amp; 5; // null
0 &amp;&amp; &quot;아무거나 와도 상관없습니다.&quot;; // 0</code></pre>
</li>
</ol>
<p><code>NOT 연산자</code>는 피연산자를 불린형으로 변환하고, 변환된 불린값의 역을 반환한다.</p>
<pre><code class="language-javascript">result = !value;

!true; // false
!0; // true</code></pre>
<p>NOT연산자가 피연산자를 불린형으로 변환하고 평가하는 특징을 활용해 , 두 개 연달아 사용하면 역의 역연산을 수행하므로 피연산자를 불린형으로 변환할 수 있다.</p>
<pre><code class="language-javascript">!!&quot;non-empty string&quot;; // true
!!null; // false</code></pre>
<h3 id="nullish-병합-연산자">nullish 병합 연산자</h3>
<p>여러 피연산자 중 값이 확정되어있는 변수를 찾을 수 있다.</p>
<pre><code class="language-javascript">a ?? b // a가 null 혹은 undefined이면 b, 아니면 a 이다.</code></pre>
<hr>
<h1 id="조건문">조건문</h1>
<h3 id="if문">if문</h3>
<p>괄호 안에 들어가는 조건을 평가하고, 그 결과를 불린값으로 변환하여 true이면 코드 블록이 실행된다.</p>
<pre><code class="language-javascript">let year = prompt(&#39;ECMAScript-2015 명세는 몇 년도에 출판되었을까요?&#39;, &#39;&#39;);

if (year == 2015) {
  alert( &#39;정답입니다!&#39; )
};</code></pre>
<ul>
<li><code>0</code>, <code>&quot;&quot;</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code>은 불린형으로 변환 시 모두 false가 된다. 이 들을 falsy 값이라 부른다.</li>
<li>이 외의 값을 불린형으로 변환 시 true가 되므로, truthy 값이라 부른다.</li>
</ul>
<h3 id="else문">else문</h3>
<p><code>if문</code>에 <code>else문</code>을 불일 수 있다.</p>
<p><code>else</code> 뒤에 이어지는 코드 블록은 조건이 거짓일 때 실행된다.</p>
<pre><code class="language-javascript">let year = prompt(&#39;ECMAScript-2015 명세는 몇 년도에 출판되었을까요?&#39;, &#39;&#39;);

if (year == 2015) {
  alert( &#39;정답입니다!&#39; );
} else {
  alert( &#39;오답입니다!&#39; ); // 2015 이외의 값을 입력한 경우
}</code></pre>
<p>else문은 필수가 아닌 선택 사항이다.</p>
<h3 id="else-if문">else if문</h3>
<p>여러 개의 조건을 처리할 때 쓰이는 구문이다.</p>
<pre><code class="language-javascript">let year = prompt(&#39;ECMAScript-2015 명세는 몇 년도에 출판되었을까요?&#39;, &#39;&#39;);

if (year &lt; 2015) {
  alert( &#39;숫자를 좀 더 올려보세요.&#39; );
} else if (year &gt; 2015) {
  alert( &#39;숫자를 좀 더 내려보세요.&#39; );
} else {
  alert( &#39;정답입니다!&#39; );
}</code></pre>
<h3 id="조건부-연산자">조건부 연산자</h3>
<p>물음표 연산자 또는 삼항 연산자라고도 불리며, 조건문을 좀 더 간결하게 변형할 수 있다. </p>
<p>연산자 앞의 조건이 truthy이면 value1이, falsy라면 value2 가 반환된다.</p>
<pre><code class="language-javascript">let result = condition ? value1 : value2;</code></pre>
<p>추가적으로 물음표 연산자는 조건에 따라 반환 값을 달리하려는 목적으로 만들어졌다. 조건을 여러 분기로 만들어 처리할 때는 if를 사용하는 것이 좋다.</p>
<h3 id="switch문">switch문</h3>
<p>복수의 <code>if</code> 조건문을 <code>switch</code> 조건문으로 바꿀 수 있다.</p>
<pre><code class="language-javascript">switch(x) {
  case &#39;value1&#39;:  // if (x === &#39;value1&#39;)
    ...
    [break]

  case &#39;value2&#39;:
    ...
    [break]

  default:    // 옵션임
    ...
    [break]
}</code></pre>
<ol>
<li>변수 x의 값과 첫 번째 case문의 값부터 차례대로 비교한다.</li>
<li>값이 일치하는 case문의 body를 실행한다. 일치하는 case가 없으면 default 문이 존재할 시, default 문의 코드를 실행한다.</li>
<li>body가 실행되고 break문을 만나거나 switch문이 끝나면 코드의 실행이 멈춘다.</li>
<li>참고로 case안에 break가 없으면 조건과 일치하지 않아도 하위의 case문들을 모두 실행한다. 따라서, break 문은 꼭 쓰는 것이 좋다.</li>
</ol>
<p>switch와 case 문의 조건에는 변수 뿐만아니라 어떤 표현식이든 올 수 있다.</p>
<hr>
<h1 id="반복문loop">반복문(Loop)</h1>
<h3 id="while-반복문">while 반복문</h3>
<p>condition이 truthy이면 body가 실행된다.</p>
<pre><code class="language-javascript">while (condition) {
  // ...
}</code></pre>
<h3 id="do-while-반복문">do while 반복문</h3>
<p>본문이 먼저 한 번 실행되고, 조건이 truthy이면 본문이 계속 실행된다.</p>
<pre><code class="language-javascript">do {
  // body
} while (condition);</code></pre>
<h3 id="for-반복문">for 반복문</h3>
<blockquote>
</blockquote>
<ol>
<li>가장 먼저 begin이 실행된다.</li>
<li>condition이 truthy이면 body가 실행된다. </li>
<li>그 후 step을 실행한다.</li>
<li>2~3을 반복한다.</li>
</ol>
<pre><code class="language-javascript">for (begin; condition; step) {
  // body
}</code></pre>
<p>begin과 condition, step은 생략될 수 있다.</p>
<pre><code class="language-javascript">let i = 0; // i를 선언하고 값도 할당하였습니다.

// begin 생략
for (; i &lt; 3; i++) { // &#39;begin&#39;이 필요하지 않기 때문에 생략하였습니다.
  alert( i ); // 0, 1, 2
}

// begin과 step 생략
let i = 0;

for (; i &lt; 3;) {
  alert( i++ );
}


// 모두 생략 =&gt; 무한 반복문
for (;;) {
  // 끊임 없이 본문이 실행됩니다.
}</code></pre>
<h3 id="반복문-빠져나오기">반복문 빠져나오기</h3>
<p>대개는 반복문의 조건이 falsy가 되면 반복문이 종료된다.</p>
<p>이외에도 <code>break</code> 지시자를 사용해 원하는 때에 반복문을 빠져나올 수 있다.</p>
<pre><code class="language-javascript">let sum = 0;

while (true) {

  let value = +prompt(&quot;숫자를 입력하세요.&quot;, &#39;&#39;);

  if (!value) break; // (*)

  sum += value;

}
alert( &#39;합계: &#39; + sum );</code></pre>
<h3 id="다음-반복으로-넘어가기">다음 반복으로 넘어가기</h3>
<p><code>continue</code> 지시자를 사용해 현재 실행 중인 반복을 멈추고, 조건이 truthy일 때 다음 반복을 강제로 실행시킬 수 있다.</p>
<pre><code class="language-javascript">for (let i = 0; i &lt; 10; i++) {

  // 조건이 참이라면 남아있는 본문은 실행되지 않습니다.
  if (i % 2 == 0) continue;

  alert(i); // 1, 3, 5, 7, 9가 차례대로 출력됨
}</code></pre>
<h3 id="break--continue-지시자와-라벨을-사용해서-반복문을-빠져-나오거나-넘어가기">break / continue 지시자와 라벨을 사용해서 반복문을 빠져 나오거나 넘어가기</h3>
<p>중첩된 반복문에서 한 번에 빠져나오고 싶을 때가 있다.</p>
<p>이때는 반복문 앞에 라벨을 사용한다.</p>
<pre><code class="language-javascript">labelName: for (...) {
  ...
}

// 예시
outer: for (let i = 0; i &lt; 3; i++) {

  for (let j = 0; j &lt; 3; j++) {

    let input = prompt(`(${i},${j})의 값`, &#39;&#39;);

    // 사용자가 아무것도 입력하지 않거나 Cancel 버튼을 누르면 두 반복문 모두를 빠져나옵니다.
    if (!input) break outer; // (*)

    // 입력받은 값을 가지고 무언가를 함
  }
}
alert(&#39;완료!&#39;);</code></pre>
<p>마찬가지로 continue 지시자에 사용하면 해당 레이블이 지정된 반복문의 다음 반복을 실행한다.</p>
<hr>
<h1 id="함수function">함수(function)</h1>
<p>함수를 이용해 중복 작성 없이 유사한 동작을 하는 코드를 여러 번 호출할 수 있다.</p>
<h3 id="함수-선언함수-선언문과-호출">함수 선언(함수 선언문)과 호출</h3>
<p>함수는 <code>function</code> 키워드, 함수 이름, 매개변수들(생략가능), 함수 본문으로 구성된다.</p>
<p>정의된 함수 이름 옆에 괄호를 붙여 호출한다.</p>
<pre><code class="language-javascript">//함수 선언
function funcName(param1, param2, ... paramN) {
  // body ...
}

// 함수 호출
funcName(); </code></pre>
<h3 id="지역-변수local-variable-or-내부-변수inner-variable">지역 변수(local variable) or 내부 변수(inner variable)</h3>
<p>함수 내에서 선언한 변수로, 함수 안에서만 접근할 수 있다.</p>
<pre><code class="language-javascript">function showMessage() {
  let message = &quot;안녕하세요!&quot;; // 지역 변수

  alert( message );
}

showMessage(); // 안녕하세요!

alert( message ); // ReferenceError: message is not defined (message는 함수 내 지역 변수이기 때문에 에러가 발생합니다.)</code></pre>
<h3 id="전역-변수global-variable-or-외부-변수outer-variable">전역 변수(global variable) or 외부 변수(outer variable)</h3>
<p>함수 외부에서 선언된 변수로, 함수 내부에서 외부 변수에 접근하거나 수정할 수 있다.</p>
<pre><code class="language-javascript">let userName = &#39;John&#39;;

function showMessage() {
  userName = &quot;Bob&quot;; // (1) 외부 변수를 수정함

  let message = &#39;Hello, &#39; + userName;
  alert(message);
}

alert( userName ); // 함수 호출 전이므로 John 이 출력됨

showMessage();

alert( userName ); // 함수에 의해 Bob 으로 값이 바뀜</code></pre>
<p>다만 함수 내부에 외부 변수와 동일한 이름을 가진 지역변수가 선언되었다면, 해당 지역변수는 외부 변수를 가린다.</p>
<pre><code class="language-javascript">let userName = &#39;John&#39;;

function showMessage() {
  let userName = &quot;Bob&quot;; // 같은 이름을 가진 지역 변수를 선언합니다.

  let message = &#39;Hello, &#39; + userName; // Bob
  alert(message);
}

// 함수는 내부 변수인 userName만 사용합니다,
showMessage();

alert( userName ); // 함수는 외부 변수에 접근하지 않습니다. 따라서 값이 변경되지 않고, John이 출력됩니다.</code></pre>
<h3 id="매개변수parameter-인자">매개변수(parameter, 인자)</h3>
<p>매개변수를 이용해 임의의 데이터를 함수 안에 전달할 수 있다.</p>
<pre><code class="language-javascript">function showMessage(from, text) { // 인자: from, text
  alert(from + &#39;: &#39; + text);
}

showMessage(&#39;Ann&#39;, &#39;Hello!&#39;); // Ann: Hello! (*)
showMessage(&#39;Ann&#39;, &quot;What&#39;s up?&quot;); // Ann: What&#39;s up? (**)</code></pre>
<p>함수 호출 시에 각각의 매개변수 위치에 맞춰 인수(argument)를 전달하면 된다.</p>
<p>전달된 값은 매개변수에 복사되고, 매개변수는 지역변수처럼 사용된다.</p>
<p>매개변수가 있는 함수 호출시에 인수를 전달하지 않으면, 해당 매개변수는 undefined가 된다.</p>
<p>이 때, 매개변수에는 default 값을 설정해줄 수 있다.</p>
<pre><code class="language-javascript">function showMessage(from, text = &quot;no text given&quot;) {
  alert( from + &quot;: &quot; + text );
}

showMessage(&quot;Ann&quot;); // Ann: no text given</code></pre>
<h3 id="반환값return-value">반환값(return value)</h3>
<p>함수 선언시에 <code>return</code> 지시자를 사용해 함수를 호출했을 때, 호출한 곳에 특정 값을 반환하게 할 수 있다.</p>
<pre><code class="language-javascript">function sum(a, b) {
  return a + b;
}

let result = sum(1, 2);
alert( result ); // 3</code></pre>
<p>return문은 함수 내 어디서든 사용할 수 있다.</p>
<p>return을 만나게 되면 함수 실행은 즉시 중단되고, 함수를 호출한 곳에 값을 반환한다.</p>
<p>return 문이 없거나 return만 있는 함수는 undefined를 반환한다.</p>
<h3 id="함수-선언시-암묵적인-규칙">함수 선언시 암묵적인 규칙</h3>
<blockquote>
</blockquote>
<ol>
<li>함수는 동작 하나만 담당해야 한다.</li>
<li>이름만 보고도 어떤 동작을 하는지 알 수 있는 코드를 자기 설명적(self-describing) 코드라하고, 함수는 자기 설명적 코드여야한다. 즉, 함수 이름은 함수가 어떤 동작을 하는지 표현해야한다.</li>
</ol>
<h3 id="함수를-선언하는-또-다른-방법--함수-표현식function-expression">함수를 선언하는 또 다른 방법 : 함수 표현식(function expression)</h3>
<p>자바스크립트는 함수를 특별한 종류의 값으로 취급한다.</p>
<p>이러한 특성에 의해, 함수 선언문 이외에도 함수 표현식을 사용해 함수를 만들 수 있다.</p>
<p>변수를 선언하고 할당하는 것처럼, 함수를 생성하고 변수에 할당할 수 있다.</p>
<pre><code class="language-javascript">// 함수 선언문
function sayHi() {
  alert( &quot;Hello&quot; );
}

// 함수 표현식
let sayHi = function() {
  alert( &quot;Hello&quot; );
};</code></pre>
<p>위의 어떤 방식으로 함수를 생성했든, 함수는 값으로 취급되고 변수에 할당할 수 있다. 따라서, 변수를 다른 변수에 할당하는 것처럼 함수도 다른 변수에 할당할 수 있다.</p>
<pre><code class="language-javascript">function sayHi() {   // (1) 함수 생성
  alert( &quot;Hello&quot; );
}

let func = sayHi;    // (2) 함수 복사

func(); // Hello     // (3) 복사한 함수를 실행(정상적으로 실행됩니다)!
sayHi(); // Hello    //     본래 함수도 정상적으로 실행됩니다.</code></pre>
<h3 id="콜백함수callback-function">콜백함수(callback function)</h3>
<pre><code class="language-javascript">function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( &quot;동의하셨습니다.&quot; );
}

function showCancel() {
  alert( &quot;취소 버튼을 누르셨습니다.&quot; );
}

// 사용법: 함수 showOk와 showCancel가 ask 함수의 인수로 전달됨
ask(&quot;동의하십니까?&quot;, showOk, showCancel);</code></pre>
<p>위 예시에서 함수 ask의 인수, showOk와 showCacncel을 <code>콜백함수</code> 또는 <code>콜백</code>이라 한다.</p>
<p>쉽게 말해 어떤 함수를 함수의 인수로 전달하고, 인수로 전달한 그 함수를 나중에 호출(called back)하는 것이 콜백 함수의 개념이다.</p>
<h3 id="익명함수anonymous-function">익명함수(anonymous function)</h3>
<pre><code class="language-javascript">function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  &quot;동의하십니까?&quot;,
  function() { alert(&quot;동의하셨습니다.&quot;); },
  function() { alert(&quot;취소 버튼을 누르셨습니다.&quot;); }
);</code></pre>
<p>위의 예시에서 함수 ask의 인수에 이름이 없이 선언된 함수를 익명함수라 한다.</p>
<p>익명 함수는 변수에 할당된 것이 아니므로, 함수 ask 내부에서만 접근할 수 있다.</p>
<h3 id="함수-표현식과-함수-선언문-차이">함수 표현식과 함수 선언문 차이</h3>
<ol>
<li><p>문법적 차이</p>
</li>
<li><p>자바스크립트 엔진이 언제 함수를 생성하는지 차이</p>
<ul>
<li>함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성한다. 따라서, 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있다.</li>
<li>함수 선언문은 함수 선언문이 정의되기 전에도 호출할 수 있다. 쉽게 말해 함수 호출이 함수 선언보다 앞서도 함수를 사용할 수 있다. 이게 가능한 이유는 자바스크립트의 내부 알고리즘 때문이다. 자바스크립트는 스크립트를 실행하기 전 준비단계에서 전역에 선언된 함수 선언문을 찾고, 해당 함수를 생성한다. 스크립트는 함수 선언문이 모두 처리된 이후에서야 실행된다. 따라서 스크립트 어디서든 함수 선언문으로 선언한 함수에 접근할 수 있다.<pre><code class="language-javascript">//  함수 표현식
sayHi(&quot;John&quot;); // error!
</code></pre>
</li>
</ul>
</li>
</ol>
<p>let sayHi = function(name) {  // (*) 마술은 일어나지 않습니다.
  alert( <code>Hello, ${name}</code> );
};</p>
<p>// 함수 선언문
sayHi(&quot;John&quot;); // Hello, John</p>
<p>function sayHi(name) {
  alert( <code>Hello, ${name}</code> );
}</p>
<pre><code>
3. 스코프(scope)
strict mode에서 함수 선언문이 어떤 코드 블록 내에 위치하면, 선언된 함수는 해당 코드 블록 내에서만 접근할 수 있다.
```javascript
let age = prompt(&quot;나이를 알려주세요.&quot;, 18);

// 조건에 따라 함수를 선언함
if (age &lt; 18) {

  function welcome() {
    alert(&quot;안녕!&quot;);
  }

} else {

  function welcome() {
    alert(&quot;안녕하세요!&quot;);
  }

}

// 함수를 나중에 호출합니다.
welcome(); // Error: welcome is not defined</code></pre><p>이를 해결하기 위해서는 함수 표현식을 쓰면 된다.</p>
<pre><code class="language-javascript">let age = prompt(&quot;나이를 알려주세요.&quot;, 18);

let welcome;

if (age &lt; 18) {

  welcome = function() {
    alert(&quot;안녕!&quot;);
  };

} else {

  welcome = function() {
    alert(&quot;안녕하세요!&quot;);
  };

}

welcome(); // 제대로 동작합니다.</code></pre>
<h3 id="화살표-함수arrow-function">화살표 함수(arrow function)</h3>
<p>함수 표현식보다 단순하고 간결한 문법으로 함수를 만들 수 있는 방법이다.</p>
<pre><code class="language-javascript">let func = (arg1, arg2, ...argN) =&gt; expression</code></pre>
<p>인수가 없다면 괄호를 비워 놓으면 된다.</p>
<pre><code class="language-javascript">let func = () =&gt; expression</code></pre>
<p>함수 본문이 여러 줄이라면 중괄호 안에 함수 본문을 넣고, return 지시자를 사용해 명시적으로 결과값을 반환해주어야 한다.</p>
<pre><code class="language-javascript">let sum = (a, b) =&gt; {  // 중괄호는 본문 여러 줄로 구성되어 있음을 알려줍니다.
  let result = a + b;
  return result; // 중괄호를 사용했다면, return 지시자로 결괏값을 반환해주어야 합니다.
};

alert( sum(1, 2) ); // 3</code></pre>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://ko.javascript.info/first-steps">https://ko.javascript.info/first-steps</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 11 자바스크립트) 자바스크립트 소개]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-11-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@dev_diver/22.-10.-11-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Tue, 11 Oct 2022 10:17:49 GMT</pubDate>
            <description><![CDATA[<h1 id="자바스크립트란">자바스크립트란?</h1>
<blockquote>
<p>웹 페이지에 생동감을 불어넣기위해 만들어진 프로그래밍 언어이다.</p>
</blockquote>
<h3 id="스크립트script">스크립트(Script)</h3>
<p>자바스크립트로 작성한 프로그램을 뜻한다. 웹페이지의 HTML 안에 작성하고, 웹페이지를 불러올 때 자동으로 실행된다.</p>
<h3 id="자바스크립트만의-강점-세-가지">자바스크립트만의 강점 세 가지</h3>
<ul>
<li>HTML / CSS와 완전히 통합할 수 있다.</li>
<li>간단한 일을 간단하게 처리할 수 있게 한다.</li>
<li>모든 주요 브라우저에서 지원하고, 기본 언어로 사용된다.</li>
</ul>
<hr>
<h1 id="자바스크립트-실행환경">자바스크립트 실행환경</h1>
<p>자바스크립트 엔진이 들어 있는 모든 디바이스에서 동작가능하다. 기본적으로 자바스크립트 가상 머신이라는 엔진이 내장된 브라우저뿐만아니라 서버에서 실행할 수도 있다.</p>
<h3 id="자바스크립트-엔진의-종류">자바스크립트 엔진의 종류</h3>
<blockquote>
</blockquote>
<ul>
<li>V8 : Chrome, Opera</li>
<li>SpiderMonky : Firefox</li>
<li>Trident, Chakra : IE</li>
<li>ChakraCore : Edge</li>
<li>SquirrelFish : Safari</li>
</ul>
<h3 id="자바스크립트-엔진의-간단한-기본-동작-원리">자바스크립트 엔진의 간단한 기본 동작 원리</h3>
<blockquote>
</blockquote>
<ol>
<li>파싱 : 자바스크립트 엔진이 스크립트를 읽는다.</li>
<li>컴파일 : 읽은 스크립트를 기계어로 전환한다.</li>
<li>실행 : 기계어로 전환된 코드가 실행된다.</li>
</ol>
<p>자바스크립트 엔진은 위의 각 단계마다 최적화를 진행한다. 심지어 컴파일이 끝나고 실행중인 코드를 감시하면서 이 코드로 흘러가는 데이터를 분석하고, 분석 결과를 토대로 기계어로 전환된 코드를 다시 최적화하기도 한다. 이런 과정을 거치면 스크립트 실행 속도가 더 빨라진다.</p>
<h3 id="브라우저에서-자바스크립트로-할-수-있는-일">브라우저에서 자바스크립트로 할 수 있는 일</h3>
<blockquote>
</blockquote>
<ul>
<li>웹페이지에 새로운 HTML을 추가하거나, 기존 HTML이나 스타일을 수정</li>
<li>마우스 클릭, 포인터의 움직임, 키보드 눌림 등 사용자의 행동(이벤트)에 반응</li>
<li>네트워크를 통해 원격 서버에 요청을 보내거나, 파일 다운로드, 업로드하기 (AJAX, COMET 등 사용)</li>
<li>쿠키 설정, 사용자에게 질문을 건네거나 메시지 보여주기</li>
<li>클라이언트측에 데이터 저장하기(로컬 스토리지)</li>
</ul>
<p>모던 자바스크립트는 기본적으로 브라우저를 대상으로 만든 언어이기 때문에, 메모리나 CPU 같은 로우레벨의 조작이나, 운영체제가 지원하는 기능을 허용하지 않기 때문에 안전하다.</p>
<p>자바스크립트의 능력은 실행 환경에 상당한 영향을 받는다. 예를들어, Node.js 환경에선 임의의 파일을 읽거나 쓰고, 네트워크 요청을 수행하는 함수를 지원한다. 브라우저 환경에선 웹페이지 조작, 클라이언트와 서버의 상호작용에 관한 모든 일을 할 수 있다.</p>
<h3 id="브라우저에서-자바스크립트로-할-수-없는-일">브라우저에서 자바스크립트로 할 수 없는 일</h3>
<blockquote>
</blockquote>
<ul>
<li>웹페이지 내 스크립트는 디스크에 저장된 임의의 파일을 읽거나 쓰고, 복사하거나 실행할 때 제약을 받을 수 있음. 단, 사용자가 브라우저 창에 파일을 끌어다 두거나, <code>&lt;input&gt;</code> 태그를 통해 파일을 선택할 때와 같이 특정 상황에서는 파일 접근을 허용함.</li>
<li>카메라나 마이크 같은 디바이스와 상호 작용하려면 사용자의 명시적인 허가가 있어야 함.</li>
<li>브라우저 내의 탭과 창은 대개 서로의 정보를 알 수 없음. 단, 자바스크립트를 사용해 한 창에서 다른 창을 열 때는 예외가 적용됨. 하지만 이 경우에도 도메인이나 프로토콜, 포트가 다르다면 페이지에 접근할 수 없음. 이런 제약사항을 동일 출처 정책(Same Origin Policy)이라 부름. 이 정책을 피하기 위해서 두 페이지는 데이터 교환에 동의해야 하고, 동의와 관련된 특수한 자바스크립트 코드를 포함하고 있어야 함.</li>
<li>자바스크립트를 이용하면 페이지를 생성한 서버와 쉽게 정보를 주고 받을 수 있음. 하지만 타 사이트나 도메인에서 데이터를 받아오는 것은 불가능함. 가능하다 해도 원격 서버에서 HTTP 헤더 등을 이용하여 명확히 승인을 해줘야 함. </li>
</ul>
<p>브라우저는 보안을 위해 자바스크립트의 기능에 제약을 걸어놓는다. 이런 제약은 악성 웹페이지가 개인 정보에 접근하거나 사용자의 데이터를 손상시키는 것을 막기 위해 만들어졌다.</p>
<p>단, 브라우저 환경 밖, 예를 들어 서버라면 이러한 제약은 존재하지 않는다. 또, 모던 브라우저에선 추가 권한 허가를 요청하는 플러그인이나 익스텐션 설치가 허용된다.</p>
<hr>
<h1 id="자바스크립트-이외의-언어들">자바스크립트 이외의 언어들</h1>
<p>근래엔 브라우저에서 실행 되기전에 자바스크립트로 트랜스파일(transpile)할 수 있는 새로운 언어들이 많이 등장했다.</p>
<p>최신 툴들은 자바스크립트 이외의 언어로 작성한 코드를 자바스크립트로 자동 변환해준다.</p>
<h3 id="자바스크립트로-트랜스파일이-가능한-언어-종류">자바스크립트로 트랜스파일이 가능한 언어 종류</h3>
<blockquote>
</blockquote>
<ul>
<li><code>CoffeScript</code> : 간단한 문법을 도입하여 명료하고 이해하기 쉬운 코드 작성할 수 있는 언어</li>
<li><code>TypeScript</code> : 자료형을 강제로 명시하여 개발을 단순화하고, 복잡한 시스템을 지원하려는 목적으로 만들어진 언어</li>
<li><code>Flow</code> : 자료형을 강제한다. Typescript와는 다른 방식을 사용하는 언어</li>
<li><code>Dart</code> : 모바일 앱과 같이 브라우저가 아닌 환경에서 동작하는 고유의 엔진을 가진 독자적인 언어</li>
</ul>
<p>이외에도 다양한 언어들이 존재한다.</p>
<hr>
<h1 id="자바스크립트-명세서">자바스크립트 명세서</h1>
<blockquote>
</blockquote>
<p><strong>ECMA-262 명세서</strong></p>
<blockquote>
</blockquote>
<ul>
<li>자바스크립트를 정의하는 공식 문서로 매년 새로운 버전이 나온다.</li>
<li><a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-262/">https://www.ecma-international.org/publications-and-standards/standards/ecma-262/</a></li>
</ul>
<hr>
<h1 id="자바스크립트-매뉴얼">자바스크립트 매뉴얼</h1>
<blockquote>
</blockquote>
<p><strong>MDN Javascript Reference</strong></p>
<blockquote>
</blockquote>
<ul>
<li>자바스크립트에 관한 다양한 예제와 정보가 있는 매뉴얼</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference</a></li>
</ul>
<hr>
<h1 id="자바스크립트-호환성-표">자바스크립트 호환성 표</h1>
<blockquote>
</blockquote>
<ul>
<li>특정 브라우저나 엔진이 내가 사용하려는 기능을 지원하는지 확인할 수 있다.</li>
<li><a href="http://caniuse.com">http://caniuse.com</a></li>
</ul>
<hr>
<h1 id="참고문헌">참고문헌</h1>
<p><a href="https://ko.javascript.info/getting-started">https://ko.javascript.info/getting-started</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 10 Next) div contenteditable]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-10-Next-div-contenteditable</link>
            <guid>https://velog.io/@dev_diver/22.-10.-10-Next-div-contenteditable</guid>
            <pubDate>Sun, 09 Oct 2022 16:07:40 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-발생">문제 발생</h1>
<p>프로젝트 진행 중 div contenteditable로 여러 줄 텍스트를 입력받았다.</p>
<p>innerText로 각 줄의 문자열들을 배열로 저장하면 개행시에 &quot;&quot;, &quot;&quot; 두 개의 공백 문자열이 저장되는 문제 발생</p>
<p>한 번의 개행에 두 개의 공백이 발생하므로, 개행이 두배로 복사되는 문제 발생</p>
<h1 id="원인-분석">원인 분석</h1>
<p>div contenteditable 하위 element로 공백이 <code>&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</code> 형태로 DOM이 나타난다. 아마 &quot;&quot;가 두 개 저장되는 이유는 비어있는 <code>&lt;div&gt;</code>와 <code>&lt;br/&gt;</code>을 각각 공백으로 받아들이기 때문이라고 생각한다.</p>
<h1 id="문제-해결">문제 해결</h1>
<pre><code class="language-javascript">document.getElementById(&quot;inputDiv&quot;)?.innerText.replaceAll(&#39;\n\n&#39;,&#39;\n&#39;).split(&quot;\n&quot;)</code></pre>
<p>두 개의 개행문자들을 하나로 replace한 후, 다시 개행문자 하나로 문자열들을 나눠서 배열에 저장한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22 .10. 06 Next.js) Authentication 관리]]></title>
            <link>https://velog.io/@dev_diver/22-.10.-06-Next.js-Authentication-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@dev_diver/22-.10.-06-Next.js-Authentication-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 06 Oct 2022 13:00:13 GMT</pubDate>
            <description><![CDATA[<p>미들웨어를 이용한 authentication 관리 기법</p>
<p><a href="https://velog.io/@sbinha/next.js-%EC%97%90%EC%84%9C-middleware.ts%EC%9D%98-%EC%97%AD%ED%95%A0%EA%B3%BC-%EC%B5%9C%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C">https://velog.io/@sbinha/next.js-%EC%97%90%EC%84%9C-middleware.ts%EC%9D%98-%EC%97%AD%ED%95%A0%EA%B3%BC-%EC%B5%9C%EA%B7%BC-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C</a></p>
<p>님의 힌트를 얻음. 내용은 추후 추가 예정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 10. 01 Next.js) SSR 토큰 관리]]></title>
            <link>https://velog.io/@dev_diver/22.-10.-01-Next.js-SSR-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@dev_diver/22.-10.-01-Next.js-SSR-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sat, 01 Oct 2022 03:11:14 GMT</pubDate>
            <description><![CDATA[<h1 id="ssr방식에서-토큰-관리하기">SSR방식에서 토큰 관리하기</h1>
<ol>
<li>로그인 post 요청 후 서버에서 access token 과 refresh 토큰 받음</li>
<li>zustand에 access token, 쿠키에 refresh token을 저장</li>
<li>헤더에 access token 추가하여 서버에 정보들 요청</li>
<li>새로고침 시 zustand로 저장한 access token이 초기화되므로, 쿠키의 refresh token을 이용해 재발급</li>
<li>재발급 받은 토큰들을 2번 방법으로 재설정</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[22. 09. 30 Next.js) s3 이미지 렌더링 문제]]></title>
            <link>https://velog.io/@dev_diver/22.-09.-30-Next.js-s3-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev_diver/22.-09.-30-Next.js-s3-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 30 Sep 2022 14:27:42 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-발생">문제 발생</h1>
<p>background-image 속성으로 s3 버킷의 파일 url을 넣어주는 과정에 값은 정확히 들어가지만 이미지가 렌더링은 안되고, 링크를 클릭하면 이미지가 다운로드되는 문제 발견.</p>
<h1 id="원인-분석">원인 분석</h1>
<p>next.js가 url을 base url 기준으로 읽어오는 방식이 문제인 것으로 판단됨.</p>
<h1 id="문제-해결">문제 해결</h1>
<p>아래와 같이 경로를 rewrite 하는 코드를 설정하여 해결.</p>
<pre><code class="language-javascript">// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
          source: &#39;/:path*&#39;,
          destination: &#39;https://example/:path*&#39;, 
      },
    ]
  },
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>