Graceful asynchronous programming with Promises - Web 개발 학습하기

Graceful asynchronous programming with Promises
===============================================

* 이전
* Overview: Asynchronous
* 다음

**Promises** 는 이전 작업이 완료될 때 까지 다음 작업을 연기 시키거나, 작업실패를 대응할 수 있는 비교적 새로운 JavaScript 기능입니다. Promise는 비동기 작업 순서가 정확하게 작동되게 도움을 줍니다. 이번 문서에선 Promise가 어떻게 동작하는지, 웹 API와 어떻게 사용할 수 있는지 그리고 직접 코드를 만들어 볼것 입니다.

|  |  |
| --- | --- |
| Prerequisites: |
 Basic computer literacy, a reasonable understanding of JavaScript
 fundamentals.
  |
| Objective: | To understand promises and how to use them. |

What are promises?
------------------

앞서서 Promises 를 미리 봤지만, 지금부턴 좀더 깊이있게 들여다 볼 차례 입니다..

Promise는 어떤 작업의 중간상태를 나타내는 오브젝트 입니다. — 미래에 어떤 종류의 결과가 반환됨을 *promise* (약속) 해주는 오브젝트라고 보면 됩니다. Promise는 작업이 완료되어 결과를 반환해주는 정확한 시간을 보장해주지는 않지만, 사용할 수 있는 결과를 반환했을때 프로그래머의 의도대로 다음 코드를 진행 시키거나, 에러가 발생했을 때 그 에러를 우아하게/깔끔하게 처리할 수 있습니다.

일반적으로 우리는 비동기 작업이 결과를 반환하는데 얼마의 시간이 걸리는지 보다는(*작업 시간이 매우 오래 걸리지 않는 한*) 그 결과를 사용할 수 있는지 여부에 더 관심이 있습니다. 물론 나머지 코드 블럭을 막지 않는다는 것에 있어서 매우 좋습니다.

우리가 Promise로 가장 많이 할 작업중 하나는 Promise를 반환하는 웹API를 사용하는 것 입니다. 가상의 비디오 채팅 애플리케이션이 있다고 해봅시다. 애플리케이션에는 친구 목록이 있고 각 친구 목록 옆의 버튼을 클릭하면 해당 친구와 비디오 채팅을 시작합니다.

그 버튼은 사용자 컴퓨터의 카메라와 마이크를 사용하기 위해 `getUserMedia()` (en-US) 를 호출합니다. `getUserMedia()` 는 사용자가 이러한 장치를 사용할 수 있는 권한을 가지고 있는지 확인해야 하고, 어떤 마이크와 카메라를 사용할 것인지 (혹은 음성 통화인지, 아니면 다른 옵션들이 있는지)를 체크해야하기 때문에 모든 결정이 내려질 때 까지 다음 작업을 차단할 수 있습니다. 또한 카메라와 마이크가 작동하기 전 까지 다음 작업을 차단할수도 있습니다.

`getUserMedia()` 는 브라우저의 main thread에서 실행되므로 `getUserMedia()` 결과가 반환되기 전 까지 후행 작업이 모두 차단됩니다. 이러한 blocking은 우리가 바라는게 아닙니다. Promise가 없으면 이러한 결정이 내려지기 전 까지 브라우저의 모든 것을 사용할 수 없게됩니다. 따라서 사용자가 선택한 장치를 활성화하고 소스에서 선택된 스트림에 대해`MediaStream` (en-US) 직접 반환하는 대신 `getUserMedia()` 는 모든 장치가 사용 가능한 상태가 되면 `MediaStream` (en-US)이 포함된 `promise`를 반환합니다.

비디오 채팅 애플리케이션의 코드는 아래처럼 작성할 수 있습니다.

```js
function handleCallButton(evt) {
  setStatusMessage("Calling...");
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((chatStream) => {
      selfViewElem.srcObject = chatStream;
      chatStream
        .getTracks()
        .forEach((track) => myPeerConnection.addTrack(track, chatStream));
      setStatusMessage("Connected");
    })
    .catch((err) => {
      setStatusMessage("Failed to connect");
    });
}
```

이 기능은 상태 메시지에 "Calling..."을 출력하는 `setStatusMessage()` 함수로 시작하며 통화가 시도되고 있음을 나타냅니다. 그런 다음 `getUserMedia()`을 호출하여 비디오와 오디오 트랙이 모두 있는 스트림 요청을 합니다. 그리고 스트림을 획득하면 카메라에서 나오는 스트림을 "self view,"로 표시하기 위해 video엘리먼트를 설정합니다. 그리고 각 스트림의 트랙을 가져가 다른 사용자와의 연결을 나타내는 WebRTC `RTCPeerConnection` (en-US)에 추가합니다. 그리고 마지막으로 상태 메시지를 "Connected"로 업데이트 합니다.

`getUserMedia()` 가 실패하면, `catch` 블럭이 실행되며, `setStatusMessage()` 를 사용하여 상태창에 오류 메시지를 표시합니다.

여기서 중요한건 `getUserMedia()`는 카메라 스트림이 아직 확보되지 않았음에도 거의 즉시 반환을 해줬다는 것 입니다. `handleCallButton()` 함수가 자신을 호출한 코드로 결과를 이미 반환을 했더라도 `getUserMedia()`의 작업이 종료되면 프로그래머가 작성한 다음 핸들러를 호출할 것 입니다. 앱이 스트리밍을 했다고 가정하지 않는 한 계속 실행될 수 있습니다.

**참고:** You can learn more about this somewhat advanced topic, if you're interested, in the article Signaling and video calling. Code similar to this, but much more complete, is used in that example.

The trouble with callbacks
--------------------------

Promise가 왜 좋은지 이해하기 위해 구식 callbacks을 살펴보고 어떤게 문제인지 파악 해보겠습니다.

피자를 주문한다고 생각해봅시다. 피자를 잘 주문하려면 몇 가지 단계를 진행해야 합니다. 토핑 위에 도우를 올리고 치즈를 뿌리는 등 각 단계가 뒤죽박죽 이거나 혹은 도우를 반죽하고 있는데 그 위에 토마토소스를 바르는 등 이전 작업이 끝나지 않고 다음 작업을 진행하는 것은 말이 안 됩니다. :

1. 먼저 원하는 토핑을 고릅니다. 결정 장애가 심할 경우 토핑을 고르는데 오래 걸릴 수 있습니다. 또한 마음을 바꿔 피자 대신 카레를 먹으려고 가게를 나올 수 있습니다.
2. 그다음 피자를 주문합니다. 식당이 바빠서 피자가 나오는 데 오래 걸릴 수 있고, 마침 재료가 다 떨어졌으면 피자를 만들 수 없다고 할 것 입니다.
3. 마지막으로 피자를 받아서 먹습니다. 그런데! 만약 지갑을 놓고 와서 돈을 내지 못한다면 피자를 먹지 못할 수 있습니다.

구식 callbacks을 사용하면 아래와 같은 모습의 코드가 나타날것 입니다. :

```js
chooseToppings(function (toppings) {
  placeOrder(
    toppings,
    function (order) {
      collectOrder(
        order,
        function (pizza) {
          eatPizza(pizza);
        },
        failureCallback,
      );
    },
    failureCallback,
  );
}, failureCallback);
```

이런 코드는 읽기도 힘들 뿐 아니라 (종종 "콜백 지옥" 이라 불림), `failureCallback()` 을 여러 번 작성해야 하며 또한 다른 문제점도 가지고 있습니다.

### Improvements with promises

위의 상황에서 Promise를 사용하면 읽기, 작성, 실행 모두 다 쉬워집니다. callback 대신 비동기 Promise를 사용하면 아래처럼 작성할 수 있습니다. :

```js
chooseToppings()
  .then(function (toppings) {
    return placeOrder(toppings);
  })
  .then(function (order) {
    return collectOrder(order);
  })
  .then(function (pizza) {
    eatPizza(pizza);
  })
  .catch(failureCallback);
```

보기에 훨씬 더 좋군요! — 이렇게 작성하면 앞으로 어떤 일이 일어날지 쉽게 예측 가능합니다. 그리고 단 한개의 `.catch()` 을 사용하여 모든 에러를 처리합니다. 그리고 main thread를 차단하지 않습니다. (그래서 피자를 주문하고 기다리는 동안 하던 게임을 마저 할 수 있습니다.), 또한 각 함수가 실행되기 전 이전 작업이 끝날때까지 기다립니다. 이런식으로 여러 개의 비동기 작업을 연쇄적으로 처리할 수 있습니다. 왜냐햐면 각 `.then()` 블럭은 자신이 속한 블럭의 작업이 끝났을 때의 결과를 새로운 Promise 반환해주기 때문입니다. 어때요, 참 쉽죠?

화살표 함수를 사용하면 코드를 조금 더 간단하게 고칠 수 있습니다. :

```js
chooseToppings()
  .then((toppings) => placeOrder(toppings))
  .then((order) => collectOrder(order))
  .then((pizza) => eatPizza(pizza))
  .catch(failureCallback);
```

혹은 아래처럼 표현할 수 있습니다. :

```js
chooseToppings()
  .then((toppings) => placeOrder(toppings))
  .then((order) => collectOrder(order))
  .then((pizza) => eatPizza(pizza))
  .catch(failureCallback);
```

화살표 함수의 `() => x` 표현은 `() => { return x; }`의 약식 표현이므로 잘 작동합니다.

함수는 arguments를 직접 전달 하므로 함수처럼 표현하지 않고 아래와 같이 작성할 수도 있습니다. :

```js
chooseToppings()
  .then(placeOrder)
  .then(collectOrder)
  .then(eatPizza)
  .catch(failureCallback);
```

그런데 이렇게 작성하면 읽기가 쉽지 않습니다. 사용자의 코드가 지금의 예제보다 더 복잡하다면 위의 방법은 사용하기 힘듭니다.

**참고:** 다음 장에서 배울 `async`/`await` 문법으로 좀 더 간결화 할 수 있습니다.

Promise는 이벤트 리스너와 유사하지만 몇 가지 다른점이 있습니다. :

* Promise는 한번에 성공/실패 중 하나의 결과값을 가집니다. 하나의 요청에 두 번 성공하고나 실패할 수 없습니다. 또한 이미 성공한 작업이 다시 실패로 돌아갈 수 없고 실패한 작업이 성공으로 돌아갈 수 없습니다.
* If a promise has succeeded or failed and you later add a success/failure callback, the correct callback will be called, even though the event took place earlier.
Explaining basic promise syntax: A real example
-----------------------------------------------

모던 웹 API는 잠재적으로 긴 작업을 수행하는 함수에 Promise를 사용하므로 Promise가 무엇인지 이해하는것은 매우 중요합니다. 현대적인 웹 기술을 사용하려면 Promise를 사용해야합니다. 챕터의 후반부에서 직접 Promise를 만들어보겠지만, 지금은 일단 웹 API에서 접할 수 있는 몇 가지 예제를 살펴보겠습니다.

첫 번째로, 웹에서 이미지를 가져오기 위하여 `fetch()` 메서드를 사용할 때 입니다.`blob()` (en-US) 메서드는 fetch가 응답한 원시 body컨텐츠를 `Blob` 오브젝트로 변환시켜주고`<img>` 엘리먼트에 표현합니다. 이예제는 first article of the series유사합니다. 다만 Promise를 사용하기 위해 약간의 변경을 하겠습니다.

**참고:** The following example will not work if you just run it directly from the file (i.e. via a `file://` URL). You need to run it through a local testing server, or use an online solution such as Glitch or GitHub pages (en-US).

1. 먼저 simple HTML template 와 fetch할 이미지인 sample image file 을 다운받습니다.
2. HTML `<body>` 하단에 `<script>` 엘리먼트를 삽입합니다.
3. `<script>` 엘리먼트 안에 아래와 같이 코드를 작성합니다. :

```js
let promise = fetch("coffee.jpg");
```

`fetch()` 메서드를 호출하여, 네트워크에서 fetch할 이미지의 URL을 매개변수로 전달합니다. 두 번째 매개변수를 사용할 수 있지만, 지금은 우선 간단하게 하나의 매개변수만 사용하겠습니다. 코드를 더 살펴보면 `promise`변수에 `fetch()` 작업으로 반환된 Promise 오브젝트를 저장하고 있습니다. 이전에 말했듯이, 지금 오브젝트는 성공도 아니고 실패도 아닌 중간 상태를 저장하고 있습니다. 공식적으로는 **pending**상태라고 부릅니다.
4. 작업이 성공적으로 진행될 때를 대응하기 위해 (이번 예제에선 `Response` 가 반환될 때 입니다. ), 우리는 Promise 오브젝트의 `.then()` 메서드를 호출합니다. `.then()` 블럭 안의 callback은 (**executor** 라고 부름) Promise가 성공적으로 완료되고`Response` 오브젝트를 반환할 때만 실행합니다. — 이렇게 성공한 Promise의 상태를 **fulfilled**라고 부릅니다. 그리고 반환된 `Response` 오브젝트를 매개변수로 전달합니다.

**참고:** The way that a `.then()` block works is similar to when you add an event listener to an object using `AddEventListener()`. It doesn't run until an event occurs (when the promise fulfills). The most notable difference is that a .then() will only run once for each time it is used, whereas an event listener could be invoked multiple times.

그리고 즉시 `blob()` 메서드를 실행하여 Response Body가 완전히 다운로드 됐는지 확인합니다. 그리고 Response Body가 이용 가능할 때 추가 작업을 할 수 있는 `Blob` 오브젝트로 변환시킵니다. 해당 코드는 아래와 같이 작성할 수 있습니다. :

```js
(response) => response.blob();
```

위의 코드는 아래의 코드를 축약한 형태입니다.

```js
function(response) {
  return response.blob();
}
```

이제 추가 설명은 충븐하므로, JavaScript의 첫 번째 줄 아래에 다음과 같은 라인을 추가하세요.

```js
let promise2 = promise.then((response) => response.blob());
```

5. 각 `.then()` 을 호출하면 새로운 Promise를 만드는데, 이는 매우 유용합니다. 왜냐하면 `blob()` 메서드도 Promise를 반환하기 때문에, 두 번째 Promise의 `.then()` 메서드를 호출함으로써 이행시 반환되는 `Blob` 오브젝트를 처리할 수 있습니다. 한 가지 메서드를 실행하여 결과를 반환하는 것보다 Blob에 좀 더 복잡한 일을 추가하고 싶습니다. 이럴때는 중괄호{ }로 묶습니다. (그렇지 않으면 에러가 발생합니다.).
 이어서 아래와 같은 코드를 추가합니다.:

```js
let promise3 = promise2.then((myBlob) => {});
```

6. 이제 executor 함수를 아래와 같이 채워넣습니다. 중괄호 안에 작성하면 됩니다. :

```js
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement("img");
image.src = objectURL;
document.body.appendChild(image);
```

여기서 우리는 두 번째 Promise가 fulfills일 때 반횐된 Blob을 매개변수로 전달받는 `URL.createObjectURL()` 메서드를 실행하고 있습니다. 이렇게 하면 오브젝트가 가지고있는 URL이 반환됩니다. 그 다음 `<img>` 엘리먼트를 만들고, 반환된 URL을 `src` 속성에 지정하여 DOM에 추가합니다. 이렇게 하면 페이지에 그림이 표시됩니다.

If you save the HTML file you've just created and load it in your browser, you'll see that the image is displayed in the page as expected. Good work!

**참고:** You will probably notice that these examples are somewhat contrived. You could just do away with the whole `fetch()` and `blob()` chain, and just create an `<img>` element and set its `src` attribute value to the URL of the image file, `coffee.jpg`. We did, however, pick this example because it demonstrates promises in a nice simple fashion, rather than for its real-world appropriateness.

### Responding to failure

현재 에러가 발생했을 때 어떻게 처리를 해야할 지 작성된 코드가 없기때문에 코드를 조금만 더 추가하여 좀 더 완벽하게 작성해봅시다. (Promise에서 에러가 발생한 상태를 **rejects**라 부릅니다). 이전에 봤던대로 `.catch()` (en-US) 블럭을 추가하여 오류를 핸들링 할 수 있습니다. 아래처럼 말이죠 :

```js
let errorCase = promise3.catch((e) => {
  console.log(
    "There has been a problem with your fetch operation: " + e.message,
  );
});
```

에러 메시지를 확인하고 싶으면 잘못된 url을 지정해보세요, 개발자 도구 콘솔에서 에러를 확인할 수 있을것 입니다.

물론 `.catch()` 블록 없이 코드를 작동시킬 수 있습니다. 하지만 좀 더 깊게 생각해보면 `.catch()` 블록이 없으면 어떤 에러가 발생했는지, 어떻게 해결해야 하는지 디버깅이 어렵습니다. 실제 앱에서 `.catch()` 을 사용하여 이미지 가져오기를 다시 실행하거나, 기본 이미지를 표시하는 등 작업을 지시할 수 있습니다.

**참고:** You can see our version of the example live (see the source code also).

### Chaining the blocks together

위에서 사용한 코드는 작업이 어떻게 처리되는지 명확하게 보여주기 위해 매우 길게 코드를 작성했습니다. 이전 글에서 봤듯이, `.then()` 블럭을 사용하여 연쇄 작업을 진행할 수 있습니다. (또한 `.catch()` 블럭을 사용하여 에러 처리도 했지요). 앞선 예제의 코드는 아래와 같이 작성할 수도 있습니다. (see also simple-fetch-chained.html on GitHub):

```js
fetch("coffee.jpg")
  .then((response) => response.blob())
  .then((myBlob) => {
    let objectURL = URL.createObjectURL(myBlob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => {
    console.log(
      "There has been a problem with your fetch operation: " + e.message,
    );
  });
```

fulfilled promise 결과에 의해 반환된 값이 다음 `.then()` 블록의 executor 함수가 가진 파라미터로 전달 된다는 것을 꼭 기억하세요.

**참고:** `.then()`/`.catch()` blocks in promises are basically the async equivalent of a `try...catch` (en-US) block in sync code. Bear in mind that synchronous `try...catch` won't work in async code.

Promise terminology recap
-------------------------

위의 섹션에서 다룬 내용은 정말 많습니다. 매우 중요한 내용을 다뤘으므로 개념을 명확히 이해하기 위해 몇번이고 다시 읽어보는게 좋습니다.

1. Promise가 생성되면 그 상태는 성공도 실패도 아닌 **pending**상태라고 부릅니다..
2. Promise결과가 반환되면 결과에 상관 없이 **resolved**상태라고 부릅니다..
    1. 성공적으로 처리된 Promise는 **fulfilled**상태이다. 이 상태가 되면 Promise 체인의 다음 `.then()` 블럭에서 사용할 수 있는 값을 반환합니다.. 그리고 `.then()` 블럭 내부의 executor 함수에 Promise에서 반환된 값이 파라미터로 전달됩니다..
    2. 실패한 Promise는 **rejected**상태이다. 이때 어떤 이유(**reason)** 때문에 Promise가 rejected 됐는지를 나타내는 에러 메시지를 포함한 결과가 반환됩니다. Promise 체이닝의 제일 마지막 `.catch()` 에서 상세한 에러 메시지를 확인할 수 있습니다.
Running code in response to multiple promises fulfilling
--------------------------------------------------------

위의 예제에서 Promise사용의 기초를 확인했습니다. 이제 고급 기능들을 한번 보겠습니다. 제일 먼저 확인해볼 예제는 다음과 같습니다. 연쇄적으로 일어나는 작업은 좋습니다. 그런데 모든 Promise가 fulfilled일 경우 코드를 실행하고 싶은 경우가 있을것 입니다.

해당 기능을 `Promise.all()` 이라는 스테틱 메서드를 사용하여 만들 수 있습니다. 이 메서드는 Promise의 배열을 매개변수로 삼고, 배열의 모든 Promise가 fulfil일 때만 새로운 fulfil `Promise` 오브젝트를 반환합니다. 아래처럼 말이죠 :

```js
Promise.all([a, b, c]).then(values => {
  ...
});
```

배열의 모든 Promise가 fulfil 이면, `.then()` 블럭의 executor 함수로의 매개변수로 Promise 결과의 배열을 전달합니다. `Promise.all()` 의 Promise의 배열 중 하나라도 reject라면, 전체 결과가 reject가 됩니다.

이 방법은 매우 유용합니다. 웹 UI의 컨텐츠를 동적인 방법으로 채운다고 생각 해보겠습니다. 대부분 경우에 듬성듬성 내용을 채우기보단, 완전한 내용을 채울것 입니다.

다른 예제를 만들어서 실행해 보겠습니다.

1. 이미 만들어진 page template을 다운받으세요 그리고 `</body>` 뒤에 `<script>` 엘리먼트를 만들어주세요.
2. 이미지 그리고 텍스트 파일(coffee.jpg, tea.jpg, and description.txt)을 다운받고 page template 와 같은 경로에 저장해주세요.
3. 먼저 Promise를 반환하는 몇 가지 함수를 만들어 `Promise.all()`로 결과를 반환합니다. 세 개의 `fetch()` 작업이 끝나고 다음 요청을 진행하고 싶다면 아래 코드처럼 `Promise.all()`블럭을 작성합니다. :

