JavaScript가 뭔가요? - Web 개발 학습하기

JavaScript가 뭔가요?
================

* Overview: First steps
* 다음

 MDN의 JavaScript 초급자 과정에 오신 걸 환영합니다!
 이 글에서는 "JavaScript는 무엇인가요?", "JavaScript로 무엇을 할 수 있나요?"와 같은 질문에 답하며 JavaScript의 용도에 익숙해지도록 높은 수준에서 JavaScript를 살펴볼 것입니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: | 기본적인 컴퓨터 이해 능력, HTML과 CSS 기초. |
| 목표: |
 JavaScript가 뭔지, 어떤 일을 할 수 있는지, 웹 사이트에 어떻게 적용하는지 알아보기
 알기.
  |

높은 수준의 정의
---------

JavaScript는 웹 페이지에서 복잡한 기능을 구현할 수 있는 스크립팅 또는 프로그래밍 언어입니다. 웹 페이지가 단순히 정적인 정보를 표시하는 것 이상으로 시의적절한 콘텐츠 업데이트, 대화형 지도, 애니메이션 2D/3D 그래픽, 스크롤링 비디오 주크박스 등을 표시할 때마다 JavaScript가 관련되어 있을 가능성이 높습니다. JavaScript는 표준 웹 기술이라는 케이크의 세 번째 층입니다. 다른 두 개(HTML과 CSS)는 학습장의 다른 곳에서 자세히 알아봤습니다.

![표준 웹 기술의 세 가지 계층, HTML, CSS, JavaScript](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/cake.png)

* HTML은 문단, 제목, 데이터 표를 정의하거나 페이지에 이미지와 동영상을 삽입하는 등 웹 콘텐츠를 구성하고 의미를 부여하는 데 사용하는 마크업 언어입니다.
* CSS는 배경색과 글꼴을 설정하고 콘텐츠를 여러 열에 배치하는 등 HTML 콘텐츠에 스타일을 적용하는 데 사용하는 스타일 규칙 언어입니다.
* JavaScript는 동적으로 변경되는 콘텐츠를 만들고, 멀티미디어를 제어하고, 이미지에 애니메이션을 적용하는 등 거의 모든 작업을 수행할 수 있는 스크립팅 언어입니다. (모든 것이 가능한 것은 아니지만 몇 줄의 JavaScript 코드로 달성할 수 있는 것은 놀랍습니다.)

세 가지 레이어가 서로 잘 어울립니다. 간단한 텍스트 레이블을 예로 들어 보겠습니다. HTML을 사용하여 구조와 목적을 부여하기 위해 마크업할 수 있습니다.

```html
<p>Player 1: Chris</p>
```

![플레이어 1의 paragraph: 일반 텍스트로서의 크리스](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/just-html.png)

CSS를 추가하면 보기 좋게 꾸밀 수 있습니다.

```css
p {
  font-family: "helvetica neue", helvetica, sans-serif;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: center;
  border: 2px solid rgb(0 0 200 / 0.6);
  background: rgb(0 0 200 / 0.6);
  color: rgb(255 255 255 / 1);
  box-shadow: 1px 1px 2px rgb(0 0 200 / 0.4);
  border-radius: 10px;
  padding: 3px 10px;
  display: inline-block;
  cursor: pointer;
}
```

![스타일이 적용된 플레이어 1의 paragraph: 크리스](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/html-and-css.png)

마지막으로는 JavaScript를 곁들여 동적인 기능을 추가할 수 있습니다.

```js
const para = document.querySelector("p");

para.addEventListener("click", updateName);

function updateName() {
  const name = prompt("Enter a new name");
  para.textContent = `Player 1: ${name}`;
}
```

이 마지막 버전의 텍스트 레이블을 클릭하여 어떤 일이 발생하는지 확인해 보세요. (이 데모는 GitHub에서 찾을 수 있습니다 - 소스코드 또는 실행된 모습을 확인해보세요)

JavaScript는 이것보다 훨씬 많은 일을 할 수 있습니다. 어떤 일이 가능한지 자세히 알아봅시다.

그래서 어떤 일을 할 수 있나요?
------------------

클라이언트 사이드 JavaScript 언어의 핵심은 다음과 같은 일을 가능하게 해주는 프로그래밍 기능들로 구성됩니다.

* 변수에 값을 저장할 수 있습니다. 바로 위의 예제를 보면, 새 이름을 입력하도록 요청하고 해당 이름을 `name`이라는 변수에 저장합니다.
* 프로그래밍에서 "문자열"(string)이라고 부르는, 텍스트 조각을 조작합니다. 위 예제에서는 문자열 "플레이어 1: "과 `name` 변수의 값을 합쳐 온전한 텍스트 레이블("Player 1: Chris")을 생성합니다.
* 웹 페이지에서 특정 이벤트에 대한 응답으로 코드를 실행합니다. 예제에서는 `click` 이벤트를 사용하여 레이블이 클릭된 시점을 감지한 다음 텍스트 레이블을 업데이트하는 코드를 실행했습니다.
* 그밖에 더 많은 것들이 있습니다!

하지만 더욱 흥미로운 것은 클라이언트 측 JavaScript 언어 위에 구축된 기능입니다. **애플리케이션 프로그래밍 인터페이스**(**API**)라고 부르는 이 기능들은 JavaScript 코드에서 사용할 수 있는 강력한 마법을 추가로 제공합니다.

 API는 개발자가 구현하기 어렵거나 불가능한 프로그램을 구현할 수 있도록 미리 만들어서 제공하는 것입니다.
 기성품 가구 키트로 집을 짓는 것과 동일한 방식으로 프로그래밍할 수 있습니다. 직접 디자인을 구상하고, 올바른 목재를 찾고, 모든 패널을 올바른 크기와 모양으로 자르고, 올바른 크기의 나사를 찾아서 책장을 만드는 것보다 기성품 패널을 나사로 고정하여 책장을 만드는 것이 훨씬 쉽습니다.

API는 일반적으로 두 개의 범주로 나뉩니다.

![두 카테고리의 API; 브라우저 측면에 표시되는 서드파티 API와 브라우저에 표시되는 브라우저 API](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/browser.png)

**브라우저 API**는 웹 브라우저에 내장되어 있으며, 주변 컴퓨터 환경의 데이터를 노출하거나 유용한 복잡한 작업을 수행할 수 있습니다.

* `DOM (Document Object Model) API`를 사용하면 HTML과 CSS를 조작하여 HTML을 생성, 제거 및 변경하고 페이지에 새 스타일을 동적으로 적용하는 등의 작업을 수행할 수 있습니다.
 예를 들어 페이지에 팝업 창이 나타나거나 새로운 콘텐츠가 표시될 때마다(위의 간단한 데모에서 보았듯이) DOM이 작동하는 것입니다.
* `Geolocation API`로 지리정보를 가져올 수 있습니다.
 이것이 Google 지도가 사용자의 위치를 찾아 지도에 표시하는 방법입니다.
* `Canvas`와 `WebGL` API를 사용하면 애니메이션 2D 및 3D 그래픽을 만들 수 있습니다.
 두 웹 기술을 사용해서 만들 수 있는 놀라운 결과를 엿보려면 Chrome Experiments와 webglsamples를 방문하세요.
* `HTMLMediaElement`와 `WebRTC`를 포함하는 오디오와 비디오 API (en-US)를 사용하면 웹 페이지에서 바로 오디오 및 비디오를 재생하거나 웹 카메라에서 비디오를 가져와 다른 사람의 컴퓨터에 표시하는 등 멀티미디어로 정말 흥미로운 작업을 할 수 있습니다. (간단한 스냅샷 데모를 방문해서 감을 잡아보세요.)

**참고:** 위의 데모 중 상당수는 구형 브라우저에서 작동하지 않으므로 실험할 때는 Firefox, Chrome, Edge, Opera와 같은 최신 브라우저를 사용하여 코드를 실행하는 것이 좋습니다.
 이후에 프로덕션 코드 (고객이 사용하게 될 코드) 배포에 다가가게 되면 크로스 브라우저 테스트 (en-US)를 고려하세요.

**서드파티 API**는 기본적으로 브라우저에 내장되어 있지 않으며, 일반적으로 웹 어딘가에서 해당 코드와 정보를 가져와야 합니다.

* Twitter API로 여러분의 최신 트윗을 웹 사이트가 보여주도록 구현할 수 있습니다.
* Google 지도 API와 OpenStreetMap API로 웹 사이트에 지도를 삽입하고, 지도 관련 기능을 추가할 수 있습니다.

**참고:** 위의 서드파티 API는 고급 기능으로, 이 과정에서는 다루지 않습니다. 이에 대한 자세한 내용은 클라이언트 사이드 웹 API 과정에서 알아보세요.

여기 나열된 항목 말고도 훨씬 다양한 API들이 존재합니다! 하지만 아직 너무 앞서나가진 마세요. JavaScript를 하루 배운 것으로 차세대 Facebook, Google 지도, Instagram을 만들 수는 없는 법입니다. 아직 배워야 할 기본이 많이 남았고, 기본을 배우는 것이 이 과정을 보는 이유이니, 시작해 봅시다!

웹 페이지에서 JavaScript는 어떤 일을 하나요?
------------------------------

여기서는 실제로 몇 가지 코드를 살펴보면서 페이지에서 JavaScript를 실행하면 실제로 어떤 일이 일어나는지 살펴보겠습니다.

브라우저에서 웹 페이지를 로드할 때 어떤 일이 일어나는지 간단히 정리해 보겠습니다. (CSS 동작 방식에서 먼저 알아봤습니다.) 브라우저에서 웹 페이지를 로드하면 실행 환경(브라우저 탭) 내에서 코드(HTML, CSS 및 JavaScript)를 실행하게 됩니다. 이는 원재료(코드)를 받아 제품(웹 페이지)을 출력하는 공장과 같습니다.

![페이지가 로드될 때 HTML, CSS, JavaScript 코드가 함께 브라우저 탭에 콘텐츠를 생성합니다.](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/execution.png)

 JavaScript의 가장 일반적인 용도는 위에서 언급했듯이 DOM (Document Object Model) API를 통해 HTML과 CSS를 동적으로 수정하여 사용자 인터페이스를 업데이트하는 것입니다.
 웹 문서의 코드는 일반적으로 페이지에 표시되는 순서대로 로드되고 실행됩니다.
 수정하려는 HTML과 CSS 코드보다 JavaScript를 먼저 불러와 실행해버리면 오류가 발생할 수 있습니다.
 이 글의 뒷부분에 있는 스크립트 로딩 전략에서 이 문제의 해결법을 알아보겠습니다.

### 브라우저 보안

 각 브라우저 탭에는 코드를 실행할 수 있는 별도의 그릇이 있습니다. (전문 용어로 '실행 환경'이라고 부릅니다.) 대부분의 경우 각 탭의 코드는 완전히 독립적으로 실행되며 한 탭의 코드가 다른 탭이나 다른 웹사이트의 코드에 직접적인 영향을 미칠 수 없습니다.
 이건 중요한 보안 절차입니다. 이런 제약이 없으면 해커들이 다른 웹사이트의 정보를 훔치는 코드를 작성하는 등 나쁜 짓을 할 수 있기 때문입니다.

**참고:** 서로 다른 웹 사이트/탭 사이에서 코드와 정보를 안전하게 주고받을 수 있는 방법이 있지만, 지금 과정과는 거리가 멀기 때문에 여기서는 다루지 않겠습니다.

### JavaScript 실행 순서

 브라우저가 JavaScript 블록을 마주치면, 일반적으로는 순서대로 위에서 아래로 실행합니다.
 따라서 코드 배치 순서에도 주의를 기울여야 합니다.
 예를 들기 위해 맨 위의 첫 예제 코드로 돌아가봅시다.

```js
const para = document.querySelector("p");

para.addEventListener("click", updateName);

function updateName() {
  const name = prompt("Enter a new name");
  para.textContent = `Player 1: ${name}`;
}
```

위 코드는 텍스트 단락을 선택(1번 줄)해서 이벤트 수신기를 첨부(3번 줄)하여, 사용자가 단락을 클릭하면 `updateName()` 코드 블록(5번 ~ 8번 줄)을 실행하도록 합니다. `updateName()` 코드 블록(이렇게 재사용 가능하도록 나눠놓은 코드 블록을 "함수"라고 합니다)은 새 이름을 요청한 다음 해당 이름을 단락에 삽입하여 표시를 업데이트합니다.

 코드의 처음 두 줄의 순서를 바꾸면 더 이상 작동하지 않고 브라우저 개발자 콘솔 (en-US)에 오류 `TypeError: para is undefined`가 나타납니다.
 `para` 객체가 아직 존재하지 않으므로 이벤트 수신기를 추가할 수 없기 때문입니다.

**참고:** 이는 매우 흔한 오류로, 코드에서 참조하는 객체가 실제로 존재하는지 확인한 후에 작업을 수행해야 합니다.

### 인터프리터와 컴파일러

 프로그래밍에서 **인터프리터**와 **컴파일러**라는 용어를 들어봤을 것입니다.
 인터프리터를 사용하는 언어에서는 코드가 위에서 아래로 실행되고 코드 실행 결과가 즉시 반환됩니다.
 브라우저에서 코드를 실행하기 전에 코드를 다른 형태로 변환할 필요가 없습니다.
 코드는 프로그래머에게 친숙한 텍스트 형식으로 수신되어 바로 처리됩니다.

 반면, 컴파일러를 사용하는 컴파일 언어는 컴퓨터에서 실행되기 전에 다른 형태로 변환(컴파일)됩니다.
 예를 들어, C/C++에서는 코드를 컴파일러로 기계언어로 변환하여, 그 결과를 컴퓨터가 실행합니다.
 프로그램은 프로그램의 원본 소스 코드에서 생성된 이진 형식(바이너리)으로부터 실행됩니다.

 JavaScript는 가볍고, 인터프리터를 사용하는 프로그래밍 언어입니다.
 웹 브라우저는 JavaScript 코드를 원문 텍스트 형식으로 입력받아 실행합니다.
 기술적인 측면으로 따지자면, 대부분의 모던 JavaScript 인터프리터들은 실제 성능 향상을 위해 **JIT 컴파일**(just-in-time 컴파일)이라는 기술을 사용하는데, 스크립트의 실행과 동시에 소스 코드를 더 빠르게 실행할 수 있는 이진 형태로 변환하여 최대한 높은 실행 속도를 얻는 방법입니다.
 하지만 컴파일이 미리 처리되는 것이 아니라 런타임에 처리되기 때문에 JavaScript는 여전히 인터프리터 언어로 분류됩니다.

두 방법 모두 각자의 장점을 가지고 있으나, 여기서 그 부분까지 논하지는 않겠습니다.

### 서버 사이드와 클라이언트 사이드 코드

 웹 개발의 맥락에서 **서버 사이드 코드**와 **클라이언트 사이드 코드**를 들어봤을 것입니다.
 클라이언트 사이드 코드는 사용자의 컴퓨터에서 실행되는 코드로, 웹 페이지를 볼 때 페이지의 클라이언트 측 코드가 다운로드된 후 실행되어 브라우저에 표시됩니다.
 이 과정에서 다루는 것은, 정확히는 **클라이언트 사이드 JavaScript**입니다.

 반대로 서버 사이드 코드는 서버에서 실행되고 그 결과가 다운로드되어 브라우저에 표시됩니다.
 인기 있는 서버 사이드 웹 언어로는 PHP, Python, Ruby, ASP.NET, 그리고 JavaScript가 있습니다!
 JavaScript는 브라우저 뿐만 아니라, 많은 사람들이 사용하는 Node.js 환경처럼 서버 사이드 언어로도 사용할 수 있습니다. 서버 사이드 JavaScript에 관해서는 동적 웹 사이트 – 서버 사이드 프로그래밍 과정에서 더 알아보세요.

### 동적 코드와 정적 코드

**동적**이라는 단어는 클라이언트 사이드 JavaScript와 서버 사이드 언어를 모두 설명하는 데 사용되며, 웹 페이지/웹 앱의 디스플레이를 업데이트하여 상황에 따라 다른 내용을 표시하고 필요에 따라 새 콘텐츠를 생성하는 기능을 의미합니다.
 서버 사이드 코드는 데이터베이스에서 데이터를 가져오는 등 서버에서 새 콘텐츠를 동적으로 생성하는 반면, 클라이언트 사이드 JavaScript는 새 HTML 표를 생성하고 서버에서 요청한 데이터로 채운 다음 사용자에게 표시되는 웹 페이지에 테이블을 표시하는 등 클라이언트 브라우저 내부에서 새 콘텐츠를 동적으로 생성합니다.
 두 가지 맥락에서 의미는 약간 다르지만 서로 연관되어 있으며 일반적으로 두 가지 접근 방식(서버와 클라이언트 사이드)이 함께 작동합니다.

동적으로 바뀌는 내용이 없는 웹 페이지를 **정적**인 페이지라고 하며, 항상 동일한 콘텐츠만 표시합니다.

웹 페이지에 JavaScript를 어떻게 넣나요?
---------------------------

 JavaScript는 CSS와 유사한 방식으로 HTML 페이지에 적용됩니다.
 CSS가 외부 스타일시트를 적용하는 데 `<link>` 요소를 사용하고 내부 스타일시트를 적용하는 데 `<style>` 요소를 사용하는 반면, JavaScript는 HTML의 세계에서 단 하나의 친구인 `<script>` 요소만 있으면 됩니다. 이것이 어떻게 작동하는지 알아봅시다.

### 내부 JavaScript

1. 먼저, 예제 파일인 apply-javascript.html을 여러분의 컴퓨터로 복사하세요. 적당한 폴더에 저장하면 됩니다.
2. 브라우저와 텍스트 편집기로 1번의 파일을 여세요. HTML 코드가 클릭 가능한 버튼 하나를 포함하는 간단한 웹 페이지를 만드는 것을 확인할 수 있습니다.
3. 텍스트 편집기로 가서 아래의 코드를 추가하세요. 닫는 `</head>` 태그 바로 앞에 넣어주세요.

```html
<script>
 // JavaScript goes here
</script>
```

4. 이제 `<script>` 요소 안에 JavaScript를 넣어서 웹 페이지를 조금 흥미롭게 만들어보겠습니다. 아래 코드를 "// JavaScript goes here" 바로 아래에 넣어주세요.

```js
document.addEventListener("DOMContentLoaded", () => {
  function createParagraph() {
    const para = document.createElement("p");
    para.textContent = "You clicked the button!";
    document.body.appendChild(para);
  }

  const buttons = document.querySelectorAll("button");

  for (const button of buttons) {
    button.addEventListener("click", createParagraph);
  }
});
```

5. 파일을 저장하고 브라우저에서 페이지를 새로고침하세요. 이제 버튼을 클릭하는 순간 새로운 문단이 생성돼 밑에 추가되는 모습을 볼 수 있습니다.

**참고:** 예제가 잘 동작하지 않으면 각 단계를 다시 차근차근 시도하면서 모두 정확히 따라했는지 확인해보세요.
 시작 코드를 저장할 때 `.html` 파일로 저장했나요?
 `<script>` 요소를 닫는 `</head>` 요소 바로 앞에 배치했나요?
 JavaScript 코드를 정확히 보이는 그대로 입력했나요? **JavaScript는 대소문자를 구별하고 매우 까다로우므로 구문을 예제 그대로 입력하지 않으면 동작하지 않을 수 있습니다.**

**참고:** 완성된 예제 코드를 GitHub의 apply-javascript-internal.html에서 볼 수 있습니다. (실제로 동작하는 모습도 확인해보세요)

