<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>saint.log</title>
        <link>https://velog.io/</link>
        <description>frontend developer</description>
        <lastBuildDate>Thu, 08 Dec 2022 10:39:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. saint.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/st_hwang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프론트엔드 개발에서의 디자인 패턴]]></title>
            <link>https://velog.io/@st_hwang/babwm67z</link>
            <guid>https://velog.io/@st_hwang/babwm67z</guid>
            <pubDate>Thu, 08 Dec 2022 10:39:08 GMT</pubDate>
            <description><![CDATA[<p>팀프로젝트를 진행하면서 다른 프론트엔드 개발자들과 프론트엔드에서의 디자인 패턴을 어떻게 가져갈 것인가에 대한 이야기를 나누게 되었다. 혼자 개발을 한다면 자신만의 스타일대로 코드를 작성하겠지만, 협업을 하면서는 어떻게 코드를 작성하는지를 먼저 결정하고 그 방법론대로 개발을 하는 것이 가독성, 유지보수성, 재사용성 측면에서 더 좋을 것이라 생각했다. 근데 막상 생각해보니 디자인 패턴에 대한 개념이 모호했다. 소프트웨어 아키텍처? 디자인 패턴? 헷갈리는 용어도 많고 많은 패턴들이 있어서 이번 기회에 개념을 명확하게 잡아보고 싶었다.</p>
<h3 id="소프트웨어-아키텍처와-디자인-패턴의-차이">소프트웨어 아키텍처와 디자인 패턴의 차이</h3>
<p>처음에는 같은 의미인줄로만 알았던 소프트웨어 아키텍처와 디자인 패턴이라는 용어. 이 둘의 차이점을 알아보기 위해 각각의 정의에 대해서 알아보았다.</p>
<p><strong><code>소프트웨어 아키텍처</code></strong> : 유연성, 확장성, 실현가능성, 재사용성, 보안성과 같은 소프트웨어의 특성들을 기술적, 사업적 기대사항에 맞는 구조화된 솔루션으로 만들어가는 과정으로 아키텍처 특성(architecture chararteristic), 아키텍처 결정(architecture decision), 설계 원칙(design principle)이 결합된 시스템의 구조(structure)</p>
<ul>
<li><p>아키텍처 특성 : 시스템의 기능과 직교하는 시스템의 성공 기준 (성능, 확장성, 신뢰성, 활용성, 유지보수성, 접근성, 보안성, 사용성 등)</p>
</li>
<li><p>아키텍처 결정 : 시스템 구축에 필요한 <code>규칙</code>들을 정하는 것. 시스템의 제약 조건을 형성</p>
</li>
<li><p>설계 원칙 : 시스템 구축에 필요한 <code>가이드라인</code>을 정하는 것.</p>
</li>
</ul>
<p><strong><code>소프트웨어 디자인 패턴</code></strong> : 코드 레벨의 디자인 (각각의 모듈이 어떤 역할을 수행하는지, 클래스 범위, 함수의 목적 등). 소프트웨어의 특정 구현을 직접 제공하지는 않지만, 반복되는 문제 상황들을 최적화된 방법으로 해결하도록 돕는 역할</p>
<p>각각의 정의를 살펴보면 디자인 패턴은 소프트웨어 아키텍처보다 작은 범위에 속하는 <strong>어떻게 코드를 작성할 것인가에 대한 방법론</strong>이라고 할 수 있을 것 같다.</p>
<h3 id="어떤-디자인-패턴을-사용할-것인가">어떤 디자인 패턴을 사용할 것인가?</h3>
<p>디자인 패턴의 종류를 살펴보니 굉장히 많은 디자인 패턴들이 있었다. 처음에는 뭣도 모르고 많은 책들과 글들을 읽어보았는데 고전적인 디자인 패턴들은 모두 객체 지향 프로그래밍(OOP)를 중심으로 한 디자인 패턴이었다. 예전에는 프론트엔드와 백엔드가 나누어져 있지도 않았으니... 생성, 구조, 행동 패턴으로 나뉘어지는 GOF 패턴에는 다양한 패턴(23가지)이 있었는데 살펴보니 모두 클래스와 객체의 관계를 중심으로 한 객체 지향성 디자인 패턴이었다. 프론트엔드에서 사용할만한 패턴들은 아니라고 생각했다. 당연히 일부 개념은 나도 모르게 사용하고 있었을 수도 있지만. 애초에 GOF 패턴이 나온게 30년 전이다.</p>
<p>프론트엔드가 백엔드로부터 분리된 것보다 훨씬 오래된 이런 고전적 패턴들을 살펴보고 비교해보는 것은 맞지 않다고 생각되었다. 그래서 MVC로 대표되는 MV* 모델(MVC, MVVM), FLUX 패턴, 아토믹 패턴 등을 살펴보았다.</p>
<h3 id="mvc-패턴">MVC 패턴</h3>
<p>옛날에 국비 교육을 받으면서 자바를 배울 때 강사님이 MVC 패턴이 얼마나 중요한지 반드시 이해해야 된다면서 열심히 설명해주셨던 기억이 난다... MVC 패턴은 Model - View - Controller 로 역할을 구분지어 분류해놓은 것이다. 각각의 역할은 다음과 같다.</p>
<ul>
<li><code>Model</code> : 데이터와 비즈니스 로직 영역</li>
<li><code>View</code> : UI. 유저가 직접 마주하는 화면.</li>
<li><code>Controller</code> : Model과 View의 조율자 역할. View로 부터 유저의 입력이 들어오면, Model의 데이터를 핸들링하여 결과를 다시 View로 보내는 역할</li>
</ul>
<img src="https://developer.mozilla.org/en-US/docs/Glossary/MVC/model-view-controller-light-blue.png" alt="MVC - 용어 사전 | MDN" style="zoom:24%;" />

<p>MVC 패턴은 굉장히 유명한 디자인 패턴인데다가, 나온지가 꽤 되었는데도 아직까지도 소프트웨어 개발에서 많이 쓰이고 있다. 그러면 프론트엔드 개발을 할 때에도 MVC 패턴을 활용할 수 있을까? 개인적으로는 별로 좋은 것 같지 않다. 일단 MVC 패턴에서 프론트엔드는 거의 View만을 담당하는데다가(물론 프론트엔드에서도 컨트롤러 역할을 하는 로직들이 있을 수 있다) MVC 패턴에서의 View는 그냥 단순히 화면을 보여주는 역할로 정의한 반면에 프론트엔드에서는 그 View에서 발생하는 수많은 이벤트를 처리하는 것이 중요하다. 그럼 이게 View인지 Controller인지 개념도 모호해지고... </p>
<p>현재 프론트엔드 개발을 할 때에는 많은 View가 존재하고 또 각각의 View에서 필요한 데이터가 많고, View에서 Model과 직접 소통이 필요한 경우도 많을텐데, Controller를 통해 접근할 수 밖에 없다면 Controller도 굉장히 커질 것 같다. 그리고 View를 잘 다루는 것이 프론트엔드 개발자가 할 중요한 일인데 View를 단순히 화면으로 보는 디자인 패턴으로 개발을 어떻게 함? 결론은 프론트엔드랑 MVC 패턴은 조금 안맞는 것 같다. </p>
<h3 id="mvvm-패턴">MVVM 패턴</h3>
<p>기존의 MVC 패턴을 변형해서 Model, Veiw, ViewModel 로 바뀌었다. 가장 중요한 달라진 점은 Controller가 ViewModel로 바뀌었는데, View를 위한 모델인 것이다. 기존의 Controller는 View에서 데이터를 요청할 때마다 기능을 반복적으로 수행했다면, ViewModel은 VIew와 데이터 바인딩이 되어있다. 쉽게 말해서 Model에서 데이터가 변경되면 View에서도 자동으로 바뀐다. 앞서 MVC 패턴의 문제점이었던 View와 Model의 양방향 소통성을 해결하고, Controller가 방대해지는 것을 방지할 수 있다. 이러한 데이터 바인딩은 처음에 앵귤러에서 지원을 해서 주목받았다고 하는데 많은 프레임워크들이 지원하고 있는 기능이다.(앵귤러, vue, 리액트, 스벨트 등). 그런데 이러한 MVVM 패턴도 많은 View가 계층적으로 존재하기 때문에, 데이터를 공유하면서 발생하는 Props Drilling Problem 문제점을 해결하지는 못했다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/785c860d-1371-4087-a31a-e0ccc55254e9/image.png" alt=""></p>
<h3 id="redux와-flux-패턴">Redux와 Flux 패턴</h3>
<p>Flux 패턴은 페이스북에서 발표한 개념으로 단방향으로 흐름을 제어하고 동작하게끔 하는 패턴이다. View에서 어떠한 입력을 통해 Action을 호출하게 되면 그 Action이 Dispatch를 통해 다시 store에 데이터를 전달하고, store에서 변경사항이 있으면 다시 View가 바뀌는 이러한 단방향적인 구조를 가진다. 사실 실제로 바로 쓰이지는 않았고 단방향성의 개념을 심어주는 것에서 그쳤다고 한다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/cf0d64cb-d5b5-43a8-883b-68605d94ca77/image.png" alt=""></p>
<p>그리고 이러한 Flux 패턴을 활용한 Redux가 등장한다. 기존의 문제점이었던 Props Drilling Problem의 문제점을 해결하면서, Store, Dispatch, Reducer의 개념을 명확히 했다. Redux 패턴은 많은 컴포넌트로 나누어져 있던 View를 하나의 개념으로 봤다. 비즈니스 로직에서 완전히 View를 분리시켜서 View 단에서 UI 구성에 필요한 데이터들을 상태(state)라고 하고, 이러한 상태들을 관리하는 것이 상태 관리(state management)라고 하게 됐는데, Redux 등장 이후 많은 상태 관리 라이브러리들이 후속하여 등장하였다. 이때부터 상태 관리가 프론트엔드의 주요 아젠다로 떠오른 계기라고 할 수 있을 것 같다. </p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/b8fe6d2a-540f-4765-9021-2f525c9a389d/image.webp" alt=""></p>
<p>하지만 Redux 패턴도 치명적인 문제가 있는데 Redux를 사용해본 사람은 알겠지만 바로 보일러플레이트가 너무 많아요... Redux 공식 홈페이지에서는 보일러플레이트 줄이는 방법을 아예 하나의 챕터로 안내하고 있으며, 개선을 위해 Redux toolkit이 등장하기도 했다. 또한 복잡한 Redux를 쓰기보다는 더 간단하게 Props Drilling Problem을 해결하기 위한 방법으로 React 라이브러리에서 제공하는 <code>Context API</code>를 사용하기도 한다.</p>
<h3 id="atomic-패턴">Atomic 패턴</h3>
<p>아토믹 패턴은 컴포넌트를 잘 관리하기 위한 방법론으로 View를 원자(Atoms), 분자(Molecules), 유기체(Organisms), 템플릿(Templates), 페이지(Pages) 순으로 작은 것들을 만들고 결합해 더 큰 단위의 View를 그리는 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/3e7d4bc8-1308-4fe2-ad2c-e1f0f32b9011/image.png" alt=""></p>
<ul>
<li>원자 : button, input, font  등 가장 작은 구성요소</li>
<li>분자 : 여러 개의 원자 요소의 구성으로, 여기서부터 복잡한 요소를 구성하고 재사용하기 시작함</li>
<li>유기체 : 사용자에게 더 의미 있는 정보를 제공하거나 인터랙션하는 정교한 인터페이스를 구성하는 원자 또는 분자의 조합</li>
<li>템플릿 : 실제 콘텐츠가 입혀지기 전에 정해지는 와이어 프레임</li>
<li>페이지 : 완성된 최종 View</li>
</ul>
<p>이러한 아토믹 패턴에서 Atoms 개념을 따온 것이 Recoil 라이브러리에 도입되었는데, Recoil은 Redux 보다 간단한 문법으로 컴포넌트 외부에서 공통의 데이터를 set, get 할 수 있게 하면서 동시에 동기화를 할 수 있게 한다. Recoil에는 Atoms와 Selector라는 개념이 있는데, Selector는 다른 atom이나 selector를 입력으로 받아들이면서 상위의 atoms 또는 seletor가 업데이트되면 하위의 selector 함수도 다시 실행되며 컴포넌트들도 다시 렌더링된다. 이러한 방법은 Redux보다 성능적으로 우수하며 (O(1), Redux는 O(n)), 사용하기에 따라 상태 정의의 유지 관리가 더 쉬울 수 있다. (중앙집중식 vs 분산관리식). 단점은 미들웨어가 존재하지 않기 때문에 때에 따라 사용하기 적합하지 않는 환경이 있을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/bdbb9d2d-0c3a-441d-89b3-b38c23ce7ddd/image.png" alt=""></p>
<p>그 외에도 MVC 패턴에 리액트쿼리를 도입한 개념도 있는 것 같고, 다양한 디자인 패턴들이 필요에 의해 만들어지고 주장되는 것 같다. 디자인 패턴을 알아보다보면서 프론트엔드의 역사도 대략적으로 알게되고 시야가 조금 넓어진 것 같다. 결국 디자인 패턴은 하나의 방법론이고 필요한 것이긴 하지만 너무 타이트하게 패턴을 맞추려고 하다보면 오히려 패턴에 코드를 우겨넣는 일도 발생할 것 같다는 생각이 든다. 어떠한 패턴이 우리의 서비스에 더 알맞을 것인지 조금은 느슨하게 생각해볼 필요가 있다고 느꼈다.</p>
<blockquote>
<p>참고</p>
<p>마크 리처즈, <em>소프트웨어 아키텍처 101</em>, 한빛미디어(주), 2021</p>
<p><a href="https://codeburst.io/software-architecture-the-difference-between-architecture-and-design-7936abdd5830">https://codeburst.io/software-architecture-the-difference-between-architecture-and-design-7936abdd5830</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Glossary/MVC">https://developer.mozilla.org/ko/docs/Glossary/MVC</a></p>
<p><a href="https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-MV-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94">https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-MV-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94</a></p>
<p><a href="https://innovature.ai/react-state-management-with-redux/">https://innovature.ai/react-state-management-with-redux/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트쿼리] useQuery (공홈 번역)]]></title>
            <link>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%BF%BC%EB%A6%AC-useQuery-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%BF%BC%EB%A6%AC-useQuery-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Mon, 05 Dec 2022 10:24:33 GMT</pubDate>
            <description><![CDATA[<p>공식 홈페이지 문서를 번역한 내용입니다.</p>
<blockquote>
<p>출처
<a href="https://tanstack.com/query/v4/docs/guides/queries">https://tanstack.com/query/v4/docs/guides/queries</a></p>
</blockquote>
<h2 id="usequery">useQuery</h2>
<h3 id="query-basics">Query Basics</h3>
<p>쿼리는 고유 키에 연결된 데이터의 비동기 소스에 대한 선언적 종속성입니다. 쿼리는 Promise 메서드를 통해(GET, POST 메서드를 포함한) 서버에서 데이터를 페치하는데 사용될 수 있습니다. 메서드가 서버에서 데이터를 수정한다면 <code>Mutations</code> 를 사용하는 것을 추천합니다.</p>
<p>컴포넌트나 커스텀 훅에서 쿼리를 구독하려면 다음 항목들과 함께 <code>useQuery</code> 훅을 사용하세요.</p>
<ul>
<li>쿼리의 고유키</li>
<li>Promise를 반환하는 함수 <ul>
<li>데이터를 해결하거나</li>
<li>에러를 던지는</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">import { useQuery } from &#39;@tanstack/react-query&#39;

function App() {
  const info = useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchTodoList })
}</code></pre>
<p>고유키는 어플리케이션에서 모든 쿼리들에 대해 내부적으로 리페칭, 캐싱, 공유 등에 사용됩니다.</p>
<p><code>useQuery</code> 에 의해 반환된 쿼리 결과는 템플릿 작성이나 다른 데이터의 사용에 필요한 모든 정보를 포함하고 있습니다.</p>
<pre><code class="language-tsx">const result = useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchTodoList })</code></pre>
<p><code>result</code> 오브젝트는 몇 가지 중요한 상태가 포함되어 있습니다. 쿼리는 주어진 순간에 오직 하나의 상태가 될 수 있습니다.</p>
<ul>
<li><code>isLoading</code> 또는 <code>status === &#39;loading&#39;</code> - 쿼리가 아직 데이터가 없음</li>
<li><code>isError</code> 또는 <code>status === &#39;error&#39;</code> - 쿼리가 에러 상태임</li>
<li><code>isSuccess</code> 또는 <code>status === &#39;success&#39;</code> - 쿼리가 성공하고 데이터를 사용가능함</li>
</ul>
<p>이런 중요한 상태들을 넘어서, 쿼리 상태에 대해 더 많은 중요한 정보들을 이용할 수 있습니다.</p>
<ul>
<li><code>error</code> - 쿼리가 <code>isError</code> 상태면, <code>error</code> 프로퍼티를 통해 에러 객체를 사용할 수 있습니다.</li>
<li><code>data</code> - 쿼리가 <code>success</code> 상태면, <code>data</code> 프로퍼티를 통해 데이터를 사용할 수 있습니다.</li>
</ul>
<p>대부분의 쿼리의 경우 일반적으로 <code>isLoading</code> 상태를 체크한 다음, <code>isError</code> 상태, 마지막으로 데이터를 사용할 수 있고 성공적인 상태를 렌더한다고 생각하면 됩니다.</p>
<pre><code class="language-tsx">function Todos() {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodoList,
  })

  if (isLoading) {
    return &lt;span&gt;Loading...&lt;/span&gt;
  }

  if (isError) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;
  }

  // We can assume by this point that `isSuccess === true`
  return (
    &lt;ul&gt;
      {data.map(todo =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}</code></pre>
<p>boolean 값이 싫다면 <code>&#39;status&#39;</code> 상태에서도 사용할 수 있습니다.</p>
<pre><code class="language-tsx">function Todos() {
  const { status, data, error } = useQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodoList,
  })

  if (status === &#39;loading&#39;) {
    return &lt;span&gt;Loading...&lt;/span&gt;
  }

  if (status === &#39;error&#39;) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;
  }

  // also status === &#39;success&#39;, but &quot;else&quot; logic works, too
  return (
    &lt;ul&gt;
      {data.map(todo =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}</code></pre>
<p>타입스크립트는 <code>loading</code>, <code>error</code> 값에 접근하기 전에 데이터의 타입을 정확하게 줄여줄 것입니다.</p>
<h3 id="fetchstatus">FetchStatus</h3>
<p>게다가 <code>status</code> 외에도, <code>result</code> 오브젝트에서 추가적인 <code>fetchStatus</code> 프로퍼티를 다음과 같은 옵션을 통해 얻을 수 있습니다.</p>
<ul>
<li><code>fetchStatus === &#39;fetching&#39;</code> - 쿼리가 현재 페치중</li>
<li><code>fetchStatus === &#39;paused&#39;</code> - 쿼리가 페치를 원하지만 잠시 중지중</li>
<li><code>fetchStatus === &#39;idle&#39;</code> - 쿼리가 아무것도 안하는중</li>
</ul>
<h3 id="why-two-different-states">Why two different states?</h3>
<p>백그라운드 리페치와 유효기간 재검증 로직은 <code>status</code> 와 <code>fetchStatus</code> 의 모든 조합을 가능하게 만듭니다. 예를 들어,</p>
<ul>
<li><code>success</code> 상태의 쿼리는 보통 fetchStatus의 <code>idle</code> 이 되지만, 백그라운드 리페치가 일어나는 중이라면 여전히 <code>fetching</code> 상태일 것이다.</li>
<li>데이터를 마운트하지 않는 쿼리는 <code>loading</code> 상태에 있고, fetchStatus의 <code>fetching</code> 상태에 있겠지만, 네트워크 연결이 없다면 <code>paused</code> 상태에 있을 것이다.</li>
</ul>
<p>따라서 쿼리는 실질적인 데이터 페칭없이는 <code>loading</code> 상태에 있을 것입니다. </p>
<ul>
<li><code>status</code> : 데이터가 있는지 없는지에 대한 정보를 줍니다. </li>
<li><code>fetchStatus</code> : <code>queryFn</code> 이 작동하는지 안하는지에 대한 정보를 줍니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트쿼리] Important Defaults (공홈 번역)]]></title>
            <link>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%9D%98-%EC%A4%91%EC%9A%94%ED%95%9C-%EA%B8%B0%EB%B3%B8%EA%B0%92-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%9D%98-%EC%A4%91%EC%9A%94%ED%95%9C-%EA%B8%B0%EB%B3%B8%EA%B0%92-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Mon, 05 Dec 2022 09:20:10 GMT</pubDate>
            <description><![CDATA[<p>공식 홈페이지 문서를 번역한 내용입니다.</p>
<blockquote>
<p>출처
<a href="https://tanstack.com/query/v4/docs/guides/important-defaults">https://tanstack.com/query/v4/docs/guides/important-defaults</a></p>
</blockquote>
<h2 id="important-defaults">Important Defaults</h2>
<p>리액트 쿼리는 공격적이면서도 온전한 기본값으로 구성되어 있습니다. 때때로 이런 기본값이 새로운 유저를 당황하게 만들고, 새롭게 배우고 / 디버깅하는 것을 어렵게 만들 수 있습니다. 리액트 쿼리를 배우고 사용할 때에는 다음을 염두하는 것이 좋습니다.</p>
<ul>
<li><code>useQuery</code> 나 <code>useInfiniteQuery로</code> 생성된 Query 인스턴스는 기본적으로 캐시된 데이터를 stale (오래된) 상태로 간주합니다.</li>
</ul>
<blockquote>
<p>이 동작을 변경하려면, <code>staleTime</code> 옵션을 통해 쿼리를 전역적으로 쿼리별로 당신의 쿼리를 구성하면 됩니다. <code>staleTime</code> 옵션을 길게 설정하는 것은 쿼리들이 데이터를 자주 리페칭하지 않는 것을 의미합니다.</p>
</blockquote>
<ul>
<li><p>stale 쿼리들은 다음과 같은 경우 자동적으로 리페치합니다.</p>
<ul>
<li>쿼리 마운트의 새로운 인스턴스</li>
<li>윈도우 창이 다시 포커싱될 때</li>
<li>네트워크가 재연결될 때</li>
<li>(설정해놓은 ) refetch 주기마다 자동적으로</li>
</ul>
</li>
</ul>
<p>예상치 못한 리페칭이 일어난다는 것은 아마도 윈도우 창을 다시 포커싱하고, 리액트 쿼리가 <code>refetchOnWindowFocus</code> 를 수행한 것입니다. 개발 도중에 특히 브라우저와 DevTools와 앱 사이에서 다시 포커싱을 하면 리페칭이 발생하므로 유의하세요.</p>
<blockquote>
<p>리페칭이 일어나는 동작을 변경하고 싶다면, <code>refetchOnMount</code>, <code>refetchOnWindowFocus</code>, <code>refetchOnReconnet</code>, <code>refetchInterval</code> 과 같은 옵션을 사용하면 됩니다.</p>
</blockquote>
<ul>
<li><code>useQuery</code>, <code>useInfiniteQuery</code> 의 활성화된 인스턴스가 없는 쿼리 결과나 쿼리 옵저버는 &#39;inactive&#39; 라는 라벨값이 붙고 나중에 다시 쓸 때를 대비해서 캐시에 남아 있습니다.</li>
<li>기본적으로 &#39;inactive&#39; 쿼리들은 5분 후에 가비지 컬렉터 대상이 됩니다.</li>
</ul>
<blockquote>
<p>이 동작을 변경하려면 <code>cacheTime</code> 기본값을 <code>1000*60*5</code> 말고 다른 시간으로 변경하세요.</p>
</blockquote>
<ul>
<li>지수 백오프 지연으로 실패한 쿼리는 캡쳐링되고 UI에 에러가 표시되기 전에 3번 연결을 재시도합니다.</li>
</ul>
<blockquote>
<p>이 동작을 변경하려면 지수 백오프 함수에 대한 <code>retry</code>, <code>retryDelay</code> 의 기본값을 3이 아닌 것으로 설정하세요.</p>
</blockquote>
<ul>
<li>기본적으로 쿼리 결과는 구조적으로 공유되어 데이터가 실제로 변경되었는지 감지하고, 변경되지 않은 경우 useMemo나 useCallback와 관련된 value의 안정화를 위해서 데이터 레퍼런스가 변경되지 않은 채로 유지됩니다. 이 개념이 이상하게 느껴져도 걱정하지 않아도 됩니다. 99.9% 의 시간동안 이 기능을 꺼놓을 필요가 없고, 당신의 앱은 비용없이 성능이 더 좋아질겁니다.</li>
</ul>
<blockquote>
<p>구조적 공유는 JSON 호화값에서만 작동하며 다른 value 타입은 변경된 것으로 간주됩니다. 예를 들어, 거대한 응답값으로 인해 퍼포먼스 이슈가 보고싶다면 <code>config.structuralSharing</code> 플래그를 사용해서 이 옵션을 꺼놓을 수 있습니다. 쿼리 응답값에서 JSON에 호환되지 않는 값을 처리하고 데이터가 변경되었는지 여부를 감지하고자 한다면 <code>config.isDataEqual</code> 에서 데이터 비교 함수를 정의내리거나, <code>config.structuralSharing</code> 에 예전 응답값과 새로운 응답값을 비교하고, 필요에 따라 레퍼런스를 유지하는 커스텀 함수를 제공하면 됩니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 쿼리가 왜 필요할까? (공홈 번역)]]></title>
            <link>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EA%B0%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@st_hwang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EA%B0%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-%EA%B3%B5%ED%99%88-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Thu, 01 Dec 2022 10:19:18 GMT</pubDate>
            <description><![CDATA[<p>리액트 쿼리가 왜 필요한지. 다른 많은 상태 관리 라이브러리와는 어떤 다른 점이 있는지 공식 홈페이지의 개요를 읽어보며 알 수 있었습니다. 영어로 되어있는 홈페이지 내용을 번역해 보았는데, 좀 더 이해하기 쉬운 방향으로 해석하느라 모든 단어 하나하나를 해석하지는 않았습니다.</p>
<h2 id="overview">Overview</h2>
<p>리액트 쿼리는 React를 위한 데이터-페칭 라이브러리 정도로 생각되곤 하지만, fetching 뿐 아니라 cashing, 서버 데이터와의 동기화 및 서버 데이터 업데이트까지 수행합니다.</p>
<h3 id="motivation">motivation</h3>
<p>기본적으로 React 어플리케이션은 컴포넌트에서 데이터를 fetching 하거나 updating 하는 방법을 제공하지 않으므로, 개발자는 data와 fetching 하는 방법을 고안해야 합니다. 이 말은 React hook을 사용해서 컴포넌트 베이스의 state와 effect를 결합하거나, 데이터를 저장하고 동기화하는  일반적인 상태 관리 라이브러리를 사용해야 함을 의미합니다. </p>
<p>전통적인 대부분의 상태 관리 라이브러리는 클라이언트 상태에서는 잘 동작하지만, 비동기적이거나 서버 상태에서는 잘 동작하지 않습니다. 서버 상태는 완전히 다르기 때문입니다. </p>
<p>서버 상태는</p>
<ul>
<li>개발자가 컨트롤하거나 소유할 수 없는 곳에서 원격으로 유지됩니다</li>
<li>feching / updating 을 위해 비동기 API가 필요합니다</li>
<li>보통 공동 소유권을 가지고 다른 사람이 당신이 모르는 사이 변경할 수 있습니다</li>
<li>주의하지 않으면 잠재적으로 오래된 상태가 될 수도 있습니다</li>
</ul>
<p>어플리케이션에서 서버 상태의 특성을 알게된다면, 다음과 같은 과제들이 발생할 것입니다.</p>
<ul>
<li>Cashing (아마도 프로그래밍에서 가장 어려운 것)</li>
<li>동일한 데이터에 대한 중복된 요청을 단일 요청으로 변경</li>
<li><code>오래된</code> 데이터를 업데이트</li>
<li>데이터가 언제 <code>오래되는지</code> 파악하는 것</li>
<li>데이터 업데이트를 빠르게 반영</li>
<li>페이지네이션이나 lazy loading 같은 성능 최적화</li>
<li>메모리와 서버 상태 가비지 콜렉션의 관리</li>
<li>구조적 공유를 통한 쿼리 결과 memoizing</li>
</ul>
<p>당신이 이 리스트를 보고 압도당하지 않는다면 당신은 이미 모든 서버 상태 문제를 해결할 수 있다는 것을 의미합니다. 그러나 당신이 평범한 사람이라면 이러한 과제들을 전부, 혹은 대부분 다뤄보지 않았을테고 수박 겉핥기만 해봤을 것입니다.</p>
<p>리액트 쿼리는 서버 상태를 다루는 가장 좋은 라이브러리입니다. 특별한 설정없이도 놀라울 정도로 잘 작동하고, 어플리케이션 성장에 따라 선호에 맞춰 커스텀화할 수도 있습니다. </p>
<p>리액트 쿼리는 서버 상태에 대한 많은 과제들을 해결하고, 어플리케이션의 데이터를 컨트롤하는 것에 도움을 줄 수 있습니다.</p>
<p>더 기술적으로 말하자면 리액트 쿼리는 다음과 같은 역할을 수행합니다.</p>
<ul>
<li>어플리케이션에서 복잡한 많은 코드들을 제거하고, 단 몇줄의 리액트 쿼리 로직으로 대체할 수 있습니다.</li>
<li>새로운 서버 데이터 소스와 연결하는 것에 대한 걱정없이 어플리케이션을 더 유지보수하기 쉽게, 새로운 기능을 추가할 수 있도록 지원합니다.</li>
<li>어플리케이션이 더 빠르고 반응성이 좋게 느껴지도록하여 사용자 경험을 향상시킬 수 있습니다.</li>
<li>대역폭을 절약하고 메모리 성능을 향상시킬 수 있습니다.</li>
</ul>
<h3 id="코드-예시">코드 예시</h3>
<p>아래 예제에서 리액트 쿼리 깃허브 프로젝트 자체에 대한 깃허브 통계를 가져오는 기본적인 형식의 리액트 쿼리 코드를 볼 수 있습니다.</p>
<pre><code class="language-ts">import { QueryClient, QueryClientProvider, useQuery } from &#39;@tanstack/react-query&#39;

const queryClient = new QueryClient()

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Example /&gt;
    &lt;/QueryClientProvider&gt;
  )
}

function Example() {
  const { isLoading, error, data } = useQuery({
    queryKey: [&#39;repoData&#39;],
    queryFn: () =&gt;
      fetch(&#39;https://api.github.com/repos/tannerlinsley/react-query&#39;).then(res =&gt;
        res.json()
      )
  })

  if (isLoading) return &#39;Loading...&#39;

  if (error) return &#39;An error has occurred: &#39; + error.message

  return (
    &lt;div&gt;
      &lt;h1&gt;{data.name}&lt;/h1&gt;
      &lt;p&gt;{data.description}&lt;/p&gt;
      &lt;strong&gt;👀 {data.subscribers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;✨ {data.stargazers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;🍴 {data.forks_count}&lt;/strong&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>위의 내용을 보면 리액트 쿼리는 복잡한 코드들을 더 간단하게 작성할 수 있게 하고, 서버 상태를 직접 컨트롤하는 것에 도움을 준다는 것을 알 수 있습니다. 확실히 리액트 쿼리로 fetching을 했을 때 코드가 더 간결하고 가독성이 높아졌었던 경험이 있습니다. 공식 홈페이지 DOCS에서 다양한 가이드들과 API 레퍼런스들도 잘 설명되어 있는데 다음에는 그 중 중요한 부분들을 골라서 포스팅해보겠습니다.</p>
<blockquote>
<p>참고
<a href="https://tanstack.com/query/v4/docs/overview#motivation">https://tanstack.com/query/v4/docs/overview#motivation</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NEXT.js] next/image 호스트 구성 에러 해결]]></title>
            <link>https://velog.io/@st_hwang/NEXT.js-nextimage-%ED%98%B8%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EC%84%B1-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@st_hwang/NEXT.js-nextimage-%ED%98%B8%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EC%84%B1-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 17 Nov 2022 04:07:42 GMT</pubDate>
            <description><![CDATA[<p>Next.js에서는 이미지를 로드하기 위한 <code>next/imgage</code> 컴포넌트를 지원한다.
<code>next.image</code>는 지연 로딩(lazy loading)을 지원하며 그 밖의 다양한 props를 지원한다.</p>
<p>next.js 13 버젼을 이용해서 프로젝트를 하던 중 카카오 버튼 이미지를 로드하기 위해 카카오 홈페이지의 이미지 주소를 src로 지정했는데 다음과 같은 에러가 났다.
<img src="https://velog.velcdn.com/images/st_hwang/post/5e3c5c00-4022-481a-a31f-1b37163db9a4/image.png" alt=""></p>
<p>next/image는 외부 이미지를 로드하기 위해 src 주소를 서버로 지정하면 <code>next.config.js</code> 파일에 <code>remotePattenrs</code> 프로퍼티를 구성해주어야 하며, 이는 악의적인 사용자로부터 내 어플리케이션을 보호하기 위함이라고 Next.js 공식 홈페이지에 명시되어 있다. </p>
<p>해당 오류는 next.config.js에 다음과 같이 호스트를 구성하여 해결할 수 있다. 이해가 쉽도록 프로젝트에서 직접 쓰고 있는 코드를 가져왔다.</p>
<pre><code class="language-js">/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  compiler: { styledComponents: true },
  images: {
    remotePatterns: [
      {
        protocol: &#39;https&#39;,
        hostname: &#39;developers.kakao.com&#39;,
        port: &#39;&#39;,
        pathname: &#39;/assets/img/about/logos/kakaolink/**&#39;,
      },
    ],
  },
};

module.exports = nextConfig;</code></pre>
<p>Next.js 12.3 이전 버젼은 다음과 같이 해결할 수 있다.</p>
<pre><code class="language-js">const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  compiler: { styledComponents: true },
  images: {
        domain: &#39;developers.kakao.com&#39;,
  },
};

module.exports = nextConfig;</code></pre>
<p>근데 나는 그냥 이미지 직접 다운받아서 로컬에서 가져오는 걸로 에러 해결함ㅎ</p>
<blockquote>
<p>참고 
<a href="https://nextjs.org/docs/messages/next-image-unconfigured-host">https://nextjs.org/docs/messages/next-image-unconfigured-host</a>
<a href="https://nextjs.org/docs/api-reference/next/image">https://nextjs.org/docs/api-reference/next/image</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[clean code] 05 - 08장 내용정리]]></title>
            <link>https://velog.io/@st_hwang/clean-code-05-08%EC%9E%A5-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@st_hwang/clean-code-05-08%EC%9E%A5-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 20 Oct 2022 16:18:04 GMT</pubDate>
            <description><![CDATA[<h1 id="5-형식-맞추기">5. 형식 맞추기</h1>
<blockquote>
<p>프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 한다.</p>
</blockquote>
<h3 id="형식을-맞추는-목적">형식을 맞추는 목적</h3>
<p>오늘 구현한 코드의 가독성은 앞으로 바뀔 코드의 품질에 지대한 영향을 미침.</p>
<p>맨 처음 잡아놓은 구현 스타일과 가독성 수준은 유지보수 용이성과 확장성에 계속 영향을 미침.</p>
<h3 id="적절한-행-길이를-유지하라">적절한 행 길이를 유지하라</h3>
<p>일반적으로 큰 파일보다 작은 파일이 이해하기 쉬움</p>
<h5 id="신문-기사처럼-작성하라">신문 기사처럼 작성하라</h5>
<p>이름은 간단하면서도 설명이 가능하게</p>
<p>소스 파일 첫 부분은 고채원 개념과 알고리즘을 설명</p>
<p>아래로 내려갈수록 의도를 세세하게 묘사.</p>
<p>마지막에는 가장 저차원 함수와 세부내열</p>
<h5 id="개념은-빈-행으로-분리하라">개념은 빈 행으로 분리하라</h5>
<p>각 행은 수식이나 절을 나타내고, 일련의 행 묶음은 완결된 생각 하나를 표현. 생각 사이에는 빈 행을 넣어 분리해야 좋음</p>
<p>빈 행을 빼버리면 코드 가독성이 현저하게 떨어짐</p>
<h5 id="세로-밀집도">세로 밀집도</h5>
<p>줄꿈이 개념을 분리한다면 세로 밀집도는 연관성을 의미. 즉, 서로 밀접한 코드 행은 세로로 가까이 놓아야 함</p>
<h5 id="수직-거리">수직 거리</h5>
<p>서로 밀접한 개념은 세로로 가까이 둬야 한다. protected 변수를 피해야 하는 이유 중 하나</p>
<p>같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현 (연관성이란 한 개념을 이해하는 데 다른 개념이 중요한 정도)</p>
<p><strong><code>변수</code></strong>는 사용하는 위치에 최대한 가까이 선언. 루프를 제어하는 변수는 흔히 루프 문 내부에 선언</p>
<p><strong><code>인스턴스 변수</code></strong>는 클래스 맨 처음에 선언. 잘 설계한 클래스는 대다수 클래스 메서드가 인스턴스 변수를 사용하기 때문 (자바에서는 클래스 맨 처음, C++에서는 클래스 마지막)</p>
<p><strong><code>종속 함수</code></strong> 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치. 가능한 호출하는 함수를 호출되는 함수보다 먼저 배치 (소스 코드 모듈이 고차원에서 저차원으로 내려감)</p>
<p><strong><code>개념적 유사성</code></strong> 친화도가 높을수록 코드를 가까이 배치 (한 함수가 다른 함수를 호출하는 직접적인 종속성. 변수와 변수를 사용하는 함수. 비슷한 동작을 수행하는 일군의 함수)</p>
<h3 id="가로-형식-맞추기">가로 형식 맞추기</h3>
<p>짧은 행이 바람직. 저자는 120자 정도로 행 길이를 제한</p>
<h5 id="가로-공백과-밀집도">가로 공백과 밀집도</h5>
<p>공백을 사용해 밀접한 개념과 느슨한 개념을 표현</p>
<pre><code class="language-java">private measureLine(String line) {
    lineCount++;
  int lineSize = line.length();
  totalChars += lineSize;
  lineWidthHistorgram.addLine(lineSize, lineCount);
  recordWidestLine(lineSize);
}</code></pre>
<p>할당 연산자를 강조하기 위해 앞뒤로 공백. 공백을 넣으면 두 가지 주요 요소가 확실히 나뉘어짐</p>
<p>함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않음. 함수와 인수가 밀접하기 때문. 공백을 넣으면 한 개념이 아니라 별개로 보임</p>
<p>괄호 안 인수는 공백으로 분리.</p>
<h5 id="가로-정렬">가로 정렬</h5>
<p>유용하지 않음. 선언문과 할당문을 별도로 정렬하지 않으면 오히려 결함을 찾기 쉬움</p>
<h5 id="들여쓰기">들여쓰기</h5>
<p>범위(scope)로 이루어진 계층을 표현하기 위해 코드를 들여씀</p>
<h5 id="가짜-범위">가짜 범위</h5>
<p>빈 while 문이나 for 문을 피하지 못할 때는 빈 블록을 올바로 들여쓰고 괄호로 감싼다</p>
<pre><code class="language-java">while (dis.read(buf, 0, readBufferSize) != -1)
;</code></pre>
<h3 id="팀-규칙">팀 규칙</h3>
<p>팀은 한 가지 규칙에 합의해야 하고, 모든 팀원은 그 규칙에 따라야 함 </p>
<h1 id="6-객체와-자료-구조">6. 객체와 자료 구조</h1>
<p>변수를 private로 정의하는 이유는 남들이 변수 의존성을 줄이기 위해서 </p>
<p>그렇다면 get 함수와 set 함수를 public으로 해서 변수를 외부에 노출하는 이유는?</p>
<h3 id="자료-추상화">자료 추상화</h3>
<p>자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋음. 인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않음</p>
<p>개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 고민해야함. 아무 생각 없이 조회/설정 함수를 추가하는 방법은 나쁘다</p>
<h3 id="자료객체-비대칭">자료/객체 비대칭</h3>
<ul>
<li><p><code>객체</code>는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.</p>
</li>
<li><p><code>자료 구조</code>는 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다.</p>
</li>
</ul>
<p><code>자료 구조를 사용하는 절차적 코드</code>는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽고, <code>객체 지향 코드</code>는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉬움</p>
<p>반면, <code>절차적 코드</code>는 새로운 자료 구조를 추가하려면 모든 함수를 고쳐야 하고, <code>객체 지향 코드</code>는 새로운 함수를 추가하려면 모든 클래스를 고쳐야 함</p>
<p>때로는 클래스와 객체 지향 기법이 가장 적합하고, 때로는 단순한 자료 구조와 절차적인 코드가 적합한 상황도 있음</p>
<blockquote>
<p>자료 구조 (Data structure) : 메모리에 저장된 변수 값들의 모음. </p>
</blockquote>
<h3 id="디미터-법칙">디미터 법칙</h3>
<blockquote>
<p> 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다 (<code>정보 은닉</code> 강조)</p>
</blockquote>
<p>객체는 조회 함수로 내부 구조를 공개하면 안된다는 의미</p>
<p>클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다고 주장</p>
<ul>
<li>클래스 C</li>
<li>f가 생성한 객체</li>
<li>f 인수로 넘어온 객체</li>
<li>C 인스턴스 변수에 저장된 객체</li>
</ul>
<h5 id="기차-충돌-train-wreck">기차 충돌 (train wreck)</h5>
<pre><code class="language-java">final String putputDir = ctxt.getOptions().getSrcatchDir().getAbsolutePath();</code></pre>
<p>위와 같은 코드는 여러 객차가 한 줄로 이어진 기차처럼 보이기 때문에 <code>기차 충돌</code>이라고 부름. 조잡하다 여겨지는 방식이므로 피하는 것이 좋다</p>
<pre><code class="language-java">Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();</code></pre>
<p>위 코드가 디미터 법칙을 위반하는가의 여부는 ctxt, Options, ScratchDir이 객체인지 자료 구조인지에 따라 다름</p>
<p>객체라면 내부 구조를 숨겨야 하므로 디미터 법칙을 위반하고, 자료 구조라면 내부 구조를 노출하므로 디미터 법칙이 적용되지 않음</p>
<p>위 코드는 조회 함수를 사용해서 혼란을 일으킨다. 코드를 다음과 같이 구현했다면 디미터 법칙을 구현할 필요가 없음</p>
<pre><code class="language-java">final String outputDir = ctxt.options.scratchDir.absolutePate;</code></pre>
<p>자료 구조는 무조건 함수 없이 공개 변수만 포함하고, 객체는 비공개 변수와 공개함수를 포함하는 것이 좋음</p>
<h5 id="잡종-구조">잡종 구조</h5>
<p>때떄로 절반은 객체, 절반은 자료 구조인 잡종 구조가 나오는데, 이런 잡종 구조는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어려움</p>
<p>잡종 구조는 되도록 피하는 편이 좋다.</p>
<h5 id="구조체-감추기">구조체 감추기</h5>
<p>ctxt, options, scratchDir이 객체라면 앞선 코드 예제처럼 체이닝을 하면 안됨. 객체는 내부 구조를 감춰야 한다.</p>
<h5 id="자료-전달-객체">자료 전달 객체</h5>
<p>자료 구조체의 전형적인 형태는 <strong>공개 변수만 있고 함수가 없는 클래스</strong>. <code>자료 전달 객체(Data Transfer Object, DTO)</code></p>
<p>데이터베이스에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체</p>
<h5 id="활성-레코드">활성 레코드</h5>
<p>DTO의 특수한 형태로 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조지만, 대개 save나 find와 같은 탐색 함수도 제공</p>
<p>활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한 결과</p>
<p>활성 레코드는 자료 구조로 취급하고, 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성하는 것이 바람직</p>
<h3 id="자바스크립트에서의-적용">자바스크립트에서의 적용</h3>
<h4 id="getter--setter-사용">getter / setter 사용</h4>
<p>getter, setter를 사용하여 객체의 데이터에 접근하는 것이 객체의 속성을 찾는 것보다 훨씬 나음</p>
<ul>
<li>객체의 속성을 얻는 것 이상 많은 것을 하고 싶을 때, 코드에서 모든 접근자를 찾아 바꾸고 할 필요가 없음</li>
<li>set할 때 검증 로직을 추가하는 것이 코드를 더 간단하게 만듦</li>
<li>내부용 API를 캡슐화할 수 있음</li>
<li>getting / setting 할 때 로그를 찾거나 에러처리 하기 쉬움</li>
<li>서버에서 객체 속성을 받아올 때 lazy load 할 수 있음</li>
</ul>
<h5 id="안-좋은-예">안 좋은 예</h5>
<pre><code class="language-javascript">function makeBankAccount() {
  // ...

  return {
    // ...
    balance: 0
  };
}

const account = makeBankAccount();
account.balance = 100;</code></pre>
<h5 id="좋은-예">좋은 예</h5>
<pre><code class="language-javascript">function makeBankAccount() {
  // private으로 선언된 변수
  let balance = 0;

  // 아래 return을 통해 public으로 선언된 &quot;getter&quot;
  function getBalance() {
    return balance;
  }

  // 아래 return을 통해 public으로 선언된 &quot;setter&quot;
  function setBalance(amount) {
    // ... balance를 업데이트하기 전 검증로직
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance
  };
}

const account = makeBankAccount();
account.setBalance(100);</code></pre>
<h4 id="객체에-비공개-멤버를-만들어라">객체에 비공개 멤버를 만들어라</h4>
<p><code>클로져</code>를 이용하면 가능</p>
<h5 id="안좋은-예">안좋은 예</h5>
<pre><code class="language-javascript">const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error(&quot;Instantiate Animal with `new`&quot;);
  }

  this.age = age;
};

Animal.prototype.move = function() {};

const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error(&quot;Instantiate Mammal with `new`&quot;);
  }

  Animal.call(this, age);
  this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error(&quot;Instantiate Human with `new`&quot;);
  }

  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};</code></pre>
<h5 id="좋은-예-1">좋은 예</h5>
<pre><code class="language-javascript">class Animal {
  constructor(age) {
    this.age = age;
  }

  move() { /* ... */ }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }

  liveBirth() { /* ... */ }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }

  speak() { /* ... */ }
}</code></pre>
<h1 id="7-오류-처리">7. 오류 처리</h1>
<p>오류 처리는 프로그램에 반드시 필요한 요소 중 하나.</p>
<p>깨끗한 코드는 읽기도 좋아야 하지만 안정성도 높아야 한다.</p>
<p><strong>오류 처리를 프로그램 논리와 분리</strong>하면 독립적인 추론이 가능해지며 코드 유지보수성도 크게 높아진다.</p>
<h3 id="오류-코드보다-예외를-사용하라">오류 코드보다 예외를 사용하라</h3>
<p>오류가 발생하면 예외를 던지는 편이 낫다. 그러면 호출자 코드가 더 깔끔해진다.</p>
<p>논리가 오류 처리 코드와 뒤섞이지 않아서 보기에도 좋고 개념도 독립적이게 된다.</p>
<pre><code class="language-java">public class DeviseController {
  ...
  public void sendShutDown(){
    try {
      tryToShutDown();
    } catch (DeviceShutDownError e){
      logger.log(e);
    }
  }

  private void tryToShutDown() throws DeviceShutDownError {
    DeviceHandle handle = getHandle(DEV1);
    DeviceHandle record = retrieveDeviceRecord(handle);

    pauseDevice(handle);
    clearDeviceWorkQueue(handle);
    closeDevice(handle);
  }

  private DeviceHandle getHandle(DeviceId id){
    ...
  }
  ...
}</code></pre>
<h3 id="try-catch-finally-문부터-작성하라">try-catch-finally 문부터 작성하라</h3>
<p>try 블록은 트랜잭션과 비슷하다. try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다.</p>
<p>예외가 발생할 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 낫다. 그러면 try 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬워진다.</p>
<p>강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장. 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉬워진다.</p>
<h3 id="미확인-unchecked-예외를-사용하라">미확인 (unchecked) 예외를 사용하라</h3>
<p>확인된 예외는 OCP (Open Closed Pinciple) 을 위반한다. 메서드에서 확인된 예외를 던졌는데 catch 블록이 위에 있다면 그 사이 메서드 모두 선언부에 해당 예외를 정의해야 한다.</p>
<p>즉, 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다는 말. 캡슐화가 깨진다</p>
<h3 id="예외에-의미를-제공하라">예외에 의미를 제공하라</h3>
<p>예외를 던질 때는 전후 상황을 충분히 덧붙인다. 그러면 오류가 발생한 원인과 위치를 찾기가 쉬워진다. 오류 메세지에 정보를 담아 예외와 함께 던진다</p>
<h3 id="호출자를-고려해-예외-클래스를-정의하라">호출자를 고려해 예외 클래스를 정의하라</h3>
<p>애플리케이션에서 오류를 정의할 때 가장 중요한 것은 오류를 잡아내는 방법</p>
<p>외부 API를 사용할 때는 감싸기 기법이 최선. </p>
<p>외부 API를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어들고 나중에 갈아타는 비용도 적다. </p>
<p>감싸기 클래스에서 외부 API를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기도 쉬워짐</p>
<p>마지막 장점으로 특정 업체가 API를 설계한 방식에 발목을 잡히지 않음. 프로그램이 사용하기 편리한 API를 정의하면 됨</p>
<h3 id="정상흐름을-정의하라">정상흐름을 정의하라</h3>
<p>특수 사례 패턴 : 클래스를 만들거나 객체를 조작해 특수 사례를 처리. </p>
<p>클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다. 클래스나 객체가 예외적인 상황을 캡슐화해서 처리하게 됨</p>
<h3 id="null을-반환하지-마라">null을 반환하지 마라</h3>
<p>null을 반환하는 습관은 오류를 유발하는 행위. null을 반환하면 일거리를 늘릴 뿐만 아니라 호출자에게 문제를 떠넘긴다.</p>
<p>null을 반환하는 것보다는 빈 컬렉션을 반환하는게 낫다</p>
<h3 id="null을-전달하지-마라">null을 전달하지 마라</h3>
<p>메서드에서 null을 반환하는 방식도 나쁘지만, 메서드로 null을 전달하는 방식은 더 나쁨. </p>
<p>정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다</p>
<h3 id="자바스크립트에서의-적용-에러-처리">자바스크립트에서의 적용 (에러 처리)</h3>
<h4 id="단순히-에러를-확인만-하지마라">단순히 에러를 확인만 하지마라</h4>
<p>try/catch 로 어떤 코드를 감쌌다면 그에 대한 계획이 있거나 어떠한 장치를 해야 한다.</p>
<h5 id="안-좋은-예-1">안 좋은 예</h5>
<pre><code class="language-javascript">try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}</code></pre>
<h5 id="좋은-예-2">좋은 예</h5>
<pre><code class="language-javascript">try {
  functionThatMightThrow();
} catch (error) {
  // 첫번째 방법은 console.error를 이용하는 것입니다. 이건 console.log보다 조금 더 알아채기 쉽습니다.
  console.error(error);
  // 다른 방법은 유저에게 알리는 방법입니다.
  notifyUserOfError(error);
  // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법입니다.
  reportErrorToService(error);
  // 혹은 그 어떤 방법이 될 수 있습니다.
}</code></pre>
<h4 id="프로미스가-실패된-것을-무시하지-마라">프로미스가 실패된 것을 무시하지 마라</h4>
<h5 id="안-좋은-예-2">안 좋은 예</h5>
<pre><code class="language-javascript">getdata()
.then(data =&gt; {
  functionThatMightThrow(data);
})
.catch(error =&gt; {
  console.log(error);
});</code></pre>
<h5 id="좋은-예-3">좋은 예</h5>
<pre><code class="language-javascript">getdata()
.then(data =&gt; {
  functionThatMightThrow(data);
})
.catch(error =&gt; {
  // 첫번째 방법은 console.error를 이용하는 것입니다. 이건 console.log보다 조금 더 알아채기 쉽습니다.
  console.error(error);
  // 다른 방법은 유저에게 알리는 방법입니다.
  notifyUserOfError(error);
  // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법입니다.
  reportErrorToService(error);
  // 혹은 그 어떤 방법이 될 수 있습니다.
});</code></pre>
<h1 id="8-경계">8. 경계</h1>
<p>시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다.</p>
<p>외부 코드를 우리 코드에 깔끔하게 통합해야만 한다.</p>
<h3 id="외부-코드-사용하기">외부 코드 사용하기</h3>
<p>인터페이스 제공자는 적용성을 최대한 넓히려 하고, 인터페이스 사용자는 자신의 요구에 집중하는 인터페이스를 바란다.</p>
<pre><code class="language-java">public class Sensors {
  private Map sensors = new HashMap();

  public Sensor getById(String id){
    return (Sensor) sensors.get(id);
  }
}</code></pre>
<p>경계 인터페이스인 Map을 sensors 안으로 숨기면 Map 인터페이스가 변하더라도 나머지 프로그램에 영향을 미치지 않는다. </p>
<p>Map과 같은 <strong>경계 인터페이스를 사용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다.</strong></p>
<h3 id="경계-살피고-익히기">경계 살피고 익히기</h3>
<p>학습 테스트 : 우리쪽 코드를 작성해 외부 코드를 호출하는 대신 먼저 간단한 테스트 케이스를 작성해 외부 코드를 익히는 방법</p>
<h3 id="학습-테스트는-공짜-이상이다">학습 테스트는 공짜 이상이다</h3>
<p>학습 테스트는 필요한 지식만 확보하는 손쉬운 방법. 패키지 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인</p>
<h3 id="아직-존재하지-않는-코드를-사용하기">아직 존재하지 않는 코드를 사용하기</h3>
<p>우리가 바라는 인터페이스를 구현하면 우리가 인터페이스를 전적으로 통제한다는 장점이 생긴다. 코드 가독성이 높아지고 코드 의도도 분명해진다.</p>
<p>우리가 통제하지 못하고 정의하지도 않은 코드(기능)은 API에서 분리하고 필요한 부분만 자체적으로 인터페이스를 정의한다.</p>
<p>나중에 다른 팀이 API를 정의하면 거기에 맞춰 구현하면 된다. </p>
<p>이러한 방식의 설계는 테스트도 편하다.</p>
<h3 id="깨끗한-경계">깨끗한 경계</h3>
<p>경계에 위치하는 코드는 깔끔하게 분리하고 기대치를 정의하는 테스트 케이스도 작성한다. 이쪽 코드에서 외부 패키지를 세세하게 알 필요는 없다.</p>
<p>통제가 불가능한 외부 패키지에 의존하는 것보다 통제가 가능한 우리 코드에 의존하는 편이 좋다.</p>
<p>외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자.</p>
<p>새로운 클래스로 경계를 감싸거나 아니면 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자.</p>
<h4 id="참고--httpsgithubcomryanmcdermottclean-code-javascript">참고 : <a href="https://github.com/ryanmcdermott/clean-code-javascript">https://github.com/ryanmcdermott/clean-code-javascript</a></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[[clean code]  01 - 04장 내용 정리]]></title>
            <link>https://velog.io/@st_hwang/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-01-04%EC%9E%A5-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@st_hwang/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-01-04%EC%9E%A5-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 20 Oct 2022 16:10:11 GMT</pubDate>
            <description><![CDATA[<h1 id="1-깨끗한-코드">1. 깨끗한 코드</h1>
<h3 id="나쁜-코드">나쁜 코드</h3>
<p>바쁘다는 핑계로 코드를 마구 짰던 경험들이 있고, 나중에 손보겠다고 생각하지만 그 나중은 결코 오지 않는다.</p>
<p>나쁜 코드는 개발 속도를 크게 떨어트리며 팀 생산성을 떨어트린다. 이에 재설계를 시작하지만 재설계는 시간과 돈이 많이 드는 작업.</p>
<p>언제나 코드를 최대한 깨끗하게 유지하는 습관이 오히려 코드 짜는 기한을 맞출 수 있게하고, 비용을 절감할 수 있음.</p>
<h3 id="깨끗한-코드란">깨끗한 코드란?</h3>
<blockquote>
<p>보기에 즐거운 코드</p>
<p>세세한 사항까지 꼼꼼하게 처리하는 코드</p>
<p>한 가지를 잘하는 코드</p>
</blockquote>
<blockquote>
<p>가독성이 좋은 코드</p>
<p>반드시 필요한 내용만 담는 코드</p>
</blockquote>
<blockquote>
<p>다른 사람이 고치기 쉬운 코드 - 단위 테스트 케이스 / 인수 테스트 케이스</p>
<p>인간이 읽기 좋은 코드</p>
</blockquote>
<blockquote>
<p>주의 깊게 작성한 코드</p>
</blockquote>
<blockquote>
<p>중복을 피하기</p>
<p>한 가지 기능만 수행하기</p>
<p>제대로 표현하기</p>
<p>작게 추상화하기</p>
</blockquote>
<blockquote>
<p>짐작하는 기능을 그대로 수행하는 코드</p>
</blockquote>
<p><strong>저자의 생각 - 읽기 쉬운 코드가 중요함 (가독성)</strong></p>
<h3 id="🔥-보이스카우트-규칙">🔥 보이스카우트 규칙</h3>
<blockquote>
<p>캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.</p>
</blockquote>
<p>한꺼번에 많은 시간과 노력을 투자해 코드를 정리할 필요는 없음. 변수 이름 하나를 개선하고, 조금 긴 함수 하나를 분할하고, 약간의 중복을 제거하고, 복잡한 if문 하나를 정리하면 충분</p>
<h1 id="2-의미-있는-이름">2. 의미 있는 이름</h1>
<h2 id="--이름을-잘-짓는-규칙들">- 이름을 잘 짓는 규칙들</h2>
<h3 id="의도를-분명히-밝혀라">의도를 분명히 밝혀라</h3>
<p>의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워짐</p>
<h3 id="그릇된-정보를-피하라">그릇된 정보를 피하라</h3>
<p>널리 쓰이는 의미가 있는 단어를 다른 의미로 사용하면 안됨 (ex. hp, aix, sco, List 등)</p>
<p>서로 흡사한 이름을 사용하지 않도록 주의</p>
<p>소문자 L, 대문자 O 등 헷갈릴 수 있는 문자 주의</p>
<h3 id="의미-있게-구분하라">의미 있게 구분하라</h3>
<p>연속된 숫자를 덧붙이거나 불용어(noise word)를 추가하는 방식을 피하라 (ex. ProductInfo - ProductData)</p>
<h3 id="발음하기-쉬운-이름을-사용하라">발음하기 쉬운 이름을 사용하라</h3>
<h3 id="검색하기-쉬운-이름을-사용하라">검색하기 쉬운 이름을 사용하라</h3>
<p>문자 하나를 사용하는 이름과 상수 주의</p>
<p>이름 길이는 범위 크기에 비례해야 함 (여러 곳에서 사용할수록 검색하기 쉬운 명확한 이름이어야 함)</p>
<h3 id="인코딩을-피하라">인코딩을 피하라</h3>
<p>이름에 타입을 넣지 말기</p>
<p>접두어를 무분별하게 사용하지 않기</p>
<h3 id="자신의-기억력을-자랑하지-마라">자신의 기억력을 자랑하지 마라</h3>
<p>명료함이 최고. 자신의 기억력을 믿고 문자 하나만 사용하는 변수를 만든다거나 하지 말기</p>
<h3 id="클래스-이름---명사--명사구">클래스 이름 - 명사 / 명사구</h3>
<h3 id="메서드-이름---동사--동사구">메서드 이름 - 동사 / 동사구</h3>
<p>접근자, 변경자, 조건자는 값 앞에 get, set, is 를 붙임</p>
<h3 id="기발한-이름은-피하라">기발한 이름은 피하라</h3>
<h3 id="한-개념에-한-단어를-사용하라">한 개념에 한 단어를 사용하라</h3>
<p>일관성 있는 어휘를 사용 (똑같은 메서드인데 클래스마다 fetch, retrieve, get 등으로 제각각 부르면 혼란스러움)</p>
<h3 id="말장난을-하지-마라">말장난을 하지 마라</h3>
<h3 id="해법-영역에서-가져온-이름을-사용하라">해법 영역에서 가져온 이름을 사용하라</h3>
<p>기술 개념에는 기술 이름이 가장 적합한 선택</p>
<h3 id="문제-영역에서-가져온-이름을-사용하라">문제 영역에서 가져온 이름을 사용하라</h3>
<p>적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져옴</p>
<h3 id="의미-있는-맥락을-추가하라">의미 있는 맥락을 추가하라</h3>
<h3 id="불필요한-맥락을-없애라">불필요한 맥락을 없애라</h3>
<h1 id="3-함수">3. 함수</h1>
<h3 id="작게-만들어라">작게 만들어라!</h3>
<p>함수를 만드는 첫째 규칙은 &#39;작게!&#39;</p>
<p>if / else / while 문 등에 들어가는 블록은 한 줄이어야 함. 대게 거기서 함수를 호출. 블록 안에서 호출하는 함수 이름이 적절하면 코드 이해하기도 쉬움.</p>
<p>중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻</p>
<h3 id="한-가지만-해라">한 가지만 해라!</h3>
<blockquote>
<p>함수는 한 가지를 해야 한다. 그 한가지를 잘해야 한다. 그 한 가지만을 해야 한다.</p>
</blockquote>
<h3 id="함수-당-추상화-수준은-하나로">함수 당 추상화 수준은 하나로!</h3>
<p>함수가 확실히 한 가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 함</p>
<p>핵심은 짧으면서도 한 가지만 하는 함수</p>
<h5 id="위에서-아래로-코드-읽기-내려가기-규칙">위에서 아래로 코드 읽기: 내려가기 규칙</h5>
<p>한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 옴. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아지는 것</p>
<blockquote>
<p>추상화 수준 : 해당 코드를 읽으면서 파악할 수 있는 정보의 수준. 더 자세한 정보를 알 수 있으면 추상화 수준이 낮아진다.</p>
</blockquote>
<h3 id="switch-문">Switch 문</h3>
<p>switch 문은 작게 만들기 어렵고, 한 가지 작업만 하는 switch 문도 만들기 어려움.</p>
<p>각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법이 있음 (다형성 이용)</p>
<h3 id="서술적인-이름을-사용하라">서술적인 이름을 사용하라!</h3>
<p>함수가 하는 일을 좀 더 잘 표현. (testableHtml -&gt; SetupTeardownIncluder.render)</p>
<p>이름을 붙일 때는 일관성이 있어야 함. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사 사용 (includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage, includeSetupPage)</p>
<h3 id="함수-인수">함수 인수</h3>
<p>이상적인 인수 개수는 0개(무항), 다음은 1개(단항), 다음은 2개(이항), 3개(삼항)은 가능한 피하는 편이 좋고 4개(다항) 이상은 정말 특별한 경우 아니면 사용 안하는게 좋음</p>
<p>인수는 개념을 이해하기 어렵게 만듦</p>
<h5 id="많이-쓰는-단항-형식">많이 쓰는 단항 형식</h5>
<p>함수에 인수 1개를 넘기는 경우 중 하나는 인수에 질문을 던지는 경우 <code>boolean fileExists(&quot;MyFile&quot;)</code> - 스트링 객체를 처리하고 boolean 값을 반환</p>
<p>다른 하나는 인수를 뭔가로 변환해 결과를 반환하는 경우 <code>InputStream fileOpen(&quot;MyFile&quot;)</code> - String 형의 파일 이름을 InputStream 으로 변환해서 반환</p>
<p>다소 드물게 사용하는 형식은 이벤트 함수. 이벤트 함수는 입력 인수만 있고, 함수 호출을 이벤트로 해석해 입력 인수로 시스템 상태를 바꿈</p>
<p>플래그 인수 : 피해라!</p>
<h5 id="이항-함수">이항 함수</h5>
<p>단항 함수보다 이해하기 어렵고 위험함. 단항 함수로 바꿀 수 있으면 바꿔라</p>
<h5 id="삼항-함수">삼항 함수</h5>
<p>만들 때 신중히 고려해라.</p>
<p>가변 인수를 취하는 함수는 단항, 이항, 삼항 함수로 취급할 수 있음</p>
<h3 id="부수-효과를-일으키지-마라">부수 효과를 일으키지 마라!</h3>
<p>부수 효과는 시간적인 결합을 초래하고, 시간적인 결합은 혼란을 일으킴</p>
<h5 id="출력-인수">출력 인수</h5>
<p>일반적으로 출력 인수는 피해야 함. 출력 인수로 사용하라고 설계한 변수가 this</p>
<h3 id="명령과-조회를-분리하라">명령과 조회를 분리하라!</h3>
<p>함수는 객체 상태를 변경하거나 객체 정보를 반환하거나 둘 중 하나. 둘 다 하면 혼란</p>
<h3 id="오류-코드보다-예외를-사용하라">오류 코드보다 예외를 사용하라!</h3>
<p>오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되서 코드가 깔끔해짐</p>
<h5 id="try--catch-블록-뽑아내기">try / catch 블록 뽑아내기</h5>
<p>try/catch 블록은 정상 동작과 오류 처리 동작을 뒤섞으므로 별도 함수로 뽑아내는 편이 좋음</p>
<h5 id="오류-처리도-한-가지-작업이다">오류 처리도 한 가지 작업이다.</h5>
<p>오류를 처리하는 함수는 오류만 처리해야 마땅함. 함수에 키워드 try가 있다면 함수는 try 문으로 시작해 catch/finally 문으로 끝나야 함</p>
<h3 id="반복하지-마라">반복하지 마라!</h3>
<p>중복은 소프트웨어에서 만악의 근원임</p>
<h3 id="함수를-어떻게-짜죠">함수를 어떻게 짜죠?</h3>
<p>처음부터 완벽하게 짜려고 하지말고, 계속 수정하고 보완해나가면서 리팩토링</p>
<h1 id="4-주석">4. 주석</h1>
<p>저자는 주석을 줄이고 코드를 더 명확히 해야한다고 말하고 있음</p>
<h3 id="주석은-나쁜-코드를-보완하지-못한다">주석은 나쁜 코드를 보완하지 못한다</h3>
<p>표현력이 풍부하고 깔끔하며 주석이 거의 없는 코드가, 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋음</p>
<h3 id="코드로-의도를-표현하라">코드로 의도를 표현하라</h3>
<h3 id="좋은-주석">좋은 주석</h3>
<ul>
<li><p>법적인 주석 - 저작권 정보 / 소유권 정보 등</p>
</li>
<li><p>정보를 제공하는 주석</p>
</li>
<li><p>의도를 설명하는 주석 - 저자의 의도가 드러나는 주석</p>
</li>
<li><p>의미를 명료하게 밝히는 주석</p>
</li>
<li><p>결과를 경고하는 주석</p>
</li>
<li><p>TODO 주석 - 앞으로 할 일</p>
</li>
<li><p>중요성을 강조하는 주석</p>
</li>
</ul>
<h3 id="나쁜-주석---대다수의-주석이-해당">나쁜 주석 - 대다수의 주석이 해당</h3>
<ul>
<li>주절거리는 주석 - 특별한 이유 없이 의무감으로 마지못해 다는 주석</li>
<li>같은 이야기를 중복하는 주석</li>
<li>오해할 여지가 있는 주석</li>
<li>의무적으로 다는 주석</li>
<li>이력을 기록하는 주석</li>
<li>있으나 마나 한 주석</li>
<li>함수나 변수로 표현할 수 있다면 주석을 달지 마라</li>
<li>닫는 괄호에 다는 주석</li>
<li>공로를 돌리거나 저자를 표시하는 주석</li>
<li>주석으로 처리한 코드</li>
<li>HTML 주석</li>
<li>전역 정보</li>
<li>너무 많은 정보</li>
<li>모호한 관계 - 주석과 주석이 설명하는 관계가 명백하지 않은 주석</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React-Native] 파이어베이스 ios 연동 CocoaPod 오류 해결]]></title>
            <link>https://velog.io/@st_hwang/React-Native-%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4-ios-%EC%97%B0%EB%8F%99-CocoaPod-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@st_hwang/React-Native-%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4-ios-%EC%97%B0%EB%8F%99-CocoaPod-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 18 Oct 2022 19:23:09 GMT</pubDate>
            <description><![CDATA[<p>파이어베이스 ios 연동 시에 CocoaPod 업데이트 에러 해결</p>
<p>ios 등록 후 <code>pod install --repo-update</code> 시 에러 발생</p>
<pre><code>[!] The following Swift pods cannot yet be integrated as static libraries:

The Swift pod `FirebaseCoreInternal` depends upon `GoogleUtilities`, which does not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries), you may set `use_modular_headers!` globally in your Podfile, or specify `:modular_headers =&gt; true` for particular dependencies.

The Swift pod `FirebaseStorage` depends upon `FirebaseStorageInternal`, `FirebaseAppCheckInterop`, `FirebaseAuthInterop`, `FirebaseCore`, and `FirebaseCoreExtension`, which do not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries), you may set `use_modular_headers!` globally in your Podfile, or specify `:modular_headers =&gt; true` for particular dependencies.
Couldn&#39;t install Pods. Updating the Pods project and trying again...
Command `pod install` failed.
└─ Cause: The following Swift pods cannot yet be integrated as static libraries:

The Swift pod `FirebaseCoreInternal` depends upon `GoogleUtilities`, which does not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries), you may set `use_modular_headers!` globally in your Podfile, or specify `:modular_headers =&gt; true` for particular dependencies.

The Swift pod `FirebaseStorage` depends upon `FirebaseStorageInternal`, `FirebaseAppCheckInterop`, `FirebaseAuthInterop`, `FirebaseCore`, and `FirebaseCoreExtension`, which do not define modules. To opt into those targets generating module maps (which is necessary to import them from Swift when building as static libraries), you may set `use_modular_headers!` globally in your Podfile, or specify `:modular_headers =&gt; true` for particular dependencies.</code></pre><p>해석하자면 Swift pods 를 정적 라이브러리로 통합할 수 없어서 발생하는 에러</p>
<p><code>ios&gt;Podfile</code> 파일에서 </p>
<p><code>use_frameworks! :linkage =&gt; podfile_properties[&#39;ios.useFrameworks&#39;].to_sym if podfile_properties[&#39;ios.useFrameworks&#39;]</code> 코드를</p>
<p><code>use_frameworks! :linkage =&gt; :static</code> 로 변경하여 해결</p>
<p>참고 
<a href="https://rnfirebase.io/">https://rnfirebase.io/</a>
<a href="https://github.com/lottie-react-native/lottie-react-native/issues/784#issuecomment-890010383">https://github.com/lottie-react-native/lottie-react-native/issues/784#issuecomment-890010383</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSR / SSR with Next.js]]></title>
            <link>https://velog.io/@st_hwang/CSR-SSR-with-Next.js</link>
            <guid>https://velog.io/@st_hwang/CSR-SSR-with-Next.js</guid>
            <pubDate>Tue, 27 Sep 2022 16:12:50 GMT</pubDate>
            <description><![CDATA[<p>CSR(Client-side Rendering)과 SSR(Server-side Rendering)의 차이를 명확히 알려면,
우선 SPA(Single page Application)의 개념을 이해하고 넘어가는게 좋을 것 같다.</p>
<h2 id="spa란--mpa--multi-page-application">SPA란? (&lt;=&gt; MPA : Multi page application)</h2>
<p>용어 그대로 <strong>하나의 페이지를 가진 어플리케이션</strong>이다. 초기 웹에서는 여러 개의 HTML 문서들이 서버에 있으면, 페이지가 바뀔 때마다 HTML 문서들을 받아와서 유저에게 보여주는 Static Sites 구조였다. 이는 페이지가 바뀜에 따라 전체 문서가 재렌더링되는 다소 비효율적인 방식이었다. 
이러한 전통적인 방식은 fetch와 Ajax가 등장한 이후 전체 문서가 아니라 필요한 데이터만 동적으로 받아올 수 있는 SPA 방식으로 점차 변화하였다. 오늘날 많은 사람들이 사용하는 리액트, 뷰, 스벨트 등 다양한 프레임워크들이 이러한 SPA 방식을 따라 사용되고 있다. SPA은 기본적으로 CSR 방식으로 사용되었는데, 필요에 따라 SSR 방식으로도 사용될 수도 있다.</p>
<h2 id="1-csrclient-side-rendering의-장단점">1. CSR(Client-side Rendering)의 장단점</h2>
<p>CSR은 client-side의 자바스크립트가 모든 UI를 만드는 것을 의미한다. 
브라우저가 처음 HTML을 가지고 올 때 빈 HTML 태그와 자바스크립트 링크만 가지고 와서, 유저가 처음 접속하면 빈 화면만 보게 된다. 이후 링크된 자바스크립트 파일을 가져와 다운이 완료되면 자바스크립트에 의해 UI가 그려지고 로직들이 동작하게 된다.
만약 브라우저에서 자바스크립트가 비활성화된다면 유저는 <code>&lt;noscript&gt;</code> 라는 빈 태그만 보게 된다.</p>
<blockquote>
<ul>
<li>장점
  페이지 이동 시 빠른 이동과 더 나은 사용자 경험을 제공한다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>단점
  자바스크립트를 다운받는 동안 유저는 빈 화면만 볼 수 있다.
  HTML 파일이 비어있어 SEO가 좋지 않다.</li>
</ul>
</blockquote>
<h2 id="2-spa로-구성된-웹-앱에서-ssrserver-side-rendering이-필요한-이유">2. SPA로 구성된 웹 앱에서 SSR(Server-side Rendering)이 필요한 이유</h2>
<p>SSR은 클라이언트에서 모든 것을 처리하던 CSR과 달리 서버에서 필요한 데이터를 모두 가져오는 방식으로, 서버에서 모든 렌더링을 마치고 일부 Data와 결합된 HTML 파일을 가져오는 방식이다. 모든 자바스크립트 파일을 다운받아 렌더링하는 CSR과 달리 서버에서 미리 렌더링되어 있는 HTML을 받아오고, 이후에는 동적으로 페이지를 구성하기 때문에 CSR보다 첫 번째 페이지의 로딩이 더 빠르다. 또한 HTML에 정보들이 있기 때문에 CSR 방식보다 SEO에 유리하다는 장점도 있다.</p>
<blockquote>
<ul>
<li>장점
  첫 페이지 로딩이 빠르다. 
  완성된 HTML 파일을 받아오기 때문에 SEO가 좋다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>단점
  새로운 페이지로 이동할 때마다 서버에 요청해서 페이지를 받기 때문에, 깜박임 현상이 있다.
  서버에 과부하가 걸릴 수 있다.
  뷰는 볼 수 있어도, 기능이 동작하지 않는 경우가 발생할 수 있다. (TIV는 짧지만, TTI가 길 수 있다.)</li>
</ul>
</blockquote>
<p>CSR과 SSR은 각각의 장단점을 가지고 있고, 개발자는 필요에 따라 자신에게 맞는 방식을 사용하는 것이 좋다. CSR과 SSR 뿐 아니라 SSG 방식과 ISR 방식도 있다. 
Next.js는 리액트 안에서 hybrid static, SSR을 쉽게 사용할 수 있도록 하는 프레임워크다.</p>
<h2 id="3-nextjs-프로젝트를-세팅한-뒤-yarn-start-스크립트를-실행했을-때">3. Next.js 프로젝트를 세팅한 뒤 yarn start 스크립트를 실행했을 때</h2>
<p>Next.js 를 시작하는 방법은 Next.js 공식 홈페이지에 잘 설명되어 있다.
가장 쉬운 방법은 <code>create-next-app</code> 을 사용하는 것이다.</p>
<pre><code>npx create-next-app@latest
# or
yarn create next-app
# or
pnpm create next-app</code></pre><p>타입 스크립트를 사용한다면 <code>--ts</code> 와 함께 사용하면 된다.</p>
<pre><code>npx create-next-app@latest --ts
# or
yarn create next-app --typescript
# or
pnpm create next-app --ts</code></pre><p>생성된 프로젝트에서 yarn start 스크립트를 실행하면 어떻게 되는지 코드를 통해 알아보자</p>
<p>yarn start 를 실행하면 어떻게 되는지 찾기 위해서 next.js 레포지토리에서 command.ts 파일을 찾았다.</p>
<p><a href="https://github.com/vercel/next.js/blob/canary/packages/next/lib/commands.ts">https://github.com/vercel/next.js/blob/canary/packages/next/lib/commands.ts</a></p>
<pre><code class="language-typescript">export type cliCommand = (argv?: string[]) =&gt; void

export const commands: { [command: string]: () =&gt; Promise&lt;cliCommand&gt; } = {
  build: () =&gt; Promise.resolve(require(&#39;../cli/next-build&#39;).nextBuild),
  start: () =&gt; Promise.resolve(require(&#39;../cli/next-start&#39;).nextStart),
  export: () =&gt; Promise.resolve(require(&#39;../cli/next-export&#39;).nextExport),
  dev: () =&gt; Promise.resolve(require(&#39;../cli/next-dev&#39;).nextDev),
  lint: () =&gt; Promise.resolve(require(&#39;../cli/next-lint&#39;).nextLint),
  telemetry: () =&gt;
    Promise.resolve(require(&#39;../cli/next-telemetry&#39;).nextTelemetry),
  info: () =&gt; Promise.resolve(require(&#39;../cli/next-info&#39;).nextInfo),
}</code></pre>
<p>여러 가지 커맨드가 모여져 있는데 그 중 start 명령어는 <code>next-start 파일</code>에서 <code>nextStart</code>를 호출하는 것을 알 수 있었다.</p>
<p>cli 폴더를 찾아 <code>next-start.ts</code> 파일을 살펴보았다.</p>
<p><a href="https://github.dev/vercel/next.js/blob/canary/packages/next/cli/next-start.ts">https://github.dev/vercel/next.js/blob/canary/packages/next/cli/next-start.ts</a> </p>
<pre><code class="language-typescript">import arg from &#39;next/dist/compiled/arg/index.js&#39;
import { startServer } from &#39;../server/lib/start-server&#39;
import { getPort, printAndExit } from &#39;../server/lib/utils&#39;
import * as Log from &#39;../build/output/log&#39;
import isError from &#39;../lib/is-error&#39;
import { getProjectDir } from &#39;../lib/get-project-dir&#39;
import { cliCommand } from &#39;../lib/commands&#39;

const nextStart: cliCommand = (argv) =&gt; {
  const validArgs: arg.Spec = {
    // Types
    &#39;--help&#39;: Boolean,
    &#39;--port&#39;: Number,
    &#39;--hostname&#39;: String,
    &#39;--keepAliveTimeout&#39;: Number,

    // Aliases
    &#39;-h&#39;: &#39;--help&#39;,
    &#39;-p&#39;: &#39;--port&#39;,
    &#39;-H&#39;: &#39;--hostname&#39;,
  }
  let args: arg.Result&lt;arg.Spec&gt;
  try {
    args = arg(validArgs, { argv })
  } catch (error) {
    if (isError(error) &amp;&amp; error.code === &#39;ARG_UNKNOWN_OP.TION&#39;) {
      return printAndExit(error.message, 1)
    }
    throw error
  }
  if (args[&#39;--help&#39;]) {
    console.log(`
      Description
        Starts the application in production mode.
        The application should be compiled with \`next build\` first.

      Usage
        $ next start &lt;dir&gt; -p &lt;port&gt;

      &lt;dir&gt; represents the directory of the Next.js application.
      If no directory is provided, the current directory will be used.

      Options
        --port, -p      A port number on which to start the application
        --hostname, -H  Hostname on which to start the application (default: 0.0.0.0)
        --keepAliveTimeout  Max milliseconds to wait before closing inactive connections
        --help, -h      Displays this message
    `)
    process.exit(0)
  }

  const dir = getProjectDir(args._[0])
  const host = args[&#39;--hostname&#39;] || &#39;0.0.0.0&#39;
  const port = getPort(args)

  const keepAliveTimeoutArg: number | undefined = args[&#39;--keepAliveTimeout&#39;]
  if (
    typeof keepAliveTimeoutArg !== &#39;undefined&#39; &amp;&amp;
    (Number.isNaN(keepAliveTimeoutArg) ||
      !Number.isFinite(keepAliveTimeoutArg) ||
      keepAliveTimeoutArg &lt; 0)
  ) {
    printAndExit(
      `Invalid --keepAliveTimeout, expected a non negative number but received &quot;${keepAliveTimeoutArg}&quot;`,
      1
    )
  }

  const keepAliveTimeout = keepAliveTimeoutArg
    ? Math.ceil(keepAliveTimeoutArg)
    : undefined

  startServer({
    dir,
    hostname: host,
    port,
    keepAliveTimeout,
  })
    .then(async (app) =&gt; {
      const appUrl = `http://${app.hostname}:${app.port}`
      Log.ready(`started server on ${host}:${app.port}, url: ${appUrl}`)
      await app.prepare()
    })
    .catch((err) =&gt; {
      console.error(err)
      process.exit(1)
    })
}

export { nextStart }</code></pre>
<p>중간 중간 추가적인 커맨드를 구성하는 코드들이 있었는데, 마지막으로 <code>startServer 함수</code>를 호출한다.</p>
<p>--help 커맨드에 달려있는 주석을 보면 <code>next build</code> 를 먼저 선행해야 한다고 되어있다.</p>
<p>실제로 테스트를 위해 생성한 next 프로젝트에서 아무것도 하지 않고 곧바로 <code>next start</code> 를 입력하면 에러가 발생하는 것을 알 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/09dd2592-c90b-46c3-a1ed-cde9c4e450a8/image.png" alt=""></p>
<p><code>yarn build</code>를 해주면 <code>.next 폴더</code>가 생성되면서 서버를 시작하기 위한 준비가 끝난다.</p>
<p>이 상태에서 <code>yarn start</code> 를 실행하면 서버가 켜진다. <code>startServer</code>는 어떤 식으로 동작하는지 한 번 살펴보았다.</p>
<p><a href="https://github.dev/vercel/next.js/blob/canary/packages/next/cli/next-start.ts">https://github.dev/vercel/next.js/blob/canary/packages/next/cli/next-start.ts</a></p>
<pre><code class="language-typescript">import type { NextServerOptions, NextServer, RequestHandler } from &#39;../next&#39;
import { warn } from &#39;../../build/output/log&#39;
import http from &#39;http&#39;
import next from &#39;../next&#39;

interface StartServerOptions extends NextServerOptions {
  allowRetry?: boolean
  keepAliveTimeout?: number
}

export function startServer(opts: StartServerOptions) {
  let requestHandler: RequestHandler

  // http 모듈의 createServer 를 이용해서 웹 서버 객체를 만듦
  const server = http.createServer((req, res) =&gt; {
    return requestHandler(req, res)
  })

  if (opts.keepAliveTimeout) {
    server.keepAliveTimeout = opts.keepAliveTimeout
  }

  return new Promise&lt;NextServer&gt;((resolve, reject) =&gt; {
    let port = opts.port
    let retryCount = 0

    server.on(&#39;error&#39;, (err: NodeJS.ErrnoException) =&gt; {
      if (
        port &amp;&amp;
        opts.allowRetry &amp;&amp;
        err.code === &#39;EADDRINUSE&#39; &amp;&amp;
        retryCount &lt; 10
      ) {
        warn(`Port ${port} is in use, trying ${port + 1} instead.`)
        port += 1
        retryCount += 1
        server.listen(port, opts.hostname)
      } else {
        reject(err)
      }
    })

    let upgradeHandler: any

    if (!opts.dev) {
      server.on(&#39;upgrade&#39;, (req, socket, upgrade) =&gt; {
        upgradeHandler(req, socket, upgrade)
      })
    }

    server.on(&#39;listening&#39;, () =&gt; {
      const addr = server.address()
      const hostname =
        !opts.hostname || opts.hostname === &#39;0.0.0.0&#39;
          ? &#39;localhost&#39;
          : opts.hostname

      const app = next({
        ...opts,
        hostname,
        customServer: false,
        httpServer: server,
        port: addr &amp;&amp; typeof addr === &#39;object&#39; ? addr.port : port,
      })

      requestHandler = app.getRequestHandler()
      upgradeHandler = app.getUpgradeHandler()
      resolve(app)
    })

    server.listen(port, opts.hostname)
  })
}</code></pre>
<p><code>startServer</code>에서는 <code>http.create()</code> 를 통해 웹 서버 객체를 만들고, 프로미스를 반환하는데  이 프로미스 객체를 보니 에러가 나는 경우, 업그레이드가 필요한 경우, 서버가 정상적으로 작동하는 경우로 나누어져 있었다.</p>
<p>서버가 정상적으로 켜지는 경우는 </p>
<pre><code class="language-typescript">      const app = next({
        ...opts,
        hostname,
        customServer: false,
        httpServer: server,
        port: addr &amp;&amp; typeof addr === &#39;object&#39; ? addr.port : port,
      })
      ...
    resolve(app)</code></pre>
<p>이 코드로 보이는데, <code>next()</code> 가 무엇인지 살펴보러 <code>next.ts</code> 파일을 봤다.</p>
<p><a href="https://github.com/vercel/next.js/blob/canary/packages/next/server/next.ts">https://github.com/vercel/next.js/blob/canary/packages/next/server/next.ts</a></p>
<p>코드가 조금 길긴한데, 읽어보니 <code>render 메서드</code>에서 this.getServer().renderToHTML(...args) 를 반환하고 있었다.</p>
<pre><code class="language-typescript">  async renderToHTML(...args: Parameters&lt;Server[&#39;renderToHTML&#39;]&gt;) {
    const server = await this.getServer()
    return server.renderToHTML(...args)
  }</code></pre>
<p><code>getServer</code> 메서드에서는 <code>this.serverPromise</code>를 반환하는데 코드를 보니까 <code>createServer</code>로 서버를 만들어서 <code>serverPromise</code> 에 할당하는 듯 했다. </p>
<pre><code class="language-typescript">  private async getServer() {
    if (!this.serverPromise) {
      setTimeout(getServerImpl, 10)
      this.serverPromise = this.loadConfig().then(async (conf) =&gt; {
        this.server = await this.createServer({
          ...this.options,
          conf,
        })
        if (this.preparedAssetPrefix) {
          this.server.setAssetPrefix(this.preparedAssetPrefix)
        }
        return this.server
      })
    }
    return this.serverPromise
  }</code></pre>
<p><code>createServer</code> 메서드</p>
<pre><code class="language-typescript">  private async createServer(options: DevServerOptions): Promise&lt;Server&gt; {
    if (options.dev) {
      const DevServer = require(&#39;./dev/next-dev-server&#39;).default
      return new DevServer(options)
    }
    const ServerImplementation = await getServerImpl()
    return new ServerImplementation(options)
  }</code></pre>
<p><code>getServierImpl()</code> 로 새 객체를 만들어서 반환하는 것 같은데... <code>getServerImpl()</code> 은 또 <code>next-server.ts</code>에서 가져오는 모양.</p>
<p><a href="https://github.com/vercel/next.js/blob/canary/packages/next/server/next-server.ts">https://github.com/vercel/next.js/blob/canary/packages/next/server/next-server.ts</a></p>
<pre><code class="language-typescript">import { RenderOpts, renderToHTML } from &#39;./render&#39;

export default class NextNodeServer extends BaseServer {
    // ...
    protected async renderHTML(
    req: NodeNextRequest,
    res: NodeNextResponse,
    pathname: string,
    query: NextParsedUrlQuery,
    renderOpts: RenderOpts
  ): Promise&lt;RenderResult | null&gt; {
    // Due to the way we pass data by mutating `renderOpts`, we can&#39;t extend the
    // object here but only updating its `serverComponentManifest` field.
    // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952
    renderOpts.serverComponentManifest = this.serverComponentManifest
    renderOpts.serverCSSManifest = this.serverCSSManifest
    renderOpts.fontLoaderManifest = this.fontLoaderManifest

    if (this.hasAppDir &amp;&amp; renderOpts.isAppPath) {
      return appRenderToHTMLOrFlight(
        req.originalRequest,
        res.originalResponse,
        pathname,
        query,
        renderOpts
      )
    }

    return renderToHTML(
      req.originalRequest,
      res.originalResponse,
      pathname,
      query,
      renderOpts
    )
  }</code></pre>
<p><code>next-server.ts</code> 파일의 <code>NextNodeServer 클래스</code>에서 <code>renderHTML</code>이라는 메서드를 볼 수 있었는데, 반환값이<code>renderToHTML()</code>이고, 이게 또 <code>render.tsx</code>에서 가져온다;;</p>
<p><a href="https://github.com/vercel/next.js/blob/canary/packages/next/server/render.tsx">https://github.com/vercel/next.js/blob/canary/packages/next/server/render.tsx</a></p>
<p><code>renderToHTML</code> 을 보다보니 조금 익숙한 것들이 보인다.</p>
<pre><code class="language-typescript">export async function renderToHTML(
...
    // Component will be wrapped by ServerComponentWrapper for RSC
  let Component: React.ComponentType&lt;{}&gt; | ((props: any) =&gt; JSX.Element) =
    renderOpts.Component</code></pre>
<p><code>React Server Component</code>를 구성하는 <code>JSX.Element</code> 발견.</p>
<pre><code class="language-typescript">  const AppContainerWithIsomorphicFiberStructure: React.FC&lt;{
    children: JSX.Element
  }&gt; = ({ children }) =&gt; {
    return (
      &lt;&gt;
        {/* &lt;Head/&gt; */}
        &lt;Noop /&gt;
        &lt;AppContainer&gt;
          &lt;&gt;
            {/* &lt;ReactDevOverlay/&gt; */}
            {dev ? (
              &lt;&gt;
                {children}
                &lt;Noop /&gt;
              &lt;/&gt;
            ) : (
              children
            )}
            {/* &lt;RouteAnnouncer/&gt; */}
            &lt;Noop /&gt;
          &lt;/&gt;
        &lt;/AppContainer&gt;
      &lt;/&gt;
    )
  }</code></pre>
<p>리액트 엘리먼트를 반환하는 <code>AppContainerWithIsomorphicFiberStructure</code> 는 <code>ctx</code> 에서 활용되는 것을 볼 수 있다.</p>
<pre><code class="language-typescript">  const ctx = {
    err,
    req: isAutoExport ? undefined : req,
    res: isAutoExport ? undefined : res,
    pathname,
    query,
    asPath,
    locale: renderOpts.locale,
    locales: renderOpts.locales,
    defaultLocale: renderOpts.defaultLocale,
    AppTree: (props: any) =&gt; {
      return (
        &lt;AppContainerWithIsomorphicFiberStructure&gt;
          {renderPageTree(App, OriginComponent, { ...props, router })}
        &lt;/AppContainerWithIsomorphicFiberStructure&gt;
      )
    },
    defaultGetInitialProps: async (
      docCtx: DocumentContext,
      options: { nonce?: string } = {}
    ): Promise&lt;DocumentInitialProps&gt; =&gt; {
      const enhanceApp = (AppComp: any) =&gt; {
        return (props: any) =&gt; &lt;AppComp {...props} /&gt;
      }

      const { html, head: renderPageHead } = await docCtx.renderPage({
        enhanceApp,
      })
      const styles = jsxStyleRegistry.styles({ nonce: options.nonce })
      jsxStyleRegistry.flush()
      return { html, head: renderPageHead, styles }
    },
  }
  let props: any
  ...
  props = await loadGetInitialProps(App, {
    AppTree: ctx.AppTree,
    Component,
    router,
    ctx,
  })</code></pre>
<p>그리고 이 <code>ctx</code> 는 <code>loadGetInitialProps</code> 에서 다시 활용되는데 이름을 보니까 초기값을 가져오는 듯</p>
<pre><code class="language-typescript">    try {
      data = await getServerSideProps({
        req: req as IncomingMessage &amp; {
          cookies: NextApiRequestCookies
        },
        res: resOrProxy,
        query,
        resolvedUrl: renderOpts.resolvedUrl as string,
        ...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
        ...(previewData !== false
          ? { preview: true, previewData: previewData }
          : undefined),
        locales: renderOpts.locales,
        locale: renderOpts.locale,
        defaultLocale: renderOpts.defaultLocale,
      })
      canAccessRes = false
    } catch (serverSidePropsError: any) {
      // remove not found error code to prevent triggering legacy
      // 404 rendering
      if (
        isError(serverSidePropsError) &amp;&amp;
        serverSidePropsError.code === &#39;ENOENT&#39;
      ) {
        delete serverSidePropsError.code
      }
      throw serverSidePropsError
    }</code></pre>
<p>서버사이드 렌더링을 하는 코드로 보인다.</p>
<pre><code class="language-typescript">  const renderDocument = async () =&gt; {
    async function loadDocumentInitialProps(
      renderShell?: (
        _App: AppType,
        _Component: NextComponentType
      ) =&gt; Promise&lt;ReactReadableStream&gt;
    ) {
      const renderPage: RenderPage = async (
        options: ComponentsEnhancer = {}
      ): Promise&lt;RenderPageResult&gt; =&gt; {
        ...
       const html = await renderToString(
          &lt;Body&gt;
            &lt;AppContainerWithIsomorphicFiberStructure&gt;
              {renderPageTree(EnhancedApp, EnhancedComponent, {
                ...props,
                router,
              })}
            &lt;/AppContainerWithIsomorphicFiberStructure&gt;
          &lt;/Body&gt;
        )
        return { html, head }
      }
    ...
      let styles
      if (hasDocumentGetInitialProps) {
        styles = docProps.styles
        head = docProps.head
      } else {
        styles = jsxStyleRegistry.styles()
        jsxStyleRegistry.flush()
      }

      return {
        bodyResult,
        documentElement,
        head,
        headTags: [],
        styles,
      }
    }
  }</code></pre>
<p>드디어 페이지를 렌더링하는 코드 발견. <code>renderToString</code> 메서드를 쓰는 것을 볼 수 있는데 파일 위 쪽으로 가면 이 메서드를 볼 수 있다.</p>
<pre><code class="language-typescript">async function renderToString(element: React.ReactElement) {
  if (!shouldUseReactRoot) return ReactDOMServer.renderToString(element)
  const renderStream = await ReactDOMServer.renderToReadableStream(element)
  await renderStream.allReady
  return streamToString(renderStream)
}

async function renderToStaticMarkup(element: React.ReactElement) {
  if (!shouldUseReactRoot) return ReactDOMServer.renderToStaticMarkup(element)
  return renderToString(element)
}</code></pre>
<p>코드를 보니 서버에 있는 ReactDOM 요소를 string으로 변환해서 렌더링하는 코드로 보인다.</p>
<p><code>yarn start</code> 를 하면 결국 서버를 실행하고, 서버에 있는 ReactDOM의 초기 렌더링 값을 HTML형태의 string으로 반환해서 렌더링하는 것으로 보인다.</p>
<p>처음으로 프레임워크의 코드를 뜯어 보았는데 이해가 안되는 코드들도 많고 우선 코드량이 엄청나다... 이런 코드들을 어떻게 다 만들 수 있었는지 참.</p>
<p>나도 언젠가 저런 것들을 만들어야지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<38. 브라우저 렌더링 과정>]]></title>
            <link>https://velog.io/@st_hwang/38.-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@st_hwang/38.-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Wed, 13 Jul 2022 02:52:37 GMT</pubDate>
            <description><![CDATA[<h1 id="38-브라우저-렌더링-과정">38. 브라우저 렌더링 과정</h1>
<p>대부분의 프로그래밍 언어는 운영체제(Operating System; OS), 가상 머신(Virtual Machine; VM) 위에서 실행되지만, 웹 애플리케이션의 클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS 와 함께 실행됨. 따라서 브라우저 환경을 고려할 때 더 효율적인 클라이언트 사이드 자바스크립트 프로그래밍이 가능함.</p>
<p>이를 위해 브라우저가 html, css, 자바스크립트로 작성된 텍스트 문서를 어떻게 파싱하여 브라우저에 렌더링하는지 알아야 함</p>
<blockquote>
<p>파싱은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고, 토큰에 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스 트리(parse tree/syntax tree) 를 생성하는 일련의 과정을 말함. 일반적으로 파싱이 완료된 이후에는 파스 트리를 기반으로 중간 언어인 바이트코드를 생성하고 실행.</p>
</blockquote>
<blockquote>
<p>렌더링은 html, css, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말함</p>
</blockquote>
<p><strong>브라우저의 렌더링 과정</strong></p>
<ol>
<li>브라우저는 html, css, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받음</li>
<li>브라우저의 렌더링 엔진은 서버로부터 응답된 html와 css를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성</li>
<li>브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환하여 실행. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합됨</li>
<li>렌더 트리를 기반으로 html 요소의 레이아웃(위치와 크기)를 계산하고 브라우저 화면에 html 요소를 페인팅</li>
</ol>
<h2 id="381--요청과-응답">38.1  요청과 응답</h2>
<p>브라우저에 핵심 기능은 필요한 리소스(html, css, js, 이미지, 폰트 등의 정적 파일 또는 서버가 동적으로 생성한 데이터)를 서버에 요청(request)하고 서버로부터 응답(response)받아 브라우저에 시각적으로 렌더링라는 것. 즉 렌더링에 필요한 리소스는 모두 서버에 존재하므로 필요한 리소스를 서버에 요청하고 서버가 응답한 리소스를 파싱하여 렌더링.</p>
<p>서버에 요청을 전송하기 위해 브라우저는 주소창을 제공. 브라우저 주소창에 URL을 입력하고 엔터 키를 누르면 URL의 호스트 이름이 DNS를 통해 IP 주소로 변환되고 이 IP 주소를 갖는 서버에게 요청을 전송.</p>
<blockquote>
<p>호스트명 : 네트워크에 연결된 장치(컴퓨터, 파일 서버, 케이블 모뎀 등)들에게 부여되는 고유한 이름. 특히 인터넷에서는 웓드 와이드 웹, 전자우편 등에서 호스트명을 흔히 사용하고 도메인 이름과 유사하지만 더 넓은 의미. 대부분 호스트명이라고 하면 인터넷 상에서의 호스트명을 가리키는 경우가 많음</p>
</blockquote>
<blockquote>
<p>DNS (Domain Name System) : 영문/한글로 되어있는 도메인 주소를 네트워크에서 찾아갈 수 있는 IP 주소로 변경해주는 것</p>
</blockquote>
<p><img src="https://hanseul-lee.github.io/2020/12/24/20-12-24-URL/uri.png" alt="URI의 구조"></p>
<p>예를 들어, 브라우저 주소 창에 <a href="https://poiemaweb.com">https://poiemaweb.com</a> 을 입력하고 엔터 키를 누르면 루트 요청(스킴과 호스트 만으로 구성된 URI에 의한 요청)이 poiemaweb.com 서버로 전송. 루트 요청에는 명확히 리소스를 요청하는 내용이 없지만, 일반적으로 서버는 루트 요청에 의해 index.html을 응답하도록 기본 설정되어 있음. 즉, <a href="https://poiemaweb.com">https://poiemaweb.com</a> 은 <a href="https://poiemaweb.com/index.html">https://poiemaweb.com/index.html</a> 과 같은 요청</p>
<p>만일 index.html이 아닌 다른 정적 파일을 서버에 요청하려면 브라우저 주소창에 <a href="https://poiemaweb.com/assets/data/data.json">https://poiemaweb.com/assets/data/data.json</a> 과 같이 요청할 정적 파일의 경로와 파일 이름을 URI 호스트 뒤의 패스(path)에 기술하여 서버에 요청. 그러면 서버는 루트 폴더의 assets/data 폴더 내에 있는 정적 파일 data.json을 응답</p>
<p>브라우저 주소창을 통해 서버에게 정적 파일만을 요청하는 것은 아니고, 자바스크립트를 통해 동적으로 서버에 정적/동적 데이터를 요청할 수도 있음</p>
<p>브라우저가 서버에 요청한 내용과 서버가 응답한 내용을 개발자 도구의 Network 패널에서 확인 가능</p>
<p>index.html 뿐만 아니라 css, 자바스크립트, 이미지, 폰트 파일들이 응답된 것은 브라우저 렌더링 엔진이 html(index.html)을 파싱하는 도중에 외부 리소스를 로드하는 태그, css 파일을 로드하는 link 태그,  이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그 등을 만나면 html 의 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청하기 때문</p>
<h2 id="382-http-11과-http-20">38.2 HTTP 1.1과 HTTP 2.0</h2>
<p><strong>HTTP (HyperText Transfer Protocol)</strong> : 웹에서 브라우저와 서버가 통신하기 위한 프로토콜(규약)</p>
<p>HTTP/1.1은 기봍적으로 커넥션당 하나의 요청과 응답만 처리. 즉 여러 개의 요청을 한 번에 전송할 수 없고 응답 또한 마찬가지. 따라서 HTML 문서 내에 포함된 여러 개의 리소스 요청, link 태그, img 태그, script 태그 등에 의한 리소스 요청이 개별적으로 전송되고 응답 또한 개별적으로 전송. 이처럼 HTTP/1.1은 리소스의 동시 전송이 불가능한 구조이므로 요청할 리소스의 개수에 비례하여 응답 시간도 증가하는 단점이 있음.</p>
<p>HTTP/2는 커넥션당 여러 개의 요청과 응답, 즉 다중 요청/응답이 가능하여 여러 리소스의 동시 전송이 가능해 HTTP/1.1 에 비해 페이지 로드 속도가 약 50% 빠름</p>
<h2 id="383-html-파싱과-dom-생성">38.3 HTML 파싱과 DOM 생성</h2>
<p>브라우저의 요청에 의해 서버가 응답한 html 문서는 문자열로 이루어진 순수한 텍스트. 이를 브라우저에 시각적인 픽셀로 렌더링하려면 html 문서를 브라우저가 이해할 수 있는 자료구조(객체) 로 변환하여 메모리에 저장해야함. 브라우저 렌더링 엔진은 응답받은 html 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 <strong>DOM</strong> 생성</p>
<img src="https://velog.velcdn.com/images%2Fwhow1101%2Fpost%2Fc7263443-fdef-4982-a7f4-4d4521a0f4ce%2Fchap2.png" alt="img" style="zoom:50%;" />



<ol>
<li>서버에 존재하던 html 파일이 브라우저 요청에 의해 응답. 이때 서버는 브라우저가 요청한 html 파일을 읽어 들여 메모리에 저장한 다음 메모리에 저장된 바이트(2진수) 를 인터넷을 경우하여 응답</li>
<li>브라우저는 서버가 응답한 html 문서를 바이즈(2진수)  형태로 응답받음. 응답된 바이트 형태의 html 문서는 meta 태그의 charset  어트리뷰트에 의해 지정된 인코딩 방식(예 : UTF-8)을 기준으로 문자열로 변환됨. 참고로 meta 태그의 charset 어트리뷰트에 선언된 인코딩 방식(예 : UTF-8) 은 <code>content-type: text/html; charset=utf-8</code> 과 같이 응답 헤더에 담겨 응답되고, 브라우저는 이를 확인하고 문자열로 반환</li>
<li>문자열로 변환된 html 문서를 읽어 들여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해함</li>
<li>각 토큰들을 객체로 변환하여 노드(node)들을 생성. 토큰의 내용에 따라 문서 노드, 요소 노트, 어트리뷰트 노트, 텍스트 노드가 생성됨. 노드는 이후 DOM 을 구성하는 기본 요소가 됨</li>
<li>html 문서는 html 요소들의 집합으로 이루어지며 html 요소는 중첩 관계를 갖는다. 즉 html 요소의 콘텐츠 영역(시작 태그와 종료 태그 사이)에는 텍스트뿐만 아니라 다른 html 요소도 포함될 수 있다. 이때 html 요소 간에는 중첩 관계에 의해 부자 관계가 형성됨. 이러한 html 요소 간의 부자 관계를 반영하여 모든 노드들을 <strong>트리 자료구조로 구성</strong>. 이 노드들로 구성된 트리 자료 구조를 <strong><code>DOM</code></strong> 이라 부름 </li>
</ol>
<h2 id="384--css-파싱과-cssom-생성">38.4  CSS 파싱과 CSSOM 생성</h2>
<p>렌더링 엔진은 DOM을 생성해 나가다가 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중지. link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 html과 동일한 파싱 과정(바이트 -&gt; 문자 -&gt; 토큰 -&gt; 노드 -&gt; CSSOM)을 거치며 해석하여 <strong>CSSOM</strong> 생성. CSS 파싱이 완료되면 HTML 파싱이 중단된 지점부터 다시 HTML 을 파싱하기 시작하여 DOM 생성 재개.</p>
<p>CSSOM은 CSS의 상속을 반영하여 생성됨.</p>
<h2 id="385-렌더-트리-생성">38.5 렌더 트리 생성</h2>
<p>렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 각각 DOM, CCSOM 생성. DOM과 CSSOM은 렌더링을 위해 렌더 트리로 결합됨</p>
<p>렌더 트리는 렌더링을 위한 트리 구조의 자료구조로, 브라우저 화면에 렌더링되지 않는 노드(meta 태그, script 태그 등)와 css에 의해 비표시(display:none) 되는 노드들은 포함되지 않음. 즉, 렌더 트리는 브라우저 화면에 렌더링되는 노드만으로 구성됨</p>
<p>이후 완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력됨</p>
<p>브라우저의 렌더링 과정은 다음과 같은 경우 반복해서 레이아웃 계산과 페인팅이 재차 실현될 수 있음</p>
<ol>
<li>자바스크립트에 의한 노드 추가 또는 삭제</li>
<li>브라우저 창의 리사이징에 의한 뷰포트 크기 변경</li>
<li>html 요소의 레이아웃에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경</li>
</ol>
<p>레이아웃 게산과 페인팅을 다시 실행하는 리렌더링은 성능에 악영향을 주는 작업이므로 가급적 리렌더링이 빈번하게 발생하지 않도록 주의할 필요가 있음</p>
<h2 id="386-자바스크립트-파싱과-실행">38.6 자바스크립트 파싱과 실행</h2>
<p>html 문서를 파싱한 결과물로서 생성된 DOM은 html 문서의 구조와 정보뿐 아니라 html 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스로서 DOM API 제공</p>
<p>즉, 자바스크립트 코드에서 DOM API를 사용하면 이미 생성된 DOM을 동적으로 조작할 수 있음</p>
<p><code>렌더링 엔진</code>은 DOM을 생성해 나가다 script를 만나면 DOM 생성을 일시 중단하고, script 태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 서버에 요청하여 로드한 자바스크립트 파일이나 script 태그 내의 자바스크립트 코드를 파싱하기 위해 <code>자바스크립트 엔진</code>에 제어권을 넘김. 이후 자바스크립트 파싱과 실행이 종료되면 <code>렌더링 엔진</code>으로 다시 제어권을 넘겨 html 파싱이 중단된 지점부터 다시 html 파싱을 시작하여 DOM 생성을 재개</p>
<p>자바스크립트 파싱과 실행은 브라우저의 렌더링 엔진이 아니라 <code>자바스크립트 엔진</code>이 처리함. 자바스크립트 엔진은 <strong>자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어로 변환하고 실행하는 역할</strong>. 모든 자바스크립트 엔진은 ECMAScript 사양을 준수.</p>
<p>렌더링 엔진이 html과 css를 파싱하여 DOM과 CSSOM을 생성하듯이 <code>자바스크립트 엔진</code>은 자바스크립트를 해석하여 <strong>AST(Abstract Syntax Tree; 추상적 구문 트리)를 생성</strong>. 그리고 AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 <strong>바이트코드를 생성하여 실행</strong>함</p>
<p><img src="https://velog.velcdn.com/images%2Fkhg04170%2Fpost%2Fe1cb1494-89d1-4529-af48-db6926155f26%2FIMG_C64FD440D0A4-1.jpeg" alt="img"></p>
<h4 id="토크나이징-tokenizing">토크나이징 tokenizing</h4>
<p>단순한 문자열인 <strong>자바스크립트 소스코드를 어휘분석(lexical analysis)하여 문법적 의미를 갖는 최소 단위인 토큰들로 분해</strong>. 이 과정을 렉싱이라고 부르기도 하지만 토크나이징과 미묘한 차이가 있음</p>
<h4 id="파싱-parsing">파싱 parsing</h4>
<p><strong>토큰들의 집합을 구문 분석(syntactic analysis)하여 <code>AST(추상적 구문 트리)</code>를 생성</strong>. <code>AST</code>는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조. AST는 인터프리터나 컴파일러만이 사용하는 것은 아님. AST를 사용하면 TypeScript, Babel, Prettier 같은 트랜스파일러(transpiler)를 구현할 수도 있음. 참고(<a href="https://astexplorer.net">https://astexplorer.net</a>)</p>
<h4 id="바이트코드-생성과-실행">바이트코드 생성과 실행</h4>
<p>파싱의 결과물로서 생성된 <code>AST</code>는 인터프리터가 실행할 수 있는 중간 코드인 <code>바이트코드</code>로 변환되고 인터프리터에 의해 실행됨.</p>
<h2 id="387--리플로우와-리페인트">38.7  리플로우와 리페인트</h2>
<p>자바스크립트 코드에서 DOM이나 CSSOM 을 변경하는 DOM API 가 사용된 경우 DOM 이나 CSSOM 이 변경됨. 이때 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 화면에 다시 렌더링하는데 이를 리플로우(reflow), 리페인트(repaint)라고 함</p>
<p><code>리플로우</code>는 레이아웃 계산을 다시 하는 것을 말하며, 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행됨.</p>
<p><code>리페인트</code>는 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 것을 말함.</p>
<p>따라서 리플로우와 리페인트가 반드시 순차적으로 실행되는 것은 아니며, 레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행됨</p>
<h2 id="388--자바스크립트-파싱에-의한-html-파싱-중단">38.8  자바스크립트 파싱에 의한 HTML 파싱 중단</h2>
<p>브라우저는 동기적으로 위에서 아래 방향으로 순차적으로 HTML, CSS, 자바스크립트를 파싱하고 실행. 따라서 script 태그의 위치에 따라 html 파싱이 블로킹되어 DOM 생성이 지연될 수 있으며, script 태그의 위치는 중요한 의미를 가짐</p>
<p><img src="https://velog.velcdn.com/images%2Fkhg04170%2Fpost%2Fb4491004-5a66-4cc8-a714-7ee9a19cce7e%2FScreen%20Shot%202021-07-14%20at%207.34.37%20AM.png" alt="img"></p>
<p>script 태그 내의 자바스크립트 코드에서 DOM이나 CSSOM 을 변경하는 DOM API 를 사용하는 경우 DOM, CSSOM이 이미 생성되어 있어야함. 따라서 <strong>body 요소 가장 아래에 자바스크립트를 위치시키는 것이 좋음</strong>. DOM이 완성되지 않은 상태에서 자바스크립트가 DOM을 조작하면 에러가 발생할 수 있고, 자바스크립트 로딩/파싱/실행으로 인해 html 요소들의 렌더링에 지장받는 일이 발생하지도 않아 페이지 로딩 시간도 단축됨.</p>
<img src="https://velog.velcdn.com/images%2Fkhg04170%2Fpost%2Fb9f794e5-c12d-4b6a-997d-6b0448877d38%2FScreen%20Shot%202021-07-14%20at%207.40.30%20AM.png" alt="img" style="zoom:50%;" />



<h2 id="389-script-태그의-asyncdefer-어트리뷰트">38.9 script 태그의 async/defer 어트리뷰트</h2>
<p>자바스크립트 파싱에 의한 DOM 생성이 중단되는 문제를 근본적으로 해결하기 위해 HTML5부터 script 태그에 async/defer 어트리튜브가 추가됨</p>
<p>async/defer 어트리뷰트는 src 어트리뷰트를 통해 외부 자바스크립트 파일을 로드하는 경우에만 사용할 수 있음 (src 어트리뷰트가 없는 인라인 자바스크립트에서는 사용 불가)</p>
<p>async/defer 어트리뷰트를 사용하면 html 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행되나, 자바스크립트 실행 시점에 차이가 있음</p>
<h4 id="async-어트리뷰트">async 어트리뷰트</h4>
<p>html 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행. 단 자바스크립트 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되며, 이때 html 파싱이 중단됨</p>
<p>여러 개의 script 태그에 async 어트리뷰트를 지정하면 script 태그의 순서와는 상관없이 로드가 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지는 않음. 순서 보장이 필요한 script 태그에는 async 어트리뷰트를 지정하면 안됨</p>
<p><img src="https://velog.velcdn.com/images%2Fkhg04170%2Fpost%2F604cefaf-b13b-48b3-b9c5-bb3d07cadf0a%2FScreen%20Shot%202021-07-14%20at%207.45.46%20AM.png" alt="img"></p>
<h4 id="defer-어트리뷰트">defer 어트리뷰트</h4>
<p>html 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행되나, 자바스크립트의 파싱과 실행은 html 파싱이 완료된 직후, 즉 DOM 생성이 완료된 직후 진행.</p>
<p><img src="https://velog.velcdn.com/images%2Fkhg04170%2Fpost%2F6e44c7b5-014d-42c7-a0d0-da1651ce1d2a%2FScreen%20Shot%202021-07-14%20at%207.52.02%20AM.png" alt="img"></p>
<p>출처 : 드림코딩 유튜브 영상 <a href="https://www.youtube.com/watch?v=tJieVCgGzhs&amp;list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&amp;index=2&amp;ab_channel=%EB%93%9C%EB%A6%BC%EC%BD%94%EB%94%A9">https://www.youtube.com/watch?v=tJieVCgGzhs&amp;list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&amp;index=2&amp;ab_channel=%EB%93%9C%EB%A6%BC%EC%BD%94%EB%94%A9</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<32. String & 33. Symbol>]]></title>
            <link>https://velog.io/@st_hwang/32.-String-33.-Symbol</link>
            <guid>https://velog.io/@st_hwang/32.-String-33.-Symbol</guid>
            <pubDate>Wed, 13 Jul 2022 02:50:27 GMT</pubDate>
            <description><![CDATA[<h1 id="32-string">32. String</h1>
<p>표준 빌트인 객체인 String 은 원시 타입인 문자열을 다룰 때 유용한 프로퍼티와 메서드를 제공</p>
<h2 id="321-string-생성자-함수">32.1 String 생성자 함수</h2>
<p>표준 빌트인 객체인 String 객체는 <code>생성자 함수 객체</code>이므로 new 연산자와 함께 호출하여 String 인스턴스를 생성할 수 있음</p>
<p>String 생성자 함수에 인수를 전달하지 않고 new 연산자와 함께 호출하면 [[StringData]] 내부 슬롯에 빈 문자열을 할당한 String 래퍼 객체 생성</p>
<pre><code class="language-js">const strObj = new String();
console.log(strObj); // String {length: 0, [[PrimitiveValue]]: &quot;&quot;} 
// [[PrimitiveValue]] = [[StringData]] 내부 슬롯을 가리킴. ES5에서는 [[StringData]]를 [[PrimitiveValue]]라 부름</code></pre>
<p>String 생성자 함수의 인수로 문자열을 전달하면서 new 연산자와 함께 호출하면 [[StringData]] 내부 슬롯에 인수로 전달받은 문자열을 할당한 String 래퍼 객체 생성</p>
<pre><code class="language-js">const strObj = new String(&#39;Lee&#39;);
console.log(strObj); // String {0: &quot;L&quot;, 1: &quot;e&quot;, 2: &quot;e&quot;, length: 3, [[Prototype]]: String, [[PrimitiveValue]]: &quot;Lee&quot;}</code></pre>
<p>Strigng 래퍼 객체는 배열과 마찬가지로 length 프로퍼티와 인덱스를 나타내는 숫자 형식의 문자열을 프로퍼티 키로, 각 문자를 프로퍼티 값으로 갖는 <code>유사 배열 객체</code>이면서 <code>이터러블</code>. 따라서 <strong>배열과 유사하게 인덱스를 사용하여 각 문자에 접근할 수 있음</strong></p>
<p>단, 문자열은 원시 값이므로 변경할 수 없는데, 에러는 발생하지 않음(무시됨)</p>
<p>String 생성자 함수의 인수로 문자열이 아닌 값을 전달하면 인수를 문자열로 <strong>강제 변환</strong>한 후, [[StringData]] 내부 슬롯에 변환된 문자열을 할당한 String 래퍼 객체 생성</p>
<p>new 연산자를 사용하지 않고 String 생성자 함수를 호출하면 String 인스턴스가 아닌 문자열을 반환. 이를 이용해서 <strong>명식적으로 타입을 변환하기도 함</strong></p>
<pre><code class="language-js">String(1); // -&gt; &#39;1&#39;
String(NaN); // -&gt; &#39;NaN&#39;
String(false); // -&gt; &#39;false&#39;</code></pre>
<h2 id="322-length-프로퍼티">32.2 length 프로퍼티</h2>
<p>length 프로퍼티는 <strong>문자열의 문자 개수를 반환</strong></p>
<pre><code class="language-js">&#39;Hello&#39;.length; // -&gt; 5</code></pre>
<p>String 래퍼 객체는 배열과 마찬가지로 length 프로퍼티를 가짐. 그리고 인덱스를 나타내는 숫자를 프로퍼티, 각 문자를 프로퍼티 값으로 가지므로 <strong>String 래퍼 객체 = 유사 배열 객체</strong></p>
<h2 id="323-string-메서드">32.3 String 메서드</h2>
<p>String 객체는 원본 String 래퍼 객체를 직접 변경하는 메서드는 존재하지 않으며, 언제나 새로운 문자열을 반환</p>
<p>문자열은 변경 불가능한(immutable) 원시 값이기 때문에 String 래퍼 객체도 읽기 전용 객체로 제공됨. (writable 프로퍼티 어트리뷰트 값이 false)</p>
<p>String 래퍼 객체가 읽기 전용 객체가 아니라면 변경된 String 래퍼 객체를 문자열로 되돌릴 때 문자열이 변경. 따라서 <strong>String 객체의 모든 메서드는 String 래퍼 객체를 직저 변경할 수 없고, 언제나 새로운 문자열을 생성하여 반환</strong></p>
<h3 id="3231-stringprototypeindexof">32.3.1 String.prototype.indexOf</h3>
<p><code>indexOf</code> 메서드는 <strong>대상 문자열(메서드를 호출한 문자열)에서 인수로 전달받은 문자열을 검색하여 첫 번째 인덱스 반환</strong>. 없으면 -1 반환</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.indexOf(&#39;l&#39;); // -&gt; 2
str.indexOf(&#39;or&#39;); // -&gt; 7
str.indexOf(&#39;x&#39;); // -&gt; -1</code></pre>
<p>indexOf 메서드의 2번째 인수로 검색을 시작할 인덱스를 전달할 수 있음</p>
<pre><code class="language-js">str.indexOf(&#39;l&#39;, 3); // -&gt; 3</code></pre>
<p>indexOf 메서드는 대상 문자열에 특정 문자열이 존재하는지 확인할 때 유용</p>
<pre><code class="language-js">if(str.indexOf(&#39;Hello&#39;) !== -1){
  // 문자열 str에 &#39;Hello&#39;가 포함되어 있는 경우 실행
}</code></pre>
<p>ES6에서 도입된 String.prototype.includes 메서드를 사용하는게 가독성이 더 좋음</p>
<pre><code class="language-js">if(str.includes(&#39;Hello&#39;)){
  // 문자열 str에 &#39;Hello&#39;가 포함되어 있는 경우 실행
}</code></pre>
<h3 id="3232-stringprototypesearch">32.3.2 String.prototype.search</h3>
<p><code>search</code> 메서드는 <strong>대상 문자열에서 인수로 전달받은 정규 표현식과 매치하는 문자열을 검색하여 일치하는 문자열의 인덱스를 반환</strong>. 검색에 실패하면 -1 반환</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.search(/o/); // -&gt; 4
str.search(/x/); // -&gt; -1</code></pre>
<h3 id="3233-stringprototypeincludes-es6">32.3.3 String.prototype.includes (ES6)</h3>
<p><code>includes</code> 메서드는 <strong>대상 문자열에 인수로 전달받은 문자열이 포함되어 있는지 확인하여 그 결과를 true / false로 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.includes(&#39;Hello&#39;); // -&gt; true
str.includes(&#39; &#39;); // -&gt; true
str.includes(&#39;x&#39;); // -&gt; false
str.includes(); // false</code></pre>
<p>2번째 인수로 검색을 시작할 인덱스를 전달할 수 있음</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.includes(&#39;l&#39;, 3); // -&gt; true
str.includes(&#39;H&#39;, 3); // -&gt; false</code></pre>
<h3 id="3234-stringprototypestartswith-es6">32.3.4 String.prototype.startsWith (ES6)</h3>
<p><code>startsWith</code> 메서드는 <strong>대상 문자열이 인수로 전달받은 문자열로 시작하는지 확인하여 그 결과를 true / false 로 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.startsWith(&#39;He&#39;); // -&gt; true</code></pre>
<p>2번째 인수로 검색을 시작할 인덱스를 전달할 수 있음</p>
<pre><code class="language-js">str.startsWith(&#39; &#39;, 5); // -&gt; true</code></pre>
<h3 id="3235-stringprototypeendswith-es6">32.3.5 String.prototype.endsWith (ES6)</h3>
<p><code>endsWith</code> 메서드는 <strong>대상 문자열이 인수로 전달받은 문자열로 끝나는지 확인하여 그 결과를 true 또는 false로 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.endWith(&#39;ld&#39;); // -&gt; true</code></pre>
<p>2번째 인수로 검색할 문자열의 길이를 전달할 수 있음</p>
<pre><code class="language-js">// 문자열 str의 처음부터 5자리까지가 &#39;lo&#39;로 끝나는지 확인
str.endWith(&#39;lo&#39;, 5); // -&gt; true</code></pre>
<h3 id="3236-stringprototypecharat">32.3.6 String.prototype.charAt</h3>
<p><code>charAt</code> 메서드는 <strong>대상 문자열에서 인수로 전달받은 인덱스에 위치한 문자를 검색하여 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

for(let i = 0; i &lt; str.length; i++){
  console.log(str.charAt(i)); // H e l l o
}</code></pre>
<p>인수로 전달받은 인덱스는 문자열의 범위 사이의 정수여야 하며, 인덱스가 문자열의 범위를 벗어난 정수인 경우 빈 문자열 반환</p>
<pre><code class="language-js">str.charAt(5); // -&gt; &#39;&#39;</code></pre>
<h3 id="3237-stringprototypesubstring">32.3.7 String.prototype.substring</h3>
<p><code>substring</code> 메서드는 <strong>대상 문자열에서 <code>첫 번째 인수로 전달받은 인덱스</code>에 위치한 문자부터 <code>두 번째 인수로 전달받은 인덱스</code>에 위치하는 문자의 바로 이전 문자까지의 부분 문자열 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

// 인덱스 1부터 인덱스 4 이전까지의 부분 문자열 반환
str.substring(1, 4); // -&gt; ell</code></pre>
<p>두 번째 인수를 생략하면 첫 번째 인수부터 마지막 문자까지의 문자열 반환</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.substring(1); // -&gt; &#39;ello world&#39;</code></pre>
<p>다음과 같이 인수를 전달하여도 정상 동작함</p>
<ul>
<li>첫 번째 인수 &gt; 두 번째 인수인 경우 두 인수 교환</li>
<li>인수 &lt; 0 또는 NaN인 경우 0으로 취급</li>
<li>인수 &gt; 문자열의 길이인 경우 인수를 문자열의 길이로 취급</li>
</ul>
<pre><code class="language-js">const str = &#39;Hello World&#39;; // str.length == 11

// 첫 번째 인수 &gt; 두 번째 인수인 경우 두 인수 교환
str.substring(4, 1); // -&gt; &#39;ell&#39;

// 인수 &lt; 0 또는 NaN인 경우 0으로 취급
str.substring(-2); // &#39;Hello world&#39;

// 인수 &gt; 문자열의 길이(str.length)인 경우 인수를 문자열의 길이로 취급
str.substring(1, 100); // -&gt; &#39;ello world&#39;
str.substring(20); // -&gt; &#39;&#39;</code></pre>
<p>String.prototype.indexOf 메서드와 함께 사용하면 특정 문자열을 기준으로 앞뒤에 위치한 부분 문자열을 취득할 수 있음</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

str.substring(0, str.indexOf(&#39; &#39;)); // -&gt; &#39;Hello&#39;
str.substring(str.indexOf(&#39; &#39;)+1, str.length); // -&gt; &#39;world&#39;</code></pre>
<h3 id="3238-stringprototypeslice">32.3.8 String.prototype.slice</h3>
<p>slice 메서드는 substring 메서드와 동일하게 동작. 단 <code>slice</code> 메서드에는 <strong>음수인 인수를 전달할 수 있는데, 음수인 인수를 전달하면 대상 문자열의 가장 뒤에서부터 시작하여 문자열을 잘라내어 반환</strong></p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

// substring과 slice 메서드는 동일하게 동작
str.substring(0, 5); // -&gt; &#39;hello&#39;
str.slice(0, 5); // -&gt; &#39;hello&#39;

// substring : 인수 &lt; 0 또는 NaN인 경우 0으로 취급
str.substring(-5); // -&gt; &#39;hello world&#39;
// slice : 인수 &lt; 0인 경우 뒤에서부터 잘라내어 반환
str.slice(-5); // -&gt; &#39;world&#39;</code></pre>
<h3 id="3239-stringprototypetouppercase">32.3.9 String.prototype.toUpperCase</h3>
<p><code>toUpperCase</code> 메서드는 <strong>대상 문자열을 모두 대문자로 변경한 문자열 반환</strong></p>
<h3 id="32310-stringprototypetolowercase">32.3.10 String.prototype.toLowerCase</h3>
<p><code>toLowerCase</code> 메서드는 <strong>대상 문자열을 모두 소문자로 변경한 문자열 반환</strong></p>
<h3 id="32311-stringprototypetrim">32.3.11 String.prototype.trim</h3>
<p><code>trim</code> 메서드는 <strong>대상 문자열 앞뒤에 공백 문자가 있을 경우 이를 제거한 문자열 반환</strong></p>
<pre><code class="language-js">const str = &#39;    foo  &#39;;
str.trim(); // -&gt; &#39;foo&#39;</code></pre>
<p>String.prototype.replace 메서드에 정규 표현식을 인수로 전달하여 공백 문자를 제거할 수도 있음</p>
<pre><code class="language-js">const str = &#39;    foo  &#39;;

str.replace(/\s/g, &#39;&#39;); // -&gt; &#39;foo&#39;
str.replace(/\s+/g, &#39;&#39;); // -&gt; &#39;foo&#39;
str.replace(/\s+$/g, &#39;&#39;); // -&gt; &#39;    foo&#39;</code></pre>
<h3 id="32312-stringprototyperepeat-es6">32.3.12 String.prototype.repeat (ES6)</h3>
<p>repeat 메서드는 대상 문자열을 인수로 전달받은 정수만큼 반복해 연결한 새로운 문자열을 반환. 인수로 전달받은 정수가 0이면 빈 문자열을 반환. 음수면 RangeError 발생. 인수를 생략하면 기본값 0으로 설정</p>
<pre><code class="language-js">const str = &#39;abc&#39;;

str.repeat(); // -&gt; &#39;&#39;
str.repeat(1); // -&gt; &#39;abc&#39;
str.repeat(2); // -&gt; &#39;abcabc&#39;
str.repeat(-1); // -&gt; RangeError: Invalid count value</code></pre>
<h3 id="32313-stringprototypereplace">32.3.13 String.prototype.replace</h3>
<p><code>replace</code> 메서드는 <strong>대상 문자열에서 첫 번째 인수로 전달받은 문자열 또는 정규표현식을 검색하여 두 번째 인수로 전달한 문자열로 치환할 문자열 반환</strong>. 검색된 문자열이 여럿 존재할 경우 첫 번째로 검색된 문자열만 치환.</p>
<pre><code class="language-js">const str = &#39;Hello world&#39;;

str.replace(&#39;world&#39;, &#39;Lee&#39;); // -&gt; &quot;Hello Lee&quot;</code></pre>
<p>특수한 교체 패턴을 사용할 수 있음.</p>
<p>replace 첫 번째 인수로 정규 표현식을 전달할 수 있음</p>
<p>replace 두 번째 인수로 치환 함수를 전달할 수 있음</p>
<h3 id="32314-stringprototypesplit">32.3.14 String.prototype.split</h3>
<p><code>split</code> 메서드는 <strong>대상 문자열에서 첫 번째 인수로 전달한 문자열 또는 정규 표현식을 검색하여 문자열을 구분한 후 분리된 각 문자열로 이루어진 배열 반환</strong>. 인수로 빈 문자열을 전달하면 각 문자를 모두 분리하고, 인수를 생략하면 대상 문자열 전체를 단일 요소로 하는 배열 반환</p>
<pre><code class="language-js">const str = &#39;How are you doing?&#39;

// 공백으로 구분(단어로 구분)하여 배열로 반환
str.split(&#39; &#39;); // -&gt; [&#39;How&#39;, &#39;are&#39;, &#39;you&#39;, &#39;doing?&#39;]

// \s는 여러 가지 공백 문자(스페이스, 탭 등)을 의미. 즉 [\t\r\n\v\f]와 같은 의미
str.split(/\s/); // -&gt; [&#39;How&#39;, &#39;are&#39;, &#39;you&#39;, &#39;doing?&#39;]

// 인수로 빈 문자열을 전달하면 각 문자를 모두 분리
str.split(&#39;&#39;); // -&gt; [&#39;H&#39;, &#39;o&#39;, &#39;w&#39;, &#39; &#39;, &#39;a&#39;, &#39;r&#39;, &#39;e&#39;, &#39; &#39;, &#39;y&#39;, &#39;o&#39;, &#39;u&#39;, &#39; &#39;, &#39;d&#39;, &#39;o&#39;, &#39;i&#39;, &#39;n&#39;, &#39;g&#39;, &#39;?&#39;]

// 인수를 생략하면 대상 문자열 전체를 단일 요소로 하는 배열 반환
str.split(); // -&gt; [&#39;How are you doing?&#39;]</code></pre>
<p>두 번째 인수로 배열의 길이 지정 가능</p>
<pre><code class="language-js">str.split(&#39; &#39;, 3); // -&gt; [&#39;How&#39;, &#39;are&#39;, &#39;you&#39;]</code></pre>
<p>split 메서드는 배열을 반환하기 때문에 Array.prototype.reverse, Array.prototype.join 메서드와 함께 사용하면 문자열을 역순으로 뒤집을 수 있음</p>
<h1 id="33-7번째-데이터-타입-symbol">33. 7번째 데이터 타입 Symbol</h1>
<h2 id="331-심벌이란">33.1 심벌이란?</h2>
<p>자바스크립트에는 6개의 타입(문자열, 숫자, 불리언, undefined, null, 객체 타입)이 있었는데, ES6에서 심벌이 도입</p>
<p><strong><code>심벌</code></strong> : <strong>변경 불가능한 원시 타입의 값. 다른 값과 중복되지 않는 유일무이한 값</strong>. 주로 이름의 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용</p>
<p>프로퍼티 키로 사용할 수 있는 값은 빈 문자열을 포함하는 모든 문자열 또는 심벌 값</p>
<h2 id="332-심벌-값의-생성">33.2 심벌 값의 생성</h2>
<h3 id="3321-symbol-함수">33.2.1 Symbol 함수</h3>
<p><strong>심벌 값은 Symbol 함수를 호출하여 생성</strong>. 다른 원시값의 값은 리터럴 표기법을 통해 값을 생성할 수 있지만, 심벌 값은 Symbol 함수를 호출하여 생성해야 함</p>
<p>이 때 생성된 심벌 값은 외부로 노출되지 않아 확인할 수 없으며, 다른 값과 절대 중복되지 않는 유일무이한 값</p>
<pre><code class="language-js">// Symbol 함수를 호출하여 유일무이한 심벌 값 생성
const mySymbol = Symbol();
console.log(typeof mySymbol); // symbol

// 심벌 값은 외부로 노출되지 않아 확인할 수 없음
console.log(mySymbol); // Symbol()</code></pre>
<p>Symbol 함수는 String, Number, Boolean 생성자 함수와는 달리 <strong>new 연산자와 함께 호출하지 않음</strong> (심벌 값은 변경 불가능한 원시 값)</p>
<pre><code class="language-js">new Symbol(); // TypeError: Symbol is not a constructor</code></pre>
<p>Symbol 함수에는 선택적으로 문자열을 <code>인수</code>로 전달할 수 있는데, 이 문자열은 <strong>생성된 심벌 값에 대한 설명으로 디버깅 용도로만 사용</strong>되며, 심벌 값 생성에 어떠한 영향도 주지 않음</p>
<pre><code class="language-js">// 심벌 값에 대한 설명이 같더라도 유일무이한 심벌 값 생성
const mySymbol1 = Symbol(&#39;mySymbol&#39;);
const mySymbol2 = Symbol(&#39;mySymbol&#39;);

console.log(mySymbol1 === mySymbol2); // false</code></pre>
<p>심벌 값도 문자열, 숫자, 불리언과 같이 객체처럼 접근하면 암묵적으로 래퍼 객체 생성</p>
<pre><code class="language-js">const mySymbol = Symbol(&#39;mySymbol&#39;);

// 심벌도 래퍼 객체를 생성
console.log(mySymbol.description); // mySymbol
console.log(mySymbol.toString()); // Symbol(mySymbol)</code></pre>
<p>심벌 값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않음</p>
<p>단, <strong>불리언 타입으로는 암묵적으로 타입 변환</strong>. 이를 통해 if 문 등에서 존재 확인이 가능</p>
<pre><code class="language-js">const mySymbol = Symbol();

// 심벌 값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않음
console.log(mySymbol + &#39;&#39;); // TypeError
console.log(+mySymbol); // TypeError

// 불리언 타입으로는 암묵적으로 타입 변환
console.log(!!mySymbol); // true

// if 문 등에서 존재 확인 가능
if (mySymbol) console.log(&#39;mySymbol is not empty&#39;); // mySymbol is not empty</code></pre>
<h3 id="3322-symbolfor--symbolkeyfor-메서드">33.2.2 Symbol.for / Symbol.keyFor 메서드</h3>
<p><code>Symbol.for</code> 메서드는 <strong>인수로 전달받은 문자열을 키로 사용하여 키와 심벌 값들의 쌍이 저장되어 있는 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌 값을 검색</strong>. 검색에 성공하면 새로운 심벌 값을 생성하지 않고 검색된 심벌 값 반환. 검색에 실패하면 새로운 심벌 값을 생성하여 Symbol.for 메서드의 인수로 전달된 키로 전역 심벌 레지스트리에 저장한 후, 생성된 심벌 값 반환.</p>
<pre><code class="language-js">// 전역 심벌 레지스트리에 mySymbol 이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값 생성
const s1 = Symbol.for(&#39;mySymbol&#39;);
// 전역 심벌 레지스트리에 mySymbol 이라는 키로 저장된 심벌 값이 있으면 해당 심벌 값 반환
const s2 = Symbol.for(&#39;mySymbol&#39;);

console.log(s1 === s2); // true</code></pre>
<p>Symbol 함수는 호출될 때마다 유일무이한 심벌 값을 생성. 자바스크립트 엔진이 관리하는 심벌 값 저장소인 전역 심벌 레지스트리에서 심벌 값을 검색할 수 있는 키를 지정할 수 없으므로 전역 심벌 레지스트리에 등록되어 관리되지는 않음. <strong>Symbol.for 메서드를 사용하면 애플리케이션 전역에서 중복되지 않는 유일무이한 상수인 심벌 값을 단 하나만 생성하여 전역 심벌 레지스트리를 통해 공유할 수 있음</strong></p>
<p><code>Symbol.keyFor</code> 메서드를 사용하면 <strong>전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출할 수 있음</strong></p>
<pre><code class="language-js">// 전역 심벌 레지스트리에 mySymbol 이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값 생성
const s1 = Symbol.for(&#39;mySymbol&#39;);
// 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출
Symbol.keyFor(s1); // -&gt; mySymbol

// Symbol 함수를 호출하여 생성한 심벌 값은 전역 심벌 레지스트리에 등록되어 관리되지 않음
const s2 = Symbol(&#39;foo&#39;);
// 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출
Symbol.keyFor(s2); // -&gt; undefined</code></pre>
<h2 id="333-심벌과-상수">33.3 심벌과 상수</h2>
<p>4방향(위, 아래, 왼쪽, 오른쪽)을 나타내는 상수를 정의한다고 생각해보면</p>
<pre><code class="language-js">// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수 정의
// 이때 값 1, 2, 3, 4에는 특별한 의미가 없고, 상수 이름에 의미가 있음
const Direction = {
  UP: 1,
  DOWN: 2,
  LEFT: 3,
  RIGHT: 4
};

// 변수에 상수를 할당
const myDirection = Direction.UP;

if (myDirection === Direction.UP){
  console.log(&#39;You are going UP.&#39;)
}</code></pre>
<p>값에는 특별한 의미가 없고 상수 이름 자체에 의미가 있는 경우가 있는데, 문제는 상수 값 1, 2, 3, 4가 변경될 수 있고, 다른 변수 값과 중복될 수도 있음.</p>
<p>이러한 경우 변경/중복될 가능성이 있는 무의미한 상수 대신 중복될 가능성이 없는 유일무이한 심벌 값을 사용할 수 있음</p>
<pre><code class="language-js">// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수 정의
// 중복될 가능성이 없는 심벌 값으로 상수 값 생성
const Direction = {
  UP: Symbol(&#39;UP&#39;),
  DOWN: Symbol(&#39;DOWN&#39;),
  LEFT: Symbol(&#39;LEFT&#39;),
  RIGHT: Symbol(&#39;RIGHT&#39;)
};

const myDirection = Direction.UP;

if (myDirection === Direction.UP){
  console.log(&#39;You are going UP.&#39;)
}</code></pre>
<p>자바스크립트에서 명명된 숫자 상수의 집합인 enum(enumerated type; 열거형) 을 흉내 내어 사용하려면 객체의 변경을 방지하기 위해 객체를 동결하는 Object.freeze 메서드와 심벌 값을 사용</p>
<pre><code class="language-js">// JavaScript enum
// Direction 객체는 불변 객체이며 프로퍼티 값은 유일무이한 값
const Direction = Object.freeze({
  UP: Symbol(&#39;UP&#39;),
  DOWN: Symbol(&#39;DOWN&#39;),
  LEFT: Symbol(&#39;LEFT&#39;),
  RIGHT: Symbol(&#39;RIGHT&#39;)
});

const myDirection = Direction.UP;

if (myDirection === Direction.UP){
  console.log(&#39;You are going UP.&#39;)
}</code></pre>
<h2 id="334-심벌과-프로퍼티-키">33.4 심벌과 프로퍼티 키</h2>
<p>객체의 프로퍼티 키는 빈 문자열을 포함하는 모든 문자열 또는 심벌 값으로 만들 수 있고, 동적으로 생성할 수도 있음</p>
<p>심벌 값을 프로퍼티 키로 사용하려면 프로퍼티 키로 사용할 심벌 값에 대괄호를 사용해야 하며, 프로퍼티에 접근할 때도 대괄호를 사용해야 함</p>
<p><strong>심벌 값은 유일무이한 값이므로 심벌 값으로 프로퍼티 키를 만들면 다른 프로퍼티 키와 절대 충돌하지 않음</strong></p>
<pre><code class="language-js">const obj = {
  // 심벌 값으로 프로퍼티 키 생성
  [Symbol.for(&#39;mySymbol&#39;)]: 1
};

obj[Symbol.for(&#39;mySymbol&#39;)]; // -&gt; 1</code></pre>
<h2 id="335-심벌과-프로퍼티-은닉">33.5 심벌과 프로퍼티 은닉</h2>
<p>심벌 값을 프로퍼티 키로 사용하여 생성한 프로퍼티는 for ... in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로 찾을 수 없음. (프로퍼티 은닉)</p>
<pre><code class="language-js">const obj = {
  // 심벌 값으로 프로퍼티 키 생성
  [Symbol.for(&#39;mySymbol&#39;)]: 1
};

for (const key in obj){
  console.log(key); // 아무것도 출력되지 않음
}

console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []</code></pre>
<p>ES6에서 도입된 Object.getOwnPropertySymbols 메서드 사용하면 심벌 값을 프로퍼티 키로 사용하여 생성한 프로퍼티를 찾을 수 있음</p>
<pre><code class="language-js">const obj = {
  // 심벌 값으로 프로퍼티 키 생성
  [Symbol.for(&#39;mySymbol&#39;)]: 1
};

// getOwnPropertySymbols 메서드는 인수로 전달한 객체의 심벌 프로퍼티 키를 배열로 반환
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(mySymbol)]

// getOwnPropertySymbols 메서드로 심벌 값도 찾을 수 있음
const symbolKey1 = Object.getOwnPropertySymbols(obj)[0];
console.log(obj[symbolKey1]); // 1</code></pre>
<h2 id="336-심벌과-표준-빌트인-객체-확장">33.6 심벌과 표준 빌트인 객체 확장</h2>
<p>일반적으로 표준 빌트인 객체에 사용자 정의 메서드를 직접 추가하여 확장하는 것은 권장하지 않음. 표준 빌트인 객체는 읽기 전용으로 사용하는 것이 좋음. 그 이유는 개발자가 직접 추가한 메서드와 미래에 표준 사양으로 추가될 메서드의 이름이 중복될 수 있기 때문. 표준 빌트인 메서드를 사용자 정의 메서드가 덮어쓴다면 문제가 됨</p>
<pre><code class="language-js">// 표준 빌트인 객체를 확장하는 것은 권장하지 않음
Array.prototype.sum = function(){
  return this.reduce((acc, cur) =&gt; acc + cur, 0);
};

[1, 2].sum(); // -&gt; 3</code></pre>
<p>하지만 중복될 가능성이 없는 심벌 값으로 프로퍼티 키를 생성하여 표준 빌트인 객체를 확장하면 표준 빌트인 객체의 기존 프로퍼티 키와 충돌하지 않는 것은 물론, 표준 사양의 버전이 올라감에 따라 추가될지 모르는 어떤 프로퍼티 키와도 충돌할 위험이 없어서 안전하게 표준 빌트인 객체를 확장할 수 있음</p>
<pre><code class="language-js">// 심벌 값으로 프로퍼티 키를 동적 생성하면 다른 프로퍼티 키와 절대 충돌하지 않아 안전
Array.prototype.[Symbol.for(&#39;sum&#39;)] = function(){
  return this.reduce((acc, cur) =&gt; acc + cur, 0);
};

[1, 2].[Symbol.for(&#39;sum&#39;)](); // -&gt; 3</code></pre>
<h2 id="337-well-known-symbol">33.7 Well-known Symbol</h2>
<p>자바스크립트가 기본 제공하는 빌트인 심벌값이 있음. 빌트인 심벌 값은 Symbol 함수의 프로퍼티에 할당되어 있음.</p>
<p>자바스크립트가 기본 제공하는 빌트인 심벌 값을 ECMAScript 사양에서는 Well-known Symbol 이라 부르며, Well-known Symbol은 자바스크립트 엔진 내부 알고리즘에 사용됨</p>
<p>Array, String, Map, Set, TypeArray, arguments, NodeList, HTMLCollection과 같이 for...of문으로 순회 가능한 빌트인 이터러블은 Well-known Symbol인 Symbol.iterator를 키로 갖는 메서드를 가지며, Symbol.iterator 메서드를 호출하면 이터레이터를 반환하도록 ECMAScript 사양에 규정되어 있음. (이터레이션 프로토콜)</p>
<p>만약 빌트인 이터러블이 아닌 일반 객체를 이터러블처럼 동작하도록 구현하고 싶으면 이터레이션 프로토콜을 따르면 됨. 즉 ECMASript 사양에 규정되어 있는 대로 Well-known Symbol 인 Symbol.iterator를 키로 갖는 메서드를 객체에 추가하고 이터레이터를 반환하도록 구현하면 그 객체는 이터러블이 됨</p>
<pre><code class="language-js">// 1 ~ 5 범위의 정수로 이루어진 이터러블
const iterable = {
  // Symbol.iterator 메서드를 구현하여 이터러블 프로토콜 준수
  [Symbol.iterator](){
    let cur = 1;
    const max = 5;
    // Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터 반환
    return {
      next(){
        return { value: cur++, done: cur &gt; max + 1};
      }
    };
  }
};

for (const num of iterable){
  console.log(num); // 1 2 3 4 5
}</code></pre>
<p> 이 때 이터레이션 프로토콜을 준수하기 위해 일반 객체에 추가해야 하는 메서드의 키 Symbol.iterator는 기존 프로퍼티 키 또는 미래에 추가할 프로퍼티 키와 절대로 중복되지 않음</p>
<p>이처럼 <strong>심벌은 중복되지 않는 상수 값을 생성하는 것은 물론 기존에 작성된 코드에 영향을 주지 않고 새로운 프로퍼티를 추가하기 위해, 즉 하위 호환성을 보장하기 위해 도입됨</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<31. RegExp>]]></title>
            <link>https://velog.io/@st_hwang/31.-RegExp</link>
            <guid>https://velog.io/@st_hwang/31.-RegExp</guid>
            <pubDate>Wed, 13 Jul 2022 02:48:52 GMT</pubDate>
            <description><![CDATA[<h1 id="31-regexp">31. RegExp</h1>
<h2 id="311-정규-표현식이란">31.1 정규 표현식이란?</h2>
<p><code>정규표현식</code>(regular exprossion)은 <strong>일정한 패턴을 가진 문자열의 집합을 표현하기 위해 사용하는 형식 언어</strong></p>
<p>정규표현식은 문자열을 대상으로 패턴 매칭 기능 제공. 패턴 매칭 기능은 <strong>특정 패턴과 일치하는 문자열을 검색하거나 추출 또는 치환할 수 있는 기능</strong></p>
<p>정규표현식을 사용하면 반복문과 조건문 없이 패턴을 정의하고 테스트하는 것으로 간단히 체크할 수 있으나, 정규표현식은 주석이나 공백을 허용하지 않고 여러 가지 기호를 혼합하여 사용하기 때문에 가독성이 좋지 않음</p>
<pre><code class="language-js">const tel = &#39;010-1234-5678&#39;;
const regExp = /^\d{3}-\d{4}-\d{4}$/;
regExp.test(tel); // -&gt; true</code></pre>
<h2 id="312-정규-표현식의-생성">31.2 정규 표현식의 생성</h2>
<p>정규표현식 객체(RegExp 객체)를 생성하기 위해서는 정규 표현식 리터럴과 RegExp 생성자 함수를 사용. 일반적인 방법은 정규 표현식 리터럴을 사용</p>
<img src="https://velog.velcdn.com/images/jiseong/post/f731ae0b-2ce6-486e-a56f-1518f28d4728/image.png" alt="정규 표현식" style="zoom:50%;" />

<p>정규표현식 리터럴은 패턴과 플래그로 구성</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;

// 패턴: is
// 플래그: i =&gt; 대소문자를 구별하지 않고 검색
const regexp = /is/i;

// test 메서드는 target 문자열에 대해 정규 표현식 regexp의 패턴을 검색하여 매칭 결과를 불리언 값으로 반환
regexp.test(target); // -&gt; true</code></pre>
<p>RegExp 생성자 함수를 사용하여 RegExp 객체를 생성할 수도 있음</p>
<pre><code class="language-js">new RegExp(pattern[, flags])
// pattern: 정규 표현식의 패턴
// flags: 정규 표현식의 플래그(g, i, m, u, y)</code></pre>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;

const regexp = new RegExp(/is/i); // ES6
// const regexp = new RegExp(/is/, &#39;i&#39;);
// const regexp = new RegExp(&#39;is&#39;, &#39;i&#39;);

regexp.test(target); // -&gt; true</code></pre>
<h2 id="313-regexp-메서드">31.3 RegExp 메서드</h2>
<h3 id="3131-regexpprototypeexec">31.3.1 RegExp.prototype.exec</h3>
<p><code>exec</code> 메서드는 <strong>인수로 전달받은 문자열에 대해 정규 표현식의 패턴을 검색하여 매칭 결과를 배열로 반환</strong>. 매칭 경과가 없는 경우 null 반환</p>
<p>exec 메서드는 문자열 내의 모든 패턴을 검색하는 g 플래그를 지정해도 첫 번째 매칭 결과만 반환</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;
regExp.exec(target); // -&gt; [&#39;is&#39;, index: 5, input: &#39;Is this all there is?&#39;, groups: undefined]</code></pre>
<h3 id="3132-regexpprototypetest">31.3.2 RegExp.prototype.test</h3>
<p><code>test</code> 메서드는 <strong>인수로 전달받은 문자열에 대해 정규 표현식의 패턴을 검색하여 매칭 결과를 불리언 값으로 반환</strong></p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;
regExp.test(target); // -&gt; true</code></pre>
<h3 id="3133-stringprototypematch">31.3.3 String.prototype.match</h3>
<p>String 표준 빌트인 객체가 제공하는 <code>match</code> 메서드는 <strong>대상 문자열과 인수로 전달받은 정규 표현식과의 매칭 결과를 배열로 반환</strong>. 매칭 결과 없는 경우 null 반환</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;
target.match(regExp); // -&gt; [&#39;is&#39;, index: 5, input: &#39;Is this all there is?&#39;, groups: undefined]</code></pre>
<p>exec 메서드는 문자열 내의 모든 패턴을 검색하는 g 플래그를 지정해도 첫 번째 매칭 결과만 반환하지만 String.prototype.match 메서드는 g 플래그가 지정되면 모든 매칭 결과를 배열로 반환</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/g;
target.match(regExp); // -&gt; [&quot;is&quot;, &quot;is&quot;]</code></pre>
<h2 id="314-플래그">31.4 플래그</h2>
<p>패턴과 함께 정규 표현식을 구성하는 플래그는 정규 표현식의 검색 방식을 설정하기 위해 사용. 플래그는 총 6개가 있음</p>
<table>
<thead>
<tr>
<th align="center">플래그</th>
<th align="center">의미</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">i</td>
<td align="center">Ignore case</td>
<td align="center">대소문자를 구별하지 않고 패턴 검색</td>
</tr>
<tr>
<td align="center">g</td>
<td align="center">Global</td>
<td align="center">대상 문자열 내에서 패턴과 일치하는 모든 문자열을 전역 검색함</td>
</tr>
<tr>
<td align="center">m</td>
<td align="center">Multi line</td>
<td align="center">문자열의 행이 바뀌더라라도 패턴 검색을 계속함</td>
</tr>
</tbody></table>
<p>플래그는 옵션이므로 선택적으로 사용할 수 있고, 순서와 상관없이 하나 이상의 플래그를 동시에 설정할 수도 있음. 어떠한 플래그를 사용하지 않은 경우 대소문자 구별해서 패턴 검색. 문자열에 패턴 검색 매칭 대상이 1개 이상 존재해도 첫 번째 매칭한 대상만 검색하고 종료</p>
<h2 id="315-패턴">31.5 패턴</h2>
<p>정규 표현식의 패턴은 문자열의 일정한 규칙을 표현하기 위해 사용하며, 정규 표현식의 플래그는 정규 표현식의 검색 방식을 설정하기 위해 사용</p>
<p>패턴은 <code>/</code> 로 열고 닫으며 문자열의 따옴표 생략. 따옴표를 포함하면 따옴표까지도 패턴에 포함되어 검색됨. 또한 패턴은 특별한 의미를 가지는 메타문자 또는 기호로 표현할 수 있음. </p>
<p>어떤 문자열 내에 패턴과 일치하는 문자열이 존재할 때 &#39;정규 표현식과 매치한다&#39;고 표현.</p>
<h3 id="3151-문자열-검색">31.5.1 문자열 검색</h3>
<p>정규 표현식의 패턴에 문자 또는 문자열을 지정하면 검색 대상 문자열에서 패턴으로 지정한 문자 또는 문자열 검색. RegExp 메서드를 사용하여 검색 대상 문자열과 정규 표현식의 매칭 결과를 구하면 검색 수행.</p>
<p>검색 대상 문자열과 플래그를 생략한 정규 표현식의 매칭 결과를 구하면 대소문자를 구별하여 정규 표현식과 매치한 첫 번째 결과만 반환</p>
<h3 id="3152-임의의-문자열-검색">31.5.2 임의의 문자열 검색</h3>
<p><code>.</code> 은 임의의 문자 한 개를 의미. 문자 내용은 무엇이든 상관없음</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;

// 임의의 3자리 문자열을 대소문자를 구별하여 전역 검색
const regExp = /.../g;

target.match(regExp); // -&gt; [&#39;Is &#39;, &#39;thi&#39;, &#39;s a&#39;, &#39;ll &#39;, &#39;the&#39;, &#39;re &#39;, &#39;is?&#39;]</code></pre>
<h3 id="3153-반복-검색">31.5.3 반복 검색</h3>
<p><code>{m, n}</code>은 앞선 패턴이 최소 m번, 최대 n번 반복되는 문자열을 의미. 콤마 뒤에 공백이 있으면 정상 동작하지 않음</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb AAA&#39;;

// &#39;A&#39;가 최소 1번, 최대 2번 반복되는 문자열을 전역 검색
const regExp = /A{1, 2}/g;

target.match(regExp); // -&gt; [&quot;A&quot;, &quot;AA&quot;, &quot;A&quot;, &quot;AA&quot;, &quot;A&quot;]</code></pre>
<p><code>{n}</code>은 앞선 패턴이 n번 반복되는 문자열을 의미. 즉 {n}은 {n, n}과 같음</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb AAA&#39;;

// &#39;A&#39;가 2번 반복되는 문자열을 전역 검색
const regExp = /A{2}/g;

target.match(regExp); // -&gt; [&quot;AA&quot;, &quot;AA&quot;]</code></pre>
<p><code>{n,}</code>은 앞선 패턴이 최소 n번 이상 반복되는 문자열을 의미</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb AAA&#39;;

// &#39;A&#39;가 최소 2번 이상 반복되는 문자열을 전역 검색
const regExp = /A{2,}/g;

target.match(regExp); // -&gt; [&quot;AA&quot;, &quot;AAA&quot;]</code></pre>
<p><code>+</code>는 앞선 패턴이 최소 한번 이상 반복되는 문자열을 의미. 즉 +는 {1,}과 같음.</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb AAA&#39;;

// &#39;A&#39;가 최소 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /A+/g;

target.match(regExp); // -&gt; [&quot;A&quot;, AA&quot;, &quot;A&quot;, &quot;AAA&quot;]</code></pre>
<p><code>?</code>는 앞선 패턴이 최대 한 번(0번 포함) 이상 반복되는 문자열을 의미. 즉 ?는 {0, 1}과 같음</p>
<pre><code class="language-js">const target = &#39;color colour&#39;;

// &#39;colo&#39; 다음 &#39;u&#39;가 최대 한 번(0번 포함) 이상 반복되고 &#39;r&#39;이 이어지는 문자열 &#39;color&#39;, &#39;colour&#39;를 전역 검색
const regExp = /colou?r/g;

target.match(regExp); // -&gt; [&quot;color&quot;, &quot;colour&quot;]</code></pre>
<h3 id="3154-or-검색">31.5.4 OR 검색</h3>
<p><code>|</code>은 or의 의미를 가짐.</p>
<pre><code class="language-Js">const target = &#39;A AA B BB Aa Bb&#39;;

// &#39;A&#39; 또는 &#39;B&#39;를 전역 검색
const regExp = /A|B/g;

target.match(regExp); // -&gt; [&quot;A&quot;, &quot;A&quot;, &quot;A&quot;, &quot;B&quot;, &quot;B&quot;, &quot;B&quot;, &quot;A&quot;, &quot;B&quot;]</code></pre>
<p>분해되지 않은 단어 레벨로 검색하기 위해서는 <code>+</code>와 함께 사용</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb&#39;;

// &#39;A&#39; 또는 &#39;B&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /A+|B+/g;

target.match(regExp); // -&gt; [&quot;A&quot;, &quot;AA&quot;, &quot;B&quot;, &quot;BB&quot;, &quot;A&quot;, &quot;B&quot;]</code></pre>
<p><code>[ ]내의 문자</code>는 or로 동작. 그 뒤에 +를 사용하면 앞선 패턴을 한 번 이상 반복</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb&#39;;

// &#39;A&#39; 또는 &#39;B&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /[AB]+/g;

target.match(regExp); // -&gt; [&quot;A&quot;, &quot;AA&quot;, &quot;B&quot;, &quot;BB&quot;, &quot;A&quot;, &quot;B&quot;]</code></pre>
<p>범위를 지정하려면 [ ]내에 <code>-</code>를 사용</p>
<pre><code class="language-js">const target = &#39;A AA BB ZZ Aa Bb&#39;;

// &#39;A&#39; ~ &#39;Z&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /[A-Z]+/g;

target.match(regExp); // -&gt; [&quot;A&quot;, &quot;AA&quot;, &quot;BB&quot;, &quot;ZZ&quot;, &quot;A&quot;, &quot;B&quot;]</code></pre>
<p>대소문자를 구별하지 않고 알파벳을 검색하는 방법은 다음과 같음</p>
<pre><code class="language-js">const target = &#39;A AA BB ZZ Aa Bb&#39;;

// &#39;A&#39; ~ &#39;Z&#39; 또는 &#39;a&#39; ~ &#39;z&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /[A-Za-z]+/g;

target.match(regExp); // -&gt; [&#39;A&#39;, &#39;AA&#39;, &#39;BB&#39;, &#39;ZZ&#39;, &#39;Aa&#39;, &#39;Bb&#39;]</code></pre>
<p>숫자를 검색하는 방법은 다음과 같음</p>
<pre><code class="language-js">const target = &#39;A BB 12,345&#39;;

// &#39;0&#39; ~ &#39;9&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /[0-9]+/g;

target.match(regExp); // -&gt; [&#39;12&#39;, &#39;345&#39;]</code></pre>
<p>쉼표를 패턴에 포함시키는 방법은 다음과 같음</p>
<pre><code class="language-js">const target = &#39;A BB 12,345&#39;;

// &#39;0&#39; ~ &#39;9&#39; 또는 &#39;,&#39;가 한 번 이상 반복되는 문자열을 전역 검색
const regExp = /[0-9,]+/g;

target.match(regExp); // -&gt; [&#39;12,345&#39;]</code></pre>
<p><code>\d</code> 는 숫자를 의미. 즉 \d는 [0-9]와 같음. <code>\D</code>는 반대로 숫자가 아닌 문자를 의미</p>
<pre><code class="language-js">const target = &#39;A BB 12,345&#39;;

// &#39;0&#39; ~ &#39;9&#39; 또는 &#39;,&#39;가 한 번 이상 반복되는 문자열을 전역 검색
let regExp = /[\d,]+/g;

target.match(regExp); // -&gt; [&#39;12,345&#39;]

// 숫자가 아닌 문자 또는 &#39;,&#39;가 한 번 이상 반복되는 문자열을 전역 검색
regExp = /[\D,]+/g;

target.match(regExp); // [&#39;A BB &#39;, &#39;,&#39;]</code></pre>
<p><code>\w</code>은 알파벳, 숫자, 언더스코어를 의미. 즉 \w는 [A-Za-z0-9_]와 같음. <code>\W</code>은 반대로 알파벳, 숫자, 언더스코어가 아닌 문자를 의미</p>
<pre><code class="language-js">const target = &#39;Aa Bb 12,345 _$%&amp;&#39;;

// 알파벳, 숫자, 언더스코어, &#39;,&#39;가 한 번 이상 반복되는 문자열을 전역 검색
let regExp = /[\w,]+/g;

target.match(regExp); // -&gt; [&#39;Aa&#39;, &#39;Bb&#39;, &#39;12,345&#39;, &#39;_&#39;]

// 알파벳, 숫자, 언더스코어가 아닌 문자 또는 &#39;,&#39;가 한 번 이상 반복되는 문자열을 전역 검색
regExp = /[\W,]+/g;

target.match(regExp); // -&gt; [&#39; &#39;, &#39; &#39;, &#39;,&#39;, &#39; &#39;, &#39;$%&amp;&#39;]</code></pre>
<h3 id="3155-not-검색">31.5.5 NOT 검색</h3>
<p>[...]내의 <code>^</code>은 not의 의미를 가짐. 예를 들어 <code>[^0-9]</code>는 숫자를 제외한 문자를 의미</p>
<pre><code class="language-js">const target = &#39;AA BB 12 Aa Bb&#39;;
const regExp = /[^0-9]+/g;
target.match(regExp); // -&gt; [&quot;AA BB Aa Bb&quot;]</code></pre>
<h3 id="3156-시작-위치로-검색">31.5.6 시작 위치로 검색</h3>
<p>[...] 밖의 <code>^</code>은 문자열의 시작을 의미</p>
<pre><code class="language-js">const target = &#39;httts://poiemaweb.com&#39;;

// &#39;https&#39;로 시작하는지 검사
const regExp = /^https/;

regExp.test(target); // -&gt; true</code></pre>
<h3 id="3157-마지막-위치로-검색">31.5.7 마지막 위치로 검색</h3>
<p><code>$</code>은 문자열의 마지막을 의미</p>
<pre><code class="language-js">const target = &#39;httts://poiemaweb.com&#39;;

// &#39;com&#39;으로 끝나는지 검사
const regExp = /com$/;

regExp.test(target); // -&gt; true</code></pre>
<h2 id="316-자주-사용하는-정규-표현식">31.6 자주 사용하는 정규 표현식</h2>
<h3 id="3161-특정-단어로-시작하는지-검사">31.6.1 특정 단어로 시작하는지 검사</h3>
<pre><code class="language-js">const url = &#39;https://example.com&#39;;

// &#39;http://&#39; 또는 &#39;https://&#39; 로 시작하는지 검사
/^https?:\/\//.test(url); // -&gt; true
/^(http|https):\/\//.test(url); // -&gt; true</code></pre>
<h3 id="3162-특정-단어로-끝나는지-검사">31.6.2 특정 단어로 끝나는지 검사</h3>
<pre><code class="language-js">const fileName = &#39;index.html&#39;;
// &#39;html&#39;로 끝나는지 검사
/html$/.test(fileName); // -&gt; true</code></pre>
<h3 id="3163-숫자로만-이루어진-문자열인지-검사">31.6.3 숫자로만 이루어진 문자열인지 검사</h3>
<pre><code class="language-js">const target = &#39;12345&#39;;

// 숫자로만 이루어진 문자열인지 검사
/^\d+&amp;/.test(target); // -&gt; true</code></pre>
<h3 id="3164-하나-이상의-공백으로-시작하는지-검사">31.6.4 하나 이상의 공백으로 시작하는지 검사</h3>
<p><code>\s</code> 는 여러 가지 공백 문자(스페이스, 탭 등)을 의미. 즉 \s 는 [\t\r\n\v\f]와 같은 의미</p>
<pre><code class="language-js">const target = &#39; Hi!&#39;;

// 하나 이상의 공백으로 시작하는지 검사
/^[\s]+/.test(target; // -&gt; true</code></pre>
<h3 id="3165-아이디로-사용-가능한지-검사">31.6.5 아이디로 사용 가능한지 검사</h3>
<pre><code class="language-js">const id = &#39;abc123&#39;;

// 알파벳 대소문자 또는 숫자로 시작하고 끝나며 4~10자리인지 검사
/^[A-Za-z0-9]{4,10}$/.test(id); // -&gt; true</code></pre>
<h3 id="3166-메일-주소-형식에-맞는지-검사">31.6.6 메일 주소 형식에 맞는지 검사</h3>
<pre><code class="language-js">const email = &#39;ungmo2@gmail.com&#39;;

/^[0-9a-zA-Z](-_\.)?[0-9a-zA-Z])*@[0-9a-zA-Z](-_\.)?[0-9a-zA-Z]*\.[a-zA-Z]{2,3}$/.test(email); // -&gt; true</code></pre>
<p>참고) 인터넷 메시지 형식 규약인 RFC 5322 에 맞는 패턴 매칭</p>
<pre><code class="language-js">(?:[a-z0-9!#$%&amp;&#39;*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;&#39;*+/=?^_`{|}~-]+)*|&quot;(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*&quot;)@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])</code></pre>
<h3 id="3167-핸드폰-번호-형식에-맞는지-검사">31.6.7 핸드폰 번호 형식에 맞는지 검사</h3>
<pre><code class="language-js">const cellphone = &#39;010-1234-5678&#39;;

/^\d{3}-\d{3,4}-\d{4}$/.test(cellphone); // -&gt; true</code></pre>
<h3 id="3168-특수문자-포함-여부-검사">31.6.8 특수문자 포함 여부 검사</h3>
<pre><code class="language-js">const target = &#39;abc#123&#39;;

([^A-Za-z0-9]/gi).test(target); // -&gt; true</code></pre>
<p>특수문자를 제거할 때는 String.prototype.replace 메서드 사용</p>
<pre><code class="language-js">target.replace(/[^A-Za-z0-9]/gi, &#39;&#39;); // -&gt; abc123</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<28.Number & 29.Math & 30.Date>]]></title>
            <link>https://velog.io/@st_hwang/28.Number-29.Math-30.Date</link>
            <guid>https://velog.io/@st_hwang/28.Number-29.Math-30.Date</guid>
            <pubDate>Wed, 13 Jul 2022 02:46:54 GMT</pubDate>
            <description><![CDATA[<h1 id="28-number">28. Number</h1>
<p>표준 빌트인 객체 Number는 원시 타입인 숫자를 다룰 때 유용한 프로퍼티와 메서드를 제공함</p>
<h2 id="281-number-생성자-함수">28.1 Number 생성자 함수</h2>
<p>표준 빌트인 객체인 Number 객체는 생성자 함수 객체이므로, new 연산자와 함께 호출하여 Number 인스턴스를 생성할 수 있음</p>
<p>Number 생성자 함수에 인수를 전달하지 않고 new 연산자와 함께 호출하면 [[NumberData]] 내부 슬롯에 0을 할당한 Number 래퍼 객체 생성</p>
<pre><code class="language-js">const numObj = new Number();
console.log(numObj); // Number {[[PrimitiveValue]]: 0}</code></pre>
<p>[[PrimitiveValue]] 라는 접근할 수 없는 프로퍼티가 있는데 이는 [[NumberData]] 내부 슬롯을 가리킴 (ES5에서는 이렇게 불렀음)</p>
<p>Number 생성자 함수의 인수로 숫자를 전달하면서 new 연산자와 함께 호출하면 [[NumberData]] 내부 슬롯에 인수로 전달받은 숫자를 할당한 Number 래퍼 객체를 생성</p>
<pre><code class="language-js">const numObj = new Number(10);
console.log(numObj); // Number {[[PrimitiveValue]]: 10}</code></pre>
<p>Number 생성자 함수의 인수로 숫자가 아닌 값을 전달하면 인수를 숫자로 강제 변환한 후, [[NumberData]] 내부 슬롯에 변환된 숫자를 할당한 Number 래퍼 객체 생성. 숫자로 변환할 수 없으면 NaN 할당한 Number 래퍼 객체 생성</p>
<pre><code class="language-js">let numObj = new Number(&#39;10&#39;);
console.log(numObj); // {[[PrimitiveValue]]: 10}

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

// 불리언 타입 =&gt; 숫자 타입
Number(true); // -&gt; 1
Number(false); // -&gt; 0</code></pre>
<h2 id="282-number-프로퍼티">28.2 Number 프로퍼티</h2>
<h3 id="2821-numberepsilon-es6">28.2.1 Number.EPSILON (ES6)</h3>
<p>Number.EPSILON 은 1과 1보다 큰 숫자 중에서 가장 작은 숫자와의 차이와의 차이와 같음</p>
<p>부동소수점 산술 연산은 정확한 결과를 기대하기 어렵기 때문에, Number.EPSILON으로 부동소수점으로 인해 발생하는 오차를 해결하기 위해 사용</p>
<pre><code class="language-js">0.1 + 0.2 === 0.3; // false -&gt; 0.30000000000000004

function isEqual(a, b){
  // a와 b를 뺀 값의 절대값이 Number.EPSLION 보다 작으면 같은 수로 인정
  return Math.abs(a - b) &lt; Number.EPSLION;
}

isEqual(0.1 + 0.2, 0.3); // true </code></pre>
<h3 id="2822-numbermax_value">28.2.2 Number.MAX_VALUE</h3>
<p>Number.MAX_VALUE 는 자바스크립트에서 표현할 수 있는 가장 큰 양수 값으로 Number.MAX_VALUE 보다 큰 숫자는 Infinity</p>
<pre><code class="language-js">Number.MAX_VALUEl // -&gt; 1.7976931348623157e+308
Infinity &gt; Number.MAX_VALUE; // -&gt; true</code></pre>
<h3 id="2823-numbermin_value">28.2.3 Number.MIN_VALUE</h3>
<p>Number.MIN_VALUE 는 자바스크립트에서 표현할 수 있는 가장 작은 양수 값으로 Number.MIN_VALUE 보다 작은 숫자는 0</p>
<pre><code class="language-js">Number.MIN_VALUE; // -&gt; 5e-324
Number.MIN_VALUE &gt; 0; // -&gt; true</code></pre>
<h3 id="2824-numbermax_safe_integer">28.2.4 Number.MAX_SAFE_INTEGER</h3>
<p>Number.MAX_SAFE_INTEGER 는 자바스크립트에서 안전하게 표현할 수 있는 가장 큰 정수값</p>
<pre><code class="language-js">Number.MAX_SAFE_INTEGER; // -&gt; 9007199254740991</code></pre>
<h3 id="2825-numbermin_safe_integer">28.2.5 Number.MIN_SAFE_INTEGER</h3>
<p>Number.MIN_SAFE_INTEGER 는 자바스크립트에서 안전하게 표현할 수 있는 가장 작은 정수값</p>
<pre><code class="language-js">Number.MIN_SAFE_INTEGER; // -&gt; -9007199254740991</code></pre>
<h3 id="2826-numberpositive_infinity">28.2.6 Number.POSITIVE_INFINITY</h3>
<p>Number.POSITIVE_INFINITY 는 양의 무한대를 나타내는 숫자값 Infinity와 같음</p>
<pre><code class="language-js">Number.POSITIVE_INFINITY; // -&gt; Infinity</code></pre>
<h3 id="2827-numbernegative_infinity">28.2.7 Number.NEGATIVE_INFINITY</h3>
<p>Number.NEGATIVE_INFINITY 는 음의 무한대를 나타내는 숫자값 -Infinity와 같음</p>
<pre><code class="language-js">Number.NEGATIVE_INFINITY; // -&gt; -Infinity</code></pre>
<h3 id="2828-numbernan">28.2.8 Number.NaN</h3>
<p>Number.NaN 은 숫자가 아님(Not-a-Number) 를 나타내는 숫자값으로 Number.NaN은 window.NaN과 같음</p>
<pre><code class="language-js">Number.Nan; // -&gt; NaN</code></pre>
<h2 id="283-number-메서드">28.3 Number 메서드</h2>
<h3 id="2831-numberisfinite-es6">28.3.1 Number.isFinite (ES6)</h3>
<p><code>Number.isFinite</code> 정적 메서드는 <strong>인수로 전달한 숫자값이 정상적인 유한수, 즉 Infinity나 -Infinity가 아닌지 검사하고 그 결과를 불리언 값으로 반환</strong></p>
<pre><code class="language-js">// 인수가 정상적인 유한수면 true 반환
Number.isFinite(0); // -&gt; true
Number.isFinite(Number.MAX_VALUE); // -&gt; true
Number.isFinite(Number.MIN_VALUE); // -&gt; true

// 인수가 무한수이면 false 반환, 인수가 NaN이면 언제나 false 반환
Number.isFinite(Infinity); // -&gt; false
Number.isFinite(-Infinity); // -&gt; false
Number.isFinite(NaN); // -&gt; false</code></pre>
<p>빌트인 전역 함수 isFinite 는 전달받은 인수를 숫자로 암묵적 타입 변환하여 검사를 수행하나, Number.isFinite은 전달받은 인수를 암묵적 타입 변환하지 않고 숫자가 아닌 인수가 주어졌을 때 반환값은 언제나 false</p>
<pre><code class="language-js">// Number.isFinite 는 인수를 숫자로 암묵적 타입 변환하지 않음
Number.isFinite(null); // -&gt; false

// isFinte 는 인수를 숫자로 암묵적 타입 변환. null은 0으로 암묵적 타입 변환됨
isFinite(null); // -&gt; true</code></pre>
<h3 id="2832-numberisinteger-es6">28.3.2 Number.isInteger (ES6)</h3>
<p><code>Number.isInteger</code> 정적 메서드는 <strong>인수로 전달된 숫자값이 정수인지 검사하여 그 결과를 불리언 값으로 반환</strong>, 암묵적 타입 변환 안함</p>
<pre><code class="language-js">// 인수가 정수이면 true 반환
Number.isInteger(0); // -&gt; true
Number.isInteger(123); // -&gt; true
Number.isInteger(-123); // -&gt; true

// 암묵적 타입 변환 안함
Number.isInteger(&#39;123&#39;); // -&gt; false</code></pre>
<h3 id="2833-numberisnan-es6">28.3.3 Number.isNaN (ES6)</h3>
<p><code>Number.isNaN</code> 정적 메서드는 <strong>인수로 전달된 숫자값이 NaN인지 검사하여 그 결과를 불리언 값으로 반환</strong></p>
<pre><code class="language-js">// 인수가 NaN이면 true 반환
Number.isNaN(NaN); // -&gt; true</code></pre>
<p>빌트인 전역 함수 isNaN은 전달받은 인수를 숫자로 암묵적 타입 변환하여 검사를 수행하나, Number.isNaN 메서드는 전달받은 인수를 숫자로 암묵적 타입 변환하지 않고 숫자가 아닌 인수가 주어졌을 때 반환값은 언제나 false</p>
<pre><code class="language-js">// Number.isNaN은 인수를 숫자로 암묵적 타입 변환하지 않음
Number.isNaN(undefined); // -&gt; false

// isFinte는 인수를 숫자로 암묵적 타입 변환. undefined는 NaN으로 암묵적 타입 변환됨
isNaN(undefined); // -&gt; true</code></pre>
<h3 id="2834-numberissafeinteger-es6">28.3.4 Number.isSafeInteger (ES6)</h3>
<p><code>Number.isSafeInteger</code> 정적 메서드는 <strong>인수로 전달된 숫자값이 안전한 정수인지 검사하여 그 결과를 불리언 값으로 반환</strong>. 안전한 정수값은 -(253 -1)과 253 - 1 사이의 정수값. 인수를 숫자로 암묵적 타입 변환하지 않음</p>
<pre><code class="language-js">Number.isSafeInteger(0); // -&gt; true
Number.isSafeInteger(1000000000000000); // -&gt; true
Number.isSafeInteger(10000000000000000); // -&gt; false
Number.isSafeInteger(0.5); // false
Number.isSafeInteger(&#39;123&#39;); // false</code></pre>
<h3 id="2835-numberprototypetoexponential">28.3.5 Number.prototype.toExponential</h3>
<p><code>toExponential</code> 메서드는 <strong>숫자를 지수 표기법으로 변환하여 문자열로 반환</strong>. 지수 표기법이란 매우 크거나 작은 숫자를 표현할 때 주로 사용하며 e(Exponent) 앞에 있는 숫자에 10의 n승을 곱하는 형식으로 수를 나타내는 방식. 인수로 소수점 이하로 표현할 자릿수 전달할 수 있음</p>
<pre><code class="language-js">(77.1234).toExponential(); // -&gt; &#39;7.71234e+1&#39;
(77.1234).toExponential(4); // -&gt; &#39;7.7123e+1&#39;</code></pre>
<p>숫자 리터럴과 함께 Number 프로토타입 메서드를 사용할 경우 에러가 발생함</p>
<pre><code class="language-js">77.toExponential(); // SyntaxError: Invalid or Unexpected token</code></pre>
<p>자바스크립트 엔진은 숫자 뒤의 <code>.</code> 을 부동 소수점 숫자의 소수 구분 기호로 해석하기 때문에 77.toExponential()에서 77은 래퍼 객체가 되어 toExponential을 프로퍼티로 해석할 수 없으므로 에러 발생.</p>
<pre><code class="language-js">77.1234.toExponential(); // -&gt; &#39;7.71234e+1&#39;</code></pre>
<p>숫자의 소수점은 하나만 존재하므로 두 번째 <code>.</code> 은 프로퍼티 접근 연산자로 해석. 숫자 리터럴과 함께 메서드를 사용할 경우 혼란을 방지하기 위해 그룹 연산자를 사용할 것을 권장</p>
<h3 id="2836-numberprototypetofixed">28.3.6 Number.prototype.toFixed</h3>
<p><code>toFixed</code> 메서드는 <strong>숫자를 반올림하여 문자열로 반환</strong>. 반올림하는 소수점 이하 자릿수를 나타내는 0 ~ 20 사이의 정수값을 인수로 전달할 수 있고, 인수 생략하면 기본값 0이 지정됨</p>
<pre><code class="language-js">(12345.6789).toFixed(); // -&gt; &#39;12346&#39;
(12345.6789).toFixed(1); // -&gt; &#39;12345.7&#39; </code></pre>
<h3 id="2837-numberprptotypetoprecision">28.3.7 Number.prptotype.toPrecision</h3>
<p><code>toPrecision</code> 메서드는 <strong>인수로 전달받은 전체 자릿수까지 유효하도록 나머지 자릿수를 반올림하여 문자열로 반환</strong>. 인수로 전달받은 전체 자릿수로 표현할 수 없는 경우 지수 표기법으로 결과 반환. 전체 자릿수를 나타내는 0 ~ 21 사이의 정수값을 인수로 전달할 수 있고, 생략하면 기본값 0 지정</p>
<pre><code class="language-js">(12345.6789).toPrecision(); // -&gt; &#39;12345.6789&#39;
(12345.6789).toPrecision(1); // -&gt; &#39;1e+4&#39;
(12345.6789).toPrecision(6); // -&gt; &#39;12345.7&#39;</code></pre>
<h3 id="2838-numberprototypetostring">28.3.8 Number.prototype.toString</h3>
<p><code>toString</code> 메서드는 <strong>숫자를 문자열로 변환하여 반환</strong>. 진법을 나타내는 2~36 사이의 정수값을 인수로 전달할 수 있고, 생략하면 기본값 10진법 지정</p>
<pre><code class="language-js">(10).toString(); // -&gt; &#39;10&#39;
(16).toString(2); // -&gt; &#39;10000&#39;</code></pre>
<h1 id="29-math">29. Math</h1>
<p>표준 빌트인 객체인 Math는 수학적인 상수와 함수를 위한 프로퍼티와 메서드를 제공. Math는 생성자 함수가 아니기 때문에 정적 프로퍼티와 정적 메서드만 제공함</p>
<h2 id="291-math-프로퍼티">29.1 Math 프로퍼티</h2>
<h3 id="2911-mathpi">29.1.1 Math.PI</h3>
<p>원주율 PI값(π = 3.14...) 반환</p>
<pre><code class="language-js">Math.PI; // -&gt; 3.141592653589793</code></pre>
<h2 id="292-math-메서드">29.2 Math 메서드</h2>
<h3 id="2921-mathabs">29.2.1 Math.abs</h3>
<p><code>Math.abs</code> 메서드는 <strong>인수로 전달된 숫자의 절대값을 반환</strong>. 절대값은 반드시 0 또는 양수여야함</p>
<pre><code class="language-js">Math.abs(-1); // -&gt; 1
Math.abs(&#39;&#39;); // -&gt; 0
Math.abs(null); // -&gt; 0
Math.abs(undefined); // -&gt; NaN</code></pre>
<h3 id="2922-mathround">29.2.2 Math.round</h3>
<p><code>Math.round</code> 메서드는 <strong>인수로 전달된 숫자의 소수점 이하를 반올림한 정수 반환</strong></p>
<pre><code class="language-js">Math.round(1.4); // -&gt; 1
Math.round(-1.4); // -&gt; -1
Math.round(); // NaN</code></pre>
<h3 id="2923-mathceil">29.2.3 Math.ceil</h3>
<p><code>Math.ceil</code> 메서드는 <strong>인수로 전달된 숫자의 소수점 이하를 올림한 정수 반환</strong></p>
<pre><code class="language-js">Math.ceil(1.4); // -&gt; 2
Math.ceil(-1.4); // -&gt; -1</code></pre>
<h3 id="2924-mathfloor">29.2.4 Math.floor</h3>
<p><code>Math.floor</code> 메서드는 <strong>인수로 전달된 숫자의 소수점 이하를 내림한 정수 반환</strong></p>
<pre><code class="language-js">Math.floor(1.9); // -&gt; 1
Math.floor(-1.9); // -&gt; -2</code></pre>
<h3 id="2925-mathsqrt">29.2.5 Math.sqrt</h3>
<p><code>Math.sqrt</code> 메서드는 <strong>인수로 전달된 숫자의 제곱근 반환</strong></p>
<pre><code class="language-Js">Math.sqrt(9); // -&gt; 3
Math.sqrt(-9); // -&gt; NaN
Math.sqrt(0); // -&gt; 0</code></pre>
<h3 id="2926-mathrandom">29.2.6 Math.random</h3>
<p><code>Math.random</code> 메서드는 <strong>임의의 난수(랜덤한 숫자) 반환</strong>. Math.random 메서드가 반환한 난수는 0에서 1 미만의 실수(0포함 1제외)</p>
<pre><code class="language-js">const random = Math.floor((Math.random() * 10) + 1);
console.log(random); // 1에서 10 범위의 정수</code></pre>
<h3 id="2927-mathpow">29.2.7 Math.pow</h3>
<p><code>Math.pow</code> 메서드는 <strong>첫 번째 인수를 밑으로, 두 번째 인수를 지수로 거듭제곱한 결과 반환</strong></p>
<pre><code class="language-js">Math.pow(2, 8); // -&gt; 256
Math.pow(2, -1); // -&gt; 0.5
Math.pow(2); // -&gt; NaN</code></pre>
<p>Math.pow 메서드 대신 ES7에서 도입된 지수 연산자를 사용하면 가독성이 더 좋음</p>
<pre><code class="language-js">2 ** 2 ** 2; // -&gt; 16
Math.pow(Math.pow(2, 2), 2); // -&gt; 16</code></pre>
<h3 id="2928-mathmax">29.2.8 Math.max</h3>
<p>Math.max 메서드는 전달받은 인수 중에서 가장 큰 수 반환. 인수가 전달되지 않으면 -Infinity 반환</p>
<pre><code class="language-js">Math.max(1); // -&gt; 1
Math.max(1, 2); // -&gt; 2
Math.max(); // -&gt; -Infinity</code></pre>
<p>배열을 인수로 전달받아 배열의 요소 중에서 최대값을 구하려면 Function.prototype.apply 메서드 또는 스프레드 문법 사용</p>
<pre><code class="language-js">Math.max.apply(null, [1, 2, 3]); // -&gt; 3
Math.max(...[1, 2, 3]); // -&gt; 3</code></pre>
<h3 id="2929-mathmin">29.2.9 Math.min</h3>
<p>Math.min 메서드는 전달받은 인수 중에서 가장 작은 수 반환. 인수가 전달되지 않으면 Infinity 반환</p>
<pre><code class="language-js">Math.min(1); // -&gt; 1
Math.min(1, 2); // -&gt; 1
Math.min(); // -&gt; Infinity</code></pre>
<p>배열을 인수로 전달받아 배열의 요소 중에서 최소값을 구하려면 Function.prototype.apply 메서드 또는 스프레드 문법 사용</p>
<pre><code class="language-js">Math.min.apply(null, [1, 2, 3]); // -&gt; 1
Math.min(...[1, 2, 3]); // -&gt; 1</code></pre>
<h1 id="30-date">30. Date</h1>
<p>표준 빌트인 객체인 Date는 날짜와 시간을 위한 메서드를 제공하는 빌트인 객체이면서 생성자 함수.</p>
<p>UTC(협정 세계시) : 국제 표준시.  GMT(그리니치 평균시)와는 일상에서 혼용되어 사용하나 기술적인 표기에서는 UTC 사용</p>
<p>KST(한국 표준시): UTC + 9시간</p>
<h2 id="301-date-생성자-함수">30.1 Date 생성자 함수</h2>
<p>Date는 생성자 함수이며 Date 생성자 함수로 생성한 Date 객체는 내부적으로 날짜와 시간을 나타내는 정수값을 가짐. 이 값은 1970년 1월 1일 0시를 나타내는 Date 객체를 0으로 두고 계산된 값. 현재 날짜와 시간이 아닌 다른 날짜와 시간을 다루고 싶은 경우 Date 생성자 함수에 명시적으로 해당 날짜와 시간 정보를 인수로 지정</p>
<p>Date 생성자 함수로 객체를 생성하는 방법은 4가지가 있음</p>
<h3 id="3011-new-date">30.1.1 new Date()</h3>
<p><code>Date 생성자 함수</code>를 <strong><code>인수 없이</code> new 연산자와 함께 호출하면 현재 날짜와 시간을 가지는 Date 객체를 반환</strong>. Date 객체는 내부적으로 날짜와 시간을 나타내는 정수값을 갖지만 Date 객체를 콘솔에 출력하면 기본적으로 날짜와 시간 정보를 출력</p>
<pre><code class="language-js">new Date(); // -&gt; Mon Jun 27 2022 17:38:23 GMT+0900 (한국 표준시)</code></pre>
<p>Date 생성자 함수를 new 연산자 없이 호출하면 Date 객체를 반환하지 않고 날짜와 시간 정보를 나타내는 문자열 반환</p>
<pre><code class="language-js">Date(); // -&gt; &#39;Mon Jun 27 2022 17:38:23 GMT+0900 (한국 표준시)&#39;</code></pre>
<h3 id="3012-new-datemilliseconds">30.1.2 new Date(milliseconds)</h3>
<p><code>Date 생성자 함수</code>에 <strong><code>숫자 타입의 밀리초</code>를 인수로 전달하면 1970년 1월 1일 0시를 기점으로 인수로 전달된 밀리초만큼 경과한 시간을 나타내는 Date 객체 반환</strong></p>
<pre><code class="language-js">new Date(0); // -&gt; Thu Jan 01 1970 09:00:00 GMT+0900 (한국 표준시)
new Date(86400000); // -&gt; Fri Jan 02 1970 09:00:00 GMT+0900 (한국 표준시)</code></pre>
<h3 id="3013-new-datedatestring">30.1.3 new Date(dateString)</h3>
<p><code>Date 생성자 함수</code>에 <strong><code>날짜와 시간을 나타내는 문자열</code>을 인수로 전달하면 지정된 날짜와 시간을 나타내는 Date 객체 반환</strong>. 이 때 인수로 전달한 문자열은 Date.parse 메서드에 의해 해석 가능한 형식이어야 함</p>
<pre><code class="language-js">new Date(&#39;May 26, 2020 10:00:00&#39;); // -&gt; Tue May 26 2020 10:00:00 GMT+0900 (한국 표준시)
new Date(&#39;2020/03/26/10:00:00&#39;); // -&gt; Thu Mar 26 2020 10:00:00 GMT+0900 (한국 표준시)</code></pre>
<h3 id="3014-new-dateyear-month-day-hour-minute-second-millisecond">30.1.4 new Date(year, month[, day, hour, minute, second, millisecond])</h3>
<p><code>Date 생성자 함수</code>에 <strong><code>연, 월, 일, 시, 분, 초, 밀리초를 의미하는 숫자</code>를 인수로 전달하면 지정된 날짜와 시간을 나타내는 Date 객체 반환</strong>. 연, 월은 반드시 지정해야 하고, 지정하지 않은 옵션 정보는 0 또는 1로 초기화</p>
<table>
<thead>
<tr>
<th align="center">인수</th>
<th align="center">내용</th>
</tr>
</thead>
<tbody><tr>
<td align="center">year</td>
<td align="center">연을 나타내는 1900년 이후의 정수. 0부터 99는 1900부터 1999로 처리됨</td>
</tr>
<tr>
<td align="center">month</td>
<td align="center">월을 나타내는 0 ~ 11까지의 정수(주의: 0부터 시작, 0 = 1월)</td>
</tr>
<tr>
<td align="center">day</td>
<td align="center">일을 나타내는 1 ~ 31까지의 정수</td>
</tr>
<tr>
<td align="center">hour</td>
<td align="center">시를 나타내는 0 ~ 23까지의 정수</td>
</tr>
<tr>
<td align="center">minute</td>
<td align="center">분을 나타내는 0 ~ 59까지의 정수</td>
</tr>
<tr>
<td align="center">second</td>
<td align="center">초를 나타내는 0 ~ 59까지의 정수</td>
</tr>
<tr>
<td align="center">millisecond</td>
<td align="center">밀리초를 나타내는 0 ~ 999까지의 정수</td>
</tr>
</tbody></table>
<p>연, 월을 지정하지 않은 경우 1970년 1월 1일 0시를 나타내는 Date 객체 반환</p>
<pre><code class="language-js">new Date(2020, 2); // -&gt; Sun Mar 01 2020 00:00:00 GMT+0900 (한국 표준시)
new Date(2020, 2, 26, 10, 00, 00, 0); // -&gt; Thu Mar 26 2020 10:00:00 GMT+0900 (한국 표준시)</code></pre>
<h2 id="302-date-메서드">30.2 Date 메서드</h2>
<h3 id="3021-datenow">30.2.1 Date.now</h3>
<p>1970년 1월 1일 00:00:00(UTC)을 기점으로 현재 시간까지 경과한 밀리초를 숫자로 반환</p>
<pre><code class="language-js">Date.now(); // -&gt; 1656331097888</code></pre>
<h3 id="3022-dateparse">30.2.2 Date.parse</h3>
<p>1970년 1월 1일 00:00:00(UTC)을 기점으로 인수로 전달된 지정 시간(new Date(dateString)의 동일한 형식)까지의 밀리초를 숫자로 반환</p>
<pre><code class="language-js">// UTC
Date.parse(&#39;Jan 2, 1970 00:00:00 UTC&#39;); // -&gt; 86400000
// KTC
Date.parse(&#39;Jan 2, 1970 00:00:00&#39;); // -&gt; 54000000</code></pre>
<h3 id="3023-dateutc">30.2.3 Date.UTC</h3>
<p>1970년 1월 1일 00:00:00(UTC)을 기점으로 인수로 전달된 지정 시간까지의 밀리초를 숫자로 반환. new Date(year, month[, day, hour, minute, second, millisecond]) 형식</p>
<pre><code class="language-js">Date.UTC(1970, 0, 2); // -&gt; 86400000
Date.UTC(&#39;1970/1/2&#39;); // -&gt; NaN</code></pre>
<h3 id="3024-dateprototypegetfullyear">30.2.4 Date.prototype.getFullYear</h3>
<p><strong>Date 객체의 <code>연도</code>를 나타내는 정수 반환</strong></p>
<pre><code class="language-js">new Date(&#39;2020/07/24&#39;).getFullYear(); // -&gt; 2020</code></pre>
<h3 id="3025-dateprototypesetfullyear">30.2.5 Date.prototype.setFullYear</h3>
<p>Date 객체에 연도를 나타내는 정수 설정. 연도 이외에 옵션으로 월, 일 설정 가능</p>
<pre><code class="language-js">const today = new Date();

// 년도 지정
today.setFullYear(2000);
today.getFullTear(); // -&gt; 2000

// 년도/월/일 지정
today.setFullYear(1900, 0, 1);
today.getFullYear(); // -&gt; 1900</code></pre>
<h3 id="3026-dateprototypegetmonth">30.2.6 Date.prototype.getMonth</h3>
<p>Date 객체의 월을 나타내는 0 ~ 11의 정수를 반환. 1월은 0, 12월은 11</p>
<pre><code class="language-js">new Date(&#39;2020/07/24&#39;).getMonth(); // -&gt; 6</code></pre>
<h3 id="3027-dateprototypesetmonth">30.2.7 Date.prototype.setMonth</h3>
<p>Date 객체에 월을 나타내는 0 ~ 11의 정수를 설정. 월 이외에 옵션으로 일도 설정 가능</p>
<pre><code class="language-js">const today = new Date();

// 월 지정
today.setMonth(0); // 1월
today.getMonth(); // -&gt; 0

// 월/일 지정
today.setMonth(11, 1); // 12월 1일
today.getMonth(); // -&gt; 11</code></pre>
<h3 id="3028-dateprototypegetdate">30.2.8 Date.prototype.getDate</h3>
<p>Date 객체의 날짜(1 ~ 31)를 나타내는 정수를 반환</p>
<pre><code class="language-js">new Date(&#39;2020/07/24&#39;).getDate(); // -&gt; 24</code></pre>
<h3 id="3029-dateprototypesetdate">30.2.9 Date.prototype.setDate</h3>
<p>Date 객체에 날짜(1 ~ 31)를 나타내는 정수를 반환</p>
<pre><code class="language-js">const today = new Date();

// 날짜 지정
today.setDate(1);
today.getDate(); // -&gt; 1</code></pre>
<h3 id="30210-dateprototypegetday">30.2.10 Date.prototype.getDay</h3>
<p>Date 객체의 요일(0 ~ 6)를 나타내는 정수 반환. 일요일이 0, 토요일이 6</p>
<pre><code class="language-js">new Date(&#39;2020/07/24&#39;).getDay(); // -&gt; 5</code></pre>
<h3 id="30211-dateprototypegethours">30.2.11 Date.prototype.getHours</h3>
<p>Date 객체의 시간(0 ~ 23)을 나타내는 정수 반환</p>
<pre><code class="language-js">new Date(&#39;2020/07/24/12:00&#39;).getHours(); // -&gt; 12</code></pre>
<h3 id="30212-dateprototypesethours">30.2.12 Date.prototype.setHours</h3>
<p>Date 객체에 시간(0 ~ 23)을 나타내는 정수 설정. 시간 이외에 옵션으로 분, 초, 밀리초도 설정할 수 있음</p>
<pre><code class="language-js">const today = new Date();

// 시간 지정
today.setHours(7);
today.getHours(); // -&gt; 7

// 시간/분/초/밀리초 지정
today.setHours(0, 0, 0, 0); // 00:00:00:00
today.getHours(); // -&gt; 0</code></pre>
<h3 id="30213-dateprototypegetminutes">30.2.13 Date.prototype.getMinutes</h3>
<p>Date 객체의 분(0 ~ 59)을 나타내는 정수 반환</p>
<pre><code class="language-js">new Date(&#39;2020/07/24/12:30&#39;).getMinutes(); // -&gt; 30</code></pre>
<h3 id="30214-dateprototypesetminutes">30.2.14 Date.prototype.setMinutes</h3>
<p>Date 객체에 분(0 ~ 59)을 나타내는 정수 설정. 분 이외에 옵션으로 초, 밀리초도 설정 가능</p>
<pre><code class="language-Js">const today = new Date();

// 분 지정
today.setMinutes(50);
today.getMinutes(); // -&gt; 50

// 분/초/밀리초 지정
today.setMinutes(5, 10, 999); // HH:05:10:999
today.getMinutes(); // 5</code></pre>
<h3 id="30215-dateprototypegetseconds">30.2.15 Date.prototype.getSeconds</h3>
<p>Date 객체에 초(0 ~ 59)를 나타내는 정수 반환. </p>
<h3 id="302-16-dateprototypesetseconds">30.2. 16 Date.prototype.setSeconds</h3>
<p>Date 객체에 초(0 ~ 59)를 나타내는 정수 설정. 초 이외에 옵션으로 밀리초도 설정 가능</p>
<h3 id="30217-dateprototypegetmilliseconds">30.2.17 Date.prototype.getMilliseconds</h3>
<p>Date 객체의 밀리초(0 ~ 999)를 나타내는 정수 반환</p>
<h3 id="30218-dateprototypesetmilliseconds">30.2.18 Date.prototype.setMilliseconds</h3>
<p>Date 객체에 밀리초(0 ~ 999)를 나타내는 정수 설정</p>
<h3 id="30219-dateprototypegettime">30.2.19 Date.prototype.getTime</h3>
<p>1970년 1월 1일 0시를 기점으로 Date 객체의 시간까지 경과된 밀리초 반환</p>
<pre><code class="language-js">new Date(&#39;2020/07/24/12:30&#39;).getTime(); // -&gt; 1595561400000</code></pre>
<h3 id="30220-dateprototypesettime">30.2.20 Date.prototype.setTime</h3>
<p>Date 객체에 1970년 1월 1일 0시를 기점으로 경과된 밀리초 설정</p>
<h3 id="30221-dateprototypegettimezoneoffset">30.2.21 Date.prototype.getTimezoneOffset</h3>
<p>UTC와 Date 객체에 지정된 locale 시간과의 차이를 분 단위로 반환. KST - 9H = UTC</p>
<pre><code class="language-js">const today = new Date(); // today의 지정 로캘은 KST

today.getTimezoneOffset(); // -540</code></pre>
<h3 id="30222-dateprototypetodatestring">30.2.22 Date.prototype.toDateString</h3>
<p>사람이 읽을 수 있는 형식의 문자열로 Date 객체의 날짜를 반환</p>
<pre><code class="language-js">const today = new Date(&#39;2020/7/24/12:30&#39;);
today.toString(); // -&gt; &#39;Fri Jul 24 2020 12:30:00 GMT+0900 (한국 표준시)&#39;
today.toTimeString(); // -&gt; &#39;12:30:00 GMT+0900 (한국 표준시)&#39;</code></pre>
<h3 id="30223-dateprototypetotimestring">30.2.23 Date.prototype.toTimeString</h3>
<p>사람이 읽을 수 있는 형식으로 Date 객체의 시간을 표현한 문자열 반환</p>
<h3 id="30224-dateprototypetoisostring">30.2.24 Date.prototype.toISOString</h3>
<p>ISO 8601 형식으로 Date 객체의 날짜와 시간을 표현한 문자열 반환</p>
<pre><code class="language-js">const today = new Date(&#39;2020/7/24/12:30&#39;);
today.toString(); // -&gt; &#39;Fri Jul 24 2020 12:30:00 GMT+0900 (한국 표준시)&#39;
today.toISOString(); // -&gt; &#39;2020-07-24T03:30:00.000Z&#39;</code></pre>
<h3 id="30225-dateprototypetolocalestring">30.2.25 Date.prototype.toLocaleString</h3>
<p>인수로 전달한 로캘을 기준으로 Date 객체의 날짜와 시간을 표현한 문자열 반환. 인수를 생략한 경우 브라우저가 동작 중인 시스템의 로캘 적용</p>
<pre><code class="language-js">const today = new Date(&#39;2020/7/24/12:30&#39;);
today.toString(); // -&gt; &#39;Fri Jul 24 2020 12:30:00 GMT+0900 (한국 표준시)&#39;
today.toLocaleString(); // -&gt; &#39;2020. 7. 24. 오후 12:30:00&#39;
today.toLocaleString(&#39;ko-KR&#39;); // -&gt; &#39;2020. 7. 24. 오후 12:30:00&#39;
today.toLocaleString(&#39;en-US&#39;); // -&gt; &#39;7/24/2020, 12:30:00 PM&#39;</code></pre>
<h3 id="30226-dateprototypetolocaletimestring">30.2.26 Date.prototype.toLocaleTimeString</h3>
<p>인수로 전달한 로캘을 기준으로 Date 객체의 시간을 표현한 문자열을 반환. 인수를 생략한 경우 브라우저가 동작 중인 시스템의 로캘 적용</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<27. 배열>]]></title>
            <link>https://velog.io/@st_hwang/3l8rayym</link>
            <guid>https://velog.io/@st_hwang/3l8rayym</guid>
            <pubDate>Wed, 13 Jul 2022 02:43:05 GMT</pubDate>
            <description><![CDATA[<h1 id="27-배열">27. 배열</h1>
<h2 id="271-배열이란">27.1 배열이란?</h2>
<p><strong>배열 (array)</strong> : 여러 개의 값을 순차적으로 나열한 자료구조</p>
<p><strong>요소 (element)</strong> : 배열이 가지고 있는 값. 자바스크립트의 모든 값은 배열의 요소가 될 수 있음(원시값, 객체, 함수, 배열 등)</p>
<p><strong>인덱스 (index)</strong> : 배열의 요소의 위치를 나타내는 정수. 0부터 시작</p>
<p>요소에 접근할 때는 <strong>대괄호 표기법</strong>을 사용함.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/fa17010d-51cd-4b32-84c3-377b25cbd506/image.png" alt=""></p>
<p><strong>length 프로퍼티</strong> : 배열 요소의 개수(배열의 길이)를 나타내는 프로퍼티</p>
<p>배열은 인덱스와 length 프로퍼티를 갖기 때문에 반복문을 쓰면 순차적으로 요소에 접근할 수 있음</p>
<pre><code class="language-js">// 배열의 순회
for (let i=0; i &lt; arr.length; i++){
  console.log(arr[i]); // &#39;apple&#39; &#39;banana&#39; &#39;orange&#39;
}</code></pre>
<h2 id="272-자바스크립트-배열은-배열이-아니다">27.2 자바스크립트 배열은 배열이 아니다</h2>
<p><strong>자바스크립트에서의 배열은 사실 좀 특이한 객체임. 배열 아님. 아무튼 아님.</strong></p>
<p>자바스크립트에서는 배열이라는 타입이 존재하지 않으며, 배열은 객체 타입. 배열의 프로포타입 객체 = Array.prototype</p>
<pre><code class="language-js">typeof arr // -&gt; object</code></pre>
<p>일반적인 자료구조에서 말하는 배열은 밀집 배열</p>
<blockquote>
<p>밀집 배열 : 배열의 요소가 하나의 데이터 타입으로 통일되어 있으며 서로 연속적으로 인접해 있는 배열</p>
<p>밀집 배열은 각 요소가 동일한 데이터 크기를 가지고, 빈틈없이 연속적으로 이어져 있어 인덱스를 통해 단 한번의 연산으로 임의의 요소에 접근할 수 있음 (O(1))</p>
<p>요소의 접근이 효율적이고, 고속으로 동작</p>
<p>배열에 요소를 삽입하거나 삭제하는 경우 배열의 요소를 이동시켜야 하는 단점</p>
</blockquote>
<p>자바스크립트의 배열은 <strong>일반적인 배열의 동작을 흉내 낸 특수한 <code>객체</code></strong>. 자바스크립트는 문법적으로 <strong>희소 배열</strong>을 허용</p>
<blockquote>
<p><strong>희소 배열</strong> : 배열의 요소가 연속적으로 이어져 있지 않고, 일부가 비어있는 배열</p>
<p>자바스크립트 배열은 인덱스를 나타내는 문자열을 <code>프로퍼티</code>로 가지며, length 프로퍼티를 가지는 특수한 객체. 자바스크립트 배열의 요소는 프로퍼티(인덱스)의 값</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/57727fb8-c08e-40f6-9972-975d01b2cf17/image.png" alt=""></p>
<ul>
<li><p>자바스크립트 배열은 해시 테이블로 구현된 객체이므로 <strong>인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느림</strong></p>
</li>
<li><p><strong>특정 요소를 검색하거나 요소를 삽입 또는 삭제하는 경우 일반적인 배열보다 빠름</strong></p>
</li>
<li><p>인덱스로 배열 요소에 접근할 때 일반적인 배열보다 느릴 수 밖에 없는 구조적 단점을 보완하기 위해 배열을 일반 객체와 구별하여 좀 더 배열처럼 동작하도록 최적화하여 구현</p>
<p>=&gt; <strong>배열이 일반 객체보다 약 2배 정도 빠름</strong></p>
</li>
</ul>
<p><strong>배열과 일반 객체의 차이</strong></p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center">객체</th>
<th align="center">배열</th>
</tr>
</thead>
<tbody><tr>
<td align="center">구조</td>
<td align="center">프로퍼티키와 프로퍼티 값</td>
<td align="center">인덱스와 요소</td>
</tr>
<tr>
<td align="center">값의 참조</td>
<td align="center">프로퍼티 키</td>
<td align="center">인덱스</td>
</tr>
<tr>
<td align="center">값의 순서</td>
<td align="center">X</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center">length 프로퍼티</td>
<td align="center">X</td>
<td align="center">O</td>
</tr>
</tbody></table>
<h2 id="273-length-프로퍼티와-희소-배열">27.3 length 프로퍼티와 희소 배열</h2>
<p><strong>length 프로퍼티</strong> : 요소의 개수(배열의 길이). 빈 배열의 경우 length 프로퍼티는 0. </p>
<p>length 프로퍼티 값은 배열에 요소를 추가하거나 삭제하면 자동 갱신됨</p>
<p>length 프로퍼티 값은 요소의 개수를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당할 수도 있음</p>
<p>현재 length 프로퍼티 값보다 작은 숫자 값을 할당하면 배열의 길이가 줄어듦</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/f72d6acd-1661-4ba2-9c06-e2c9c7ea44e9/image.png" alt=""></p>
<p>현재 length 값보다 큰 숫자 값을 할당하면, length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지는 않음</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/3b9dce58-13c2-4970-8061-9c84decbf99d/image.png" alt=""></p>
<p>위 예제의 emnty * 2 는 실제로 추가된 배열의 요소가 아니고, arr[1], arr[2]에는 값이 존재하지 않음 ( = undefined)</p>
<p>일반적인 배열의 length는 배열 요소의 개수(배열의 길이)와 언제나 일치하지만, </p>
<p>희소 배열은 length와 배열 요소의 개수가 일치하지 않으며, 항상 length가 실제 요소 개수보다 큼</p>
<p>배열을 생성할 경우에는 희소 배열을 생성하지 않도록 주의 필요. </p>
<p><strong>배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선</strong></p>
<h2 id="274-배열-생성">27.4 배열 생성</h2>
<p>배열을 생성하는 방법</p>
<ul>
<li>배열 리터럴</li>
<li>Array 생성자 함수</li>
<li>Array.of 메서드 (ES6)</li>
<li>Array.from 메서드 (ES6)</li>
</ul>
<h3 id="2741-배열-리터럴">27.4.1 배열 리터럴</h3>
<p>배열 리터럴은 0개 이상의 요소를 쉼표로 구분하여 대괄호( [ ] )로 묶는다. 배열 리터럴은 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재</p>
<pre><code class="language-js">const arr = [1, 2, 3];
console.log(arr.length); // 0</code></pre>
<p>배열 리터럴에 요소를 하나도 추가하지 않으면 배열의 길이, 즉 length 프로퍼티 값이 0인 배열이 됨</p>
<pre><code class="language-js">const arr = [];
console.log(arr.length); // 0</code></pre>
<p>배열 리터럴에 요소를 생략하면 희소 배열 생성</p>
<pre><code class="language-Js">const arr = [1, , 3]; // 희소 배열
// 희소 배열의 length는 배열의 실제 요소 개수보다 언제나 크다
console.log(arr.length); // 3
console.log(arr);              // [1, empty, 3]
console.log(arr[1]);          // undefined</code></pre>
<h3 id="2742-array-생성자-함수">27.4.2 Array 생성자 함수</h3>
<p>Array 생성자 함수는 전달된 인수의 개수에 따라 다르게 동작하므로 주의가 필요</p>
<ul>
<li><p>전달된 인수가 1개이고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성 (희소배열). length</p>
<pre><code class="language-js">  const arr = new Array(10);

  console.log(arr); // [empty * 10]
  console.log(arr.length); // 10

  console.log(Object.getOwnPropertyDescriptors(arr));
  /*
  {
      length: {value: 10, writable: true, enumerable: false, configurable: false}
  } 
  */</code></pre>
</li>
<li><p>전달된 인수가 없는 경우 빈 배열 생성. 배열 리터럴 [] 과 같음</p>
<pre><code class="language-js">  new Araay(); // -&gt; []</code></pre>
</li>
<li><p>전달된 인수가 2개이상이거나 숫자가 아닌 경우 인수를 요소로 갖는 배열 생성</p>
<pre><code class="language-js">  // 전달된 인수가 2개 이상이면 인수를 요소로 갖는 배열 생성
  new Array(1, 2, 3); // -&gt; [1, 2, 3]

  // 전달된 인수가 1개지만 숫자가 아니면 인수를 요소로 갖는 배열 생성
  new Array({}); // -&gt; [{}]</code></pre>
</li>
</ul>
<p>Array 생성자 함수는 new 연산자 없이 호출해도 배열을 생성하는 생성자 함수로 동작 (Array 생성자 함수 내부에서 new.target 을 확인하기 때문)</p>
<pre><code class="language-js">Array(1, 2, 3); // -&lt; [1, 2, 3]</code></pre>
<h3 id="2743-arrayof-es6">27.4.3 Array.of (ES6)</h3>
<p>Array.of 메서드는 전달된 인수를 요소로 갖는 배열을 생성. Array 생성자 함수와 다르게 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열 생성함</p>
<pre><code class="language-Js">Array.of(1); // -&gt; [1]
Array.of(1, 2, 3); // -&gt; [1, 2, 3]
Array.of(&#39;string&#39;); // -&gt; [&#39;string&#39;]</code></pre>
<h3 id="2744-arrayfrom-es6">27.4.4 Array.from (ES6)</h3>
<p>Array.from 메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환</p>
<pre><code class="language-js">// 유사 배열 객체를 변환하여 배열 생성
Array.from({ length:2, 0: &#39;a&#39;, 1: &#39;b&#39;}); // -&gt; [&#39;a&#39;, &#39;b&#39;]

// 이터러블을 변환하여 배열 생성
Array.from(&#39;Hello&#39;); // -&gt; [&#39;H&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]</code></pre>
<p>두 번째 인수로 전달한 콜백 함수에 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하면서 호출하고, 콜백 함수의 반환값으로 구성된 배열 반환</p>
<pre><code class="language-js">// Array.from에 length만 존재하는 유사 배열 객체를 전달하면 undefined를 요소로 채움
Array.from({ length: 3 }); // -&gt; [undefined, undefined, undefined]

// Array.from은 두 번째 인수로 전달한 콜백 함수의 반환값으로 구성된 배열 반환
Array.from({ length: 3 }, (v, i) =&gt; i ); // [0, 1, 2]</code></pre>
<h2 id="275-배열-요소의-참조">27.5 배열 요소의 참조</h2>
<p>배열 요소를 참조할 때에는 대괄호 표기법( [ ] ) 사용. 대괄호 안에는 인덱스 기입. </p>
<p>존재하지 않는 요소에 접근하면 undefined 반환 =&gt; 배열이 <strong>인덱스를 나타내는 문자열을 프로퍼티로 갖는 객체</strong>이기 때문</p>
<pre><code class="language-js">const arr = [1, 2]

console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // undefined</code></pre>
<h2 id="276-배열-요소의-추가와-갱신">27.6 배열 요소의 추가와 갱신</h2>
<p>객체에 프로퍼티를 동적으로 추가할 수 있는 것처럼 배열에도 요소를 동적으로 추가할 수 있음</p>
<p>존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가되고, length 프로퍼티 값은 자동으로 갱신</p>
<pre><code class="language-js">const arr = [0];

// 배열 요소의 추가
arr[1] = 1;
console.log(arr); // [0, 1]
console.log(arr.length); // 2</code></pre>
<p>현재 배열의 length 프로퍼티 값보다 큰 인덱스로 새로운 요소를 추가하면 희소 배열이 됨</p>
<p>이 때 인덱스로 요소에 접근하여 명시적으로 값을 할당하지 않은 요소는 생성되지 않음</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/b28e2db2-b0ee-42e9-baf1-b42c71f489b2/image.png" alt=""></p>
<p>이미 요소가 존재하는 요소에 값을 재할당하면 요소값이 갱신됨</p>
<pre><code class="language-js">arr[1] = 10;
console.log(arr); // [0, 10, empty * 98, 100]</code></pre>
<p>인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수를 사용. </p>
<p>만약 정수 이외의 값을 인덱스처럼 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성되고, 이 때 추가된 프로퍼티는 length 프로퍼티 값에 영향을 주지 않음</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/7fc8186c-9a90-48e4-bb33-cb11a739cbd2/image.png" alt=""></p>
<h2 id="277-배열-요소의-삭제">27.7 배열 요소의 삭제</h2>
<p>배열은 사실 객체이기 때문에 배열의 특정 요소를 삭제하기 위해 delete 연산자를 사용할 수 있음 =&gt; <strong>희소 배열로 만드니까 사용하지 않는게 좋다</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];

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

// length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
console.log(arr.length); // 3</code></pre>
<p>delete 메서드보다 <strong>Array.prototype.splice 메서드를 사용하자</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];

// Array.prototype.splice(삭제를 시작할 인덱스, 삭제할 요소 수)
// arr[1]부터 1개의 요소 제거
arr.splice(1, 1);
console.log(arr); // [1, 3]

// length 프로퍼티가 자동 갱신됨
console.log(arr.length); // 2</code></pre>
<h2 id="278-배열-메서드">27.8 배열 메서드</h2>
<p>배열을 다룰 때 다양한 빌트인 메서드 있음. Array 생성자 함수 =&gt; 정적 메서드 제공, Array.prototype =&gt; 프로토타입 메서드 제공</p>
<blockquote>
<p><strong>배열 메서드가 결과물을 반환하는 패턴</strong> =&gt; 가급적 원본 배열을 변경하지 않는 메서드를 사용하는게 좋음</p>
<ul>
<li>원본 배열(배열 메서드를 호출한 배열. 배열 메서드 구현체 내부에서의 this)을 직접 변경**하는 메서드 (mutator method)</li>
<li><strong>원본 배열을 직접 변경하지 않고 새로운 배열을 생성</strong>하여 반환하는 메서드 (accessor method)</li>
</ul>
</blockquote>
<h3 id="2781-arrayisarray">27.8.1 Array.isArray</h3>
<p>Array 생성자 함수의 정적 메서드</p>
<p><strong>전달된 인수가 배열이면 true, 배열이 아니면 false 반환</strong></p>
<pre><code class="language-js">// true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());

// false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(1);
Array.isArray(&#39;Array&#39;);
Array.isArray(true);
Array.isArray(false);
Array.isArray({ 0: 1, length: 1});</code></pre>
<h3 id="2782-arrayprototypeindexof">27.8.2 Array.prototype.indexOf</h3>
<p><strong>원본 배열에서 인수로 전달된 요소를 검색하여 인덱스 반환</strong></p>
<ul>
<li>원본 배열에 인수로 전달한 요소와 중복되는 요소가 여러 개 있으면 <code>첫 번째로 검색된 요소의 인덱스</code> 반환</li>
<li>원본 배열에 인수로 전달한 요소가 존재하지 않으면 <code>-1</code> 반환. NaN은 검색 안되고 무조건 -1 반환</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 2, 3];

// 배열 arr에서 요소 2를 검색하여 첫 번째로 검색된 요소의 인덱스르 반환
arr.indexOf(2); // -&gt; 1
// 배열 arr에 요소 4가 없으므로 -1 반환
arr.indexOf(4); // -&gt; -1
// 두 번째 인수는 검색을 시작할 인덱스. 두 번째 인수를 생략하면 처음부터 검색
arr.indexOf(2, 2); // -&gt; 2</code></pre>
<p>indexOf 메서드 대신 ES7 에서 도입된 Array.prototype.includes 메서드를 사용하면 가독성이 좋음</p>
<pre><code class="language-js">const foods = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;];

// foods 배열에 &#39;orange&#39; 요소가 있는지 검색해서 없으면 추가
if(foods.indexOf(&#39;orange&#39;)){
  foods.push(&#39;orange&#39;)
}

// foods 배열에 &#39;orange&#39; 요소가 있는지 검색해서 없으면 추가
if(!foods.includes(&#39;orange&#39;)){
    foods.push(&#39;orange&#39;);
}</code></pre>
<h3 id="2783-arrayprototypepush">27.8.3 Array.prototype.push</h3>
<p><strong>인수로 전달받은 모든 값을 원본 배열의 <code>마지막 요소</code>로 <code>추가</code>하고 변경된 length 프로퍼티 값을 반환. 원본 배열을 <code>직접 변경</code></strong></p>
<pre><code class="language-js">const arr = [1, 2];

// 인수로 전달받은 모든 값을 원본 배열 arr의 마지막 요소로 추가하고 변경된 length 값 반환
let result = arr.push(3, 4);
console.log(result); // 4

// push 메서드는 원본 배열을 직접 변경
console.log(arr); // [1, 2, 3, 4]</code></pre>
<p>push 메서드는 성능 면에서 좋지 않아서, 마지막 요소로 추가할 요소가 하나뿐이라면 length 프로퍼티를 사용해서 배열의 마지막에 요소를 직접 추가하는 것이 더 빠름</p>
<pre><code class="language-js">const arr = [1, 2];

arr[arr.length] = 3;
console.log(arr); // [1, 2, 3]</code></pre>
<p>스프레드 문법(ES6)을 사용하는게 부수효과가 없어서 좋음</p>
<pre><code class="language-js">const arr = [1, 2];

const newArr = [...arr, 3];
console.log(newArr); // [1, 2, 3]</code></pre>
<h3 id="2784-arrayprototypepop">27.8.4 Array.prototype.pop</h3>
<p><strong>원본 배열에서 <code>마지막 요소</code>를 <code>제거</code>하고 제거한 요소 반환. 원본 배열을 <code>직접 변경</code></strong>. 원본 배열이 빈 배열이면 undefined 반환. </p>
<pre><code class="language-js">const arr = [1, 2];

let result = arr.pop();
console.log(result); // 2

console.log(arr); // [1]</code></pre>
<h3 id="2784-arrayprototypeunshift">27.8.4 Array.prototype.unshift</h3>
<p><strong>인수로 전달받은 모든 값을 원본 배열의 <code>선두 요소</code>로 <code>추가</code>하고 변경된 length 프로퍼티 값 반환. 원본 배열을 <code>직접 변경</code></strong></p>
<pre><code class="language-js">const arr = [1, 2];

let result = arr.unshift(3, 4);
console.log(result); // 4

console.log(arr); // [3, 4, 1, 2]</code></pre>
<p>스프레드 문법을 사용하는게 부수 효과가 없어서 좋음</p>
<pre><code class="language-js">const arr = [1, 2];

const newArr = [3, ...arr];
console.log(newArr); // [3, 1, 2]</code></pre>
<h3 id="2786-arrayprototypeshift">27.8.6 Array.prototype.shift</h3>
<p><strong>원본 배열에서 <code>첫 번째 요소</code>를 <code>제거</code>하고 제거한 요소 반환. 원본 배열을 직접 변경.</strong> 원본 배열이 빈 배열이면 undefined 반환</p>
<pre><code class="language-js">const arr = [1, 2];

let result = arr.shift();
console.log(result); // 1

console.log(arr); // [2]</code></pre>
<h3 id="2787-arrayprototypeconcat">27.8.7 Array.prototype.concat</h3>
<p><strong>인수로 전달된 값들(배열 또는 원시값)을 원본의 <code>마지막 요소</code>로 <code>추가</code>한 새로운 배열 반환.</strong> 인수로 전달된 값이 배열인 경우 배열을 해체하여 새로운 배열의 요소로 추가. <strong>원본 배열 <code>변경X</code></strong></p>
<pre><code class="language-js">const arr1 = [1, 2];
const arr2 = [3, 4];

let result = arr1.concat(arr2);
console.log(result); // [1, 2, 3, 4]

result = arr1.concat(3);
console.log(result); // [1, 2, 3]

result = arr1.concat(arr2, 5);
console.log(result); // [1, 2, 3, 4, 5]</code></pre>
<p>push와 unshift 메서드와 concat 메서드의 차이점</p>
<ul>
<li>push, unshift =&gt; 원본 배열 직접 변경. / concat =&gt; 원본 배열을 변경하지 않고 새로운 배열 반환</li>
<li>push, unshift를 사용할 경우 원본 배열을 반드시 변수에 저장해야함. / concat을 사용할 경우 반환값을 반드시 변수에 할당받아야 함</li>
<li>인수로 전달받은 값이 배열인 경우 push, unshift는 배열을 그대로 추가. / concat은 인수로 전달받은 배열을 해체하여 새로운 배열에 추가</li>
</ul>
<p>concat 메서드는 스프레드 문법(ES6)으로 대체 가능</p>
<pre><code class="language-js">let result = [1, 2].concat([3, 4]);
console.log(result); // [1, 2, 3, 4]

result = [...[1, 2], ...[3, 4]];
console.log(result); // [1, 2, 3, 4]</code></pre>
<p>책에서는 push / unshift 메서드와 concat 메서드를 사용하는 대신 스프레드 문법을 일관성 있게 사용하는 것을 권장</p>
<h3 id="2788-arrayprototypesplice">27.8.8 Array.prototype.splice</h3>
<p><strong>원본 배열 <code>중간에 요소를 추가</code>하거나 <code>중간에 있는 요소를 제거</code>하는 경우 splice 메서드 사용. splice 메서드는 3개의 매개변수가 있고, 원본 배열을 <code>직접 변경</code></strong>. 제거 요소 반환</p>
<ul>
<li><code>start</code> : 원본 배열의 <strong>요소를 제거하기 시작할 인덱스</strong>. start만 지정하면 원본 배열의 start부터 모든 요소 제거. start가 음수일 경우 배열의 끝에서의 인덱스. -1 : 맨끝</li>
<li><code>deleteCount</code> : 원본 배열의 요소를 제거하기 시작할 인덱스인 start부터 <strong>제거할 요소의 개수</strong>. 0개이면 아무 요소도 제거 안함</li>
<li><code>items [옵션]</code>: 제거할 위치에 <strong>삽입할 요소들의 목록</strong>. 생략할 경우 원본 배열에서 요소 제거만 함</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3, 4];

// 원본 배열 인덱스 1부터 2개 요소 제거, 새 요소 20, 30을 그 자리에 삽입
const result = arr.splice(1, 2, 20, 30);

// 제거한 요소를 배열로 반환
console.log(result); // [2, 3]
// splice 메서드는 원본 배열을 직접 변경
console.log(arr); // [1, 20, 30, 4]</code></pre>
<ul>
<li>splice 메서드의 두 번째 인수를 0으로 지정하면 아무 요소도 제거 안하고 새로운 요소만 삽입</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3, 4];

// 원본 배열 인덱스 1부터 0개 요소 제거, 새 요소 100을 그 자리에 삽입
const result = arr.splice(1, 0, 100);

// 제거한 요소를 배열로 반환
console.log(result); // []
// splice 메서드는 원본 배열을 직접 변경
console.log(arr); // [1, 100, 2, 3, 4]</code></pre>
<ul>
<li>splice 메서드의 세 번째 인수를 생략하면 원본 배열에서 지정된 요소를 제거만 함</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3, 4];

// 원본 배열의 인덱스 1부터 2개의 요소 제거
const result = arr.splice(1, 2);

// 제거한 요소를 배열로 반환
console.log(result); // [2, 3]
// splice 메서드는 원본 배열을 직접 변경
console.log(arr); // [1, 4]</code></pre>
<ul>
<li>splice 메서드의 두 번째 인수를 생략하면 첫 번째 인수로 전달된 시작 인덱스부터 모든 요소를 제거</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3, 4];

// 원본 배열의 인덱스 1부터 모든 요소 제거
const result = arr.splice(1);

// 제거한 요소를 배열로 반환
console.log(result); // [2, 3, 4]
// splice 메서드는 원본 배열을 직접 변경
console.log(arr); // [1]</code></pre>
<ul>
<li>특정 요소를 제거하려면 indexOf 메서드를 통해 특정 요소의 인덱스를 취득한 다음 splice 메서드로 제거</li>
</ul>
<pre><code class="language-js">const arr= [1, 2, 3, 1, 2];

function remove(array, item){
  // 제거할 item 요소의 인덱스 취득
  const index = array.indexOf(item);

  // 취득한 인덱스를 이용해 요소 제거
  if(index !== -1) array.splice(index, 1);

  return array
}

console.log(remove(arr, 2)); // [1, 3, 1, 2]
console.log(remove(arr, 10)); // [1, 3, 1, 2]</code></pre>
<ul>
<li>filter 메서드를 사용하면 특정 요소 모두 삭제 (중복된 요소 전부)</li>
</ul>
<h3 id="2789-arrayprototypeslice">27.8.9 Array.prototype.slice</h3>
<p><strong>인수로 전달된 범위의 요소들을 <code>복사</code>하여 배열로 반환. 원본 배열 <code>변경X</code>. 두 개의 매개변수를 가짐</strong></p>
<ul>
<li><code>start</code> : <strong>복사를 시작할 인덱스</strong>. 음수인 경우 배열의 끝에서의 인덱스. ex) slice(-2) 는 배열의 끝에서부터 두 개의 요소 복사</li>
<li><code>end</code> : <strong>복사를 종료할 인덱스</strong>. 이 인덱스에 해당하는 요소는 복사X. 생략시 기본값은 length 프로퍼티 값(배열 끝까지)</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3];

// arr[0]부터 arr[1] 이전(arr[1] 미포함)까지 복사하여 반환
arr.slice(0, 1); // -&gt; [1]

// arr[1]부터 arr[2] 이전)(arr[2] 미포함)까지 복사하여 반환
arr.slice(1, 2); // -&gt; [2]</code></pre>
<ul>
<li>slice 메서드의 두 번째 인수를 생략하면 첫 번째 인수로 전달된 시작 인덱스부터 모든 요소를 복사하여 배열로 반환</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3];

arr.slice(1); // -&gt;[2, 3]</code></pre>
<ul>
<li>slice 메서드의 첫 번째 인수가 음수인 경우 배열의 끝에서부터 요소를 복사하여 배열로 반환</li>
</ul>
<pre><code class="language-Js">const arr = [1, 2, 3];

arr.slice(-1); // -&gt; [3]
arr.slice(-2); // -&gt; [2, 3]</code></pre>
<ul>
<li>slice 메서드의 인수를 모두 생략하면 원본 배열의 복사본을 생성하여 반환. 이 때 생성된 복사본은 얕은 복사</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3];

const copy = arr.slice();
console.log(copy); // [1, 2, 3]
console.log(copy === arr); // false</code></pre>
<h3 id="27810-arrayprototypejoin">27.8.10 Array.prototype.join</h3>
<p><strong>원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달받은 문자열(구분자)로 연결한 문자열을 반환</strong>. 구분자는 생략 가능하며 기본 구분자는 콤마(&#39;,&#39;)</p>
<pre><code class="language-js">const arr = [1, 2, 3, 4];

arr.join(); // -&gt; &#39;1,2,3,4&#39;
arr.join(&#39;&#39;); // -&gt; &#39;1234&#39;
arr.join(&#39;:&#39;); // -&gt; &#39;1:2:3:4&#39; </code></pre>
<h3 id="27811-arrayprototypereverse">27.8.11 Array.prototype.reverse</h3>
<p><strong>원본 배열의 <code>순서를 반대로 뒤집고</code> 변경된 배열을 반환. 원본 배열을 <code>직접 변경</code></strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];

const result = arr.reverse();

console.log(arr); // [3, 2, 1]
console.log(result); // [3, 2, 1]</code></pre>
<h3 id="27812-arrayprototypefill-es6">27.8.12 Array.prototype.fill (ES6)</h3>
<p><strong>인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채우고 원본 배열을 반환. 원본 배열을 <code>직접 변경</code></strong></p>
<pre><code class="language-JS">const arr = [1, 2, 3];

arr.fill(0);
console.log(arr); // [0, 0, 0]</code></pre>
<p><code>두 번째 인수</code>로 <strong>요소 채우기를 시작할 인덱스 전달</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];

arr.fill(0, 1);
console.log(arr); // [1, 0, 0]</code></pre>
<p><code>세 번째 인수</code>로 <strong>요소 채우기를 멈출 인덱스 전달</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3, 4, 5];

arr.fill(0, 1, 3);
console.log(arr); // [1, 0, 0, 4, 5]</code></pre>
<p>fill 메서드를 사용하면 배열을 생성하면서 특정 값으로 요소를 채울 수 있음</p>
<pre><code class="language-js">const arr = new Array(3);
console.log(arr); // [empty * 3]

const result = arr.fill(1);
console.log(arr); // [1, 1, 1]
console.log(result); // [1, 1, 1]</code></pre>
<h3 id="27813-arrayprototypeincludes-es7">27.8.13 Array.prototype.includes (ES7)</h3>
<p><strong>배열 내에 특정 요소가 포함되어 있는지 확인하여 true, false 반환</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];

// 배열에 요소 2가 포함되어 있는지 확인
arr.includes(2); // -&gt; true

// 배열에 요소 100이 포함되어 있는지 확인
arr.includes(100); // -&gt; false</code></pre>
<p><code>두 번째 인수</code>로 <strong>검색을 시작할 인덱스 전달</strong>. 생략시 기본값 0. 음수를 전달하면 length 프로퍼티 값과 음수 인덱스를 합산(length + index)하여 검색 시작 인덱스 설정</p>
<pre><code class="language-js">const arr = [1, 2, 3];

// 배열에 요소 1이 포함되어 있는지 인덱스 1부터 확인
arr.includes(1, 1); // -&gt; false

// 배열에 요소 3이 포함되어 있는지 인덱스 2(arr.length - 1)부터 확인
arr.includes(3, -1); // -&gt; true</code></pre>
<h3 id="27814-arrayprototypeflat-es10ecmascipt-2019">27.8.14 Array.prototype.flat (ES10;ECMAScipt 2019)</h3>
<p><strong>인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화</strong>. 인수를 생략할 경우 기본값 1. 인수로 Infinity를 전달하면 중첩 배열 모두를 평탄화</p>
<pre><code class="language-js">[1, [2, 3, 4, 5]].flat(); // -&gt; [1, 2, 3, 4, 5]

[1, [2, [3, [4]]]].flat(); // -&gt; [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(1); // -&gt; [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].flat(2); // =&gt; [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat().flat(); // -&gt; [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(infinity); // -&gt; [1, 2, 3, 4]</code></pre>
<h2 id="279-배열-고차-함수">27.9 배열 고차 함수</h2>
<p><strong>고차 함수(Higer-Order Function;HOF)</strong> : 함수를 인수로 전달받거나 함수를 반환하는 함수</p>
<p>고차 함수는 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향하는 <code>함수형 프로그래밍</code>에 기반을 둠.</p>
<blockquote>
<p>함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 로직 내 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고, 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임. 함수형 프로그래밍은 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환</p>
</blockquote>
<h3 id=""></h3>
<h3 id="2791-arrayprototypesort">27.9.1 Array.prototype.sort</h3>
<p><strong>배열의 요소를 정렬하고 정렬된 배열 반환. 기본적으로 오름차순으로 요소 정렬. 원본 배열을 <code>직접 변경</code></strong></p>
<pre><code class="language-js">const fruits = [&#39;Banana&#39;, &#39;Orange&#39;, &#39;Apple&#39;];

fruits.sort();
console.log(fruits); // [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Orange&#39;]</code></pre>
<p>내림차순으로 요소를 정렬하려면 sort 메서드를 사용한 후 reverse 메서드를 사용하여 순서를 뒤집으면 됨</p>
<pre><code class="language-js">const fruits = [&#39;Banana&#39;, &#39;Orange&#39;, &#39;Apple&#39;];

fruits.sort();
console.log(fruits); // [&#39;Apple&#39;, &#39;Banana&#39;, &#39;Orange&#39;]

fruits.reverse();
console.log(fruits); // [&#39;Orange&#39;, &#39;Banana&#39;, &#39;Apple&#39;]</code></pre>
<p><strong>숫자 요소를 정렬할 때는 주의가 필요함</strong>.</p>
<p>sort 메서드는 유니코드 순서에 따라 정렬하는데 숫자 타입일 때는 배열을 일시적으로 문자열로 변환한 후 정렬하기 때문에 앞자리 기준으로 정렬하게 됨</p>
<p>따라서 <strong>숫자 요소를 정렬할 때는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 함</strong></p>
<p>비교 함수의 반환값이 0보다 작으면 비교 함수의 첫 번째 인수를 우선하여 정렬하고, 0이면 정렬하지 않고, 0보다 크면 두 번째 인수를 우선하여 정렬</p>
<p>정렬한 배열에서 첫 번째 인수, 마지막 인수는 최소/최대값이 됨</p>
<pre><code class="language-js">const points = [40, 100, 1, 5, 2, 25, 10];

// 오름차순 정렬
points.sort((a, b) =&gt; a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]

// 내림차순 정렬
console.log((a, b)=&gt; b - a);
console.log(points); // [100, 40, 25, 5, 2, 1]</code></pre>
<h3 id="2792-arrayprototypeforeach">27.9.2 Array.prototype.forEach</h3>
<p>for문을 대체할 수 있는 고차 함수로, forEach 메서드는 자신의 내부에서 반복문을 실행. undefined 값 반환</p>
<p>forEach 메서드는 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 <strong>자신을 호출한 배열의 모든 요소를 순회하면서 수행해야할 처리를 콜백 함수로 전달받아 반복 호출</strong></p>
<pre><code class="language-js">const numbers = [1, 2, 3];
const pows = [];

// forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출
numbers.forEach(item =&gt; pows.push(item * 2));
console.log(pows); // [1, 4, 9]</code></pre>
<p>forEach 메서드는 콜백함수를 호출할 때 3개의 인수, forEach 메서드를 호출한 배열의 <code>요소값</code>, <code>인덱스</code>, <code>forEach 메서드를 호출한 배열(this)</code>를 순차적으로 전달</p>
<p>forEach 메서드는 원본 배열을 변경하지는 않지만, 콜백 함수를 통해 원본 배열을 변경할 수는 있음 (세번째 매개변수 변경)</p>
<pre><code class="language-js">const numbers = [1, 2, 3];

numbers.forEach((item, index, arr) =&gt; { arr[index] = item ** 2; });
console.log(numbers); // [1, 4, 9]</code></pre>
<p>forEach 메서드의 두 번째 인수로 forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있음</p>
<pre><code class="language-js">class Numbers {
  numberArray = [];

  multiply(arr) {
    arr.forEach(function(item){
      this.numberArray.push(item * item);
    }, this); // forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체
  }
}

const numbers = new Numbers();
numbers.multiply([1, 2, 3]);
console.log(numbers.numbersArray); // [1, 4, 9]</code></pre>
<p>화살표 함수 사용이 더 나은 방법</p>
<pre><code class="language-js">class Numbers {
  numberArray = [];

  multiply(arr) {
    // 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조
    arr.forEach(item =&gt; this.numberArray.push(item * item));
  }
}

const numbers = new Numbers();
numbers.multiply([1, 2, 3]);
console.log(numbers.numbersArray); // [1, 4, 9]</code></pre>
<h3 id="2793-arrayprototypemap">27.9.3 Array.prototype.map</h3>
<p>map 메서드는 <strong>자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출하고, <code>콜백 함수의 반환값들로 구성된 새로운 배열</code> 반환</strong>. 원본 배열 <code>변경X</code></p>
<p>요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수</p>
<pre><code class="language-js">const numbers = [1, 4, 9];

const roots = numbers.map(item =&gt; Math.sqrt(item));

console.log(roots); // [1, 2, 3]
console.log(numbers); // [1, 4, 9]</code></pre>
<p>map 메서드는 콜백 함수를 호출할 때 map 메서드를 호출한 배열의 <code>요소값</code>과, <code>인덱스</code>, <code>map 메서드를 호출한 배열 자체(this)</code>를 순차적으로 전달</p>
<p>map 메서드의 두 번째 인수로 map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있음</p>
<h3 id="2794-arrayprototypefilter">27.9.4 Array.prototype.filter</h3>
<p>자신을 호출한 <strong>배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출하고, 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열 반환</strong>. 원본 배열 <code>변경X</code></p>
<p>filter 메서드가 생성하여 반환한 새로운 배열의 length 프로퍼티 값은 filter 메서드를 호출한 배열의 length 프로퍼티 값과 같거나 적음</p>
<pre><code class="language-js">arr.filter(callback(element[, index[, array]])[, thisArg])</code></pre>
<blockquote>
<p><strong>fileter 메서드 인수</strong> : 1. 콜백 함수, 2. 콜백을 실행할때 this로 쓸 값(옵션)</p>
<p><strong>fileter 메서드 콜백 함수 인수</strong> : 1. 요소값 2. 요소 인덱스 3. 순회(traverse)되는 배열 객체(filter 메서드를 호출한 배열;this)</p>
</blockquote>
<pre><code class="language-js">const numbers = [1, 2, 3, 4, 5];

// numbers 배열에서 홀수인 요소만 필터링 (1은 true로 평가)
const odds = numbers.filter(item =&gt; item % 2);
console.log(odds); // [1, 3, 5]</code></pre>
<p>filter 메서드는 콜백 함수를 호출할 때, filter 메서드를 호출한 배열의 요소값과 인덱스, filter 메서드를 호출한 배열(this)를 순차적으로 전달</p>
<p>filter 메서드의 두 번째 인수로 filter 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있음</p>
<p>filter 메서드를 사용해 특정 요소를 제거할 경우 특정 요소가 중복되어 있다면 중복된 요소가 모두 제거. 특정 요소 하나만 제거하려면 splice 메서드 사용</p>
<h3 id="2795-arrayprototypereduce">27.9.5 Array.prototype.reduce</h3>
<p>자신을 호출한 <strong>배열을 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출하고, <code>콜백 함수의 반환값</code>을 다음 순회 시에 <code>콜백 함수의 첫 번째 인수로 전달</code>하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환</strong>. 원본 배열 <code>변경X</code></p>
<blockquote>
<p><strong>reduce 메서드 인수</strong> : 1. 콜백 함수, 2. 초기값</p>
<p><strong>reduce 메서드 콜백 함수 인수</strong> : 1. 초기값 or 콜백 함수의 이전 반환값, 2. reduce 메서드를 호출한 배열 요소값, 3. 인덱스, 4. reduce 메서드를 호출한 배열 자체 (this)</p>
</blockquote>
<pre><code class="language-js">const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) =&gt; accumulator + currentValue, 0);

console.log(sum); // 10</code></pre>
<table >
  <tr align="center">
      <td rowspan="2">구분</td><td colspan="4">콜백 함수에 전달되는 인수</td><td rowspan="2">콜백 함수의 반환값<br>(accumulator + currentValue)</td>
  </tr>
    <tr align="center">
    <td>accumulator</td><td>currnetValue</td><td>index</td><td>array</td>
</tr>
    <tr align="center">
    <td>첫 번째 순회</td><td>0 (초기값)</td><td>1</td><td>0</td><td>[1, 2, 3, 4]</td><td>1</td>
</tr>
    <tr align="center">
    <td>두 번째 순회</td><td>1</td><td>2</td><td>1</td><td>[1, 2, 3, 4]</td><td>3</td>
</tr>
    <tr align="center">
    <td>세 번째 순회</td><td>3</td><td>3</td><td>2</td><td>[1, 2, 3, 4]</td><td>6</td>
</tr>
    <tr align="center">
    <td>네 번째 순회</td><td>6</td><td>4</td><td>3</td><td>[1, 2, 3, 4]</td><td>10</td>
</tr>
</table>


<p>reduce 메서드를 호출할 때는 초기값을 생략하지 말고 언제나 전달하는 것이 안전함</p>
<h3 id="2796-arrayprototypesome">27.9.6 Array.prototype.some</h3>
<p><strong>자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하고, 콜백 함수의 반환값이 단 한 번이라도 참이면 true, 모두 거짓이면 false 반환</strong>.</p>
<p>즉, 배열의 요소 중에 콜백 함수를 정의한 조건을 만족하는 요소가 1개 이상 존재하는지 확인하여 그 결과를 불리언 타입으로 반환. 호출한 배열이 빈 배열이면 false 반환</p>
<p>some 메서드의 콜백 함수는 some 메서드를 호출한 <code>요소값</code>과 <code>인덱스</code>, <code>some 메서드를 호출한 배열 자체(this)</code>를 순차적으로 전달받을 수 있음</p>
<pre><code class="language-js">[5, 10, 15].some(item =&gt; item &gt; 10); // -&gt; true
[5, 10, 15].some(item =&gt; item &lt; 0); // -&gt; false
[].some(item =&gt; item &gt; 3); // -&gt; false</code></pre>
<p>some 메서드의 두 번째 인수로 some 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있음</p>
<h3 id="2797-arrayprototypeevery">27.9.7 Array.prototype.every</h3>
<p><strong>자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하고, 콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false 반환.</strong></p>
<p>즉, 배열의 모든 요소가 콜백 함수를 통해 정의한 조건을 모두 만족하는지 확인하여 그 결과를 불리언 타입으로 반환. 호출한 배열이 빈 배열이면 true 반환</p>
<p>every 메서드 콜백 함수의 인수 : every 메서드를 호출한 요소값, 인덱스, every 메서드를 호출한 배열 자체(this)</p>
<p>every 메서드의 두 번째 인수로  every 메서드의 콜백 함수 내부에서 this로 사용할 객체 전달 가능</p>
<h3 id="2798-arrayprototypefind-es6">27.9.8 Array.prototype.find (ES6)</h3>
<p><strong>자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하고, <code>반환값이 true인 첫 번째 요소</code> 반환. 반환값이 true인 요소가 없으면 <code>undefined</code> 반환</strong></p>
<p>find 메서드 콜백 함수의 인수 : <code>find 메서드를 호출한 요소값</code>, <code>인덱스</code>, <code>find 메서드를 호출한 배열 자체(this)</code></p>
<p>find 메서드의 두번째 인수 : 콜백 함수 내부에서 this로 사용할 객체</p>
<pre><code class="language-js">const users = [
  { id : 1, name: &#39;Lee&#39;},
  { id : 2, name: &#39;Kim&#39;},
  { id : 3, name: &#39;Choi&#39;},
  { id : 4, name: &#39;Park&#39;},
];

users.find(user =&gt; user.id === 2); // { id : 2, name: &#39;Kim&#39;}</code></pre>
<h3 id="2799-arrayprototypefindindex-es6">27.9.9 Array.prototype.findIndex (ES6)</h3>
<p><strong>자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여 <code>반환값이 true인 첫 번째 요소의 인덱스</code> 반환. 반환값이 true인 요소가 없으면 <code>-1</code> 반환</strong></p>
<p>findIndex 메서드 콜백 함수의 인수 : find 메서드를 호출한 요소값, 인덱스, findIndex 메서드를 호출한 배열 자체(this)</p>
<p>find 메서드의 두번째 인수 : 콜백 함수에서 내부에서 this로 사용할 객체</p>
<h3 id="27910-arrayprototypeflatmap-es10">27.9.10 Array.prototype.flatMap (ES10)</h3>
<p>flatMap 메서드는 map 메서드를 통해 생성된 새로운 배열을 평탄화. 즉, map 메서드와 flat 메서드를 순차적으로 실행하는 효과가 있음</p>
<p>단, flat 메서드는 1단계만 평탄화</p>
<pre><code class="language-js">const arr = [&#39;hello&#39;, &#39;world&#39;];

arr.map(x =&gt; x.split(&#39;&#39;)).flat();
// -&gt; [&#39;h&#39;,&#39;e&#39;,&#39;l&#39;,&#39;l&#39;,&#39;o&#39;,&#39;w&#39;,&#39;o&#39;,&#39;r&#39;,&#39;l&#39;,&#39;d&#39;]

arr.flatMap(x =&gt; x.split(&#39;&#39;));
// -&gt; [&#39;h&#39;,&#39;e&#39;,&#39;l&#39;,&#39;l&#39;,&#39;o&#39;,&#39;w&#39;,&#39;o&#39;,&#39;r&#39;,&#39;l&#39;,&#39;d&#39;]</code></pre>
<p><img src="https://ppss.kr/wp-content/uploads/2017/02/3-16.png" alt="img"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<26. 함수의 추가 기능>]]></title>
            <link>https://velog.io/@st_hwang/26.-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EA%B0%80-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@st_hwang/26.-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EA%B0%80-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Wed, 13 Jul 2022 02:36:44 GMT</pubDate>
            <description><![CDATA[<h1 id="26-함수의-추가-기능">26. 함수의 추가 기능</h1>
<h2 id="261-함수의-구분">26.1 함수의 구분</h2>
<p>ES6 이전까지 자바스크립트 함수는 별 구분 없이 다양한 목적(일반 함수, 생성자 함수, 객체에 바인된 메서드)으로 사용되어 왔음.</p>
<p>즉, ES6 이전의 모든 함수는 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로 호출할 수 있는 callable이면서 constructor 였음</p>
<pre><code class="language-js">var foo = function () {}

foo(); // undefined
new foo(); // -&gt; foo {}</code></pre>
<p>주의할 점은 ES6 이전에 일반적으로 메서드라고 부르던 객체 바인딩된 함수도 callable 이며 constructor</p>
<pre><code class="language-js">var obj = {
  x: 10,
  f: function () { return this.x; }
};

// 프로퍼티 f에 바인딩된 함수를 메서드로 호출
console.log(obj.f()); // 10

// 프로퍼티 f에 바인딩된 함수를 일반 함수로 호출
var bar = obj.f
console.log(bar()); // undefined

// 프로퍼티 f에 바인딩된 함수를 생성자 함수로 호출
console.log(new obj.f()); // f {}</code></pre>
<p>이는 객체에 바인딩된 함수가 prototype 프로퍼티를 가지며, 프로토타입 객체도 생성한다는 것을 의미하며,</p>
<p>콜백 함수도 constructor가 되기 때문에 불필요한 프로토타입 객체를 생성한다.</p>
<p>이러한 문제를 해결하기 위해 ES6에서는 함수를 사용 목적에 따라 세 가지 종류로 명확히 구분</p>
<table>
<thead>
<tr>
<th align="center">ES6 함수의 구분</th>
<th align="center">consturctor</th>
<th align="center">prototype</th>
<th align="center">super</th>
<th align="center">arguments</th>
</tr>
</thead>
<tbody><tr>
<td align="center">일반 함수(Normal)</td>
<td align="center">O</td>
<td align="center">O</td>
<td align="center">X</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center">메서드(Method)</td>
<td align="center">X</td>
<td align="center">X</td>
<td align="center">O</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center">화살표 함수(Arrow)</td>
<td align="center">X</td>
<td align="center">X</td>
<td align="center">X</td>
<td align="center">X</td>
</tr>
</tbody></table>
<h2 id="262-메서드">26.2 메서드</h2>
<p>ES6 이전에는 메서드에 대한 명확한 정의가 없었으며, 일반적으로 메서드는 객체에 바인딩된 함수를 일컫는 의미로 사용</p>
<p><strong>ES6 사양에서 <code>메서드</code>는 <code>메서드 축약 표현</code>으로 정의된 함수만을 의미</strong></p>
<pre><code class="language-js">const obj = {
  x: 1,
  // foo는 메서드
  foo( { return this.x; }),
  // bar에 바인딩된 함수는 메서드가 아닌 일반 함수
  bar: function() { return this.x; }
};

console.log(obj.foo()); // 1
console.log(obj.bar()); // 1</code></pre>
<p><code>ES6 메서드</code>는 <strong>인스턴스를 생성할 수 없는 non-constructor</strong></p>
<pre><code class="language-js">new obj.foo(); // -&gt; TypeError: obj.foo is not a constructor
new obj.bar(); // -&gt; bar {}</code></pre>
<p>ES6 메서드는 인스턴스를 생성할 수 없으므로 <strong>prototype 프로퍼티가 없고 프로토타입도 생성하지 않음</strong></p>
<p>표준 빌트인 객체가 제공하는 프로토타입 메서드와 정적 메서드도 모두 non-constructor</p>
<p>ES6 메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]를 가지며, super 참조는 내부 슬롯 [[HomeObject]]를 사용하여 수퍼클래스의 메서드를 참조하므로 내부 슬롯 [[HomeObject]]를 갖는 ES6 키워드는 super 키워드를 사용할 수 있음</p>
<pre><code class="language-js">const base = {
  name: &#39;Lee&#39;,
  sayHi() {
    return `Hi! ${this.name}`;
  }
};

const derived = {
  __proto__: base,
  // sayHi는 ES6 메서드, [[HomeObject]]를 가짐
  // sayHi의 [[HomeObject]]는 derived.prototype 을 가리키고
  // super는 sayHi의 [[HomeObject]]의 프로토타입인 base.prototype 을 가리킴
  sayHi(){
    return `${super.sayHi()}. how are you doing?`
  }
};

console.log(derived.sayHi()); // Hi! Lee. how are you doing?</code></pre>
<p>ES6 메서드가 아닌 함수는 super 키워드를 사용할 수 없음</p>
<p>ES6 메서드는 본연의 기능(super)를 추가하고 의미적으로 맞지 않는 기능(constructor) 제거</p>
<p>따라서 메서드를 정의할 때 프로퍼티 값으로 익명 함수 표현식을 할당하는 ES6 이전의 방식은 사용하지 않는 것이 좋음</p>
<h2 id="263-화살표-함수">26.3 화살표 함수</h2>
<p>화살표 함수(arrow function)는 function 키워드 대신 화살표(=&gt;, fat arrow)를 사용하여 기존의 함수 정의 방식보다 간략하게 함수를 정의할 수 있음</p>
<p>화살표 함수는 콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용</p>
<h3 id="2631-화살표-함수-정의">26.3.1 화살표 함수 정의</h3>
<h4 id="함수-정의">함수 정의</h4>
<p>화살표 함수는 함수 선언문으로 정의할 수 없고, 함수 표현식으로 정의해야 함. 호출 방식은 기존 함수와 동일</p>
<pre><code class="language-js">const multiply = (x, y) =&gt; x * y;
multiply(2, 3); // -&gt; 6</code></pre>
<h4 id="매개변수-선언">매개변수 선언</h4>
<p>매개변수가 여러 개인 경우 소괄호 () 안에 매개 변수를 선언</p>
<pre><code class="language-js">const arrow = (x, y) =&gt; { ... };</code></pre>
<p>매개변수가 한 개인 경우 소괄호 생략 가능. 매개변수가 없는 경우는 소괄호 생략 못함</p>
<pre><code class="language-js">const arrow = x =&gt; { ... };</code></pre>
<h4 id="함수-몸체-정의">함수 몸체 정의</h4>
<p>함수 몸체가 하나의 문으로 구성되면 중괄호 {} 생략 가능. 이때 함수 몸체 내부의 문이 값으로 평가될 수 있는 표현식인 문이라면 암묵적으로 반환됨</p>
<pre><code class="language-js">const power = x =&gt; x ** 2;
power(2); // -&gt; 4

// 위 표현은 다음과 동일함
const power = x =&gt; { return x ** 2; };</code></pre>
<p>함수 몸체를 감싸는 <strong>중괄호 {}를 생략한 경우 함수 몸체 내부의 문이 표현식이 아닌 문이라면 에러 발생</strong>. 표현식이 아닌 문은 반환할 수 없기 때문</p>
<p>따라서 함수 몸체가 하나의 문으로 구성된다 해도 함수 몸체의 문이 표현식이 아닌 문이라면 중괄호를 생략할 수 없음</p>
<pre><code class="language-js">const arrow = () =&gt; xonst x = 1; // SyntaxError: Unexpected token &#39;const&#39;
const arrow = () =&gt; { const X = 1; };</code></pre>
<p>객체 리터럴을 반환하는 경우 객체 리터럴을 소괄호 ()로 감싸 주어야 함</p>
<pre><code class="language-js">const create = (id, content) =&gt; ({id, content});
create(1, &#39;JavaScript&#39;); // {id: 1, content: &quot;JavaScript&quot;}

// 위 표현은 다음과 동일함
const create = (id, content) =&gt; { return { id, content }; };</code></pre>
<p>객체 리터럴을 소괄호 ()로 감싸지 않으면 객체 리터럴의 중괄호 {}를 함수 몸체를 감싸는 중괄호 {}로 잘못 해석</p>
<p>함수 몸체가 여러 개의 문으로 구성된다면 함수 몸체를 감싸는 중괄호 {}를 생략할 수 없고, 반환값이 있다면 명시적으로 반환해야함</p>
<pre><code class="language-js">const sum = (a, b) =&gt; {
  const result a+b;
  return result;
};</code></pre>
<p>화살표 함수도 즉시 실행 함수로 사용할 수 있음</p>
<pre><code class="language-js">const person = (name =&gt; ({
  sayHi() { return `Hi? My name is ${name}.`; }
}))(&#39;Lee&#39;);

console.log(person.sayHi()); // Hi? My name is Lee.</code></pre>
<p>화살표 함수도 일급 객체이므로 Array.prototype.map, Array.prototype.filter, Array.prototype.reduce 같은 고차 함수에 인수로 전달 가능</p>
<pre><code class="language-js">// ES5
[1, 2, 3].map(function(v){
  return v * 2;
}); // [ 2, 4, 6]

// ES6
[1, 2, 3].map(v =&gt; v * 2); // [ 2, 4, 6]</code></pre>
<h3 id="2632-화살표-함수와-일반-함수의-차이">26.3.2 화살표 함수와 일반 함수의 차이</h3>
<h5 id="1-화살표-함수는-인스턴스를-생성할-수-없는-non-constructor">1. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor</h5>
<p>​    생성자 함수로 호출할 수 없음. 인스턴스를 생성할 수 없으므로 prototype 프로퍼티가 없고 프로토타입도 생성하지 않음</p>
<h5 id="2-중복된-매개-변수-이름을-선언할-수-없다">2. 중복된 매개 변수 이름을 선언할 수 없다</h5>
<p>​    일반 함수는 중복된 매개변수 이름을 선언해도 에러가 발생하지 않지만, 화살표 함수에서는 중복된 매개변수 이름을 선언하면 에러 발생</p>
<h5 id="3-화살표-함수는-함수-자체의-this-arguments-super-newtarget-바인딩을-갖지-않는다">3. 화살표 함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않는다</h5>
<p>​    화살표 함수 내부에서 this, arguments, super, new.target 을 참조하면 스코프 체인을 통해 <strong>상위 스코프의 this, arguments, super, new.target 참조</strong></p>
<p>​    만약 화살표 함수와 화살표 함수가 중첩되어 있다면 스코프 체인 상 가장 가까운 상위 함수 중에서 화살표 함수가 아닌 함수의 this, arguments, super, new.target 참조</p>
<h3 id="2633-this">26.3.3 this</h3>
<p>화살표 함수가 일반 함수와 구별되는 가장 큰 특징은 바로 this. 화살표 함수는 다른 함수의 인수로 전달되어 콜백 함수로 사용되는 경우가 많음</p>
<p>화살표 함수의 this는 일반 함수의 this와 다르게 동작하는데, 이는 콜백 함수 내부의 this가 외부 함수의 this와 다르기 때문에 발생하는 문제를 해결하기 위해 의도적으로 설계된 것</p>
<p>this는 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정되는데, 일반 함수로서 호출되는 콜백 함수의 경우 this가 전역 객체를 가리키기 때문에 문제가 됨</p>
<pre><code class="language-js">class Prefixer {
  constructor(prefix){
    this.prefix = prefix;
  }

  add(arr){
    // add 메서드는 인수로 전달된 배열 arr를 순회하며 배열의 모든 요소에 prefix를 추가
    // 1️⃣
    return arr.map(function(item){
      return this.prefix + item; // 2️⃣
      // -&gt; TypeError: Cannot read property &#39;prefix&#39; of undefined
    })
  }
}</code></pre>
<p>외부 함수의 this(1️⃣) : prefixer 객체, 콜백 함수의 this(2️⃣) : undefined(클래스는 strict mode가 적용되서 전역 객체가 아니라 undefined 바인딩)</p>
<p>ES6 이전 해결 방법</p>
<ol>
<li><p>add 메서드를 호출한 prefixer 객체를 가리키는 this를 일단 회피시킨 후에 콜백 함수 내부에서 사용</p>
<pre><code class="language-js"> ...
 add(arr){
   const that = this;
   return arr.map(function(item){
     return that.prefix + &#39; &#39; + item;
   });
 }
 ...</code></pre>
</li>
<li><p>Array.prototype.map 두 번째 인수로 add 메서드를 호출한 prefixer 객체를 가리키는 this를 전달. ES5에서 도입된 Array.prototype.map은 두 번째 인수로 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있음</p>
<pre><code class="language-js"> ...
 add(arr){
   return arr.map(function(item){
     return this.prefix + &#39; &#39; + item;
   }, this); // this에 바인딩된 값이 콜백 함수 내부의 this에 바인딩됨
 }
 ...</code></pre>
</li>
<li><p>Function.prototype.bind 메서드를 사용하여 add 메서드를 호출한 prefixer 객체를 가리키는 this를 바인딩</p>
<pre><code class="language-js"> ...
 add(arr){
   return arr.map(function(item){
     return this.prefix + &#39; &#39; + item;
   }.bind(this)); // this에 바인딩된 값이 콜백 함수 내부의 this에 바인딩됨
 }
 ...</code></pre>
</li>
</ol>
<p>ES6에서는 화살표 함수를 사용하여 콜백 함수 내부의 this 문제를 해결</p>
<pre><code class="language-js">class Prefixer {
  constructor(prefix){
    this.prefix = prefix;
  }

  add(arr){
    return arr.map(item =&gt; this.prefix + item);
  }
}

const prefixer = new Prefixer(&#39;-webkit-&#39;);
console.log(prefixer.add([&#39;transition&#39;, &#39;user-select&#39;]));
// [&#39;-webkit-transition&#39;, &#39;-webkit-user-select&#39;]</code></pre>
<p>화살표 함수는 함수 자체의 this 바인딩을 갖지 않기 때문에 <strong>화살표 함수 내부에서 this를 참조하면 상위스코프의 this를 그대로 참조 (lexical this)</strong></p>
<pre><code class="language-js">// 화살표 함수는 상위 스코프의 this 참조
() =&gt; this.x;

// 위와 동일하게 동작
(function(){ return this.x}).bind(this);</code></pre>
<p>화살표 함수와 화살표 함수가 중첩되어 있으면 스코프 체인 상에서 가장 가까운 상위 함수 중에서 화살표 함수가 아닌 함수의 this를 참조하고,</p>
<p>화살표 함수가 전역 함수라면 화살표 함수의 this는 전역 객체를 가리킴</p>
<p>프로퍼티에 할당한 화살표 함수도 스코프 체인 상에서 가장 가까운 상위 함수 중에서 화살표 함수가 아닌 함수의 this를 참조함</p>
<p>화살표 함수는 함수 자체의 this 바인딩을 갖지 않기 때문에 Function.prototype.call, Function.prototype.apply, Function.prototype.bind 메서드를 사용해도 화살표 함수 내부의 this를 교체할 수 없음 (화살표 함수도 위의 메서드를 호출할 수는 있는데 this를 교체할 순 없고 언제나 상위 스코프의 this 바인딩을 참조함)</p>
<pre><code class="language-js">window.x = 1;

const normal = function(){return this.x;};
const arrow = () =&gt; this.x;

console.log(normal.call({ x: 10})); // 10
console.log(arrow.call({ x: 10})); // 1</code></pre>
<p>메서드를 화살표 함수로 정의하는 것은 피해야 하는데, 화살표 함수 내부의 this가 호출한 객체를 가리키지 않고 상위 스코프의 this가 가리키는 객체를 가리키기 때문</p>
<p>메서드는 ES6 메서드 축약 표현으로 정의한 ES6 메서드를 사용하는 것이 좋다. </p>
<p>프로토타입 객체의 프로퍼티에 화살표 함수를 할당하는 경우도 동일한 문제 발생. 프로퍼티를 동적 추가할 때는 일반 함수를 할당</p>
<p>일반 함수가 아닌 ES6 메서드를 동적 추가하고 싶으면 객체 리터럴을 바인딩하고 프로토타입의 constructor 프로퍼티와 생성자 함수 간의 연결을 재설정</p>
<h3 id="2634-super">26.3.4 super</h3>
<p>화살표 함수는 함수 자체의 super 바인딩을 갖지 않으므로, <strong>화살표 함수 내부에서 super를 참조하면 상위 스코프의 super 참조</strong></p>
<pre><code class="language-js">class Base {
  constructor(name){
    this.name = name;
  }

  sayHi(){
    return &#39;Hi! ${this.name}&#39;;
  }
}

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

const derived = new Derived(&#39;Lee&#39;);
console.log(derived.sayHi()); // Hi! Lee how are you doing?</code></pre>
<p>super 내부 슬롯 [[HomeObject]]를 갖는 ES6 메서드 내에서만 사용할 수 있는 키워드.</p>
<p>sayHi 클래스 필드에 할당한 화살표 함수는 ES6 메서드는 아니지만 함수 자체의 super 바인딩을 갖지 않으므로 super를 참조해도 에러가 발생하지 않고, this와 마찬가지로 클래스 필드에 할당한 화살표 함수 내부에서 super를 참조하면 constructor 내부의 super 바인딩을 참조</p>
<h3 id="2635-arguments">26.3.5. arguments</h3>
<p>화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않음. 따라서 <strong>화살표 함수 내부 arguments를 참조하면 상위 스코프의 arguments 참조</strong></p>
<pre><code class="language-js">(function(){
  // 화살표 함수 foo의 arguments는 상위 스코프인 즉시 실행 함수의 argunments 를 가리킨다.
  const foo = () =&gt; console.log(arguments); // [Arguments] { &#39;0&#39;: 1, &#39;1&#39;: 2 }
  foo(3, 4);
}(1, 2));

// 화살표 함수 foo의 arguments는 상위 스코프인 전역의 arguments를 가리킨다.
// 하지만 전역에는 arguments 객체가 존재하지 않는다.
const foo = () =&gt; console.log(arguments); 
foo(1, 2); // ReferenceError: arguments is not defined</code></pre>
<p>arguments 객체는 함수를 정의할 때 매개변수의 개수를 확정할 수 없는 가변 인자 함수를 구현할 때 유용.</p>
<p>화살표 함수에서는 arguments 객체를 사용할 수 없음. 상위 스코프의 arguments 객체를 참조할 수는 있지만 화살표 함수 자신에게 전달된 인수 목록을 확인할 수 없고, 상위 함수에게 전달된 인수 목록을 참조하므로 도움이 안됨. =&gt; <strong>화살표 함수로 가변 인자 함수를 구현해야 할 때는 반드시 Rest 파라미터를 사용해야 함</strong></p>
<h2 id="264-rest-파라미터">26.4 Rest 파라미터</h2>
<h3 id="2641-기본-문법">26.4.1 기본 문법</h3>
<p>Rest 파라미터(나머지 매개변수)는 매개변수 이름 앞에 세개의 점 ... 을 붙여서 정의한 매개변수를 의미. <strong>Rest 파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받음</strong></p>
<pre><code class="language-js">function foo(...rest){
  // 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터
  console.log(rest); // [1, 2, 3, 4, 5]
}

foo(1, 2, 3, 4, 5);</code></pre>
<p>일반 매개변수와 Rest 파라미터는 함께 사용할 수 있음. 이때 함수에 전달된 인수들은 매개변수와 Rest 파라미터에 순차적으로 할당됨</p>
<pre><code class="language-js">function foo(param, ...rest){
  console.log(param); // 1
  console.log(rest); // [2, 3, 4, 5]
}

foo(1, 2, 3, 4, 5);

function bar(param1, param2, ...rest){
  console.log(param1); // 1
  console.log(param2); // 2
  console.log(rest); // [3, 4, 5]
}

bar(1, 2, 3, 4, 5)</code></pre>
<p>Rest 파라미터는 먼저 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 구성된 배열이므로, Rest 파라미터는 반드시 마지막 파라미터여야함</p>
<p>Rest 파라미터는 단 하나만 선언할 수 있음</p>
<p>Rest 파라미터는 함수 정의 시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티에 영향을 주지 않음</p>
<h3 id="2642-rest-파라미터와-arguments-객체">26.4.2 Rest 파라미터와 arguments 객체</h3>
<p>ES5 에서는 함수를 정의할 때 매개변수의 개수를 확정할 수 없는 가변 인자 함수의 경우 매개변수를 통해 인수를 전달받는 것이 불가능하므로 arguments 객체를 활용</p>
<p>arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있는 순회 가능한 유사 배열 객체이며, 함수 내부에서 지역 변수처럼 사용 가능</p>
<pre><code class="language-js">function sum(){
  console.log(arguments);
}

sum(1, 2); // {length: 2, &#39;0&#39;: 1, &#39;1&#39;: 2}</code></pre>
<p>하지만 arguments 객체는 배열이 아닌 유사 배열 객체이므로 배열 메서드를 사용하려면 .call / .apply 메서드를 이용해 arguments 객체를 배열로 변환해야됨</p>
<p>ES6에서는 rest 파라미터를 사용하여 가변 인자 함수의 인수 목록을 배열로 직접 전달받음</p>
<pre><code class="language-js">function sum(...args){
  return args.reduce((pre, cur) =&gt; pre + cur, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15</code></pre>
<p>함수와 ES6 메서드는 Rest 파라미터와 arguments 객체 모두 사용 가능. 화살표 함수는 함수 자체의 arguments를 갖지 않으므로 Rest 파라미터 사용해야됨</p>
<h2 id="265-매개변수-기본값">26.5 매개변수 기본값</h2>
<p>함수를 호출할 때 매개변수의 개수만큼 인수를 전달하는 것이 바람직하지만 그렇지 않은 경우에도 에러 발생 X. 자바스크립트 엔진이 매개변수 개수와 인수 개수 체크 안하기 때문</p>
<p>인수가 전달되지 않은 매개변수의 값은 undefined</p>
<pre><code class="language-js">function sum(x, y){
  return x + y;
}
console.log(sum(1)); // NaN</code></pre>
<p>따라서 매개변수에 인수가 전달되었는지 확인하여 인수가 전달되지 않은 경우 매개 변수에 기본값을 할당할 필요가 있음</p>
<pre><code class="language-js">function sum(x, y){
  // 인수가 전달되지 않은 경우 기본값 할당
  x = x || 0;
  y = y || 0;

  return x + y;
}

console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1</code></pre>
<p>ES6에서 도입된 매개변수 기본값을 사용하면 인수 체크 및 초기화 간소화 가능</p>
<pre><code class="language-js">function sum(x = 0, y = 0){
  return x + y;
}

console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1</code></pre>
<p>매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우와 undefined를 전달한 경우에만 유효</p>
<p>Rest 파라미터에는 기본값을 지정할 수 없음</p>
<p>매개변수 기본값은 함수 정의 시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티와 arguments 객체에 아무 영향도 주지 않음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<25. 클래스>]]></title>
            <link>https://velog.io/@st_hwang/25.-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@st_hwang/25.-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Wed, 13 Jul 2022 02:35:29 GMT</pubDate>
            <description><![CDATA[<h1 id="25-클래스">25. 클래스</h1>
<h2 id="251-클래스는-프로토타입의-문법적-설탕인가">25.1 클래스는 프로토타입의 문법적 설탕인가?</h2>
<p>자바스크립트는 <code>프로토타입 기반 객체지향 언어</code>이며, <strong>프로토타입 기반 객체지향 언어는 클래스가 필요없는 객체지향 프로그래밍 언어</strong></p>
<p>ES5에서는 클래스 없이도 생성자 함수와 프로토타입을 통해 객체지향 언어의 상속을 구현할 수 있다.</p>
<pre><code class="language-js">var Person = (function(){
  // 생성자 함수
  function Person(name){
    this.name = name;
  }

  // 프로토타입 메서드
  Person.prototype.sayHi = function(){
    console.log(&#39;Hi! My name is&#39; + this.name);
  }

  // 정적 메서드
  Person.sayHello = function(){
    console.log(&#39;Hello!&#39;)
  }
  // 생성자 함수 반환
  return Person;
}());

// 인스턴스 생성
var me = new Person(&#39;Lee&#39;);
me.sayHi(); // Hi! My name is Lee</code></pre>
<p>ES6에서 도입된 클래스는 클래스 기반 객제지향 프로그래밍 언어와 매우 흡사한 새로운 객체 생성 메커니즘을 제시</p>
<p>ES6의 클래스가 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새롭게 클래스 기반 객체지향 모델을 제공하는 것은 아님</p>
<p>사실 <strong>클래스는 함수</strong>이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있는 문법적 설탕이라고도 볼 수 있음</p>
<p>생성자 함수와 클래스는 프로토타입 기반의 객체지향을 구현했다는 점에서 유사하지만, 클래스가 생성자 함수 기반의 객체 생성 방식보다 견고하고 명료.</p>
<p>특히 클래스의 extends 와 super 키워드는 상속 관계 구현을 더욱 간결하고 명료하게 함.</p>
<blockquote>
<p> <code>클래스</code>는 프로토타입 기반 객체 생성 패턴의 단순한 문법적 설탕이라고 보기보단 <strong>새로운 객체 생성 메커니즘</strong>으로 보는 것이 좀 더 합당함</p>
</blockquote>
<p><strong>클래스와 생성자 함수의 차이</strong></p>
<ol>
<li>클래스를 new 연산자 없이 호출하면 에러가 발생하지만, 생성자 함수를 new 연산자 없이 호출하면 일반 함수로서 호출됨</li>
<li>클래스는 상속을 지원하는 extends와 super 키워드 제공. 생성자 함수는 지원안함</li>
<li>클래스는 호이스팅이 발생하지 않는 것처럼 동작. 하지만 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅 발생</li>
<li>클래스 내의 모든 코드에는 암묵적으로 strict mode 가 지정되어 실행되며 해체 불가. 하지만 생성자 함수는 strict mode가 암묵적으로 지정되지 않음</li>
<li>클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false로, 열거되지 않음</li>
</ol>
<h2 id="252-클래스-정의">25.2 클래스 정의</h2>
<p>클래스는 class 키워드를 사용하여 정의. 클래스 이름은 생성자 함수와 마찬가지로 파스칼 케이스 사용이 일반적 (사용안해도 에러 발생 X)</p>
<pre><code class="language-js">// 클래스 선언문
class Person {}</code></pre>
<p>일반적이진 않지만 함수와 마찬가지로 표현식으로 클래스를 정의할 수도 있음.</p>
<pre><code class="language-js">// 익명 클래스 표현식
const Person = class {};

// 기명 클래스 표현식
const Person = class MyClass {};</code></pre>
<p>클래스를 표현식으로 정의할 수 있다는 것은 <strong>클래스가 값으로 사용할 수 있는 일급 객체</strong>라는 것을 의미</p>
<ul>
<li>무명의 리터럴로 생성 가능. 즉, 런타임에 생성 가능</li>
<li>변수나 자료구조(객체, 배열 등)에 저장 가능</li>
<li>함수의 매개변수에 전달 가능</li>
<li>함수의 반환값으로 사용 가능</li>
</ul>
<p>클래스 몸체에서 정의할 수 있는 메서드는 constructor(생성자), 프로토타입 메서드, 정적 메서드 세 가지가 있음</p>
<pre><code class="language-js">// 클래스 선언문
class Person {
  // 생성자
  constructor(name){
    // 인스턴스 생성 및 초기화
    this.name = name; // name 프로퍼티는 public
  }

  // 프로토타입 메서드
  sayHi(){
    console.log(`Hi! My name is ${this.name}`);
  }

  // 정적 메서드
  static sayHello(){
    console.log(&#39;Hello!&#39;);
  }
}

// 인스턴스 생성
const me = new Person(&#39;Lee&#39;);

// 인스턴스의 프로퍼티 참조
console.log(me.name); // Lee
// 프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee
// 정적 메서드 호출
Person.sayHello(); // Hello!</code></pre>
<h2 id="253-클래스-호이스팅">25.3 클래스 호이스팅</h2>
<p>클래스는 함수로 평가됨</p>
<pre><code class="language-js">// 클래스 선언문
class Person{}

console.log(typeof Person); // function</code></pre>
<p>클래스 선언문으로 정의한 클래스는 런타임 이전에 먼저 평가되어 함수 객체 생성</p>
<p>이때 클래스가 평가되어 생성된 함수 객체는 생성자 함수로서 호출할 수 있는 함수, 즉 constructor</p>
<p>생성자 함수로서 호출할 수 있는 함수는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성. </p>
<p>프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재</p>
<p>단, 클래스는 클래스 정의 이전에 참조할 수 없음</p>
<p>클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생하지만, let, const 키워드로 선언한 변수처럼 호이스팅</p>
<p>따라서 클래스 선언문 이전에 일시적 사각지대 (Temporal Dead Zone; TDZ)에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작</p>
<p>모든 식별자는 호이스팅됨. 모든 선언문은 런타임 이전에 먼저 실행되기 때문에 </p>
<h2 id="254-인스턴스-생성">25.4 인스턴스 생성</h2>
<p>클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스 생성</p>
<p>클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로 반드시 new 연산자와 함께 호출해야됨</p>
<p>클래스를 가리키는 식별자를 사용해 인스턴스를 생성하지 않고 기명 클래스의 클래스 이름을 사용해 인스턴스를 생성하면 에러발생. 기명 함수 표현식과 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능하기 때문</p>
<pre><code class="language-js">const Person = class MyClass {};

// 함수 표현식과 마찬가지로 클래스를 가리키는 식별자로 인스턴스를 생성해야 함
const me = new Person();

// 클래스 이름 MyClass는 함수와 동일하게 클래스 몸체 내부에서만 유효한 식별자
console.log(MyClass); // ReferenceError : MyClass is not defined

const you = new MyClass(); // ReferenceError : MyClass is not defined</code></pre>
<h2 id="255-메서드">25.5 메서드</h2>
<p>클래스 몸체에서 정의할 수 있는 메서드</p>
<ol>
<li>생성자(constructor)</li>
<li>프로토타입 메서드 </li>
<li>정적 메서드</li>
</ol>
<h3 id="2551-constructor">25.5.1 constructor</h3>
<p>인스턴스를 생성하고 초기화하기 위한 특수한 메서드. constructor는 이름을 변경할 수 없음</p>
<pre><code class="language-js">class Person {
    // 생성자
    constructor(name){
      // 인스턴스 생성 및 초기화
        this.name = name;
    }
}</code></pre>
<p>클래스는 평가되어 <code>함수 객체</code>가 되며, 클래스도 함수 객체 고유의 프로퍼티를 갖고 있음. <strong>프로토타입과 연결되어 있으며 자신의 스코프 체인 구성</strong></p>
<p>모든 함수 객체가 가지고 있는 prototype 프로퍼티가 가리키는 <strong>프로토타입 객체의 constructor 프로퍼티는 클래스 자신을 가리킴</strong></p>
<p>이는 클래스가 인스턴스를 생성하는 생성자 함수라는 것을 의미하며, 즉 new 연산자와 함께 클래스를 호출하면 클래스는 인스턴스를 생성</p>
<p>constructor 내부에서 this로 추가한 프로퍼티는 클래스가 생성한 인스턴스의 프로퍼티가 됨. </p>
<p>constructor 내부의 this는 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스를 가리킴</p>
<p>constructor는 메서드로 해석되는 것이 아니라 클래스가 평가되어 생성한 함수 객체 코드의 일부가 됨. 클래스가 평가되면 constructor의 기술된 동작을 하는 함수 객체 생성</p>
<p><code>constructor</code>는 <strong>클래스 내 최대 한 개만 존재 가능</strong>하며, 생략 불가. 생략하면 빈 constructor에 의해 빈 객체 생성. </p>
<p>프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 constructor에 매개변수를 선언하고 인스턴스를 생성할 때 초기값 전달</p>
<p><code>constructor</code> 내에서는 <strong>인스턴스의 생성</strong>과 동시에 <strong>인스턴스 프로퍼티 추가를 통해 인스턴스의 초기화 실행</strong></p>
<pre><code class="language-js">class Person {
        // 인수로 인스턴스 초기화
    constructor(name, address){
      // 인스턴스 생성 및 초기화
      this.name = name;
      this.address = address;
    }
}

// 인수로 초기값 전달. 초기값은 constructor에 전달됨
const me = new Person(&#39;Lee&#39;, &#39;Seoul&#39;);
console.log(me); // Person {name: &quot;Lee&quot;, address: &quot;Seoul&quot;}</code></pre>
<p><code>constructor</code> 는 별도의 <strong>반환문을 갖지 않아야 함</strong>. new 연산자와 함께 클래스가 호출되면 생성자 함수와 동일하게 암묵적으로 this, 즉 인스턴스를 반환하기 때문</p>
<p>만약 this 가 아닌 다른 객체를 명시적으로 반환하면 this, 즉 인스턴스가 반환되지 못하고 return 문에 명시한 객체가 반환됨</p>
<p>하지만 명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환됨. <strong>constructor 내부에서 return문을 반드시 생략!</strong></p>
<h3 id="2552-프로토타입-메서드">25.5.2 프로토타입 메서드</h3>
<p>생성자 함수를 사용하여 인스턴스를 생성하는 경우, 프로토타입 메서드를 생성하기 위해서는 명시적으로 프로토타입에 메서드를 추가해야함</p>
<pre><code class="language-js">function Person(name){
    this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHi = function(){
    console.log(`Hi, my name is ${this.name}`);
}

const me = new Person(&#39;kim&#39;);
me.sayHi(); // Hi! My name is Lee</code></pre>
<p>클래스 몸체에서 정의한 메서드는, 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 됨</p>
<pre><code class="language-js">class Person {
  constructor(name){
    // 인스턴스 생성 및 초기화
    this.name = name;
  }
  // 프로토타입 메서드
  sayHi(){
    console.log(`Hi, my name is ${this.name}`);
  }
}

const me = new Person(&#39;Lee&#39;, &#39;Seoul&#39;);
me.sayHi(); // Hi! My name is Lee</code></pre>
<p>생성자 함수와 마찬가지로 <strong>클래스가 생성한 인스턴스는 프로토 타입 체인의 일원이 됨</strong></p>
<pre><code class="language-js">// me 객체의 프로토타입은 Person.prototype
Object.getPrototypeOf(me) === Person.prototype; // -&gt; true
me instanceof Person; // -&gt; true

// Person.prototype의 프로토타입은 Object.prototype
Object.getPrototypeOf(Person.prototype) === Object.prototype; // -&gt; true
me instanceof Objectl // -&gt; true

// me 객체의 constructor는 Person 클래스
me.constructor === Person; // -&gt; true</code></pre>
<h3 id="2553-정적-메서드">25.5.3 정적 메서드</h3>
<p>정적 메서드: 인스턴스를 생성하지 않아도 호출할 수 있는 메서드</p>
<p>생성자 함수는 정적 메서드를 생성하기 위해 명시적으로 생성자 함수에 메서드를 추가해야 함.</p>
<pre><code class="language-js">function Person(name){
    this.name = name;
}

// 정적 메서드
Person.sayHi = function(){
    console.log(`hi, my name is ${this.name}`);
};

// 정적 메서드 호출
const me = new Person(&#39;kim&#39;);</code></pre>
<p>클래스에서는 <strong>메서드에 static 키워드를 붙이면 정적 메서드(클래스 메서드)가 됨</strong></p>
<pre><code class="language-js">class Person {
  Person(name){
    this.name = name;
  }
  // 정적 메서드
  static sayHi(){
    console.log(&#39;Hi!&#39;)
    }
}</code></pre>
<p>정적 메서드는 클래스에 바인딩된 메서드가 됨.</p>
<p>클래스는 클래스 정의(클래스 선언문이나 클래스 표현식)가 평가되는 시점에 함수 객체가 되므로 인스턴스와 달리 별다른 생성 과정이 필요 없고, 정적 메서드는 클래스 정의 이후 인스턴스를 생성하지 않아도 호출할 수 있음.</p>
<p>정적 메서드는 프로토타입 메서드처럼 인스턴스로 호출하지 않고 클래스로 호출</p>
<p>정적 메서드는 인스턴스로 호출할 수 없음. 인스턴스의 프로토타입 체인 상에는 클래스가 존재하지 않기 때문에 인스턴스로 클래스의 메서드를 상속받을 수 없다.</p>
<h3 id="2554-정적-메서드와-프로토타입-메서드의-차이">25.5.4 정적 메서드와 프로토타입 메서드의 차이</h3>
<ol>
<li>정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다름</li>
<li>정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출</li>
<li>정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만, 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있음</li>
</ol>
<p>​        =&gt; 메서드 내부에서 인스턴스 프로퍼티를 참조하려면 this 사용하고, 프로토타입 메서드로 정의해야함. this 사용하지 않는 메서드는 정적 메서드로 정의하는 것이 좋음</p>
<p>​            클래스 또는 생성자 함수를 하나의 네임스페이스로 사용하여 정적 메서드로 모아놓으면 이름 충돌 가능성을 줄여 주고 관련 함수 구조화할 수 있는 효과</p>
<h3 id="2555-클래스에서-정의한-메서드의-특징">25.5.5 클래스에서 정의한 메서드의 특징</h3>
<ol>
<li>function 키워드를 생략한 메서드 축약 표현 사용</li>
<li>객체 리터럴과는 다르게 클래스에 메서드를 정의할 때는 콤마가 필요없음</li>
<li>암묵적으로 strict mode로 실행</li>
<li>for ... in 문이나 Object.keys 등으로 열거할 수 없음. 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false</li>
<li>내부 메서드 [[Constructor]]를 갖지 않는 non-constructor. 따라서 new 연산자와 함께 호출할 수 없음</li>
</ol>
<h2 id="256--클래스의-인스턴스-생성-과정">25.6  클래스의 인스턴스 생성 과정</h2>
<h4 id="1-인스턴스-생성과-this-바인딩">1. 인스턴스 생성과 this 바인딩</h4>
<p>new 연산자와 함께 클래스를 호출하면 <strong>constructor 의 내부 코드가 실행되기에 앞서 암묵적으로 빈 객체 생성(=클래스가 생성한 인스턴스)</strong></p>
<p>이 때 클래스가 생성한 인스턴스의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키는 객체 설정.</p>
<p>그리고 <strong>암묵적으로 생성된 빈 객체, 즉 인스턴스는 this에 바인딩</strong>. 따라서 constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킴</p>
<h4 id="2-인스턴스-초기화">2. 인스턴스 초기화</h4>
<p>constructor 내부 코드가 실행되어 <strong>this에 바인딩되어 있는 인스턴스 초기화</strong>. 즉, this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고  constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티값 초기화. constructor 생략시 이 과정도 생략</p>
<h4 id="3-인스턴스-반환">3. 인스턴스 반환</h4>
<p>클래스의 모든 처리가 끝나면 <strong>완성된 인스턴스가 바인딩된 this가 암묵적으로 반환</strong>됨</p>
<h2 id="257-프로퍼티">25.7 프로퍼티</h2>
<h3 id="2571-인스턴스-프로퍼티">25.7.1 인스턴스 프로퍼티</h3>
<p>인스턴스 프로퍼티는 항상 constructor 내부에서 정의해야 함. constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 됨</p>
<p>자바스크립트에서는 접근 제한자를 지원하지 않기 때문에 인스턴스 프로퍼티는 언제나 public</p>
<h3 id="2572-접근자-프로퍼티">25.7.2 접근자 프로퍼티</h3>
<p>접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티 (getter / setter)</p>
<p>getter는 인스턴스 프로퍼티에 접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용. 리턴 필수</p>
<p>setter는 인스턴스 프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 정의. 매개변수(1개만) 필수</p>
<pre><code class="language-js">class Person {
  constructor(firstName, lastName){
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // fullName은 접근자 함수로 구성된 접근자 프로퍼티
  // getter 함수
  get fullName(){
    return `${this.firstName} ${this.lastName}`
  }

  // setter 함수
  set fullName(name){
    [this.firstName, this.lastName] = name.split()
  }
}

const me = new Person(&#39;Ungmo&#39;, &#39;Lee&#39;);

// 데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(`${me.firstName} ${me.lastName}`); // Ungmo Lee

// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출됨
me.fullName = &#39;Heegun Lee&#39;;
console.log(me); // {fisrtName: &quot;Heegun&quot;, lastName: &quot;Lee&quot;}

// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출됨
console.log(me.fullName); // Heegun Lee

// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
console.log(Object.getOwnPropertyDesciptor(Person.prototype, &#39;fullName&#39;));
// {get: f, set: f, enumerable: false, configurable: true}</code></pre>
<p>getter / setter 는 인스턴스 프로퍼티처럼 사용되며, 호출되는 것이 아니라 프로퍼티처럼 참조하는 형식으로 사용하며, 참조 시에 내부적으로 호출됨</p>
<h3 id="2573-클래스-필드-정의-제안">25.7.3 클래스 필드 정의 제안</h3>
<p>클래스 필드 : 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어</p>
<p>자바스크립트의 클래스에서 인스턴스 프로퍼티를 선언하고 초기화하려면 반드시 constructor 내부에서 this에 프로퍼티를 추가해야함</p>
<p>또한, 자바스크립트의 클래스에서 인스턴스 프로퍼티를 참조하려면 반드시 this를 사용하여 참조해야 함</p>
<p>자바스크립트의 클래스 몸체에는 메서드만 선언할 수 있으며, 자바처럼 클래스 몸체에 클래스 필드를 선언하면 SyntaxError 발생</p>
<p>자바스크립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 새로운 표준사항인 &#39;Class field declarations&#39;가 TC39 프로세스 stage3에 제안되어 있음</p>
<p>최신 브라우저와 최신 Node.js에서는 클래스 필드를 클래스 몸체에 정의할 수 있음</p>
<pre><code class="language-js">class Person {
  // 클래스 필드 정의
  name = &#39;kim&#39;;
}

const me = new Person();
console.log(me); // Person {name: &quot;Lee&quot;}</code></pre>
<p>클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드를 바인딩 해서는 안된다. this 는 클래스의 constructor와 메서드 내에서만 유효</p>
<pre><code class="language-js">class Person {
  // this에 클래스 필드를 바인딩해서는 안됨
  this.name = &#39;&#39;; // SyntaxError: Unexpected token &#39;.&#39;
}</code></pre>
<p>클래스 필드를 참조하는 경우 자바스크립트에서는 반드시 this를 사용해야 함. 클래스 필드에 초기값을 할당하지 않으면 undefined</p>
<p>인스턴스를 생성할 때 외부의 초기값으로 클래스 필드를 초기화할 필요가 있다면 constructor에서 클래스 필드를 초기화(클래스 필드를 정의할 필요가 없음)</p>
<p>함수는 일급 객체이므로 함수를 클래스 필드에 할당할 수 있고, 이 함수는 프로토타입 메서드가 아닌 인스턴스 메서드가 됨. 모든 클래스 필드는 인스턴스 프로퍼티가 되기 때문(권장X)</p>
<h4 id="2574-private-필드-정의-제안">25.7.4 private 필드 정의 제안</h4>
<p>인스턴스 프로퍼티는 기본적으로 public</p>
<p>TC39 프로세스 stage3에 private 필드를 정의할 수 있는 새로운 표준 사양이 제안되어 있음. 최신 브라우저와 최신 노드에서 이미 구현되어 있음</p>
<p>private 필드 선두에는 <code>#</code>을 붙여주고 참조할 때도 <code>#</code>을 붙여주어야 함</p>
<pre><code class="language-js">class Person {
    // private 필드 정의
    #name = &#39;&#39;;

    constructor(name){
      // private 필드 참조
      this.#name = name;
    }
}

cosnt me = new Person(&#39;Lee&#39;);
// private 필드 #name은 클래스 외부에서 참조할 수 없음
console.log(me.#name); // SyntaxError</code></pre>
<p>클래스 외부에서 private 필드에 접근하려면 접근자 프로퍼티를 통해 간접적으로 접근해야 함</p>
<pre><code class="language-js">class Person {
    // private 필드 정의
    #name = &#39;&#39;;

    constructor(name){
      this.#name = name;
    }

      // name은 접근자 프로퍼티
      get name(){
      return this.#name.trim();
    }
}

cosnt me = new Person(&#39;Lee&#39;);
console.log(me.name); // Lee</code></pre>
<p>private 필드는 반드시 클래스 몸체에 정의해야 함. constructor에 정의하면 에러 발생</p>
<h3 id="2575-static-필드-정의-제안">25.7.5 static 필드 정의 제안</h3>
<p>static public 필드, static private 필드, static private 메서드를 정의할 수 있는 표준 사양이 제안되어 있음. 최신 버젼에서는 이미 구현됨</p>
<pre><code class="language-js">class MyMath {
    // static public 필드 정의
    static PI = 22/7;

    // static private 필드 정의
    static #num = 10;

        // static 메서드
    static increment(){
        return ++MyMath.#num;
    }
}

console.log(MyMath.PI); // 3.14..
console.log(MyMath.increment()); // 11</code></pre>
<h2 id="258-상속에-의한-클래스-확장">25.8 상속에 의한 클래스 확장</h2>
<h3 id="2581-클래스-상속과-생성자-함수-상속">25.8.1 클래스 상속과 생성자 함수 상속</h3>
<p>프로토타입 기반 상속 : 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 개념</p>
<p><strong>클래스 상속 : 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의하는 것</strong></p>
<pre><code class="language-js">class Animal {
  constructor(age, weight){
    this.age = age;
    this.weight = weight;
  }

  eat(){ return &#39;eat&#39;; }
  move(){ return &#39;move&#39;; }
}

// 상속을 통해 Animal 클래스를 확장한 Bird 클래스
class Bird extends Animal {
  fly(){ return &#39;fly&#39;; }
}

const bird = new Bird(1, 5);

console.log(bird); // Bird {age: 1, weight: 5}
console.log(bird instanceof Bird); // true
console.log(bird instanceof Animal); // true

console.log(bird.eat()); // eat
console.log(bird.move()); // move
console.log(bird.fly()); // fly</code></pre>
<h3 id="2582-extends-키워드">25.8.2 extends 키워드</h3>
<p>상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받을 클래스를 정의한다.</p>
<pre><code class="language-js">// 수퍼(베이스/부모) 클래스
class Base {}

// 서브(파생/자식) 클래스
class Derived extends Base {}</code></pre>
<p>서브클래스 (파생/자식 클래스) : 상속을 통해 확장된 클래스</p>
<p>수퍼클래스 (베이스/부모 클래스) : 서브클래스에게 상속된 클래스</p>
<p>수퍼클래스와 서브클래스는 인스턴스의 프로토타입 체인뿐 아니라 클래스 간의 프로토타입 체인도 생성해서, 프로토타입 메서드 / 정적 메서드 모두 상속 가능하다.</p>
<h3 id="2583-동적-상속">25.8.3 동적 상속</h3>
<p>extends 키워드는 클래스뿐 아니라 생성자 함수를 상속받아서 클래스를 확장할 수도 있는데, extends 키워드 앞에는 반드시 클래스가 와야함</p>
<pre><code class="language-js">// 생성자 함수
function Base(a){
  this.a = a;
}

// 생성자 함수를 상속받는 서브클래스
class Derived extends Base {}

const derived = new Derived(1);
console.log(derived); // Derived {a: 1}</code></pre>
<p>extends 키워드 다음에는 클래스뿐 아니라 <strong>[[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있음</strong></p>
<p>이를 통해 동적으로 상속받을 대상을 결정할 수 있음</p>
<pre><code class="language-js">function Base(){}
class Base2 {}
let condition = true;

// 조건에 따라 동적으로 상속 대상을 결정하는 서브클래스
class Derived extends (condition ? Base1 : Base2) {}

const derived = new Derived();
console.log(derived); // Derived {}

console.log(derived instanceof Base1); // true
console.log(derived instanceof Base2); // false</code></pre>
<h3 id="2584--서브클래스의-constructor">25.8.4  서브클래스의 constructor</h3>
<p>클래스에서 constructor를 생략하면 클래스에 다음과 같이 비어있는 constructor가 암묵적으로 정의됨</p>
<pre><code class="language-js">constructor(){}</code></pre>
<p>서브클래스에서 constructor를 생략하면 클래스에 다음과 같은 constructor가 암묵적으로 정의됨</p>
<pre><code class="language-js">constructor(...args){ super(...args); }</code></pre>
<p><code>super()</code> 는 수퍼클래스의 constructor를 호출하여 인스턴스를 생성</p>
<h3 id="2585-super-키워드">25.8.5 super 키워드</h3>
<p>super 키워드는 함수처럼 호출할 수도 있고, this와 같이 식별자처럼 참조할 수도 있는 특수한 키워드</p>
<ul>
<li>super를 <code>호출</code>하면 수퍼클래스의 constructor를 호출</li>
<li>super를 <code>참조</code>하면 수퍼클래스의 메서드를 호출할 수 있음</li>
</ul>
<h4 id="super-호출">super 호출</h4>
<p><strong>super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출</strong></p>
<p>주의사항</p>
<ol>
<li>서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super를 호출해야함</li>
<li>서브클래스의 constructror에서 super를 호출하기 전에는 this를 참조할 수 없음</li>
<li>super는 반드시 서브클래스의 constructor에서만 호출하고, 서브클래스가 아닌 클래스에서 호출하면 에러발생</li>
</ol>
<h4 id="super-참조">super 참조</h4>
<p><strong>메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있음</strong></p>
<ol>
<li>서브클래스의 프로토타입 메서드 내에서 super.sayHi는 수퍼클래스의 프로토타입 메서드 sayHi를 가리킴</li>
</ol>
<pre><code class="language-js">class Base {
  constructor(name){
    this.name = name;
  }

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

class Derived extends Base {
  sayHi(){
    // super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킴
    return `${super.sayHi()}. how are you doing?`
  }
}

const derived = new Derived(&#39;Lee&#39;);
console.log(derived.sayHi()); // Hi! Lee. how are you doing?</code></pre>
<p>super 참조를 통해 수퍼클래스의 메서드를 참조하려면 <strong><code>super</code>가 수퍼클래스의 메서드가 바인딩된 객체, 즉 <code>수퍼클래스의 prototype 프로퍼티에 바인딩된 프로토타입</code>을 참조할 수 있어야 함</strong></p>
<p>super는 <strong><code>자신을 참조하고 있는 메서드가 바인딩되어 있는 객체의 프로토타입</code>을 가리킴</strong></p>
<p>super 참조가 동작하기 위해서는 super를 참조하고 있는 메서드가 바인딩되어있는 객체의 프로토타입을 찾을 수 있어야함. 이를 위해 메서드는 내부 슬롯 [[HomeObject]]를 가지며 자신을 바인딩하고 있는 객체를 가리킴</p>
<p>super 참조는 클래스뿐 아니라 객체 리터럴에서도 사용 가능</p>
<blockquote>
<p>주의할 것은 ES6의 메서드 축약 표현으로 정의된 함수만이 [[HomeObject]]를 가짐. 따라서 ES6의 메서드 축약 표현으로 정의된 함수만이 super 참조를 할 수 있음</p>
</blockquote>
<pre><code class="language-js">const obj = {
  // ES6의 메서드 축약 표현으로 정의한 메서드
  foo(){},
  // 일반 함수이므로 HomeObject를 갖지 않음
  bar: function(){}
}</code></pre>
<ol start="2">
<li>서브클래스의 정적 메서드 내에서 super.sayHi는 수퍼클래스의 정적 메서드 sayHi를 가리킴</li>
</ol>
<h3 id="2586-상속-클래스의-인스턴스-생성-과정">25.8.6 상속 클래스의 인스턴스 생성 과정</h3>
<p>상속 관계에 있는 두 클래스가 협력하며 인스턴스를 생성하는 과정은 조금 더 복잡</p>
<p>서브 클래스가 new 연산자와 함께 호출되면 다음 과정을 통해 인스턴스를 생성함</p>
<h4 id="1-서브클래스의-super-호출">1. 서브클래스의 super 호출</h4>
<p>자바스크립트 엔진은 수퍼클래스와 서브클래스를 구분하기 위해 [[ConstructorKind]]를 가지는데 수퍼클래스는 [[ConstructorKind]]값이 &quot;base&quot;, 다른 클래스를 상속받는 서브클래스는 [[ConstructorKind]]값이 &quot;derived&quot;로 설정</p>
<p>다른 클래스를 상속받지 않는 클래스(그리고 생성자 함수도) 는 new 연산자와 함께 호출되었을 때 암묵적으로 빈 객체, 즉 인스턴스를 생성하고 이를 this에 바인딩</p>
<p><strong>서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성 위임. 이때문에 서브클래스 constructor에서 반드시 super를 호출해야함</strong></p>
<p>super가 없으면 에러가 발생하는데 인스턴스를 생성하는 주체는 수퍼클래스라서 수퍼클래스의 consturctor를 호출하는 super가 호출되야 인스턴스를 생성할 수 있기때문</p>
<h4 id="2-수퍼클래스의-인스턴스-생성과-this-바인딩">2. 수퍼클래스의 인스턴스 생성과 this 바인딩</h4>
<p>수퍼클래스의 constructor 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체 생성. 이 빈 객체가 클래스가 생성한 인스턴스. 이 빈 객체(인스턴스)는 this에 바인딩</p>
<p>인스턴스는 수퍼클래스가 생성한 것이지만 new 연산자와 함께 호출된 클래스는 서브클래스. new 연산자와 함께 호출되는 함수를 가리키는 <code>new.target</code>은 서브클래스를 가리킴</p>
<p>따라서 <strong>인스턴스는 <code>new.target</code>이 가리키는 서브클래스가 생성한 것으로 처리</strong>함</p>
<h4 id="3-수퍼클래스의-인스턴스-초기화">3. 수퍼클래스의 인스턴스 초기화</h4>
<p>수퍼클래스의 constructor가 실행되어 this에 바인딩되어 있는 인스턴스 초기화. </p>
<p>즉 <strong>this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티 추기화</strong></p>
<h4 id="4-서브클래스의-constructor로의-복귀와-this-바인딩">4. 서브클래스의 constructor로의 복귀와 this 바인딩</h4>
<p>super 호출이 종료되고 제어 흐름이 서브클래스 constructor로 돌아옴. 이때 <strong>super가 반환한 인스턴스가 this에 바인딩. 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용</strong></p>
<p>이처럼 super가 호출되지 않으면 인스턴스가 생성되지 않으며, this 바인딩도 할 수 없음. 서브클래스의 constructor 에서 super를 호출하기 전에는 this를 참조할 수 없는 이유가 바로 이 때문. 따라서 서브클래스의 constructor 내부의 인스턴스 초기화는 반드시 super 호출 이후에 처리되어야 함</p>
<h4 id="5-서브클래스의-인스턴스-초기화">5. 서브클래스의 인스턴스 초기화</h4>
<p>super 호출 이후 서브클래스의 constructor에 기술되어 있는 인스턴스 초기화 실행. <strong>this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티 초기화</strong></p>
<h4 id="6-인스턴스-반환">6. 인스턴스 반환</h4>
<p>클래스의 모든 처리가 끝나면 <code>완성된 인스턴스가 바인딩된 this</code> 반환</p>
<h3 id="2587-표준-빌트인-생성자-함수-확장">25.8.7 표준 빌트인 생성자 함수 확장</h3>
<p>extends 키워드 다음에는 클래스뿐아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있음</p>
<p>따라서 String, Number, Array 같은 표준 빌트인 객체도 생성자 함수이므로 extends 키워드를 사용하여 확장할 수 있음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(자바스크립트) 비동기 한 번에 이해하기]]></title>
            <link>https://velog.io/@st_hwang/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@st_hwang/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 02 Jun 2022 22:35:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/st_hwang/post/43902cec-5632-440f-8b36-b0c91738a771/image.png" alt=""></p>
<h2 id="1-비동기가-뭐죠">1. 비동기가 뭐죠?</h2>
<p>자바스크립트는 기본적으로 동기적으로 동작한다. 그런데 자바스크립트를 통해 개발을 하다보면 비동기 처리라는 용어를 쉽게 접할 수 있다.</p>
<p>비동기 처리가 도대체 뭘까? 용어의 정의를 통해 알아보자.</p>
<ul>
<li><p><strong>동기적 (synchronous)</strong> :  코드가 한 줄씩 순서대로 실행</p>
</li>
<li><p><strong>비동기적 (asynchronous)</strong> : 코드가 순서대로 실행되지 않고, 언제 코드가 실행될지 예측할 수 없음</p>
</li>
</ul>
<p>예제 코드를 통해 알아보자.</p>
<pre><code class="language-js">console.log(&#39;1&#39;);
console.log(&#39;2&#39;);
console.log(&#39;3&#39;);</code></pre>
<p>위의 예제 코드를 실행하면 1 2 3 이 순서대로 실행된다. 이렇게 실행되는 것이 동기적으로 실행되는 것이다.</p>
<p>그렇다면 다시 아래 예제를 보자.</p>
<pre><code class="language-js">console.log(&#39;1&#39;);
setTimeout(() =&gt; console.log(&#39;2&#39;), 1000);
console.log(&#39;3&#39;);</code></pre>
<p>위의 예제 코드를 실행하면 1 2 3 이 순서대로 실행되지 않고, 1 3 2 순서대로 실행된다.</p>
<p>이렇듯 <strong>실행 순서를 예측할 수 없게 동작하는 것</strong>을 <code>비동기적</code>으로 실행된다고 한다.</p>
<p>자바스크립트에서는 이렇게 비동기적으로 동작하는 여러 요소가 있는데 대표적으로 Ajax, 이벤트 리스너, setTimeout 등이 있고, 리액트에서 자주 쓰이는 setState 도 비동기적으로 동작한다. (setState의 내부 로직을 살펴보면 이벤트 리스너와 관련이 있음)</p>
<blockquote>
<p>그렇다면 자바스크립트의 일부 요소는 왜 비동기적으로 동작할까?</p>
</blockquote>
<h2 id="2-비동기-왜씀">2. 비동기 왜씀</h2>
<p>위에서 사용한 예제 코드를 다시 가져와보자</p>
<pre><code class="language-js">console.log(&#39;1&#39;);
setTimeout(() =&gt; console.log(&#39;2&#39;), 10000); // delay를 10초로 늘려보았다.
console.log(&#39;3&#39;);</code></pre>
<p>setTimeout 함수의 delay 시간을 10초로 늘려보았다. 그러면 1 3 이 출력된 후 10초 후에 2가 출력된다.</p>
<p>근데 setTimeout 함수가 비동기적으로 동작하지 않는다고 생각해보면 1이 먼저 출력되고 10초 후에 2가 출력되고, 그 후에 3이 출력된다.</p>
<p>너무 <code>비효율적</code>이다. 만약에 Ajax가 동기적으로 실행된다고 생각해보면 서버에 데이터를 요청하고 다시 응답을 받기까지 아무 코드도 실행되지 않을 것이다. (브라우저 잠깐 마비됨...)</p>
<p>이벤트 리스너면 더 최악이다. 온클릭 이벤트가 동기적으로 실행된다고 생각하면 클릭하기 전에 다른 코드가 실행이 안될 것이다.</p>
<p>다시 말해서 <strong>언제 끝날지 모르는 코드를 비동기적으로 처리해주는게 훨씬 더 <code>효율적</code></strong>이기 때문에 비동기적으로 처리해주는 것이다.</p>
<h2 id="3-비동기적으로-동작하는-원리">3. 비동기적으로 동작하는 원리</h2>
<p>이렇듯 보다 효율적으로 동작하기 위한 비동기적인 처리의 동작 원리는 무엇일까?</p>
<p>여기서부터는 자바스크립트 엔진과 웹 브라우저 동작 원리를 같이 알 필요가 있다. (최대한 간단히 비동기 처리를 이해할 정도로만 설명해보겠다.)</p>
<h3 id="3-1-자바스크립트는-싱글-스레드임">3-1 자바스크립트는 싱글 스레드임</h3>
<p>비동기적 동작의 원리를 위해하기 위해서는 몇 가지 개념에 대해 짚고 넘어가야 한다.</p>
<p>우선 자바스크립트는 싱글 스레드 방식으로 동작한다는 것이다.</p>
<p><code>스레드</code>는 <strong>프로세스 내에서 실행되는 흐름의 단위</strong>이며, 스레드는 각자의 스택을 가진다.</p>
<p>멀티 스레드인 경우 스레드의 수 만큼 각각의 스택이 있지만, <strong>자바스크립트는 싱글 스레드이기 때문에 하나의 스택</strong>을 가진다.</p>
<p><code>스택(stack)</code>은 <strong>자바스크립트에서 실행해야하는 함수들을 순차적으로 담아 처리하는 공간</strong>이며, 후입선출(LIFO)의 특징이 있다.</p>
<p>자바스크립트는 <strong>스택이 하나이기 때문에 기본적으로 한 번에 코드 하나씩 처리</strong>된다.</p>
<p>메모리 할당이 일어나는 메모리 힙이라는 것도 있는데 이는 우선 설명에서 제하겠다.</p>
<p>또 콜백 큐, 이벤트 루프라는 개념도 비동기적 처리와 연관이 있다.</p>
<p><code>큐(queue)</code>는 대기소라는 뜻을 가지고 있는데 콜백 큐는 <strong>콜백 함수를 보관</strong>하며, 스택과는 반대로 선입선출(FIFO)되는 자료구조이다.</p>
<h3 id="3-2-뭔소리임-그래서-어떻게-동작함">3-2 뭔소리임. 그래서 어떻게 동작함?</h3>
<p>자바스크립트는 기본적으로 하나의 스택에서 코드 하나씩 처리되지만, 웹 브라우저에서 제공되는 <code>Web API</code> 경우 조금 다르다.</p>
<p><strong>Web API(DOM, Ajax, setTimeout, 이벤트 리스너 등)는 자바스크립트 엔진의 스레드에서 실행되는 것이 아니라 다른 스레드를 쓴다.</strong></p>
<p>즉, <strong>자바스크립트는 싱글 스레드이지만 브라우저에 의해 멀티 스레드처럼 동작</strong>할 때가 있다.</p>
<p>그림을 통해 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/4f7a5321-3c39-4cab-a7c8-fc2cbdb251e7/image.png" alt=""></p>
<p><code>스택</code>에서는 한 번에 한 줄 씩 코드가 실행되는데, 먼저 console.log(&#39;1&#39;); 을 실행하고, 이를 스택에서 제거한다. <strong>(1 출력됨)</strong></p>
<p>setTimeout() 함수는 Web API 이기 때문에 자바스크립트 엔진에서 처리하는 것이 아니라 <code>Web API</code> 에서 처리를 하게 된다.</p>
<p><code>스택</code>은 console.log(&#39;3&#39;); 을 실행하고, 이를 <code>스택</code>에서 제거하는데 <strong>(3 출력됨)</strong>, 이와 동시에 <code>Web API</code> 에서 setTimeout() 함수 처리가 진행된다.</p>
<p>setTimeout() 함수가 처리 완료되면 (정해진 시간이 다되면), Web API는 setTimeout() 함수의 콜백 함수를 <code>콜백 큐</code>로 보낸다.</p>
<p>(Ajax의 경우 네트워크 통신이 완료되면 콜백 함수를 콜백 큐로 보내고, 이벤트 리스너는 이벤트가 실행되는 조건에 콜백 함수를 큐로 보낸다.)</p>
<p><code>콜백 큐</code>로 보내진 콜백 함수는 잠시 대기하게 되는데, 이 때 <code>이벤트 루프</code>가 스택이 비었으면 <code>콜백 큐</code>에 있는 콜백 함수를 스택으로 올린다.</p>
<p><code>스택</code>으로 보내진 콜백 함수( console.log(&#39;2&#39;); )는 곧바로 실행된 후에 <code>스택</code>에서 제거된다. <strong>(2 출력됨)</strong></p>
<blockquote>
<p>이벤트 루프는 스택이 비었으면 콜백 큐에 콜백 함수가 있는지 확인하고 보내는 작업을 &#39;계속&#39;하기 때문에 루프라고 함</p>
</blockquote>
<p>이러한 과정을 거쳐서 setTimeout() 함수는 비동기적으로 동작하게 된다. 다른 Web API도 같은 과정을 거쳐 비동기적으로 동작한다.</p>
<h2 id="4-비동기-처리-콜백-함수-promise-asyncawait">4. 비동기 처리 (콜백 함수, Promise, async/await)</h2>
<p>자바스크립트에서 비동기를 왜 쓰는지, 비동기가 어떻게 동작하는지 알아봤다. </p>
<p>그런데 비동기 요소들은 가끔 예상치 못한 오류를 일으키곤 한다. 언제 실행될지 예측할 수 없어서 개발자의 의도와 코드 실행의 순서가 다를 수 있기 때문이다.</p>
<p>가령 Ajax를 사용해서 네트워크 통신을 통해 얻을 수 있는 response 객체의 data 라는 값이 있다고 가정해보자.</p>
<pre><code class="language-js">const getData = () =&gt; {
  const response = Ajax로 데이터 받아오기();
  if(response.data){
      console.log(&quot;성공&quot;);
  } else {
      console.log(&quot;실패&quot;);
  }
};</code></pre>
<p>이 때, 위의 코드를 실행하면 무엇을 출력할지 예측할 수 있을까? if 문이 실행되는 시점에 response 값을 받아왔을지 받아오는 중인지 모르기 때문에 예측할 수 없다.</p>
<p>따라서, 비동기 요소들을 내가 의도한 순서대로 처리해야 될 필요가 발생할 수 있는데, 이 때 사용되는 것이 바로 <code>콜백 함수</code>, <code>Promise 객체</code>, <code>async/await</code> 이다.</p>
<h3 id="4-1-콜백-함수">4-1 콜백 함수</h3>
<h4 id="4-1-1-콜백-함수란">4-1-1 콜백 함수란?</h4>
<p><code>콜백 함수</code>는 <strong>다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수</strong>를 뜻한다.</p>
<pre><code class="language-js">setTimeout(() =&gt; {
  console.log(&#39;1&#39;);
  console.log(&#39;2&#39;);
  console.log(&#39;3&#39;);
, 1000);</code></pre>
<p>아까 위의 예제 코드를 이런 식으로 변형한다고 가정해본다면 1초후에 1 2 3 순서대로 출력될 것이다.</p>
<p>모든 코드를 setTimeout() 함수의 콜백 함수로 포함시켰기 때문이다.</p>
<p>이렇듯 비동기적 함수에서의 콜백 함수는 백그라운드 코드 실행이 끝나면 호출되어, 다음 작업을 실행하게 할 수 있다.</p>
<p>위의 getData 함수를 콜백 함수를 이용해서 아래처럼 작성하면 데이터를 받아오는 함수의 실행이 끝나면 콜백 함수가 동작할 것이다.</p>
<pre><code class="language-js">const getData = () =&gt; {
  Ajax로 데이터 받아오기(() =&gt; {
    if(데이터받아오기 성공){
        console.log(&quot;성공&quot;);
    } else {
        console.log(&quot;실패&quot;);
    }  
  });
};</code></pre>
<p><strong>비동기적 함수를 내가 원하는 순서대로 처리하고 싶으면 <code>비동기적 함수가 실행된 후에 실행되기를 원하는 함수</code>를 <code>비동기 함수의 콜백 함수</code>로 넘기면 된다.</strong></p>
<p>(모든 콜백 함수가 비동기적 처리를 위한 것은 아니다.)</p>
<h4 id="4-1-2-콜백-지옥">4-1-2 콜백 지옥</h4>
<p>이렇게 콜백 함수를 통해 내가 원하는 순서대로 비동기 처리를 할 수 있는데, 콜백 함수를 너무 남발하다보면 콜백 지옥이라는 것을 경험하게 된다.</p>
<pre><code class="language-js">setTimeout(() =&gt; {
    console.log(1); // 1초 뒤에 1 출력
    setTimeout(() =&gt; {
        console.log(2); // 1 출력되면 다시 1초 뒤에 2 출력
        setTimeout(() =&gt; {
            console.log(3); // 2 출력되면 다시 1초 뒤에 3 출력
        }, 1000)      
    }, 1000)
}, 1000)</code></pre>
<p>위의 코드를 실행하면 1초 간격으로 1 2 3 이 출력된다. 콜백 함수 3개가 중첩되어 있는데 3개를 중첩한 것만으로 <strong>가독성이 현저하게 떨어지게 된다.</strong></p>
<p>가독성이 떨어지면 로직을 이해하기도 힘들뿐더러, 에러나 디버깅 작업도 굉장히 힘들다.</p>
<h3 id="4-2-promise-es6">4-2 Promise (ES6)</h3>
<h4 id="4-2-1-promise-쓰면-콜백-지옥-어느-정도-해결-가능">4-2-1 Promise 쓰면 콜백 지옥 어느 정도 해결 가능</h4>
<p><code>Promise</code>는 <strong>자바스크립트에서 제공하는 비동기를 간편하게 처리할수 있도록 도와주는 객체</strong>로, 비동기 <strong>처리 상태</strong>와 <strong>처리 결과</strong>를 관리한다.</p>
<p>비동기적 기능을 수행하고 나서 정상적으로 기능이 수행됐다면 성공 메시지와 처리된 값을 전달하고, 예상치 못한 문제가 발생하면 에러를 전달한다.</p>
<p>Promise 가 콜백 함수에 비해 가지는 <code>장점</code>은 다음과 같다.</p>
<ol>
<li>.then 메서드를 이용해서 순차적으로 실행하기 때문에 코드가 옆으로 길어지지 않는다. (콜백 함수보다는 <strong>가독성 좋음</strong>)</li>
<li>.catch 메서드를 이용해서 비동기 처리가 실패했을 때 실행할 코드를 짤 수 있다. (<strong>에러처리 가능</strong>. 콜백 함수는 이런거 못함)</li>
</ol>
<h4 id="4-2-2-promise-생성">4-2-2 Promise 생성</h4>
<p>Promise 생성자 함수를 이용해서 Promise 객체를 만들 수 있다.</p>
<pre><code class="language-js">const getData = new Promise((resolve, reject) =&gt; {
  Ajax로 데이터 받아오기(() =&gt; {
      if(데이터받아오기 성공){
           resolve(&#39;성공&#39;);    
      } else {
           reject(&#39;실패&#39;);
      }  
  });
});</code></pre>
<p>위와 같이 Promise 생성자의 콜백 함수는 두 개의 함수를 매개 변수를 가지는데, 첫 번째가 <code>resolve</code>, 두 번째가 <code>reject</code> 함수이다.</p>
<p><code>resolve</code>는 <strong>비동기 작업을 성공적으로 완료되면 그 결과를 인수로 전달받으면서 호출</strong>되고, <code>reject</code>는 <strong>비동기 처리가 실패하면 에러를 인수로 전달받으면서 호출</strong>된다.</p>
<p>프로미스 객체는 또한 <strong>비동기 처리가 어떻게 진행되고 있는지 나타내는 상태(state)</strong> 정보를 갖는다. </p>
<p><code>&lt;pending&gt;</code> : 비동기 처리 진행 중 /  <code>&lt;fulfiled&gt;</code> : 비동기 처리 성공한 상태 /  <code>&lt;rejected&gt;</code> : 비동기 처리 실패한 상태</p>
<p>프로미스는 처음 생성되면 pending 상태이며, 비동기 처리를 성공하면 그 결과와 함께 resolve 함수를 호출하면서 프로미스 상태를 fulfiled로 변경하고, </p>
<p>실패하면 Error 결과와 함께 reject 함수를 호출하면서 프로미스 상태를 rejected로 변경한다. 이런 과정으로 프로미스 객체는 비동기 처리 결과와 상태를 가진다.</p>
<h4 id="4-2-3-프로미스-후속-처리-메서드">4-2-3 프로미스 후속 처리 메서드</h4>
<p>생성된 <code>Promise 객체</code>는 .then / .catch / .finally 라는 후속 처리 메서드를 가지는데, 프로미스 비동기 처리 상태가 변하면 후속 처리 메서드가 선택적으로 호출된다.</p>
<p><code>.then</code> 은 <strong>프로미스 기능이 원할히 이루어졌을 때 호출</strong> (then으로도 에러 처리를 할 수 있으나 성공했을 때만 사용하는 거 권장),</p>
<p><code>.catch</code> <strong>는 에러가 발생했을 때 호출</strong>,</p>
<p><code>.finally</code> 는 <strong>기능이 성공하던 실패하던 무조건 한 번 호출</strong>된다.</p>
<p>이 때 알아두어야할 특징은 <strong>후속 처리 메서드는 프로미스를 반환</strong>한다. 그래서 아래 예제처럼 후속 처리 메서드를 연속적으로 쓸 수 있는데 이를 <strong>프로미스 체이닝</strong>이라고 한다.</p>
<pre><code class="language-js">getData
    .then((value) =&gt; { // 프로미스가 resolve 상태인 경우 실행
        console.log(value); // 성공 
    })
    .catch(error =&gt; { // 프로미스에서 reject를 리턴했을 때 에러를 출력하지 않고 catch 블록 실행
        console.log(error); // 실패
    })
    .finally(() =&gt; { // 성공하던 실패하던 무조건 마지막에 호출
        console.log(&#39;finally&#39;); // finally
});</code></pre>
<h4 id="4-2-4-fetch">4-2-4 fetch</h4>
<p>Web API 중 http 요청 기능을 제공하는 <code>fetch</code>는 프로미스 기능을 지원한다.</p>
<p>fetch 함수는 <strong>http 응답을 나타내는 response 객체를 래핑한 Promise 객체를 반환</strong>한다. 따라서 위에서 설명한 프로미스 후속 처리 메서드를 통해 처리할 수 있다.</p>
<pre><code class="language-js">const getData = () =&gt; {
  fetch(&#39;http://example.com/movies.json&#39;)
  .then((response) =&gt; response.json())
  .then((data) =&gt; console.log(data));
};</code></pre>
<p>위의 예제처럼 <code>프로미스 체이닝</code>으로 처리할 수도 있고 필요한 경우 .catch를 이용해 에러 처리도 할 수 있어 간편하다. </p>
<p>이 글에선 fetch가 프로미스와 연관이 있다는 것만 간단하게 소개하고, 나중에 혹시 axios와 fetch를 비교해보는 글을 쓰게 된다면 그 때 자세한 사용법을 써보도록 하겠다.</p>
<p>참고 : <a href="https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch">https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch</a></p>
<h3 id="4-3-async--await-es8">4-3 async / await (ES8)</h3>
<h4 id="4-3-1-프로미스보다-쉬운-async--await">4-3-1 프로미스보다 쉬운 async / await</h4>
<p>ES8에서 도입된 async / await은 프로미스를 더 가독성 좋게, 비동기 처리를 동기 처리처럼 동작하도록 구현한다.</p>
<p>프로미스 체이닝을 통해서 프로미스 후속 처리를 계속 진행하다보면 가독성이 조금 떨어질 수 있는데, 이 때 async / await 을 통해 조금 더 간편하게 사용할 수 있다.</p>
<h4 id="4-3-2-async-함수">4-3-2 async 함수</h4>
<p>함수 앞에 async 키워드를 붙여 만들 수 있는 async 함수는 언제나 프로미스를 반환한다. (함수선언문 앞, 함수표현식 앞, 화살표 함수 앞 모두 사용 가능)</p>
<pre><code class="language-js">const getData = async () =&gt; {
    const response = Ajax로 데이터 받아오기();
    return response;
};

// 이제 getData를 프로미스처럼 사용할 수 있다.
getData
    .then((response) =&gt; response.json())
    .then((data) =&gt; console.log(data));</code></pre>
<h4 id="4-3-3-await-함수">4-3-3 await 함수</h4>
<p><code>await</code> 은 <strong>프로미스가 비동기 처리가 수행될 때까지 기다리다가 프로미스의 처리 결과를 반환</strong>한다. </p>
<p>await 키워드는 반드시 async 함수 내부에서 사용해야 하며, 반드시 프로미스 앞에서 사용해야 한다.</p>
<pre><code class="language-js">const getData = async () =&gt; {
  const response = Ajax로 데이터 받아오기();
  return response;
};

const Data출력 = async () =&gt; {
  const {data} = await getData(); // 여기서 getData가 처리될 때까지 잠시 대기
  console.log(data);
};
Data출력();</code></pre>
<p>위의 getData 를 실행하면 await 키워드는 getData 함수가 처리될 때까지 대기했다가 처리가 끝나면 data를 할당한다.</p>
<p>이렇듯 await 키워드를 사용하면 <strong>프로미스 상태가 변환되는 시간동안 대기하게 되므로 사용할 때 주의가 필요</strong>하다.</p>
<p>위에서 설명한 fetch 예제를 async / await 을 통해 변경하면 다음과 같다.</p>
<pre><code class="language-js">const getData = async () =&gt; {
    const response = await fetch(&#39;http://example.com/movies.json&#39;);
    const { data } = await response.json();
    console.log(data);
};</code></pre>
<h4 id="4-3-4-async--await-에서의-에러처리">4-3-4 async / await 에서의 에러처리</h4>
<p><strong>async / await 에서의 에러처리는 <code>try ... catch 문</code>을 이용</strong>해서 할 수 있다.</p>
<pre><code class="language-js">const getData = async () =&gt; {
  try {
      const response = await fetch(&#39;http://example.com/movies.json&#39;);
    const { data } = await response.json();
    console.log(data);  
  } catch(error) {
       console.log(error) 
  }
};</code></pre>
<h2 id="끝">끝!</h2>
<p>자바스크립트에서의 비동기 개념은 아주 많이 쓰이고 중요한 개념이므로 꼭 정리하고 가는 것이 좋다!
되도록 비동기 개념에 대해서 쉽게 이해할 수 있는 방향으로 글을 썼는데, 
보다 자세한 내용을 따로 공부해보는 것을 추천드립니다. 
특히 콜백 함수, 프로미스, async/await 진짜 진짜 중요함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<24. 클로저>]]></title>
            <link>https://velog.io/@st_hwang/24.-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@st_hwang/24.-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Thu, 02 Jun 2022 01:03:21 GMT</pubDate>
            <description><![CDATA[<h1 id="24-클로저">24. 클로저</h1>
<p>클로저는 자바스크립트 고유의 개념이 아니다. 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.</p>
<p>클로저는 자바스크립트 고유의 개념이 아니므로 클로저의 정의는 ECMAScript 사양에 등장하지 않는다. MDN에서는 클로저를 다음과 같이 정의한다.</p>
<blockquote>
<p>A closure is the combination of a function and the lexical envirionment within which that function was declared.</p>
<p>클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.</p>
</blockquote>
<h2 id="241-렉시컬-스코프">24.1 렉시컬 스코프</h2>
<p><strong>렉시컬 스코프(정적 스코프)</strong> : 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 <strong>함수를 어디에 정의했는지에 따라 상위 스코프 결정</strong> </p>
<pre><code class="language-js">const x = 1;

function foo(){
  const x = 10;
  bar();
}

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

foo(); // 1
bar(); // 1</code></pre>
<p>함수의 상위 스코프는 함수를 어디서 정의했는지로 결정되므로 foo 함수와 bar 함수의 상위 스코프는 전역</p>
<p>즉, 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않음</p>
<blockquote>
<p>스코프 : 실행 컨텍스트의 렉시컬 환경</p>
<p>스코프 체인 : 렉시컬 환경이 자신의 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결되는 것</p>
</blockquote>
<p>함수의 <code>상위 스코프</code> 결정 = 렉시컬 환경의 <code>외부 렉시컬 환경에 대한 참조값</code> 결정</p>
<p>=&gt; 렉시컬 스코프란 렉시컬 환경의 <code>외부 렉시컬 환경에 대한 참조</code>에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의한(환경)에 의해 결정되는 것</p>
<h2 id="242-함수-객체의-내부-슬롯-envirionment">24.2 함수 객체의 내부 슬롯 [[Envirionment]]</h2>
<p>렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과 상관없이, 자신이 정의된 환경(상위 스코프)를 기억해야함.</p>
<p>이를 위해 <strong>함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장</strong></p>
<p>함수 정의가 평가되어 함수 객체를 생성할 때, 자신의 정의된 환경에 의해 결정된 상위 스코프의 참조(=현재 실행 중인 실행 컨텍스트) 저장</p>
<p>함수 정의가 평가되어 함수 객체를 생성하는 시점 = 상위 함수가 평가 또는 실행되고 있는 시점 = 실행 중인 실행 컨텍스트가 상위 스코프의 실행 컨텍스트</p>
<blockquote>
<p>상위 스코프 : 함수 객체의 내부 슬롯 [[Environment]]에 저장된, 현재 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조. </p>
<p>​                     자신이 호출되었을 때 생성될 함수 렉시컬 환경의, 외부 렉시컬 환경에 대한 참조에 저장될 참조값</p>
</blockquote>
<h2 id="243-클로저와-렉시컬-환경">24.3 클로저와 렉시컬 환경</h2>
<pre><code class="language-js">const x = 1;

function outer() {
  const x = 10;
  const inner = function () { console.log(x) };
  return inner;
}

// outer 함수를 호출하면 outer함수는 중첩함수 inner를 반환하고 생명 주기를 마감
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거 됨
const innerFunc = outer();
innerFunc(); // 10</code></pre>
<p>outer 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 팝되어 제거될 때, </p>
<p>outer 함수의 지역 변수 x와 변수 값 10을 저장하고 있던 outer 함수의 실행 컨텍스트가 제거되었으므로, outer 함수의 지역 변수 x도 생명 주기 마감</p>
<p>그런데 이미 생명 주기가 종료되어 실행 컨텍스트 스택에서 제거된 outer 함수 지역 변수 x가 동작하듯이 innerFunc는 10을 출력</p>
<blockquote>
<p><strong>클로저</strong> : 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있는데, 이러한 중첩함수를 클로저라 함</p>
</blockquote>
<p>위 예제에서 outer 함수를 호출하면 outer 함수의 렉시컬 환경이 생성되고, outer 함수 객체의 <code>[[Environment]] 내부 슬롯에 저장된 전역 렉시컬 환경</code>을 outer 함수 렉시컬 환경의 <code>외부 렉시컬 환경에 대한 참조</code>에 할당 (상위스코프).</p>
<p>그 다음 중첩함수 inner가 평가될 때 자신의 [[Environment]] 내부 슬롯에 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 outer 함수의 렉시컬 환경을 상위 스코프로 저장</p>
<p>outer 함수의 실행이 종료되면 inner 함수를 반환하며 outer 함수의 생명 주기 종료. outer 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 제거.</p>
<blockquote>
<p>그러나, <code>outer 함수의 실행 컨텍스트</code>는 실행 컨텍스트에서 제거되지만 <code>outer 함수의 렉시컬 환경</code>까지 소멸되지는 않음</p>
<p>왜? outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조, inner 함수는 전역 변수 innerFunc에 의해 참조되므로 가비지 컬렉션의 대상이 안됨</p>
</blockquote>
<p>outer 함수가 반환한 inner 함수를 호출하면 inner 함수의 실행 컨텍스트 생성, 실행 컨텍스트 스택에 푸시.</p>
<p>렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 inner 함수 객체의 [[Envirionment]] 내부 슬롯에 저장되어 있는 참조값이 할당 (outer 함수 렉시컬 환경)</p>
<p>중첩 함수 inner는 외부 함수 outer보다 더 오래 생존. (클로저)</p>
<p>외부 함수보다 더 오래 생존한 중첩 함수는 외부 함수의 생존 여부(실행 컨텍스트의 생존 여부)와 상관없이 자신이 정의된 위치에 의해 결정된 상위 스코프를 기억. </p>
<p>이처럼 중첩 함수의 내부에서는 상위 스코프를 참조할 수 있으므로 상위 스코프의 식별자를 참조할 수 있고, 식별자의 값을 변경할 수도 있음</p>
<blockquote>
<p>자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저이지만, </p>
<p><strong>상위 스코프의 어떤 식별자도 참조하지 않는 함수</strong>는 브라우저 최적화를 통해 상위 스코프를 기억하지 않기 때문에 클로저라고 하지 않음</p>
<p><strong>중첩 함수가 상위 스코프의 식별자를 참조하고 있어도, 중첩 함수가 외부 함수보다 먼저 소멸되는 경우</strong>도 클로저라고 하지 않음</p>
</blockquote>
<blockquote>
<p><strong>클로저 : 중첩 함수가 상위 스코프의 식별자를 참조하고 있고, 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적</strong></p>
<p>클로저인 중첩 함수가 상위 스코프의 식별자 중 일부 식별자만 참조한다면 브라우저는 최적화를 통해 상위 스코프의 식별자 중 클로저가 참조하는 식별자만 기억</p>
<p>이 때 클로저가 참조하는 식별자를 <strong>자유 변수(free variable)</strong> 라고 함. 클로저는 자유 함수에 묶여 있는 함수라고 할 수 있음</p>
</blockquote>
<h2 id="244-클로저의-활용">24.4 클로저의 활용</h2>
<p>클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용. <strong>상태를 안전하게 은닉 (information hiding)</strong> 하고 <strong>특정 함수에게만 상태 변경을 허용하기 위해</strong> 사용</p>
<h3 id="예시-1">예시 1)</h3>
<pre><code class="language-js">// 카운트 상태 변수
let num = 0;

// 카운트 상태 변경 함수
const increase = function() {
  // 카운트 상태를 1만큼 증가시킨다.
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3</code></pre>
<p>이 때 카운트 상태 변수는 전역 변수를 통해 관리되고 있기 때문에 언제든지 누구나 접근할 수 있고 변경할 수 있음 (암묵적 결합 상태)</p>
<p>따라서 카운터 상태를 안전하게 변경하고 유지하기 위해서 increas 함수만이 num 변수를 참조하고 변경할 수 있게 하는 것이 바람직함. num 변수를 지역변수로 설정해야 함</p>
<pre><code class="language-js">// 카운트 상태 변경 함수
const increase = function(){
  // 카운트 상태 변수
  let num = 0;
  // 카운터 상태를 1만큼 증가시킨다.
  return ++num;
}

// 이전 상태를 유지 하지 못한다.
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1</code></pre>
<p>num 변수를 지역 변수로 변경하여 의도치 않은 상태 변경은 방지했지만, increase 함수가 호출될 때 마다 num이 다시 선언되고 초기화되기 때문에 이전 상태를 유지하지 못함</p>
<p>이전 상태를 유지할 수 있도록 <strong>클로저</strong>를 사용해야 함</p>
<pre><code class="language-js">// 카운트 상태 변경 함수
const increase = (function () {
  // 카운트 상태 변수
  let num = 0;

  //클로저
  return function() {
    return ++num;
  };
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3</code></pre>
<p>위 예제에서는 즉시 실행 함수가 호출되고 즉시 실행 함수가 반환한 함수가 increase 변수에 할당됨.</p>
<p><code>increase 변수에 할당된 함수</code>는 자신이 정의된 위치에 의해 결정된 상위 스코프(즉시 실행 함수의 렉시컬 환경)를 기억하는 <strong><code>클로저</code></strong></p>
<p>즉시 실행 함수는 호출된 이후 소멸되지만 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출</p>
<p>즉시 실행 함수가 반환한 <code>클로저</code>는 자신이 정의된 위치에 의해 결정된 상위 스코프(즉시 실행 함수의 렉시컬 환경)를 기억하고 있으므로, 카운트 상태를 유지하기 위한 자유 변수 num을 언제 어디서 호출하든지 참조하고 변경할 수 있음</p>
<p>즉시 실행 함수는 한 번만 실행되므로 increase가 호출될 때마다 num 변수가 재차 초기화될 일은 없고, num 변수는 외부에서 직접 접근할 수 없는 private 변수이므로 전역 변수를 사용했을 때와 같이 의도하지 않은 변경을 걱정할 필요도 없기 때문에 더 안정적인 프로그래밍이 가능</p>
<pre><code class="language-js">const counter = (function() {
  // 카운트 상태 변수
  let num = 0;

  // 클로저인 메서드를 갖는 객체를 반환
  // 객체 리터럴은 스코프를 만들지 않는다.
  // 따라서 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경
  return {
    // num : 0, // 프로퍼티는 public 하므로 은닉되지 않는다.
    increase() {
      return ++num;
    },
       decrease() {
      return num &gt; 0 ? --num : 0;
    }
  };
}());

console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0</code></pre>
<p>위 예제의 increase, decrease 메서드의 상위 스코프는 increase, decrease 메서드가 평가되는 시점에 실행 중인 실행 컨텍스트(즉시 실행 함수의 실행 컨텍스트) 의 렉시컬 환경 </p>
<p>따라서 increase, decrease 함수는 즉시 실행 함수 스코프의 식별자를 참조할 수 있음</p>
<blockquote>
<p>변수 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다.</p>
<p>클로저는 외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 <strong>함수형 프로그래밍</strong>에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 사용</p>
</blockquote>
<h3 id="예시2--함수형-프로그래밍에서의-클로저-활용">예시2 ) 함수형 프로그래밍에서의 클로저 활용</h3>
<pre><code class="language-js">// 함수를 인수로 전달받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저 반환
function makeCounter(perdicate){
  // 카운트 상태를 유지하기 위한 자유 변수
  let counter = 0;

  // 클로저 반환
  return function () {
    // 인수로 전달받은 보조 함수에 상태 변경을 위임
    counter = perdicate(counter);
    return counter;
  };
}

// 보조함수
function increase(n) {
  return ++n;
}

// 보조함수
function decrease(n) {
    return --n;
}

// 함수로 함수를 생성
// makeConter 함수는 보조 함수를 인수로 전달받아 함수를 반환
const increaser = makeCounter(increase) // 1️⃣
console.log(increaser()); // 1
console.log(increaser()); // 2

// increaser 함수와는 별개의 독립된 렉시컬 환경을 가지기 때문에 카운터 상태가 연동되지 않음
const decreaser = makeCounter(decrease) // 2️⃣
console.log(decreaser()); // -1
console.log(decreaser()); // -2</code></pre>
<p>makeCounter 함수를 호출해 함수를 반환할 때, 반환된 함수는 자신만의 독립된 렉시컬 환경을 가짐</p>
<p>1️⃣에서 makeCounter 함수를 호출하면 makeCounter 함수의 실행 컨텍스트가 생성되고 makeCounter 함수는 함수 객체를 생성하여 반환한 뒤 소멸.</p>
<p>이 때, makeCounter가 반환한 함수는 makeCounter 함수의 렉시컬 환경을 상위 스코프로서 기억하는 클로저이며, 전역 변수 increaser에 할당</p>
<p>2️⃣에서 makeCounter 함수를 호출하면 새로운 makeCounter 함수의 실행 컨텍스트가 생성되고 makeCounter 함수는 함수 객체를 생성하여 반환한 뒤 소멸.</p>
<p>makeCounter가 반환한 함수는 makeCounter 함수의 렉시컬 환경을 상위 스코프로서 기억하는 클로저이며, 전역 변수 decreaser에 할당</p>
<p>전역 변수 increaser와 decreaserdp 할당된 함수는 <strong>각각 자신만의 독립된 렉시컬 환경을 갖기 때문에 자유 변수 counter를 공유하지 않아</strong> 카운터 증감이 연동되지 않음</p>
<p>증감이 가능한 카운터를 만들려면 렉시컬 환경을 공유하는 클로저를 만들어야하며, 이를 위해서는 <strong>makeCounter 함수를 두 번 호출하지 말아야 함</strong></p>
<pre><code class="language-js">// 함수를 인수로 전달받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저 반환
const counter = (function () {
  // 카운터 상태를 유지하기 위한 자유 변수
  let counter = 0;

// 함수를 인수로 전달받는 클로저 반환
  return function (predicate){
    // 인수로 전달받은 보조 함수에 상태 변경 위임
    counter = predicate(counter);

    return counter;
  };
}());

// 보조함수
function increase(n) {
  return ++n;
}

// 보조함수
function decrease(n) {
    return --n;
}

// 보조 함수를 전달하여 호출
console.log(counter(increase)); // 1
console.log(counter(increase)); // 2

// 자유 변수 공유
console.log(counter(decrease)); // 1
console.log(counter(decrease)); // 0</code></pre>
<h2 id="245-캡슐화와-정보-은닉">24.5 캡슐화와 정보 은닉</h2>
<p><strong>캡슐화 (encapsulation)</strong> : 객체의 상태를 나타내는 <code>프로퍼티</code>와 프로퍼티를 참조하고 조작할 수 있는 동작인 <code>메서드</code>를 하나로 묶는 것</p>
<p><strong>정보은닉 (information hiding)</strong> : 적절치 못한 접근으로부터 객체의 상태가 변경되는 것을 방지해 정보를 보호, 객체 간의 상호 의존성(결합도)를 낮춤</p>
<p>자바스크립트는 private, public 같은 접근 제한자를 제공하지 않기 때문에, 객체의 모든 프로퍼티와 메서드는 기본적으로 public</p>
<pre><code class="language-js">function Person(name, age){
  this.name = name; // public
  let _age = age; // private

  // 인스턴스 메서드
  this.sayHi = function(){
    console.log(`Hi! My name is ${this.name}. I am ${_age}`)
  };
}

const me = new Person(&#39;Lee&#39;, 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined

const you = new Person(&#39;Kim&#39;, 30);
you.sayHi(); // H1! my name is Kim. I am 30.
console.log(me.name); // Kim
console.log(you._age); // undefined</code></pre>
<p>위 예제의 name 프로퍼티는 외부로 공개되어 있어서 자유롭게 참조하거나 변경할 수 있으므로 public</p>
<p>_age 변수는 Person 생성자 함수의 지역 변수. Person 생성자 함수 외부에서 참조하거나 변경할 수 없으므로 private</p>
<p>sayHi 메서드는 인스턴스 메서드이므로 Person 객체가 생성될 때 마다 중복 생성됨. 이를 prototype 메서드로 변경하면 중복 생성 방지 가능</p>
<pre><code class="language-js">function Person(name, age){
  this.name = name; // public
  let _age = age; // private
}

// 프로토타입 메서드
Person.prototype.sayHi = function(){
  // Person 생성자 함수의 지역 변수 _age를 참조할 수 없음
    console.log(`Hi! My name is ${this.name}. I am ${_age}`)
};</code></pre>
<p>Peerson.prototype.sayHi 메서드 내에서 Person 생성자 함수의 지역 변수 _age 를 참조할 수 없음</p>
<p>즉시 실행 함수를 사용하여 Person 생성자 함수와 Person.prototype.sayHi 메서드를 하나의 함수 내로 모으면 참조 가능</p>
<pre><code class="language-js">const Person = (function () {
  let _age = 0; // private

  // 생성자 함수
  function Person(name , age){
    this.name = name; // public
    _age = age;
  }

  // 프로토타입 메서드
  Person.prototype.sayHi = function() {
    console.log(_age);
  }

  // 생성자 함수 반환
  return Person;
}());

const me = new Person(&#39;Lee&#39;, 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined

const you = new Person(&#39;Kim&#39;, 30);
you.sayHi(); // H1! my name is Kim. I am 30.
console.log(me.name); // Kim
console.log(you._age); // undefined</code></pre>
<p>Person 생성자 함수와 Person 생성자 함수의 인스턴스가 상속받아 호출할 Person.prototype.sayHi 메서드는 즉시 실행 함수가 종료된 이후 호출되지만,</p>
<p>이미 종료되어 소멸한 즉시 실행 함수의 지역 변수 _age 를 참조할 수 있음 (클로저)</p>
<p>하지만 위의 코드도 Person 생성자 함수가 여러 개의 인스턴스를 생성할 경우 _age 변수의 상태가 유지되지 않는다는 문제가 있음</p>
<pre><code class="language-js">const me = new Person(&#39;Lee&#39;, 20);
me.sayHi(); // Hi! My name is Lee. I am 20.

const you = new Person(&#39;Kim&#39;, 30);
you.sayHi(); // H1! my name is Kim. I am 30.

// _age 값이 변경됨
me.sayHi(); // Hi! My name is Lee. I am 30.</code></pre>
<p>sayHi 메서드의 상위 스코프는 어떤 인스턴스로 호출하더라도 하나의 동일한 상위 스코프를 사용하기 때문에 _age 값이 유지되지 않음</p>
<blockquote>
<p>자바스크립트는 정보 은닉을 완전하게 지원하지 않음</p>
</blockquote>
<h2 id="246-자주-발생하는-실수">24.6 자주 발생하는 실수</h2>
<pre><code class="language-js">var funcs = [];

for (var i = 0 ; i &lt; 3 ; i++){
  funcs[i] = function() {return i;}
}

for (var j = 0; j &lt; funcs.length; j++){
  console.log(funcs[j]()) // 3, 3, 3
}</code></pre>
<p>위 예제에서는 0, 1, 2를 반환할 것 같지만, 3, 3, 3을 반환함.</p>
<p>var 키워드로 선언한 i 변수는 블록 레벨 스코프가 아닌 함수 레벨 스코프를 갖는 전역 변수이므로 0, 1, 2가 순차적으로 할당되고,</p>
<p>funcs 배열의 요소로 추가한 함수를 호출하면 전역 변수 i의 값을 참조하여 i의 값 3이 출력됨</p>
<pre><code class="language-js">var funcs = [];

for (var i = 0 ; i &lt; 3 ; i++){
  funcs[i] = (function(id){
    return function(){
      return id;
    };
  }(i))
}

for (var j = 0; j &lt; funcs.length; j++){
  console.log(funcs[j]()) // 0, 1, 2
}</code></pre>
<p>즉시 실행 함수는 전역 변수 i에 현재 할당되어 있는 값을 인수로 전달받아 매개변수 id에 할당한 후 중첩 함수(클로저)를 반환하고 종료</p>
<p>즉시 실행 함수가 반환한 함수는 func 배열에 순차적으로 저장. 이 때 즉시 실행 함수의 매개변수 id는 반환된 중첩함수의 상위 스코프에 존재하기 때문에 id는 자유 변수가 되어 값 유지</p>
<blockquote>
<p>ES6 let 키워드 사용하면 말끔하게 해결됨</p>
<p>for 문의 변수 선언문에서 let 키워드로 선언한 초기화 변수를 사용한 for문은 코드 블록이 반복 실행될 때마다 for문 코드 블록의 새로운 렉시컬 환경 생성</p>
</blockquote>
<p>또다른 방법으로는 함수형 프로그래밍 기법인 고차 함수 사용하는 기법이 있음</p>
<p>이 방법은 변수와 반복문의 사용을 억제할 수 있기 때문에 오류를 줄이고 가독성을 좋게 만듦</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<20. strick mode / 21. 빌트인 객체 / 22. this>]]></title>
            <link>https://velog.io/@st_hwang/20.-strick-mode-21.-%EB%B9%8C%ED%8A%B8%EC%9D%B8-%EA%B0%9D%EC%B2%B4-22.-this</link>
            <guid>https://velog.io/@st_hwang/20.-strick-mode-21.-%EB%B9%8C%ED%8A%B8%EC%9D%B8-%EA%B0%9D%EC%B2%B4-22.-this</guid>
            <pubDate>Thu, 02 Jun 2022 01:01:46 GMT</pubDate>
            <description><![CDATA[<h1 id="20-strict-mode">20. strict mode</h1>
<h2 id="201-strict-mode란">20.1 strict mode란?</h2>
<p>암묵적 전역 (implicit global) : 모든 스코프에 사용한 변수의 선언이 존재하지 않을 때 자바스크립트 엔진이 암묵적으로 전역 객체에 프로퍼티를 동적 생성하는 것</p>
<pre><code class="language-js">function foo(){
  x = 10;
}
foo();

console.log(x); // 10</code></pre>
<p>암묵적 전역은 개발자의 의도와는 상관없이 발생하기 때문에 오류를 발생시키는 원인이 될 수 있으니, 반드시 변수를 선언한 다음에 사용해야 함</p>
<p>하지만 오타나 문법 지식의 미비로 실수가 발생할 수 있기 때문에, 잠재적 오류를 발생시키기 어려운 개발 환경을 만들고 그 환경에서 개발하는 것이 좋음</p>
<p>이를 지원하기 위해 ES5부터 strcit mode(엄격 모드)가 추가되었고, strict mode는 자바스크립트 언어의 문법을 좀 더 엄격하게 적용시켜 오류를 발생시킬 가능성이 높거나 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 명시적인 에러를 발생시킴</p>
<p>ESLint 같은 린트 도구를 사용해도 strict mode 와 유사한 효과를 얻을 수 있는데 린트 도구는 정적 분석 기능을 통해 소스코드를 실행하기 전에 소스코드를 스캔하여 문법적 오류뿐 아니라 잠재적 오류까지 찾아내고 오류의 원인을 리포팅해줌 (참고 : <a href="https://poiemaweb.com/eslint">https://poiemaweb.com/eslint</a>)</p>
<h2 id="202-strict-mode의-적용">20.2 strict mode의 적용</h2>
<p>strict mode를 적용하려면 전역의 선두 또는 함수의 몸체의 선두에 <code>&#39;use strict&#39;;</code> 를 추가. 전역 선두에 추가하면 스크립트 전체에 strict mode 적용됨</p>
<pre><code class="language-js">&#39;use strict&#39;;

function foo(){
  x = 10; // ReferenceError : x is not defined
}
foo()</code></pre>
<p>함수 몸체의 선두에 추가하면 해당 함수와 중첩 함수에 strict mode 적용</p>
<pre><code class="language-js">function foo(){
  &#39;use strict&#39;;

  x = 10; // ReferenceError : x is not defined
}
foo()</code></pre>
<p>코드 선두에 &#39;use strict&#39;; 를 위치시키지 않으면 제대로 동작 안한다</p>
<pre><code class="language-js">function foo(){
  x = 10; // 에러를 발생시키지 않음
  &#39;use strict&#39;;
}
foo()</code></pre>
<h2 id="203-전역에-strict-mode를-적용하는-것은-피하자">20.3 전역에 strict mode를 적용하는 것은 피하자</h2>
<p>전역에 적용한 strict mode는 스크립트 단위로 적용됨</p>
<p>스크립트 단위로 적용된 strict mode는 다른 스크립트에 영향을 주지 않고 해당 스크립트에 한정되어 적용됨</p>
<p>strict mode 스크립트와 non-stirct mode 스크립트를 혼용하는 것은 오류를 발생시킬 수 있음</p>
<p>특히 외부 서드파티 라이브러리를 사용하는 경우 라이브러리가 non-strict mode인 경우도 있기 때문에 전역에 strict mode를 적용하는 것은 바람직하지 않음</p>
<p>이런 경우 즉시 실행 함수로 스크립트 전체를 감싸서 스코프를 구분하고 즉시 실행 함수 선두에 stirct mode 적용</p>
<pre><code class="language-js">// 즉시 실행 함수 선두에 strict mode 적용
(function (){
  &#39;use strict&#39;;

  // Do something
})</code></pre>
<h2 id="204-함수-단위로-stirct-mode를-적용하는-것도-피하자">20.4 함수 단위로 stirct mode를 적용하는 것도 피하자</h2>
<p>함수 단위로 strict mode를 적용할 수도 있으나,</p>
<p>어떤 함수는 strict mode를 적용하고 어떤 함수는 strict mode를 적용하지 않는 것은 바람직하지 않으며, 모든 함수에 일일이 strict mode를 적용하는 것도 번거롭다.</p>
<p>strict mode가 적용된 함수가 참조할 함수 외부의 컨텍스트에 strict mode를 적용하지 않는다면 문제가 발생할 수 있다.</p>
<pre><code class="language-js">(function(){
  // non-strict mode
  var let = 10; // 에러가 발생하지 않음

  function foo(){
    &#39;use struct&#39;;
    let = 20; // SyntaxError = Unexpected strict mode reserved word
  }
  foo();
});</code></pre>
<p><strong>따라서 strict mode는 즉시 실행 함수로 감싼 스크립트 단위로 적용하는 것이 바람직</strong></p>
<h2 id="205-strict-mode가-발생시키는-에러">20.5 strict mode가 발생시키는 에러</h2>
<h3 id="2051-암묵적-전역">20.5.1 암묵적 전역</h3>
<p>선언하지 않은 변수를 참조하면 ReferenceError 발생</p>
<pre><code class="language-Js">(function(){
  &#39;use strict&#39;;
  x = 1;
  console.log(x); // ReferenceError : x is not defined
}());</code></pre>
<h3 id="2052-변수-함수-매개변수의-삭제">20.5.2. 변수, 함수, 매개변수의 삭제</h3>
<p>delete 연산자로 변수, 함수, 배개변수를 삭제하면 SyntaxError 발생</p>
<pre><code class="language-Js">(function(){
  &#39;use strict&#39;;

  var x = 1;
  delete x; // SyntaxError : Delete of an unqualified identifier in strict mode.

  function foo(a){
    delete a; // SyntaxError : Delete of an unqualified identifier in strict mode.
  }
  delete foo; // SyntaxError : Delete of an unqualified identifier in strict mode.
}());</code></pre>
<h3 id="2053-매개변수-이름의-중복">20.5.3 매개변수 이름의 중복</h3>
<p>중복된 매개변수 이름을 사용하면 SyntaxError 발생</p>
<pre><code class="language-js">(function(){
  &#39;use strict&#39;;

  // SyntaxError : Dupulicate parameter name not allowed in this context
  function foo(x, y){
    return x + x;
  }
  console.log(foo(1, 2));
}());</code></pre>
<h3 id="2054-with문의-사용">20.5.4 with문의 사용</h3>
<p>with문을 사용하면 SyntaxError 발생</p>
<p>with문 : 전달된 객체를 스코프 체인에 추가</p>
<pre><code class="language-js">(function(){
  &#39;use strict&#39;

  // SyntaxError : strict mode code may not include a with statement
  with({ x : 1}){
    console.log(x);
  }
}());</code></pre>
<h2 id="206-strict-mode-적용에-의한-변화">20.6 strict mode 적용에 의한 변화</h2>
<h3 id="2061-일반-함수의-this">20.6.1 일반 함수의 this</h3>
<p>strict mode에서 함수를 일반 함수로서 호출하면 this에 undefined 바인딩. 생성자 함수가 아닌 일반 함수 내부에선 this를 사용할 필요가 없기 때문 (에러는 발생하지 않음)</p>
<h3 id="2062-arguments-객체">20.6.2 arguments 객체</h3>
<p>strict mode에서는 매개변수에 전달된 인수를 재할당하여 변경해도 arguments 객체에 반영되지 않음</p>
<pre><code class="language-js">(function(a){
  &#39;use strict&#39;;
  // 매개변수에 전달된 인수를 재할당하여 변경
  a = 2;

  // 변경된 인수가 arguments 객체에 반영되지 않음
  console.log(arguments); // { 0: 1, length: 1}
})());</code></pre>
<h1 id="21-빌트인-객체">21. 빌트인 객체</h1>
<h2 id="211-자바스크립트-객체의-분류">21.1 자바스크립트 객체의 분류</h2>
<ul>
<li><p>표준 빌트인 객체 (standard built-in objects / native objects / global objects)</p>
<blockquote>
<p>ECMAScript 사양에 정의된 객체. 애플리케이션 전역의 공통 기능 제공. 표준 빌트인 객체는 전역 객체의 프로퍼티로 제공되므로 별도의 선언없이 전역 변수처럼 참조 가능</p>
</blockquote>
</li>
<li><p>호스트 객체 (host objects)</p>
<blockquote>
<p>자바스크립트 실행 환경(브라우저나 node.js) 에서 추가로 제공하는 객체. </p>
<p>브라우저 환경에서는 DOM, BOM, Canvas, XMLHttpRequest, fetch, requestAnimationFram, SVG, Web Storage, Web Component, Web Worker 와 같은 클라이언트 사이드 Web API를 호스트 객체로 제공, Node.js 환경에서는 Node.js 고유의 API를 호스트 객체로 제공</p>
</blockquote>
</li>
<li><p>사용자 정의 객체 (User-defined objects)</p>
<blockquote>
<p>표준 빌트인 객체와 호스트 객체처럼 기본 제공되는 객체가 아닌 사용자가 직접 정의한 객체</p>
</blockquote>
</li>
</ul>
<h2 id="212-표준-빌트인-객체">21.2 표준 빌트인 객체</h2>
<p>자바스크립트는 Object, String, Number, Boolean, Symbol, Date, Math, RegExp, Array, Map/Set, WeakMap/WeakSet, Function, Promise, Reflect, Proxy, JSON, Error 등 40여개의 표준 빌트인 객체를 제공 (참고 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects</a>)</p>
<p>Math, Reflect, JSON을 제외한 표준 빌트인 객체는 모두 인스턴스를 생성할 수 있는 생성자 함수 객체. (생성자 함수로 호출이 가능)</p>
<p><code>생성자 함수인 표준 빌트인 객체가 생성한 인스턴스의 프로토타입</code>은 표준 빌트인 객체의 <code>prototype 프로퍼티에 바인딩된 객체</code>임</p>
<ul>
<li>String 생성자 함수로 생성한 String 인스턴스의 프로토타입은 String.prototype</li>
<li>Number 생성자 함수로 생성한 Number 인스턴스의 프로토타입은 Number.prototype ... 40개 모두 동일</li>
</ul>
<p>표준 빌트인 객체의 prototype 프로퍼티에 바인딩된 객체(ex. String.prototype)는 다양한 기능의 빌트인 프로토타입 메서드를 제공 </p>
<p>이 프로토타입 메서드는 생성자 함수로 생성된 모든 인스턴스가 상속해서 사용 가능</p>
<p>표준 빌트인 객체는 인스턴스 없이도 호출 가능한 빌트인 정적 메서드를 제공함</p>
<pre><code class="language-js">// Number 생성자 함수에 의한 Number 객체 생성
const numObj = new Number(1.5); // Number {1.5}

// toFixed는 Number.prototype의 프로토타입 메서드
// Number.prototype.toFixed는 소수점 자리를 반올림하여 문자열로 반환
console.log(numObj.toFixed()); // 2

// isInteger는 Number의 정적 메서드
// Number.isInteger는 인수가 정수(integer)인지 검사하여 그 결과를 Boolean으로 반환
console.log(Number.isInteger(0.5)); // false</code></pre>
<h2 id="213-원시값과-래퍼-객체">21.3 원시값과 래퍼 객체</h2>
<p>문자열, 숫자, 불리언 등의 원시값이 있는데도 문자열, 숫자, 불리언 객체를 생성하는 String, Number, Boolean 등의 표준 빌트인 생성자 함수가 존재하는 이유는 무엇일까?</p>
<pre><code class="language-js">const str = &#39;hello&#39;;

// 원시 타입인 문자열이 프로퍼티와 메서드를 갖고 있는 객체처럼 동작한다.
console.log(str.length); // 5
console.log(str.toUpperCase()); // HELLO</code></pre>
<p>원시값은 객체가 아니므로 프로퍼티와 메서드를 가질 수 없는데 원시값인 문자열이 객체처럼 동작</p>
<p>=&gt; 원시값에 대해 객체처럼 마침표 표기법(대괄호 표기법)으로 접근하면 자바스크립트 엔진이 일시적으로 원시값을 연관된 객체로 변환하기 때문</p>
<p><strong>래퍼 객체 (wrapper object)</strong> : 문자열, 숫자 불리언 값에 대해 객체처럼 접근하면 생성되는 임시 객체</p>
<blockquote>
<p>문자열에 대해 마침표 표기법으로 접근하면 래퍼 객체인 String 생성자 함수의 인스턴스가 생성되고 문자열은 래퍼 객체의 [[StringData]] 내부 슬롯에 할당</p>
<p>이 때 문자열 래퍼 객체인 String 생성자 함수의 인스턴스는 String.prototype의 메서드를 상속받아 사용 가능 </p>
<p>그 후래퍼 객체의 처리가 종료되면 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된 원시값으로 원래의 상태로 되돌리고, 래퍼 객체는 가비지 컬렉션의 대상이 됨</p>
</blockquote>
<p>문자열, 숫자, 불리언, 심벌은 래퍼 객체에 의해 객체처럼 사용할 수 있으므로, 표준 빌트인 객체의 프로토타입 메서드 또는 프로퍼티를 참조할 수 있다</p>
<p>따라서 <strong>String, Number, Boolean 생성자 함수를 New 연산자와 함께 호출하여 인스턴스를 생성할 필요가 없다!</strong></p>
<p>문자열, 숫자, 불리언, 심벌 이외의 원시값인 null, undefined는 래퍼 객체를 생성하지 않으므로 이 값을 객체처럼 사용하면 에러가 발생함</p>
<h2 id="214-전역-객체">21.4 전역 객체</h2>
<p><strong>전역 객체 (global object)</strong> : 코드가 실행되기 이전 단계에서 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 측수한 객체, 어떤 객체에도 속하지 않은 최상위 객체</p>
<p>환경에 따라 지칭하는 이름이 다름</p>
<ul>
<li>브라우저 : window, self, this, frames</li>
<li>Node.js : global</li>
<li>E11 이후 : globalThis 로 통일</li>
</ul>
<p>전역 객체는 표준 빌트인 객체와 환경에 따른 호스트 객체, var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 가짐</p>
<p><strong>전역 객체는 계층적 구조상 어떤 객체에도 속하지 않은 모든 빌트인 객체(표준 빌트인 객체와 호스트 객체)의 최상위 객체</strong>. 프로토타입 상속 관계상 최상위 객체라는 의미가 아님</p>
<p>전역 객체는 어떤 객체의 프로퍼티도 아니며 객체의 구조상 표준 빌트인 객체와 호스트 객체를 프로퍼티로 소유한다는 의미</p>
<p><strong><code>전역 객체의 특징</code></strong></p>
<ul>
<li>개발자가 의도적으로 생성할 수 없다. 전역 객체를 생성할 수 있는 생성자 함수가 제공되지 않는다.</li>
<li>전역 객체의 프로퍼티와 메서드를 참조할 때는 window(또는 global)를 생략할 수 있다.</li>
<li>Object, String, NUmber, Boolean, Function, Array, RegExp, Date, Match, Promise 와 같은 모든 표준 빌트인 객체를 프로퍼티로 가지고 있다.</li>
<li>실행 환경(브라우저, Node.js)에 따라 추가적으로 프로퍼티와 메서드를 갖는다. 브라우저에선 Web API를 호스트 객체로 제공하고 Node.js 에서는 고유의 API</li>
<li>var 키워드로 선언한 전역 변수와 선언하지 않은 변수에 값을 할당한 암묵적 전역, 그리고 전역 함수는 전역 객체의 프로퍼티가 된다.</li>
<li>let, const는 전역 객체의 프로퍼티가 아니라 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재</li>
<li>브라우저의 모든 자바스크립트 코드는 하나의 전역 객체 window를 공유함. 여러 개의 script 태그로 자바스크립트 코드를 분리해도 하나의 전역 객체 공유</li>
</ul>
<h3 id="2141-빌트인-전역-프로퍼티">21.4.1 빌트인 전역 프로퍼티</h3>
<p><strong>빌트인 전역 프로퍼티 (built-in global property)</strong> : 전역 객체의 프로퍼티</p>
<h5 id="infinity">Infinity</h5>
<blockquote>
<p>Infinity 프로퍼티는 무한대를 나타내는 숫자값 Infinity를 갖는다</p>
</blockquote>
<p><strong>NaN</strong></p>
<blockquote>
<p>NaN 프로퍼티는 숫자가 아님(Not-a-Number)을 타나내는 숫자값 NaN을 갖는다. NaN 프로퍼티는 Number.NaN 프로퍼티와 같다.</p>
</blockquote>
<p><strong>undefined</strong></p>
<blockquote>
<p>undefined 프로퍼티는 원시 타입 undefined를 값으로 갖는다</p>
</blockquote>
<h3 id="2142-빌트인-전역-함수">21.4.2 빌트인 전역 함수</h3>
<p><strong>빌트인 전역 함수 (built-in flobal function)</strong> : 애플리케이션 전역에서 호출할 수 있는 빌트인 함수로 전역 객체의 메서드</p>
<h5 id="eval">eval</h5>
<blockquote>
<p>자바스크립트 코드를 나타내는 문자열을 인수로 전달받고, 전달받은 문자열 코드가 표현식이면 문자열 코드를 런타임에 평가하여 값을 생성하고,</p>
<p>전달받은 인수가 표현식이 아닌 문이라면 eval 함수는 해당 문을 런타임에 실행</p>
</blockquote>
<pre><code class="language-js">// 표현식인 문
eval(&#39;1 + 2;&#39;); // -&gt; 3
// 표현식이 아닌 문
eval(&#39;var x = 5;&#39;); // -&gt; undefined

// eval 함수에 의해 런타임에 변수 선언문이 실행되어 x 변수가 선언됨
console.log(x); // 5</code></pre>
<p>eval 함수를 통해 사용자로부터 입력받은 콘텐츠를 실행하는 것은 보안에 매우 취약하며, eval 함수를 통해 실행되는 코드는 자바스크립트 엔진에 의해 최적화가 수행되지 않으므로 일반적인 코드 실행에 비해 처리 속도가 느리다. <strong>따라서, eval 함수의 사용은 금지해야 한다.</strong> 크롬에서 eval 함수 써보니까 되지도 않는다.</p>
<h5 id="isfinite">isFinite</h5>
<blockquote>
<p>전달받은 인수가 정상적인 유한수인지 검사하여 유한수면 true를 반환하고, 무한수면 false를 반환</p>
<p>전달받은 인수 타입이 숫자가 아니라면 숫자로 타입을 변환한 후 검사를 수행하고, 인수가 NaN으로 평가되는 값이면 false를 반환함</p>
</blockquote>
<pre><code class="language-js">isFinite(0); // true
isFinite(2e64); // true
isFinite(&quot;10&quot;); // true: &#39;10&#39; -&gt; 10
isFinite(null); // true: null -&gt; 0

isFinite(Infinity); // false
isFinite(NaN); // false
isFinite(&#39;Hello&#39;); // false</code></pre>
<h5 id="isnan">isNan</h5>
<blockquote>
<p>전달받은 인수가 NaN인지 검사하여 결과를 불리언 타입으로 반환</p>
</blockquote>
<pre><code class="language-js">isNaN(NaN); // true
isNaN(10); // false</code></pre>
<h5 id="parsefloat">parseFloat</h5>
<blockquote>
<p>전달받은 문자열 인수를 부동 소수점 숫자, 즉 실수로 해석하여 반환</p>
</blockquote>
<pre><code class="language-js">parseFloat(&quot;3.14&quot;); // 3.14
parseFloat(&quot;10.00&quot;); // 10

// 공백으로 나뉜 문자열은 첫 번째 문자열만 반환
parseFloat(&quot;34 45 67&quot;); // 34
parseFloat(&quot;40 years&quot;); // 40

// 첫 번째 문자열을 숫자로 변환 할 수 없으면 NaN 반환
parseFloat(&quot;years 40&quot;); // NaN

// 앞뒤 공백 무시
parseFloat(&quot; 40 &quot;); // 40</code></pre>
<h5 id="parseint">parseInt</h5>
<blockquote>
<p>전달받은 문자열 인수를 정수로 해석하여 반환. 두 번째 인수로 진법을 나타내는 기수(2~36)을 전달할 수 있고, 기수를 지정하면 첫 번째 인수로 전달된 문자열을 해당 기수의 숫자로 해석하여 반환. 반환값은 언제나 10진수</p>
</blockquote>
<pre><code class="language-js">// &#39;10&#39;을 10진수로 해석하고 그 결과를 10진수 정수로 반환한다.
parseInt(&quot;10&quot;); // 10

// &#39;10&#39;을 2진수로 해석하고 그 결과를 10진수 정수로 반환한다.
parseInt(&quot;10&quot;, 2); // 2

// &#39;10&#39;을 8진수로 해석하고 그 결과를 10진수 정수로 반환한다.
parseInt(&quot;10&quot;, 8); // 8

// &#39;10&#39;을 16진수로 해석하고 그 결과를 10진수 정수로 반환한다.
parseInt(&quot;10&quot;, 16); // 16</code></pre>
<p>참고로 기수를 지정하여 10진수 숫자를 해당 기수의 문자열로 변환하여 반환하고 싶을 때는 <code>Number.prototype.toString</code> 메서드 사용</p>
<pre><code class="language-js">const x = 15;

// 15를 2진수로 변환해서 문자열로 반환
x.toString(2); // &#39;1111&#39;
// 문자열 &#39;1111&#39;을 2진수로 해석하고 그 결과를 10진수 정수로 반환
parseInt(x.toString(2),2); // 15

// 15를 8진수로 변환해서 문자열로 반환
x.toString(8); // &#39;17&#39;

// 15를 16진수로 변환해서 문자열로 반환
x.toString(16); // &#39;f&#39;</code></pre>
<p>첫 번째 인수로 전달한 문자열이 해당 지수의 숫자로 변경될 수 없다면 NaN 반환</p>
<pre><code class="language-js">// &#39;A&#39;는 10진수로 해석할 수 없다.
parseInt(&quot;A0&quot;); // NaN

// &#39;2&#39;는 2진수로 해석할 수 없다.
parseInt(&quot;20&quot;, 2); // NaN</code></pre>
<h5 id="encodeuri--decodeuri">encodeURI / decodeURI</h5>
<blockquote>
<p>encodeURI 함수는 완전한 URI를 문자열로 전달받아 이스케이프 처리를 위해 인코딩. URI는 인터넷에 있는 자원을 나타내는 유일한 주소를 뜻함</p>
</blockquote>
<p>한글을 포함한 대부분의 외국어나 아스키 문자 셋에 정의되지 않은 특수 문자의 경우 URL에 포함될 수 없다. 따라서 URL 내에서 의미를 갖고 있는 문자(%, ?. #)나 URL에 올수 없는 문자(한글, 공백 등) 또는 시스템에 의해 해석될 수 있는 문자(&lt;, &gt;)를 이스케이프 처리하여 야기될 수 있는 문제를 예방</p>
<pre><code class="language-js">const uri = &#39;https://seongtaek.com/name=성택&#39;
const enc = encodeURI(uri)
console.log(enc) // https://seongtaek.com/name=%EC%84%B1%ED%83%9D

// decodeURI 함수는 인코딩된 완전한 URI를 전달받아 이스케이프 처리 이전으로 디코딩
const dec = decodeURI(enc); // https://seongtaek.com/name=성택
console.log(dec);</code></pre>
<h5 id="encodeuricomponent--decodeuricomponent">encodeURIComponent / decodeURIComponent</h5>
<blockquote>
<p>encodeURIComponent 함수는 URI 구성 요소를 인수로 전달받아 인코딩. 인수로 전달된 문자열을 URI의 구성요소인 쿼리 스트링의 일부로 간주해서 쿼리 스트링 구분자로 사용되는 <code>=</code>, <code>?</code>, <code>&amp;</code>까지 인코딩. encodeURI는 매개변수로 전달된 문자열을 완전한 URI 전체라고 간주하므로 <code>=</code>, <code>?</code>, <code>&amp;</code> 은 인코딩 하지 않음</p>
</blockquote>
<h3 id="2143-암묵적-전역">21.4.3 암묵적 전역</h3>
<p><strong>암묵적 전역</strong> : 선언하지 않은 변수를 전역 객체에 프로퍼티로 동적 생성하는 것</p>
<pre><code class="language-js">var x = 10;

function foo() {
  // 선언하지 않은 식별자에 값을 할당
  y = 20; // window.y = 20
}
foo();

// 선언하지 않은 식별자 y를 전역에서 참조할 수 있다. y는 암묵적 전역
console.log(x + y); // 30</code></pre>
<p>변수 선언 없이 단지 전역 객체의 프로퍼티로 추가되었을 뿐이고, y는 변수가 아니다. 따라서 변수 호이스팅이 발생하지 않는다.</p>
<pre><code class="language-js">// 전역 변수 x는 호이스팅이 발생한다.
console.log(x); // undefined

// 전역 변수가 아니라 단지 전역 객체의 프로퍼티인 y는 호이스팅이 발생하지 않는다.
console.log(y); // ReferneceError : y is not defined

var x = 10;

function foo() {
  // 선언하지 않은 식별자에 값을 할당
  y = 20; // window.y = 20
}
foo();

// 선언하지 않은 식별자 y를 전역에서 참조할 수 있다. y는 암묵적 전역
console.log(x + y); // 30</code></pre>
<h1 id="22-this">22. this</h1>
<h2 id="221-this-키워드">22.1. this 키워드</h2>
<p><strong>this</strong> : 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수 (self-referencing variable). </p>
<p>this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있음</p>
<p>this는 자바스크립트 엔진에 의해 암묵적으로 생성되는데 this가 가리키는 값, <strong>this 바인딩은 함수 호출 방식에 의해 동적으로 결정됨</strong></p>
<h2 id="222-함수-호출-방식과-this-바인딩">22.2 함수 호출 방식과 this 바인딩</h2>
<p>this 바인딩은 함수 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정됨</p>
<p>함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프를 결정. 하지만 this 바인딩은 함수 호출 시점에 결정</p>
<p><strong>함수 호출 방식</strong></p>
<ol>
<li>일반 함수 호출</li>
<li>메서드 호출</li>
<li>생성자 함수 호출</li>
<li>Function.prototype.apply / call / bind 메서드에 의한 간접 호출</li>
<li>arrow function (ES6)</li>
</ol>
<h3 id="2221-일반-함수-호출">22.2.1 일반 함수 호출</h3>
<blockquote>
<p>일반 함수로 호출하면 함수 내부의 <strong>this는 전역 객체 (global object)</strong>가 바인딩. 이는 전역 함수는 물론이고 중첩 함수도 마찬가지. </p>
<p>콜백 함수도 일반 함수로 호출되면 콜백 함수 내부의 this에도 전역 객체가 바인딩됨</p>
</blockquote>
<pre><code class="language-js">function foo() {
  console.log(this); // window
  function bar() {
    console.log(this); // window
  }
  bar();
}
foo();</code></pre>
<p>this는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서 this는 의미가 없음</p>
<p>따라서 strict mode가 적용된 일반 함수 내부의 this는 <code>undefined</code>가 바인딩</p>
<pre><code class="language-js">function foo() {
  &#39;use strict&#39;;
  console.log(&quot;foo&#39;s this: &quot;, this); // undefined
  function bar() {
    console.log(&quot;bar&#39;s this:&quot;, this); // undefined
  }
  bar();
}
foo();</code></pre>
<h3 id="2222-메서드-호출">22.2.2 메서드 호출</h3>
<blockquote>
<p>메서드 내부의 this에는 메서드를 호출한 객체, 즉 메서드를 호출할 때 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체가 바인딩</p>
<p><strong>메서드를 소유한 객체가 아니라 메서드를 호출한 객체에 바인딩</strong>됨</p>
</blockquote>
<pre><code class="language-js">const person = {
  name: &#39;Lee&#39;,
  getName() {
    // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩됨
    return this.name;
  }
};

// 메서드 getName을 호출한 객체는 person
console.log(person.getName()); // Lee</code></pre>
<pre><code class="language-js">const anotherPerson = {
  name: &#39;Kim&#39;
};

// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson
 console.log(anotherPerson.getName()); // Kim

// getName 메서드를 변수에 할당
const getName = person.getName;

// getName 메서드를 일반 함수로 호출
console.log(getName()); // &#39;&#39;
// 일반 함수로 호출된 getName 함수 내부의 this.name 은 브라우저 환경에서 window.name과 같음
// 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 &#39;&#39;</code></pre>
<h3 id="2223-생성자-함수-호출">22.2.3 생성자 함수 호출</h3>
<blockquote>
<p>생성자 함수 내부의 this에는 <strong>생성자 함수가 (미래에) 생성할 인스턴스</strong>가 바인딩</p>
</blockquote>
<pre><code class="language-js">// 생성자 함수
function Circle(radius){
  // 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다
  this.radius = radius;
  this.getDiameter = function(){
    return 2 * this.radius;
  };
}

// 반지름이 5인 Circle 객체를 생성
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성
const circle2 = new Circle(10);

console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20</code></pre>
<h3 id="2224-functionprototypeapply--call--bind-메서드에-의한-간접-호출">22.2.4 Function.prototype.apply / call / bind 메서드에 의한 간접 호출</h3>
<blockquote>
<p>apply 와 call 메서드는 함수를 호출하면서 <strong>첫 번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩</strong></p>
<p>apply와 call 메서드는 호출할 함수에 인수를 전달하는 방식만 다를 뿐 동일하게 동작함</p>
<p>apply 메서드는 호출할 함수의 인수를 배열로 묶어서 전달. call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달</p>
</blockquote>
<pre><code class="language-js">function getThisBinding() {
  console.log(arguments);
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// getThisBinding을 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩
// apply 메서드는 호출할 함수의 인수를 배열로 묶어서 전달
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// {a : 1}

// call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// {a : 1}</code></pre>
<p>apply와 call 메서드의 대표적인 용도는 <code>arguments</code> 객체와 같은 <strong>유사 배열 객체에 배열 메서드를 사용하는 경우</strong></p>
<p>arguments 객체는 배열이 아니기 때문에 Array.prototype.slice 같은 배열의 메서드를 사용할 수 없으나 apply, call 메서드를 사용하면 가능하다</p>
<pre><code class="language-Js">function convertArgumentsToArray() {
  console.log(arguments); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

  // arguments 객체를 배열로 변환
  // Array.prototype.slice를 인수 없이 호출하면 배열의 복사본 생성
  const arr = Array.prototype.slice.call(arguments);
  // const arr = Array.prototype.slice.apply(arguments);
  console.log(arr);

  return arr;
}

convertArgumentsToArray(1, 2, 3); // [1, 2, 3]</code></pre>
<blockquote>
<p>Function.prototype.bind 메서드는 apply와 call 메서드와 달리 함수를 호출하지 않고 this로 사용할 객체만 전달</p>
</blockquote>
<pre><code class="language-Js">function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// bind 메서드는 함수에 this로 사용할 객체를 전달
// bind는 함수를 호출하지는 않는다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding

// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 적용된 것을 확인할 수 있다.
console.log(getThisBinding.bind(thisArg)()); // { a: 1 }</code></pre>
<p>bind 메서드는 메서드의 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용됨</p>
<h3 id="추가-화살표-함수-es6">추가. 화살표 함수 (ES6)</h3>
<blockquote>
<p>화살표 함수에서 this는 상위 스코프의 this를 그대로 참조함. 이는 화살표 함수는 함수 자체의 this 바인딩을 갖지 않기 때문</p>
<p>이를 lexical this라고 하는데 이는 렉시컬 스코프와 같이 화살표 함수의 this가 함수가 정의된 위치에 의해 결정된다는 것을 의미</p>
<p>화살표 함수와 화살표 함수가 중첩되어 있다면 상위 화살표 함수에도 this 바인딩이 없으므로 스코프 체인 상 가장 가까운 상위 함수 중에서 화살표 함수가 아닌 함수의 this를 참조</p>
</blockquote>
<pre><code class="language-js">var 오브젝트1 = {
  함수 : function(){ console.log(this) }
}

오브젝트1.함수() // {함수: ƒ}


var 오브젝트2 = {
  함수 : () =&gt; { console.log(this) }
}

오브젝트2.함수() // Window</code></pre>
<table>
<thead>
<tr>
<th align="center">함수 호출 방식</th>
<th align="center">this 바인딩</th>
</tr>
</thead>
<tbody><tr>
<td align="center">일반 함수 호출</td>
<td align="center">전역 객체</td>
</tr>
<tr>
<td align="center">메서드 호출</td>
<td align="center">메서드를 호출한 객체( . 연산자 앞 객체)</td>
</tr>
<tr>
<td align="center">생성자 함수 호출</td>
<td align="center">생성자 함수가 (미래에) 생성할 인스턴스</td>
</tr>
<tr>
<td align="center">Function.prototype.apply / call /bind 메서드에 의한 간접 호출</td>
<td align="center">Function.prototype.apply / call / bind 메서드에 첫 번째 인수로 전달한 객체</td>
</tr>
<tr>
<td align="center">화살표 함수 (ES6)</td>
<td align="center">상위 스코프(부모)의 this</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[<19. 프로토타입>]]></title>
            <link>https://velog.io/@st_hwang/19.-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@st_hwang/19.-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</guid>
            <pubDate>Thu, 02 Jun 2022 00:59:02 GMT</pubDate>
            <description><![CDATA[<h1 id="19-프로토타입">19. 프로토타입</h1>
<p>자바스크립트는 <strong>프로토타입 기반의 객체지향 프로그래밍 언어</strong></p>
<p>자바스크립트는 객체 기반의 프로그래밍 언어이며, 자바스크립트를 이루고 있는 거의 모든 것이 객체다. (원시값을 제외한 나머지 값은 모두 객체)</p>
<h2 id="191-객체지향-프로그래밍">19.1 객체지향 프로그래밍</h2>
<p>객체 : 속성을 통해 여러 개의 값(상태 데이터, 동작)을 하나의 단위로 구성한 복합적인 자료구조</p>
<p><strong>객체지향 프로그래밍</strong> : 독립적인 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임</p>
<p>객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각하는데,</p>
<p>따라서 객체는 상태 데이터와 동작을 하나의 논리적 단위로 묶은 복합적인 자료구조이며, 객체의 상태 데이터를 <strong>프로퍼티(property)</strong>, 동작을 <strong>메서드(method)</strong>라고 함</p>
<h2 id="192-상속과-프로토타입">19.2 상속과 프로토타입</h2>
<p><strong>상속 (inheritance)</strong> : 객체지향 프로그래밍의 핵심 개념으로, 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것</p>
<p>자바스크립트는 프로토타입을 기반으로 상속을 구현하여, 기존의 코드를 적극적으로 재사용함으로써 불필요한 중복을 제거함</p>
<pre><code class="language-js">// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를 공유해서 사용할 수 있도록 프로토타입에 추가
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function () {
  return Math.PI * this.radius ** 2;
};

// 인스턴스 생성
// Circle 생성자 함수가 생성하는 모든 인스턴스는 자신의 상태를 나타내는 radius 프로퍼티만 개별적으로 소유하고, 
// 내용이 동일한 getArea 메서드를 상속받아 사용할 수 있다. (중복 방지)
const circle1 = new Circle(1);
const circle2 = new Circle(2);

// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는 프로토타입 Circle.prototype 으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true

console.log(circle1.getArea());
console.log(circle2.getArea());</code></pre>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/adbdf8a5-9280-40b6-a16e-e09b9b728990/image.png" alt=""></p>
<h2 id="193-프로토타입-객체">19.3 프로토타입 객체</h2>
<p><code>프로토타입 객체</code>는 객체지향 프로그래밍의 근간을 이루는 <strong>객체 간 상속을 구현하기 위해 사용</strong>됨</p>
<p>모든 객체는 <strong>[[Prototype]]</strong> 이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조다. (null 인 경우도 있음)</p>
<p>모든 객체는 하나의 프로토타입을 가지며, 모든 프로토타입은 생성자 함수와 연결되어 있다.</p>
<p>[[Prototype]] 내부 슬롯에는 직접 접근 불가, <code>__proto__</code> 접근자 프로퍼티를 통해 [[Prototype]] 내부 슬롯이 가리키는 프로토타입에 간접 접근 가능</p>
<p>프로토타입은 자신의 constructor 프로퍼티를 통해 생성자 함수에 접근할 수 있고, 생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근 가능</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/1f6751a8-41e2-489b-8c44-c88a2733b4d4/image.png" alt=""></p>
<h3 id="1931-__proto__-접근자-프로퍼티">19.3.1 <code>__proto__</code> 접근자 프로퍼티</h3>
<p>모든 객체는 <code>__proto__</code> 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능</p>
<h5 id="1-__proto__-는-접근자-프로퍼티다">1. <code>__proto__</code> 는 접근자 프로퍼티다.</h5>
<p><strong>접근자 프로퍼티</strong> : 자체적으로 값([Value] 어트리뷰트)를 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 쓰는 접근자 함수 ([[Get]], [[Set]] 프로퍼티 어트리뷰트)</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/43875595-f359-4f55-917f-4a9725956783/image.png" alt=""></p>
<p>Object.prototype 의 접근자 프로퍼티인 <code>__proto__</code> 는 getter/setter 함수라고 부르는 접근자 함수([[Get]], [[Set]])를 통해 [[Prototype]] 값인 프로토타입을 취득 or 할당</p>
<p><code>__proto__</code> 접근자 프로퍼티를 통해 프로토타입에 접근하면 getter 함수인 [[Get]] 호출</p>
<p><code>__proto__</code> 접근자 프로퍼티를 통해 프로토타입에 할당하면 setter 함수인 [[Set]] 호출</p>
<pre><code class="language-js">const obj = {};
const parent = { x : 1 };

// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;

// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

console.log(obj.x); // 1</code></pre>
<h5 id="2-__proto__-접근자-프로퍼티는-상속을-통해-사용된다">2. <code>__proto__</code> 접근자 프로퍼티는 상속을 통해 사용된다.</h5>
<p><code>__proto__</code> 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티</p>
<p>모든 객체는 상속을 통해 <code>Object.prototype.__proto__</code> 접근자 프로퍼티를 사용할 수 있다.</p>
<h5 id="3-__proto__-접근자-프로퍼티를-통해-프로토타입에-접근하는-이유">3. <code>__proto__</code> 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유</h5>
<p>[[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 <strong>상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해</strong>서이다.</p>
<pre><code class="language-js">const parent = {};
const child = {};

// child의 프로토타입을 parent로 설정
child.__proto__ = parent;
// parent의 프로토타입을 child로 설정
parent.__proto__ = child; // TypeError : Cyclic __proto__ value</code></pre>
<blockquote>
<p>프로토타입 체인은 단방향 링크드 리스트로 구현되어야 한다. 즉, 프로퍼티 검색 방향이 한쪽 방향으로만 흘러가야 한다.</p>
</blockquote>
<p>위의 예제와 같이 순환 참조(circular reference) 하는 프로토타입 체인이 만들어지면 프로토타입 체인 종점이 존재하지 않기 때문에 무한 루프에 빠진다.</p>
<p>따라서 아무 체크없이 무조건적으로 프로토타입을 교체할 수 없도록 <code>__proto__</code> 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현되어 있다.</p>
<h5 id="4-__proto__-접근자-프로퍼티를-코드-내에서-직접-사용하는-것은-권장하지-않는다">4. <code>__proto__</code> 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.</h5>
<p>모든 객체가 <code>__proto__</code> 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에, 코드 내에서 <code>__proto__</code> 접근자 프로퍼티를 직접 사용하는 것은 권장하지 않음</p>
<p>직접 상속을 통해 Object.prototype 을 상속받지 않은 객체를 생성할 수도 있기 때문에, <code>__proto__</code> 접근자 프로퍼티를 사용할 수 없는 경우가 있음</p>
<pre><code class="language-js">// obj는 프로토타입 체인의 종점이다. 따라서 Object.__proto__ 를 상속받을 수 없다.
const obj = Object.create(null);

// obj는 Object.__proto__를 상속받을 수 없다.
console.log(obj.__proto__); // undefined

// 따라서 __proto__ 보다 Object.getPrototypeOf 메서드를 사용하는 편이 좋다.
console.log(Object.getPrototype(obj)); // null</code></pre>
<p> 따라서 <code>__proto__</code> 접근자 프로퍼티 대신 <strong>프로토타입의 참조를 취득</strong>하고 싶은 경우에는 <strong><code>Object.getPrototypeOf</code></strong> 메서드를 사용하고,</p>
<p><strong>프로토타입을 교체</strong>하고 싶은 경우에는 <strong><code>Object.SetprototypeOf</code></strong> 메서드를 사용할 것을 권장</p>
<pre><code class="language-js">const obj = {};
const parent = { x : 1 };

// obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); // obj.__proto__;
// obj 객치의 프로토타입을 교체
Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent;

console.log(obj.x); // 1</code></pre>
<h3 id="1932-함수-객체의-prototype-프로퍼티">19.3.2 함수 객체의 prototype 프로퍼티</h3>
<p>함수 객체만이 소유하는 <code>prototype 프로퍼티</code>는 <strong>생성자 함수가 생성할 인스턴스의 프로토타입</strong>을 가리킨다.</p>
<pre><code class="language-js">// 함수 객체는 prototype 프로퍼티를 소유한다.
(function(){}).hasOwnProperty(&#39;prototype&#39;); // true

// 일반 객체는 prototype 프로퍼티를 소유하지 않는다.
({}).hasOwnProperty(&#39;prototype&#39;); // false</code></pre>
<p>따라서 생성자 함수로서 호출할 수 없는 함수, non-constructor (화살표 함수, ES6 메서드 축약 표현으로 정의된 메서드)는 </p>
<p>prototype 프로퍼티를 소유하지 않으며, 프로토타입도 생성하지 않는다.</p>
<pre><code class="language-js">// 화살표 함수는 non-construntor다.
const Person = name =&gt; {
  this.name = name;
};

// non-constructor는 prototype 프로퍼티를 소유하지 않는다.
console.log(Person.hasOwnProperty(&#39;prototype&#39;)); // false

// non-constructor는 프로토타입을 생성하지 않는다.
console.log(Person.prototype); // undefined

// ES6의 메서드 축약 표현으로 정의한 메서드는 non-constructor이다.
const obj = {
  foo(){}
}

// non-constructor는 prototype 프로퍼티를 소유하지 않는다.
console.log(obj.foo.hasOwnProperty(&#39;prototype&#39;))

// non-constructor는 프로토타입을 생성하지 않는다.
console.log(obj.foo.prototype)</code></pre>
<p>모든 객체가 가지고 있는 <code>__proto__</code> 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리키지만 사용 주체가 다르다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>소유</th>
<th>값</th>
<th>사용 주체</th>
<th>사용 목적</th>
</tr>
</thead>
<tbody><tr>
<td><code>__proto__</code> 접근자 프로퍼티</td>
<td>모든 객체</td>
<td>프로토타입의 참조</td>
<td>모든 객체</td>
<td>객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용</td>
</tr>
<tr>
<td>prototype 프로퍼티</td>
<td>constructor</td>
<td>프로토타입의 참조</td>
<td>생성자 함수</td>
<td>생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용</td>
</tr>
</tbody></table>
<pre><code class="language-js">// 생성자 함수
function Person(name){
  this.name = name;  
}

const me = New Person(&#39;Lee&#39;);

// Person.prototype과 me.__proto__는 동일한 프로토타입을 가리킨다.
console.log(Person.prototype === me.__proto__); // true</code></pre>
<h3 id="1933-프로토타입의-construntor-프로퍼티와-생성자-함수">19.3.3 프로토타입의 construntor 프로퍼티와 생성자 함수</h3>
<p>모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.</p>
<p>이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄진다.</p>
<pre><code class="language-js">// 생성자 함수
function Person(name){
  this.name = name;
}

const me = new Person(&#39;Lee&#39;);

// me 객체의 생성자 함수는 Person
// me 객체의 프로토타입 Person.prototype 에 있는 constructor 프로퍼티를 상속받아 사용할 수 있다.
console.log(me.constructor === Person ); // true</code></pre>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/1411033f-2c5e-4b8d-905a-7b325be33daa/image.png" alt=""></p>
<h2 id="194-리터럴-표기법에-의해-생성된-객체의-생성자-함수와-프로토타입">19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입</h2>
<p><code>생성자 함수에 의해 생성된 인스턴스</code>는 <strong>프로토타입의 constructor 프로퍼티에 의해 생성자 함수(인스턴스를 생성한 생성자 함수)와 연결</strong></p>
<p>리터럴 표기법에 의한 객체 생성 방식과 같이, new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하는게 아닌 객체 생성 방식도 있다.</p>
<pre><code class="language-js">// 객체 리터럴
const obj = {};

// 함수 리터럴 
const add = function(a, b){ return a + b };

// 배열 리터럴
const arr = [1, 2, 3];

// 정규 표현식 리터럴
const regexp = /is/ig;</code></pre>
<p>리터럴 표기법에 의해 생성된 객체도 물론 프로토타입이 존재하지만, </p>
<p>리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 할 수 없다.</p>
<pre><code class="language-js">// obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성
const obj = {};

// 근데 obj 객체의 생성자 함수는 Object 생성자 함수다?
console.log(obj.constructor === Object); // true</code></pre>
<p>위의 예제에서 객체 리터럴로 생성했는데 consturctor 가 Object와 연결되어있다. ECMAScript에 그렇게 정의되어 있기 때문</p>
<p>Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null 을 인수로 전달하면서 호출하면,</p>
<p>내부적으로 추상연산 OrdinaryObjectCreate를 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/89ec4381-eb1f-4e27-bb03-7b041515b5d9/image.png" alt="">
<a href="https://262.ecma-international.org/11.0/#sec-object-value">https://262.ecma-international.org/11.0/#sec-object-value</a></p>
<pre><code class="language-js">// 2. Object 생성자 함수에 의한 객체 생성
// 인수가 전달되지 않았을 때 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성한다.
let obj = new Object();
console.log(obj);

// 1. new.target이 undefined나 Object가 아닌 경우
// 인스턴스 -&gt; Foo.prototype -&gt; Object.prototype 순으로 프로토타입 체인이 생성된다.
class Foo extends Object {}
new Foo(); // Foo {}

// 3. 인수가 전달된 경우에는 인수를 객체로 변환한다.
// Number 객체 생성
obj = new Object(123);
console.log(obj);

// String 객체 생성
obj = new Object(&#39;123&#39;);
console.log(obj);</code></pre>
<p>객체 리터럴이 평가될 때는 다음과 같이 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/926c8f1f-e5b3-414f-a62f-73304dae9947/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/7e44556b-1e43-43bf-bc9a-8104d01d5845/image.png" alt=""></p>
<p>Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일,</p>
<p>new.target의 확인이나 프로퍼티를 추가하는 처리 등 세부 내용은 다름. <strong>객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님</strong></p>
<p>함수 객체의 경우도 Function 생성자 함수를 호출하여 생성한 함수는 렉시컬 스코프를 만들지 않고 전역 함수인 것처럼 스코프를 생성하며 클로저도 만들지 않는다.</p>
<p>함수 선언문과 함수 표현식을 평가하여 함수 객체를 생성한 것은 Function 생성자 함수가 아닌데, constructor 프로퍼티를 통해 확인하면 같다.</p>
<pre><code class="language-js">// foo 함수는 Function 생성자 함수로 생성한 함수 객체가 아니라 함수 선언문으로 생성했다.
function foo(){}

// 하지만 constructor 프로퍼티를 통해 확인해보면 함수 foo의 생성자 함수는 Function 생성자 함수다.
console.log(foo.constructor === Function); // true</code></pre>
<p>리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하여, 가상적인 생성자 함수를 갖는다.</p>
<p>프로토타입은 생성자 함수와 더불어 생성되며, <code>prototype</code>, <code>constructor</code> 프로퍼티에 의해 연결되어 있기 때문이다.</p>
<p><strong>프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.</strong></p>
<p><strong>리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입</strong></p>
<table>
<thead>
<tr>
<th>리터럴 표기법</th>
<th>생성자 함수</th>
<th>프로토타입</th>
</tr>
</thead>
<tbody><tr>
<td>객체 리터럴</td>
<td>Object</td>
<td>Object.prototype</td>
</tr>
<tr>
<td>함수 리터럴</td>
<td>Function</td>
<td>Function.prototype</td>
</tr>
<tr>
<td>배열 리터럴</td>
<td>Array</td>
<td>Array.prototype</td>
</tr>
<tr>
<td>정규 표현식 리터럴</td>
<td>RegExp</td>
<td>RegExp.prototype</td>
</tr>
</tbody></table>
<h2 id="195-프로토타입의-생성-시점">19.5 프로토타입의 생성 시점</h2>
<p>모든 객체는 생성자 함수와 연결되어 있으며, 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨</p>
<p>생성자 함수는 사용자가 직접 정의한 사용자 정의 생성자 함수와 자바스크립트가 기본 제공하는 빌트인 제공 생성자 함수로 구분할 수 있음</p>
<h3 id="1951-사용자-정의-생성자-함수와-프로토타입-생성-시점">19.5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점</h3>
<p>생성자 함수로서 호출할 수 있는 함수, 즉 contructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨</p>
<pre><code class="language-js">// 함수 정의(constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // {constructor: ƒ}

// 생성자 함수
function Person(name){
  this.name = name;
}</code></pre>
<p>생성자 함수로서 호출할 수 없는 함수, 즉 non-consturctor는 프로토타입이 생성되지 않는다.</p>
<pre><code class="language-js">// 화살표 함수는 non-consturctor
const Person = name =&gt; {
  this.name = name;
};

// non-constructor는 프로토타입이 생성되지 않는다.
console.log(Person.prototype); // undefined</code></pre>
<p>함수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되므로, 함수 선언문으로 정의된 Person 생성자 함수는 어떤 코드보다 먼저 평가되어 함수 객체가 된다.</p>
<p>이때 프로토타입도 더불어 생성되고, 생성된 프로토타입은 Person 생성자 함수의 prototype 프로퍼티에 바인딩된다.</p>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/423f6700-053e-4fd3-bb6e-f92e154d3320/image.png" alt=""></p>
<p>이처럼 빌트인 생성자 함수가 아닌 <code>사용자 정의 함수</code>는 <strong>자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 더불어 생성</strong>되며,</p>
<p>생성된 프로토타입의 프로토타입은 언제나 Object.prototype이다.</p>
<h3 id="1952-빌트인-생성자-함수와-프로토타입-생성-시점">19.5.2 빌트인 생성자 함수와 프로토타입 생성 시점</h3>
<p><code>모든 빌트인 생성자 함수</code>는 <strong>전역 객체(CSR에서는 window, SSR에서는 global)가 생성되는 시점(코드가 실행되기 이전 시점)에 생성</strong>되고, 이때 <strong>프로토타입이 함께 생성</strong>됨. </p>
<p>생성된 프로토타입은 빌트인 생성자 함수의 <code>prototype 프로퍼티</code>에 바인딩됨.</p>
<p>이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨으로써 생성된 객체가 프로토타입을 상속받음</p>
<img src="https://s1.md5.ltd/image/1711ab3d4822f2b629d327be00927516.png"/>



<h2 id="196-객체-생성-방식과-프로토타입의-결정">19.6 객체 생성 방식과 프로토타입의 결정</h2>
<p>객체의 생성 방법 : 객체 리터럴, Object 생성자 함수, 생성자 함수, Object.create 메서드, 클래스(ES6)</p>
<p>다양한 방법으로 생성된 모든 객체는 각 방식마다 세부적인 객체 생성 방식의 차이는 있으나 <strong>모두 추상 연산 OrdinaryObjectCreate 에 의해 생성</strong>됨</p>
<p>OrdinaryObjectCreate는 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달 받고, 추가할 프로터피 목록을 옵션으로 전달할 수 있음</p>
<p>즉, 프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정되며, 이 인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정됨</p>
<h3 id="1961-객체-리터럴에-의해-생성된-객체의-프로토타입">19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입</h3>
<p>자바스크립트 엔진은 객체 리터럴을 평가하여 객체를 생성할 때 추상 연산 OrdinaryObjectCreate를 호출하며, </p>
<p>이때 OrdinaryObjectCreate에 전달되는 프로토타입은 Object.prototype. 즉, <strong>객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype</strong></p>
<pre><code class="language-js">const obj = { x : 1 };

// 객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty(&#39;x&#39;)); // true</code></pre>
<h3 id="1962-object-생성자-함수에-의해-생성된-객체의-프로토타입">19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입</h3>
<p>Object 생성자 함수를 인수 없이 호출하면 빈 객체가 생성되며, 객체 리터럴과 마찬가지로 추상 연산 OrdinaryObjectCreate가 호출됨.</p>
<p>이때 OrdinaryObjectCreate에 전달되는 프로토타입은 Object.prototype. 즉, <strong>Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype</strong></p>
<pre><code class="language-js">const obj = new Object();
obj.x = 1;

// Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty(&#39;x&#39;)); // true</code></pre>
<p>객체 리터럴과 Object 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식</p>
<p><code>객체 리터럴 방식</code>은 <strong>객체 리터럴 내부에 프로퍼티를 추가</strong>, <code>Object 생성자 함수 방식</code>은 <strong>일단 빈 객체를 생성한 이후 프로퍼티를 추가</strong></p>
<h3 id="1963-생성자-함수에-의해-생성된-객체의-프로토타입">19.6.3 생성자 함수에 의해 생성된 객체의 프로토타입</h3>
<p>new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출됨.</p>
<p>이때 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체.</p>
<p>즉, <code>생성자 함수에 의해 생성되는 객체의 프로토타입</code>은 <strong>생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체</strong></p>
<pre><code class="language-js">function Person(name){
  this.name = name;
}

const me = new Person(&#39;Lee&#39;);</code></pre>
<p><img src="https://velog.velcdn.com/images/st_hwang/post/b30d28b3-3b2f-4495-b457-93999c6f95d3/image.png" alt=""></p>
<p>표준 빌트인 객체인 Object 생성자 함수와 더불어 생성된 프로토타입 Object.prototype은 다양한 빌트인 메서드(hasOwnProperty, propertyIsEnumerable 등) 를 갖고있으나,</p>
<p><strong>사용자 정의 생성자 함수 Person과 더불어 생성된 프로토타입 Person.prototype 의 프로퍼티는 consturctor 하나뿐</strong></p>
<p>프로토타입 Person.prototype에 프로퍼티를 추가/삭제하면 프로토타입 체인에 즉각 반영되어 하위(자식) 객체가 상속받을 수 있다.</p>
<pre><code class="language-js">function Person(name){
  this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function(){
  console.log(`Hi! my name is ${this.name}`);
};

const me = new Person(&#39;Lee&#39;);
const you = new Person(&#39;Kim&#39;);

me.sayHello(); // Hi! my name is Lee
you.sayhello(); // Hi! my name is Kim</code></pre>
<h2 id="197-프로토타입-체인">19.7 프로토타입 체인</h2>
<pre><code class="language-js">function Person(name){
  this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function(){
  console.log(`Hi! my name is ${this.name}`);
}

const me = new Person(&#39;Lee&#39;);

// hasOwnproperty는 Object.prototype의 메서드
console.log(me.hasOwnProperty(&#39;name&#39;)); // true</code></pre>
<p>Person 생성자 함수에 의해 생성된 me 객체가 Object.prototype의 메서드 hasOwnProperty를 호출할 수 있음</p>
<p>이는 me 객체가 person.prototype 뿐만 아니라 Object.prototype도 상속받았다는 것을 의미함. <strong>프로토타입의 프로토타입은 언제나 <code>Object.prototype</code></strong></p>
<pre><code class="language-js">Object.getPrototype(me) === person.prototype; // true
Object.getPrototype(Person.prototype) === Object.prototype; // true</code></pre>
<img src="https://velog.velcdn.com/images%2Fnoahshin__11%2Fpost%2Fc0a94928-969d-4388-9978-69358a2c27de%2Fimage.png"/>

<p><strong>프로토타입 체인</strong></p>
<blockquote>
<p>자바스크립트 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조에 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것. 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘</p>
</blockquote>
<p><code>me.hasOwnProperty(&#39;name&#39;);</code> 메서드를 호출하면 자바스크립트는 다음과 같은 과정을 거쳐 메서드를 검색 </p>
<ol>
<li>hasOwnproperty 메서드를 호출한 me 객체에서 hasOwnProperty 메서드 검색. me 객체에 hasOwnProperty가 없으므로 프로토타입 체인을 따라 [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입 Person.protytpe으로 이동하여 hasOwnProperty 검색</li>
<li>Person.prototype에도 hasOwnProperty가 없으므로 프로토타입 체인을 따라 [[Prototype]] 내부 슬롯에 바인딩되어있는 Object.prototype으로 이동하여 검색</li>
<li>Object.prototype에는 hasOwnProperty가 존재하므로 자바스크립트 엔진은 Object.prototype.hasOwnProperty를 호출. 이때 this 에는 me 객체 바인딩</li>
</ol>
<pre><code class="language-js">Object.prototype.hasOwnProperty.call(me, &#39;name&#39;);</code></pre>
<p>프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype이므로, <strong>모든 객체는 Object.prototype을 상속</strong>받음.</p>
<p><strong>Object.prototype을 프로토타입 체인의 종점(end of prototype chain)이라 함</strong>. Object.prototype의 [[Prototype]] 내부 슬롯의 값은 null</p>
<p>프로토타입 체인의 종점인 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined 반환 (에러 발생 X)</p>
<p>자바스크립트 엔진은 프로토타입 체인을 따라 프로퍼티/메서드를 검색하며, 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이라 할 수 있다.</p>
<p>반면에 프로퍼티가 아닌 식별자는 스코프 체인에서 검색하며, 스코프 체인은 식별자 검색을 위한 메커니즘이라고 할 수 있다.</p>
<pre><code class="language-js">me.hasOwnProperty(&#39;name&#39;);</code></pre>
<p>위 예제의 경우 먼저 스코프 체인에서 me 식별자를 검색하고 찾으면, me 객체의 프로토타입 체인에서 hasOwnProperty 메서드를 검색 </p>
<p>스코프 체인과 프로토타입 체인은 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용됨</p>
<h2 id="198-오버라이딩과-프로퍼티-섀도잉">19.8 오버라이딩과 프로퍼티 섀도잉</h2>
<p><strong>프로토타입 프로퍼티</strong> : 프로토타입이 소유한 프로퍼티(메서드 포함)</p>
<p><strong>인스턴스 프로퍼티</strong> : 인스턴스가 소유한 프로퍼티</p>
<p>프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 인스턴스 프로퍼티로 추가.</p>
<p>상속 관계에 의해 프로퍼티가 가려지는 현상을 <code>프로퍼티 섀도잉</code> 이라고 함</p>
<pre><code class="language-js">const Person = (function(){
  // 생성자 함수
  function Person(name){
    this.name = name;
  }

  // 프로토타입 메서드
  Person.prototype.sayHello = function(){
    consolt.log(`Hi! My name is ${this.name}`)
  };

  // 생성자 함수를 반환
  return Person;
}());

const me = new Person(&#39;Lee&#39;);

// 인스턴스 메서드
me.sayHello = function(){
  console.log(`Hey! My name is ${this.name}`);
};

// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.(오버라이딩)
me.sayHello(); // Hey! My name is Leedls</code></pre>
<p>인스턴스 메서드 sayHello 가 프로토타입 메서드 sayHello 를 오버라이딩해서 프로토타입 메서드 sayHello가 가려짐 (프로퍼티 섀도잉)</p>
<blockquote>
<p>오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식</p>
<p>오버로드 : 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식 (자바스크립트는 지원 X)</p>
</blockquote>
<p>인스턴스 메서드 sayHello 를 삭제해도 프로토타입 메서드가 아닌 인스턴스 메서드 sayHello 가 삭제됨</p>
<pre><code class="language-js">// 인스턴스 메서드 삭제
delete me.sayHello;
// 인스턴스에는 sayHello 메서드가 없으므로 프로퍼티 메서드 호출
me.sayHello(); // Hi! My name is Lee

// 프로토타입 메서드 삭제 시도
delete me.sayHello;
// 프로토타입 메서드는 삭제되지 않음
me.sayHello(); // Hi! My name is Lee</code></pre>
<p>다시 한번 sayHello 삭제해도 프로토타입 메서드는 삭제되지 않음.</p>
<p>하위 객체를 통해 프로토타입에 get 엑세스는 허용되나 set 액세스는 허용되지 않기 때문</p>
<p><strong>프로토타입 프로퍼티를 변경 / 삭제</strong>하려면 하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아니라 <strong>프로토타입에 직접 접근해야됨</strong></p>
<pre><code class="language-js">// 프로토타입 메서드 변경
Person.prototype.sayHello = function(){
  console.log(`Hey! My name is ${this.name}`);
};
me.sayHello(); // Hey! My name is Lee

// 프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello(); // TypeError : me.sayHello is not a function</code></pre>
<h2 id="199-프로토타입의-교체">19.9 프로토타입의 교체</h2>
<p>부모 객체인 프로토타입을 동적으로 변경할 수 있는 특징을 활용해 객체 간의 상속 관계를 동적으로 변경할 수 있음</p>
<p><strong>프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있음</strong></p>
<h3 id="1991-생성자-함수에-의한-프로토타입의-교체">19.9.1 생성자 함수에 의한 프로토타입의 교체</h3>
<pre><code class="language-js">const Person = (function(){
  function Person(name){
    this.name = name;
  }

  // 1️⃣ 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
  Person.prototype = {
    sayHello(){
      console.log(`Hi! My name is ${this.name}`);
    }
  };

  return Person;
}());

const me = new Person(&#39;Lee&#39;);</code></pre>
<p>1️⃣ 에서 Person.prototype 에 객체 리터럴 할당 = Person 생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체</p>
<p><img src="https://velog.velcdn.com/images%2Frlatp1409%2Fpost%2Fe7f0fa32-756f-4656-9617-e7980ca5b2ec%2FIMG_5782CD35EFE7-1.jpeg" alt="img"></p>
<p>프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없음. constructor 프로퍼티는 자바스크립트 엔진이 프로토타입을 생성할 때 암묵적으로 추가한 프로퍼티</p>
<p>따라서 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나옴</p>
<pre><code class="language-js">// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색됨
console.log(me.constructor === Object); // true</code></pre>
<p><strong>프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴</strong></p>
<p>프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살릴 수 있음</p>
<pre><code class="language-js">const Person = (function(){
  function Person(name){
    this.name = name;
  }

  // 1️⃣ 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
  Person.prototype = {
    // constructor 프로퍼티와 생성자 함수 간의 연결 설정
    constructor : Person,
    sayHello(){
      console.log(`Hi! My name is ${this.name}`);
    }
  };

  return Person;
}());

const me = new Person(&#39;Lee&#39;);

// constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false</code></pre>
<h3 id="1992-인스턴스에-의한-프로토타입의-교체">19.9.2 인스턴스에 의한 프로토타입의 교체</h3>
<p>프로토타입은 생성자 함수의 prototype 프로퍼티뿐만 아니라 인스턴스의 <code>__proto__</code> 접근자 프로퍼티(또는 <code>Object.getPrototypeOf 메서드</code>)를 통해 접근 가능.</p>
<p><strong>인스턴스의 <code>__proto__</code> 접근자 프로퍼티(또는 <code>Object.getPrototypeOf 메서드</code>) 를 통해 프로포타입을 교체할 수 있음</strong></p>
<blockquote>
<ul>
<li><p>생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것</p>
</li>
<li><p><code>__proto__</code> 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것</p>
</li>
</ul>
</blockquote>
<pre><code class="language-js">function Person(name){
  this.name = name;
}

const me = new Person(&#39;Lee&#39;);

// 프로토타입으로 교체할 객체
const parent = {
  sayHello() {
      console.log(&#39;Hi! my name is ${this.name}&#39;);
  }
};

// 1️⃣ me 객체의 프로토타입을 parent 객체로 교체
Obejct.setPrototype(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작
// me.__proto__ = parent;

me.sayHello(); // Hi! My name is Lee</code></pre>
<p>1️⃣ 에서 객체의 프로토타입을 parent 객체로 교체</p>
<p><img src="https://velog.velcdn.com/images%2Frlatp1409%2Fpost%2F8328e2de-c26a-4b6b-bc03-9b1d31034d70%2FIMG_402A271A02BE-1.jpeg" alt="img"></p>
<p>생성자 함수에 의한 교체와 마찬가지로 <strong>프로토타입으로 교체한 객체에는 constructor 프로퍼티가 없으므로 constructor 프로퍼티와 생성자 함수 간의 연결 파괴</strong></p>
<p>따라서 프로토타입의 constructor 프로퍼티로 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나옴</p>
<pre><code class="language-js">// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색됨
console.log(me.constructor === Object); // true</code></pre>
<p>프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살릴 수 있음</p>
<pre><code class="language-js">function Person(name){
  this.name = name;
}

const me = new Person(&#39;Lee&#39;);

// 프로토타입으로 교체할 객체
const parent = {
  constructor : Person,
  sayHello() {
      console.log(&#39;Hi! my name is ${this.name}&#39;);
  }
};

// 1️⃣ me 객체의 프로토타입을 parent 객체로 교체
Obejct.setPrototype(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작
// me.__proto__ = parent;

me.sayHello(); // Hi! My name is Lee

// constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false

// 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨
console.log(Person.prototype === Object.getPrototype(me)); // true</code></pre>
<blockquote>
<p>생성자 함수에 의한 프로토타입 교체 시 Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지만,</p>
<p>인스턴스에 의한 프로토타입 교체 시 Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지 않음</p>
</blockquote>
<p>프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 번거롭기 때문에, <strong>프로토타입은 직접 교체하지 않는 것이 좋다.</strong></p>
<p>상속 관계를 인위적으로 설정하려면 <strong><code>직접 상속</code>이 더 편리하고 안전</strong>. 또는 <code>클래스</code>를 사용하면 간편하고 직관적으로 상속 관계를 구현할 수 있음</p>
<h2 id="1910-instantof-연산자">19.10 instantof 연산자</h2>
<p><code>instanceof 연산자</code>는 이항 연산자로서 <strong>좌변에 객체를 가리키는 식별자</strong>, <strong>우변에 생성자 함수를 가리키는 식별자</strong>를 피연산자로 받고,</p>
<p>우변의 피연산자가 함수가 아닌 경우 TypeError 발생</p>
<p>우변의 생성자 함수의 prototype 에 바인딩된 객체가 좌변의 객체의 <strong>프로토타입 체인 상에 존재</strong>하면 true, 아니면 false</p>
<pre><code class="language-js">객체 instanceof 생성자함수</code></pre>
<p>instanceof 연산자는 생성자 함수의 prototype 에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인</p>
<blockquote>
<p>따라서, 생성자 함수에 의해 프로토타입이 교체되어 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도, 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결은 파괴되지 않으므로 instanceof 는 아무 영향도 안받음</p>
</blockquote>
<pre><code class="language-js">const Person = (function(){
  function Person(name){
    this.name = name;
  }

  // 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
  Person.prototype = {
    sayHello(){
      console.log(`Hi! My name is ${this.name}`);
    }
  };

  return Person;
}());

const me = new Person(&#39;Lee&#39;);

console.log(me.constructor === Person); // false

console.log(me instanceof Person); // true
console.log(me instanceof Object); // true</code></pre>
<h2 id="1911-직접-상속">19.11 직접 상속</h2>
<h3 id="19111-objectcreate-에-의한-직접-상속">19.11.1 Object.create 에 의한 직접 상속</h3>
<p><code>Object.create 메서드</code>는 <strong>명시적으로 프로토타입을 지정하여 새로운 객체 생성</strong>. 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출</p>
<ul>
<li><p><code>첫 번째 매개변수</code> : <strong>생성할 객체의 프로토타입으로 지정할 객체</strong></p>
</li>
<li><p><code>두 번째 매개 변수</code> : *<em>생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이루어진 객체 *</em>(생략 가능)</p>
</li>
<li><p>반환값 : 지정된 프로토타입 및 프로퍼티를 갖는 새로운 객체</p>
</li>
</ul>
<p>Object.create 메서드는 첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체 생성. 즉, <strong>객체를 생성하면서 직접적으로 상속 구현</strong></p>
<p>*<em>Object create 장점 *</em></p>
<ul>
<li>new 연산자가 없어도 객체 생성 가능</li>
<li>프로토타입을 지정하면서 객체 생성 가능</li>
<li>객체 리터럴에 의해 생성된 객체도 상속받을 수 있음</li>
</ul>
<p>ESLint 에서는 Object.prototype 의 빌트인 메서드를 객체가 직접 호출하는 것은 권장 X. Object.create 로 프로토타입 체인 종점에 위치하는 객체 생성할 수 있기 때문</p>
<p>따라서 Object.prototype 빌트인 메서드는 간접적으로 호출하는 것을 권장</p>
<pre><code class="language-js">// 프로토타입이 null인 객체 생성
const obj = Object.create(null);
obj.a = 1;

// console.log(obj.hasOwnProperty(&#39;a&#39;));
// TypeError: obj.hasOwnProperty is not a function

// Object.prototype 빌트인 메서드는 객체로 직접 호출하지 않음
console.log(Object.prototype.hasOwnProperty.call(obj, &#39;a&#39;)); // true</code></pre>
<h3 id="19112-객체-리터럴-내부에서-__proto__-에-의한-직접-상속-es6">19.11.2 객체 리터럴 내부에서 <code>__proto__</code> 에 의한 직접 상속 (ES6)</h3>
<p>Object.create 메서드에 의한 직접 상속은 두 번째 인자로 프로퍼티 정의하는 것이 번거로움</p>
<p>객체를 생성한 이후 프로퍼티를 추가하는 방법도 있으나 이 또한 깔끔한 방법은 아님</p>
<p>ES6에서는 객체 리터럴 내부에서 <code>__proto__</code> 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있음</p>
<pre><code class="language-js">const myProto = { x: 10 };

const obj = {
  y: 20,
  // 객체를 직접 상속받음
  // obj -&gt; myProto -&gt; Object.prototype -&gt; null
  __proto__: myProto
};
// 위 코드는 아래와 동일함
const obj = Object.create(myProto, {
  y: { value: 20, writable: true, enumerable: true, configurable: true }
});

console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); // true</code></pre>
<h2 id="1912-정적-프로퍼티메서드">19.12 정적 프로퍼티/메서드</h2>
<p><strong><code>정적 프로퍼티/메서드</code></strong>는 <strong>생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드</strong>를 뜻함</p>
<p>정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없음</p>
<p>반대로 <strong>프로토타입 메서드를 호출하면 인스턴스를 생성</strong>해야 하지만 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있음</p>
<p>this를 참조하지 않는 프로토타입 메서드는 정적 메서드로 변경해도 동일한 효과를 얻을 수 있음</p>
<p>참고) 프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음 (Object.prototype.isPrototypeOf = Object#isPrototypeOf)</p>
<pre><code class="language-js">// 생성자 함수
function Person(name){
  this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHello = function(){
  console.log(`Hi! My name is ${this.name}`);
};

// 정적 프로퍼티
Person.staticProp = &#39;static prop&#39;;

// 정적 메서드
Person.staticMethod = function(){
  console.log(&#39;staticMethod&#39;);
};

const me = new Person(&#39;Lee&#39;);

// 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출한다.
Person.staticMethod(); // staticMethod

// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
me.staticMethod(); // TypeError: me.staticMethod is not a function</code></pre>
<h2 id="1913-프로퍼티-존재-확인">19.13 프로퍼티 존재 확인</h2>
<h3 id="19131-in-연산자">19.13.1 in 연산자</h3>
<p><code>in 연산자</code>는 <strong>객체 내에 특정 프로퍼티가 존재하는지 여부를 확인</strong></p>
<pre><code class="language-js">// key : 프로퍼티 키를 나타내는 문자열
// Object : 객체로 평가되는 표현식
Key in object</code></pre>
<pre><code class="language-js">const person = {
  name: &#39;Lee&#39;,
  address: &#39;Seoul&#39;
};

console.log(&#39;name&#39; in person); // true
console.log(&#39;address&#39; in person); // true
console.log(&#39;age&#39; in person); // false</code></pre>
<p>in 연산자는 <strong>확인 대상 객체의 프로퍼티뿐 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인</strong>하므로 주의가 필요</p>
<p>in연산자 대신 ES6에서 도입된 <code>Reflect.has</code> 메서드를 사용할 수도 있음</p>
<pre><code class="language-js">const person = { name: &#39;Lee&#39; };

console.log(Reflect.has(person, &#39;name&#39;)); // true
console.log(Reflect.has(person, &#39;toString&#39;)); // true</code></pre>
<h3 id="19132-objectprototypehasownproperty-메서드">19.13.2 Object.prototype.hasOwnProperty 메서드</h3>
<p><code>Object.prototype.hasOwnProperty</code> 메서드를 사용해도 <strong>객체에 특정 프로퍼티가 존재하는지 확인 가능</strong></p>
<p>인수로 전달받은 프로퍼티 키가 <strong>객체 고유의 프로퍼티 키인 경우에만 true</strong>, 상속받은 프로토타입 프로퍼티 키인 경우 false 반환</p>
<pre><code class="language-js">console.log(person.hasOwnProperty(&#39;name&#39;)); // true
console.log(person.hasOwnProperty(&#39;age&#39;)); // false
console.log(person.hasOwnProperty(&#39;toString&#39;)); // false</code></pre>
<h2 id="1914-프로퍼티-열거">19.14 프로퍼티 열거</h2>
<h3 id="19141-for--in-문">19.14.1 for ... in 문</h3>
<p><code>for ... in</code> 문은 <strong>객체의 모든 프로퍼티를 순회하며 열거</strong></p>
<p><code>for ... in</code> 문은 <strong>객체의 프로퍼티 개수만큼 순회하며 for ... in 문의 변수 선언문에서 선언한 변수에 프로퍼티 키를 할당</strong></p>
<pre><code class="language-js">for (변수선언문 in 객체){...}</code></pre>
<pre><code class="language-js">const person = {
  name: &#39;Lee&#39;,
  address: &#39;Seoul&#39;
};

// for ... in 문의 변수 prop에 person 객체의 프로퍼티 키가 할당
for (const key in person){
  console.log(key + &#39;: &#39; + person[key]);
}
// name: Lee
// address: Seoul</code></pre>
<p>for ... in 문은 <strong>객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거</strong></p>
<p>toString 메서드처럼 열거할 수 없도록 정의되어 있는 프로퍼티는 열거안됨</p>
<pre><code class="language-js">const person = {
  name: &#39;Lee&#39;,
  address: &#39;Seoul&#39;,
  __proto__: { age: 20}
};

for (const key in person){
  console.log(key + &#39;: &#39; + person[key]);
}
// name: Lee
// address: Seoul
// age: 20</code></pre>
<p>for ... in 문은 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음</p>
<p>상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인해야 함</p>
<p><strong>배열에는 for ... in 문을 사용하지 말고 일반적인 for 문, for ... of 문, Array.prototype.forEach 메서드 사용 권장</strong></p>
<h3 id="19142-objectkeysvaluesentries-메서드">19.14.2 Object.keys/values/entries 메서드</h3>
<p>객체 자신의 고유 프로퍼티만 열거하기 위해서는 for ... in 문 보다 Object.keys/values/entries 메서드 사용 권장</p>
<p><code>Object.keys</code> 메서드는 <strong>객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환</strong></p>
<pre><code class="language-js">const person = {
  name: &#39;Lee&#39;,
  address: &#39;Seoul&#39;,
  __proto__: {age: 20}
};

console.log(Object.keys(person)); // [&quot;name&quot;, &quot;address&quot;]</code></pre>
<p><code>Obejct.values</code> 메서드(ES8)는 <strong>객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환</strong></p>
<pre><code class="language-js">console.log(Object.values(person)); // [&quot;Lee&quot;, &quot;Seoul&quot;]</code></pre>
<p><code>Object.entries</code> 메서드(ES8)는 <strong>객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환</strong></p>
<pre><code class="language-js">console.log(Object.entries(person)); // [[&quot;name&quot;, &quot;Lee&quot;], [&quot;address&quot;, &quot;Seoul&quot;]]

Object.entries(person).forEach(([key, value]) =&gt; console.log(key, value));
// name Lee
// address Seoul</code></pre>
]]></description>
        </item>
    </channel>
</rss>