```js
let a = fetch(url1);
let b = fetch(url2);
let c = fetch(url3);

Promise.all([a, b, c]).then(values => {
  ...
});
```

Promise가 fulfilled가 됐을 때, fulfilment handler 핸들러로 전달된 values 매개변수에는 각 `fetch()` 의 결과로 발생한 세 개의 `Response` 오브젝트가 들어있습니다 .
 하지만 우리는 단순히 결과만 넘겨주고 싶지 않습니다. 우리는`fetch()` 언제 끝나는지 보다 불러온 데이터에 더 관심이 있습니다. 그말은 브라우저에 표현할 수 있는 Blob과 텍스트 문자열이 불러와 졌을 때 `Promise.all()` 블럭을 실행하고 싶다는 것 입니다. `<script>` 엘리먼트에 아래와 같이 작성합니다. :

```js
function fetchAndDecode(url, type) {
  return fetch(url)
    .then((response) => {
      if (type === "blob") {
        return response.blob();
      } else if (type === "text") {
        return response.text();
      }
    })
    .catch((e) => {
      console.log(
        "There has been a problem with your fetch operation: " + e.message,
      );
    });
}
```

살짝 복잡해 보이므로 하나하나 살펴봅시다. :
    1. 먼저 fetchAndDecode() 함수를 정의했고 함수의 매개변수로 컨텐츠의 URL과 가져오는 리소스의 타입을 지정합니다.
    2. 함수 내부에 첫 번째 예에서 본 것과 유사한 구조를 가진 코드가 있습니다. — `fetch()` 함수를 호출하여 전달받은 URL에서 리소스를 받아오도록 했습니다. 그리고 다음 Promise를 연쇄적으로 호출하여 디코딩된 (혹은 "읽은") Response Body를 반환하게 합니다. 이전 예에선 Blob만을 가져오기 때문에 `blob()` 메서드만 썼습니다.
    3. 여기에선 이전과 다른 두 가지가 있습니다. :
        * 먼저 두 번째 Promise에서는 불러올 리소스의 `type` 이 무엇인지에 따라 반환받는 데이터가 다릅니다. executor 함수 내부에, 간단한 `if ... else if` 구문을 사용하여 어떤 종류의 파일을 디코딩해야 하는지에 따라 다른 Promise를 반환하게 했습니다. (이 경우 `blob` 이나 `text`밖에 없지만, 이것을 잘 활용하여 다른 코드에 확장하여 적용할 수 있습니다.).
        * 두 번째로, `fetch()` 호출 앞에 `return` 키워드를 추가했습니다. 이렇게 하면 Promise 체이닝의 마지막 결과값을 함수의 결과로 반환해 줄 수 있습니다. (이 경우 `blob()` 혹은 `text()`메서드에 의해 반환된 Promise 입니다.) 사실상 `fetch()` 앞의 `return` 구문은 체이닝 결과를 다시 상단으로 전달하는 행위 입니다.
    4. 블럭의 마지막에는 `.catch()` 블럭을 추가하여 작업중 발생한 에러를 `.all()`의 배열로 전달합니다. 아무 Promise에서 reject가 발생하면, catch 블럭은 어떤 Promise에서 에러가 발생했는지 알려줄 것 입니다. `.all()` (아래쪽에 있는) 블럭의 리소스에 문제가 있지 않는 이상 항상 fulfil일것 입니다. `.all` 블럭의 마지막 체이닝에 `.catch()` 블럭을 추가하여 reject됐을때 확인을 할 수 있습니다.함수의 body 안에 있는 코드는 비동기적이고 Promise 기반이므로, 전체 함수는 Promise로 작동합니다. — 편리하죠?.
4. 다음으로 fetchAndDecode() 함수를 세 번 호출하여 이미지와 텍스트를 가져오고 디코딩 하는 과정을 시작합니다. 그리고 반환된 Promise를 각각의 변수에 저장합니다. 이전 코드에 이어서 아래 코드를 추가하세요. :

```js
let coffee = fetchAndDecode("coffee.jpg", "blob");
let tea = fetchAndDecode("tea.jpg", "blob");
let description = fetchAndDecode("description.txt", "text");
```

5. 다음으로 위의 세 가지 코드가 모두 fulfilled가 됐을 때 원하는 코드를 실행하기 위해 `Promise.all()` 블럭을 만듭니다. 우선, `.then()` call 안에 비어있는 executor 를 추가하세요 :

```js
Promise.all([coffee, tea, description]).then((values) => {});
```

위에서 Promise를 포함하는 배열을 매개 변수로 사용하는 것을 확인할 수 있습니다. executor는 세 가지 Promise가 resolve될 때만 실행될 것 입니다. 그리고 executor가 실행될 때 개별적인 Promise의 결과를 포함하는 [coffee-results, tea-results, description-results] 배열을 매개 변수로 전달받을 것 입니다. (여기선 디코딩된 Response Body 입니다.).
6. 마지막으로 executor 함수를 작성합니다. 예제에선 반환된 결과를 별도의 변수로 저장하기 위해 간단한 동기화 코드를 사용합니다. (Blob에서 오브젝트 URLs 생성), 그리고 페이지에 텍스트와 이미지를 표시합니다.

```js
console.log(values);
// Store each value returned from the promises in separate variables; create object URLs from the blobs
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];

// Display the images in <img> elements
let image1 = document.createElement("img");
let image2 = document.createElement("img");
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);

// Display the text in a paragraph
let para = document.createElement("p");
para.textContent = descText;
document.body.appendChild(para);
```

7. 코드를 저장하고 창을 새로고치면 보기엔 좋지 않지만, UI 구성 요소가 모두 표시된 것을 볼 수 있습니다.

여기서 제공한 코드는 매우 기초적이지만, 내용을 전달하기에는 아주 좋습니다..

**참고:** If you get stuck, you can compare your version of the code to ours, to see what it is meant to look like — see it live, and see the source code.

**참고:** If you were improving this code, you might want to loop through a list of items to display, fetching and decoding each one, and then loop through the results inside `Promise.all()`, running a different function to display each one depending on what the type of code was. This would make it work for any number of items, not just three.

Also, you could determine what the type of file is being fetched without needing an explicit `type` property. You could, for example, check the `Content-Type` HTTP header of the response in each case using `response.headers.get("content-type")` (en-US), and then react accordingly.

Running some final code after a promise fulfills/rejects
--------------------------------------------------------

Promise의 결과가 fulfilled 인지 rejected인지 관계 없이 Promise가 완료된 후 최종 코드 블럭을 실행하려는 경우가 있을 것입니다. 이전에는 아래 예시처럼 `.then()` 블럭과`.catch()` 블럭의 callbacks에 아래와 같이 runFinalCode()를 넣었었습니다. :

```js
myPromise
  .then((response) => {
    doSomething(response);
    runFinalCode();
  })
  .catch((e) => {
    returnError(e);
    runFinalCode();
  });
```

보다 최근의 현대 브라우저에서는 `.finally()` 메서드를 사용할 수 있습니다. 이 메서드를 Promise 체이닝의 끝에 배치하여 코드 반복을 줄이고 좀 더 우아하게 일을 처리할 수 있습니다. 아래와 같이 마지막 블럭에 적용할 수 있습니다. :

```js
myPromise
  .then((response) => {
    doSomething(response);
  })
  .catch((e) => {
    returnError(e);
  })
  .finally(() => {
    runFinalCode();
  });
```

실제 예시는 promise-finally.html demo 에 나와있습니다. (see the source code also). 이 예시는 위에서 만들어본 `Promise.all()` 데모와 똑같이 작동합니다. 다만 이번에는 `fetchAndDecode()` 함수에 다음 연쇄 작업으로 `finally()` 를 호출합니다.:

```js
function fetchAndDecode(url, type) {
  return fetch(url)
    .then((response) => {
      if (type === "blob") {
        return response.blob();
      } else if (type === "text") {
        return response.text();
      }
    })
    .catch((e) => {
      console.log(
        `There has been a problem with your fetch operation for resource "${url}": ` +
          e.message,
      );
    })
    .finally(() => {
      console.log(`fetch attempt for "${url}" finished.`);
    });
}
```

이 로그는 각 fetch시도가 완료되면 콘솔에 메시지를 출력하여 사용자에게 알려줍니다.

**참고:** `then()`/`catch()`/`finally()` is the async equivalent to `try`/`catch`/`finally` in sync code.

Building your own custom promises
---------------------------------

여기까지 오느라 수고하셨습니다. 여기까지 오면서 우리는 Promise를 직접 만들어봤습니다. 여러 개의 Promise를 `.then()` 을 사용하여 체이닝 하거나 사용자 정의함수를 조합하여, 비동기 Promise기반 함수를 만들었습니다. 이전에 만든 `fetchAndDecode()` 함수가 이를 잘 보여주고있죠.

다양한 Promise 기반 API를 결합하여 사용자 정의 함수를 만드는 것은, Promise와 함께 원하는 기능을 만드는 가장 일반적인 방법이며, 대부분 모던 API는 이와 같은 원리를 기반으로 만들어지고 있습니다. 그리고 또 다른 방법이 있습니다.

### Using the Promise() constructor

`Promise()` constructor를 사용하여 사용자 정의 Promise를 만들 수 있습니다. 주로 Promise기반이 아닌 구식 비동기 API코드를 Promise기반 코드로 만들고 싶을 경우 사용합니다. 이 방법은 구식 프로젝트 코드, 라이브러리, 혹은 프레임워크를 지금의 Promise 코드와 함께 사용할 때 유용합니다.

간단한 예를 들어 살펴보겠습니다. — 여기 Promise와 함께 사용되는 `setTimeout()` 호출이 있습니다. — 이 함수는 2초 후에 "Success!"라는 문자열과 함께 resolve됩니다. (통과된 `resolve()` 호출에 의해);

```js
let timeoutPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success!");
  }, 2000);
});
```

`resolve()` 와`reject()` 는 Promise의 fulful / reject일때의 일을 수행하기 위해 호출한 함수입니다. 이번의 경우 Promise는 "Success!"문자와 함께 fulfill 됐습니다.

따라서 이 Promise를 호출할 때, 그 끝에 `.then()` 블럭을 사용하면 "Success!" 문자열이 전달될 것입니다. 아래 코드는 간단한 alert메시지를 출력하는 방법 입니다. :

```js
timeoutPromise.then((message) => {
  alert(message);
});
```

혹은 아래처럼 쓸 수 있죠

```js
timeoutPromise.then(alert);
```

Try running this live to see the result (also see the source code).

위의 예시는 유연하게 적용된 예시가 아닙니다. — Promise는 항산 하나의 문자열로만 fulfil됩니다. 그리고 `reject()` 조건도 정의되어있지 않습니다. (사실, `setTimeout()` 은 실패 조건이 필요없습니다, 그러니 이 예제에서는 없어도 됩니다.).

**참고:** Why `resolve()`, and not `fulfill()`? The answer we'll give you, for now, is *it's complicated*.

### Rejecting a custom promise

`reject()` 메서드를 사용하여 Promise가 reject상태일 때 전달할 값을 지정할 수 있습니다. — `resolve()`와 똑같습니다. 여기엔 하나의 값만 들어갈 수 있습니다. Promise가 reject 되면 에러는 `.catch()` 블럭으로 전달됩니다.

이전 예시를 좀 더 확장하여 `reject()` 을 추가하고, Promise가 fulfil일 때 다른 메시지도 전달할 수 있게 만들어봅시다.

이전 예시 previous example를 복사한 후 이미 있는 `timeoutPromise()` 함수를 아래처럼 정의해주세요. :

```js
function timeoutPromise(message, interval) {
  return new Promise((resolve, reject) => {
    if (message === "" || typeof message !== "string") {
      reject("Message is empty or not a string");
    } else if (interval < 0 || typeof interval !== "number") {
      reject("Interval is negative or not a number");
    } else {
      setTimeout(function () {
        resolve(message);
      }, interval);
    }
  });
}
```

함수를 살펴보면 두 가지 매개변수가 있습니다. — 출력할 메시지와(message) 메시지를 출력할 때 까지 기다릴 시간(`interval`)입니다. 맨 위에 `Promise` 오브젝트를 반환하도록 되어있습니다. 따라서 함수를 실행하면 우리가 사용하고 싶은 Promise가 반환될 것 입니다..

Promise constructor 안에는 몇가지 사항을 확인하기 위해 `if ... else` 구문이 있습니다. :

1. 첫번째로 메시지의 유효성을 검사합니다. 메시지가 비어있거나 문자가 아닌 경우, 에러 메시지와 함께 Promise를 reject합니다.
2. 그 다음으로 interval의 유효성을 검사합니다. 숫자가 아니거나 음수일 경우, 에러 메시지와 함께 Promise를 reject합니다.
3. 마지막은 항목은, 두 매개변수를 확인하여 유효할 경우 `setTimeout()`함수에 지정된 interval에 맞춰 Promise를 resolve합니다.

`timeoutPromise()` 함수는 `Promise`를 반환하므로, `.then()`, `.catch()`, 기타등등 을 사용해 Promise 체이닝을 만들 수 있습니다. 아래와 같이 작성해봅시다. — 기존에 있는 `timeoutPromise` 를 삭제하고 아래처럼 바꿔주세요. :

```js
timeoutPromise("Hello there!", 1000)
  .then((message) => {
    alert(message);
  })
  .catch((e) => {
    console.log("Error: " + e);
  });
```

이 코드를 저장하고 브라우저를 새로 고침하면 1초 후에 'Hello there!' alert가 출력될 것 입니다. 이제 메시지 내용을 비우거나 interval을 음수로 지정해보세요 그렇게 하면 Promise가 reject되며 에러 메시지를 콘솔에 출력해 줄 것입니다. 또한 resolved 메시지를 다르게 만들어 줄 수도 있습니다.

**참고:** You can find our version of this example on GitHub as custom-promise2.html (see also the source code).

### A more real-world example

위의 예제는 개념을 이해하기 쉽게 단순하게 만들었지만, 실제로 그다지 비동기적이지는 않습니다. 억지로 비동기적 작업을 구현하기 위해 `setTimeout()`을 사용하여 함수를 만들었지만 사용자 정의 Promise를 만들고 에러를 다루기엔 충분한 예제였습니다.

좀 더 공부해볼 추가내용을 소개해주고 싶습니다. 바로 Jake Archibald's idb library입니다 이 라이브러리는 `Promise()` constructor의 비동기작업 응용을 보여주는 유용한 라이브러리 입니다. 클라이언트측에서 데이터를 저장하고 검색하기 위한 구식 callback 기반 API로 Promise와 함께 사용하는 IndexedDB API입니다. main library file을 살펴보면 우리가 지금까지 다뤄본것과 같은 종류의 테크닉을 볼 수 있습니다. 아래 코드 블록은 basic request model이 Promise를 사용하게끔 변환해 주는 IndexedDB 메서드 입니다. :

```js
function promisifyRequest(request) {
  return new Promise(function (resolve, reject) {
    request.onsuccess = function () {
      resolve(request.result);
    };

    request.onerror = function () {
      reject(request.error);
    };
  });
}
```

우리가 했던것 처럼 적절한 타이밍에 Promise를 fulfil하고 reject하는 이벤트 핸들러를 두 개 추가했습니다. :

* `request` (en-US)의 `success` event (en-US)가 실행될 때, `onsuccess` (en-US) 핸들러에 의해 fulfill된 Promise의 request `result` (en-US)를 반환한다.
* 반면 `request` (en-US)'s `error` event (en-US)가 실행되면 `onerror` (en-US) 핸들러에 의해 reject된 Promise의 request `error` (en-US)를 반환한다.
Conclusion
----------

Promises are a good way to build asynchronous applications when we don't know the return value of a function or how long it will take to return. They make it easier to express and reason about sequences of asynchronous operations without deeply nested callbacks, and they support a style of error handling that is similar to the synchronous `try...catch` statement.

Promises work in the latest versions of all modern browsers; the only place where promise support will be a problem is in Opera Mini and IE11 and earlier versions.

We didn't touch on all promise features in this article, just the most interesting and useful ones. As you start to learn more about promises, you'll come across further features and techniques.

Most modern Web APIs are promise-based, so you'll need to understand promises to get the most out of them. Among those APIs are WebRTC, Web Audio API, Media Capture and Streams, and many more. Promises will be more and more important as time goes on, so learning to use and understand them is an important step in learning modern JavaScript.

See also
--------

* `Promise()`
* Using promises
* We have a problem with promises by Nolan Lawson
* 이전
* Overview: Asynchronous
* 다음


Introducing workers - Web 개발 학습하기

Introducing workers
===================

* 이전
* Overview: Asynchronous
* 다음

"비동기 JavaScript" 학습 과정의 마지막 글에서는 별도의 Thread 실행 시 일부 작업을 실행할 수 있는 **workers** 를 소개합니다.

|  |  |
| --- | --- |
| 필요한 사전 지식 | 기본적인 컴퓨터 사용능력, 함수와 이벤트 처리기를 포함한 JavaScript 기초에 대한 이해 |
| 목적 | 웹 워커 사용법을 이해하기 위함 |

이 과정의 첫 번째 글에서 우리는 프로그램이 장기간 진행되는 동기 작업을 수행할 때 창이 완전히 응답하지 않는 것을 보았습니다. 근본적으로 그 이유는 프로그램이 *단일 스레드* 이기 때문입니다. *스레드* 는 프로그램이 따르는 일련의 명령입니다. 이 프로그램이 단일 스레드로 구성되어 있어서 한 번에 한 가지 작업만 수행할 수 있습니다. 따라서 장기간 실행 중인 동기 호출이 반환되기를 기다리고 있으면 다른 작업을 수행할 수 없습니다.

워커는 다른 스레드에서 어떤 작업을 실행할 수 있는 기능을 제공하므로 이 작업을 시작하고 다른 처리(예: 사용자 작업 처리)를 계속할 수 있습니다.

하지만 이것에 대한 대가가 있습니다. 다중 스레드 코드를 사용하면 스레드가 언제 중단되고 다른 스레드가 언제 실행될지 절대 알 수 없습니다. 따라서 두 스레드가 같은 변수에 접근할 수 있는 경우 변수가 언제든지 예기치 않게 변경될 수 있으며, 이에 따라 찾기 어려운 버그가 발생합니다.

웹에서는 이러한 문제를 방지하기 위해 메인 코드와 워커 코드가 서로의 변수에 직접 접근할 수 없습니다. 워커와 메인 코드는 완전히 별개의 세계에서 실행되며, 서로 메시지를 보내야만 상호 작용할 수 있습니다. 특히, 워커는 DOM(window, document, 페이지 요소 등)에 액세스할 수 없습니다.

워커에는 세 가지 유형이 있습니다.

* dedicated workers
* shared workers
* service workers

이 글에서는 첫 번째 워커의 예를 살펴본 후 나머지 두 가지 워커는 간략히 살펴보겠습니다.

웹 워커 사용하기
---------

첫 번째 글에서 소수를 계산하는 페이지가 있던 것을 기억하시나요? 워커를 사용하여 소수 계산을 실행할 것이므로 사용자에 대한 페이지 응답성을 유지할 수 있습니다.

### 동기 소수 생성기

먼저 이전 예제의 JavaScript를 다시 살펴봅시다.

```js
function generatePrimes(quota) {
  function isPrime(n) {
    for (let c = 2; c <= Math.sqrt(n); ++c) {
      if (n % c === 0) {
        return false;
      }
    }
    return true;
  }

  const primes = [];
  const maximum = 1000000;

  while (primes.length < quota) {
    const candidate = Math.floor(Math.random() \* (maximum + 1));
    if (isPrime(candidate)) {
      primes.push(candidate);
    }
  }

  return primes;
}

document.querySelector("#generate").addEventListener("click", () => {
  const quota = document.querySelector("#quota").value;
  const primes = generatePrimes(quota);
  document.querySelector("#output").textContent =
    `Finished generating ${quota} primes!`;
});

document.querySelector("#reload").addEventListener("click", () => {
  document.querySelector("#user-input").value =
    'Try typing in here immediately after pressing "Generate primes"';
  document.location.reload();
});
```

이 프로그램에서는 `generatePrimes()`를 호출한 후 프로그램이 전혀 응답하지 않습니다.

### 워커를 이용한 소수 생성기

이 예제를 위해 먼저 https://github.com/mdn/learning-area/blob/main/javascript/asynchronous/workers/start를 로컬에 복사해보겠습니다. 이 디렉토리에는 4개의 파일이 있습니다.

* index.html
* style.css
* main.js
* generate.js