### 외부 JavaScript

내부 JavaScript는 잘 동작했지만, JavaScript를 외부 파일로 분리하고 싶으면 어떡할까요? 지금부터 살펴봅시다.

1. 위의 HTML 파일과 같은 폴더에 새로운 파일을 만드세요. `script.js`라는 이름을 붙여줍니다. 파일 확장자가 .js여야 JavaScript로 인식되므로 이 확장자를 사용하세요.
2. 현재 `<script>` 요소를 다음 코드로 대체하세요.

```html
<script src="script.js" defer></script>
```

3. `script.js` 파일 안에 아래 내용을 입력하세요.

```js
function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "You clicked the button!";
  document.body.appendChild(para);
}

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", createParagraph);
}
```

4. 저장하고 브라우저를 새로고침하세요. 아까와 같은 모습을 볼 수 있습니다!
 분명히 동작은 같지만, JavaScript 파일을 외부 파일로 분리할 수 있었습니다.
 일반적으로 코드를 체계적으로 정리하고 여러 HTML 파일에서 재사용할 수 있다는 측면에서 스크립트를 외부 파일로 분리하는 것이 좋습니다.
 또한 HTML에 스크립트 덩어리를 넣지 않았기에 HTML을 더 쉽게 읽을 수 있습니다.

**참고:** 완성된 예제 코드를 GitHub의 apply-javascript-external.html과 script.js에서 볼 수 있습니다. (실제로 동작하는 모습도 확인해보세요)

### 인라인 JavaScript 처리기

 가끔 HTML 안에 실제 JavaScript 코드가 있는 경우가 있습니다.
 아래 코드처럼요.

```js
function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "You clicked the button!";
  document.body.appendChild(para);
}
```

```html
<button onclick="createParagraph()">Click me!</button>
```

아래에서 이 버전의 데모를 사용해 볼 수 있습니다.

이 데모는 앞의 두 섹션과 완전히 같은 기능을 가지고 있지만 `<button>` 요소가 버튼 클릭 동작을 `onclick` 인라인 처리기로 포함한다는 점만 다릅니다.

**하지만, 이 방법을 사용하지 말아주세요.** JavaScript로 HTML 코드를 물들이는 것은 나쁜 방법일 뿐더러 비효율적입니다. JavaScript를 적용하려는 모든 버튼마다 일일히 `onclick="createParagraph()"`를 추가해야 하니까요.

### 대신 addEventListener 사용하기

 HTML에 JavaScript를 직접 넣는 대신, 순수한 JavaScript 구문을 사용하세요.
 `querySelectorAll()` 함수를 사용하면 현재 페이지 내의 모든 버튼을 선택할 수 있습니다.
 그런 다음 `addEventListener()`를 사용하여 각각에 핸들러를 할당하여 버튼을 반복할 수 있습니다.
 이를 위한 코드는 아래와 같습니다.

```js
const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", createParagraph);
}
```

 이 속성은 `onclick` 속성보다 약간 길 수 있지만 페이지에 있는 버튼의 수나 추가 또는 제거된 버튼의 수에 관계없이 모든 버튼에 대해 작동합니다.
 JavaScript는 변경할 필요가 없습니다.

**참고:** `apply-javascript.html` 버전을 편집하고 파일에 버튼을 몇 개 더 추가해 보세요.
 브라우저를 새로고침해보면, 아무 버튼을 누르더라도 새로운 문단이 생성되는 모습을 볼 수 있습니다.
 멋지지 않나요?

### 스크립트 로딩 전략

 스크립트를 적절한 시점에 불러오는 일에는 몇 가지 문제가 있습니다. 뭐든지 보이는 것만큼 간단하지는 않은 법이죠.
 흔히 마주치게 되는 문제는, 페이지의 모든 HTML이 표시되는 순서대로 로드된다는 것입니다.
 JavaScript를 사용해서 페이지 내의 요소(더 정확하게는 Document Object Model (en-US))를 조작하려고 할 때, 해당 요소를 포함한 HTML 코드보다 JavaScript를 먼저 불러와버리면 코드가 올바르게 동작하지 못할 것입니다.

 앞선 내부와 외부 JavaScript 예제에서는 HTML 본문을 읽기 전에 문서의 헤드에서 JavaScript를 불러와 실행합니다.
 이 경우 오류가 발생할 수 있으므로 몇 가지 구문을 사용하여 문제를 해결했습니다.

내부 예제에서는 코드 주위에 이런 구조를 볼 수 있습니다.

```js
document.addEventListener("DOMContentLoaded", () => {
  // …
});
```

 이건 이벤트, 그중에서도 HTML 본문 전체를 불러와 읽었다는 것을 나타내는 브라우저의 `DOMContentLoaded` 이벤트를 수신하는 이벤트 수신기입니다.
 이 블록 내부의 JavaScript는 이벤트가 발생하기 전에는 실행되지 않으므로 로딩 시점으로 인한 오류를 예방할 수 있습니다. (이후 과정에서 이벤트에 대해 배우기를 참조하세요.)

외부 예제에서는 `<script>` 태그 요소에 도달하면 브라우저에 HTML 콘텐츠를 계속 다운로드하도록 지시하는 `defer` 속성이라는 보다 최신 JavaScript 기능을 사용하여 문제를 해결합니다.

```html
<script src="script.js" defer></script>
```

이 경우 스크립트와 HTML이 동시에 로드되어 코드가 작동합니다.

**참고:** `defer` 특성이 오류를 예방하므로, 외부 예제에서는 `DOMContentLoaded` 이벤트를 사용하지 않았습니다.
 `defer`은 외부 스크립트에서만 작동하기 때문에 내부 JavaScript 예제에서는 `defer` 솔루션을 사용하지 않았습니다.

 고전적인 방법은 스크립트 요소를 본문의 맨 마지막(`</body>` 태그 바로 앞)에 배치하는 것입니다. 그러면 모든 HTML을 읽은 후에 스크립트를 불러오게 됩니다.
 이 방법의 문제는 HTML DOM을 모두 불러오기 전에는 스크립트의 로딩과 분석이 완전히 중단된다는 점입니다.
 그래서 많은 스크립트를 포함하는 대형 사이트에서는 이로 인해 사이트 속도가 느려지는 중대한 성능 문제가 발생할 수 있습니다.

#### async와 defer

 스크립트 중단 문제를 해결할 수 있는 기능에는 사실 두 가지가 있습니다. `async`와 (위에서 본) `defer`입니다.
 어떤 차이점이 있는지 알아봅시다.

`async` 특성을 지정하면 스크립트를 가져오는 동안 페이지 로딩을 중단하지 않습니다.
 그러나 다운로드가 끝나면 스크립트가 바로 실행되는데, 실행 도중에는 페이지 렌더링이 중단됩니다.
 스크립트의 실행 순서를 보장할 방법은 없습니다.
 따라서 `async`는 스크립트가 서로 독립적으로 실행되고, 다른 스크립트에 의존하지 않는 경우에 사용하는 것이 가장 좋습니다.

`defer` 속성으로 로드된 스크립트는 페이지에 표시되는 순서대로 로드됩니다.
 또한 페이지 콘텐츠를 모두 불러오기 전까지는 실행하지 않으므로, 스크립트가 DOM의 위치에 의존하는 경우(예: 페이지에서 하나 이상의 요소를 수정하는 경우) 유용합니다.

다음은 다양한 스크립트 로드 방법과 해당 방법이 페이지에 미치는 영향을 시각적으로 표현한 것입니다.

![세 가지 스크립트 로딩 방법의 작동 방식: 기본값은 JavaScript를 가져와 실행하는 동안 구문 분석이 차단됩니다. async를 사용하면 구문 분석이 실행 중에만 일시 중지됩니다. defer을 사용하면 구문 분석은 일시 중지되지 않지만 다른 모든 구문 분석이 완료된 후에 실행이 이루어집니다.](/ko/docs/Learn/JavaScript/First_steps/What_is_JavaScript/async-defer.jpg)

이 이미지는 HTML 명세에서 가져와 CC BY 4.0 라이선스 하에 축소한 이미지입니다.

예를 들어, 아래와 같이 세 개의 스크립트 요소를 가지고 있다고 가정해 보겠습니다.

```html
<script async src="js/vendor/jquery.js"></script>

<script async src="js/script2.js"></script>

<script async src="js/script3.js"></script>
```

 위 코드로는 스크립트가 HTML의 순서대로 불러와질 것이라고 확실하게 예측할 수 없습니다.
 `jquery.js`가 `script2.js` 및 `script3.js`의 앞이나 뒤에 로드될 수 있으며, 이 경우 스크립트가 실행될 때 `jquery`가 정의되지 않기 때문에 `jquery`에 따라 해당 스크립트의 모든 함수가 오류를 발생시킬 수 있습니다.

`async`는 로드할 백그라운드 스크립트가 많고 가능한 한 빨리 제자리에 배치하고 싶을 때 사용해야 합니다.
 예를 들어 실제로 게임이 시작될 때 필요한 게임 데이터 파일을 로드해야 하지만 지금은 스크립트 로딩으로 인해 차단되지 않고 게임 인트로, 제목, 로비만 표시하고 싶다고 가정해 보겠습니다.

`defer` 속성(아래 참조)을 사용하여 로드된 스크립트는 페이지에 표시되는 순서대로 실행되며 스크립트와 콘텐츠가 다운로드되는 즉시 실행됩니다:

```html
<script defer src="js/vendor/jquery.js"></script>

<script defer src="js/script2.js"></script>

<script defer src="js/script3.js"></script>
```

 두 번째 예제에서는 `jquery.js`가 `script2.js` 및 `script3.js`보다 먼저 로드되고, `script2.js`가 `script3.js`보다 먼저 로드되는 것을 확인할 수 있습니다.
 페이지 콘텐츠가 모두 로드될 때까지 실행되지 않으므로 스크립트가 DOM의 위치에 의존하는 경우(예: 페이지에서 하나 이상의 요소를 수정하는 경우) 유용합니다.

요약하자면,

* `async`와 `defer` 모두 브라우저가 페이지의 다른 내용(DOM 등등)을 불러오는 동안 스크립트를 별도 스레드에서 불러오게 만듭니다. 덕분에 스크립트를 가져오는 동안 페이지 로딩이 중단되지 않습니다.
* `async` 속성을 지정한 스크립트는 다운로드가 끝나는 즉시 실행합니다.
 이렇게 하면 현재 페이지의 렌더링을 중단하며, 특정 실행 순서가 보장되지 않습니다.
* `defer` 속성을 지정한 스크립트는 순서를 유지한 채로 가져오며 모든 콘텐츠를 다 불러온 이후에 실행합니다.
* 스크립트를 즉시 실행해야 하고 종속성이 없는 경우 `async`를 사용하세요.
* 다른 스크립트에 의존하거나 DOM 로딩이 필요한 스크립트에는 `defer`를 사용하여 스크립트를 로드하고, 원하는 순서에 맞춰서 `<script>` 요소를 배치하세요.
주석
--

 HTML 및 CSS와 마찬가지로 JavaScript 코드에서도 브라우저가 무시하는 주석을 작성할 수 있으며, 코드 작동 방식에 대한 지침을 동료 개발자(아니면 6개월 후에 코드를 다시 살펴보고 무엇을 했는지 기억이 나지 않는 여러분)에게 제공하기 위해 존재합니다.
 주석은 매우 유용하며, 특히 규모가 큰 애플리케이션의 경우 자주 사용해야 합니다.
 주석에는 두 종류가 있습니다.

* 한 줄 짜리 주석은 맨 앞에 이중 빗금(//)으로 작성합니다.

```js
// 나, 주석
```

* 여러 줄 주석은 /\*과 \*/의 사이에 작성합니다.

```js
/\*
 나 또한
 주석
\*/
```

그래서, 마지막으로 봤던 JavaScript 예제 코드에 주석을 추가한다면 이런 모습이 되겠죠.

```js
// 함수: 새로운 문단을 생성해서 HTML 본문 끝에 붙입니다.

function createParagraph() {
  const para = document.createElement("p");
  para.textContent = "You clicked the button!";
  document.body.appendChild(para);
}

/\*
 1. 페이지 내의 모든 버튼에 대한 참조를 배열 형태로 가져옵니다.
 2. 버튼 각각을 순회하면서 클릭 이벤트 수신기를 추가합니다.

 아무 버튼이나 클릭하면 createParagraph() 함수가 실행됩니다.
\*/

const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", createParagraph);
}
```

**참고:** 보통은 많은 주석이 적은 주석보다 낫지만, 변수가 무엇인지 설명하기 위해(변수 이름이 더 직관적이어야 함) 또는 매우 간단한 연산을 설명하기 위해(코드가 지나치게 복잡할 수 있음) 많은 주석을 추가하는 경우 주의해야 합니다.

요약
--

 이로써 JavaScript의 세계로 한 걸음 내딛었습니다.
 왜 JavaScript를 써야 하는지, 그리고 어떤 일을 할 수 있는지에 대해 익숙해지기 위해 이론부터 시작했습니다.
 그 과정에서 몇 가지 코드 예제를 보고 JavaScript가 웹사이트의 나머지 코드와 어떻게 조화를 이루는지 등을 배웠습니다.

 지금 당장은 JavaScript가 약간 버겁게 느껴질지도 모르겠지만, 너무 걱정하지 마세요. 이 과정에 걸친 간단한 단계들을 밟다 보면 전체적인 모습이 보일겁니다.
 다음 글에서는 바로 실전으로 들어가서 스스로 JavaScript 예제를 만들어보도록 합시다.

* Overview: First steps
* 다음


문자열 제대로 다루기 - Web 개발 학습하기

문자열 제대로 다루기
===========

* 이전
* Overview: First steps
* 다음

이제까지 문자열의 기초를 살펴보았습니다. 이제부터 - 텍스트 문자열의 길이 찾기, 문자열 합치기 및 쪼개기 등과 같은- 내장된 메서드를 사용하여 문자열에서 수행할 수 있는 유용한 작업에 대해 생각해 봅시다. 문자열의 한 문자를 다른 문자로 대체하는 등의 작업을 수행합니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: |
 기본 컴퓨터 활용 능력, HTML 및 CSS에 대한 기본적인 이해,
 JavaScript가 무엇인지 이해합니다.
  |
| 목표: |
 문자열이 객체임을 이해하고, 해당 객체에서 사용할 수있는 몇 가지
 기본 메서드를 사용하여 문자열을 조작하는 방법을 배웁니다.
  |

Strings as objects
------------------

이전에 말했지만, 다시 말하면 - JavaScript의 모든 것이 객체입니다. 문자열을 만들 때, 예를 들면

```js
var string = "This is my string";
```

변수가 문자열 객체 인스턴스되면, 결과적으로 수많은 속성과 메서드가 사용 가능하게 됩니다. String 객체 페이지로 이동하여 페이지 측면의 목록을 내려다 보면 이것을 볼 수 있습니다!

이제 뇌가 녹기 시작하기 전에, 걱정하지 마십시오! 학습 여행 중에 초기에 대부분을 알 필요가 없습니다. 그러나 여기에서 살펴볼 몇 가지 사항을 자주 사용하게 될 것입니다.

콘솔에 예제를 작성해 보세요. 아래의 한 가지를 제공합니다(새 탭이나 새 창에서 콘솔을 열 수 있고, 브라우저의 개발자 콘솔을 사용할 수도 있습니다).

### 문자열의 길이 찾기

간단합니다 — 간단하게 `length` 프로퍼티를 사용할 수 있습니다. 다음 코드를 입력해 보세요.

```js
var browserType = "mozilla";
browserType.length;
```

결과는 7을 리턴해야 합니다. 'mozilla'는 7글자이기 때문입니다. 이것은 여러 가지 이유로 유용합니다. 예를 들어 이름의 길이에 따라 이름의 순서를 정렬해야 하던가, 유저가 작성한 이름이 특정 길이 이상일 때 너무 길다는 것을 알려줘야 하는 경우에 사용할 수 있습니다.

### 특정 문자열 찾기

관련하여, 대괄호 표기법을 이용해서 문자열 안의 문자를 구할 수 있습니다. 대괄호 표기법은 변수명 끝에 대괄호를 포함합니다. 대괄호 안에는 구하고 싶은 문자의 숫자를 포함시키면 되며, 예를 들어 아래의 경우 첫 번째 문자를 구할 수 있습니다:

```js
browserType[0];
```

컴퓨터는 1이 아니라 0부터 숫자를 셉니다! 문자열의 마지막 문자를 구하기 위해서, 우리는 다음 코드를 사용할 수 있으며, 기술적인 `length` 프로퍼티과 같이 사용하면 아래와 같습니다:

```js
browserType[browserType.length - 1];
```

"mozilla"는 7글자이지만, 숫자는 0부터 시작하기 때문에 글자의 위치는 6입니다. 그렇기 때문에 `length-1`을 사용합니다. 예를 들어, 여러 문자열 중 첫 번째 문자를 찾아 알파벳순으로 정렬해야 할 경우에 사용할 수 있습니다.

### 문자열 내부의 하위 문자열 찾기 및 추출

1. 때때로 큰 문자열 안의 작은 문자열(우리는 이것을 하위 문자열이라고 이야기 한다.)을 찾고 싶을 것입니다. 이 작업은 `indexOf()`를 사용하여 완료할 수 있습니다, which takes a single parameter — 찾기 원하는 하위 문자열을 찾을 수 있습니다. 시도해 봅시다:

```js
browserType.indexOf("zilla");
```

결과는 2입니다. 하위 문자열인 "zilla"는 "mozilla'의 2번 위치(0, 1, 2— 그러므로 3번째 문자열)에서 시작합니다. 이러한 코드는 문자열을 필터링하는 데 사용될 수 있습니다. 예를 들어 웹 주소 목록에서 "mozilla"가 포함된 주소만 인쇄하고 싶은 경우입니다.
2. 다른 방법으로도 할 수 있으며, 더욱 효율적일 수 있습니다. 다음 예제를 따라해 봅시다:

```js
browserType.indexOf("vanilla");
```

이렇게 하면 -1( 하위 문자열 (이 경우 'vanilla')이 기본 문자열에서 발견되지 않으면 반환한다.)의 결과를 얻을 수 있습니다.
 하위 문자열 'mozilla'가 포함되지 않은 문자열의 모든 인스턴스를 찾으려면 이 연산자를 사용하고 아래에 표시된 것처럼 부정 연산자를 사용해서 작업을 수행할 수 있습니다. 다음과 같이 할 수 있습니다:

```js
if (browserType.indexOf("mozilla") !== -1) {
  // do stuff with the string
}
```

3. 문자열 내에서 부분 문자열이 어디에서 시작되고 어떤 문자로 끝나는지 알고 싶으면 `slice()`를 사용하여 문자열을 추출할 수 있습니다. 다음을 시도해 봅시다:

```js
browserType.slice(0, 3);
```

"moz"를 반환합니다 - 첫 번째 파라메터는 추출을 시작할 문자 위치이고 두 번째 파라메터는 추출할 문자의 갯수입니다. 따라서 슬라이스는 첫 번째 위치에서부터 세 번째 위치까지 포함됩니다.
4. 또한 특정 문자 뒤에 문자열의 나머지 문자를 모두 추출하려는 경우 두 번째 매개 변수를 포함하지 않고 문자열에서 나머지 문자를 추출할 위치의 문자 위치만 포함하면 됩니다. 다음을 시도해보십시오.

```js
browserType.slice(2);
```

이렇게 하면 "zilla"가 반환됩니다. 문자의 2번째 위치는 "z"이고 두 번째 매개 변수를 포함하지 않았기 때문에 반환된 하위 문자열은 문자열의 나머지 문자 모두입니다.

**참고:** `slice()` has other options too; study the `slice()` page to see what else you can find out.

### 대/소문자 변경

문자열 메소드 `toLowerCase()` 와`toUpperCase()` 는 문자열을 가져와 그것을 모두 각각 대문자나 소문자로 바꿉니다. 이는 데이터베이스에 저장하기 전에 모든 사용자 입력 데이터를 표준화하려는 경우 유용합니다.

다음 행을 입력하여 어떻게 되는지 살펴보겠습니다.

```js
var radData = "My NaMe Is MuD";
radData.toLowerCase();
radData.toUpperCase();
```

### 문자열의 일부를 변경하기

문자열 내의 한 하위 문자열을 `replace()` (en-US) 를 통해 다른 하위 문자열로 바꿀 수 있습니다. 이 작업은 기본적인 수준에서 매우 간단하게 작동합니다. 하지만 아직 시도해보지 않은 고급 작업도 있습니다.

그것은 2개의 매개변수를 가집니다. — 바뀜을 당하는 문자와 바꾸려는 문자입니다. 다음 예제를 따라해보세요.:

```js
browserType.replace("moz", "van");
```

예제
--

이 섹션에서는 문자열을 다루는 방법을 설명합니다. 아래의 각 실습에서는 문자열로 이루어진 배열을 루프문을 사용해 bullet list(불릿 리스트)로 표현하였습니다. 지금 배열이나 루프를 이해할 필요가 없습니다. - 이러한 내용은 추후에 설명합니다. 중요한것은 각각의 문자열이 우리가 원하는 형식으로 출력하는 코드를 작성하는 것입니다.

각 예제에는 리셋 버튼이 있고, 리셋 버튼은 실수를 했거나 코드가 작동하지 않아서 재설정하는데 사용할 수 있습니다. 해결 방법을 모를 때, 해답 버튼(solution button)을 누르면 해답을 볼 수 있습니다.

### 인사말 필터링 하기

첫 번째 예제는 간단히 시작해봅시다. 우리는 배열에 들어있는 크리스마스 인사말 메시지를 정렬하려고 합니다. if(...)을 사용해 각 문자열을 비교하고 크리스마스 메시지인 경우의 목록만 인쇄하려고 합니다.

1. 먼저 각 메시지가 크리스마스 메시지인지 여부를 테스트할 수 있는 방법을 생각해봅시다. 메시지들은 어떤 문자열이 있고, 존재하는지 테스트하기 위해 어떤 방법을 사용할 수 있을까요?
2. 연산자와 피연산자를 사용해 조건문을 만들어야 합니다. 연산자 왼쪽에 있는것과 연산자 오른쪽에 있는 것이 동등한가요? 또는 이 경우 왼쪽 메서드가 오른쪽으로 결과값을 전달합니까?
3. 힌트 : 이 경우 메서드 호출이 결과값과 같지 않은지 테스트하는 것이 더 유용할 수 있습니다.
```

<h2>Live output</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>

<textarea id="code" class="playable-code" style="height: 290px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
const greetings = ['Happy Birthday!',
                 'Merry Christmas my love',
                 'A happy Christmas to all the family',
                 'You\'re all I want for Christmas',
                 'Get well soon'];

for (let greeting of greetings) {
  // Your conditional test needs to go inside the parentheses
  // in the line below, replacing what's currently there
  if (greeting) {
    const listItem = document.createElement('li');
    listItem.textContent = greeting;
    list.appendChild(listItem);
  }
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

const jsSolution = `const list = document.querySelector('.output ul');
list.innerHTML = '';
const greetings = ['Happy Birthday!',
 'Merry Christmas my love',
 'A happy Christmas to all the family',
 'You\\\'re all I want for Christmas',
 'Get well soon'];

for (let greeting of greetings) {
 // Your conditional test needs to go inside the parentheses
 // in the line below, replacing what's currently there
 if (greeting.includes('Christmas')) {
 const listItem = document.createElement('li');
 listItem.textContent = greeting;
 list.appendChild(listItem);
 }
}`;

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  let caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = function () {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

### 대/소문자 맞게 수정하기

이 예제에는 영국 도시의 이름들을 모아놨습니다만 대/소문자가 잘못되어 있습니다. 우리는 이 문자들을 첫 번째 문자를 제외하고 모두 소문자로 변경해야 합니다. 이것은 다음과 같은 방식으로 할 수 있습니다:

1. `input` 변수에 담긴 문자열 전체를 소문자로 변환한 후 새로운 변수에 저장하세요.
2. 새로운 변수에 저장된 문자열의 첫 문자를 다른 변수에 저장하세요
3. Using this latest variable as a substring, replace the first letter of the lowercase string with the first letter of the lowercase string changed to upper case. Store the result of this replace procedure in another new variable.
4. Change the value of the `result` variable to equal to the final result, not the `input`.

**참고:** A hint — the parameters of the string methods don't have to be string literals; they can also be variables, or even variables with a method being invoked on them.
```

<h2>Live output</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>

<textarea id="code" class="playable-code" style="height: 250px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
const cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];