"index.html" 파일과 "style.css" 파일은 이미 완성되어있습니다.

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <script type="text/javascript" src="main.js" defer></script>
    <link href="style.css" rel="stylesheet" />
  </head>

  <body>
    <label for="quota">Number of primes:</label>
    <input type="text" id="quota" name="quota" value="1000000" />

    <button id="generate">Generate primes</button>
    <button id="reload">Reload</button>

    <textarea id="user-input" rows="5" cols="62">
Try typing in here immediately after pressing "Generate primes"</textarea
 >

    <div id="output"></div>
  </body>
</html>
```

```css
textarea {
  display: block;
  margin: 1rem 0;
}
```

"main.js"와 "generate.js" 파일은 비어 있습니다. 우리는 메인 코드를 "main.js"에 추가하고 워커 코드를 "generate.js"에 추가할 것입니다.

이 설명을 통해 워커 코드가 메인 코드와는 별도로 유지됨을 알 수 있습니다. 위의 "index.html"을 보면 `<script>` 요소엔 메인 코드만 포함되어 있음을 알 수 있습니다.

이제 다음 코드를 "main.js"에 복사해보세요.

```js
// Create a new worker, giving it the code in "generate.js"
const worker = new Worker("./generate.js");

// When the user clicks "Generate primes", send a message to the worker.
// The message command is "generate", and the message also contains "quota",
// which is the number of primes to generate.
document.querySelector("#generate").addEventListener("click", () => {
  const quota = document.querySelector("#quota").value;
  worker.postMessage({
    command: "generate",
    quota: quota,
  });
});

// When the worker sends a message back to the main thread,
// update the output box with a message for the user, including the number of
// primes that were generated, taken from the message data.
worker.addEventListener("message", (message) => {
  document.querySelector("#output").textContent =
    `Finished generating ${message.data} primes!`;
});

document.querySelector("#reload").addEventListener("click", () => {
  document.querySelector("#user-input").value =
    'Try typing in here immediately after pressing "Generate primes"';
  document.location.reload();
});
```

* 먼저 `Worker()` (en-US) 생성자를 사용하여 워커를 만듭니다. 이 워커에 워커 스크립트를 가리키는 URL을 전달합니다. 워커가 생성되자마자 워커 스크립트가 실행됩니다.
* 그런 다음 동기화 버전과 마찬가지로 "Generate primes" 버튼에 `click` 이벤트 처리기를 추가합니다. 그러나 이제 `generatePrimes()` 함수를 호출하는 대신 `worker.postMessage()` (en-US)를 사용하여 워커에게 메시지를 보냅니다. 이 메시지는 인수를 받을 수 있으며, 인수를 받은 경우 다음 두 가지 속성을 포함하는 JSON 개체를 전달합니다.
    + `command`: 워커가 수행할 작업을 식별하는 문자열입니다.(워커가 둘 이상의 작업을 수행할 수 있는 경우)
    + `quota`: 생성할 소수의 개수입니다.
* 다음으로, 워커에게 `message` 이벤트 처리기를 추가합니다. 이것은 워커가 작업이 완료되면 알려주고 결과 데이터를 전달할 수 있도록 하기 위함입니다. 처리기는 메시지의 `data` 속성에서 데이터를 가져와 출력 요소에 씁니다(이 데이터는 `quota`와 정확히 일치하므로 다소 무의미하지만 원리를 보여줍니다).
* 마지막으로 "Reload" 버튼에 대한 `click` 이벤트 처리기를 구현합니다. 이것은 동기 버전과 정확히 같습니다.

이제 워커 코드를 보겠습니다. 다음 코드를 "generate.js"에 복사해보세요.

```js
// Listen for messages from the main thread.
// If the message command is "generate", call `generatePrimes()`
addEventListener("message", (message) => {
  if (message.data.command === "generate") {
    generatePrimes(message.data.quota);
  }
});

// Generate primes (very inefficiently)
function generatePrimes(quota) {
  function isPrime(n) {
    for (let c = 2; c <= Math.sqrt(n); ++c) {
      if (n % c === 0) {
        return false;
      }
    }
    return true;
  }

  const primes = [];
  const maximum = 1000000;

  while (primes.length < quota) {
    const candidate = Math.floor(Math.random() \* (maximum + 1));
    if (isPrime(candidate)) {
      primes.push(candidate);
    }
  }

  // When we have finished, send a message to the main thread,
  // including the number of primes we generated.
  postMessage(primes.length);
}
```

메인 스크립트가 워커를 만들자마자 이 코드를 실행한다는 것을 기억하세요.

워커가 가장 먼저 하는 일은 메인 스크립트의 메시지 수신을 기다리는 것입니다. 워커는 워커의 전역 함수인 `addEventListener()`를 사용하여 이 작업을 수행합니다. `message` 이벤트 처리기 속에서 이벤트의 `data` 속성은 메인 스크립트에서 전달된 인수의 복사본이 들어있습니다. 메인 스크립트가 `generate` 명령을 전달하면 메시지 이벤트에서 `generatePrimes()`를 `quota`를 전달하며 호출합니다.

`generatePrimes()` 함수는 동기 버전과 비슷하지만, 값을 반환하는 대신 작업이 끝나면 메인 스크립트로 메시지를 보냅니다. 이를 위해 `postMessage()` (en-US) 함수를 사용합니다. 이는 `addEventListener()`와 같이 워커의 전역 함수입니다. 이미 살펴본 바와 같이, 메인 스크립트는 이 메시지를 수신하고 있으며 메시지가 수신되면 DOM을 업데이트합니다.

**참고:** 이 사이트를 실행하려면 로컬 웹 서버를 실행해야 합니다. file:// URL로는 워커를 로드할 수 없기 때문입니다. 테스트 로컬 서버 설정 가이드를 참조하시기 바랍니다. 이렇게 하면 "Generate primes"를 눌러 기본 페이지를 응답할 수 있는 상태로 유지할 수 있습니다.

예제를 만들거나 실행하는 데 문제가 있는 경우 https://github.com/mdn/learning-area/blob/main/javascript/asynchronous/workers/finished에서 완성된 버전을 확인하고 https://mdn.github.io/learning-area/javascript/asynchronous/workers/finished 에서 라이브로 사용해 볼 수 있습니다.

다른 종류의 워커
---------

우리가 방금 만든 워커가 *dedicated worker* 입니다. 단일 스크립트 인스턴스에서 사용됩니다.

다른 유형의 워커도 있습니다.

* *Shared workers*는 서로 다른 창에서 실행되는 여러 스크립트에서 공유될 수 있습니다.
* *Service workers*는 사용자가 오프라인 상태일 때 웹 애플리케이션이 작동할 수 있도록 리소스를 캐싱하는, 마치 프록시 서버처럼 작동합니다. Progressive Web Apps의 핵심 구성 요소입니다.
결론
--

이 글에서는 웹 애플리케이션이 작업을 별도의 스레드로 넘길 수 있도록 하는 웹 워커를 소개했습니다. 메인 스레드와 워커는 변수를 직접 공유하지 않고, 상대방이 `message` 이벤트로 수신하는 메시지로 소통합니다.

비록 워커가 메인 애플리케이션이 액세스할 수 있는 모든 API, 특히 DOM에 액세스할 수는 없지만, 메인 애플리케이션의 응답성을 유지하는데는 효과적인 방법이 될 수 있습니다.

같이 보기
-----

* Using web workers (en-US)
* Using service workers (en-US)
* Web workers API
* 이전
* Overview: Asynchronous
* 다음


배열(Arrays) - Web 개발 학습하기

배열(Arrays)
==========

* 이전
* Overview: First steps
* 다음

이 모듈의 이 마지막 문서에서, 우리는 배열을 살펴볼 것입니다. 배열이란 하나의 변수명 아래에 데이터 아이템의 리스트를 저장하는 간편한 방법입니다. 여기서 우리는 왜 배열이 유용한지 보고, 어떻게 배열을 만들고, 검색하고(retrieve), 추가하고, 그리고 배열에 저장된 원소들(items)을 삭제하는지, 그리고 그 외의 많은 것을 탐구합니다.

|  |  |
| --- | --- |
| 선행요소: | 기초 컴퓨터 활용지식, HTML, CSS 그리고 JavaScript에 대한 기초 지식. |
| 목적: | 배열이 무엇인지 이해하고 JavaScript에서 어떻게 활용하는지 배운다. |

배열이란?
-----

배열이란 일반적으로 "리스트같은 객체(list-like objects)"라고 기술됩니다. 배열은 보통 리스트에 저장된 다수의 값들을 포함하고 있는 하나의 객체입니다. 배열 객체는 변수에 저장될 수 있고 다른 종류의 값들처럼 훨씬 같은 방법으로 다뤄질 수 있는데, 차이는 우리는 배열 안의 각 값들에 개별적으로 접근할 수 있고, 리스트를 통해 반복(loop)하고 모든 값에 동일한 것을 할 수 있는 것과 같이, 리스트와 함께 엄청 유용하고 효율적인 것들을 할 수 있다는 것입니다. 아마도 우리는 배열에 저장된 상품명과 가격을 가지고 있고, 그 값들을 통해 반복하고, 모든 가격을 하나로 합하고 총액을 청구서의 하단에 출력하고 싶을지도 모릅니다.

만약 배열이 없다면 모든 원소를 별도의 변수에 저장해야 할 것이고, 각 원소에 대해 별도로 출력하고 더하는 코드를 불러야 할 것입니다. 이런 방식은 작성하기에 훨씬 오래 걸리고, 덜 효율적이며, 오류가 나기 쉽습니다. 만약 우리가 청구서에 더할 10개의 물품을 가지고 있다면 이것만으로도 이미 짜증나는데, 100개의 물품, 혹은 1000개의 물품이라면 어떨까요? 우리는 이 문서에서 나중에 이 예시로 돌아올 것입니다.

지난 문서에서처럼, 브라우저 개발자 콘솔에 몇몇 예제를 입력함으로써 배열의 진짜 기초에 대해 배워 봅시다. (아래의 콘솔 창을 사용하거나, 당신이 선호하는 개발자 콘솔 (en-US)을 사용하면 됩니다.)
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>JavaScript console</title>
    <style>
 \* {
 box-sizing: border-box;
 }

 html {
 background-color: #0c323d;
 color: #809089;
 font-family: monospace;
 }

 body {
 max-width: 700px;
 }

 p {
 margin: 0;
 width: 1%;
 padding: 0 1%;
 font-size: 16px;
 line-height: 1.5;
 float: left;
 }

 .input p {
 margin-right: 1%;
 }

 .output p {
 width: 100%;
 }

 .input input {
 width: 96%;
 float: left;
 border: none;
 font-size: 16px;
 line-height: 1.5;
 font-family: monospace;
 padding: 0;
 background: #0c323d;
 color: #809089;
 }

 div {
 clear: both;
 }
 </style>
  </head>
  <body></body>

  <script>
 var geval = eval;
 function createInput() {
 var inputDiv = document.createElement("div");
 var inputPara = document.createElement("p");
 var inputForm = document.createElement("input");

 inputDiv.setAttribute("class", "input");
 inputPara.textContent = ">";
 inputDiv.appendChild(inputPara);
 inputDiv.appendChild(inputForm);
 document.body.appendChild(inputDiv);

 if (document.querySelectorAll("div").length > 1) {
 inputForm.focus();
 }

 inputForm.addEventListener("change", executeCode);
 }

 function executeCode(e) {
 try {
 var result = geval(e.target.value);
 } catch (e) {
 var result = "error — " + e.message;
 }

 var outputDiv = document.createElement("div");
 var outputPara = document.createElement("p");

 outputDiv.setAttribute("class", "output");
 outputPara.textContent = "Result: " + result;
 outputDiv.appendChild(outputPara);
 document.body.appendChild(outputDiv);

 e.target.disabled = true;
 e.target.parentNode.style.opacity = "0.5";

 createInput();
 }

 createInput();
 </script>
</html>
```

### 배열 만들기

배열은 대괄호로 구성되며 쉼표로 구분 된 항목들을 포함합니다.

1. 쇼핑 목록을 배열에 저장하고 싶다면 다음과 같이 하면 됩니다. 콘솔에 다음 행을 입력하십시오.

```js
var shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
shopping;
```

2. 위의 예제에서, 각 원소는 문자열이지만, 우리는 다양한 데이터 유형을 배열에 저장할 수 있습니다 (문자열, 숫자, 개체, 다른 변수, 심지어 다른 배열). 동일한 형태의 항목만 넣거나 (아래 sequence 처럼) 다양한 형태의 항목을 함께 넣을 수 (아래 random 처럼) 있습니다. 모두 숫자, 문자열 등일 필요는 없습니다. 다음을 입력해보세요.

```js
var sequence = [1, 1, 2, 3, 5, 8, 13];
var random = ["tree", 795, [0, 1, 2]];
```

3. 다음으로 넘어가기 전 여러분 마음대로 배열을 만들어 보세요.
### 배열 항목의 접근과 수정

그런 다음 문자열의 문자에 접근했던 것과 같은 방법으로 괄호 표기법을 사용하여 배열의 개별 항목에 접근 할 수 있습니다.

1. 콘솔에 다음을 입력하세요:

```js
shopping[0];
// returns "bread"
```

2. 단일 배열 항목에 새 값을 제공하여, 배열의 항목을 수정할 수도 있습니다. 한번 해보세요:

```js
shopping[0] = "tahini";
shopping;
// shopping will now return [ "tahini", "milk", "cheese", "hummus", "noodles" ]
```

**참고**: 전에도 말했지만, 컴퓨터는 숫자를 셀 때 0부터 시작합니다!
3. 배열 내부의 배열을 다중배열(multidimensional array)이라고 합니다. 대괄호 두개를 함께 연결하여 다른 배열 안에있는 배열 내부의 항목에 접근 할 수 있습니다.예를 들어 `random` 배열 내부의 세 번째 원소에 접근하려면(앞 섹션 참조) 다음과 같이 할 수 있습니다:

```js
random[2][2];
```

4. 다음 단계로 넘어가기 전에 충분히 배열 예제를 연습해 보세요.
### 배열의 갯수 알아내기

`length` 속성을 사용해서, 문자열의 (문자의) 길이를 알아낸 것과 정확히 같은 방식으로 (얼마나 많은 원소가 배열에 들어 있는지) 배열의 길이를 알아낼 수 있습니다. 다음을 봅시다.:

```js
sequence.length;
// should return 7
```

이것은 다른 사용법을 가지고 있지만, 이것은 가장 흔히 반복문이 배열의 모든 원소를 거치기 전까지 계속하라고 말하는 데 쓰입니다. 다음이 예시입니다.

```js
var sequence = [1, 1, 2, 3, 5, 8, 13];
for (var i = 0; i < sequence.length; i++) {
  console.log(sequence[i]);
}
```

다음 문서에서 반복문에 대해서 자세히 다루겠지만, 간단히 말하자면, 이 코드는 다음을 말하고 있습니다.

1. 배열의 원소 번호 0에서 반복을 시작합니다.
2. 배열의 길이와 같은 원소 번호에서 반복을 멈춥니다. 이것은 어떠한 길이의 배열에서도 작동하지만, 이 경우에서는 원소 번호 7에서 반복을 멈춥니다 (마지막 원소(우리가 반복문이 포함하기를 원하는)는 번호 6이므로, 이것은 좋습니다.)
3. 각 원소에 대해, `console.log()`로 브라우저 콘솔에 출력합니다.
유용한 배열 메서드
----------

이번 섹션에서는 문자열을 배열 원소로 나누고 그 역으로도 할 수 있게 하는, 그리고 새로운 원소들을 배열에 추가할 수 있게 하는 몇몇 꽤 유용한 배열에 관련된 메서드들을 살펴볼 것입니다.

### 문자열을 배열로, 배열을 문자열로 변환하기

프로그램을 만들다보면 종종 긴 문자열로 이루어진 원시 데이터를 제공받게 될 것이고, 원시 데이터를 정제하여 더 유용한 데이터를 추출해 테이블 형태로 표시하는 등 작업을 수행해야 합니다. 이러한 작업을 위해 `split()` 메서드를 사용할 수 있습니다. `split()` 메서드는 사용자가 원하는 매개변수로 문자열을 분리하여 배열로 표현해줍니다.

**참고:** 사실 엄밀히 따지면 문자열 메서드지만, 배열과 함께 사용하기 때문에 여기에 넣었습니다.

1. `split()` 메서드가 어떻게 작동하는지 살펴봅시다. 우선 콘솔에서 아래와 같은 문자열을 만듭니다:

```js
var myData = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle";
```

2. 콤마로 분리해 봅시다:

```js
var myArray = myData.split(",");
myArray;
```

3. 마지막으로 새로운 배열의 길이를 찾고 이 배열에서 몇 가지 항목을 검색해 봅시다:

```js
myArray.length;
myArray[0]; // the first item in the array
myArray[1]; // the second item in the array
myArray[myArray.length - 1]; // the last item in the array
```

4. 또한 아래 방법처럼 `join()` 메서드를 사용하여 배열을 다시 문자열로 만들 수 있습니다:

```js
var myNewString = myArray.join(",");
myNewString;
```

5. 배열을 문자열로 변환하는 또 다른 방법은 `toString()` 메서드를 사용하는 것입니다. `toString()` 은 `join()` 과 달리 매개변수가 필요없어서 더 간단하지만, 더 많은 제한이 있습니다. `join()` 을 사용하면 다른 구분자를 지정할 수 있지만, `toString()` 은 항상 콤마를 사용합니다. (콤마 말고 다른 매개변수를 사용하여 4단계를 실행해 보세요.)

```js
var dogNames = ["Rocket", "Flash", "Bella", "Slugger"];
dogNames.toString(); //Rocket,Flash,Bella,Slugger
```

### 배열에 원소를 추가하고 제거하기

이번엔 배열에 원소를 추가하고 제거하는 방법을 알아볼 차례입니다. 위에서 만든 `myArray` 를 다시 사용하겠습니다. 섹션을 순서대로 진행하지 않았다면 아래와 같은 배열을 만들어주세요:

```js
var myArray = [
  "Manchester",
  "London",
  "Liverpool",
  "Birmingham",
  "Leeds",
  "Carlisle",
];
```

먼저, 배열의 맨 끝에 원소를 추가하거나 제거하기 위해 각각 `push()` and `pop()` 를 사용할 수 있습니다.

1. 먼저 `push()` 를 사용합니다. — 배열의 끝에 추가할 원소를 반드시 하나 이상 포함해야 한다는 점을 기억하고 아래와 같이 따라해보세요:

```js
myArray.push("Cardiff");
myArray;
myArray.push("Bradford", "Brighton");
myArray;
```

2. 메서드 호출이 완료되면 배열의 새 길이가 리턴됩니다. 만약 새 배열의 길이를 변수에 저장하고 싶다면, 다음과 같이 할 수 있습니다:

```js
var newLength = myArray.push("Bristol");
myArray;
newLength;
```

3. 배열의 마지막 원소를 제거하는 방법은 `pop()`으로 매우 간단합니다. 아래와 같이 따라해 보세요:

```js
myArray.pop();
```

4. 메서드 호출이 완료되면 제거된 원소가 리턴됩니다. 이 원소를 새 변수에 저장하기 위해서, 다음과 같이 할 수 있습니다:

```js
var removedItem = myArray.pop();
myArray;
removedItem;
```

`unshift()` 와`shift()`는 `push()` 와 `pop()`과 완전히 동일하게 동작합니다. 다만, 배열의 맨 끝이 아닌 제일 처음 부분의 원소를 추가하거나 제거합니다.

1. 먼저 `unshift()` 를 사용해봅시다:

```js
myArray.unshift("Edinburgh");
myArray;
```

2. 이제 `shift()`를 사용해봅시다!

```js
var removedItem = myArray.shift();
myArray;
removedItem;
```

활동적 학습: 상품들 출력하기!
-----------------

앞에서 설명했던 예제로 돌아가 봅시다. 상품의 이름과 가격을 청구서에 출력하고, 가격의 합계를 내서 그것을 밑에 출력합니다. 아래의 수정 가능한 예제에, 숫자를 포함하고 있는 주석이 있습니다. 각 줄은 코드에 추가할 장소를 표시하고 있습니다. 그것들은 다음과 같습니다.

1. 아래의 `// number 1` 주석은 몇 개의 문자열인데, 각 문자열은 상품의 이름과 가격을 포함하고 있고 콜론에 의해 분리되어 있습니다. 우리는 당신이 이것을 `products`라는 배열으로 바꾸고 이곳에 이것을 저장하기를 원합니다.
2. `// number 2` 주석이 있는 같은 라인에서는 for 반복문이 시작됩니다. 이 라인에서 우리는 현재 `i <= 0`를 가지고 있는데, 이 조건 테스트(conditional test)는 "`i`가 더 이상 0보다 작거나 같지 않을 때 멈추시오"라고 말하고 있고, `i`는 0에서 시작하기 때문에, 이는 for 반복문이 즉시 멈추는 것을 유발하는 조건 테스트입니다. 우리는 당신이 이것을 `i`가 `products`배열의 길이(length)보다 더 이상 작지 않을 때 반복을 멈추게 하는 조건 테스트로 바꾸기를 원합니다.
3. `// number 3` 주석 바로 아래에서 우리는 당신이 현재 배열의 원소를, 한 개는 단순히 이름을 포함하고 한 개는 단순히 가격을 포함하는 두 개의 원소로 분리하는 한 줄의 코드를 작성하기를 원합니다. 만약 당신이 이것을 어떻게 하는지 확실히 알지 못한다면, 유용한 문자열 메서드(Useful string methods)를 도움을 위해 참고하거나, 더 나은 정보를 원한다면, 이 글의 문자열과 배열 전환하기 부분을 참고하세요.
4. 상기 코드 라인의 부분으로써, 당신은 또한 가격을 문자열에서 숫자로 전환하기를 원할 것입니다. 만약 당신이 어떻게 이것을 하는지 기억하지 못한다면, 첫번째 문자열 문서를 살펴보세요.
5. 만들어지고 0의 값이 주어진 `total`이라는 변수가 코드의 위쪽에 있습니다. 이 반복문 안에서 (`// number 4` 아래) 우리는 당신이 현재 상품의 가격을 반복문의 각 반복마다 총액(total)에 합하는 코드 한 줄을 추가해서, 코드의 끝에서 올바른 총액이 청구서에 인쇄되도록 하기를 원합니다. 당신은 이것을 하기 위해 대입 연산자(assignment operator)가 필요할지도 모릅니다.
6. `itemText` 변수가 "current item name — $current item price"와 같이 만들어지도록, 예를 들자면 각각의 경우에 "Shoes — $23.99" 처럼 만들어지도록 우리는 당신이 `// number 5` 바로 아래의 줄을 변경해서, 각각의 물품에 대한 올바른 정보가 청구서에 출력되도록 하기를 원합니다. 이것은, 당신에게 친숙할지도 모르는, 단지 단순한 문자열 연결(concatenation)입니다.
```

<h2>Live output</h2>

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

  <p></p>
</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: 410px;width: 95%">
var list = document.querySelector('.output ul');
var totalBox = document.querySelector('.output p');
var total = 0;
list.innerHTML = '';
totalBox.textContent = '';
// number 1
'Underpants:6.99'
'Socks:5.99'
'T-shirt:14.99'
'Trousers:31.99'
'Shoes:23.99';

for (var i = 0; i <= 0; i++) { // number 2
// number 3

// number 4

// number 5
itemText = 0;

var listItem = document.createElement('li');
listItem.textContent = itemText;
list.appendChild(listItem);
}

totalBox.textContent = 'Total: $' + total.toFixed(2);
</textarea>

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

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;
var 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();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nvar totalBox = document.querySelector('.output p');\nvar total = 0;\nlist.innerHTML = '';\ntotalBox.textContent = '';\n\nvar products = ['Underpants:6.99',\n 'Socks:5.99',\n 'T-shirt:14.99',\n 'Trousers:31.99',\n 'Shoes:23.99'];\n\nfor(var i = 0; i < products.length; i++) {\n var subArray = products[i].split(':');\n var name = subArray[0];\n var price = Number(subArray[1]);\n total += price;\n itemText = name + ' — $' + price;\n\n var listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n}\n\ntotalBox.textContent = 'Total: $' + total.toFixed(2);";
var 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) {
  var scrollPos = textarea.scrollTop;
  var caretPos = textarea.selectionStart;

  var front = textarea.value.substring(0, caretPos);
  var 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();
};
```

```
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-color: #f5f9fa;
}
```

실습: 상위 5개의 검색어
--------------

`push()` 와 `pop()`같은 배열 메서드의 좋은 사용은 웹 앱(web app)에서 현재 활동적인 원소(item)의 기록을 유지할 때입니다. 예를 들자면 동영상으로 된 장면에서, 당신은 현재 보이는 배경을 나타내는 객체들의 배열을 가지고 있고 성능이나 어수선함의 이유로 오직 한번에 50개만이 보여지기를 원할지도 모릅니다. 새로운 객체가 생성되고 배열에 추가되며, 원하는 수를 유지하기 위해, 오래된 것들은 배열에서 삭제될 수 있습니다.

이 예제에서 우리는 더욱 단순한 사용을 보일 것입니다. 여기서 우리는 당신에게 검색 상자가 있는 가짜 검색 사이트를 제공합니다. 개념은 용어가 검색 상자에 입력되었을 때, 상위 5개의 이전 검색 용어들이 목록에 보이는 것입니다. 용어의 수가 5를 넘어갈 때, 마지막 용어는 새로운 용어가 위에 추가될 때마다 지워지기 시작해서, 5개의 이전 용어가 계속 보입니다.

**참고:** 실제 검색 앱에서는, 당신은 아마도 이전 검색으로 돌아가기 위해 이전 검색 용어를 클릭할 수 있고, 이것은 실제 검색 결과를 보여줄 것입니다! 우리는 단지 우선은 단순하게 하고 있습니다.

앱을 완성하기 위해, 우리는 당신이 다음을 하기를 필요로 합니다.

1. `// number 1` 주석 밑에 배열의 시작에 검색 입력(input)에 입력된 현재 값을 추가하는 코드를 추가하세요. 이것은 `searchInput.value`를 사용하여 탐색될 수 있습니다.
2. `// number 2` 주석 밑에 현재 배열의 끝에 있는 값을 제거하는 코드를 추가하세요.

```html
<h2>Live output</h2>
<div class="output" style="min-height: 150px;">
  <input type="text" /><button>Search</button>

  <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: 370px; width: 95%">
var list = document.querySelector('.output ul');
var searchInput = document.querySelector('.output input');
var searchBtn = document.querySelector('.output button');

list.innerHTML = '';

var myHistory = [];

searchBtn.onclick = function() {
// we will only allow a term to be entered if the search input isn't empty
if (searchInput.value !== '') {
// number 1

    // empty the list so that we don't display duplicate entries
    // the display is regenerated every time a search term is entered.
    list.innerHTML = '';

    // loop through the array, and display all the search terms in the list
    for (var i = 0; i < myHistory.length; i++) {
      itemText = myHistory[i];
      var listItem = document.createElement('li');
      listItem.textContent = itemText;
      list.appendChild(listItem);
    }

    // If the array length is 5 or more, remove the oldest search term
    if (myHistory.length >= 5) {
      // number 2

    }

    // empty the search input and focus it, ready for the next term to be entered
    searchInput.value = '';
    searchInput.focus();

}
}
</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;
}
```

```
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var solution = document.getElementById("solution");
var code = textarea.value;
var 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();
});

var jsSolution =
  "var list = document.querySelector('.output ul');\nvar searchInput = document.querySelector('.output input');\nvar searchBtn = document.querySelector('.output button');\n\nlist.innerHTML = '';\n\nvar myHistory= [];\n\nsearchBtn.onclick = function() {\n if(searchInput.value !== '') {\n myHistory.unshift(searchInput.value);\n\n list.innerHTML = '';\n\n for(var i = 0; i < myHistory.length; i++) {\n itemText = myHistory[i];\n var listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n }\n\n if(myHistory.length >= 5) {\n myHistory.pop();\n }\n\n searchInput.value = '';\n searchInput.focus();\n }\n}";
var 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) {
  var scrollPos = textarea.scrollTop;
  var caretPos = textarea.selectionStart;

  var front = textarea.value.substring(0, caretPos);
  var 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();
};
```

당신의 기량을 시험해 보세요!
----------------

당신은 이 문서의 끝에 도달했지만, 가장 중요한 정보들을 기억할 수 있으신가요? 당신은 나아가기 전에 이 정보들을 보유하고 있다는 것을 확인하는 추가적인 테스트들을 찾을 수 있습니다 — 다음을 보세요: Test your skills: Arrays (en-US)

결론
--

이 문서를 쭉 읽으셨다면 배열이 꽤 유용해 보인다는 것에 동의할 겁니다. 배열은 종종 모든 원소에 동일한 것을 하기 위해 반복문과 함께 JavaScript의 모든 곳에서 찾아볼 수 있습니다. 다음 모듈에서 반복문에 대해 알기 위해 있는 모든 유용한 기본을 가르칠 것이지만, 우선 스스로에게 박수를 보내고 휴식을 취하세요. 이 모듈의 모든 문서를 독파하셨으니 잠깐 쉬어가도 좋습니다!

유일한 남은 것은 이 모듈의 평가인데, 이는 이 평가 이전에 오는 문서들에 대한 당신의 이해를 테스트할 것입니다.

참고
--

* Indexed collections — 배열과 배열의 친척인 typed arrays에 대한 고급 단계 가이드
* `Array` — `Array` 객체 참조 페이지 — 이 페이지에서 다뤄진 기능들과 더 많은 것들에 대한 자세한 참조 가이드
* 이전
* Overview: First steps
* 다음


당신의 기량을 시험해 보세요: 변수(variables) - Web 개발 학습하기

당신의 기량을 시험해 보세요: 변수(variables)
==============================

이 기량 테스트의 목적은 당신이 우리의 필요한 정보를 저장하기 — 변수 문서를 이해했는지를 평가하기 위함입니다.

**참고:** You can try out solutions in the interactive editors below, however it may be helpful to download the code and use an online tool such as CodePen, jsFiddle, or Glitch to work on the tasks.

If you get stuck, then ask us for help — see the Assessment or further help section at the bottom of this page.

**참고:** 아래에 있는 예제들에서, 답을 알아내기 위해 시도하는 것을 돕기 위해 만약 당신의 코드에 오류가 있으면, 페이지의 결과 패널에 출력될 것입니다 (또는 다운로드 버전의 경우, 브라우저의 JavaScript 콘솔에 출력될 것입니다).

변수 1
----

이 과제에서 우리는 당신이 다음을 하기를 원합니다:

* `myName` 이라는 이름의 변수 선언(declare)하기.
* 분리된 라인에, `myName` 을 적절한 값으로 초기화(initialize)하기 (당신은 당신의 실제 이름이나, 혹은 무언가 다른 것을 사용할 수 있습니다).
* 같은 줄에, `myAge` 라는 변수를 선언하고 값을 주어 초기화하기.

완료된 예제를 만들기 위해 아래의 live code를 업데이트 해 보세요:

Download the starting point for this task to work in your own editor or in an online editor.

변수 2
----

이 과제에서 당신은 기존의 `myName` 변수에 저장된 값을 당신의 이름으로 정정하기 위해 새로운 줄을 추가할 필요가 있습니다.

완료된 예제를 만들기 위해 아래의 live code를 업데이트 해 보세요:

Download the starting point for this task to work in your own editor or in an online editor.

변수 3
----

이제 마지막 과제입니다. 여기서 당신은 기존의 몇몇 코드를 가지고 있는데, 여기에는 현재 두 개의 오류가 있습니다. 결과 패널은 `Chris` 라는 이름과, 20년 후 Chris는 몇 살일지에 대한 문장을 출력해야만 합니다. 당신은 어떻게 문제를 고치고 출력을 정정하실 건가요?

완료된 예제를 만들기 위해 아래의 live code를 업데이트 해 보세요:

Download the starting point for this task to work in your own editor or in an online editor.

평가 혹은 추가적인 도움
-------------

당신은 이 예제들을 위에 있는 대화형 에디터(Interactive Editor)에서 연습할 수 있습니다.

만약 당신이 작업을 평가받고 싶거나, 막혀서 도움을 청하기를 원한다면:

1. 당신의 작업을 CodePen, jsFiddle, 또는 Glitch 같은 온라인에서 공유 가능한 에디터에 올리세요. 당신은 직접 코드를 작성하거나, 위 섹션들에 링크된 시작점 파일을 사용할 수 있습니다.
2. MDN Discourse forum Learning category 에 평가 및 또는 도움을 요청하는 글을 작성하세요. 당신의 글은 다음을 포함해야만 합니다.
    * "변수 1 기량 테스트에 대한 평가 원함"과 같은 서술적인 제목.
    * 당신이 이미 무엇을 시도해 봤는지, 그리고 우리가 무엇을 하기를 원하는지에 대한 설명, 예를 들자면, 만약 당신이 막혀서 도움이 필요하거나, 평가를 원하거나.
    * (위의 단계 1에서 언급된) 온라인에서 공유 가능한 에디터에 있는, 당신이 평가를 원하거나 도움이 필요한 예제의 링크. 이것은 익숙해지기에 좋은 실천입니다. 코딩 문제가 있는 누군가를 도울 때, 그들의 코드를 보지 못한다면 그들을 돕는 것은 몹시 힘듭니다.
    * 우리가 당신이 도움을 원하는 문제를 찾을 수 있도록, 실제 과제나 평가 페이지의 링크.


Silly story generator - Web 개발 학습하기

Silly story generator
=====================

* 이전
* Overview: First steps

이 모듈에서 배운 지식들을 바탕으로 랜덤하게 꾸며진 이야기(silly stories)가 만들어지는 재미있는 앱을 만들어 볼거에요. 즐겨봅시다!

|  |  |
| --- | --- |
| 필요한 사전 지식: | 이 평가에 시도하기 전에 이 모듈에서 배운 항목을 모두 수행해봐야 합니다. |
| 목표: |
 변수, 숫자, 연산자, 문자열 및 배열 같은 JavaScript의 기본 항목에 대한
 전반적인 이해를 테스트합니다.
  |

시작하기
----

이 평가를 시작하기 전에, 아래와 같은 것을 해야 합니다.

* HTML 예제 파일이 있는 사이트로 이동하여 파일을 복사하여 자기 컴퓨터에 새 디렉토리를 만들고 파일이름 `index.html` 로 저장합니다. 이 안에는 예제를 위한 CSS가 포함되어 있습니다.
* 또 다른 가공 전의 텍스트가 있는 사이트 로 가서 별도의 브라우저 탭으로 열어 놓으세요. 이것은 나중에 필요합니다.

위 방법 대신에, 여러분은 테스트에 JSBin 또는 Glitch 같은 사이트를 이용할 수 있습니다. 이 온라인 에디터들 내부에 HTML, CSS 그리고 JavaScript를 붙여넣을 수 있습니다. 만약 당신이 사용하는 온라인 에디터가 Javascript 패널(또는 탭)을 갖고 있지 않다면, 자유롭게 HTML 페이지 내부에 `<script>` 엘리멘트를 넣을 수 있습니다.

**참고:** 문제가 해결되지 않으면 이 페이지 하단의 평가 또는 추가 도움말 섹션을 참조해 도움을 요청하세요.

프로젝트에 대한 간략한 설명
---------------

이제 프로젝트 시작을 위해 가공하지 않은 HTML/CSS 그리고 몇몇 텍스트를 만들고 확인했습니다. 이제 아래와 같은 기능을 하는 프로그램을 만들기 위해 JavaScript를 코딩해야 합니다.

* "Generate random story" 버튼를 누를 때 마다 꾸며진 이야기를 만들어야 합니다.
* 생성 버튼을 누르기 전에 "사용자 지정 이름 입력" 텍스트 필드에 사용자 지정 이름을 입력한 경우에만 스토리의 기본 이름인 "Bob"을 사용자 지정 이름으로 바꿉니다.
* 생성 버튼을 누르기 전에 영국 라디오 버튼을 선택한 경우 스토리의 기본 미국 무게 및 온도 수량과 단위를 영국 단위로 변환합니다.
* 버튼을 누를 때마다 새로운 꾸며진 스토리를 무작위로 생성합니다.

다음 스크린샷은 완성된 프로그램이 출력해야 하는 내용의 예를 보여줍니다.

![바보 같은 스토리 생성기 앱은 텍스트 필드, 라디오 버튼 2개, 무작위 스토리를 생성하는 버튼으로 구성되어 있습니다.](/ko/docs/Learn/JavaScript/First_steps/Silly_story_generator/screen_shot_2018-09-19_at_10.01.38_am.png)

더 많은 아이디어를 얻으려면 완성된 예제를 살펴보세요. 하지만 소스 코드를 들여다보지 마세요!

완료하기
----

이번 섹션에선 프로젝트를 어떻게 진행해야 하는지 자세하게 설명합니다.

기본 설정:

1. `index.html` 파일과 같은 디렉토리에 `main.js` 라는 이름의 파일을 새로 만듭니다.
2. `main.js`를 참조하는 HTML에 `<script>` 요소를 삽입하여 외부 JavaScript 파일을 HTML에 적용합니다. 닫는 `</body>` 태그 바로 앞에 넣습니다.

초기 변수와 함수:

1. 가공 전의 텍스트 파일에서, "1. COMPLETE VARIABLE AND FUNCTION DEFINITIONS" 바로 밑의 코드를 모두 복사해서 `main.js` 파일의 맨 위에 붙여넣습니다. 이 내용은 세 개의 변수 제공하는데, `customName` 변수는 "Enter custom name" 텍스트 필드 값을 저장하고, `randomize` 변수에는 "Generate random story" 버튼 객체를 저장하고, HTML 바디에 끝에 있는 클래스가 `story`인 `<p>` 요소는 story 변수에 저장하며 해당 요소에는 랜덤한 이야기가 복사됩니다. 또한, `randomValueFromArray()` 함수는 배열을 가져와서 배열이 가진 항목 중 하나를 랜덤하게 반환합니다.
2. 가공전 텍스트 파일의 두번째 섹션 "2. RAW TEXT STRINGS"에는 프로그램에 자동으로 입력되어 랜덤 이야기 요소에 출력하는 문자열이 포함되어 있습니다. `main.js` 에 아래 설명대로 변수를 만듭니다:
    1. 먼저 `storyText`변수에 굉장히 긴 가장 첫 번째 문자열을 저장합니다.
    2. `insertX` 배열에 세 개의 문자열 집단의 첫 번째를 저장합니다.
    3. `insertY` 배열에 세 개의 문자열 집단의 두 번째을 저장합니다.
    4. `insertZ` 배열에 세 개의 문자열 집단의 세 번째를 저장합니다.

이벤트 핸들러와 완성되지 않은 함수 배치:

1. 가공되지 않은 텍스트 파일로 돌아갑니다.
2. "3. EVENT LISTENER AND PARTIAL FUNCTION DEFINITION" 밑의 코드를 복사하고, `main.js` 파일의 맨 밑에다 붙여 넣으세요.
    * `randomize` 변수에 클릭 이벤트 리스너를 추가하세요. 그러면 버튼이 클릭되었을 때, `result()` 함수가 실행됩니다.
    * 코드에 부분적으로 완료된 `result()` 함수를 추가하세요. 이 평가의 뒤 부분에서 함수가 완성되고 정상적으로 동작하도록 내용을 추가할 것입니다.

`result()` 함수 완성하기:

1. `newStory`라 불리는 새 변수를 만들고, 이것의 값을 `storyText` 와 똑같이 설정하세요. 이것은 버튼을 누르고 함수가 수행될 때 마다 새 이야기('story')가 임의로 만들어 질 수 있게 합니다. `storyText`를 직접 변경했다면, 새로운 이야기를 한번만 만들 수 있습니다.
2. 세 개의 새로운 변수 `xItem`, `yItem`, 와 `zItem` 를 만들고 이 세 개의 변수를 `randomValueFromArray()`의 반환 결과인 세 개의 배열과 같도록 만듭니다(각 경우의 호출 결과는 각 배열에 임의의 아이템이 됩니다.) 예를 들어 `randomValueFromArray(insertX)`에 의해 작성된 `insertX` 에서 하나의 문자열을 임의로 반환하는 함수를 얻고, 호출할 수 있습니다.
3. 그 다음으로 `newStory` 문자열에 있는 세 개의 placeholders— `:insertx:`, `:inserty:`, `:insertz:` —저장된 `xItem`, `yItem`와 `zItem` 문자열을 이용하여 변환하세요. 여기서 특정 문자열 함수가 도움될 것입니다. 이 함수는 각 경우에 `newStory`와 같게 되며, 호출 시 마다 `newStory` 의 원래 값과 동일하지만 대체 될 수 있습니다. 그러므로 버튼이 눌러질때마다, 이 placeholder는 지정된 문자열로 랜덤하게 교체되게 됩니다. 추가적인 힌트로, 이 문제의 함수는 하위 문자열에서 찾은 첫번째 인스턴스만 변경하므로, 아마도 두번 이상 이 함수를 호출해야 할 수도 있습니다.
4. 첫번째 `if` 블록에서, `newStory` 의 'Bob'이라는 이름을 `name` 변수를 사용하여 변환하는 함수를 추가하세요. 이 블록에서 말하는 내용은 "만약 `customName` 텍스트 입력에 값이 들어가 있다면, 이야기속의 Bob을 사용자가 정의한 이름으로 바꾸세요" 라는 의미입니다.
5. 두번째 `if` 블록에서는 `uk` 라디오 버튼이 선택되었는지 확인합시다. `uk` 라디오 버튼이 눌려졌다면, 이야기 상의 무게와 온도 값들인 파운드와 화씨온도를 스톤과 섭씨온도로 바꾸어야 합니다. 필요한 것은 아래와 같습니다.
    1. 파운드를 스톤, 화씨온도를 섭씨온도로 변환하는 공식을 참조해 보세요.
    2. `weight` 변수를 정의하는 라인에서는, 300 파운드를 스톤으로 변환합니다. 변환 결과값을 `Math.round()` 를 실행한 결과값 끝에 `stone` 을 결합합니다.
    3. `temperature` 변수를 정의하는 줄에서는, 94를 화씨에서 섭씨온도로 변환합니다. 변환 결과값을 `Math.round()` 를 실행한 결과값 끝에 `' centigrade'`을 결합합니다.
    4. 두 변수 정의 바로 밑에, '94 화씨온도로 `temperature` 변수의 내용을 변환하는 것과, '300 pounds'을 `weight` 변수의 내용을 바꾸는 두줄의 문자열 변환라인을 추가합니다.