for (let city of cities) {
  // write your code just below here

  const result = city;
  const listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

const jsSolution = `const list = document.querySelector('.output ul');
list.innerHTML = '';
const cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];

for (let city of cities) {
 // write your code just below here
 const lower = city.toLowerCase();
 const firstLetter = lower.slice(0,1);
 const capitalized = lower.replace(firstLetter,firstLetter.toUpperCase());
 const result = capitalized;
 const listItem = document.createElement('li');
 listItem.textContent = result;
 list.appendChild(listItem);
}`;

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  let caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = function () {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

### Making new strings from old parts

In this last exercise the array contains a bunch of strings containing information about train stations in the North of England. The strings are data items that contain the three letter station code, followed by some machine-readable data, followed by a semi-colon, followed by the human-readable station name. For example:
```

MAN675847583748sjt567654;Manchester Piccadilly
```

We want to extract the station code and name, and put them together in a string with the following structure:
```

MAN: Manchester Piccadilly
```

We'd recommend doing it like this:

1. Extract the three-letter station code and store it in a new variable.
2. Find the character index number of the semi-colon.
3. Extract the human-readable station name using the semi-colon character index number as a reference point, and store it in a new variable.
4. Concatenate the two new variables and a string literal to make the final string.
5. Change the value of the `result` variable to equal to the final string, not the `input`.
```

<h2>Live output</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>

<textarea id="code" class="playable-code" style="height: 285px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
const stations = ['MAN675847583748sjt567654;Manchester Piccadilly',
                  'GNF576746573fhdg4737dh4;Greenfield',
                  'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street',
                  'SYB4f65hf75f736463;Stalybridge',
                  'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];

for (let station of stations) {
  // write your code just below here

  const result = station;
  const listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

const jsSolution = `const list = document.querySelector('.output ul');
list.innerHTML = '';
const stations = ['MAN675847583748sjt567654;Manchester Piccadilly',
 'GNF576746573fhdg4737dh4;Greenfield',
 'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street',
 'SYB4f65hf75f736463;Stalybridge',
 'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];

for (let station of stations) {
 // write your code just below here
 const code = station.slice(0,3);
 const semiColon = station.indexOf(';');
 const name = station.slice(semiColon + 1);
 const result = \`\${code}: \${name}\`;
 const listItem = document.createElement('li');
 listItem.textContent = result;
 list.appendChild(listItem);
}`;

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  let caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = function () {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

결론
--

JavaScript에서 문장과 단어들을 다룰 수 있는 프로그래밍 능력이 매우 중요하다. 웹사이트는 사람들과 소통하는 공간이기 때문이다. 이 문서는 문자열을 다룰 수 있는 기초적인 내용에 대해 다루었다. 이 내용은 앞으로 배우게 될 심화 과정에 도움이 될 것이다. 다음으로 배열에 대해 알아보겠다.

* 이전
* Overview: First steps
* 다음


뭐가 잘못됐을까요? JavaScript 문제 해결 - Web 개발 학습하기

뭐가 잘못됐을까요? JavaScript 문제 해결
===========================

* 이전
* Overview: First steps
* 다음

앞선 글의 "숫자 알아맞히기" 게임을 따라 만들다가 프로그램이 동작하지 않았나요? 두려워 마세요. 이 글에서는 JavaScript 프로그램에서 그런 문제를 찾아 수정하는 방법에 대한 팁을 제공하여 여러분이 머리카락을 쥐어뜯지 않도록 지켜드리겠습니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: | 기본적인 컴퓨터 이해 능력, HTML과 CSS 기초, JavaScript 기본 개념 |
| 목표: | 스스로의 코드에서 문제를 수정할 수 있는 능력과 자신감 얻기 |

오류의 종류
------

일반적으로, 여러분의 코드에서 뭔가 잘못됐다면 마주하게 될 오류에는 크게 두 종류가 있습니다.

* **구문 오류**: 코드에 잘못된 철자가 있으면 발생하는 오류로, 프로그램이 아예 구동하지 못하거나 중간에 멈춰버리는 현상을 일으키며, 모종의 오류 메시지도 나타납니다. 올바른 도구와, 메시지의 뜻만 파악하고 있으면 그럭저럭 고칠 만합니다!
* **논리 오류**: 구문은 올바르지만 의도한 동작과 실제 코드에 차이가 있는 경우입니다. 따라서 프로그램은 성공적으로 돌아가지만 잘못된 결과를 낳습니다. 보통 오류를 직접 가리키는 메시지가 없기 때문에 구문 오류보다 고치기도 힘든 편입니다.

더 자세히 들여다보면 다른 차별화 요소가 몇 가지 더 있습니다. 하지만 이렇게만 나눠도 커리어의 초반에는 부족하지 않을 겁니다. 앞으로 이 두 가지 유형을 모두 살펴볼 것입니다.

오류를 포함한 예제
----------

시작하기 앞서서 숫자 맞히기 게임으로 돌아갑시다. 하지만 여기서는 의도적으로 오류를 추가한 버전을 사용할 것입니다. GitHub로 이동하여 number-game-errors.html 파일의 로컬 복사본을 만듭니다.(실행 결과 미리보기).

1. 시작하려면 즐겨 사용하는 텍스트 편집기와 브라우저에서 로컬 사본을 엽니다.
2. 게임 플레이를 시도해 보세요. "Submit guess" 버튼을 누르면 작동하지 않는 것을 알 수 있습니다.

**참고:** 직접 작성한 게임 예제가 동작하지 않는 경우에도 우선 예제 파일로 진행하는 걸 추천합니다. 이 글에 적힌 기법을 먼저 알아본 다음에 여러분의 코드로 돌아가서 활용하시면 됩니다.

이 시점에서 개발자 콘솔을 참조하여 구문 오류가 발생하는지 확인한 다음 오류를 수정해 보겠습니다. 아래에서 방법을 알아보도록 하겠습니다.

구문 오류 수정하기
----------

앞선 과정에서 JavaScript 개발자 도구 콘솔 (en-US)에 간단한 JavaScript 명령을 입력해 보았습니다. 콘솔을 여는 방법이 기억나지 않으면 이전 링크를 따라 알아보시면 됩니다. 하지만 콘솔은 단순히 명령을 입력하는 기능보다 유용한데, 브라우저의 JavaScript 엔진이 읽은 JavaScript 안에 구문 오류가 존재하면 콘솔에 그 오류가 기록되기 때문입니다. 이제 오류를 잡아 볼 것입니다.

1. `number-game-errors.html`을 연 탭으로 이동해서 JavaScript 콘솔을 엽니다. 스크린샷과 비슷한 내용의 오류 메시지를 볼 수 있어야 합니다.

 ![Firefox의 "숫자 알아맞히기 게임" 데모 페이지입니다. JavaScript 콘솔에 한 가지 오류가 표시됩니다. "X TypeError: guessSubmit.addeventListener is not a function 자세히 알아보기".](/ko/docs/Learn/JavaScript/First_steps/What_went_wrong/not-a-function.png)
2. 오류 메시지의 첫 줄은 다음과 같습니다.
```

Uncaught TypeError: guessSubmit.addeventListener is not a function
number-game-errors.html:86:15
```

    * 첫 번째 부분인 `Uncaught TypeError: guessSubmit.addeventListener is not a function`에서 무엇이 잘못되었는지를 알 수 있습니다.
    * 두 번째 부분인 `number-game-errors.html:86:15`는 코드에서 오류가 발생한 위치를 알려줍니다. "number-game-errors.html" 파일의 86번째 줄, 15번째 문자입니다.
3. 코드 편집기에서 86번째 줄을 보면 다음과 같은 줄을 찾을 수 있습니다.

**경고:** 오류가 86번째 줄에 없을 수 있습니다. 로컬 컴퓨터에서 라이브 서버를 실행하는 확장 기능이 있는 코드 편집기를 사용하는 경우 추가 코드가 삽입됩니다. 이 때문에 개발자 도구는 86번째 줄이 아닌 다른 줄에서 오류가 발생한 것으로 표시합니다.

```js
guessSubmit.addeventListener("click", checkGuess);
```

4. 오류 메시지 "guessSubmit.addeventListener is not a function"은 우리가 호출한 함수를 JavaScript 인터프리터가 인식하지 못했다는 뜻입니다. 보통 이 오류는 철자를 잘못 적은 경우 발생합니다. 구문의 올바른 철자가 확실하지 않을 땐 MDN에서 기능 참고서를 살펴보는 게 도움이 됩니다. 현재 가장 좋은 방법은 자주 사용하는 검색 엔진에서 "mdn '기능 이름'"으로 검색하는 것입니다. 이 상황에서 시간을 절약하는 방법은 `addEventListener()`입니다.
5. `addEventListener()` 페이지를 보면 함수 이름의 철자가 잘못되어 오류가 발생한 것으로 보입니다. JavaScript는 대소문자를 구분하므로 철자는 물론 대소문자도 잘못 적으면 오류가 발생합니다. `addeventListener`를 `addEventListener`로 수정하면 이 문제가 해결됩니다. 지금 변경해 보세요.

**참고:** MDN의 TypeError: "x" is not a function (en-US) 참고서에서 오류에 대한 자세한 설명을 확인하세요.

### 구문 오류 2회차

1. 페이지를 저장하고 새로고침하면 오류가 사라진 것을 확인할 수 있습니다.
2. 이제 숫자를 입력하고 Submit guess 버튼을 누르면 또 다른 오류가 표시됩니다.

 ![동일한 "숫자 알아맞히기 게임" 데모의 스크린샷입니다. 이번에는 콘솔에 다른 오류가 표시되며 "X TypeError: lowOrHi is null"라는 메시지가 표시됩니다.](/ko/docs/Learn/JavaScript/First_steps/What_went_wrong/variable-is-null.png)
3. 이번 오류는 다음과 같습니다.
```

Uncaught TypeError: can't access property
"textContent", lowOrHi is null
```

사용 중인 브라우저에 따라 여기에 다른 메시지가 표시될 수 있습니다. 위의 메시지는 Firefox에 표시되는 메시지이지만, 예를 들어 Chrome에는 다음과 같은 메시지가 표시됩니다.
```

Uncaught TypeError: Cannot set properties of null
(setting 'textContent')
```

동일한 오류이지만 브라우저마다 다른 방식으로 설명합니다.

**참고:** 이 오류는 함수 `checkGuess() {}` 블록에서 발생했기 때문에 페이지가 로드되자마자 나타나지 않았습니다. 나중에 함수 문서에서 자세히 알아보겠지만 함수 내부 코드는 함수 외부 코드와 별도의 범위에서 실행됩니다. 이 경우 코드가 시행되지 않았고 86번째 줄에서 `checkGuess()` 함수가 실행될 때까지 오류가 발생하지 않았습니다.
4. 오류에 표시된 줄 번호는 80입니다. 80번째 줄을 살펴보면 다음 코드를 볼 수 있습니다.

```js
lowOrHi.textContent = "Last guess was too high!";
```

5. 이 줄은 `lowOrHi` 변수의 `textContent` 속성을 텍스트 문자열로 설정하려고 시도하고 있지만, `lowOrHi`에 예상되는 내용이 포함되어 있지 않기 때문에 작동하지 않습니다. 왜 그런지 코드에서 `lowOrHi`의 다른 인스턴스를 검색해 보겠습니다. 가장 먼저 찾을 수 있는 인스턴스는 49번째 줄에 있습니다.

```js
const lowOrHi = document.querySelector("lowOrHi");
```

6. 여기서는 문서 HTML의 요소에 대한 참조를 변수에 저장하려고 시도하고 있습니다. 이 줄을 실행한 후 변수에 무엇이 포함되는지 살펴봅시다. 50번째 줄에 다음 코드를 추가합니다.

```js
console.log(lowOrHi);
```

이 코드는 49번째 줄에서 설정하려고 시도한 후 콘솔에 `lowOrHi`값을 출력합니다. 자세한 내용은`console.log()`를 참조하세요.
7. 저장하고 새로고침하면 `console.log()`가 콘솔에 기록한 결과를 볼 수 있습니다.

 ![동일한 데모의 스크린샷입니다. 콘솔에 단순히 "null"을 읽는 하나의 로그 문이 표시됩니다.](/ko/docs/Learn/JavaScript/First_steps/What_went_wrong/console-log-output.png)
 이 시점에서 `lowOrHi`의 값은 `null`이며, 이는 Firefox 오류 메시지인 `lowOrHi is null`과 일치합니다. 따라서 49번 줄에 확실한 문제가 있습니다. null 값은 "아무것도 없음" 또는 "값 없음"을 의미합니다. 따라서 요소에 `lowOrHi`를 설정하는 코드 부분이 잘못되었습니다.
8. 어떤 문제일지 생각해 봅시다. 48번째 줄은 `document.querySelector()` 메서드를 사용해, CSS 선택자로 선택한 요소에 대한 참조를 가져옵니다. 우리 파일의 더 위쪽에서 우리가 찾으려는 문단을 볼 수 있습니다.

```html
<p class="lowOrHi"></p>
```

9. 따라서 우리가 사용했어야 하는 선택자는 마침표(`.`)로 시작하는 클래스 선택자였는데, 49번째 줄의 `querySelector()` 메서드에 제공한 선택자에는 마침표가 없습니다. 이것이 문제일 수 있습니다! `lowOrHi`를 `.lowOrHi`로 바꿔보세요.
10. 저장 후 다시 새로고침해보면 `console.log()` 명령문이 우리가 원하는 `<p>` 요소를 반환하는 모습을 볼 수 있습니다. 또 다른 오류를 고쳤습니다! 이제 `console.log()`를 삭제하거나 나중에 참고할 수 있도록 보관할 수 있습니다.

**참고:** MDN의 TypeError: "x" is (not) "y" 참고서에서 오류에 대한 자세한 설명을 확인하세요.

### 구문 오류 3회차

1. 이제는 게임을 성공적으로 플레이할 수 있습니다. 올바른 숫자를 맞히거나 추측이 부족하여 게임이 종료될 때까지 게임이 정상적으로 진행됩니다.
2. 게임이 정상적으로 끝나야 하는 순간, 다시 실패해 버립니다. 맨 처음 봤던 것과 같은 종류의 오류, "TypeError: resetButton.addeventListener is not a function"가 출력됩니다! 하지만 이번에는 94번째 줄에서 발생한 것으로 표시됩니다.
3. 94번째 줄을 확인하면 똑같은 실수가 있었다는 걸 알 수 있습니다. `addeventListener`를 `addEventListener`로 바꾸기만 하면 됩니다. 바로 수정해 보세요.
논리 오류
-----

여기까지 왔으면 게임이 정상적으로 플레이되지만, 몇 번 플레이하고 나면 게임이 항상 1을 "무작위" 숫자로 선택한다는 사실을 알게 될 것입니다. 확실히 저희가 원하는 게임 방식이 아닙니다!

게임 논리 어딘가에 확실히 문제가 있습니다. 오류를 반환하진 않지만, 정상적인 동작을 하지 못하기 때문입니다.

1. `randomNumber`변수와 난수가 처음 설정된 줄을 검색합니다. 게임 시작 시 추측하고자 하는 난수를 저장하는 인스턴스는 45번째 줄 근처에 있어야 합니다.

```js
let randomNumber = Math.floor(Math.random()) + 1;
```

2. 후속 라운드를 시작할 때 새로운 무작위 수를 생성하는 코드는 113번째 줄 근처입니다.

```js
randomNumber = Math.floor(Math.random()) + 1;
```

3. 이 줄이 실제로 문제인지 확인하려면 이전에 사용한 `console.log()`를 다시 사용하여 위의 두 줄 바로 아래에 다음 줄을 삽입해 보겠습니다.

```js
console.log(randomNumber);
```

4. 저장하고 새로고침한 다음 게임을 몇 판 플레이하면 콘솔에 기록되는 각 지점에서 `randomNumber`가 1과 같다는 것을 확인할 수 있습니다.
### 논리 파고들기

이 문제를 해결하기 위해 이 줄이 어떻게 작동하는지 살펴봅시다. 먼저 `Math.random()`을 호출하여 0과 1 사이의 0.5675493843와 같은 임의의 수를 생성합니다.

```js
Math.random();
```

다음으로, `Math.random()`을 호출한 결과를 `Math.floor()`에 전달하고, 전달된 숫자를 가장 가까운 정수로 반올림합니다. 그런 다음 그 결과에 1을 더합니다.

```js
Math.floor(Math.random()) + 1;
```

0과 1 사이의 무작위 수를 버림하면 결과는 항상 0이니, 여기에 1을 더한 결과는 항상 1입니다. 무작위 수를 버림하기 전에 먼저 100을 곱해야 원하는 숫자를 얻을 수 있을 겁니다. 다음 코드는 0부터 99까지의 무작위 수를 생성합니다.

```js
Math.floor(Math.random() \* 100);
```

따라서 여기에 1을 더하면 1과 100 사이의 수를 얻을 수 있습니다.

```js
Math.floor(Math.random() \* 100) + 1;
```

이렇게 두 줄을 모두 고친 다음 저장하고 새로고침하면 이제 게임이 의도한 대로 재생됩니다!

다른 일반적인 오류
----------

코드에서 발견할 수 있는 다른 일반적인 오류도 있습니다. 이 섹션에서는 이러한 오류를 중점적으로 설명합니다.

### SyntaxError: missing ; before statement

이 오류는 일반적으로 코드 줄 중 하나 끝에 세미콜론을 놓쳤음을 의미하지만 때로는 더 복잡할 수 있습니다. 예를 들어 `checkGuess()`함수 내에서 이 줄을 변경하는 경우입니다

```js
const userGuess = Number(guessField.value);
```

이렇게 바꾸면,

```js
const userGuess === Number(guessField.value);
```

이 오류는 사용자가 다른 작업을 수행하려고 한다고 생각하기 때문에 발생합니다. 변수를 어떤 값과 동일하게 만드는 할당 연산자(`=`)와, 어떤 값이 다른 값과 같은지 판별해서 `true`/`false`를 반환하는 일치 연산자(`===`)를 혼동하지 않도록 주의해야 합니다.

**참고:** MDN의 SyntaxError: missing ; before statement 참고서에서 오류에 대한 자세한 설명을 확인하세요.

### 어떤 값을 입력해도 항상 이겼다고 함

이 현상 또한 할당 연산자와 일치 연산자를 혼동해서 발생할 수 있습니다. 예를 들어 `checkGuess()`내에서 이 줄을 변경한다고 가정해 보겠습니다.

```js
if (userGuess === randomNumber) {
```

이렇게 바꾸면,

```js
if (userGuess = randomNumber) {
```

조건이 항상 `true`가 돼서 언제나 게임을 이겼다고 판별합니다. 조심하세요!

### SyntaxError: missing ) after argument list

이 오류는 단순한 편입니다. 일반적으로 함수/메서드 호출이 끝날 때 닫는 괄호를 놓쳤음을 의미합니다.

**참고:** MDN의 SyntaxError: missing ) after argument list 참고서에서 오류에 대한 자세한 설명을 확인하세요.

### SyntaxError: missing : after property id

이 오류는 일반적으로 잘못 형성된 JavaScript 객체와 관련이 있지만, 이 경우에는 다음과 같이 변경하여 문제를 해결했습니다.

```js
function checkGuess() {
```

이렇게 바꾸기만 해도 발생합니다.

```js
function checkGuess( {
```

브라우저가 함수의 본문을 본문이 아니라 매개변수로 잘못 인식하기 때문입니다. 괄호를 주의하세요!

### SyntaxError: missing } after function body

쉬운 오류입니다. 일반적으로 함수나 조건문 구조에 사용한 중괄호를 누락할 때 발생합니다. 예컨대 `checkGuess()` 함수 끝부분의 닫는 중괄호 중 하나를 삭제하여 발생했습니다.

### SyntaxError: expected expression, got '*string*' 또는 SyntaxError: unterminated string literal

이 두 오류는 대개 문자열 값의 열거나 닫는 따옴표가 빠지면 발생합니다. 첫 번째 오류의 *string* 부분에는 브라우저가 따옴표 대신 마주친, 예상치 못한 문자 또는 문자열이 들어갑니다. 두 번째 오류는 따옴표로 닫지 않은 문자열이 있다는 뜻입니다.

이러한 모든 오류에 대해 연습에서 살펴본 예제를 어떻게 해결했는지 생각해 봅시다. 오류가 발생하면 주어진 줄 번호를 보고 해당하는 줄로 이동하여 무엇이 잘못되었는지 찾아봅니다. 오류가 반드시 해당하는 줄에 있는 것은 아니며, 위에서 인용한 것과 똑같은 문제로 인해 오류가 발생하지 않을 수도 있다는 점을 명심해야 합니다!

**참고:** MDN의 SyntaxError: Unexpected token과 SyntaxError: unterminated string literal (en-US) 참고서에서 두 오류에 대한 자세한 설명을 확인하세요.

요약
--

지금까지 간단한 JavaScript 프로그램에서 오류를 파악하는 기본 사항을 알아봤습니다. 코드에서 무엇이 잘못되었는지 알아내는 것이 항상 그렇게 간단하지는 않겠지만, 적어도 이렇게 하면 몇 시간의 잠을 절약할 수 있고 특히 학습 여정의 초기 단계에서 일이 제대로 풀리지 않을 때 조금 더 빠르게 진행할 수 있습니다.

같이 보기
-----

* 이 글에는 없지만, JavaScript에는 더 많은 종류의 오류들이 있습니다. JavaScript 오류 참고서 (en-US)에서 오류 각각의 뜻을 자세히 설명하고 있습니다.
* 이 글을 마친 후에도 어떻게 수정해야 할지 모르는 오류가 있으면 도움을 받을 수 있습니다! 커뮤니케이션 채널에서 도움을 요청하세요. 오류가 무엇인지 알려주시면 최선을 다해 도와드리겠습니다. 여러분의 코드를 문의에 포함하면 더 좋습니다.
* 이전
* Overview: First steps
* 다음


얼마나 이해했는지 확인해보자: JSON - Web 개발 학습하기

얼마나 이해했는지 확인해보자: JSON
=====================

이 테스트의 목표는 JSON으로 작업하기를 잘 이해했는지 평가해보는 것입니다.

**참고:** 아래에 있는 편집기를 사용해보세요. 테스트 중 막히면, 도움을 요청할 수 있습니다. 페이지 하단의 "평가 또는 추가 도움말" 을 참고하세요.

**참고:** 작성한 코드에 오류가 발생하는 경우, 결과창에서 정답을 알아낼 수 있도록 돕기 위해 결과값을 보여줄 것입니다. (또는 브라우저의 JavaScript 콘솔로도 확인 가능합니다.)

JSON 1
------

이번에 수행할 유일한 과제는 JSON 데이터에 접근하고 사용해보는 것입니다. 어미 고양이들과 그들의 새끼 고양이에 관한 JSON 데이터는 sample.json에서 확인할 수 있습니다. 이 JSON은 텍스트 문자열로 호출되며, 제시된 프로미스 체인(JSON 데이터를 가져오는 것으로 시작)의 조건을 만족시킬 때 `displayCatInfo()` 함수의 `catString` 파라미터로 사용될 수 있습니다.

* `motherInfo` 의 변수 : 쉼표로 나뉘어진 세 어미 고양이의 이름
* `kittenInfo` 의 변수 : 새끼 고양이의 전체 마릿수, 수컷 수, 암컷 수

변수들의 값은 문단 내부의 화면에 출력됩니다.

몇 가지 힌트/질문:

* JSON 데이터는 `displayCatInfo()` 함수 내부에 텍스트로 제공됩니다. 데이터를 얻기 위해서는 JSON 구문을 분석해야 합니다.
* 아마도 outer loop를 사용해 반복하여 고양이와 그들의 이름을 `motherInfo` 의 변수에 추가하고, inner loop 를 사용해 모든 새끼 고양이들, 총/수컷/암컷 마릿수를 `kittenInfo` 의 변수에 추가하려고 할 것입니다.
* 마지막에 오는 어미 고양이 이름 전에는 "and"가 와야하며, 그 뒤에 작업이 완전히 멈춰야 합니다. JSON에 고양이가 무수히 많이 오더라도 작업이 잘 이뤄질꺼라고 확신할 수 있으십니까?
* `para1.textContent = motherInfo;` 과 `para2.textContent = kittenInfo;` 는 왜 `displayCatInfo()` 함수의 내부에 있어야 하며, 스크립트 마지막에 오지 않는 것일까요? 이것은 비동기와 관련이 있습니다.

완료된 예제를 다시 만드려면, 아래 코드를 변경해보세요:

**경고:** 이번 테스트를 치를 때 코드를 위에 있는 편집기에 작성하세요. 로컬환경이나 CodePen 또는 JSFiddle 에서는 이 예제가 작동하지 않을 수 있는데, *cross-origin* 라고 불리는 보안정책 때문일 것입니다. 이는 현재 환경과 다른 출처의 리소스의 요청을 제한하는 정책입니다. 위 편집기는 JavaScript 코드와 요청된 JSON 파일을 모두 동일한 출처에서 요청하고 있습니다. (Github 저장소의 <iframe>에 내장되어 있습니다.) 하지만 예를 들어 CodePen에 기입하는 코드는 JSON을 호출하는 출처와는 다르기 때문에 오류가 발생할 수 있습니다. CORS를 사용하여 이런 제약사항을 해결할 수 있지만, 이번 장에서 가르치는 범위에는 해당하지 않습니다.

평가 또는 추가 도움말
------------

위에 있는 편집기를 이용해 예제를 연습해볼 수 있습니다.

만일 작업을 평가하고나, 막혀서 도움을 요청하고 싶다면:

1. MDN 학습 토론 카테고리에 평가 또는 도움을 요청하는 글을 작성하세요. 글에는 다음 사항이 포함되어야 합니다 :
    * "Assessment wanted for JSON skill test (JSON 기술 테스트에 대한 평가를 요청드립니다)" 와 같은 제목을 사용하세요.
    * 이미 시도해보았거나, 원하는 것(예를 들어, 막혀서 도움이 필요하다거나 평가가 필요하다는 것과 같은)에 대한 세부사항을 기입해주세요.
    * 작성한 코드를 삽입하세요.
    * 실제 작업 또는 평가 페이지에 대한 링크를 삽입하세요. 그래야 도움받고자 하는 질문을 확인할 수 있습니다.


Classes in JavaScript - Web 개발 학습하기

Classes in JavaScript
=====================

* 이전
* Overview: Objects
* 다음

OOJS에 대한 개념을 설명했으니, 이 글에서는 부모 클래스에서 자식 클래스를 상속하는 방법을 알아봅니다. 덤으로 OOJS를 구현하는데 몇 가지 참고사항도 있습니다.

|  |  |
| --- | --- |
| 선수조건: |
 컴퓨터 기본지식, HTML과 CSS에 대한 기본적인 이해,JavaScript에 어느
 정도 익숙할 것 (see
 First steps and
 Building blocks). OOJS 기초 지식 (see
 Introduction to objects).
  |
| 학습목표: | Javascript에서 상속을 구현하는 법을 이해합니다. |

프로토타입 상속
--------

지금까지 몇 가지 상속을 살펴보았습니다 — 프로토타입 체인이 어떻게 동작하는지, 체인을 통해 멤버들을 탐색하는 것도 보았죠. 하지만 이는 대부분 브라우저가 알아서 처리하는 로직이었습니다. 그러면 우리가 직접 객체를 생성하고 상속하려면 어떻게 해야 할까요?

실질적인 예제를 통해 알아보도록 합시다.

시작하기
----

먼저 oojs-class-inheritance-start.html를 다운 받으시고 (running live 페이지도 보시구요). 파일 내에서 이전 예제에서 계속 봐 왔던 `Person()` 생성자를 보실 수 있습니다 — 생성자에 속성 몇 개를 정의했기에 조금 다릅니다:

```js
function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
}
```

메소드는 *전부* 아래처럼 prototype에 정의되어 있습니다:

```js
Person.prototype.greeting = function () {
  alert("Hi! I'm " + this.name.first + ".");
};
```

**참고:** 소스 코드에는 `bio()와` `farewell()`메소드가 정의되어 있습니다. 잠시 후에 다른 생성자로 어떻게 상속하는지 알아보도록 합시다.

객체 지향에 대해 처음 정의할 때 언급했었던 `Teacher` 클래스를 만들어 봅시다. `Person`을 상속받고 아래 몇 가지를 추가해서요:

1. `subject` 속성 — 교사가 가르치는 과목을 나타냅니다.
2. 기존의 `greeting()` 보다 조금 더 공손한 인사를 하는 메소드 — 교사가 학생들에게 건넬 만한 표현으로 하죠.
Teacher() 생성자 함수 정의
-------------------

제일 처음 단계에서는 `Teacher()` 생성자를 만들어야 합니다 — 기존 코드 밑에 아래 코드를 추가하세요:

```js
function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}
```

Person() 생성자와 여러모로 비슷해 보이지만 여지껏 보지 못했던 한가지 차이점이 있습니다 — `call()` 함수죠. call() 함수의 첫번째 매개변수는 다른 곳에서 정의된 함수를 현재 컨텍스트에서 실행할 수 있도록 합니다. 실행하고자 하는 함수의 첫 번째 매개변수로 this를 전달하고 나머지는 실제 함수 실행에 필요한 인자들을 전달하면 됩니다.

Teacher()의 생성자는 `Person()`을 상속받았으므로 같은 매개변수들이 필요합니다. 따라서 동일한 매개변수들을 `call()`의 인자로 전달하여 실행합니다.

마지막 줄에서는 새 속성인 `subject`를 정의하여 Person이 아닌 Teacher만이 갖는 속성을 만들어 줍니다.

참고로 아래와 같이 할 수도 있습니다:

```js
function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}
```

다만 이는 `Person()`을 상속받은게 아니라 단지 동일한 인자를 정의했을 뿐이죠. 이건 원하는 방법이 아닐 뿐더러 코드의 길이만 더 늘어났습니다.

### 매개변수가 없는 생성자 상속하기

상속하려는 생성자가 속성을 매개변수로 받지 않는다면 `call()`의 매개변수에도 아무것도 전달할 필요가 없습니다. 아래처럼 간단한 생성자가 있다면:

```js
function Brick() {
  this.width = 10;
  this.height = 20;
}
```

`width`와 `height` 속성을 상속받기 위해 아래처럼만 하면 됩니다(물론 이후 설명할 방법을 써도 되구요):

```js
function BlueGlassBrick() {
  Brick.call(this);

  this.opacity = 0.5;
  this.color = "blue";
}
```

`call()` 함수에 this만 넘긴 것을 보세요. — `Brick()` 생성자에서 매개변수를 통해 초기화 하는 속성들이 없으므로 `call()`에도 넘길 필요가 없습니다.

Teacher()의 프로토타입과 생성자 참조 설정하기
-----------------------------

다 좋은데 문제가 있습니다. 방금 정의한 새 생성자에는 생성자 함수 자신에 대한 참조만 가지고 있는 프로토타입 속성이 할당되어 있습니다. 정작 상속 받은 Person() 생성자의 prototype 속성은 없죠. Javascript 콘솔에서 `Object.getOwnPropertyNames(Teacher.prototype)`을 쳐서 확인해 보세요. 다음엔 `Teacher`를 `Person`으로 바꿔서 확인해 보세요. Teacher()생성자는 Person()의 메소드를 상속받지 못하였습니다. `Person.prototype.greeting`과 `Teacher.prototype.greeting` 구문을 실행하여 비교해 보세요. `Teacher()`가 메소드도 상속 받으려면 어떻게 해야 할까요?

1. 기존 코드에 아래 코드를 추가하세요:

```js
Teacher.prototype = Object.create(Person.prototype);
```

구원 투수 `create()`의 등판입니다. 새 객체를 생성하여 `Teacher.prototype`으로 할당했죠. 새 객체는 `Person.prototype` 객체를 자신의 프로토타입으로 가지고 있으므로 `Person.prototype`에 정의된 모든 메소드를 사용할 수 있습니다.
2. 넘어가기 전에 한가지 더 해야 합니다. 마지막 줄을 추가하고 나면 `Teacher.prototype`의 `constructor` 속성이 Person()으로 되어 있습니다. `Teacher.prototype`에 `Person.prototype`을 상속받은 객체를 할당했기 때문이죠. 코드를 저장한 뒤 브라우저로 불러와서 Teacher.prototype.constructor 구문의 반환 값을 확인해 보세요.
3. 문제의 소지가 있으므로 고쳐야 됩니다. 소스에 아래 코드를 추가하세요:

```js
Teacher.prototype.constructor = Teacher;
```

4. 저장하고 다시 브라우저에서 불러오면 의도한대로 `Teacher.prototype.constructor`가 `Teacher()`를 반환합니다. 게다가 `Person()`도 상속받았죠!
Teacher()에 새 greeting() 함수 부여하기
-------------------------------

`Teacher()`에 새로운 `greeting()` 함수를 정의하여 코드를 완성합시다.

가장 간단한 방법은 Teacher()의 프로토타입에 정의합니다. — 아래 코드를 추가하세요:

```js
Teacher.prototype.greeting = function () {
  var prefix;

  if (
    this.gender === "male" ||
    this.gender === "Male" ||
    this.gender === "m" ||
    this.gender === "M"
  ) {
    prefix = "Mr.";
  } else if (
    this.gender === "female" ||
    this.gender === "Female" ||
    this.gender === "f" ||
    this.gender === "F"
  ) {
    prefix = "Mrs.";
  } else {
    prefix = "Mx.";
  }

  alert(
    "Hello. My name is " +
      prefix +
      " " +
      this.name.last +
      ", and I teach " +
      this.subject +
      ".",
  );
};
```

조건문을 이용해서 성별에 따라 적절한 호칭이 붙은 교사의 인삿말을 alert 창으로 띄웁니다.

예제 사용해 보기
---------

소스를 환성했으니 아래 코드를 통해 새 `Teacher()` 인스턴스를 생성해 봅시다(아니면 인자를 원하는 값으로 변경하시거나요):

```js
var teacher1 = new Teacher(
  "Dave",
  "Griffiths",
  31,
  "male",
  ["football", "cookery"],
  "mathematics",
);
```

저장한 코드를 다시 불러와서 아래처럼 `teacher1`의 속성과 메소드를 확인해 봅시다:

```js
teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();
```

아주 잘 실행될 겁니다. 1, 2, 3, 6 줄은 Person() 생성자(클래스)에서 상속 받은 멤버에 접근합니다. 4번째 줄은 Teacher() 생성자(클래스)만 가지고 있는 멤버에 접근합니다. 5번째 줄은 Person()에서 상속 받은 멤버도 있지만 Teacher()가 이미 자신만의 새 메소드를 정의했으므로 Teacher()의 메소드에 접근합니다.

**참고:** 코드가 잘 동작하지 않으면 완성된 버전을 확인해 보세요. (실행 페이지도 보시구요).

이 테크닉이 Javascript에서 상속 받는 클래스를 만드는 유일한 방법은 아니지만 잘 동작하며 상속을 구현하는 방법을 잘 설명하고 있습니다.

조금 더 명확한 방식으로 Javascript에서 상속을 구현하는 새 ECMAScript 기능도 관심 가질만한 주제입니다(Classes 참조). 아직까지 많은 브라우저에서 지원하지 못하고 있기 때문에 여기서 다를 주제는 아닙니다. 여러 문서에서 제시한 코드들은 IE9보다 더 오래된 구형 브라우저에서도 사용 가능하며 더 이전 버전을 지원하기 위한 방법들도 있습니다.

JavaScript 라이브러리를 쓰면 간단합니다 — 상속 기능을 사용하기 위한 보편적인 방법이죠. 예를들어 CoffeeScript는 `class`와 `extends`등의 기능을 제공합니다.

더 연습하기
------

OOP theory section, 에서는 개념적으로 Person을 상속받고 Teacher보다 덜 공손한 `greeting()` 메소드를 재정의한 `Student` 클래스를 정의했었습니다. 해당 절에서 `Student`의 인삿말이 어땠는지 확인해 보시고 `Person()`을 상속받는 `Student()` 생성자를 구현해 보세요. `greeting()` 함수도 재정의 해 보시구요.

**참고:** 코드가 잘 동작하지 않으면 완성된 버전 을 확인해 보세요.(실행 페이지도 보시구요).

객체 멤버 요약
--------

요약하면, 상속에 있어 고려해야 할 세 가지 유형의 속성/메소드가 있습니다:

1. 생성자 함수 내에서 인스턴스에 정의하는 유형. 직접 작성한 코드에서는 생성자 함수 내에 `this.x = x` 구문과 유사하게 정의되어 있으므로 발견하기 쉽습니다. 브라우저 내장 코드에서는 객체 인스턴스(보통 `new` 키워드를 통해 생성, ex) `var myInstance = new myConstructor()`)에서만 접근할 수 있는 멤버입니다.
2. 생성자에 직접 정의하는 유형, 생성자에서만 사용 가능합니다. 브라우저 내장 객체에서 흔히 사용하는 방식인데, 인스턴스가 아니라 생성자 함수에서 바로 호출되는 유형입니다. `Object.key()` 같은 함수들이죠.
3. 인스턴스와 자식 클래스에 상속하기 위해 생성자의 프로토타입에 정의하는 유형. 생성자의 프로토타이비 속성에 정의되는 모든 멤버를 의미합니다. ex) `myConstructor.prototype.x()`.

뭐가 뭔지 헷갈려도 걱정하지 마세요 — 배우는 중이니 차츰 익숙해질겁니다.

ECMAScript 2015 클래스
-------------------

ECMAScript 2015에서는 C++나 Java와 유사한 클래스 문법을 공개하여 클래스를 조금 더 쉽고 명확하게 재활용 할 수 있게 되었습니다. 이 절에서는 프로토타입 상속으로 작성한 Person과 Teacher 예제를 클래스 문법으로 변경하고 어떻게 동작하는지 설명하겠습니다.

**참고:** 대부분의 최신 브라우저에서 새로운 클래스 작성 방식을 지원합니다만 일부 구형 브라우저(Internet Explorer가 대표적)에서는 동작하지 않으므로 하위호환성을 위해 프로토타입 상속을 배워둘 필요가 있습니다.

Class-스타일로 재작성한 Person 예제를 보시죠:
```

class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  greeting() {
    console.log(`Hi! I'm ${this.name.first}`);
  };

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  };
}
```

class 구문은 새로운 클래스를 작성함을 의미합니다. Class 블록 내에서 모든 기능을 정의할 수 있습니다.

* `constructor()` 메소드는 `Person` 클래스의 생성자를 의미합니다.
* `greeting()` and `farewell()`는 멤버 메소드입니다. 클래스의 메소드는 생성자 다음에 아무 메소드나 추가할 수 있습니다. 여기서는 읽기 쉬우라고 string 결합이 아닌 template literals을 사용했습니다.

이제 위에서 했듯이 `new` 연산자로 객체 인스턴스를 생성할 수 있습니다:
```

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
han.greeting();
// Hi! I'm Han