6. 마지막으로 함수의 두 번째 줄에서 마지막 줄에 단락을 참조하는 `story` 변수의 `textContent` 속성을 `newStory`와 동일하게 설정합니다.
힌트와 팁
-----

* JavaScript 내용 외에는 HTML을 수정할 필요가 없습니다.
* 만약 JavaScript가 정상적으로 HTML에 적용되었는지 의심스럽다면, 일시적으로 JavaScript의 내용을 지우고, 단순하고 짧은 효과가 명확한 내용을 추가해서, 저장하고 새로고침을 해보세요. 예를 들어 다음은 `<html>` 요소의 배경을 빨간색으로 바꾸므로 JavaScript가 제대로 적용되었다면 브라우저 창 전체가 빨간색으로 바뀌어야 합니다.

```js
document.querySelector("html").style.backgroundColor = "red";
```

* Math.round() 는 가장 가까운 정수로 단순하게 반올림한 결과를 반환하는 JavaScript 내장함수 입니다.
* 바꾸어야 하는 문자열에는 세 가지 인스턴스가 있습니다. `replace()` 메서드를 여러 번 반복하거나 `replaceAll()`을 사용할 수 있습니다. 문자열은 불변이라는 점을 기억하세요!
평가 또는 추가 도움말
------------

작업 평가를 받고 싶거나 막혀서 도움을 요청하고 싶은 경우.

1. 온라인 공유가 가능한 CodePen, jsFiddle, 또는 Glitch 같은 에디터에 작업한 것을 올려 두세요.
2. 평가 또는 도움을 요청을 MDN Discourse forum Learning category 에 게시글을 쓰세요. 게시글에는 다음이 포함되어야 합니다.
    * "Assessment wanted for Silly story generator"와 같은 서술적인 제목이 필요합니다.
    * 이미 시도한 작업의 세부 사항과 트위터에 바라는 사항을 알려주세요. 예를 들어 문제가 발생하여 도움이 필요하거나 평가를 원하는 경우가 있습니다.
    * 평가 받거나 도움이 필요한 예제에 대한 온라인 공유 가능 편집기 링크(위 1단계에서 설명한 대로)가 필요합니다. 코드를 볼 수 없다면 코딩 문제가 있는 사람을 도와주기가 매우 어렵기 때문에 이 방법을 사용하는 것이 좋습니다.
    * 실제 과제 또는 평가 페이지로 연결되는 링크로, 도움을 받고자 하는 문제를 찾을 수 있습니다.
* 이전
* Overview: First steps


JavaScript의 기본적인 연산 - 숫자와 연산자 - Web 개발 학습하기

JavaScript의 기본적인 연산 - 숫자와 연산자
=============================

* 이전
* Overview: First steps
* 다음

이 장은 JavaScript의 연산에 대해 다룹니다. 능숙하게 숫자를 다루기 위해 어떻게 연산자 와 그 외 기능을 사용하는지 알아봅니다.

|  |  |
| --- | --- |
| 필요한 사전 지식 | 기본적인 컴퓨터 지식, HTML과 CSS에 대한 기본적인 이해, JavaScript의 이해 |
| 목표 | JavaScript 연산에 익숙해지기 |

모두가 수학을 좋아합니다
-------------

좋아요, 아닐수도 있습니다. 일부는 좋아하겠죠. 몇몇 사람들은 구구단을 배울 때부터 싫어했을 겁니다. 그리고 몇몇은 이 사이 어딘가 있겠죠. 그러나 누구도 수학이 삶을 살아가는데 필수적 요소라는건 부정할 수 없을겁니다. 우리가 JavaScript(또는 다른 프로그래밍 언어)를 배울 때 특히 그렇습니다. 우리가 하는 일의 상당 부분은 수치형 데이터를 처리하고, 값을 계산하거나 하는 일입니다.

이 장은 당장 알아야 하는 기본적인 부분만 다룹니다.

### 숫자의 종류

프로그래밍에서는 우리 모두가 잘 알고 있는 단순한 십진수 체계도 생각보다 복잡합니다. 예를 들어, 우리는 다양한 유형의 십진수를 설명하기 위해 다양한 용어를 사용합니다.

* **정수**는 분수가 없는 부동 소수점 숫자입니다. 예를 들어 10, 400 또는 -5와 같이 양수 또는 음수가 될 수 있습니다.
* **부동 소수점 숫자**(floats)에는 소수점과 소수 자릿수가 있습니다(예: 12.5, 56.7786543).
* **Doubles**는 표준 부동 소수점 숫자보다 정밀도가 높은 특정 유형의 부동 소수점 숫자입니다(즉, 소수점 이하 자릿수까지 정확하다는 의미).

숫자 체계에도 다양한 유형이 있습니다! 10진수는 10을 기본으로 하지만(각 자릿수에 0-9를 사용한다는 의미), 다음과 같은 것도 있습니다.

* **2진수** — 10진수를 0과 1를 이용해 나타내는 데이터 타입입니다.
* **8진수** — 10진수를 0부터 7까지의 수를 이용해 나타내는 데이터 타입입니다.
* **16진수** — 10진수를 0부터 15까지의 수(1~10, A~F)를 이용해 나타내는 데이터 타입입니다. CSS의 색상을 지정할 때 쉽게 볼 수 있습니다.

**어려워서 힘들다고 느끼기 전에, 잠시 멈추세요!** 시작하기 위해서 우리는 이제부터 10진수만 사용하도록 하겠습니다. 다른 유형에 대해 생각할 필요가 없습니다.

두 번째 좋은 소식은 다른 프로그래밍 언어와 달리, JavaScript는 실수와 정수 모두 `Number`라는 하나의 데이터 타입만 사용합니다. 실수와 정수 모두 같은 데이터 타입이기 때문에 하나의 방법으로 동작하게 할 수 있다는 뜻입니다.

**참고:** 사실 JavaScript에는 `BigInt`라는 또 다른 숫자 타입이 있습니다. 정수를 나타내는 데 사용되는 데이터 타입이지만 이 글에서는 다루지 않습니다.

### 나를 위한 숫자들

우리가 필요한 기본 구문을 다시 익히기 위해 몇 가지 숫자를 빠르게 생각해 봅시다. developer tools JavaScript console (en-US) 에 들어가서 아래의 나열된 명령어를 입력해주세요.

1. 먼저, 두 개의 변수를 선언합니다. 그리고 두 개의 변수를 정수와 실수로 초기화해줍니다. 각각 변수명을 콘솔 창에 입력해주세요. 그리고 어떤 값이 나오는지 확인해주세요.

```js
var myInt = 5;
var myFloat = 6.667;
myInt;
myFloat;
```

2. 숫자는 따옴표가 없습니다. 계속 하기 전에 여러 개의 변수를 선언하고 숫자를 초기화 해주세요.
3. 우리들의 변수들의 데이터 타입을 확인합니다. JavaScript에서는 데이터 타입을 확인하기 위해 `typeof` 라는 키워드를 사용합니다. 아래와 같이 입력해 주세요:

```js
typeof myInt;
typeof myFloat;
```

`"number"` 는 정수와 실수인 경우에 나옵니다. 이것들은 정수와 실수가 다른 데이터 타입일 때 보다 다루기 쉽게 해줍니다. 그리고 다른 데이터 타입일 때 다른 방법으로 다뤄야만 합니다. 휴!
### 유용한 Number 메서드들

JavaScript에서 모든 표준 숫자를 나타내는 `Number` 객체에는 숫자를 조작하는 데 사용할 수 있는 많은 유용한 메서드가 있습니다. 간단한 소개만 하기 위해 이 문서에서는 이러한 메서드에 대해 자세히 설명하지 않고 기본적인 내용만 다룹니다. 하지만 객체 참조 페이지를 방문해서 사용 가능한 메서드에 대해 더 알아볼 가치는 있습니다.

예를 들어, 숫자를 고정된 소수점 자리수로 반올림하려면 `toFixed()` 메서드를 사용합니다. 다음 코드를 브라우저 콘솔에서 실행시켜 보세요.

```js
const lotsOfDecimal = 1.766584958675746364;
lotsOfDecimal;

const twoDecimalPlaces = lotsOfDecimal.toFixed(2);
twoDecimalPlaces;
```

### 숫자 데이터 타입으로 변환하기

때로는 계산을 수행하기 어렵게 문자열 형식으로 숫자가 저장된 경우가 있습니다. 주로 입력 폼의 입력 타입이 텍스트 (en-US)인 경우에 발생합니다. 이러한 경우에는 문자열 값을 `Number()` 생성자로 전달하면 같은 숫자를 나타내는 숫자 데이터 타입으로 변환할 수 있습니다.

예를 들어, 콘솔에 다음과 같이 입력해보세요.

```js
let myNumber = "74";
myNumber += 3;
```

실행하면 `77`이 아닌 `743`이 출력됩니다. 이는 `myNumber`가 문자열이기 때문입니다. 다음 코드를 입력하면 타입을 볼 수 있습니다.

```js
typeof myNumber;
```

올바른 계산 결과를 얻기 위해서는 다음과 같이 하면 됩니다.

```js
let myNumber = "74";
myNumber = Number(myNumber) + 3;
```

결과는 처음 예상한 대로 77입니다.

산술 연산자
------

산술연산자들의 기본적인 용도는 덧셈을 할 때 입니다.

| 연산자 | 이름 | 목적 | 예시 |
| --- | --- | --- | --- |
| `+` | 더하기 | 두 개의 숫자를 더합니다. | `6 + 9` |
| `-` | 빼기 | 왼쪽에 있는 수를 오른쪽 수로 뺍니다. | `20 - 15` |
| `*` | 곱하기 | 두 개의 숫자를 곱합니다. | `3 * 7` |
| `/` | 나누기 | 왼쪽의 숫자를 오른쪽 숫자로 나눠서 몫을 구합니다. | `10 / 5` |
| `%` | 나머지 (또는 모듈로) | 왼쪽의 숫자를 오른쪽 숫자로 나눠서 나머지를 구합니다. | `8 % 3` ( 2를 반환합니다, 8을 3으로 나눴을 때 몫이 2이기 때문입니다.) |
| `**` | 지수 | 왼쪽의 숫자를 오른쪽 숫자만큼 제곱합니다. | `5 ** 2` ( 5의 제곱이기 때문에 25를 반환합니다.) |

**참고:** 연산에 관계된 수를 피연산자라고 부릅니다.

**참고:** 지수를 계산할 때 `Math.pow()` 메서드를 사용할 수도 있고 `**`와 매우 비슷한 동작을 합니다. 예를 들어, `Math.pow(7, 3)`의 경우 `7`은 밑, `3`은 지수이므로 `343`을 반환합니다. 이 결과는 `7 ** 3`과 같습니다.

아직 수학을 공부할 필요는 없습니다. 하지만 우리는 문법 확인을 해야합니다. 아래의 명령어들을 developer tools JavaScript console (en-US) 에 입력해주세요.

1. 아래의 명령어를 콘솔창에 입력해주세요.

```js
10 + 7;
9 \* 8;
60 % 3;
```

2. 또한 변수 내부의 값을 선언하거나 초기화 할 수 있으며, 이를 계산에 이용할 수도 있습니다. 즉, 변수는 계산을 위해 가지고 있는 값과 정확히 동일하게 작동합니다. 예를 들어

```js
const num1 = 10;
const num2 = 50;
9 \* num1;
num2 / num1;
```

3. 마지막으로 다음과 같은 복잡한 식을 입력해보세요.

```js
5 + 10 \* 3;
(num2 % 9) \* num1;
num2 + num1 / 8 + 2;
```

몇몇 계산은 예상과 꽤 다르게 나옵니다. 아래의 문단에서 왜 그런지 이유를 알아보죠.

### 연산자 우선순위

위의 예제를 살펴보겠습니다. 위와 동일하게 `num2` 가 50을 `num1` 이 10을 가지고 있다고 가정하겠습니다.

```js
num2 + num1 / 8 + 2;
```

아마 "50 더하기 10은 60이고, 8 더하기 2는 10이니, 60 나누기 10은 6이다." 라고 계산했을 겁니다.

하지만 브라우저는 "10 나누기 8은 1.25이고, 50 더하기 1.25 더하기 2는 53.25이다." 라고 계산합니다.

이것은 **연산자 우선순위** 때문입니다. 연산자 우선순위는 어떤 연산자를 먼저 계산할지 결정합니다. JavaScript에서의 연산자 우선순위는 학교 수학 수업에서 배웠던 것과 같습니다. 곱하기와 나누기는 먼저 계산되고, 더하기와 빼기는 나중에 계산됩니다. 합은 항상 왼쪽에서 오른쪽으로 계산됩니다.

만약 연산자 우선순위를 무시하고 싶다면 괄호를 사용하여 먼저 계산할 부분을 지정할 수 있습니다. 다음과 같이 하면 6 이라는 결과를 얻을 수 있습니다.

```js
(num2 + num1) / (8 + 2);
```

직접 실행해 보세요.

**참고:** JavaScript의 연산자와 연산자 우선순위에 대해 더 알고 싶다면 연산자 우선순위 문서를 참고하세요.

증감 연산자
------

가끔은 반복해서 숫자 변수의 값을 1 씩 더하거나 빼고 싶을 때가 있습니다. 이럴 때는 증가(`++`) 연산자와 감소(`--`) 연산자를 사용하면 편리합니다. 우리는 JavaScript 첫 입문 (en-US) 문서에서 `guessCount` 변수에 1을 추가하여 매 턴마다 사용자의 남은 추측 횟수를 추적할 때 ++를 사용했습니다.

```js
guessCount++;
```

콘솔에서 이것을 사용해 보겠습니다. 우선, 숫자에 직접 쓸 수 없다는 것을 알아두세요. 이상하겠지만 값 자체를 계산하는 것이 아니라 변수에 새로운 업데이트된 값을 할당하는 것입니다. 다음은 오류를 반환합니다.

```js
3++;
```

다음처럼 변수만 증가시킬 수 있습니다.

```js
var num1 = 4;
num1++;
```

또 이상한 일이 일어났습니다! 이렇게 하면 `4`가 반환됩니다. 왜냐하면 브라우저가 현재 값을 반환한 뒤에 변수를 증가시키기 때문입니다. 변수 값을 다시 반환하면 증가했음을 알 수 있습니다.

```js
num1;
```

`--`에서도 똑같이 작동합니다.

```js
var num2 = 6;
num2--;
num2;
```

**참고:** 변수 앞에 연산자를 사용하면 브라우저가 변수를 먼저 증가/감소시키고 값을 반환합니다. 이번에는 `++num1`과 `--num2`를 사용하고 위의 예제를 다시 실행해 보세요.

대입 연산자
------

대입 연산자는 변수에 값을 대입하는 연산자입니다. 우리는 이미 기본적인 `=` 연산자를 많이 사용했습니다. 이 연산자는 변수 오른쪽에 있는 값을 대입합니다.

```js
var x = 3; // x의 값은 3입니다.
var y = 4; // y의 값은 4입니다.
x = y; // 이제 x의 값은 y와 같은 4입니다.
```

추가로 코드를 더 깔끔하고 효율적으로 쓸 수 있게 해주는 짧은 형태의 연산자들이 있습니다. 대표적인건 다음과 같습니다.

| 연산자 | 이름 | 목적 | 예시 | 의미 |
| --- | --- | --- | --- | --- |
| `+=` | 더하기 대입 | 오른쪽의 값을 왼쪽 변수 값에 더하고 새 변수 값을 반환합니다. | `x += 4;` | `x = x + 4;` |
| `-=` | 빼기 대입 | 오른쪽의 값을 왼쪽 변수 값에서 빼고 새 변수 값을 반환합니다. | `x -= 3;` | `x = x - 3;` |
| `*=` | 곱하기 대입 | 오른쪽의 값을 왼쪽 변수 값에 곱하고 새 변수 값을 반환합니다. | `x *= 3;` | `x = x * 3;` |
| `/=` | 나누기 대입 | 오른쪽의 값을 왼쪽 변수 값에 나누고 새 변수 값을 반환합니다. | `x /= 5;` | `x = x / 5;` |

위의 예제들을 콘솔에 입력해 보면서 어떻게 작동하는지 알아보세요. 그리고 두 번째 줄을 입력하기 전에 어떤 값이 나올지 예측해 보세요.

또한 오른쪽 표현식에서 다른 변수를 사용할 수 있습니다. 다음의 예시를 확인해보세요.

```js
var x = 3; // x는 3입니다.
var y = 4; // y는 4입니다.
x \*= y; // 이제 x의 값은 12입니다.
```

**참고:** 더 많은 연산자가 있지만, 지금은 이것만 시용해도 충분합니다.

실전 예제: 캔버스 크기 바꾸기
-----------------

이번 실습에서는 숫자와 연산자를 조작하여 박스의 크기를 바꿔보겠습니다. 박스는 Canvas API라는 브라우저 API를 사용하여 그려집니다. 이것이 어떻게 작동하는지는 신경 쓸 필요가 없습니다. 일단은 수학에 집중하세요. 박스의 너비와 높이(픽셀 단위)는 변수 `x`와 `y`에 의해 정의되며, 이 변수들은 처음에 각각 `50`의 값을 가집니다.

**새 창에서 보기**

이 예제에서는 주석으로 표시된 두 줄을 수정하여 박스의 크기를 특정 크기로 조정하고, 각각의 경우에 특정 연산자와/또는 값을 사용하도록 하겠습니다. 다음처럼 수정해 보세요.

* x를 계산하는 줄을 변경하여 박스의 너비는 여전히 50px이지만, 50은 43과 7, 그리고 산술 연산자를 사용하여 계산되도록 합니다.
* y를 계산하는 줄을 변경하여 박스의 높이는 75px이지만, 75는 25와 3, 그리고 산술 연산자를 사용하여 계산되도록 합니다.
* x를 계산하는 줄을 변경하여 박스의 너비는 250px이지만, 250은 두 개의 숫자와 나머지(모듈로) 연산자를 사용하여 계산되도록 합니다.
* y를 계산하는 줄을 변경하여 박스의 높이는 150px이지만, 150은 세 개의 숫자와 뺄셈과 나눗셈 연산자를 사용하여 계산되도록 합니다.
* x를 계산하는 줄을 변경하여 박스의 너비는 200px이지만, 200은 숫자 4와 할당 연산자를 사용하여 계산되도록 합니다.
* y를 계산하는 줄을 변경하여 박스의 높이는 200px이지만, 200은 숫자 50과 3, 곱셈 연산자, 그리고 덧셈 할당 연산자를 사용하여 계산되도록 합니다.

Reset 버튼을 눌러서 되돌릴 수 있기 때문에 코드가 동작하지 않아도 괜찮습니다. 위의 질문을 모두 통과했다면 코드를 자유롭게 수정하거나, 자신만의 도전을 해보세요.

비교 연산자
------

가끔은 참/거짓 테스트를 실행한 결과에 따라 다르게 처리하고 싶을 때가 있습니다. 이럴 땐 **비교 연산자**를 사용합니다.

| 연산자 | 이름 | 목적 | 예시 |
| --- | --- | --- | --- |
| `===` | 일치 연산자 | 왼쪽과 오른쪽의 값이 완전히 동일한지 테스트합니다. | `5 === 2 + 4` |
| `!==` | 불일치 연산자 | 왼쪽과 오른쪽 값이 서로 동일하지 않은지 테스트합니다. | `5 !== 2 + 3` |
| `<` | ~보다 작음 | 왼쪽 값이 오른쪽 값보다 작은지 테스트합니다. | `10 < 6` |
| `>` | ~보다 큼 | 왼쪽 값이 오른쪽 값보다 큰지 테스트합니다. | `10 > 20` |
| `<=` | ~보다 작거나 같음 | 왼쪽 값이 오른쪽 값보다 작거나 같은지 여부를 테스트합니다. | `3 <= 2` |
| `>=` | ~보다 크거나 같음 | 왼쪽 값이 오른쪽 값보다 크거나 같은지 여부를 테스트합니다. | `5 >= 4` |

**참고:** 일부 사람들은 `==`와 `!=`를 동등성과 불일치성을 테스트하는 데 사용합니다. 이 연산자들은 JavaScript에서 유효한 연산자이지만, `===`/`!==`와는 다릅니다. 전자는 값이 동일한지는 테스트하지만, 값의 데이터 유형이 동일한지의 여부는 테스트하지 않습니다. 후자의 경우 엄격한 버전은 값과 값의 데이터 유형의 동일성을 모두 테스트합니다. 엄격한 버전이 오류가 적은 편이기 때문에, 이를 사용하는 것을 권장합니다.

만약 콘솔에 값들을 입력한다면 `true`/`false` 값이 반환되는걸 볼 수 있습니다. 이는 이전 글에서 본 것 처럼 불리언(boolean)입니다. 코드에서 결정을 내리거나, 어떤 선택을 할 때마다 사용하기 때문에 불리언은 상당히 유용합니다. 불리언을 사용하는 예는 다음과 같습니다.

* 기능이 켜져있는지 꺼져있는지에 따라 버튼의 텍스트 라벨을 표시합니다.
* 게임을 결과에 따라 게임 오버나 승리 메시지를 표시합니다.
* 연휴에 따라 적절한 인사말을 표시합니다.
* 확대 레벨에 따라 지도를 확대하거나 축소합니다.

지금은 간단한 예제를 살펴보고, 조건문은 다음 글에서 다뤄보겠습니다

```html
<button>Start machine</button>
<p>The machine is stopped.</p>
```

```js
var btn = document.querySelector("button");
var txt = document.querySelector("p");

btn.addEventListener("click", updateBtn);

function updateBtn() {
  if (btn.textContent === "Start machine") {
    btn.textContent = "Stop machine";
    txt.textContent = "The machine has started!";
  } else {
    btn.textContent = "Start machine";
    txt.textContent = "The machine is stopped.";
  }
}
```

**새 창에서 보기**

`updateBtn()` 함수 내부에서 동등 연산자가 사용되는걸 볼 수 있습니다. 원리는 동일하지만, 이 경우엔 두 수식이 같은 값을 가지고 있는지 테스트하는 것이 아닌 버튼의 텍스트 콘텐츠가 특정 문자열을 포함하고 있는지를 테스트합니다. 버튼이 "Start machine"이라고 표시되어 있을 때 누르면 "Stop machine"으로 바꾸고 라벨을 변경합니다. 버튼이 "Stop machine"이라고 표시되어 있을 때 누르면 다시 표시를 바꿉니다.

**참고:** 두 상태 사이를 전환하는 이러한 컨트롤을 일반적으로 \*\*토글(toggle)\*\*이라고 합니다. 토글은 조명 켜기, 조명 끄기 등 한 상태와 다른 상태 사이를 전환합니다.

실력 테스트!
-------

이 글의 마지막까지 읽었지만 가장 중요한 정보를 기억할 수 있나요? 다음 단계로 넘어가기 전에 이 정보를 기억하고 있는지 확인할 수 있는 몇 가지 추가 테스트가 있습니다. 실력 테스트: 수학 (en-US)을 참고하세요.

요약
--

이번 글에서는 JavaScript에서 숫자를 다루는 기본적인 내용을 다뤘습니다. 앞으로도 숫자는 JavaScript를 배우는 동안 계속해서 사용할 것이므로 익숙해지는게 좋습니다. 수학을 좋아하지 않는 사람에겐 이번 글이 짧아서 다행이었겠군요.

다음 글에서는 텍스트와 JavaScript에서 텍스트를 조작하는 방법에 대해 알아보겠습니다.

**참고:** 수학을 좋아하고 JavaScript에서 수학이 어떻게 구현되는지 더 자세히 알고 싶으시다면 MDN의 메인 JavaScript 섹션에서 더 자세한 내용을 확인할 수 있습니다. 숫자와 날짜, 표현식 및 연산자 문서에서 시작하기 좋습니다.

* 이전
* Overview: First steps
* 다음


필요한 정보를 저장하기-변수 - Web 개발 학습하기

필요한 정보를 저장하기-변수
===============

* 이전
* Overview: First steps
* 다음

앞선 두 수업을 듣고, JavaScript에 대해(웹 기술에서 어떻게 사용되는지, 큰 범위에서의 특징) 알아보았다. 이번 수업에서는 기본중에 기본인 JavaScript의 주된 구성중 하나인 변수가 어떻게 이루어져 있는지 알아볼 것이다.

|  |  |
| --- | --- |
| 알아야 할 것 | 기본적인 컴퓨터 지식, 기본적인 HTML, CSS, JS의 이해 |
| 목표 | JavaScript 변수에 대해 익혀보기 |

필요한 툴
-----

이번 수업에서 컨텐츠에 대한 이해도를 테스트하고자, 코드를 입력하라는 요청을 받게 될 것이다. 만약 데스크탑 브라우저를 사용한다면, 코드를 실행하기 가장 좋은 프로그램은 브라우저의 JavaScript 콘솔창일 것이다.(도구의 사용법에 대해 알고자 한다면 What are browser developer tools (en-US) 를 참고하자)

변수란?
----

변수란, 숫자(합계나 계산에 사용되는) 또는 문자열(문장의 일부로 사용되는)과 같은 값의 컨테이너입니다. 그러나 변수에 대한 한 가지 특별한 점은 포함된 값이 변경될 수 있다는 것입니다. 간단한 예를 살펴 보겠습니다:

```html
<button>Press me</button>
```

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

button.onclick = function () {
  let name = prompt("What is your name?");
  alert("Hello " + name + ", nice to see you!");
};
```

이 예제에서 버튼을 누르면 몇 줄의 코드가 실행됩니다. 첫 번째 줄은 독자가 이름을 입력하도록 요청한 화면에 상자를 띄운 다음, 변수에 값을 저장합니다. 두 번째 줄은 변수 값에서 가져온, 이름이 포함된 환영 메시지가 표시됩니다.

왜 변수가 유용한 지 이해하려면, 변수를 사용하지 않고 이 예제를 작성하는 방법에 대해 생각해 봅시다. 그러면 아마 이런 식으로 끝날 것입니다.

```js
let name = prompt("What is your name?");

if (name === "Adam") {
  alert("Hello Adam, nice to see you!");
} else if (name === "Alan") {
  alert("Hello Alan, nice to see you!");
} else if (name === "Bella") {
  alert("Hello Bella, nice to see you!");
} else if (name === "Bianca") {
  alert("Hello Bianca, nice to see you!");
} else if (name === "Chris") {
  alert("Hello Chris, nice to see you!");
}

// ... and so on ...
```

우리가 사용하고있는 구문을 완전히 이해하지는 못했지만, 아이디어를 얻을 수 있어야 합니다-변수를 사용할 수 없다면, 입력된 이름을 검사하는 거대한 코드 블록을 구현해야합니다. 그런 다음 해당 이름에 대한 각각의 메시지를 출력해야 합니다. 이것은 분명히 비효율적입니다 (코드는 네 가지 선택만으로도 훨씬 더 커집니다). 그리고 가능한 모든 선택 사항(모든 이름들)을 저장할 수 없어 작동하지 않을 수도 있습니다.

변수는 이해하기 쉽습니다. 자바 스크립트에 대해 더 많이 배우면, 변수들은 자연스럽게 느껴질 것입니다.

변수에 대한 또 다른 특별한 점은 문자열과 숫자뿐 아니라 무엇이든 포함 할 수 있다는 것입니다. 변수에는 복잡한 데이터와 놀랄만 한 기능을 수행하는 함수(Function)까지 포함될 수 있습니다. 당신은 이것에 대해 점점 더 많이 배울 것입니다.

변수는 값을 포함하고 있습니다. 이것은 중요한 차이점입니다. 변수는 값 자체가 아닙니다. 변수는 값을 위한 컨테이너입니다. 당신은 변수란 물건들을 저장할 수있는 작은 골판지 상자와 같다고 생각할 수 있습니다.

![](/ko/docs/Learn/JavaScript/First_steps/Variables/boxes.png)

변수의 선언
------

변수를 사용하기 위해서, 먼저 변수를 선언해야 합니다 - 보다 정확히는, 변수를 선언한다고 부른다. 이를 위해 키워드 var를 입력하고, 당신이 원하는 변수 이름을 입력합니다.

```js
var myName;
var myAge;
```

여기서 우리는 myName과 myAge라는 두 개의 변수를 생성합니다. 웹 브라우저의 콘솔 또는 아래 콘솔에서 두행을 입력해 보십시오 (원하는 경우, 콘솔을 별도의 탭이나 창에서 열 수 있음). 그 후, 자신이 명명한 변수를 만들어 보십시오.
```

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>JavaScript console</title>
    <style>
 \* {
 box-sizing: border-box;
 }

 html {
 background-color: #0c323d;
 color: #809089;
 font-family: monospace;
 }

 body {
 max-width: 700px;
 }

 p {
 margin: 0;
 width: 1%;
 padding: 0 1%;
 font-size: 16px;
 line-height: 1.5;
 float: left;
 }

 .input p {
 margin-right: 1%;
 }

 .output p {
 width: 100%;
 }

 .input input {
 width: 96%;
 float: left;
 border: none;
 font-size: 16px;
 line-height: 1.5;
 font-family: monospace;
 padding: 0;
 background: #0c323d;
 color: #809089;
 }

 div {
 clear: both;
 }
 </style>
  </head>
  <body></body>

  <script>
 var geval = eval;
 function createInput() {
 var inputDiv = document.createElement("div");
 var inputPara = document.createElement("p");
 var inputForm = document.createElement("input");

 inputDiv.setAttribute("class", "input");
 inputPara.textContent = ">";
 inputDiv.appendChild(inputPara);
 inputDiv.appendChild(inputForm);
 document.body.appendChild(inputDiv);

 inputForm.addEventListener("change", executeCode);
 }

 function executeCode(e) {
 try {
 var result = geval(e.target.value);
 } catch (e) {
 var result = "error — " + e.message;
 }

 var outputDiv = document.createElement("div");
 var outputPara = document.createElement("p");

 outputDiv.setAttribute("class", "output");
 outputPara.textContent = "Result: " + result;
 outputDiv.appendChild(outputPara);
 document.body.appendChild(outputDiv);

 e.target.disabled = true;
 e.target.parentNode.style.opacity = "0.5";

 createInput();
 }

 createInput();
 </script>
</html>
```

**참고:** JavaScript에서는 모든 코드 명령어가 세미콜론 (;)으로 끝나야합니다. 코드를 한 줄로 작성해도 올바르게 작동할 지라도, 여러 줄의 코드를 함께 작성하는 경우에는 그렇지 않습니다. 그것을 포함시키는 습관을 갖도록 노력하십시오.

변수 이름을 입력하여 변수의 값이 실행 환경에 존재하는지 테스트 할 수 있습니다.

```js
myName;
myAge;
```

이 변수들은 값을 포함하고 있지 않은 빈 컨테이너 입니다. 변수 이름만 입력 할 경우 `undefined` 값을 반환하며 변수는 이 값(`undefined)`을 포함하게 됩니다. 만약 존재하지 않는(선언되지 않은) 변수는, 오류 메시지가 표시됩니다. 아래 변수를 입력하여 확인해 보세요.

```js
scoobyDoo;
```

**참고:** 존재하지만 값을 포함하고 있지 않은 변수와, 존재하지 않은 변수를 혼돈하지 마십시오. 둘은 매우 다른 것들입니다.

변수의 초기화
-------

변수를 선언한 후에는 값으로 초기화 할 수 있습니다. 변수 이름 다음에 등호(`=`)와 그 뒤에 부여 할 값을 입력하여 이 작업을 수행 할 수 있습니다.

```js
myName = "Chris";
myAge = 37;
```

콘솔로 돌아가 코드들을 입력해 보세요. 각각의 경우 변수에 할당한 값은 콘솔을 통해 반환되어 확인 할 수 있습니다. 또한 단순히 변수 이름을 입력하여 변수 값을 반환 할 수 있습니다. 한번 해보세요.

```js
myName;
myAge;
```

다음과 같이 변수를 선언하고 동시에 초기화 할 수 있습니다.

```js
var myName = "Chris";
```

아마도 변수의 선언과 초기화를 두줄로 나누어 하는것 보다 더 빠르기 때문에 대부분 이러한 방식을 자주 사용 하게 될 겁니다.

**참고:**

 여러 줄 문자열로 JavaScript 프로그램을 작성할 때(write a multiline JavaScript), 변수를 선언하기 전에 해당 변수의 값을 초기화 할 수 있습니다. 이것이 가능한 이유는 JavaScript 에서 일반적으로 변수 선언문이 다른 코드 보다 먼저 실행되기 때문인데, 이 동작을 **호이스팅**이라고 합니다.
 자세한 내용은 var 호이스팅 문서를 참고하세요.

변수의 재지정
-------

변수에 값이 할당되면 다른 값을 지정하여 해당 값을 업데이트 할 수 있습니다. 콘솔에 다음 행을 입력해 보세요.

```js
myName = "Bob";
myAge = 40;
```

### 변수 이름에 대한 규칙

변수를 원하는 대로 이름을 부여 할 수 있지만 제한이 있습니다. 일반적으로 라틴 문자(0-9, a-z, A-Z)와 밑줄 문자를 사용해야 합니다.

* 오류가 발생하거나 전 세계 타인이 이해하기 어려울 수 있으므로 다른 문자를 사용하면 안됩니다.
* 변수 이름의 시작부분에 밑줄(\_)을 사용하지 마세요. JavaScript 구문에서 밑줄로 시작하는 것은 특별한 의미를 가지고 있으므로 혼란을 가져올수 있습니다.
* 변수 이름의 시작부분에 숫자를 사용하지 마세요. 허용되지 않으며 오류가 발생합니다.
* 안전한 명명법은 소위 "lower camel case"(소문자 낙타 문법) 입니다. 여러 단어를 하나로 묶고 첫 단어의 시작은 소문자를 사용하며 그다음 단어의 시작은 대문자로 사용합니다. 우리는 지금까지 이 문서에서 변수 이름에 이 방법을 사용해 왔습니다.
* 포함된 데이터를 쉽게 이해 할 수 있게 변수 이름을 직관적으로 부여 합니다. 단일 문자 / 숫자 또는 긴 구절을 사용하지 마세요.변수 이름을 직관적으로 만들어, 포함된 데이터를 표현 할 수 있습니다.
* 변수는 대소문자를 구분 합니다. - `myage` 와 `myAge` 는 다른 변수 입니다.
* 마지막으로 JavaScript 예약어를 변수 이름으로 사용하면 안됩니다. (예약어란 JavaScript의 실제 구문을 구성하는 단어를 의미 합니다.) 따라서 변수 이름으로 `var`, `function`, `let`, `for` 와 같은 단어를 사용 할 수 없습니다. 브라우저는 이러한 단어를 다른 코드 아이템(예약어)로 인식하므로 오류가 발생 합니다.

**참고:** 다음 Lexical grammar — keywords (en-US) 링크에서 예약어의 목록을 확인 할 수 있습니다.

바람직한 변수 이름의 예:

```js
age
myAge
init
initialColor
finalOutputValue
audio1
audio2
```

바람직하지 않은 변수 이름의 예:

```js
1
a
_12
myage
MYAGE
var
Document
skjfndskjfnbdskjfb
thisisareallylongstupidvariablenameman
```

위의 지침을 염두에 두고 몇가지 변수를 추가로 작성해 보세요.

변수의 종류
------

변수에 저장할 수 있는 몇가지 유형의 데이터(데이터 유형)가 있습니다. 이 섹션에서는 이를 간단히 설명하고 이후 자세히 살펴보겠습니다.

지금까지 우린 두가지 형태의 데이터 유형을 살펴 봤지만 다른 유형들도 있습니다.

### 숫자

30과 같은 숫자 (정수라고도 함) 나 2.456(부동소수점 또는 부동 소수점 숫자라고도 함) 같은 십진수 숫자를 변수에 저장할 수 있습니다. JavaScript는 일부 프로그래밍 언어처럼 숫자 유형에 따른 다른 데이터 유형을 가지고 있지 않습니다. 변수에 숫자 값 대입할 때, 따옴표 사용하지 않습니다.

```js
var myAge = 17;
```

### 문자열

문자열은 텍스트의 조각입니다. 변수에 문자열 값을 대입할 때, 작은따옴표(')나 큰따옴표(")로 묶어야 합니다. 그렇지 않으면 JavaScript는 다른 변수 이름으로 해석하게 됩니다.

```js
var dolphinGoodbye = "So long and thanks for all the fish";
```

### 불리언(Booleans)

불리언(Booleans)은 `true` 이나 `false` 라는 값을 가지는 참/거짓을 표현하는 데이터 유형입니다. 일반적으로 조건을 테스트하는 데 사용되며 그 다음 코드가 조건에 따라 실행됩니다. 예를 들어 다음과 같습니다.

```js
var iAmAlive = true;
```

일반적으로 다음과 같은 방식으로 더 많이 사용됩니다.

```js
var test = 6 < 3;
```

위의 코드는 "작다" 연산자(`<`) 를 사용하여 6이 3보다 작은지를 확인 합니다. 예상 한대로 6이 3보다 작지 않으므로`false` 를 반환 합니다! 나중에 이러한 연산자에 대해 더 많이 배우게 됩니다.

### 배열

배열은 대괄호로 묶이고 쉼표로 구분 된 여러 값을 포함하는 단일 객체입니다. 다음 코드를 콘솔에 입력해 보세요.

```js
var myNameArray = ["Chris", "Bob", "Jim"];
var myNumberArray = [10, 15, 40];
```

이러한 배열이 정의되면 다음과 같은 구문을 사용하여 개별 값에 접근 할 수 있습니다. 다음 코드를 입력해 보세요.

```js
myNameArray[0]; // should return 'Chris'
myNumberArray[2]; // should return 40
```

대괄호에는 반환할 값의 위치를 지정하는 인덱스 값이 들어 있습니다. 컴퓨터는 우리 사람처럼 1대신 0부터 숫자를 센다는 것을 알 수 있습니다.

앞으로 배열에 대해 더 많이 배우게 됩니다.

### 객체

프로그래밍에서 객체(Objects)는 실제 사물(real life object)을 모델링 하는 코드 구조입니다. 예를들어 주차장 객체는 주차장의 높이와 넓이 정보를 가지고 표현 할 수 있으며, 사람 객체는 이름, 키, 몸무게, 사용하는 언어등의 정보를 가지고 표현 할 수 있습니다.

콘솔에 다음 코드를 입력해 보세요.

```js
var dog = { name: "Spot", breed: "Dalmatian" };
```

객체에 저장된 정보를 검색하기 위해서는 아래 구문을 사용합니다.

```js
dog.name;
```

지금은 객체에 대해 더 자세하게 보지 않을 것입니다. - 앞으로 모듈에 있는 객체에 대해 더 많이 배울 수 있습니다.

지정되지 않은 타입
----------

JavaScript는 "느슨한 유형의 언어(loosely typed language)" 입니다. 즉, 다른 언어와 달리 변수에 포함 할 데이터의 유형을 지정할 필요가 없습니다.(예: 숫자? 문자열?)

예를 들어, 변수를 선언하고 그 변수의 값을 따옴표로 묶은 값을 지정하면 브라우저는 변수의 값을 문자열로 인식합니다.

```js
var myString = "Hello";
```

따옴표 안에 숫자가 포함되어 있어도 여전히 문자열로 인식되므로 주의해야 합니다:

```js
var myNumber = "500"; // oops, this is still a string
typeof myNumber;
myNumber = 500; // much better — now this is a number
typeof myNumber;
```

위의 네 줄의 코드를 하나씩 콘솔에 입력하여 결과가 무엇인지 확인해 보세요.(주석은 입력하지 마세요.) 여기에서 `typeof()` 라는 특수 함수를 사용하고 있음을 알 수 있습니다. - 이 함수는 입력한 변수의 데이터 유형을 반환합니다. 위의 코드에서 처음으로 호출될 때, `myNumber` 변수에는 `'500'` 라는 문자열이 포함되어 있으므로 `string` 을 반환해야 합니다. 두 번째는 어떤 값을 반환하는지 확인해 보세요.

당신의 실력을 시험해 보세요!
----------------

이 문서를 끝까지 읽으셨지만, 중요한 것들을 여전히 기억하고 계신가요? 다음 문서를 읽기 전에 이 문서의 내용을 잘 학습하고 이해하셨는지 확인하실 수 있습니다 — 당신의 실력을 시험해 보세요: 변수(variables).

요약
--

지금까지 JavaScript의 변수란 무엇이며, 어떻게 생성하는지 알아보았습니다. 다음에는 JavaScript에서 숫자에 관해 자세히 살펴보고 기본 계산하는 방법을 알아보겠습니다.

* 이전
* Overview: First steps
* 다음


JavaScript에 발 담그기 - Web 개발 학습하기

JavaScript에 발 담그기
=================

* 이전
* Overview: First steps
* 다음

JavaScript에 관한 약간의 이론과, JavaScript가 할 수 있는 일을 알아봤으니 이제 간단한 JavaScript 프로그램을 만들어보면서 개발 과정이 어떤 느낌인지 알아보겠습니다. "숫자 알아맞히기" 게임을 차근차근 따라 만들어봅시다.

|  |  |
| --- | --- |
| 필요한 사전 지식: | 기본적인 컴퓨터 이해 능력, HTML과 CSS 기초, JavaScript 기본 개념. |
| 목표: | JavaScript 코드를 직접 작성해보고, JavaScript 프로그램 개발에 어떤 것이 필요한지 느껴보기. |

시작하기 전에, 우리의 기대 목표를 먼저 확실하게 짚고 넘어가겠습니다. 이 글을 끝낸 뒤에도, 함께 따라서 작성한 코드를 이해하지 못할 수 있습니다. 그래도 괜찮습니다. 이 시간은 JavaScript의 기능들이 함께 어떻게 동작하는지, JavaScript 코드를 작성하는 게 어떤 느낌인지 알려드리기 위한 시간일 뿐입니다. 여기에서 사용한 기능들은 이후의 다른 여러 글에서 자세하게 짚어볼 테니, 지금 당장은 굳이 모두 이해하려고 애쓰지 마세요.

**참고:** 함수와 반복문처럼 JavaScript의 많은 코드 기능은 다른 프로그래밍 언어에도 존재합니다. 코드 구문은 다르더라도, 그 개념은 많은 부분 동일합니다.

프로그래머처럼 사고하기
------------

프로그래밍에서 가장 배우기 어려운 부분은 학습할 구문이 아니라, 그 구문을 어떻게 현실 세계의 문제에 적용할까 하는 문제입니다. 그러니 프로그래머처럼 생각해야 합니다. 프로그램의 동작 방식에 대한 설명을 보고, 어떤 코드 기능이 필요한지 파악하고, 이 둘을 어떻게 함께 활용할지 고민하는 과정이죠.

노력과 프로그래밍 구문에 대한 경험, 연습, 그리고 약간의 창의성이 필요한 일입니다. 하면 할수록 더 잘 하게 될 겁니다. 단 5분 만에 "프로그래머 뇌"를 갖게 될 것이라고 약속할 수는 없겠지만, 프로그래머 사고법을 연습할 수 있는 많은 기회를 이 과정 안에서 제공하겠습니다.

그러면, 이 글에서 만들게 될 프로그램을 직접 볼 시간입니다. 주어진 과제를 해결하기 쉬운 더 작은 단위의 작업으로 쪼개는 일반적인 과정도 살펴보겠습니다.

예제 — 숫자 맞히기 게임
--------------

간단한 게임을 만들겠습니다. 바로 아래에서 결과물을 먼저 확인해 보세요.

한 번 플레이해 보세요. 다음 단계로 넘어가기 전에 먼저 게임에 익숙해져 보세요.

이 게임을 만들기 위해서, 여러분의 직장 상사가 게임의 기능을 안내해 주고 가는 상황을 상상해 봅시다.

>
> 간단한 숫자 맞히기 게임을 만들어주세요. 무작위로 선택한 1과 100 사이의 수를 플레이어가 10턴 내에 알아내는 게임입니다. 각 턴 후에는 플레이어가 고른 숫자가 맞았는지 아니면 틀렸는지 알려줘야 하고, 만약 틀린 경우에는 플레이어의 숫자가 너무 낮았는지, 아니면 너무 높았는지를 알려줘야 합니다. 플레이어가 이전에 추측한 숫자들의 기록도 보여줘야 합니다. 게임은 플레이어가 숫자를 맞히거나, 턴을 모두 소모하면 끝납니다. 게임이 끝난 뒤에는 다시 게임을 시작할 수 있는 옵션을 제공해야 합니다.
>
>
>

상사의 안내 이후에 해야 할 일은, 최대한 프로그래머의 마음가짐을 갖추고, 이 과제를 간단하고 작은 작업으로 나눠보는 것입니다.

1. 1과 100 사이의 무작위 숫자를 생성하기.
2. 플레이어의 현재 턴을 기록하기. 1부터 시작.
3. 플레이어가 정답을 추측할 수 있는 방법을 제공.
4. 플레이어가 자신의 추측 기록을 확인할 수 있도록, 제출하는 수를 먼저 어딘가에 기록하기.
5. 그 후, 제출한 수가 정답인지 확인.
6. 만약 정답이라면...
    1. 축하 메시지 보여주기.
    2. 이후에 플레이어가 다른 숫자를 제출하면 게임이 망가질 수 있으므로 제출을 막기.
    3. 게임을 다시 시작할 수 있는 컨트롤 보여주기.
7. 만약 오답이고 아직 플레이어 턴이 남아있다면...
    1. 오답임을 알려주고 제출한 숫자가 너무 높았는지 아니면 너무 낮았는지 보여주기.
    2. 다른 숫자의 제출을 허용하기.
    3. 턴 숫자를 1 늘리기.
8. 만약 오답인데 플레이어 턴이 남아있지 않다면...
    1. 플레이어에게 게임이 끝났음을 알려주기.
    2. 이후에 플레이어가 다른 숫자를 제출하면 게임이 망가질 수 있으므로 제출을 막기.
    3. 게임을 다시 시작할 수 있는 컨트롤 보여주기.
9. 게임을 다시 시작하면 게임 로직과 UI를 완전히 초기화하고 1번으로 돌아가기.

이제 다음 단계로, 위의 작업들을 코드로 작성해서 예제 프로그램으로 구성하는 법과, 이에 필요한 JavaScript 기능들을 알아보겠습니다.

### 초기 설정

자습서를 시작하기 전에 number-guessing-game-start.html 파일의 로컬 복사본을 만들어 주세요(실행 결과 미리보기). 저장한 파일을 텍스트 에디터와 웹 브라우저에서 각자 열어주세요. 현재로서는 간단한 제목, 안내 문단, 추측을 입력할 수 있는 양식이 표시되지만, 이 양식은 현재 아무 기능도 수행하지 않습니다.

우리가 코드를 추가할 부분은 HTML의 아래쪽, `<script>` 요소 안입니다.

```html
<script>
 // JavaScript 코드는 여기에 추가하면 됩니다.