let leia = new Person('Leia', 'Organa', 19, 'female' ['Government']);
leia.farewell();
// Leia has left the building. Bye for now
```

**참고:** 코드를 까보면 class 부분은 프로토타입 상속으로 변환이 됩니다. — 문법 설탕(syntactic sugar)의 일종인거죠. 하지만 읽기 쉽다는데 대부분 동의하실 겁니다.

### class 문법으로 상속

위에서 사람을 나타내는 클래스를 만들었습니다. Person 클래스는 일반적인 사람이 가질 만한 특성들을 나열하고 있죠; 이 절에서는 `Person`을 class 문법으로 상속받아 `Teacher` 클래스를 만들 예정입니다. 이 작업을 하위 클래스 생성이라 부릅니다.

하위 클래스를 만드려면 Javascript에서 extends 키워드를 통해 상속 받을 클래스를 명시합니다.
```

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    this.name = {
      first,
      last
    };

  this.age = age;
  this.gender = gender;
  this.interests = interests;
  // subject and grade are specific to Teacher
  this.subject = subject;
  this.grade = grade;
  }
}
```

`constructor()`에서 첫번쨰로 `super()` 연산자를 정의하면 코드를 조금 더 읽기 쉬워집니다. 이는 상위 클래스의 생성자를 호출하며 super()의 매개변수를 통해 상위 클래스의 멤버를 상속받을 수 있는 코드입니다.
```

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    super(first, last, age, gender, interests);

    // subject and grade are specific to Teacher
    this.subject = subject;
    this.grade = grade;
  }
}
```

`Teacher`의 인스턴스를 생성하면 의도한대로 이제 `Teacher`와 `Person` 양 쪽의 메소드와 속성을 사용할 수 있습니다.
```