</script>
```

### 데이터를 저장할 변수 추가하기

시작해 봅시다. 먼저 `<script>` 요소 안에 다음 코드를 추가해주세요.

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

const guesses = document.querySelector(".guesses");
const lastResult = document.querySelector(".lastResult");
const lowOrHi = document.querySelector(".lowOrHi");

const guessSubmit = document.querySelector(".guessSubmit");
const guessField = document.querySelector(".guessField");

let guessCount = 1;
let resetButton;
```

위 코드 조각은 우리 프로그램이 사용할 데이터를 담은 변수와 상수를 설정합니다.

변수란 기본적으로 값(숫자, 텍스트 문자열 등)에 이름을 붙이는 것입니다. 변수는 `let` 키워드와 그 뒤의 이름으로 생성할 수 있습니다.

상수 또한 변수처럼 이름을 붙인 값이지만, 변수와 달리 상수의 값은 바꿀 수 없습니다. 우리의 위 코드에서는 사용자 인터페이스의 특정 부분을 가리키는 참조를 상수에 저장하고 있습니다. HTML 요소 내부의 텍스트는 바뀔지 몰라도, 각 상수는 항상 맨 처음 설정한 요소를 가리키게 됩니다. 상수는 `const` 키워드와 그 뒤의 이름으로 생성할 수 있습니다.

변수와 상수에 값을 할당하려면 등호(`=`) 뒤에 원하는 값을 입력하세요.

코드를 자세히 살펴보겠습니다.

* 첫 번째 변수 `randomNumber`에는 수학 알고리즘을 통해 1부터 100 사이의 무작위 수를 할당합니다.
* 그다음 세 개의 상수는 HTML의 결과 문단을 가리키는 참조를 저장해서, 나중에 문단에 텍스트를 삽입할 때 사용합니다. 세 문단이 모두 한 `<div>` 요소 안에 배치된 것을 확인해보세요. 이 `<div>`는 게임을 초기화할 때 세 문단을 모두 선택하기 위해 사용합니다.

```html
<div class="resultParas">
  <p class="guesses"></p>
  <p class="lastResult"></p>
  <p class="lowOrHi"></p>
</div>
```

* 다음 두 상수는 추측한 숫자를 제출할 때 사용할 양식 텍스트 입력 칸과 제출 버튼의 참조를 저장합니다.

```html
<label for="guessField">Enter a guess: </label
><input type="text" id="guessField" class="guessField" />
<input type="submit" value="Submit guess" class="guessSubmit" />
```

* 마지막 두 변수는 플레이어가 사용한 턴 수인 1과, 지금은 없지만 이 글의 뒤에서 추가할 초기화 버튼의 참조를 저장하기 위해 사용합니다.

**참고:** 바로 다음 글을 시작으로, 이후 과정에서 변수와 상수에 대해 많이 배울 수 있습니다.

### 함수

이제 앞서 작성한 JavaScript 코드 밑에 다음 코드를 추가해 주세요.

```js
function checkGuess() {
  alert("I am a placeholder");
}
```

함수는 한 번 작성한 후 몇 번이든 실행할 수 있는, 재사용 가능한 코드 블록입니다. 같은 코드를 계속 작성할 필요가 없으므로 굉장히 유용합니다. 함수를 정의하는 방법에는 여러 가지가 있지만, 지금은 간단한 방법 하나에 집중하도록 하겠습니다. 위 코드는 `function` 키워드, 이름, 빈 소괄호와 중괄호 쌍(`{ }`)으로 함수를 정의하는 코드입니다. 함수를 호출할 때마다 실행할 코드는 중괄호 쌍 안에 배치합니다.

함수 내부 코드를 실행하기 위해선 함수의 이름을 입력하고, 그 뒤에 소괄호를 덧붙여야 합니다.

바로 시험해봅시다. 코드를 저장하고, 브라우저를 열어 새로고침으로 페이지를 다시 불러오세요. 개발자 도구의 JavaScript 콘솔 (en-US)을 열어, 다음 코드를 입력해보세요.

```js
checkGuess();
```

키보드의 `Return`/`Enter` 키를 누르면 `I am a placeholder`라는 경고 창이 나타날 겁니다. 이로써, 우리 코드 안에 호출할 때마다 경고 창을 띄우는 함수를 정의했습니다.

**참고:** 이후 과정에서 함수에 대해 많이 배울 수 있습니다.

### 연산자

JavaScript에서는 연산자로 참/거짓 판별, 수학 계산, 문자열 연결 등등 다양한 작업을 수행할 수 있습니다.

아직 콘솔을 열지 않았으면, 코드를 저장하고 브라우저를 새로고침한 후 개발자 도구의 JavaScript 콘솔 (en-US)을 열어주세요. 이제 연산자 표에서 "예제" 열에 적힌 코드를 직접 입력해 보고, `Return`/`Enter` 키를 누르면 어떤 결과가 나타나는지 확인해보세요.

먼저 산술 연산자입니다.

| 연산자 | 이름 | 예제 |
| --- | --- | --- |
| `+` | 더하기 | `6 + 9` |
| `-` | 빼기 | `20 - 15` |
| `*` | 곱하기 | `3 * 7` |
| `/` | 나누기 | `10 / 5` |

복합 할당 연산자라고 하는 바로 가기 연산자도 사용할 수 있습니다. 예를 들어 기존 번호에 새 번호를 추가하고 결과를 반환하려는 경우 이렇게 할 수 있습니다.

```js
let number1 = 1;
number1 += 2;
```

위 코드의 결과는 아래와 동일합니다.

```js
let number2 = 1;
number2 = number2 + 2;
```

조건으로 사용하기 위한 참/거짓 판별에는 비교 연산자를 사용합니다.

| 연산자 | 이름 | 예제 |
| --- | --- | --- |
| `===` | 엄격한 일치 (정확히 같은가?) |
```js
5 === 2 + 4 // false
'Chris' === 'Bob' // false
5 === 2 + 3 // true
2 === '2' // false, 숫자와 문자열은 다름
```

 |
| `!==` | 불일치 (같지 않은가?) |
```js
5 !== 2 + 4 // true
'Chris' !== 'Bob' // true
5 !== 2 + 3 // false
2 !== '2' // true, 숫자와 문자열은 다름
```

 |
| `<` | 미만 |
```js
6 < 10 // true
20 < 10 // false
```
 |
| `>` | 초과 |
```js
6 > 10 // false
20 > 10 // true
```
 |

### 텍스트 문자열

문자열은 텍스트를 표현하는 데 사용됩니다. 이미 문자열 변수를 살펴본 바 있습니다. 다음 코드에서 `"I am a placeholder"`는 문자열입니다.

```js
function checkGuess() {
  alert("I am a placeholder");
}
```

큰따옴표(`"`) 또는 작은따옴표(`'`)를 사용하여 문자열을 선언할 수 있지만, 단일 문자열 선언의 시작과 끝에 동일한 형식을 사용해야 합니다. 예를 들어 `"I am a placeholder'`라고 작성할 수 없습니다.

백틱(```)을 사용하여 문자열을 선언할 수도 있습니다. 이렇게 선언된 문자열을 *템플릿 리터럴*이라고 하며 몇 가지 특별한 속성이 있습니다. 특히 다른 변수나 표현식을 포함할 수 있습니다.

```js
const name = "Mahalia";

const greeting = `Hello ${name}`;
```

이를 통해 문자열을 결합하는 메커니즘을 사용할 수 있습니다.

### 조건

다시 `checkGuess()` 함수로 돌아갑시다. 이 함수가 하는 일이 지금처럼 임시로 넣은 메시지를 뱉기만 하는 건 아이어야 할 겁니다. 우리가 `checkGuess()` 함수에 원하는 동작은 플레이어의 추측이 정답인지 아닌지 확인하고, 적합한 반응을 하는 것입니다.

이 시점에서, `checkGuess()` 함수를 아래 코드로 교체하세요.

```js
function checkGuess() {
  const userGuess = Number(guessField.value);
  if (guessCount === 1) {
    guesses.textContent = "Previous guesses: ";
  }
  guesses.textContent += userGuess + " ";

  if (userGuess === randomNumber) {
    lastResult.textContent = "Congratulations! You got it right!";
    lastResult.style.backgroundColor = "green";
    lowOrHi.textContent = "";
    setGameOver();
  } else if (guessCount === 10) {
    lastResult.textContent = "!!!GAME OVER!!!";
    lowOrHi.textContent = "";
    setGameOver();
  } else {
    lastResult.textContent = "Wrong!";
    lastResult.style.backgroundColor = "red";
    if (userGuess < randomNumber) {
      lowOrHi.textContent = "Last guess was too low!";
    } else if (userGuess > randomNumber) {
      lowOrHi.textContent = "Last guess was too high!";
    }
  }

  guessCount++;
  guessField.value = "";
  guessField.focus();
}
```

꽤 긴 코드군요! 한 구획씩 살펴보면서 어떤 역할을 하는 코드인지 설명하겠습니다.

* 첫 줄은 `userGuess`라는 이름의 변수를 선언하고, 텍스트 필드에 입력된 현재 값을 저장합니다. 값을 내장 `Number()` 생성자로 한 번 감싼 이유는 확실하게 숫자 값으로 만들기 위해서입니다. 변수의 값을 바꿀 계획은 없기에 `const` 키워드로 선언했습니다.
* 그 아래에는 우리의 첫 번째 조건 코드 블록이 위치합니다. 조건 코드 블록을 활용하면 특정 조건이 참이거나 거짓일 때만 선택적으로 코드를 실행할 수 있습니다. 얼핏 보면 함수처럼 보이기도 하지만 조건 코드 블록은 함수가 아닙니다. 가장 간단한 형태의 조건 코드 블록은 `if` 키워드, 소괄호, 그리고 중괄호로 구성됩니다. 소괄호에는 조건 테스트가 들어가며, 이 테스트가 `true`를 반환하면 중괄호 안의 코드를 실행합니다. 테스트가 `false`를 반환하면 중괄호 안의 코드를 실행하지 않고, 조건 블록 아래 코드로 실행을 넘깁니다. 첫 번째 조건 코드 블록에서는 `guessCount` 변수가 `1`과 일치하는지, 즉 플레이어의 첫 턴인지를 테스트하고 있습니다.

```js
guessCount === 1;
```

이 조건이 참이라면 `guesses` 문단의 텍스트 내용을 `Previous guesses:`로 설정하고, 그렇지 않으면 아무것도 하지 않습니다.
* 6번째 줄에서는 현재 `userGuess` 변수의 값을 `guesses` 문단의 맨 뒤에 이어붙이고, 그 뒤에 다시 공백을 하나 덧붙여 플레이어의 추측들이 붙어있지 않도록 방지합니다.
* 다음 블록에서는 몇 개의 검사를 수행합니다.
    + 첫 번째 `if(){ }`에서는 우리의 JavaScript 코드에서 설정한 `randomNumber` 값이 플레이어의 추측과 일치하는지 확인합니다. 일치하는 경우 플레이어가 정답을 맞힌 것이니 적절한 초록색 글자로 축하 메시지를 표시하고, 낮음/높음 힌트 박스의 내용을 지우고, 뒤에 다룰 함수인 `setGameOver()`를 호출합니다.
    + 그 아래에는 `else if(){ }` 구조를 사용해서 이전 테스트에 다른 테스트를 이어 붙였습니다. 여기선 이 턴이 플레이어의 마지막 턴이었는지 확인합니다. 마지막 턴이었다면 첫 번째 조건 블록과 같은 작업을 수행하지만, 축하 메시지 대신 게임 오버 메시지를 보여줍니다.
    + 마지막 블록(`else { }`)은 앞선 테스트가 모두 참을 반환하지 않았을 때, 즉 플레이어가 정답을 맞히지도 못했고, 마지막 턴도 아닐 때 실행됩니다. 이 블록에서는 플레이어의 추측이 틀렸다고 말하고, 또 다른 테스트를 통해 추측한 값이 정답 값보다 높은지 낮은지 확인해서 적합한 메시지를 출력합니다.
* 마지막 세 줄(위 코드에서 26~28번째 줄)에서는 다음 추측을 제출받기 위한 준비를 합니다. `guessCount` 변수에 1을 더하고(`++`는 증가 연산자로, 값을 1만큼 증가시킵니다), 양식 텍스트 필드에 입력된 값을 지운 후 다시 포커스를 부여하여 다음 추측을 입력할 수 있도록 합니다.
### 이벤트

위의 과정을 통해 `checkGuess()` 함수를 성공적으로 구현했습니다. 하지만 정작 이 함수를 호출하는 곳이 없으니 아직 아무것도 하지 않습니다. "Submit guess" 버튼을 눌렀을 때 `checkGuess()` 함수를 호출하면 이상적이겠네요. **이벤트**를 통해 이 방식을 구현할 수 있습니다. 이벤트란 브라우저에서 발생하는 여러 일들(버튼 클릭, 페이지 로딩, 비디오 재생, ...)입니다. 이벤트가 발생하면 코드 블록이 그 이벤트에 반응해서 실행하도록 설정할 수 있죠. **이벤트 수신기**는 특정 이벤트의 발생을 감지, **이벤트 처리기**를 호출하며 이벤트 처리기가 바로 이벤트에 반응하는 코드 블록입니다.

다음 코드 한 줄을 `checkGuess()` 함수 아래에 추가하세요.

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

위 코드는 `guessSubmit` 버튼에 이벤트 수신기를 추가합니다. `addEventListener()`는 두 개의 입력 값('인자'라고 부릅니다)을 받는 메서드로, 각각 수신할 이벤트 유형(`click`)을 가리키는 문자열과, 이벤트가 발생하면 실행할 코드(`checkGuess()` 함수)입니다. 참고로 `addEventListener()`에 함수를 제공할 때 괄호를 붙이지 않았습니다.

코드를 저장하고 페이지를 다시 불러오세요. 예제가 어느 정도는 동작하는 걸 확인할 수 있을 겁니다. 하지만 아직 하나의 문제가 있는데, 올바른 숫자를 입력하거나 모든 턴을 다 써버리면 게임이 망가진다는 점입니다. 게임이 끝났을 때 실행할 `setGameOver()` 함수를 아직 정의하지 않았기 때문입니다. 빠진 코드를 추가하고, 우리 예제의 기능을 완성해 봅시다.

### 게임 기능 마무리

우선 코드의 맨 아래에 `setGameOver()` 함수를 추가한 뒤에 이 함수가 어떤 일을 하는지 하나씩 살펴봅시다. 지금, 아래의 코드를 여러분의 JavaScript 코드 제일 밑에 덧붙이세요.

```js
function setGameOver() {
  guessField.disabled = true;
  guessSubmit.disabled = true;
  resetButton = document.createElement("button");
  resetButton.textContent = "Start new game";
  document.body.append(resetButton);
  resetButton.addEventListener("click", resetGame);
}
```

* 첫 두 줄은 텍스트 입력 칸과 제출 버튼의 disabled 속성을 `true`로 설정해서 비활성화합니다. 비활성화하지 않으면 게임이 끝난 뒤에도 플레이어가 정답을 추가로 입력해서 게임을 망가뜨릴 수도 있어서 필요한 과정입니다.
* 그 뒤의 세 줄은 새로운 `<button>` 요소를 생성하고 텍스트 레이블을 "Start new game"으로 설정해서 우리의 기존 HTML 아래에 추가합니다.
* 마지막 줄은 위에서 추가한 새 버튼에 이벤트 수신기를 부착해서, 그 버튼을 클릭하면 `resetGame()`이라는 함수를 호출하도록 합니다.

`resetGame()`도 정의해야겠죠? 아래 코드를 다시 한번 JavaScript 코드 밑에 덧붙이세요.

```js
function resetGame() {
  guessCount = 1;

  const resetParas = document.querySelectorAll(".resultParas p");
  for (const resetPara of resetParas) {
    resetPara.textContent = "";
  }

  resetButton.parentNode.removeChild(resetButton);

  guessField.disabled = false;
  guessSubmit.disabled = false;
  guessField.value = "";
  guessField.focus();

  lastResult.style.backgroundColor = "white";

  randomNumber = Math.floor(Math.random() \* 100) + 1;
}
```

모든 것을 게임의 초기 상태로 되돌려, 플레이어가 새로운 게임을 즐길 수 있도록 하는 함수입니다. 길이가 좀 되네요.

* `guessCount`를 다시 1로 낮춥니다.
* 모든 정보 텍스트 문단의 내용을 지웁니다. `<div class="resultParas"></div>` 안의 모든 문단 요소를 선택, 하나씩 순회하면서 각각의 `textContent`를 `''`(빈 문자열)로 설정하는 방식입니다.
* HTML에서 초기화 버튼을 제거합니다.
* 양식 요소를 다시 활성화하고, 입력 칸을 비운 후 포커스를 부여해서 새로운 숫자를 입력받을 준비를 합니다.
* `lastResult` 문단의 배경색을 제거합니다.
* 이전 게임과는 다른 숫자를 맞힐 수 있도록 무작위 숫자를 새로 선택합니다.

**여기까지 왔으면 완전히 동작하는 (간단한) 게임을 완성했습니다. 축하합니다!**

이제, 예제 코드에 나타났었지만 따로 설명하진 않았던 중요한 코드 기능 몇 가지에 대해 설명하고 글을 마치겠습니다.

### 반복

특별히 살펴봐야 할 부분 중 하나는 바로 for...of 반복문입니다. 반복은 프로그래밍에서 매우 중요한 개념으로, 특정 조건을 만족할 때까지 하나의 코드 조각을 계속 실행할 수 있는 방법입니다.

직접 반복을 시험해보려면 브라우저 개발자 도구의 JavaScript 콘솔 (en-US)을 다시 열고, 다음 코드를 입력하세요.

```js
const fruits = ["apples", "bananas", "cherries"];
for (const fruit of fruits) {
  console.log(fruit);
}
```

결과가 어떤가요? 세 문자열 `'apples', 'bananas', 'cherries'`가 콘솔에 출력됐을 겁니다.

이것이 반복입니다. `const fruits = ['apples', 'bananas', 'cherries'];`는 배열이라는 걸 생성합니다. 배열이 뭔지는 이 과정 뒤쪽의 완전한 배열 안내서에서 알아보는 걸로 하고, 지금 당장은 아이템을 모은 컬렉션(위 코드의 경우, 문자열의 컬렉션)이라고 생각하면 충분합니다.

`for...of` 반복문을 사용하면 배열의 각 아이템을 하나씩 가져와서, 지정한 JavaScript 코드를 실행할 때 제공할 수 있습니다. `for (const fruit of fruits)`라는 코드의 뜻은 다음과 같습니다.

1. `fruits`의 첫 번째 아이템을 가져옵니다.
2. `fruit` 변수에 그 아이템을 저장하고, 중괄호(`{ }`) 사이의 코드를 실행합니다.
3. `fruits`의 다음 아이템을 가져온 후 2번으로 돌아갑니다. `fruits`의 모든 아이템을 가져올 때까지 반복합니다.

이때, 중괄호 사이의 코드는 콘솔에 `fruit`을 출력하는 것입니다.

이제 우리의 숫자 알아맞히기 게임 코드에 포함된 반복을 살펴봅시다. `resetGame()` 함수의 아래 부분에 주목하세요.

```js
const resetParas = document.querySelectorAll(".resultParas p");
for (const resetPara of resetParas) {
  resetPara.textContent = "";
}
```

위 코드는 `querySelectorAll()` 메서드를 사용해서 `<div class="resultParas">` 안의 모든 문단 요소를 가져오고, 반복을 통해 각 문단의 텍스트 콘텐츠를 제거합니다.

`resetParas`는 상수지만, 상수의 내부 속성인 `textContent`는 바꿀 수 있는 점에 주의하세요.

### 객체에 관한 짧은 논의

위 제목에 맞는 내용에 들어가기 전에 우리 코드에 마지막으로 한 번의 수정만 더 하겠습니다. 여러분의 JavaScript 위쪽, `let resetButton;` 바로 밑에 아래의 코드를 추가하고 저장하세요.

```js
guessField.focus();
```

`focus()` (en-US) 메서드를 사용해서, 페이지 로딩이 끝나면 텍스트 커서가 자동으로 `<input>` (en-US)에 가도록 하는 코드입니다. 즉, 사용자가 양식의 필드를 직접 클릭할 필요 없이 첫 추측을 바로 시작할 수 있는 겁니다. 사소하긴 하지만, 게임을 어떻게 플레이할 수 있는지 시각적으로 제시해줌으로써 사용성을 강화할 수 있는 좋은 방법입니다.

코드를 약간 더 자세히 분석해봅시다. 여러분이 JavaScript에서 마주치고 조작하는 것의 대부분은 객체입니다. 객체란 서로 연관된 기능을 하나로 묶은 겁니다. 여러분만의 객체도 생성할 수 있지만 꽤 고급 주제니 자세한 내용은 훨씬 뒤쪽 과정에서 알아봅시다. 지금은 여러가지 유용한 일을 해주는, 브라우저 내장 객체에 대해서 간단히 훑어보겠습니다.

우리 코드에서, `guessField` 상수는 HTML의 텍스트 입력 칸을 가리키는 참조를 저장하고 있습니다. 위쪽의 다른 변수 및 상수 선언과 함께 있죠.

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

이 참조를 가져오기 위해 `document` 객체의 `querySelector()` 메서드를 사용하는 모습입니다. `querySelector()`는 하나의 정보 — 참조를 가져오고자 하는 요소를 선택할 수 있는 CSS 선택자를 요구합니다.

이제 `<input>` (en-US) 요소의 참조를 담고 있으니, `guessField`는 다양한 속성(객체 안에 저장된 변수라고 생각할 수 있음, 일부 속성은 값을 바꿀 수 없음)과 메서드(객체 안에 저장된 함수)에 접근할 수 있습니다. 그 메서드 중 하나가 바로 `focus()`로, 다음과 같이 사용해서 텍스트 입력 칸에 포커스를 부여할 수 있습니다.

```js
guessField.focus();
```

양식 요소의 참조를 담지 않는 변수로는 `focus()`라는 메서드를 사용할 수 없을 겁니다. 예를 들어, `guesses` 상수는 `<p>` 요소의 참조를 저장하고, `guessCount` 변수는 숫자를 저장하죠.

### 브라우저 객체 다뤄보기

브라우저 객체를 몇 가지 사용해 봅시다.

1. 우선 브라우저로 숫자 알아맞히기 게임을 여세요.
2. 브라우저 개발자 도구 (en-US)를 열고, JavaScript 콘솔 탭으로 이동하세요.
3. 콘솔에 `guessField`를 입력하면 변수에 `<input>` (en-US) 요소가 저장되어 있는 것을 확인할 수 있습니다. 그리고 콘솔이 여러분의 실행 환경에 존재하는 객체와 변수 이름들을 자동으로 완성해준다는 것도요.
4. 이제 콘솔에 아래 코드를 입력하세요.

```js
guessField.value = 2;
```

`value` 속성은 입력 칸에 현재 입력된 값을 나타내는 속성입니다. 위의 명령을 실행하면, 입력 칸에 입력된 텍스트가 바뀌게 되죠!
5. 콘솔에 `gueesses`를 입력하고 엔터 키로 제출하세요. 콘솔이 변수에 `<p>`가 저장됐다고 알려줄 겁니다.
6. 아래 코드를 입력하세요.

```js
guesses.value;
```

브라우저가 `undefined`를 보여줄 겁니다. 문단에는 `value` 속성이 없기 때문입니다.
7. 문단의 텍스트를 바꾸려면 `textContent` 속성을 사용해야 합니다. 이렇게 해보세요.

```js
guesses.textContent = "Where is my paragraph?";
```

8. 더 재밌는 일도 할 수 있습니다. 아래 코드를 한 줄씩 입력해보세요.

```js
guesses.style.backgroundColor = "yellow";
guesses.style.fontSize = "200%";
guesses.style.padding = "10px";
guesses.style.boxShadow = "3px 3px 6px black";
```

페이지의 모든 요소에는 `style` 속성이 존재합니다. 이 속성은 해당 요소에 지정한 인라인 CSS 스타일을 모두 담고 있는 객체죠. 이걸 활용하면 JavaScript에서 요소의 CSS 스타일을 동적으로 제어할 수 있습니다.
지금은 끝!
------

예제 따라 해보기가 끝났습니다. 마지막까지 오셨군요. 축하합니다! 최종 코드를 직접 실행해 보거나, MDN에서 준비한 최종판을 시험해 보세요. 여러분의 예제 코드가 잘 돌아가지 않으면 완성본의 소스 코드와 비교해 보세요.

* 이전
* Overview: First steps
* 다음


문자열 다루기 — 문자열 - Web 개발 학습하기

문자열 다루기 — 문자열
=============

* 이전
* Overview: First steps
* 다음

다음으로, 프로그래밍에서 어떤 텍스트가 호출되는지 문자열에 대해 알아볼까요? 이 게시물에서는 문자열 작성, 문자열의 따옴표 이스케이프 및 문자열 결합과 같이 JavaScript를 배울 때 문자열에 대해 알아야 할 모든 일반적인 사항을 살펴보겠습니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: |
 기본 컴퓨터 활용능력, HTML 및 CSS에 대한 기본적인 이해, JavaScript에
 대한 기초적인 이해.
  |
| 목표: | JavaScript에서 문자열의 기초에 대해 익숙해지기. |

단어의 힘
-----

단어는 인간이 커뮤니케이션 함에 있어 커다란 하나의 부분이라고 할 수 있기에 매우 중요합니다. 웹은 사람들이 정보를 교환하고 공유할 수 있도록 설계된 텍스트 기반의 매체이므로, 웹에 표시되는 단어를 제어하는 것이 유용합니다. HTML은 텍스트에 구조와 의미를 부여하고, CSS는 텍스트의 스타일을 정밀하게 지정할 수 있게 해주며, JavaScript에는 문자열 조작, 사용자 지정 환영 메시지 및 프롬프트 생성, 필요할 때 올바른 텍스트 레이블 표시, 원하는 순서로 용어 정렬 등을 위한 다양한 기능이 포함되어 있습니다.

지금까지 우리가 여러분에게 보여 줬던 거의 모든 프로그램은 문자열 조작과 관련이 있습니다.

문자열 선언하기
--------

문자열은 언뜻 보기에는 숫자와 비슷하게 다루어지지만, 자세히 살펴보면 몇 가지 눈에 띄는 차이점을 발견할 수 있습니다. 브라우저 개발자 콘솔 (en-US)에 몇 가지 기본 행을 입력하여 익숙해지는 것부터 시작해 보겠습니다.

먼저 다음 줄을 입력합니다.

```js
const string = "The revolution will not be televised.";
console.log(string);
```

숫자와 마찬가지로 변수를 선언하고 문자열 값으로 초기화한 다음 값을 반환합니다. 여기서 유일한 차이점은 문자열을 작성할 때 값을 따옴표로 묶어야 한다는 점입니다.

이 작업을 수행하지 않거나 따옴표 중 하나를 누락하면 오류가 발생합니다. 다음 줄을 입력해 보세요.

```js
const badString1 = This is a test;
const badString2 = 'This is a test;
const badString3 = This is a test';
```

따옴표로 묶이지 않은 텍스트는 변수 이름, 속성 이름, 예약어 등으로 간주되므로 이러한 줄은 작동하지 않습니다. 브라우저에서 찾을 수 없는 경우 오류가 발생합니다(예: "누락됨; before 문"). 두 번째 따옴표로 표시된 것처럼 브라우저가 문자열이 시작되는 위치는 알 수 있지만 문자열의 끝을 찾을 수 없는 경우 오류("종료되지 않은 문자열 리터럴")가 발생합니다. 프로그램에서 이러한 오류가 발생하면 돌아가서 모든 문자열을 확인하여 따옴표가 누락되지 않았는지 확인하세요.

이전에 변수 문자열을 정의한 경우 다음이 작동하므로 지금 시도해 보세요.

```js
const badString = string;
console.log(badString);
```

`badString` 은 이제 `string` 과 같은 값으로 설정되었습니다.

### 따옴표, 쌍따옴표, 백틱

JavaScript에서는 따옴표(`'`), 쌍따옴표(`"`) 또는 백틱(```)을 선택하여 문자열을 감싸을 수 있습니다. 다음 모두 사용할 수 있습니다.

```js
const single = 'Single quotes';
const double = "Double quotes";
const backtick = `Backtick`;

console.log(single);
console.log(double);
console.log(backtick);
```

문자열의 시작과 끝에 같은 문자를 사용해야 하며 그렇지 않으면 오류가 발생합니다.

```js
const badQuotes = 'This is not allowed!";
```

따옴표를 사용하여 선언한 문자열과 쌍따옴표를 사용하여 선언한 문자열은 동일하며 어떤 스타일을 사용할지는 개인 취향에 따라 다르지만, 한 가지 스타일을 선택하여 코드에서 일관되게 사용하는 것이 좋습니다.

백틱을 사용하여 선언된 문자열은 템플릿 리터럴이라고 하는 특수한 종류의 문자열입니다. 템플릿 리터럴은 대부분의 경우 일반 문자열과 비슷하지만 몇 가지 특별한 속성이 있습니다.

* JavaScript 삽입을 할 수 있습니다.
* 여러 줄로 템플릿 리터럴을 선언할 수 있습니다.
JavaScript 삽입
-------------

템플릿 리터럴 내에서 JavaScript 변수나 표현식을 `${ }`로 감싸면 결과가 문자열에 포함됩니다.

```js
const name = "Chris";
const greeting = `Hello, ${name}`;
console.log(greeting); // "Hello, Chris"
```

동일한 기법을 사용하여 두 변수를 결합할 수 있습니다.

```js
const one = "Hello, ";
const two = "how are you?";
const joined = `${one}${two}`;
console.log(joined); // "Hello, how are you?"
```

이렇게 문자열을 서로 연결하는 것을 연결이라고 합니다.

컨텍스트에서 연결
---------

연결이 실제로 어떻게 사용되는지 살펴보겠습니다.

```html
<button>Press me</button>

```

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

function greet() {
  const name = prompt("What is your name?");
  const greeting = document.querySelector("#greeting");
  greeting.textContent = `Hello ${name}, nice to see you!`;
}

button.addEventListener("click", greet);
```

여기서는 팝업 대화 상자를 통해 사용자에게 질문에 대한 답변을 요청한 다음 입력한 텍스트를 지정된 변수(이 경우 이름)에 저장하는 `window.prompt()` 함수를 사용하고 있습니다. 그런 다음 일반 인사말 메시지에 `name`을 삽입하는 문자열을 표시합니다.

### "+"를 이용한 연결

템플릿 리터럴에만 `${}`를 사용할 수 있으며 일반 문자열에는 사용할 수 없습니다. `+`연산자를 사용하여 일반 문자열을 연결할 수 있습니다.

```js
const greeting = "Hello";
const name = "Chris";
console.log(greeting + ", " + name); // "Hello, Chris"
```

하지만 템플릿 리터럴을 사용하면 일반적으로 더 읽기 쉬운 코드를 얻을 수 있습니다.

### 문자열에 표현식 포함

템플릿 리터럴에 변수뿐만 아니라 JavaScript 표현식도 포함할 수 있으며, 그 결과도 결과에 포함됩니다.

```js
const song = "Fight the Youth";
const score = 9;
const highestScore = 10;
const output = `I like the song ${song}. I gave it a score of ${
 (score / highestScore) \* 100
}%.`;
console.log(output); // "I like the song Fight the Youth. I gave it a score of 90%."
```

여러줄 문자열
-------

템플릿 리터럴은 소스 코드의 줄 바꿈을 존중하므로 다음과 같이 여러 줄에 걸쳐 있는 문자열을 작성할 수 있습니다.

```js
const newline = `One day you finally knew
what you had to do, and began,`;
console.log(newline);

/\*
One day you finally knew
what you had to do, and began,
\*/
```

일반 문자열을 사용하여 동일한 출력을 얻으려면 문자열에 줄 바꿈 문자(`\n`)를 포함해야 합니다.

```js
const newline = "One day you finally knew\nwhat you had to do, and began,";
console.log(newline);

/\*
One day you finally knew
what you had to do, and began,
\*/
```

고급 기능에 대한 더 많은 예제와 자세한 내용은 템플릿 리터럴 참조 페이지를 참조하세요.

문자열에 따옴표 포함
-----------

문자열의 시작과 끝을 표시하기 위해 따옴표를 사용하는데, 문자열에 실제 따옴표를 포함하려면 어떻게 해야 할까요? 이것이 작동하지 않는다는 것을 알고 있습니다.

```js
const badQuotes = "She said "I think so!"";
```

한 가지 일반적인 옵션은 다른 문자 중 하나를 사용하여 문자열을 선언하는 것입니다.

```js
const goodQuotes1 = 'She said "I think so!"';
const goodQuotes2 = `She said "I'm not going in there!"`;
```

또 다른 옵션은 문제가 있는 따옴표를 이스케이프하는 것입니다. 문자를 이스케이프 처리한다는 것은 문자가 코드의 일부가 아닌 텍스트로 인식되도록 문자에 어떤 조치를 취한다는 뜻입니다. JavaScript에서는 문자 바로 앞에 백슬래시를 넣어 이 작업을 수행합니다. 이렇게 해보세요.

```js
const bigmouth = 'I\'ve got no right to take my place…';
console.log(bigmouth);
```

동일한 기법을 사용하여 다른 특수 문자를 삽입할 수 있습니다. 자세한 내용은 이스케이프 시퀀스 (en-US)를 참조하세요.

숫자 vs. 문자열
----------

문자열과 숫자를 연결하려고 하면 어떻게 될까요? 콘솔에서 시도해 봅시다.

```js
const name = "Front ";
const number = 242;
console.log(`${name}${number}`); // "Front 242"
```

이 경우 오류가 반환될 것으로 예상할 수 있지만 정상적으로 작동합니다. 숫자를 문자열로 표시하는 방법은 상당히 잘 정의되어 있으므로 브라우저는 숫자를 문자열로 자동 변환하고 두 문자열을 연결합니다.

문자열로 변환하려는 숫자 변수가 있거나 숫자로 변환하려는 문자열 변수가 있는 경우 다음 두 가지 구문을 사용할 수 있습니다.

* `Number()` 함수는 전달된 모든 것을 숫자로 변환할 수 있는 경우 숫자로 변환합니다. 다음을 시도해 보세요:

```js
const myString = "123";
const myNum = Number(myString);
console.log(typeof myNum);
// number
```

* 반대로 `String()` (en-US) 함수는 인수를 문자열로 변환합니다. 이렇게 해보세요.

```js
const myNum2 = 123;
const myString2 = String(myNum2);
console.log(typeof myString2);
// string
```

이러한 구조는 일부 상황에서 매우 유용할 수 있습니다. 예를 들어 사용자가 양식의 텍스트 필드에 숫자를 입력하면 문자열이 됩니다. 하지만 이 숫자를 무언가에 추가하려면 숫자여야 하므로 Number()를 통해 숫자를 전달하여 처리할 수 있습니다. 숫자 맞추기 게임의 59번째 줄에서 정확히 이 작업을 수행했습니다.

마치며
---

지금까지 JavaScript에서 다루는 문자열의 기본에 대해 알아봤습니다. 다음 글에서는 이를 바탕으로 JavaScript에서 문자열에 사용할 수 있는 몇 가지 기본 제공 메서드와 이를 사용하여 문자열을 원하는 형태로 조작하는 방법을 살펴보겠습니다.

* 이전
* Overview: First steps
* 다음