let snape = new Teacher('Severus', 'Snape', 58, 'male', ['Potions'], 'Dark arts', 5);
snape.greeting(); // Hi! I'm Severus.
snape.farewell(); // Severus has left the building. Bye for now.
snape.age // 58
snape.subject; // Dark arts
```

Person을 수정하지 않고 Teacher를 생성한 것처럼 또 다른 하위클래스도 생성할 수 있습니다.

**참고:** GitHub에서 es2015-class-inheritance.html 예제를 참조하세요(실행 페이지).

Getters와 Setters
----------------

생성한 클래스 인스턴스의 속성 값을 변경하거나 최종 값을 예측할 수 없는 경우가 있을 겁니다. Teacher 예제를 보면 인스턴스를 생성하기 전에는 어떤 과목을 가르칠지 아직 모릅니다. 학기 도중에 가르치는 과목이 변경될 수도 있구요.

이런 상황에 getter/setter가 필요합니다.

Teacher 클래스에 getter/setter를 추가해 봅시다. 마지막에 작성했던 예제를 그대로 사용해보죠.

Getter와 setter는 쌍으로 동작합니다. Getter가 현재 값을 반환한다면 그에 대응하는 setter는 해당하는 값을 변경합니다.

수정된 `Teacher` 클래스는 아래와 같습니다:
```

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    super(first, last, age, gender, interests);
    // subject and grade are specific to Teacher
    this._subject = subject;
    this.grade = grade;
  }

  get subject() {
    return this._subject;
  }

  set subject(newSubject) {
    this._subject = newSubject;
  }
}
```

위 클래스를 보시면 `subject` 속성에 대해 getter와 setter가 생겼습니다. 멤버 변수에는 를 붙여 getter/setter 와 구분을 하였습니다. 이렇게 하지 않으면 get/set 을 호출할때마다 에러가 발생합니다:

* `snape` 객체의 `_subject` 속성 값을 보려면 `snape._subject`를 실행합니다.
* To show the current value of the `_subject` property of the `snape` object we can use `snape._subject`.
* `_subject`에 새 값을 할당하려면 `snape._subject="new value"`를 실행합니다.

두 기능이 실제로 어떻게 작동하는지 아래를 참조하세요:
```

// Check the default value
console.log(snape._subject) // Returns "Dark arts"

// Change the value
snape._subject="Balloon animals" // Sets subject to "Balloon animals"

// Check it again and see if it matches the new value
console.log(snape._subject) // Returns "Balloon animals"
```

**참고:** GitHub에서 es2015-getters-setters.html 예제를 참조하세요(실행 페이지).

JavaScript에서 언제 상속을 사용해야 할까?
----------------------------

이 마지막 문서를 읽고 나면 "뭐가 이리 어렵냐"고 생각하실지도 모르겠습니다. 어렵긴 합니다 프로토타입과 상속은 Javascript에서 가장 난해한 부분이거든요. 하지만 이 부분은 Javascript가 강력하고 유연한 언어로써 작용할 수 있는 원동력이기에 충분한 시간을 들여 배울 가치가 있습니다.

어찌보면 여러분은 항상 상속하고 있었습니다. Web API나 브라우저 내장 객체인 string, array 등의 메소드/속성을 사용하면서 암묵적으로 상속을 사용하고 있었던거죠.

처음 시작하거나 작은 프로젝트에서 직접 상속을 구현하는 코드를 작성하는 경우는 그리 많지 않습니다. 필요하지도 않는데 상속을 위한 코드를 구현하는 건 시간 낭비에 불과하죠. 하지만 코드량이 많아질수록 상속이 필요한 경우가 생깁니다. 동일한 기능을 가진 클래스가 많아졌음을 발견했다면 기능들을 한데 묶어 공유할 수 있도록 일반 객체를 만들고 특이 객체들에게 상속하는 방식이 훨씬 편하고 유용하다는 점을 알 수 있습니다.

**참고:** Javascript에서는 프로토타입을 통해 상속이 구현되어 있어 이 방식을 흔히 **위임**이라고 표현합니다. 특이 객체들이 일반 객체에게 일부 기능의 실행을 위임하는 것이죠.

상속을 구현할때 상속 레벨을 너무 깊게 하지 말고, 메소드와 속성들이 정확히 어디에 구현되어 있는지 항상 인지해야 합니다. 브라우저 내장 객체의 prototype 역시 일시적으로 수정이 가능하지만 정말로 필요한 경우를 제외하고는 건드리지 말아야 합니다. 너무 깊은 상속은 디버그 할 때 끝없는 혼돈과 고통만을 줄 겁니다.

궁극적으로 객체는 함수나 반복문과 같이 고유한 역할과 장점을 지닌 채 코드를 재사용하는 또 다른 방법입니다. 서로 연관된 변수와 함수들을 하나로 묶어 다룰 필요가 있을때 객체가 좋은 아이디어입니다. 한 곳에서 다른 곳으로 데이터 집합을 전달할 때에도 객체가 유용합니다. 두가지 모두 생성자나 상속 없이도 가능한 일입니다. 딱 하나의 인스턴스만 필요할 경우 객체를 선언하지 않고 객체 리터럴만으로도 충분합니다. 당연히 상속은 필요없구요.

요약
--

이 글에서는 여러분들이 반드시 알아야 할 OOJS 이론과 문법의 나머지 부분에 대해 다루고 있습니다. 이 시점에서 여러분은 javascript 객체와 OOP 기초, 프로토타입과 프로토타입 상속, 클래스(생성자)를 만들고 인스턴스를 생성하며 기능을 추가하고, 다른 클래스를 상속 받아 하위 클래스를 만드는 방법을 배웠습니다.

다음 글에서는 Javascript 객체로 데이터를 교환하는 방식인 Javascript Object Notation(JSON)에 대해 알아봅시다.

See also
--------

* ObjectPlayground.com — A really useful interactive learning site for learning about objects.
* Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
* You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.
* 이전
* Overview: Objects
* 다음


JSON으로 작업하기 - Web 개발 학습하기

JSON으로 작업하기
===========

* 이전
* Overview: Objects
* 다음  (en-US)

JavaScript Object Notation (JSON)은 Javascript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반의 표준 포맷입니다. 웹 어플리케이션에서 데이터를 전송할 때 일반적으로 사용합니다(서버에서 클라이언트로 데이터를 전송하여 표현하려거나 반대의 경우). 여기저기서 자주 보았을테니 여기선 JSON을 파싱, 데이터에 접근하고 JSON을 생성하는 등 Javascript로 JSON을 다루는 법에 대해 알아봅시다.

|  |  |
| --- | --- |
| 사전지식: |
 컴퓨터 기초, HTML과 CSS에 대한 기본 지식, Javascript 기초 (First steps과
 Building blocks
 참조), OOJS 기초 (Introduction to objects (en-US)
 참조).
  |
| 목표: |
 JSON에 담긴 데이터를 이용하는 법과 JSON 객체 생성하는 법에 대해
 알아보기.
  |

아니, 대체 JSON이 뭐죠?
----------------

JSON 는 Douglas Crockford가 널리 퍼뜨린 Javascript 객체 문법을 따르는 문자 기반의 데이터 포맷입니다. JSON이 Javascript 객체 문법과 매우 유사하지만 딱히 Javascript가 아니더라도 JSON을 읽고 쓸 수 있는 기능이 다수의 프로그래밍 환경에서 제공됩니다.

JSON은 문자열 형태로 존재합니다 — 네트워크를 통해 전송할 때 아주 유용하죠. 데이터에 접근하기 위해서는 네이티브 JSON 객체로 변환될 필요가 있습니다. 별로 큰 문제는 아닌 것이 Javascript는 JSON 전역 객체를 통해 문자열과 JSON 객체의 상호변환을 지원합니다.

**참고:** 문자열에서 네이티브 객체로 변환하는 것은 파싱(Parsing)이라고 합니다. 네트워크를 통해 전달할 수 있게 객체를 문자열로 변환하는 과정은 문자열화(Stringification)이라고 합니다.

개별 JSON 객체를 `.json` 확장자를 가진 단순 텍스트 파일에 저장할 수 있습니다. MIME 타입은 `application/json` 입니다.

### JSON 구조

위에서 설명했듯이 JSON은 Javascript 객체 리터럴 문법을 따르는 문자열입니다. JSON 안에는 마찬가지로 Javascript의 기본 데이터 타입인 문자열, 숫자, 배열, 불리언 그리고 다른 객체를 포함할 수 있습니다. 이런 방식으로 여러분은 데이터 계층을 구축할 수 있습니다, 아래 처럼요.

```json
{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}
```

이 객체를 Javascript 프로그램에서 로드하고, 예를 들어 `superHeroes`라는 이름의 변수에 파싱하면 JavaScript object basics 문서에서 보았던 것처럼 점/브라켓 표현법을 통해 객체 내 데이터에 접근할 수 있게 됩니다. 아래와 같이요:

```js
superHeroes.homeTown;
superHeroes["active"];
```

하위 계층의 데이터에 접근하려면, 간단하게 프로퍼티 이름과 배열 인덱스의 체인을 통해 접근하면 됩니다. 예를 들어 superHeroes의 두 번째 member의 세 번째 power에 접근하려면 아래와 같이 하면 됩니다.

```js
superHeroes["members"][1]["powers"][2];
```

1. 우선 변수 이름은 — `superHeroes`입니다.
2. `members` 프로퍼티에 접근하려면, `["members"]`를 입력합니다.
3. `members`는 객체로 구성된 배열입니다. 두 번째 객체에 접근할 것이므로 `[1]`를 입력합니다.
4. 이 객체에서 `powers` 프로퍼티에 접근하려면 `["powers"]`를 입력합니다.
5. `powers` 프로퍼티 안에는 위에서 선택한 hero의 superpower들이 있습니다. 세 번째 것을 선택해야 하므로 `[2]`.

**참고:** 위에서 볼 수 있듯 JSONTest.html 예제에서 JSON 내 변수(source code 참고)를 만들었다. 당신 브라우저의 Javascript 콘솔을 통하여 이 코드를 로드하고 그 변수 안에 얻을 수 있는 데이터에 접근해보자.

### JSON에서의 배열

앞서 JSON 텍스트는 기본적으로 JavaScript의 오브젝트와 비슷하게 생겼다고 언급하였습니다. 그리고 그것은 대부분 맞습니다. "대부분 맞다"라고 말한 이유는 JavaScript의 배열 또한 JSON에서 유효하기 때문입니다.

```json
[
  {
    "name": "Molecule Man",
    "age": 29,
    "secretIdentity": "Dan Jukes",
    "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
  },
  {
    "name": "Madame Uppercut",
    "age": 39,
    "secretIdentity": "Jane Wilson",
    "powers": [
      "Million tonne punch",
      "Damage resistance",
      "Superhuman reflexes"
    ]
  }
]
```

위 예제는 완벽히 올바른 형태의 JSON입니다. 배열의 요소(파싱된 버전)에 접근하기 위해서는 배열의 인덱스를 사용하면 됩니다. `[0]["powers"][0]` 와 같이 말이죠.

### Other notes

* JSON은 순수히 데이터 포맷입니다. 오직 프로퍼티만 담을 수 있습니다. 메서드는 담을 수 없습니다.
* JSON은 문자열과 프로퍼티의 이름 작성시 큰 따옴표만을 사용해야 합니다. 작은 따옴표는 사용불가합니다.
* 콤마나 콜론을 잘못 배치하는 사소한 실수로 인해 JSON파일이 잘못되어 작동하지 않을 수 있습니다. JSONLint같은 어플리케이션을 사용해 JSON 유효성 검사를 할 수 있습니다.
* JSON은 JSON내부에 포함할 수 있는 모든 형태의 데이터 타입을 취할 수 있습니다. 즉, 배열이나 오브젝트 외에도 단일 문자열이나 숫자또한 유효한 JSON 오브젝트가 됩니다.
* JavaScript에서 오브젝트 프로퍼티가 따옴표로 묶이지 않을 수도 있는 것과는 달리, JSON에서는 따옴표로 묶인 문자열만이 프로퍼티로 사용될 수 있습니다.
해보면서 배우기: JSON을 다뤄 보자
---------------------

웹사이트에서 JSON 데이터를 어떻게 사용할 수 있는지 예제를 통해 살펴봅시다.

### 시작하기

우선 로컬 저장소에 heroes.html 와 style.css 파일을 복사해주세요. style.css 파일은 페이지에 적용할 간단한 CSS를 담고 있으며, heros.html 파일은 간단한 body HTML을 담고 있습니다.

```html
<header></header>

<section></section>
```

JavaScript 코드를 담기 위한`<script>` 요소를 추가해 주세요. 현재는 두 줄의 코드만 작성되어 있습니다. `<header>` 와 `<section>` 요소를 참조하여 변수에 담는 코드입니다. :

```js
var header = document.querySelector("header");
var section = document.querySelector("section");
```

JSON 데이터를 다음 깃허브 링크에서 가져올수 있습니다. https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json.

페이지에 JSON 데이터를 로딩하고 DOM 조작을 통해 아래와 같이 만들어 봅시다. :

![](/ko/docs/Learn/JavaScript/Objects/JSON/json-superheroes.png)

### JSON 가져오기

JSON을 가져오기 위해서는, `XMLHttpRequest` (때론 **XHR**)로 불리는 API를 사용하면 된다. 이것은 매우 유용한 JavaScript 오브젝트로 JavaScript (e.g. images, text, JSON, even HTML snippets)를 통해 우리가 서버로 부터 다양한 리소스를 가져오는 요청을 만들어 준다. 즉, 전체 페이지를 불러오지 않고도 필요한 부분만을 업데이트 할 수 있다. 이 기능은 좀 더 효과적으로 반응형 웹페이지을 다루는데 흥미로울 수 있으나, 아쉽게도 이 내용에 대한 자세한 부분은 여기서 다루지 않는다.

1. 일단, 변수로 둘 JSON의 URL을 가져와야 합니다. 아래의 코드를 당신의 JavaScript 코드 내에 추가해 주세요.

```js
var requestURL =
  "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
```

2. 요청을 만들기 위해, 우리는 `new` 키워드를 이용하여 `XMLHttpRequest` 생성자로부터 새로운 request 인스턴스를 생성해야 합니다. 아래의 코드를 추가해 주세요.

```js
var request = new XMLHttpRequest();
```

3. 이제 `open()` (en-US) 메소드를 사용해 새로운 요청을 만듭니다. 아래의 코드를 추가해 주세요.

```js
request.open("GET", requestURL);
```

이것은 최소 두 개의 매개변수를 가집니다. 다른 선택적 매개변수도 가능해요. 이건 단순 예제니깐 두 가지 필수 매개변수만 취할게요.
    * HTTP 메서드는 네트워크 요청을 만들 때 사용됩니다. 이 경우 `GET` 을 사용하는게 좋겠어요. 우린 그저 데이터를 가져오면 되니깐요.
    * URL은 요청을 보낼 곳을 지정합니다. 우리가 저장해 둔 JSON 파일의 URL로 지정할게요.
4. 다음으로, 아래의 두 줄을 추가해 주세요. `responseType` (en-US) 을 JSON으로 설정했어요. XHR로 하여금 서버가 JSON 데이터를 반환할 것이며, JavaScript 객체로서 변환될 것이라는 걸 알게 하기 위해서죠. 이제 `send()` (en-US) 메서드를 이용해 요청을 보냅시다.

```js
request.responseType = "json";
request.send();
```

5. 마지막 부분은 서버의 응답을 기다리고, 그것의 처리까지와 관련된 섹션입니다. 코드 아래에 다음의 코드를 추가해 주세요.

```js
request.onload = function () {
  var superHeroes = request.response;
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

우리는 요청에 대한 응답을 `superHeroes`라는 변수에 저장할 겁니다.(`response` (en-US) 프로퍼티로 가능) 이 변수는 이제 JSON 데이터에 기반한 JavaScript 객체를 포함하게 됩니다! 두 개의 함수를 호출해 이 객체를 전달합시다. 하나는 <`header>` 를 적절한 데이터로 채울 것이고, 다른 하나는 팀의 각 히어로에 대한 정보 카드를 생성하여 `<section>`내에 집어넣을 겁니다.

우리는 로드 이벤트가 request 객체에 발생할 때에 작동하는 이벤트 핸들러 내에 코드를 넣었습니다. (`onload` (en-US) 참고) 왜냐하면 응답이 성공적으로 돌아왔을 때만 로드 이벤트가 작동하기 때문입니다. 이러한 방식은 우리가 무언가를 시도하려고 할 때 `request.response` 가 확실히 가능하다는 것을 보장해 줍니다.

### 헤더 조작하기

우린 JSON 데이터를 가져왔고, 그것을 JavaScript 객체로 변환했어요. 이제 우리가 위에서 언급한 두 개의 함수를 사용함으로써 이것을 활용해 봅시다. 우선, 이전 코드 아래에 다음 함수 정의를 추가해 주세요.

```js
function populateHeader(jsonObj) {
  var myH1 = document.createElement("h1");
  myH1.textContent = jsonObj["squadName"];
  header.appendChild(myH1);

  var myPara = document.createElement("p");
  myPara.textContent =
    "Hometown: " + jsonObj["homeTown"] + " // Formed: " + jsonObj["formed"];
  header.appendChild(myPara);
}
```

우리는 이 매개변수를 `jsonObj`라고 이름 붙였습니다. 여러분으로 하여금 이 JavaScript 객체가 JSON으로 부터 생겨났다는 걸 상기시켜 주기 위해서죠. 첫번째로 `createElement()`로 {HTMLElement("h1")}} 요소를 생성하고, 이것의 `textContent`를 객체의 `squadName` 프로퍼티와 같도록 만들어 준 뒤, `appendChild()`를 사용해서 헤더에 붙이도록 했습니다. We then do a very similar 이와 비슷한 과정을 paragraph에도 적용했죠. 생성하고,그것의 text content를 설정하고 헤더에 붙이도록요. 차이점이라면 그것의 텍스트가 객체의 `homeTown` 과 `formed` 프로퍼티를 포함한 문자열로 설정된 거예요.

### 히어로 정보 카드 만들기

자, 다음으로 슈퍼히어로 카드를 생성하고 보여줄 함수를 코드의 마지막에 추가해 주세요.

```js
function showHeroes(jsonObj) {
  var heroes = jsonObj["members"];

  for (var i = 0; i < heroes.length; i++) {
    var myArticle = document.createElement("article");
    var myH2 = document.createElement("h2");
    var myPara1 = document.createElement("p");
    var myPara2 = document.createElement("p");
    var myPara3 = document.createElement("p");
    var myList = document.createElement("ul");

    myH2.textContent = heroes[i].name;
    myPara1.textContent = "Secret identity: " + heroes[i].secretIdentity;
    myPara2.textContent = "Age: " + heroes[i].age;
    myPara3.textContent = "Superpowers:";

    var superPowers = heroes[i].powers;
    for (var j = 0; j < superPowers.length; j++) {
      var listItem = document.createElement("li");
      listItem.textContent = superPowers[j];
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}
```

우선, 새로운 변수 내의 JavaScript 객체에 `members` 프로퍼티를 저장해 주세요. 이 배열은 각 히어로에 대한 정보를 가진 여러 개의 객체를 포함합니다.

다음으로, 우리는 for loop를 사용하여 배열 내의 각 객체에 반복 실행을 걸겁니다.

1. 몇 개의 새로운 요소를 생성해 주세요: `<article>`, `<h2>`, 3개의 `<p>`, 그리고 `<ul>`.
2. <h2>가 히어로의 `name`을 가지도록 설정해 주세요.
3. 목록 내의 정보를 소개하기 위해 세 개의 paragraph 안에 그들의 `secretIdentity`, `age`, 그리고 "Superpowers:" 라는 문장을 넣어 주세요.
4. `superPowers` 라는 새로운 변수 안에 `powers`프로퍼티를 저장해 주세요. 이것은 현재 히어로의 초능력(superpower)을 열거한 배열을 포함합니다.
5. 현재 히어로의 초능력을 반복 실행할 또다른 `for` 반복문을 사용합니다. 하나의 객체씩 `<li>` 를 생성하고, 그 안에 초능력을 집어 넣고, `appendChild()`를 사용하여 '`myList'` 라는 `<ul>` 안에 `listItem` 을 집어 넣습니다.
6. 마지막으로 `<article>` (`myArticle`) 안에 `<h2>`, `<p>`, 그리고 `<ul>` 넣은 뒤, `<section>` 안에 `<article>`을 넣어 줍니다. 요소가 추가되어 지는 순서가 중요해요.왜냐하면 HTML 내에 보여질 거거든요.

**참고:** 만약 실습에 문제를 겪고 있다면, heroes-finished.html 코드를 참조하세요. (running live 또한 보세요.)

**참고:** 만약 우리가 주로 사용하는 JavaScript 객체에 접근하기 위한 점/괄호 표기법을 따르는데 문제를 겪고 있다면, 다른 탭이나 당신의 선호하는 텍스트 에디터에 superheroes.json 파일을 여는 것이 도움이 될 것이다. 또한 당신은 점/괄호 표기법에 대한 다른 정보를 위해 JavaScript object basics 글을 다시 확인하는 것을 권합니다.

객체와 문자 사이의 변환
-------------

위의 예제는 JavaScript 객체에 접근한다는 관점에서 단순한 편이었죠. 왜냐하면 JavaScript 객체를 사용해 우리는 XHR 요청을 곧장 JSON 응답으로 변환했으니깐요.

```js
request.responseType = "json";
```

하지만 우리는 이따금 재수 없어요. 때때로 우리는 날것의 JSON 문자열을 받기도 하고, 그것을 우리 스스로가 객체로 변환시켜야 할 때도 생기거든요. 그리고 네트워크를 통해 JavaScript 객체를 보내고 싶을 때도 우리는 전송 전에 그걸 JSON(문자열)로 변환시켜야 해요. 다행히도, 이 두가지 문제가 웹 개발에 있어서 매우 흔한 덕에 다음과 같은 두가지 방법을 포함한 JSON 내장 객체가 브라우저 내에서 이용 가능합니다.

* `parse()`: JSON 문자열을 매개변수로서 수용하고, 일치하는 JavaScript 객체로서 변환합니다.
* `stringify()`: 객체를 매개변수로서 수용하고, JSON 문자열 형태로 변환합니다.

당신은 예제인 heroes-finished-json-parse.html에서 첫번째 동작을 확인할 수 있습니다.(아래 코드 참고-source code) — JSON 텍스트를 반환하기 위한 추가한 XHR와 JavaScript 객체로 전환하기 위해 사용된 `parse()`제외하면 이 코드는 이전에 우리가 빌드한 것과 완전히 같은 코드입니다. 다음은중요한 코드의 일부입니다.:

```js
request.open("GET", requestURL);
request.responseType = "text"; // now we're getting a string!
request.send();

request.onload = function () {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

예상했겠지만, `stringify()` 는 반대의 방식으로 작용됩니다. 다음 나오는 코드 라인들을 당신의 브라우저 JavaScript 콘솔에 동작을 확인하면서 한 줄씩 입력해봅시다.:

```js
var myJSON = { name: "Chris", age: "38" };
myJSON;
var myString = JSON.stringify(myJSON);
myString;
```

이 페이지에서는 우리는 JavaScript 객체를 생성하고 있으며 이 객체가 무엇을 포함하고 있는지 확인하고 `stringify()` —반환된 값을 새로운 변수에 저장합니다—를 사용해서 JSON 문자열로 변환시켰습니다. 다시 한 번 더 확인해 봅시다.

실력을 시험해보자!
----------

당신은 이 글의 끝에 도달했지만 가장 중요한 정보들을 기억하고 있습니까? 다음 단계를 가기 전 당신이 얼마나 이 정보를 습득하고 있는지 확인할 수 있는 테스트를 할 수 있습니다. — Test your skills: JSON

요약
--

이 글에서, 당신의 프로그램에서 JSON을 어떻게 생성하고 구성 요소를 파악할 수 있는지, 그리고 JSON 안에 묶여 있는 자료들에 어떻게 접근하는 방법을 포함한 JSON을 사용하기 위한 간단한 가이드를 제공했습니다. 다음 글에서는, 객체 지향적인 JavaScript에 대해 시작해 볼 것입니다.

참고
--

* JSON object reference page
* XMLHttpRequest object reference page
* Using XMLHttpRequest (en-US)
* HTTP request methods
* Official JSON web site with link to ECMA standard
* 이전
* Overview: Objects
* 다음  (en-US)


객체 지향 프로그래밍 - Web 개발 학습하기

객체 지향 프로그래밍
===========

* 이전
* Overview: Objects
* 다음

객체 지향 프로그래밍(OOP)은 Java 및 C++를 비롯한 많은 프로그래밍 언어의 기본이 되는 프로그래밍 패러다임입니다. 이 기사에서는 OOP의 기본 개념에 대한 개요를 제공합니다. **클래스와 인스턴스**, **상속**, **캡슐화**라는 세 가지 주요 개념에 대해 설명합니다. 지금은 특히 JavaScript를 참조하지 않고 이러한 개념을 설명하므로 모든 예제가 pseudocode로 제공됩니다.

**참고:** 정확히 말하면 여기에 설명된 기능은 **클래스 기반** 또는 "고전적인" OOP라고 하는 특정 스타일의 OOP입니다. 사람들이 OOP에 대해 이야기할 때 일반적으로 이것이 의미하는 유형입니다.

그런 다음 JavaScript에서 생성자와 프로토타입 체인이 이러한 OOP 개념과 어떻게 관련되고 어떻게 다른지 살펴보겠습니다. 다음 기사에서는 객체 지향 프로그램을 보다 쉽게 구현할 수 있도록 하는 JavaScript의 추가 기능에 대해 살펴보겠습니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: |
 JavaScript 함수 이해, JavaScript 기본 사항
 (
 첫 번째 단계 및
 빌딩 블록 참조) 및 OOJS 기본 사항 (
 객체 소개 및 객체 프로토타입 참조).
  |
| 목표: | 클래스 기반 객체 지향 프로그래밍의 기본 개념을 이해합니다. |

객체 지향 프로그래밍은 각 객체가 시스템의 특정 측면을 나타내는 객체 모음으로 시스템을 모델링하는 것입니다. 객체에는 함수(또는 메서드)와 데이터가 모두 포함됩니다. 객체는 사용하려는 다른 코드에 대한 공용 인터페이스를 제공하지만 자체 비공개 내부 상태를 유지합니다. 시스템의 다른 부분은 객체 내부에서 일어나는 일에 대해 신경 쓸 필요가 없습니다.

클래스 및 인스턴스
----------

OOP의 객체 측면에서 문제를 모델링할 때 시스템에 포함하려는 객체 유형을 나타내는 추상 정의를 만듭니다. 예를 들어 학교를 모델링하는 경우 교수를 나타내는 객체를 원할 수 있습니다. 모든 교수는 몇 가지 공통점이 있습니다. 모두 이름과 자신이 가르치는 과목이 있습니다. 또한 모든 교수는 특정 작업을 수행할 수 있습니다. 예를 들어 모두 논문을 채점할 수 있고 연초에 학생들에게 자신을 소개할 수 있습니다.

따라서 `Professor`는 우리 시스템의 **클래스**가 될 수 있습니다. 클래스의 정의에는 모든 교수가 가지고 있는 데이터와 방법이 나열되어 있습니다.

의사 코드에서 `Professor` 클래스는 다음과 같이 작성할 수 있습니다.
```

class Professor
    properties
        name
        teaches
    methods
        grade(paper)
        introduceSelf()
```

이는 다음과 같이 `Professor` 클래스를 정의합니다.

* 두 가지 데이터 속성: `name` 및 `teaches`
* 두 가지 메서드: 논문을 채점하는 `grade()`와 자신을 소개하는 `insertSelf()`.

그 자체로 클래스는 아무 것도 하지 않습니다. 클래스는 해당 유형의 구체적인 객체를 만들기 위한 일종의 템플릿입니다. 우리가 생성하는 각 구체적인 교수는 `Professor` 클래스의 **인스턴스**라고 합니다. 인스턴스 생성 프로세스는 **생성자**라는 특수 함수에 의해 수행됩니다. 새 인스턴스에서 초기화하려는 내부 상태에 대한 값을 생성자에 전달합니다.

일반적으로 생성자는 클래스 정의의 일부로 작성되며 일반적으로 클래스 자체와 동일한 이름을 갖습니다.
```

class Professor
    properties
        name
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()
```

이 생성자는 두 개의 매개변수를 사용하므로 새로운 구체적인 교수를 만들 때 `name`과 `teaches` 속성을 초기화할 수 있습니다.

이제 생성자가 있으므로 일부 교수들을 만들 수 있습니다. 프로그래밍 언어는 종종 `new` 키워드를 사용하여 생성자가 호출되고 있음을 알립니다.

```js
walsh = new Professor("Walsh", "Psychology");
lillian = new Professor("Lillian", "Poetry");

walsh.teaches; // 'Psychology'
walsh.introduceSelf(); // 'My name is Professor Walsh and I will be your Psychology professor.'

lillian.teaches; // 'Poetry'
lillian.introduceSelf(); // 'My name is Professor Lillian and I will be your Poetry professor.'
```

이렇게 하면 `Professor` 클래스의 인스턴스인 두 개의 객체가 생성됩니다.

상속
--

우리 학교에서도 학생들을 대표하고 싶다고 가정해 봅시다. 교수와 달리 학생들은 논문을 채점할 수 없고, 특정 과목을 가르치지 않으며, 특정 연도에 속합니다.

그러나 학생들에게는 이름이 있고 자신을 소개하고 싶을 수도 있으므로 다음과 같이 학생 클래스의 정의를 작성할 수 있습니다.
```

class Student
    properties
        name
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()
```

학생과 교수가 어떤 속성을 공유한다는 사실, 더 정확하게는 어떤 수준에서 그들은 같은 종류라는 사실을 나타낼 수 있다면 도움이 될 것입니다. **상속**을 통해 이를 수행할 수 있습니다.

우리는 학생과 교수가 모두 사람이고 사람들은 이름이 있고 자신을 소개하고 싶어한다는 것을 관찰하는 것으로 시작합니다. 우리는 사람의 모든 공통 속성을 정의하는 새 클래스 `Person`을 정의하여 이를 모델링할 수 있습니다. 그런 다음 `Professor`와 `Student`는 둘 다 `Person`에서 **파생**되어 추가 속성을 추가할 수 있습니다.
```

class Person
    properties
        name
    constructor
        Person(name)
    methods
        introduceSelf()

class Professor : extends Person
    properties
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()

class Student : extends Person
    properties
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()
```

이 경우 `Person`은 `Professor`와 `Student` 모두의 **상위 클래스** 또는 **부모 클래스**라고 말할 수 있습니다. 반대로 `Professor`와 `Student`는 `Person`의 **하위 클래스** 또는 **자식 클래스**입니다.

`introduceSelf()`가 세 클래스 모두에 정의되어 있음을 알 수 있습니다. 그 이유는 모든 사람이 자신을 소개하고 싶어하지만 그 방법이 다르기 때문입니다.

```js
walsh = new Professor("Walsh", "Psychology");
walsh.introduceSelf(); // 'My name is Professor Walsh and I will be your Psychology professor.'

summers = new Student("Summers", 1);
summers.introduceSelf(); // 'My name is Summers and I'm in the first year.'
```

학생이나 교수가 아닌 사람들을 위해 `introduceSelf()`의 기본 구현이 있을 수 있습니다.

```js
pratt = new Person("Pratt");
pratt.introduceSelf(); // 'My name is Pratt.'
```

이 기능(메서드 이름은 같지만 다른 클래스에서 구현이 다른 경우)을 **다형성**이라고 합니다. 하위 클래스의 메서드가 상위 클래스의 구현을 대체할 때 하위 클래스가 상위 클래스의 버전을 **재정의**한다고 합니다.

캡슐화
---

객체는 객체를 사용하려고 하지만 자체 내부 상태를 유지하려는 다른 코드에 대한 인터페이스를 제공합니다. 객체의 내부 상태는 **비공개**로 유지됩니다. 즉, 다른 객체가 아닌 객체 자체 메서드로만 액세스할 수 있습니다. 객체의 내부 상태를 비공개로 유지하고 일반적으로 공개 인터페이스와 비공개 내부 상태를 명확하게 구분하는 것을 **캡슐화**라고 합니다.

이 기능은 프로그래머가 객체를 사용하는 모든 코드를 찾아 업데이트하지 않고도 객체의 내부 구현을 변경할 수 있기 때문에 유용한 기능입니다. 이 객체와 시스템의 나머지 부분 사이에 일종의 방화벽을 만듭니다.

예를 들어, 학생들이 2학년 이상인 경우 양궁을 공부할 수 있다고 가정합니다. 학생의 `year` 속성을 노출하는 것만으로 이를 구현할 수 있으며 다른 코드에서 이를 검사하여 학생이 과정을 수강할 수 있는지 여부를 결정할 수 있습니다.

```js
if (student.year > 1) {
  // 그 학생을 수업에 들어오도록 허락
}
```

문제는 학생이 양궁을 공부하도록 허용하는 기준을 변경하기로 결정한 경우(예: 부모 또는 보호자의 허가를 받도록 요구하는 경우) 이 테스트를 수행하는 시스템의 모든 위치를 업데이트해야 한다는 것입니다. 한 곳에서 논리를 구현하는 `canStudyArchery()` 메서드를 `Student` 객체에 두는 것이 좋습니다.
```

class Student : extends Person
    properties
       year
    constructor
       Student(name, year)
    methods
       introduceSelf()
       canStudyArchery() { return this.year > 1 }
```

```js
if (student.canStudyArchery()) {
  // allow the student into the class
}
```

그렇게 하면 양궁 공부에 대한 규칙을 변경하려는 경우 `Student` 클래스만 업데이트하면 되며 이를 사용하는 모든 코드는 계속 작동합니다.

많은 OOP 언어에서 일부 속성을 `private`로 표시하여 다른 코드가 객체의 내부 상태에 액세스하지 못하도록 할 수 있습니다. 객체 외부의 코드가 객체에 액세스하려고 하면 오류가 발생합니다.
```

class Student : extends Person
    properties
       private year
    constructor
        Student(name, year)
    methods
       introduceSelf()
       canStudyArchery() { return this.year > 1 }

student = new Student('Weber', 1)
student.year // error: 'year' is a private property of Student
```

이와 같이 액세스를 강제하지 않는 언어에서 프로그래머는 이름을 밑줄로 시작하는 것과 같은 명명 규칙을 사용하여 속성이 비공개로 간주되어야 함을 나타냅니다.

OOP와 JavaScript
---------------

이 기사에서는 Java 및 C++와 같은 언어로 구현된 클래스 기반 객체 지향 프로그래밍의 기본 기능 중 일부를 설명했습니다.

이전 두 기사에서 우리는 두 가지 핵심 JavaScript 기능인 생성자와 프로토타입을 살펴보았습니다. 이러한 기능은 확실히 위에서 설명한 일부 OOP 개념과 어느 정도 관련이 있습니다.

* JavaScript의 **생성자**는 객체에 포함된 모든 메서드를 포함하여 객체의 "모양"을 단일 위치에서 정의할 수 있도록 클래스 정의와 같은 것을 제공합니다. 그러나 여기에서도 프로토타입을 사용할 수 있습니다. 예를 들어 메서드가 생성자의 `prototype` 속성에 정의된 경우 해당 생성자를 사용하여 생성된 모든 객체는 프로토타입을 통해 해당 메서드를 가져오므로 생성자에서 정의할 필요가 없습니다.
* **프로토타입 체인**은 상속을 구현하는 자연스러운 방법처럼 보입니다. 예를 들어 프로토타입이 `Person`인 `Student` 객체를 가질 수 있는 경우 `name`을 상속하고 `introduceSelf()`를 재정의할 수 있습니다.

그러나 이러한 기능과 위에서 설명한 "고전적인" OOP 개념 간의 차이점을 이해하는 것이 좋습니다. 여기에서 몇 가지를 강조하겠습니다.

첫째, 클래스 기반 OOP에서 클래스와 객체는 두 개의 별도 구성이며 객체는 항상 클래스의 인스턴스로 생성됩니다. 또한 클래스를 정의하는 데 사용되는 기능(클래스 구문 자체)과 객체를 인스턴스화하는 데 사용되는 기능(생성자) 사이에는 차이가 있습니다. JavaScript에서는 함수나 객체 리터럴을 사용하여 별도의 클래스 정의 없이 객체를 생성할 수 있고 종종 생성합니다. 이를 통해 기존 OOP보다 객체 작업을 훨씬 더 가볍게 만들 수 있습니다.

둘째, 프로토타입 체인은 상속 계층 구조처럼 보이고 어떤 면에서는 비슷하게 작동하지만 다른 면에서는 다릅니다. 하위 클래스가 인스턴스화되면 하위 클래스에 정의된 속성과 계층 구조에서 추가로 정의된 속성을 결합하는 단일 객체가 생성됩니다. 프로토타이핑을 사용하면 계층 구조의 각 수준이 별도의 객체로 표시되며 `__proto__` 속성을 통해 함께 연결됩니다. 프로토타입 체인의 동작은 상속보다는 **위임**에 가깝습니다. 위임은 객체가 작업을 수행하도록 요청받았을 때 작업 자체를 수행하거나 다른 객체(**대리인**)에게 대신 작업을 수행하도록 요청할 수 있는 프로그래밍 패턴입니다. 여러 면에서 위임은 상속보다 객체를 결합하는 더 유연한 방법입니다(한 가지로 런타임에 위임을 변경하거나 완전히 대체할 수 있음).

즉, 생성자와 프로토타입을 사용하여 JavaScript에서 클래스 기반 OOP 패턴을 구현할 수 있습니다. 그러나 상속과 같은 기능을 구현하기 위해 직접 사용하는 것은 까다롭기 때문에 JavaScript는 클래스 기반 OOP의 개념에 보다 직접적으로 매핑되는 프로토타입 모델 위에 추가 기능을 제공합니다. 이러한 추가 기능은 다음 문서의 주제입니다.

요약
--

이 기사에서는 클래스 기반 객체 지향 프로그래밍의 기본 기능을 설명하고 JavaScript 생성자와 프로토타입을 이러한 개념과 비교하는 방법을 간략하게 살펴보았습니다.

다음 기사에서는 클래스 기반 객체 지향 프로그래밍을 지원하기 위해 JavaScript가 제공하는 기능을 살펴보겠습니다.

* 이전
* Overview: Objects
* 다음


JavaScript 객체 기본 - Web 개발 학습하기

JavaScript 객체 기본
================

* Overview: Objects
* 다음

이 글에서는 JavaScript 객체와 관련된 기본적인 문법을 살펴보고 이전 코스에서 학습해서 이미 알고 있는 JavaScript 의 특징들과 우리가 이미 사용하고 있는 기능들이 이미 객체와 관련되어 있다는 사실을 다시 한번 복습할 것입니다.

|  |  |
| --- | --- |
| 사전 지식: |
 컴퓨터와 관련된 기본지식, HTML 과 CSS, 그리고 JavaScript 에 대한 기본
 지식 (JavaScript 첫걸음
 및
 JavaScript 구성요소
 참조).
  |
| 목표: |
 객체지향 프로그래밍에 대한 기본 이론을 이해하고, JavaScript 에서 객체가
 어떻게 처리되는지("대부분이 객체임") 학습 후, JavaScript 객체를 실제로
 이용하는 방법에 대해 알게되는 것을 목표로 합니다.
  |

객체 기본
-----

객체는 관련된 데이터와 함수(일반적으로 여러 데이터와 함수로 이루어지는데, 객체 안에 있을 때는 보통 프로퍼티와 메소드라고 부릅니다)의 집합입니다. 예제를 통해서 실제 객체가 무엇인지 알아보도록 합시다.

시작하기에 앞서, oojs.html 파일의 복사본을 로컬 환경에 만듭니다. 이 파일은 우리가 작성한 소스코드를 포함하는 작은 `<script>` 요소를 포함하고 있습니다. 우리는 기본 객체 문법을 탐구하기 위한 기반으로 이 파일을 사용할 것입니다. 예제를 제대로 따라하려면 반드시 개발자 도구 JavaScript 콘솔 (en-US)을 열어두고, 몇몇 명령어를 직접 입력할 준비가 되어있어야 합니다.

여타 JavaScript 의 요소들과 마찬가지로, 객체를 생성하는 것은 변수를 정의하고 초기화하는 것으로 시작합니다. 아래의 JavaScript 코드를 oojs.html 파일의 script tag 사이에 입력하고 저장 한 후, 리로드 해보세요.

```js
var person = {};
```

이제 브라우저의 JavaScript 콘솔 (en-US) 을 열고 `person`을 입력 한 다음 `Enter`/`Return`을 누르세요. 아래 줄 중 하나와 유사한 결과가 표시됩니다.

```js
[object Object]
Object { }
{ }
```

축하합니다. 방금 첫번째 객체를 만들었습니다. 잘했어요! 그러나 이것은 빈 객체이므로 실제로 많은 것들을 할 수 없습니다. 파일에서 JavaScript 객체를 다음과 같이 업데이트 해보겠습니다.

```js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  bio: function () {
    alert(
      this.name[0] +
        " " +
        this.name[1] +
        " is " +
        this.age +
        " years old. He likes " +
        this.interests[0] +
        " and " +
        this.interests[1] +
        ".",
    );
  },
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};
```

저장하고 새로 고침 한 후 브라우저 devtools의 JavaScript 콘솔에 다음 중 일부를 입력 해보세요.

```js
person.name;
person.name[0];
person.age;
person.interests[1];
person.bio();
person.greeting();
```

이제 객체 내부에 몇 가지 데이터와 기능이 있으며, 멋진 간단한 구문으로 액세스 할 수 있습니다!

**참고:** 만약 여기까지 진행하는데 어려움이 있다면, 제가 만들어놓은 파일과 비교해보세요 — oojs-finished.html (그리고 실행되는 예제도 보세요). Live 버전에서는 텅빈 화면만 보이겠지만, 그게 정상입니다 — 다시, 개발자도구를 열고 객체 구조를 들여다보기 위해 위에 언급된 명령어를 입력해보세요.

자, 이제 뭘 해볼까요? 객체는 각기 다른 이름(위의 예에서는 `name` 과 `age`)과 값(예제에서, `['Bob', 'Smith']` 과 `32`)을 갖는 복수개의 멤버로 구성됩니다. 한 쌍의 이름과 값은 ',' 로 구분되야 하고, 이름과 값은 ':' 으로 분리됩니다. 결국 문법은 아래와 같은 패턴이 됩니다.

```js
var objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};
```

객체를 구성하는 멤버의 값은 어떤 것이라도 될 수 있습니다. 우리가 만든 person 객체는 문자열, 숫자, 배열 두개와 두개의 함수를 가지고 있습니다. 처음 4개의 아이템은 데이터 아이템인데, 이걸 객체의 **프로퍼티(속성)** 라고 부릅니다. 끝에 두개의 아이템은 함수인데 이 함수를 통해 데이터를 가지고 뭔가 일을 할 수 있게 됩니다. 이걸 우리는 **메소드** 라고 부릅니다.

이런 객체는 객체 리터럴(**object literal)** 이라고 부릅니다. 객체를 생성할 때 컨텐츠를 그대로 대입합니다. 객체 리터럴은 클래스로부터 생성하는 방식과는 다릅니다. 이 방식은 뒤에서 살펴보게 될겁니다.

객체 리터럴을 사용해서 객체를 생성하는 것은 연속된 구조체나 연관된 데이터를 일정한 방법으로 변환하고자 할 때 많이 쓰이는 방법입니다. 예를 들면 서버에게 주소를 데이터베이스에 넣어달라고 요청하는 경우입니다. 각 아이템들을 하나 하나 개별 전송하는 것보다, 하나의 객체를 전송하는 것이 훨씬 효율적입니다. 또 각 아이템들을 이름으로 구분해서 사용하기 원할 때도 배열을 사용하는 것보다 훨씬 쉽습니다.

점 표기법
-----

위에서, 우리는 객체의 프로퍼티와 메소드를 **점 표기법**을 통해 접근했습니다. 객체 이름(person)은 **네임스페이스**처럼 동작합니다. 객체내에 **캡슐화되어있는**것에 접근하려면 먼저 점을 입력해야합니다. 그 다음 점을 찍고 접근하고자 하는 항목을 적습니다. 간단한 프로퍼티의 이름일 수도 있을 것이고, 배열의 일부이거나 객체의 메소드를 호출할 수도 있습니다.

```js
person.age;
person.interests[1];
person.bio();
```

### 하위 namespaces

다른 객체를 객체 멤버의 값으로 갖는 것도 가능합니다. 예를 들면, 다음과 같은 name 멤버를

```js
name: ['Bob', 'Smith'],
```

아래와 같이 바꿔봅시다.

```js
name : {
  first: 'Bob',
  last: 'Smith'
},
```

자, 이제 우리는 성공적으로 **하위 namespace** 를 만들었습니다. 복잡해보이지만, 사실 그렇지도 않습니다. 이 속성을 사용하려면 그저 끝에 다른 점을 하나 찍어주기만 하면 됩니다. JS 콘솔에서 아래와 같이 입력해보세요.

```js
person.name.first;
person.name.last;
```

**중요**: 객체의 속성이 바뀌었으니까, 기존 메소드 코드를 바꿔 줘야 합니다. 기존 코드를

```js
name[0];
name[1];
```

아래와 같이 바꿔줘야 합니다.

```js
name.first;
name.last;
```

그렇지 않으면 기존 메소드는 더 이상 동작하지 않을 것입니다.

괄호 표기법
------

객체의 프로퍼티에 접근하는 다른 방법으로 괄호 표기법을 사용하는 것이 있습니다. 다음과 같이 사용하는 대신

```js
person.age;
person.name.first;
```

이렇게 사용할 수 있습니다.

```js
person["age"];
person["name"]["first"];
```

이런 방식은 배열 속에 있는 항목에 접근하는 방법과 매우 유사해 보이는데 실제로도 이는 기본적으로 동일한 것입니다. 한 항목을 선택하기 위해 인덱스 숫자를 이용하는 대신에 각 멤버의 값들과 연결된 이름을 이용합니다. 객체가 간혹 **연관배열 (associative arrays**)이라고 불리는 것이 당연합니다. 연관배열은 배열이 숫자를 값에 연결하는 것과 같은 방법으로 문자열을 값에 매핑합니다.

객체 멤버 설정하기
----------

지금까지는 객체 멤버를 단순히 가져오기만(또는 **반환**) 했습니다. 설정할 멤버를 간단히 명시하여(점이나 대괄호 표기법을 사용) 객체 멤버의 값을 **설정**(갱신)하는 것도 물론 가능합니다.

```js
person.age = 45;
person["name"]["last"] = "Cratchit";
```

위의 코드를 입력한 다음, 객체 멤버값을 아래와 같이 다시 확인해봅시다.

```js
person.age;
person["name"]["last"];
```

객체 멤버를 설정하는 것은 단순히 기존에 존재하는 프로퍼티나 메소드로 값을 설정하는 것 뿐 아니라, 완전히 새로운 멤버를 생성할 수도 있습니다. JS 콘솔에서 아래 내용을 입력해보세요.

```js
person["eyes"] = "hazel";
person.farewell = function () {
  alert("Bye everybody!");
};
```

자, 이제 새로운 멤버를 테스트해보세요.

```js
person["eyes"];
person.farewell();
```

대괄호 표현의 이점 중 하나는 멤버의 값을 동적으로 변경할 수 있을 뿐아니라, 멤버 이름까지도 동적으로 사용할 수 있다는 것입니다. 자, 만약 사용자가 두개의 텍스트 입력을 통해서 people 데이터에 커스텀 값을 넣고 싶어한다고 가정해봅시다. 그 값은 다음과 같이 얻어올 수 있을겁니다.

```js
var myDataName = nameInput.value;
var myDataValue = nameValue.value;
```

이제 `person` 객체에 다음과 같이 새 멤버의 이름과 값을 추가할 수 있습니다.

```js
person[myDataName] = myDataValue;
```

자, 제대로 동작하는지 보려면 아래와 같이 `person` 객체에 대괄호를 붙여서 확인해보면 됩니다.

```js
var myDataName = "height";
var myDataValue = "1.75m";
person[myDataName] = myDataValue;
```

이제 저장하고 리로드후 아래코드를 입력해보세요.

```js
person.height;
```

점 표기법으로는 위의 예제처럼 멤버의 이름을 동적으로 사용할 수 없고, 상수 값만을 사용해야 합니다.

"this" 는 무엇인가?
--------------

자, 우리가 이제 보게될 메소드가 좀 이상하게 보일 수도 있을겁니다. 예제를 한번 봐주세요

```js
greeting: function() {
  alert('Hi! I\'m ' + this.name.first + '.');
}
```

아마도 "this" 가 뭔지 의아하실 것입니다. `this` 키워드는 지금 동작하고 있는 코드를 가지고 있는 객체를 가리킵니다. 위의 예제에서 `this` 는 `person` 객체와 동일합니다. 그럼 왜 직접 `person` 객체를 쓰지 않은걸까요? 앞으로 보게될 입문자를 위한 객체 지향 JavaScript 문서에서 우리가 생성자를 공부하게 될 때, 혹은 그것 말고도 기타 등등의 상황에서 `this` 는 매우 유용하게 사용됩니다. 이 this 라는 녀석은 객체 멤버의 컨텍스트가 바뀌는 경우에도 언제나 정확한 값을 사용하게 해줍니다(예를 들어, 두개의 다른 `person` 객체가 각각 다른 이름으로 인스턴스로 생성된 상태에서 인사말을 출력하기 위해 객체의 name 을 참조해야 한다고 생각해보세요).

간략화된 person 객체를 가지고 설명을 좀 해보겠습니다.

```js
var person1 = {
  name: "Chris",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};

var person2 = {
  name: "Deepti",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};
```

이 예제에서, 메소드의 실제 코드는 완전히 동일하지만 `person1.greeting()` 은 "Hi! I'm Chris." 를 출력합니다. 반면 `person2.greeting()` 은 "Hi! I'm Deepti." 을 출력하게 됩니다. 앞서 이야기한 것처럼, `this` 은 실행중인 코드가 속해있는 객체입니다. 객체 리터럴을 직접 지정해서 사용하는 경우라면 그리 유용하지 않겠지만, 동적으로 객체를 생성하는 경우(예를 들면 생성자를 사용하는 경우)에는 매우 유용합니다. 이 부분은 추후에 더 명확하게 이해가 될 겁니다.

객체를 줄곧 사용해 왔습니다
---------------

예제코드를 따라하다보니, 이쯤 되면 슬슬 "점" 표기법을 사용하는 것이 꽤 자연스럽게 느껴질 것입니다. 물론 이 코스 내내 사용했기 때문입니다! 샘플에서 사용하였던 브라우저 내장 API 나 JavaScript 객체들은 실제로 우리가 공부했던 구조와 완전히 동일한 방법으로 구현된 것들입니다. 물론 우리가 봤던 예제보다 복잡하기는 합니다.

자, 다음과 같이 문자열의 메소드를 사용했다고 가정합시다.

```js
myString.split(",");
```

`String` 클래스의 인스턴스가 가진 메소드를 사용하고 있습니다. 코드에서 문자열을 생성할 때 마다 `String` 의 인스턴스가 만들어지고, 그렇게 만들어진 인스턴스는 당연히 공통적으로 사용할 수 있는 메소드와 프러퍼티를 가집니다.

아래와 같이 도큐먼트 오브젝트 모델(DOM)에 접근할때면,

```js
var myDiv = document.createElement("div");
var myVideo = document.querySelector("video");
```

`Document` 클래스의 인스턴스를 통해 메소드를 사용하고 있는 것입니다. 각 웹페이지가 로딩될 때, `Document` 인스턴스가 만들어지고, 전체 웹 페이지 구조와 컨텐츠 그리고 URL같은 기능들을 제공하는 `document` 가 호출됩니다. 다시 말하지만 이건 여러 공통 메소드와 프로퍼티들이 이 인스턴스를 통해 사용가능하게 됩니다.

우리가 계속 사용해왔던 다른 내장 객체/API(`Array`, `Math` 등등)들도 마찬가지입니다

모든 내장 객체/API가 자동으로 객체의 인스턴스를 생성하는 것은 아니라는 것에 주의 하세요. 예를 들어, Notifications API — 최근 브라우져들이 시스템 알림을 사용하게 하는 기능 — 는 사용자가 발생 시키길 원하는 notification 만을 선택하게 하는 생성자를 사용해야 합니다. JavaScript 콘솔에 다음 내용을 입력해보세요

```js
var myNotification = new Notification("Hello!");
```

다음 문서에서 생성자에 대해서 좀더 자세히 알아볼 것입니다.

**참고:** 객체간 통신은 **message passing** 방식을 사용한다고 생각하는게 좋습니다. 한 객체가 다른 객체에게 어떤 액션을 요청해야 하는 경우, 그 객체는 다른 객체가 가지고 있는 메소드를 통해서 메세지를 보내는 것이고, 응답을 기다리는 것입니다. 그 응답은 것이 우리가 알고 있는 return 값입니다.

실력을 시험해보세요!
-----------

본문의 끝에 도달했지만 가장 중요한 정보를 기억할 수 있나요? 계속 진행하기전에 정보들을 잘 기억하고 있는지 확인하기 위한 몇 가지 추가 테스트를 찾을 수 있습니다. 기술 테스트 : 객체의 기본 사항 (en-US)을 참조하세요.

요약
--

축하합니다, 첫 번째 JavaScript 객체 설명 문서를 끝까지 읽으셨습니다. 이제 여러분은 JavaScript 객체를 어떻게 활용하는지 이해하게 되었습니다. 간단한 사용자 정의 객체를 만드는 방법을 포함해서요. 또 객체는 데이터와 연관된 함수를 저장하는데 매우 유용한 구조라는 것도 알게 되었습니다. 만약 `person` 객체가 가지고 있는 모든 프로퍼티와 메소드를 따로 따로 분리된 변수와 함수로 구현하려고 한다면 그것이야 말로 비효율적이고 끔찍한 일이 될 것입니다. 변수명과 함수명들이 중복된다거나 하는 일도 비일비재 할 것입니다. 객체는 고유의 패키지에 우리의 정보를 안전하게 정보를 보호해주는 역활을 합니다.

다음 장에서는 객체지향(OOP) 이론을 배우고, JavaScript 에서는 객체지향이 어떻게 사용되었는지 알아볼 것입니다.

* Overview: Objects
* 다음


Object prototypes - Web 개발 학습하기

Object prototypes
=================

* 이전
* Overview: Objects
* 다음

Javascript에서는 객체를 상속하기 위하여 프로토타입이라는 방식을 사용합니다. 본 문서에서는 프로토타입 체인이 동작하는 방식을 설명하고 이미 존재하는 생성자에 메소드를 추가하기 위해 프로토타입 속성을 사용하는 법을 알아봅니다.

|  |  |
| --- | --- |
| 선수조건: |
 컴퓨터 기본지식, HTML과 CSS에 대한 기본적인 이해,JavaScript에 어느
 정도 익숙할 것 (see
 First steps and
 Building blocks). OOJS 기초 지식 (see
 Introduction to objects).
  |
| 학습목표: |
 Javascript 객체 프로토타입을 이해하고 프로토타입 체인이 어떻게
 동작하는지, 또 프로토타입 속성에 새 메소드를 추가하는 방법을 배웁니다.
  |

프로토타입 기반 언어?
------------

JavaScript는 흔히 **프로토타입 기반 언어**(**prototype-based language**)라 불립니다.— 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 **프로토타입 객체**(**prototype object**)를 가진다는 의미입니다. 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지입니다. 이를 **프로토타입 체인**(**prototype chain**)이라 부르며 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 하는 근간입니다.

정확히 말하자면 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 `prototype`이라는 속성에 정의되어 있습니다.

JavaScript에서는 객체 인스턴스와 프로토타입 간에 연결(많은 브라우저들이 생성자의 `prototype` 속성에서 파생된 `__proto__` 속성으로 객체 인스턴스에 구현하고 있습니다.)이 구성되며 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메소드를 탐색합니다.

**참고:** 객체의 prototype(`Object.getPrototypeOf(obj)`함수 또는 deprecated 된 `__proto__`속성으로 접근 가능한)과 생성자의 `prototype` 속성의 차이를 인지하는 것이 중요합니다. 전자는 개별 객체의 속성이며 후자는 생성자의 속성입니다. 이 말은 `Object.getPrototypeOf(new Foobar())`의 반환값이 `Foobar.prototype`과 동일한 객체라는 의미입니다.

자세히 알기 위해 예제를 하나 봅시다.

프로토타입 객체 이해하기
-------------

`Person()` 생성자를 작성했던 예제 코드로 되돌아가 봅시다. — 브라우저로 예제 코드를 불러 오시구요. 이전 페이지에서 작업했던 예제 코드를 날려버렸다면 oojs-class-further-exercises.html 페이지를 방문해 주세요. (source code도 보시구요).

이 예제에서 생성자 함수를 정의했었습니다:

```js
function Person(first, last, age, gender, interests) {
  // 속성과 메소드 정의
  this.first = first;
  this.last = last;
  //...
}
```

그리고 인스턴스도 하나 만들었구요:

```js
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
```

Javascript 콘솔에서 "person1."을 치게 되면 브라우저는 아래처럼 해당 객체의 멤버 이름을 자동 완성 팝업으로 보여줄 것입니다:

![](/ko/docs/Learn/JavaScript/Objects/Object_prototypes/object-available-members.png)

위에서 `person1`의 프로토타입 객체인 `Person()`에 정의된 멤버들 — `name`, `age`, `gender`, `interests`, `bio`, `greeting`을 볼 수 있습니다. 또한 — `watch`, `valueOf`처럼 `Person()`의 프로토타입 객체인 `Object`에 정의된 다른 멤버들도 보실 수 있습니다. 이는 프로토타입 체인이 동작한다는 증거입니다.

![](/ko/docs/Learn/JavaScript/Objects/Object_prototypes/mdn-graphics-person-person-object-2.png)

 그럼 실제로는 `Object`에 정의되어 있는 메소드를 `person1`에서 호출하면 어떻게 될까요?
 아래 처럼 말이죠:

```js
person1.valueOf();
```

이 메소드는 호출된 객체의 값을 단순 반환합니다. — 직접 실행해 보세요! 이때 일어나는 일은 아래와 같습니다:

* 브라우저는 우선 `person1` 객체가 `valueOf()` 메소드를 가지고 있는지 체크합니다.
* 없으므로 `person1`의 프로토타입 객체(`Person()` 생성자의 프로토타입)에 `valueOf()` 메소드가 있는지 체크합니다.
* 여전히 없으므로 `Person()` 생성자의 프로토타입 객체의 프로토타입 객체(`Object()` 생성자의 프로토타입)가 `valueOf()` 메소드를 가지고 있는지 체크합니다. 여기에 있으니 호출하며 끝납니다!

**참고:** 프로토타입 체인에서 한 객체의 메소드와 속성들이 다른 객체로 **복사되는 것이 아님**을 재차 언급합니다. — 위에서 보시다 시피 체인을 타고 올라가며 접근할 뿐입니다.

**참고:** 특정 객체의 프로토타입 객체에 바로 접근하는 공식적인 방법은 없습니다. — Javascript 언어 표준 스펙에서`[[prototype]]`으로 표현되는 프로토타입 객체에 대한 "링크"는 내부 속성으로 정의되어 있습니다. (ECMAScript 참조). 하지만 많은 수의 모던 브라우저들이 `__proto__` (앞뒤로 언더바 2개씩) 속성을 통해 특정 객체의 프로토타입 객체에 접근할 수 있도록 구현하였습니다. 예를 들자면 `person1.__proto__` 또는 `person1.__proto__.__proto__` 코드로 체인이 어떻게 구성되어 있는지 확인해 보세요!

ECMAScript 2015부터는 `Object.getPrototypeOf(obj)` 함수를 통해 객체의 프로토타입 객체에 **바로 접근할 수 있게** 되었습니다..

프로토타입 속성: 상속 받은 멤버들이 정의된 곳
--------------------------

그럼 상속 받은 속성과 메소드들은 어디에 정의되어 있을까요? `Object` 레퍼런스 페이지에 가시면 좌측에 수 많은 속성과 메소드들이 나열되어 있는 것을 볼 수 있습니다. — 위 스크린샷에서 person1가 상속받은 멤버들보다 훨씬 많죠. 일부는 상속 되었지만 나머지는 아닙니다. — 왜일까요?

정답은 상속 받는 멤버들은 `prototype` 속성(sub-namespace라고도 하죠)에 정의되어 있기 때문입니다. — `Object.`로 시작하는게 아니라, `Object.prototype.`로 시작하는 것들이죠. `prototype` 속성도 하나의 객체이며 프로토타입 체인을 통해 상속하고자 하는 속성과 메소드를 담아두는 버킷으로 주로 사용되는 객체입니다.

So `Object.prototype.watch()`, `Object.prototype.valueOf()`등등은, 생성자를 통해 새로 생성되는 인스턴스는 물론 `Object.prototype`을 상속 받는 객체라면 어떤 객체에서든지 접근할 수 있습니다.

`Object.is()`, `Object.keys()`등 `prototype` 버킷에 정의되지 않은 멤버들은 상속되지 않습니다. 이 것들은 `Object()` 생성자에서만 사용할 수 있는 멤버들이죠.

**참고:** 척 보기엔 이상합니다. — 함수에 불과한 생성자에 멤버를 정의한다니요? 함수 역시 객체의 하나입니다 — 못 미더우시면 `Function()` 생성자 레퍼런스 페이지를 확인해 보세요.

1. prototype 속성을 직접 확인해 볼 수 있습니다. — 예제로 돌아가서 Javascript console에 아래 코드를 타이핑 해 보세요:

```js
Person.prototype;
```

2. 출력되는 것이 별로 많지 않을 겁니다 — 아직 이 커스텀 생성자 프로토타입에 아무것도 정의하지 않았거든요! 기본적으로 생성자의 프로토타입은 비어있습니다. 이번에는 아래 코드를 실행해 봅시다:

```js
Object.prototype;
```

위의 예제에서 확인했듯이 `Object`를 상속받은 객체에서 사용 가능한 수 많은 메소드들이 `Object`의 `prototype` 속성에 정의되어 있음을 알 수 있습니다.

Javascript 전반에 걸쳐 프로토타입 체인 상속이 어떻게 구성되어 있는지 확인할 수 있습니다 — 전역 객체인 `String`, `Date`, `Number`, `Array`의 프로토타입에 정의된 메소드와 속성들을 체크해 보세요. 이 프로토타입 객체들에는 이미 많은 수의 메소드가 정의되어 있으며 이는 아래처럼 문자열 객체를 생성 했을 때:

```js
var myString = "This is my string.";
```

`myString` 인스턴스가 생성되는 즉시 `split()`, `indexOf()`, `replace()` (en-US)등의 문자열을 위한 유용한 메소드들을 사용할 수 있는 이유입니다.

**경고:** **Important**: `prototype` 속성은 Javascript에서 가장 헷갈리는 명칭중 하나입니다. — 보통 `this`가 현재 객체의 프로토타입 객체를 가리킬 것이라 오해하지만 그렇지 않죠. (프로토타입 객체는 `__proto__` 속성으로 접근 가능한 내장 객체인 것 기억 하시나요?). 대신에 `prototype` 속성은 상속 시키려는 멤버들이 정의된 객체를 가리킵니다.

create() 다시보기
-------------

이전에 새 인스턴스를 생성하기 위해서 `Object.create()` 메소드를 사용하는 법을 알아 보았습니다.

1. 예를 들어서 이전 예제에서 아래 코드를 Javascript console에서 실행했었다면:

```js
var person2 = Object.create(person1);
```

2. `create()` 메소드가 실제로 하는 일은 주어진 객체를 프로토타입 객체로 삼아 새로운 객체를 생성합니다. 여기서 person2는 person1을 프로토타입 객체로 삼습니다. 아래 코드를 실행하여 이를 확인할 수 있습니다:

```js
person2.__proto__;
```

콘솔 상에는 person1이 출력됩니다.

생성자 속성
------

모든 생성자 함수는 `constructor` 속성을 지닌 객체를 프로토타입 객체로 가지고 있습니다. 이 `constructor` 속성은 원본 생성자 함수 자신을 가리키고 있습니다. 다음 장에서 볼 수 있듯이 `Person.prototype` 속성(또는 위 절에서 언급된 아무 생성자 함수의 prototype 속성)에 정의된 속성들은 `Person()` 생성자로 생성된 모든 인스턴스에서 사용할 수 있습니다. 그러므로 person1과 person2에서도 constructor 속성에 접근할 수 있습니다.

1. 예를 들어 아래 코드를 콘솔에서 실행해 보세요:

```js
person1.constructor;
person2.constructor;
```

두 구문 모두 `Person()` 생성자 함수를 반환할 것입니다.
 `constructor` 속성에 괄호를 붙이고 실행하여(인자가 필요하면 전달하구요) 새 인스턴스를 생성하는 트릭이 있습니다. 어쨌든 생성자도 함수의 일종이므로 괄호를 붙이면 실행할 수 있습니다; new 키워드를 통해 실행하면 함수를 인스턴스를 생성하기 위한 생성자로 사용할 수 있죠.
2. 아래 코드를 실행해 보세요:

```js
var person3 = new person1.constructor("Karen", "Stephenson", 26, "female", [
  "playing drums",
  "mountain climbing",
]);
```

3. 새로 생성된 객체를 테스트 해보세요, 아래처럼요:

```js
person3.name.first;
person3.age;
person3.bio();
```

잘 동작함을 알 수 있습니다. 이런 방식을 자주 사용할 필요는 없지만 실행 도중 명시적인 생성자 함수를 예측할 수 없는 상황에서 인스턴스를 생성해야 하거나 하는 경우 유용하게 사용할 수 있는 방법입니다.

`constructor` 속성은 여러 사용법이 있습니다. 예를 들어 인스턴스의 생성자 이름이 필요한 경우 아래의 코드로 알아낼 수 있습니다:

```js
instanceName.constructor.name;
```

아래 코드도 시험해 보세요:

```js
person1.constructor.name;
```

**참고:** `constructor.name` 는 변경이 가능하므로(상속이나 바인딩, 전처리, 트랜스파일러 등에 의해) 복잡한 로직에 적용하기 위해서는 `instanceof` 연산자를 사용하세요.

프로토타입 수정하기
----------

`생성자의 prototype` 속성을 수정하는 법에 대해 알아봅시다(프로토타입에 메소드를 추가하면 해당 생성자로 생성된 모든 객체에서 사용 가능합니다).

1. oojs-class-further-exercises.html 예제로 돌아가서 source code를 다운 받으세요. 기존 코드에 아래 샘플 코드를 추가하여 `prototype` 속성에 새 메소드를 추가하세요:

```js
Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

2. 저장한 코드를 브라우저로 실행하고 콘솔에서 아래 코드를 실행해 보세요.

```js
person1.farewell();
```

생성자에서 지정했던 person의 name이 alert 창으로 출력되는 것을 확인할 수 있습니다. 매우 유용한 기능이지만 중요한 점은 prototype에 새 메소드를 추가하는 순간 동일한 생성자로 생성된 모든 객체에서 추가된 메소드를 바로 사용할 수 있다는 점입니다.

잠시 정리해 봅시다. 예제에서는 생성자를 정의하고, 객체를 생성하였으며, 그 이후에 프로토타입에 새 메소드를 추가하였습니다:

```js
function Person(first, last, age, gender, interests) {
  // 속성과 메소드 정의
}

var person1 = new Person("Tammi", "Smith", 32, "neutral", [
  "music",
  "skiing",
  "kickboxing",
]);

Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

그런데도 person1에서 바로 farewell() 메소드를 사용할 수 있습니다 - 자동으로 업데이트 되기 때문이죠(역주:원문은 이런 뉘앙스입니다만 실제로는 프로토타입 객체는 모든 인스턴스에서 공유하기 때문에 정의하는 즉시 별도의 갱신 과정 없이 접근이 가능합니다).

**참고:** 코드가 잘 동작하지 않는다면 oojs-class-prototype.html 코드를 참조하세요 (running live 페이지도 보시구요).

prototype에 속성을 정의하는 경우는 별로 본 적이 없을겁니다. 왜냐면 별로 좋은 방법이 아니거든요. 속성을 추가할 때 아래와 같이 할 수 있습니다:

```js
Person.prototype.fullName = "Bob Smith";
```

사람들이 항상 밥 스미스로 부르지 않을 수 있으니 별로 좋은 방법 같지는 않아 보입니다. `fullname`을 `name.first`와 `name.last`로 나누어 보죠:

```js
Person.prototype.fullName = this.name.first + " " + this.name.last;
```

이 경우 `this`는 함수 범위가 아닌 전역 범위를 가리키므로 코드가 의도대로 동작하지 않습니다. 이대로 실행해도 `undefined undefined`만 볼 수 있죠. 윗 절에서 프로토타입에 정의한 메소드 내에서는 정상적으로 동작하는데 이는 코드가 함수 범위 내에 있으며 객체의 멤버 함수로써 동작하기에 객체 범위로 전환되었기 때문입니다. 따라서 프로토타입에 상수(한 번 할당하면 변하지 않는 값)를 정의하는 것은 가능하지만 일반적으로 생성자에서 정의하는 것이 낫습니다.

사실 일반적인 방식으로는 속성은 생성자에서, 메소드는 프로토타입에서 정의합니다. 생성자에는 속성에 대한 정의만 있으며 메소드는 별도의 블럭으로 구분할 수 있으니 코드를 읽기가 훨씬 쉬워집니다. 아래처럼요:

```js
// 생성자에서 속성 정의

function Test(a, b, c, d) {
  // 속성 정의
}

// 첫 메소드 정의

Test.prototype.x = function() { ... };

// 두번째 메소드 정의

Test.prototype.y = function() { ... };

// 그 외.
```

이런 패턴은 Piotr Zalewa의 school plan app 예제 코드에서 볼 수 있습니다.

요약
--

이 글에서는 객체 프로토타입에 대한 설명과 프로토타입 체인을 통해 다른 객체를 상속하는 방법, 프로토타입 속성과 생성자에 새 메소드를 추가하는 방법과 그와 관련된 지식을 다루고 있습니다.

다음 글에서는 직접 만든 객체간의 상속을 구현하는 방법에 대해 알아봅시다.

* 이전
* Overview: Objects
* 다음