클로저 - JavaScript

클로저
===

**클로저**는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다. 즉, 클로저는 내부 함수에서 외부 함수의 범위에 대한 접근을 제공합니다. JavaScript에서 클로저는 함수 생성 시 함수가 생성될 때마다 생성됩니다.

어휘적 범위 지정(Lexical scoping)
--------------------------

다음을 고려해 봅시다.

```js
function init() {
  var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
  function displayName() {
    // displayName() 은 내부 함수이며, 클로저다.
    console.log(name); // 부모 함수에서 선언된 변수를 사용한다.
  }
  displayName();
}
init();
```

`init()`은 지역 변수 `name`과 함수 `displayName()`을 생성합니다. `displayName()`은
 `init()` 안에 정의된 내부 함수이며 `init()` 함수 본문에서만 사용할 수 있습니다. 여기서 주의할 점은 `displayName()` 내부엔 자신만의 지역 변수가 없다는 점입니다. 그러나, 내부 함수에서 외부 함수의 변수에 접근할 수 있기 때문에, `displayName()` 역시 부모 함수 `init()`에서 선언된 변수 `name`에 접근할 수 있습니다.

이 JSFiddle 링크를 사용하여 코드를 실행하고 `displayName()` 함수 내의 `console.log()` 문이 성공적으로 부모 함수 내에서 선언된 `name` 값을 표시하는지 확인합니다.

이것은 함수가 중첩될 때 구문 분석기가 변수 이름을 확인하는 방법을 설명하는 '정적 스코프'의 예시입니다. 여기서 '정적'이란, 정적 범위 지정 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미하는데, 단어 '정적'은 이런 사실을 나타냅니다. 중첩된 함수는 외부 범위에서 선언한 변수에도 접근할 수 있습니다.

이 특정 예시에서, 범위는 "함수 범위"라고 하는데, 그 이유는 변수에 접근할 수 있고 변수가 선언된 함수 본문 내에서만 접근할 수 있기 때문입니다.

### `let`과 `const`를 사용한 범위 지정

(ES6 이전) 전통적인 JavaScript에는 함수 스코프와 전역 스코프 두 가지만 존재했습니다. `var`로 선언한 변수는 함수 내부 또는 외부에서 선언되었는지에 따라 함수 스코프 또는 전역 스코프를 가지게 됩니다. 이때, 중괄호로 표시된 블록이 스코프를 생성하지 않는다는 점에서 혼란을 일으킬 수 있습니다.

```js
if (Math.random() > 0.5) {
  var x = 1;
} else {
  var x = 2;
}
console.log(x);
```

C나 Java와 같이 블록이 스코프를 생성하는 언어의 경우, 위 코드의 `console.log` 라인에서 `x`가 어떤 블록 스코프에도 포함되지 않기 때문에 에러가 발생해야 할 것입니다. 그러나, 블록은 `var`로 선언한 변수에 대해 스코프를 생성하지 않기 때문에, 여기서 `var` 명령문은 전역 변수를 생성합니다. 이것을 클로저와 함께 사용했을 때 어떤 버그가 발생할 수 있는지 실제 예제가 아래 소개되어 있습니다.

ES6에서, JavaScript는 블록 스코프 변수를 생성할 수 있도록 `let`과 `const` 선언과 함께 시간상 사각지대 등을 도입했습니다.

```js
if (Math.random() > 0.5) {
  const x = 1;
} else {
  const x = 2;
}
console.log(x); // 참조 에러: x가 정의되어 있지 않습니다.
```

요약하자면, ES6부터 블록은 스코프로 취급되기 시작했지만, 이는 `let`과 `const`로 변수를 선언했을 때만 유효합니다. 또한, ES6에서 모듈을 도입하면서 또 다른 스코프를 제공하게 되었습니다. 추후 소개하겠지만, 클로저는 이 모든 스코프의 변수를 캡처할 수 있습니다.

클로저(Closure)
------------

다음 예제를 보겠습니다.

```js
function makeFunc() {
  const name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();
```

이 코드는 전 예제의 `init()` 함수와 완전히 동일한 결과가 실행됩니다. 다른 점(그리고 흥미로운 점)은 `displayName()` 내부 함수가 실행되기 전에 외부 함수에서 반환된다는 점입니다.

한눈에 봐서는, 이 코드가 여전히 작동하는 것이 직관적으로 보이지 않을 수 있습니다. 몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재합니다. `makeFunc()` 실행이 끝나면, `name` 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적이지만, 코드는 여전히 예상대로 작동하기 때문에 JavaScript에서는 분명히 다릅니다.

 그 이유는 JavaScript의 함수가 클로저를 형성하기 때문입니다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합입니다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성됩니다. 예시의 경우, `myFunc`은 `makeFunc`이 실행
 될 때 생성된 `displayName` 함수의 인스턴스에 대한 참조입니다. `displayName`의 인스턴스는 변수 `name` 이 있는 어휘적 환경에 대한 참조를 유지합니다. 이런 이유로, `myFunc`가 호출될 때 변수 `name`은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 `console.log` 에 전달된다.

다음은 조금 더 흥미로운 예제인 `makeAdder` 함수입니다.

```js
function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12
```

이 예제에서, 단일 인자 `x`를 받아서 새 함수를 반환하는 함수 `makeAdder(x)`를 정의합니다. 반환되는 함수는 단일인자 `y`를 받아서 `x`와 `y`의 합을 반환합니다.

본질적으로, `makeAdder`는 함수를 만들어내는 팩토리입니다. 이는 `makeAdder`함수가 특정한 값을 인자로 가질 수 있는 함수들을 리턴한다는 것을 의미합니다. 위의 예제에서, 함수 팩토리는 인자에 5와 10을 더하는 두 개의 새로운 함수들을 만들어 냅니다.

`add5`와 `add10`은 둘 다 클로저입니다. 이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장합니다. 함수 실행 시 `add5`의 어휘적 환경에서, 클로저 내부의 x는 5 이지만, `add10`의 맥락적 환경에서 x는 10입니다.

실용적인 클로저
--------

클로저는 어떤 데이터(어휘적 환경)와 그 데이터를 조작하는 함수를 연관시켜주기 때문에 유용합니다. 이것은 객체가 어떤 데이터와(그 객체의 속성) 하나 혹은 그 이상의 메소드들을 연관시킨다는 점에서 객체지향 프로그래밍과 분명히 같은 맥락에 있습니다.

결론적으로, 오직 하나의 메소드를 가지고 있는 객체를 일반적으로 사용하는 모든 곳에 클로저를 사용할 수 있습니다.

이렇게 할 수 있는 상황은 특히 웹에서 일반적입니다. 프론트엔드 JavaScript에서 우리가 쓰는 많은 코드가 이벤트 기반입니다. 우리는 몇 가지 동작을 정의한 다음 사용자에 의한 이벤트(클릭 혹은 키 누르기 같은)에 연결합니다. 이 코드는 일반적으로 콜백(이벤트에 응답하여 실행되는 단일 함수)으로 추가됩니다.

예를 들면, 페이지의 글자 크기를 조정하는 몇 개의 버튼을 추가한다고 가정합시다. 이 작업을 수행하는 한 가지 방법은 `body` 요소의 font-size를 픽셀 단위로 지정하고 상대적인 `em` 단위를 사용하여 페이지의 다른 요소(예: 헤더)의 크기를 설정하는 것입니다.

```css
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}
```

우리의 대화식 글자 크기 버튼들은 `body` 요소의 `font-size` 속성을 변경할 수 있고, 이런 조정은 상대적 단위들 덕분에 페이지의 다른 요소에 의해 선택됩니다.

여기 JavaScript 코드가 있습니다.

```js
function makeSizer(size) {
  return function () {
    document.body.style.fontSize = `${size}px`;
  };
}

const size12 = makeSizer(12);
const size14 = makeSizer(14);
const size16 = makeSizer(16);
```

`size12`, `size14`, `size16`은 body 요소의 글자 크기를 각각 12, 14, 16 픽셀로 바꾸는 함수입니다. 이 함수들을 다음과 같이 버튼들에(이 경우에는 하이퍼링크) 연결할 수 있습니다.

```js
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
```

```html
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
```

JSFiddle을 실행해보세요.

클로저를 이용해서 비공개 메서드 (private method) 흉내내기
---------------------------------------

자바와 같은 몇몇 언어들은 메서드를 비공개로 선언할 수 있는 기능을 제공합니다. 이는 같은 클래스 내부의 다른 메서드에서만 그 메서드들을 호출할 수 있다는 의미입니다.

classes 이전의 JavaScript에는 비공개 메서드를 선언하는 기본 방법이 없었지만, 클로저를 사용하여 비공개 메서드를 흉내낼 수 있다는 것이 가능했습니다. 비공개 메서드는 코드에 대한 접근을 제한하는 데만 유용한 것이 아닙니다. 또한 전역 이름 공간을 관리하는 강력한 방법을 제공합니다.

아래 코드는 비공개 함수와 변수에 접근하는 퍼블릭 함수를 정의하기 위해 클로저를 사용하는 방법을 보여줍니다. 이렇게 클로저를 사용하는 것을 모듈 디자인 패턴을 따른다고 합니다.

```js
const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.
```

이전 예제에서, 각 클로저들이 고유한 어휘적 환경을 가졌지만, 여기서 우리는 `counter.increment`, `counter.decrement`, `counter.value` 세 함수에 의해 공유되는 하나의 어휘적 환경을 만들어 줍니다.

공유되는 어휘적 환경은 실행되는 익명 함수 안에서 만들어집니다. 이 익명 함수는 정의되는 즉시 실행됩니다(IIFE로 알려져 있습니다). 이 어휘적 환경은 두 개의 비공개 항목을 포함합니다. 하나는 `privateCounter`라는 변수이고, 나머지 하나는 `changeBy`라는 함수입니다. 둘 다 익명 함수 외부에서 접근될 수 없는 비공개 항목입니다. 대신에 익명 래퍼에서 반환된 세 개의 공개 함수를 통해서만 접근할 수 있습니다.

위의 세 가지 공개 함수는 같은 환경을 공유하는 클로저입니다. JavaScript의 어휘적 유효 범위 덕분에, 세 함수 각각 `privateCounter` 변수와 `changeBy` 함수에 접근할 수 있습니다.

```js
const makeCounter = function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
};

const counter1 = makeCounter();
const counter2 = makeCounter();

console.log(counter1.value()); // 0.

counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.

counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.
```

두 카운터가 서로 독립성을 유지하는 방법에 주목하세요. 각 클로저는 자체 클로저를 통해 `privateCounter` 변수의 다른 버전을 참조합니다. 카운터 중 하나가 호출될 때마다, 이 변수의 값을 변경하여 어휘 환경이 변경됩니다. 하나의 클로저에서 변수 값을 변경해도 다른 클로저의 값에는 영향을 미치지 않습니다.

**참고:** 이런 방식으로 클로저를 사용하여 객체지향 프로그래밍의 정보 은닉과 캡슐화 같은 이점들을 얻을 수 있습니다.

클로저 스코프 체인
----------

모든 클로저에는 세가지 스코프(범위)가 있습니다:

* 지역 범위 (Local Scope, Own scope)
* 포함하고 있는 범위 (블록, 함수 또는 모듈 범위일 수 있음)
* 전역 범위 (Global Scope)

일반적으로 하는 실수는 외부 함수 자체가 중첩된 함수인 경우, 외부 함수의 범위에 대한 접근에 외부 함수의 둘러싸고 있는 범위가 포함된다는 사실을 깨닫지 못하는 것입니다. 즉, 효과적으로 함수 범위 체인을 생성합니다. 아래 예제를 확인해보세요.

```js
// 전역 범위 (global scope)
const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      // 외부 함수 범위 (outer functions scope)
      return function (d) {
        // 지역 범위 (local scope)
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20
```

익명 함수 없이 작성할 수도 있습니다.

```js
// 전역 범위 (global scope)
const e = 10;
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // 외부 함수 범위 (outer functions scope)
      return function sum4(d) {
        // 지역 범위 (local scope)
        return a + b + c + d + e;
      };
    };
  };
}

const sum2 = sum(1);
const sum3 = sum2(2);
const sum4 = sum3(3);
const result = sum4(4);
console.log(result); // 20
```

위의 예제를 보면, 일련의 중첩된 함수들을 확인할 수 있습니다. 이 함수들은 전부 외부 함수의 범위에 접근할 수 있습니다. 이 문맥에서는 클로저가 선언된 "모든" 외부 함수의 스코프에 접근한다고 말할 수 있습니다.

클로저는 블록 범위와 모듈 범위에서도 변수를 캡처할 수 있습니다. 예를 들어, 다음은 블록 범위 변수 `y`에 대한 클로저를 생성합니다.

```js
function outer() {
  let getY;
  {
    const y = 6;
    getY = () => y;
  }
  console.log(typeof y); // undefined
  console.log(getY()); // 6
}

outer();
```

모듈에 대한 클로저는 더욱 흥미롭습니다.

```js
// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
  x = val;
};
```

여기에서, 모듈은 모듈 범위 변수 `x`를 닫는 한 쌍의 getter-setter 함수를 내보냅니다. `x`는 다른 모듈에서 직접 접근할 수 없는 경우에도, 함수를 사용하여 읽고 쓸 수 있습니다.

```js
import { getX, setX } from "./myModule.js";

console.log(getX()); // 5
setX(6);
console.log(getX()); // 6
```

클로저는 원래 값이 변경되면, 가져온 값도 그에 따라 변경되기 때문에 "live bindings"로 간주되는 가져온 값을 닫을 수 있습니다.

```js
// myModule.js
export let x = 1;
export const setX = (val) => {
  x = val;
};
```

```js
// closureCreator.js
import { x } from "./myModule.js";

export const getX = () => x;
// 가져온 live binding을 닫습니다.
```

```js
import { getX } from "./closureCreator.js";
import { setX } from "./myModule.js";

console.log(getX()); // 1
setX(2);
console.log(getX()); // 2
```

루프에서 클로저 생성하기: 일반적인 실수
----------------------

`let` 키워드가 도입되기 전에는 클로저와 관련된 일반적인 문제는 루프 안에서 클로저가 생성되었을 때 발생했습니다. 다음 예제를 봅시다.

```html
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>
```

```js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your e-mail address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    // 범인은 이 줄에서 `var`를 사용하는 것입니다.
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();
```

JSFiddle 코드를 실행해보세요.

`helpText` 배열은 세 개의 도움말 힌트를 정의하며, 각 도움말은 문서의 입력 필드의 ID와 연관됩니다. 반복문을 돌면서 각 입력 필드 ID에 해당하는 엘리먼트의 `onfocus` 이벤트에 관련된 도움말을 보여주는 메소드에 연결합니다.

이 코드를 사용하면 제대로 동작하지 않는 것을 알게 됩니다. 어떤 필드에 포커스를 주더라도 나이에 관한 도움말이 표시됩니다.

그 이유는 `onfocus` 이벤트에 연결된 함수가 클로저이기 때문입니다. 이 클로저는 함수 정의와 `setupHelp` 함수 범위에서 캡처된 환경으로 구성됩니다. 반복문에서 세 개의 클로저가 만들어졌지만, 각 클로저는 값이 변하는 변수가 (`item`) 있는 같은 단일 어휘적 환경을 공유합니다. 이는 변수 `item`이 `var`로 선언되어 호이스팅(hoisting)으로 인해 함수 범위가 있기 때문입니다. `item.help`의 값은 `onfocus` 콜백이 실행될 때 결정됩니다. 반복문은 그 시간까지 이미 그 과정을 실행했기 때문에, `item` 변수 객체(세 개의 클로저 모두에서 공유하는)는 `helpText` 목록의 마지막 항목을 가리키고 있습니다.

이 경우 한 가지 해결책은 더 많은 클로저를 사용하는 방법입니다. 특히 앞에서 설명한 함수 팩토리를 사용하는 것입니다.

```js
function showHelp(help) {
  document.getElementById("help").innerHTML = help;
}

function makeHelpCallback(help) {
  return function () {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your e-mail address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();
```

이 JSFiddle 링크를 실행해보세요.

이것은 예상대로 동작합니다. 모두 단일 어휘 환경을 공유하는 콜백 대신에, `makeHelpCallback` 함수는 각각의 콜백에 "새로운 어휘적 환경"을 생성합니다. 여기서, `help`는 `helpText` 배열의 해당 문자열을 나타냅니다.

익명 클로저를 사용하여 위 코드를 작성하는 또 다른 방법은 다음과 같습니다.

```js
function showHelp(help) {
  document.getElementById("help").innerHTML = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your e-mail address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    (function () {
      var item = helpText[i];
      document.getElementById(item.id).onfocus = function () {
        showHelp(item.help);
      };
    })(); // 항목의 현재 값이 포함된 즉각적인 이벤트 리스너 추가(반복될 때까지 보존됩니다).
  }
}

setupHelp();
```

클로저를 더 이상 사용하지 않으려면, `let` 또는 `const` 키워드를 사용할 수 있습니다.

```js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  const helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (let i = 0; i < helpText.length; i++) {
    const item = helpText[i];
    document.getElementById(item.id).onfocus = () => {
      showHelp(item.help);
    };
  }
}

setupHelp();
```

위의 경우 `var` 대신 `const`을 사용하여 모든 클로저가 블록 범위 변수를 바인딩할 것이므로 추가적인 클로저가 필요하지 않습니다.

또 다른 대안은 다음과 같이 `forEach()`를 사용하여 `helpText` 배열을 반복하고 각 `<input>` (en-US)에 리스너를 추가하는 것입니다.

```js
function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your e-mail address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  helpText.forEach(function (text) {
    document.getElementById(text.id).onfocus = function () {
      showHelp(text.help);
    };
  });
}

setupHelp();
```

성능 관련 고려 사항
-----------

앞에서 언급했듯이, 각 함수 인스턴스는 자체 범위와 클로저를 관리합니다. 특정 작업에 클로저가 필요하지 않는데 다른 함수 내에서 함수를 불필요하게 작성하는 것은 처리 속도와 메모리 소비 측면에서 스크립트 성능에 부정적인 영향을 미치기 때문에, 현명하지 않습니다.

예를 들어, 새로운 객체/클래스를 생성할 때, 메소드는 일반적으로 객체 생성자에 정의되기보다는 객체의 프로토타입에 연결되어야 합니다. 그 이유는 생성자가 호출될 때마다 메서드가 다시 할당되기 때문입니다(즉, 모든 객체가 생성될 때마다).

다음 예를 생각해보세요.

```js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}
```

앞의 코드는 특정 인스턴스에서 클로저의 이점을 활용하지 않음으로 다음과 같이 클로저를 사용하지 않도록 다시 쓸 수 있습니다.

```js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName() {
    return this.name;
  },
  getMessage() {
    return this.message;
  },
};
```

그러나, 프로토타입을 다시 정의하는 것은 권장되지 않으므로, 기존 프로토타입에 추가하는 다음 예제가 더 좋습니다.

```js
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function () {
  return this.name;
};
MyObject.prototype.getMessage = function () {
  return this.message;
};
```

앞의 두 가지 예제에서, 상속된 프로토타입은 모든 객체에서 공유될 수 있으며 메서드 정의는 객체 생성시마다 발생할 필요가 없습니다. 자세한 내용은 상속 및 프로토타입 체인을 참고하세요.


JavaScript 언어 개요 - JavaScript

JavaScript 언어 개요
================

JavaScript는 타입 및 연산자, 표준 내장 객체 및 메소드를 포함하는 다중 패러다임 동적 언어입니다. 문법은 Java 및 C 언어를 기반으로 합니다. 해당 언어들의 많은 구조가 JavaScript에도 적용됩니다. JavaScript는 객체 프로토타입 및 클래스를 사용하여 객체 지향 프로그래밍을 지원합니다. 또한 함수는 표현식을 통해 쉽게 생성되고 다른 객체처럼 전달될 수 있는 first-class이므로 함수형 프로그래밍을 지원합니다.

이 페이지는 C 또는 Java와 같은 다른 언어에 대한 배경 지식이 있는 독자를 위해 작성된, 다양한 JavaScript 언어 기능에 대한 간략한 개요를 제공합니다.

데이터 타입
------

모든 언어의 구성 요소인 타입을 살펴보는 것으로 시작하겠습니다. JavaScript 프로그램은 값을 조작하며, 이러한 값은 모두 타입에 속합니다. JavaScript는 7가지 기본 유형을 제공합니다.

* Number: 매우 큰 정수를 제외한 모든 숫자값(정수 및 부동 소수점)에 사용됩니다.
* BigInt: 임의의 큰 정수에 사용됩니다.
* String: 텍스트를 저장하는 데 사용됩니다.
* Boolean: `true` 및 `false`, 일반적으로 조건 논리에 사용됩니다.
* Symbol: 충돌하지 않는 고유 식별자를 만드는 데 사용됩니다.
* Undefined: 변수에 값이 할당되지 않았음을 나타냅니다.
* Null: 의도적으로 값이 없음을 나타냅니다.

다른 모든 것은 객체라고 합니다. 일반적인 객체 타입은 다음과 같습니다.

* `Function`
* `Array`
* `Date`
* `RegExp`
* `Error`

함수는 JavaScript의 특별한 데이터 구조가 아닙니다. 호출할 수 있는 특별한 타입의 객체일 뿐입니다.

### 수 (Numbers)

JavaScript에는 Number와 BigInt라는 두 가지 내장 숫자 타입이 있습니다.

숫자 타입은 이중정밀도 64비트 부동소수점 형식(IEEE 754)의 값이며, 즉, -(253 − 1)와 253 − 1사이에서 정수를 손실 없이 안전하게 표현할 수 있습니다. 정밀도 및 부동 소수점 숫자는 최대 1.79 × 10308까지 저장할 수 있습니다. 숫자 내에서 JavaScript는 부동 소수점 숫자와 정수를 구분하지 않습니다.

```js
console.log(3 / 2); // 1.5, not 1
```

"명백한 정수"는 사실 "암묵적으로 float(실수)"입니다. IEEE 754 인코딩으로 인해 때때로 부동 소수점 산술연산이 정확하지 않을 수 있습니다.

```js
console.log(0.1 + 0.2); // 0.30000000000000004
```

비트 연산과 같이, 정수가 필요한 연산의 경우, 숫자가 32비트 정수로 변환됩니다.

Number 리터럴(literals) (en-US)에는 기본(2진수, 8진수, 10진수 또는 16진수) 또는 지수 접미사를 나타내는 접두사를 가질 수도 있습니다.

```js
console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503
```

BigInt 타입은 임의 길이의 정수입니다. 그 동작은 C의 정수 타입(예: 나누기가 0으로 자르기)과 유사하지만, 무한정 커질수 있다는 점과 다릅니다. BigInt는 숫자 리터럴과 `n` 접미사로 지정됩니다.

```js
console.log(-3n / 2n); // -1n
```

더하기, 빼기, 나머지 산술 등을 포함한 표준 산술 연산자가 지원됩니다. BigInt와 숫자는 산술 연산에서 혼합될 수 없습니다.

`Math` 객체는 표준 수학 함수와 상수를 제공합니다.

```js
Math.sin(3.5);
const circumference = 2 \* Math.PI \* r;
```

문자열을 숫자로 변환하는 세 가지 방법이 있습니다.

* `parseInt()`, 문자열에서 정수를 구문 분석합니다.
* `parseFloat()`, 부동소수점 숫자에 대한 문자열을 구문 분석합니다.
* `Number()` 함수는 문자열을 숫자 리터럴인 것처럼 구문 분석하고 다양한 숫자 표현을 지원합니다.

단항 더하기 `+`를 `Number()`의 줄임말로 사용할 수도 있습니다.

숫자 값에는 `NaN`("Not a Number"의 줄임말) 및 `Infinity`도 포함됩니다. 많은 "잘못된 수학" 연산은 `NaN`을 반환합니다. 예를 들어, 숫자가 아닌 문자열을 구문 분석하려고 하거나 `Math.log()`를 음수 값으로 지정합니다. 0으로 나누면 'Infinity'(양수 또는 음수)가 생성됩니다.

`NaN`은 전염성이 있습니다. 수학 연산의 피연산자로 제공하면 결과도 `NaN`이 됩니다. `NaN`은 자체와 같지 않은 JavaScript의 유일한 값입니다(IEEE 754 명세에 따름).

### 문자열 (Strings)

JavaScript의 문자열은 유니코드 문자의 시퀀스입니다. 이는 국제화를 다루어야 했던 모든 사람에게 반가운 소식이 될 것입니다. 더 정확하게는 UTF-16 encoded를 사용합니다.

```js
console.log("Hello, world");
console.log("你好，世界！"); // 거의 모든 유니코드 문자를 문자 그대로 문자열 리터럴로 작성할 수 있습니다.
```

문자열은 작은따옴표나 큰따옴표로 작성할 수 있습니다. JavaScript는 문자와 문자열을 구분하지 않습니다. 단일 문자를 나타내려면, 해당 단일 문자로 구성된 문자열을 사용하면 됩니다.

```js
console.log("Hello"[1] === "e"); // true
```

문자열의 길이(코드 단위)를 찾으려면, `length` 속성에 접근하세요.

문자열에는 문자열을 조작하고 문자열에 대한 정보에 접근하는 유틸리티 메서드가 있습니다. 모든 원시 타입은 설계 상 변경할 수 없기 때문에, 이러한 메서드는 새 문자열을 반환합니다.

`+` 연산자는 문자열에 대해 오버로드됩니다. 피연산자 중 하나가 문자열이면, 숫자 추가 대신 문자열 연결을 수행합니다. 특별한 템플릿 문자열 구문을 사용하면 표현식이 포함된 문자열을 더 간결하게 작성할 수 있습니다. Python의 f-문자열이나 C#의 보간된 문자열과 달리, 템플릿 리터럴은 백틱(작은따옴표나 큰따옴표가 아님)을 사용합니다.

```js
const age = 25;
console.log("I am " + age + " years old."); // 문자열 연결
console.log(`I am ${age} years old.`); // 템플릿 리터럴
```

### 이외의 타입들

JavaScript는 의도적으로 값이 없음을 나타내는 (그리고 `null` 키워드를 통해서만 접근할 수 있는) 값인 `null`과 값이 없음을 나타내는 `undefined`를 구분합니다. `undefined`을 얻을 수 있는 방법에는 여러 가지가 있습니다.

* 값이 없는 `return`문 (`return;`)은 암시적으로 `undefined`를 반환합니다.
* 존재하지 않는 object (`obj.iDontExist`)에 접근하면 `undefined`를 반환합니다.
* 초기화 없는 변수 선언 (`let x;`)은 암시적으로 변수를 `undefined`로 초기화합니다.

JavaScript에는 `true` 및 `false` 값이 가능한 불리언(Boolean) 타입이 있습니다. 둘 다 키워드입니다. 다음 규칙에 따라 모든 값을 불리언 값으로 변환할 수 있습니다.

1. `false`, `0`, 빈 문자열 (`""`),`NaN`, `null`, 와 `undefined`은 모두 `false`가 됩니다.
2. 다른 모든 값은 `true`가 됩니다.

이 변환은 `Boolean()` 함수를 써서 명시적으로 이 작업을 수행하실 수 있습니다.

```js
Boolean(""); // false
Boolean(234); // true
```

하지만 이렇게 할 필요는 거의 없습니다. JavaScript는 이러한 변환 작업을 `if` 문(제어 구조 참고)과 같이 불리언 값이 필요한 경우를 만나게 되면 자동으로 변환하기 때문입니다. 이러한 이유로 인해 우리는 때때로 불리언 컨텍스트에서 사용될 때 각각 `true`, `false`가 되는 "참 같은 값(truthy)"와 "거짓 같은 값(falsy)" 에 대해 이야기할 것입니다.

불리언 연산자는 논리 연산자 `&&` (논리합), `||` (논리곱), 그리고 `!` (부정)이 지원됩니다. 연산자를 참조하세요.

기호(Symbol) 타입은 종종 고유한 식별자를 만드는 데 사용됩니다. `Symbol()` 함수로 만든 모든 기호(Symbol)는 고유함을 보장합니다. 또한, 공유 상수인 등록된 기호와 특정 작업에 대한 "프로토콜"로 언어에서 사용되는 잘 알려진 기호가 있습니다. 기호 참조에서 자세한 내용을 읽을 수 있습니다.

변수 (Variables)
--------------

JavaScript의 변수는 `let`, `const` 및 `var`의 세 가지 키워드 중 하나를 사용하여 선언됩니다.

`let`을 사용하면 블록 레벨 변수를 선언할 수 있습니다. 선언된 변수는 "변수가 선언된 블록"에서 사용할 수 있습니다.

```js
let a;
let name = "Simon";

// myLetVariable는 여기에서 보이지 \*않습니다\*

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable는 여기에서만 사용할 수 있습니다
}

// myLetVariable는 여기에서 보이지 \*않습니다\*
```

`const`는 값이 변경되지 않을 변수를 선언할 수 있게 합니다. 변수는 "변수가 선언된 블록"에서 사용할 수 있습니다.

```js
const Pi = 3.14; // 변수 Pi 설정
console.log(Pi); // 3.14
```

`const`로 선언된 변수는 재할당할 수 없습니다.

```js
const Pi = 3.14;
Pi = 1; // 상수 변수를 변경할 수 없기 때문에 오류가 발생합니다.
```

`const` 선언은 재할당만 방지합니다. 객체인 경우 변수 값의 변형을 방지하지 않습니다.

```js
const obj = {};
obj.a = 1; // 에러 없음
console.log(obj); // { a: 1 }
```

`var` 선언은 경악할 만한 동작(예: 블록 범위가 아님)을 가질 수 있으며, 최신 JavaScript 코드에서는 권장되지 않습니다.

값을 할당하지 않고 변수를 선언하면, 그 값은 `undefined`입니다. 어차피 나중에 변경할 수 없기 때문에, 초기화해주는 것없이 `const` 변수를 선언할 수 없습니다.

`let` 및 `const` 선언 변수는 여전히 정의된 전체 범위를 차지하며, 실제 선언 줄 이전의 시간상 사각지대(temporal dead zone)으로 알려진 영역에 있습니다. 여기에는 다른 언어에서는 발생하지 않는 변수 섀도잉과 흥미로운 상호 작용이 있습니다.

```js
function foo(x, condition) {
  if (condition) {
    console.log(x);
    const x = 2;
    console.log(x);
  }
}

foo(1, true);
```

대부분의 다른 언어에서, `const x = 2` 행 이전에 `x`가 여전히 상위 범위의 매개변수 `x`를 참조해야 하기 때문에 "1"과 "2"를 기록합니다. JavaScript에서는 각 선언이 전체 범위를 차지하기 때문에 첫 번째 `console.log`에 "초기화 전에 'x'에 접근할 수 없습니다." 라는 오류가 발생합니다. 자세한 내용은 `let` 페이지를 참조하세요.

JavaScript는 동적 타입 언어입니다. 타입 (이전 섹션에서 설명한 대로)은 변수가 아닌 값과만 연결됩니다. `let` 선언 변수의 경우 항상 재할당을 통해 타입을 변경할 수 있습니다.

```js
let a = 1;
a = "foo";
```

연산자
---

JavaScript의 산술 연산자로는 `+`, `-`, `*`, `/`, `%`(나머지 연산자), `**`(지수 연산자)가 있습니다. 값은 `=` 연산자로 할당할 수 있고, `+=` 와 `-=`와 같은 복합 할당 연산자를 통해서도 할당할 수 있습니다. 이렇게 쓰인 연산자는 `x = x 연산자 y`와 같은 결과를 나타냅니다.

```js
x += 5;
x = x + 5;
```

`++` 와 `--` 를 각각 증가와 감소에 사용할 수 있습니다. 이들은 또한 전처리 또는 후처리 연산자로 사용될 수 있습니다.

`+` 연산자는 문자열을 연결합니다.

```js
"hello" + " world"; // "hello world"
```

숫자(또는 다른 값)에 문자열을 추가하면 모든 것이 먼저 문자열로 변환됩니다. 이것은 당신을 넘어뜨릴 수 있습니다.

```js
"3" + 4 + 5; // "345"
3 + 4 + "5"; // "75"
```

무언가에 빈 문자열을 추가하는 것은 해당 값을 문자열로 바꾸는 요령입니다.

JavaScript의 비교는 `<`, `>`, `<=` 및 `>=` 를 사용해 만들 수 있고, 이 연산자들은 문자열과 수 양쪽 모두에서 동작합니다. 동등성을 위해 이중 등호 연산자는 다른 타입을 제공하는 경우 타입 강제 변환을 수행하며, 때로는 흥미로운 결과를 보여줍니다. 반면에, 삼중 등호 연산자 (en-US)는 타입 강제 변환을 시도하지 않으며 일반적으로 선호하는 방법입니다.

```js
123 == "123"; // true
1 == true; // true

123 === "123"; // false
1 === true; // false
```

이중 등호와 삼중 등호에는 `!=` 와 `!==` 같은 부등호도 있습니다.

JavaScript에는 비트 연산자 및 논리 연산자도 있습니다. 특히 논리 연산자는 불리언 값으로만 작동하지 않고 값의 "진실성"에 따라 작동합니다.

```js
const a = 0 && "Hello"; // 0은 "falsy" 이라서, 0입니다.
const b = "Hello" || "world"; // "Hello"와 "world" 모두 "truthy"이라서, "Hello" 입니다.
```

`&&` 및 `||` 연산자는 단락 논리를 사용합니다. 즉, 두 번째 피연산자를 실행할지 여부는 첫 번째 피연산자에 따라 결정됩니다. 이는 속성에 접근하기 전에 null 객체를 확인하는 데 유용합니다.

```js
const name = o && o.getName();
```

또는 캐싱 값의 경우(거짓 값이 유효하지 않은 경우)

```js
const name = cachedName || (cachedName = getName());
```

전체 연산자 목록은 가이드 페이지 또는 참조 섹션을 참조하세요. 특히 연산자 우선순위에 관심이 있을 수도 있습니다.

문법
--

JavaScript 문법은 C 계열과 매우 유사합니다. 언급할 가치가 있는 몇 가지 사항이 있습니다.

* 식별자 (en-US)는 유니코드 문자를 포함할 수 있지만, 예약어 (en-US) 중 하나일 수는 없습니다.
* 주석 (en-US)은 일반적으로 `//` 또는 `/* */`을 사용합니다. `#`을 사용하는 Perl, Python, and Bash와 같은 다른 많은 스크립팅 언어와는 다릅니다.
* 세미콜론은 JavaScript에서 선택 사항입니다. 언어는 필요할 때 자동으로 삽입 (en-US)합니다. 그러나 Python과 달리 세미콜론은 여전히 문법의 일부이므로 주의해야 할 사항이 있습니다.

JavaScript 문법에 대한 자세한 내용은 어휘 문법 참조 페이지 (en-US)를 참조하세요.

제어 구조
-----

JavaScript는 C 계열의 다른 언어들과 비슷한 제어 구조를 가지고 있습니다. 조건문은 `if` 및 `else`로 지원되며, 함께 연결할 수 있습니다.

```js
let name = "kittens";
if (name === "puppies") {
  name += " woof";
} else if (name === "kittens") {
  name += " meow";
} else {
  name += "!";
}
name === "kittens meow";
```

JavaScript에는 `elif`가 없으며, `else if`는 단일 `if` 문으로 구성된 `else` 분기에 불과합니다.

JavaScript는 `while` 반복문과 `do...while` 반복문도 사용할 수 있습니다. 전자는 기본 반복에 유용하게 사용할 수 있고, 후자는 반복문을 적어도 한번 이상은 실행하고 싶을 때 사용할 수 있습니다.

```js
while (true) {
  // 무한루프!
}

let input;
do {
  input = get\_input();
} while (inputIsNotValid(input));
```

JavaScript의 `for` 반복문은 C와 Java의 반복문과 같습니다. 말하자면, 반복문에 필요한 제어 정보를 한 줄에 표현할 수 있다는 이야기지요.

```js
for (let i = 0; i < 5; i++) {
  // 내부 동작을 5번 반복합니다
}
```

JavaScript에는 두개의 중요한 for 반복문 또한 포함됩니다. iterables(특히 배열)를 반복하는 `for...of`, 객체의 모든 enumerable (en-US) 속성을 방문하는 `for...in`입니다.

```js
for (const value of array) {
  // value로 작업 수행합니다.
}

for (const property in object) {
  // 객체 프로퍼티로 작업 수행합니다.
}
```

`switch` 문은 동등 검사를 기반으로 여러 분기에 사용할 수 있습니다.

```js
switch (action) {
  case "draw":
    drawIt();
    break;
  case "eat":
    eatIt();
    break;
  default:
    doNothing();
}
```

C와 유사하게, case 절은 labels과 개념적으로 동일하므로, `break` 문을 추가하지 않으면 실행이 다음 단계로 "넘어갑니다". 그러나 이들은 실제로 점프 테이블이 아닙니다. 문자열이나 숫자 리터럴 뿐만 아니라, 모든 표현식이 `case`절의 일부가 될 수 있으며, 하나가 일치하는 값과 같을 때까지 하나씩 평가됩니다. `===` 연산자를 사용하여 둘 사이에서 비교가 이루어집니다.

Rust와 같은 일부 언어와 달리, 제어 흐름 구조는 JavaScript의 문입니다. 즉, `const a = if (x) { 1 } else { 2 }`와 같이 변수에 할당할 수 없습니다.

JavaScript 오류는 `try...catch` (en-US)문을 사용하여 처리됩니다.

```js
try {
  buildMySite("./website");
} catch (e) {
  console.error("Building site failed:", e);
}
```

에러(Error)는 `throw` 문을 사용하여 발생할 수 있습니다. 많은 기본 제공 작업도 에러가 발생할 수 있습니다.

```js
function buildMySite(siteDirectory) {
  if (!pathExists(siteDirectory)) {
    throw new Error("Site directory does not exist");
  }
}
```

일반적으로, 방금 잡은 오류의 타입을 알 수 없습니다. `throw` 문에서 무엇이든 발생할 수 있기 때문입니다. 그러나 일반적으로 위의 예와 같이 `Error` 인스턴스라고 가정할 수 있습니다. `TypeError` 및 `RangeError`와 같은 `Error` 내장 하위 클래스가 있는데, 오류에 대한 추가적인 의미체계를 제공하는 데 사용할 수 있습니다. JavaScript에는 조건부 포착이 없습니다. 한 가지의 타입의 오류만 처리하려면, 모든 것을 포착하고 `instanceof`를 사용해 오류 타입을 식별해야 하고, 그런 다음 다른 case를 다시 던져야 합니다.

```js
try {
  buildMySite("./website");
} catch (e) {
  if (e instanceof RangeError) {
    console.error("Seems like a parameter is out of range:", e);
    console.log("Retrying...");
    buildMySite("./website");
  } else {
    // 다른 에러 타입을 처리하는 방법을 모릅니다.
    // 호출 스택의 다른 부분에서 에러를 잡아서 처리할 수 있도록 던지세요.
    throw e;
  }
}
```

호출 스택의 `try...catch`에서 에러를 포착하지 못하면, 프로그램이 종료됩니다.

제어 흐름문의 전체 목록은 참조 섹션을 참조하세요.

객체 (Objects)
------------

JavaScript 객체는 간단히 이름-값 쌍(name-value pairs)의 모임입니다. 따라서 JavaScript의 객체의 모임은 다음과 비슷합니다.

* Python의 Dictionaries.
* Perl과 Ruby의 Hashes.
* C와 C++ 의 Hash tables.
* Java의 HashMaps.
* PHP의 연관 배열(Associative arrays).

JavaScript의 객체는 해시(hashes)입니다. 정적으로 타입이 지정된 언어의 객체와 달리, JavaScript의 객체는 모양이 고정되어 있지 않습니다. 속성은 언제든지 추가, 삭제, 재정렬, 변경 또는 동적으로 쿼리할 수 있습니다. 객체 키는 항상 strings 또는 기호(symbols)입니다. 정수인 배열 인덱스도 실제로는 문자열입니다.

객체는 일반적으로 리터럴 구문을 사용하여 생성됩니다.

```js
const obj = {
  name: "Carrot",
  for: "Max",
  details: {
    color: "orange",
    size: 12,
  },
};
```

객체 속성은 점(`.`) 또는 대괄호(`[]`)를 사용하여 접근할 수 있습니다. 점 표기법을 사용할 때, 키는 유효한 식별자 (en-US)여야 합니다. 반면 대괄호를 사용하면 동적 키 값으로 객체를 인덱싱할 수 있습니다.

```js
// 점 표기법
obj.name = "Simon";
const name = obj.name;

// 대괄호 표기법
obj["name"] = "Simon";
const name = obj["name"];

// 변수를 사용하여 키를 정의할 수 있습니다.
const userName = prompt("what is your key?");
obj[userName] = prompt("what is its value?");
```

속성 접근은 함께 연결할 수 있습니다.

```js
obj.details.color; // orange
obj["details"]["size"]; // 12
```

객체는 항상 참조이므로, 무언가 명시적으로 객체를 복사하지 않는 한, 객체에 대한 변형은 외부에서 볼 수 있습니다.

```js
const obj = {};
function doSomething(o) {
  o.x = 1;
}
doSomething(obj);
console.log(obj.x); // 1
```

이는 또한 별도로 생성된 두 객체가 서로 다른 참조이기 때문에 결코 같지 않음(`!==`)을 의미합니다. 동일한 객체에 대한 두 개의 참조를 보유하는 경우, 하나를 변경하면 다른 하나를 통해 관찰할 수 있습니다.

```js
const me = {};
const stillMe = me;
me.x = 1;
console.log(stillMe.x); // 1
```

객체 및 프로토타입에 대한 자세한 내용은, `객체` 참조 페이지를 참조하세요. 객체 초기화 구문에 대한 자세한 내용은 참조 페이지를 참고하세요.

일반적으로 (난해하다고 들었을 수 있는) 기본 메커니즘을 건드리지 않고 클래스를 사용하여 상속을 달성할 수 있기 때문에, 이 페이지에서는 객체 프로토타입 및 상속에 대한 모든 세부 정보를 생략했습니다. 자세한 내용은 상속 및 프로토타입 체인을 참조하세요.

배열 (Arrays)
-----------

JavaScript에서 배열은 실제로는 특별한 타입의 객체입니다. (숫자로 나타낸 속성은 자연스럽게 `[]` 구문만을 사용해서 접근하게 되므로) 일반 객체와 많이 비슷하게 동작하지만, 이 객체는 `length`라는 한가지 마법적인 속성을 가집니다. 이는 항상 배열에서 가장 큰 인덱스보다 하나 더 큰 값을 가집니다.

배열을 일반적으로 배열 리터럴로 생성됩니다.

```js
const a = ["dog", "cat", "hen"];
a.length; // 3
```

JavaScript 배열은 여전히 객체입니다. 임의의 숫자 인덱스를 포함하여 배열에 속성을 할당할 수 있습니다. 유일한 "마법"은 특정 인덱스를 설정할 때 `length`가 자동으로 업데이트 된다는 것입니다.

```js
const a = ["dog", "cat", "hen"];
a[100] = "fox";
console.log(a.length); // 101
console.log(a); // ['dog', 'cat', 'hen', empty × 97, 'fox']
```

위에서 얻은 배열을 희소 배열(*sparse array*)라고 합니다. 중간에 비어있는 슬롯이 있기 때문이며 엔진이 배열에서 해시 테이블로 배열을 최적화하지 않기 때문입니다. 배열이 밀집되어 있는지 확인하세요!

범위를 벗어난 인덱싱은 발생하지 않습니다. 존재하지 않는 배열 인덱스를 참조하려고 하면 `undefined`값이 반환됩니다.

```js
const a = ["dog", "cat", "hen"];
console.log(typeof a[90]); // undefined
```

배열은 모든 요소를 가질 수 있으며 임의로 확장하거나 축소할 수 있습니다.

```js
const arr = [1, "foo", true];
arr.push({});
// arr = [1, "foo", true, {}]
```

배열은 다른 C 유사 언어에서와 마찬가지로 `for` 루프로 반복될 수 있습니다.

```js
for (let i = 0; i < a.length; i++) {
  // a[i]을 가지고 작업 진행
}
```

배열은 또한 반복 가능하므로, C++/Java의 `for (int x : arr)` 문법와 동일한 `for...of` 루프를 사용할 수 있습니다.

```js
for (const currentValue of a) {
  // currentValue를 가지고 작업 진행
}
```

배열에는 배열 메서드가 많이 있습니다. 대부분은 배열을 순회합니다. 예를 들어, `map()`은 모든 배열 요소에 콜백을 적용하고 새 배열을 반환합니다.

```js
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
// babies = ['baby dog', 'baby cat', 'baby hen']
```

함수 (Functions)
--------------

객체와 마찬가지로, 함수는 JavaScript를 이해하는데 핵심이 되는 컴포넌트입니다. 가장 기본적인 함수의 예시는 다음과 같습니다.

```js
function add(x, y) {
  const total = x + y;
  return total;
}
```

JavaScript 함수는 0개 이상의 이름이 있는 매개변수를 가질 수 있습니다. 함수의 본체에는 원하는 만큼 구문을 작성할 수 있고 해당 함수에 지역적인 변수를 선언할 수 있습니다. `return` 문은 언제든지 값을 반환하고 함수를 종료할 수 있습니다. 반환 문이 없으면 (혹은 값이 없는 반환이 사용되면), JavaScript는 `undefined`을 반환합니다.

매개변수로 지정된 것보다 많거나 적은 변수를 사용해서도 함수를 호출할 수 있습니다. 예상되는 매개변수를 전달하지 않고 함수를 호출하면 `undefined`로 설정됩니다. 예상보다 많은 매개변수를 전달하면 추가로 전달되는 매개변수를 무시합니다.

```js
add(); // NaN
// add(undefined, undefined)와 동등합니다.

add(2, 3, 4); // 5
// 처음의 두 수가 더해집니다. 4는 무시됩니다.
```

사용할 수 있는 다른 여러 매개변수 구문이 있습니다. 예를 들어, rest 매개변수 구문을 사용하면 Python의 `*args`와 유사하게 호출자가 전달한 모든 추가 매개변수를 배열로 수집할 수 있습니다(JS는 언어 수준에서 명명된 매개변수가 없기 때문에, `**kwargs`가 없습니다).

```js
function avg(...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5
```

위의 코드에서 `args` 변수는 함수에 전달된 모든 값을 보유합니다.

나머지 매개변수(rest parameter)는 선언된 이후의 모든 인수를 저장하지만, 이전에는 저장하지 않습니다. 즉, `function avg(firstValue, ...args)`는 함수에 전달된 첫 번째 값을 `firstValue` 변수에 저장하고 나머지 인수는 `args`에 저장합니다.

함수가 인수 목록을 허용하고 이미 배열을 보유하고 있는 경우, 함수 호출에서 전개 연산자 문법(spread syntax)을 사용하여 배열을 요소 목록으로 *spread* 할 수 있습니다. 예를 들어, `avg(...numbers)`

JavaScript에는 명명된 매개 변수가 없다고 언급했습니다. 하지만 객체를 편리하게 하나로 합치고, 분해할 수 있는 구조 분해 할당을 사용하여 구현할 수 있습니다.

```js
// 중괄호({ })에 유의하세요. 객체를 분해합니다.
function area({ width, height }) {
  return width \* height;
}

// 여기서 중괄호({ })는 새로운 객체를 생성합니다.
console.log(area({ width: 2, height: 3 }));
```

기본값 매개변수 구문도 있는데, 생략된 매개변수(또는 `undefined`로 전달된 매개변수)가 기본값을 갖도록 허용합니다.

```js
function avg(firstValue, secondValue, thirdValue = 0) {
  return (firstValue + secondValue + thirdValue) / 3;
}

avg(1, 2); // NaN 대신, 1입니다.
```

### 익명 함수

JavaScript에서는 익명 함수(이름이 없는 함수)를 만들 수 있습니다. 실제로, 이런 이름없는 함수들은 다른 함수의 인자로 전달하거나 함수를 호출하는 데 사용할 수 있는 변수에 즉시 할당되거나 다른 함수에서 반환됩니다.

```js
// 괄호 앞에 함수명이 없음을 주목해주세요.
const avg = function (...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};
```

위에 정의된 익명 함수는 인수와 함께 `avg()`와 같은 형태로 실행할 수 있습니다. 즉, `function avg() {}`와 같이 이름을 붙인 함수 선언과 의미적으로 같습니다.

화살표 함수를 사용하여 익명 함수를 정의하는 또 다른 방법이 있습니다.

```js
// 괄호 앞에 함수명이 없음을 주목해주세요.
const avg = (...args) => {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

// 단순히 표현식을 반환할 때, `return`을 생략할 수 있습니다.
const sum = (a, b, c) => a + b + c;
```

화살표 함수는 의미적으로 함수 표현식과 동일하지 않습니다. 자세한 내용은 참조 페이지를 참조하세요.

익명 함수가 유용할 수 있는 또 다른 방법이 있습니다. 즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expressions)이라는 단일 표현식에서 익명 함수를 동시에 선언하고 호출할 수 있습니다.

```js
(function () {
  // …
})();
```

IIFE의 사용 사례에 대해서는 클로저를 이용해서 프라이빗 메서드 흉내내기을 참조하세요.

### 재귀 함수

JavaScript는 재귀적으로 함수를 호출할 수 있습니다. 이는 브라우저 DOM 등에서 볼 수 있는 트리 구조를 다루는데 유용합니다.

```js
function countChars(elm) {
  if (elm.nodeType === 3) {
    // TEXT\_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += countChars(child);
  }
  return count;
}
```

함수 표현식에도 이름을 지정할 수 있으므로, 재귀적일 수 있습니다.

```js
const charsInBody = (function counter(elm) {
  if (elm.nodeType === 3) {
    // TEXT\_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += counter(child);
  }
  return count;
})(document.body);
```

위와 같이 함수 표현식에 제공된 이름은 함수 자체 범위에서만 사용할 수 있습니다. 이를 통해 엔진에서 더 많은 최적화를 수행할 수 있으며, 결과적으로 더 읽기 쉬운 코드가 생성됩니다. 이 이름은 디버거와 일부 스택 추적에도 표시되므로, 디버깅 시 시간을 절약할 수 있습니다.

함수형 프로그래밍에 익숙하다면 JavaScript에서 재귀가 성능에 미치는 영향에 주의하세요. 언어 명세에는 꼬리 호출 최적화(tail-call optimization)로 지정되어 있지만, 스택 추적 및 디버깅의 어려움으로 인해 JavaScriptCore (Safari에서 사용)에서만 구현했습니다. 깊은 재귀의 경우 스택 오버플로우를 방지하기 위해, 대신 반복을 사용하는 것이 좋습니다.

### 함수는 일급 객체(first-class objects)입니다

JavaScript 함수는 일급 객체(first-class objects)입니다. 즉, 변수에 할당하고 다른 함수에 인수로 전달하고 다른 함수에서 반환할 수 있습니다. 또한, JavaScript는 명시적인 캡처 없이 즉시 사용 가능한 클로저(closures)를 지원하므로 함수형 프로그래밍 스타일을 편리하게 적용할 수 있습니다.

```js
// 함수를 반환하는 함수
const add = (x) => (y) => x + y;
// 함수를 매개변수로 받는 함수
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
```

JavaScript 함수는 JavaScript의 다른 모든 것과 마찬가지로 그 자체로 객체이며, 이전 객체 섹션에서 본 것처럼 속성을 추가하거나 변경할 수 있습니다.

### 내장 함수 (Inner functions)

JavaScript의 함수 선언은 다른 함수 내부에서도 가능합니다. JavaScript의 중첩 함수에서는 부모 함수 범위에 있는 변수에 접근이 가능하다는 게 중요합니다.

```js
function parentFunc() {
  const a = 1;

  function nestedFunc() {
    const b = 4; // parentFunc은 사용할 수 없는 변수
    return a + b;
  }
  return nestedFunc(); // 5
}
```

이런 특성은 유지보수가 용이한 코드를 만드는데 도움이 됩니다. 호출된 함수가 다른 한두 개의 함수에서만 호출되며 그 외의 다른 코드에서는 사용이 안되는 경우, 해당 함수를 내부에 중첩시킬 수 있습니다. 이런 방법을 통해 전역 범위에 함수의 개수가 줄어듭니다.

또한 전역 변수에 대한 유혹을 뿌리칠 수 있는 좋은 대안이 됩니다. 복잡한 코드를 작성하게 될 때면 여러 함수들 간에 값을 공유하기 위한 용도로 전역 변수를 사용하고 싶어지지만, 전역 변수는 유지보수를 힘들게 합니다. 이런 상황에 중첩 함수는 부모의 변수를 공유함으로써 전역 이름공간을 더럽히지 않고 함수들을 연동할 수 있습니다.

클래스
---

JavaScript는 Java와 매우 유사한 class 문법을 제공합니다.

```js
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return `Hello, I'm ${this.name}!`;
  }
}

const p = new Person("Maria");
console.log(p.sayHello());
```

JavaScript 클래스는 `new` 연산자로 인스턴스화해야 하는 함수일 뿐입니다. 클래스가 인스턴스화될 때마다, 클래스가 지정한 메서드와 속성을 포함하는 객체를 반환합니다. 클래스는 코드 구성을 강제하지 않습니다. 예를 들어, 클래스를 반환하는 함수가 있거나, 파일 당 여러 클래스가 있을 수 있습니다. 다음은 클래스 생성이 어떻게 임시방편일 수 있는지에 대한 예시입니다. 단지 화살표 함수에서 반환된 표현식일 뿐이고, 이 패턴을 믹스인(mixin)이라고 합니다.

```js
const withAuthentication = (cls) =>
  class extends cls {
    authenticate() {
      // …
    }
  };

class Admin extends withAuthentication(Person) {
  // …
}
```

정적 속성은 `static`을 추가하여 생성됩니다. Private 속성은 hash(`#`) (`private` 키워드가 아닙니다)를 앞에 추가하여 생성됩니다. 이 해시는 속성 이름의 필수적인 부분입니다. (Python에서 `#`을 `_`로 생각하세요.) 대부분의 다른 언어와 달리, 클래스 본체 외부에서 Private 속성을 읽을 수 있는 방법이 전혀 없습니다. 파생 클래스(derived classes)에서도 마찬가지입니다.

다양한 클래스 기능에 대한 자세한 안내는 guide page (en-US)를 참조하세요.

비동기 프로그래밍
---------

JavaScript는 본질적으로 단일 스레드입니다. 병렬화가 없고, 동시성만 가능합니다. 비동기 프로그래밍은 이벤트 루프에 의해 구동되며, 이를 통해 일련의 작업을 대기하고 완료를 위해 폴링(polling)할 수 있습니다.

JavaScript에서 비동기 코드를 작성하는 세 가지의 관용적인 방법이 있습니다.

* 콜백 기반 방법(예: `setTimeout()`)
* `Promise` 기반 방법
* Promise의 문법적인 설탕(syntactic sugar)인 `async`/`await` 방법

예를 들어, 파일 읽기 작업이 JavaScript에서 다음과 같이 표시될 수 있습니다.

```js
// 콜백 기반(Callback-based)
fs.readFile(filename, (err, content) => {
  // 이 콜백은 파일을 읽을 때 호출되며 잠시 후에 호출될 수 있습니다.
  if (err) {
    throw err;
  }
  console.log(content);
});
// 파일 읽기를 기다리는 동안 여기의 코드가 실행됩니다.

// 프로미스 기반(Promise-based)
fs.readFile(filename)
  .then((content) => {
    // 파일을 읽을 때 수행할 작업
    console.log(content);
  })
  .catch((err) => {
    throw err;
  });
// 파일 읽기를 기다리는 동안 여기의 코드가 실행됩니다.

// Async/await 기반
async function readFile(filename) {
  const content = await fs.readFile(filename);
  console.log(content);
}
```

언어의 중요한 부분은 어떠한 비동기 프로그래밍 기능을 특정하지 않지만, 사용자 권한 요청부터, 데이터 가져오기 및 파일 읽기까지 외부 환경과 상호작용을 할 때 중요합니다. 잠재적으로 오래 실행되는 작업을 비동기식으로 유지하면, 이 프로세스가 대기하는 동안 다른 프로세스가 계속 실행될 수 있습니다. 예를 들어, 사용자가 권한을 부여하기 위해 버튼을 클릭하기를 기다리는 동안, 브라우저가 정지되지 않습니다.

비동기 값이 있는 경우, 해당 값을 동기적으로 가져올 수 없습니다. 예를 들어, 프로미스(Promise)가 있는 경우, `then()` 메서드를 통해서만 최종 결과에 접근할 수 있습니다. 마찬가지로, `await`는 일반적으로 비동기 함수 또는 모듈인, 비동기 컨텍스트(맥락)에서만 사용할 수 있습니다. Promise는 절대로 "차단"되지 않습니다. Promise의 결과에 따라 달라지는 논리만 연기됩니다. 그 동안 다른 모든 작업은 계속 실행됩니다. 함수형 프로그래머라면, Promise를 `then()`으로 매핑할 수 있는 모나드(monads)로 생각할 수 있습니다(하지만, 적절한 모나드가 아닌 이유는 자동 평면화되기 때문입니다. 즉, `Promise<Promise<T>>`를 가질 수 없습니다).

사실, 단일 스레드 모델은 Non-Blocking IO로 인해 Node.js를 서버 측 프로그래밍에 널리 선택하게 하여, 많은 수의 데이터베이스 또는 파일 시스템 요청을 매우 효율적으로 처리할 수 있게 합니다. 그러나, 순수한 JavaScript인 CPU 바인딩(계산 집약적) 작업은 여전히 메인 스레드를 차단합니다. 실제 병렬화를 달성하려면 workers (en-US)를 사용해야 할 수도 있습니다.

비동기 프로그래밍에 대해 자세히 알아보려면, promises 사용에 대해 읽어보거나 비동기 JavaScript 자습서를 읽어보세요.

모듈
--

또한 JavaScript는 대부분의 런타임에서 지원하는 모듈 시스템을 사용합니다. 모듈은 일반적으로 파일 경로 또는 URL로 식별되는 파일입니다. `import` 및 `export`를 모듈 간에 데이터를 교환하는 명령문으로 사용할 수 있습니다.

```js
import { foo } from "./foo.js";

// export 하지 않은 변수는 모듈 내에서 지역 변수 범위를 가집니다.
const b = 2;

export const a = 1;
```

Haskell, Python, Java 등과 달리 JavaScript 모듈 해석은 전적으로 호스트 정의입니다. 일반적으로 URL 또는 파일 경로를 기반으로 하므로, 상대 파일 경로는 "작동"하고 일부 프로젝트 루트 경로 대신 현재 모듈의 경로에 상대적입니다.

그러나, JavaScript 언어는 표준 라이브러리 모듈을 제공하지 않습니다. 대신 모든 핵심 기능은 `Math` 및 `Intl` (en-US)과 같은 전역 변수로 구동됩니다. 이것은 모듈 시스템이 없는 JavaScript의 오랜 역사와, 모듈 시스템을 선택한다면 런타임 설정에 대한 일부 변경이 포함된다는 사실 때문입니다.

다른 런타임은 다른 모듈 시스템을 사용할 수 있습니다. 예를 들어, Node.js는 패키지 관리자 npm을 사용하고, 대부분 파일 시스템 기반인 반면, Deno 및 브라우저는 완전한 URL 기반이며 모듈은 HTTP URL에서 확인할 수 있습니다.

자세한 내용은 모듈 가이드 페이지를 참조하세요.

언어 및 런타임
--------

이 페이지 전체에 걸쳐, 특정 기능은 "언어 수준"이고 다른 기능은 "런타임 수준"이라고 지속적으로 언급이 되었습니다.

JavaScript는 범용 스크립팅 언어입니다. 핵심 언어 명세은 순수한 계산 논리에 중점을 두고 있습니다. 입출력을 처리하지 않습니다. 사실, 추가 런타임 수준의 API(특히 `console.log()`) 없이, JavaScript 프로그램의 동작은 완전히 관찰할 수 없습니다.

런타임 또는 호스트는 JavaScript 엔진(인터프리터)에 데이터를 공급하고 추가 전역 속성을 제공하며, 엔진이 외부 세계와 상호 작용할 수 있도록 훅을 제공합니다. 모듈 확인, 데이터 읽기, 메시지 인쇄, 네트워크 요청 보내기 등은 모두 런타임 수준 작업입니다. JavaScript는 처음부터 브라우저(DOM과 같은 API를 제공합니다), Node.js(파일 시스템 접근과 같은 API를 제공합니다)와 같은 다양한 환경에서 채택되었습니다. JavaScript는 (가장 주요 목적인) 웹, 모바일 앱, 데스크탑 앱, 서버 측 앱, 서버리스, 임베디드 시스템 등에 성공적으로 통합되었습니다. JavaScript 핵심 기능에 대해 배우는 동안, 지식을 사용하기 위해 호스트에서 제공하는 기능을 이해하는 것도 중요합니다. 예를 들어, 브라우저 및 경우에 따라 비브라우저에 의해 구현되는 모든 웹 플랫폼 API에 대해 읽어볼 수 있습니다.

추가적인 탐색
-------

이 페이지는 다양한 JavaScript 기능이 다른 언어와 어떻게 비교되는지에 대한 매우 기본적인 통찰력을 제공합니다. 언어 자체와 각 기능의 뉘앙스에 대해 자세히 알아보려면, JavaScript 자습서 및 JavaScript 참고서에 대해 읽어보세요.

작성 공간과 복잡성으로 인해 생략된 언어의 일부 필수 부분이 있지만, 직접 찾아볼 수 있습니다.

* 상속과 프로토타입 체인
* 클로저(Closures)
* 정규표현식
* 반복(Iteration)


이벤트 루프 - JavaScript

이벤트 루프
======

JavaScript의 런타임 모델은 코드의 실행, 이벤트의 수집과 처리, 큐에 대기 중인 하위 작업을 처리하는 **이벤트 루프**에 기반하고 있으며, C 또는 Java 등 다른 언어가 가진 모델과는 상당히 다릅니다.

런타임 개념
------

여기서는 이론적인 모델만을 설명합니다. 현대적인 JavaScript 엔진은 아래의 개념을 구현하고 고도로 최적화합니다.

### 시각적 표현

![스택, 힙, 큐](/ko/docs/Web/JavaScript/Event_loop/the_javascript_runtime_environment_example.svg)

### 스택

함수의 호출들은 '프레임' 스택을 형성합니다.

```js
function foo(b) {
  const a = 10;
  return a + b + 11;
}

function bar(x) {
  const y = 3;
  return foo(x \* y);
}

const baz = bar(7); // 42를 baz에 할당
```

위 코드의 실행 순서는 다음과 같습니다.

1. `bar`를 호출할 때, `bar`의 인수와 지역 변수를 포함하는 첫 번째 프레임이 생성됩니다.
2. `bar`가 `foo`를 호출할 때, `foo`의 인수와 지역 변수를 포함하는 두 번째 프레임이 생성되어 첫 번째 프레임의 위로 추가됩니다.
3. `foo`가 반환되면, 맨 위의 프레임 요소를 스택 밖으로 꺼냅니다(`bar` 호출 프레임만 남게 됩니다).
4. `bar`가 반환되면, 스택이 비어있게 됩니다.

인수와 지역 변수는 스택 바깥에 저장되므로, 바깥 함수가 반환된 후에도 계속 존재할 수 있습니다. 중첩 함수에서 지역 변수에 접근할 수 있는 이유가 이것입니다.

### 힙

객체는 힙에 할당됩니다. 힙은 단순히 메모리의 큰 (그리고 대부분 구조화되지 않은) 영역을 지칭하는 용어입니다.

### 큐

JavaScript 런타임은 메시지 큐, 즉 처리할 메시지의 대기열을 사용합니다. 각각의 메시지에는 메시지를 처리하기 위한 함수가 연결되어 있습니다.

이벤트 루프의 임의 시점에, 런타임은 대기열에서 가장 오래된 메시지부터 큐에서 꺼내 처리하기 시작합니다. 이를 위해 런타임은 꺼낸 메시지를 매개변수로, 메시지에 연결된 함수를 호출합니다. 함수를 호출하면 해당 함수가 사용할 새로운 스택 프레임이 생성됩니다.

함수 처리는 스택이 다시 텅 빌 때까지 계속됩니다. 그 후, 큐에 메시지가 남아있으면 같은 방법으로 처리를 계속 진행합니다.

이벤트 루프
------

**이벤트 루프**는 이 기능을 구현할 때 보통 사용하는 방식에서 그 이름을 얻었으며, 대략 다음과 같은 형태입니다.

```js
while (queue.waitForMessage()) {
  queue.processNextMessage();
}
```

`queue.waitForMessage()` 함수는 현재 처리할 수 있는 메시지가 존재하지 않으면 새로운 메시지가 도착할 때까지 동기적으로 대기합니다.

### "Run-to-completion"

각 메시지의 처리는 다른 메시지의 처리를 시작하기 전에 완전히 끝납니다.

이 특징은 프로그램의 동작을 추론할 때 유용한 특성을 제공합니다. 실행한 함수가 다른 작업에 의해 선점될 일이 없고, 다른 모든 코드의 실행보다 우선해서 값을 변경할 수 있으며, 중단되는 일 없이 완전히 끝나기 때문입니다. 반면, 예를 들어 C 언어에서는, 스레드에서 실행 중인 함수를 런타임 시스템이 임의로 멈추고 다른 스레드의 다른 코드를 먼저 실행할 수 있습니다.

이 모델의 단점은, 만약 메시지를 처리할 때 너무 오래 걸리면 웹 애플리케이션이 클릭이나 스크롤과 같은 사용자 상호작용을 처리할 수 없다는 점입니다. 브라우저는 "스크립트 응답 없음" 대화상자를 표시해서 이 문제를 완화합니다. 개발자로서 사용할 수 있는 좋은 방법으로는 메시지 처리를 가볍게 유지하고, 가능하다면 하나의 메시지를 여러 개로 나누는 것입니다.

### 메시지 추가하기

웹 브라우저에서는, 수신기가 부착된 이벤트가 발생하면 새로운 메시지가 추가됩니다. 수신기가 없으면, 이벤트는 유실됩니다. 즉, 클릭 이벤트 처리기가 붙은 요소를 클릭하면 메시지가 새로 추가되는 식입니다. 다른 이벤트에 대해서도 마찬가지입니다.

`setTimeout` 함수는 두 개의 매개변수를 가집니다. 첫 번째는 큐에 추가할 메시지, 두 번째는 시간 값(선택 사항, 기본 값 `0`)입니다. 시간 값은 메시지를 큐에 추가하기까지 기다릴 (최소) 지연 시간을 나타냅니다. 큐에 다른 메시지가 없고 스택이 비어있다면 `setTimeout`의 메시지는 딜레이 직후 즉시 처리됩니다. 그러나, 다른 메시지가 존재한다면 `setTimeout`은 앞선 메시지의 처리를 기다려야 합니다. 그래서 두 번째 값은 정확한 지연시간이 아닌, '최소' 지연 시간만 나타냅니다.

다음은 `setTimeout`이 타이머 만료 직후 즉시 실행되지 않는 예제입니다.

```js
const seconds = new Date().getTime() / 1000;

setTimeout(function () {
  // "2"를 출력, 즉 500밀리초가 지난 후 즉시 실행된 것이 아니라는 것
  console.log(`${new Date().getTime() / 1000 - seconds}초 후 실행됩니다.`);
}, 500);

while (true) {
  if (new Date().getTime() / 1000 - seconds >= 2) {
    console.log("좋아요, 2초간 반복했습니다.");
    break;
  }
}
```

### 0의 지연 시간

0의 지연 시간을 지정하는 것이 콜백을 0밀리초 후에 호출한다는 뜻은 아닙니다. `setTimeout`의 지연 시간에 `0` 밀리초를 지정하고 호출하더라도 콜백 함수는 즉시 실행되지 않습니다.

실제 실행 시점은 큐에서 대기 중인 작업의 수에 따라 다릅니다. 아래 예제에서는 `'평범한 메시지'`가 콜백의 호출보다 앞서 콘솔에 기록될 것입니다. 지연 시간은 요청을 처리하기 전에 대기할 '최소' 시간이고, 보장 시간이 아니기 때문입니다.

`setTimeout`에 특정 지연 시간을 지정하더라도, 큐에서 대기 중인 모든 메시지의 처리는 기다려야 합니다.

```js
(function () {
  console.log("시작");

  setTimeout(function cb() {
    console.log("콜백 1: 콜백 메시지");
  }); // has a default time value of 0

  console.log("평범한 메시지");

  setTimeout(function cb1() {
    console.log("콜백 2: 콜백 메시지");
  }, 0);

  console.log("종료");
})();
```

```js
(() => {
  console.log("시작");

  setTimeout(() => {
    console.log("콜백 1: 콜백 메시지");
  }); // 기본적으로 시간 값을 0으로 가집니다.

  console.log("this is just a message");

  setTimeout(() => {
    console.log("콜백 2: 콜백 메시지");
  }, 0);

  console.log("this is the end");
})();

// "시작"
// "평범한 메시지"
// "종료"
// "콜백 1: 콜백 메시지"
// "콜백 2: 콜백 메시지"
```

### 다수의 런타임 간 통신

웹 워커나 교차 출처 `iframe`은 자신만의 스택, 힙, 메시지 큐를 가집니다. 서로 다른 두 런타임은 `postMessage` 메서드를 통해 메시지를 보내는 방식으로만 서로 통신할 수 있습니다. 상대가 `message` 이벤트를 수신하고 있을 때, `postMessage`는 상대 런타임에 메시지를 추가합니다.

논 블로킹
-----

다른 많은 언어와 달리 JavaScript는 절대 블로킹 연산을 하지 않습니다. 논 블로킹은 이벤트 루프 모델의 무척 흥미로운 특징으로, 대부분의 입출력 처리가 이벤트와 콜백을 통해 수행되므로, 애플리케이션이 IndexedDB 질의나 XHR 요청의 반환을 대기 중이더라도, 여전히 사용자 입력 등 다른 것들을 처리할 수 있는 것입니다.

`alert`이나 동기적 XHR과 같은 레거시 예외가 존재하긴 하지만 사용하지 않는 것이 좋습니다. 물론 예외에 대한 예외는 조심하세요(그러나 이런 예외는 구현체의 버그인 경우가 많습니다).

같이 보기
-----

* HTML 표준의 이벤트 루프
* Node.js 이벤트 루프


JavaScript 안내서 - JavaScript

JavaScript 안내서
==============

JavaScript 안내서에서 JavaScript 언어 개요와 사용법을 알아보세요. 언어 기능에 대해 상세한 정보가 필요하면 JavaScript 참고서를 방문하세요.

본 안내서는 여러 장으로 구성되어 있습니다.

소개
--

Overview: Introduction

* About this guide
* About JavaScript
* JavaScript and Java
* ECMAScript
* Tools
* Hello World
Grammar and types
-----------------

Overview: Grammar and types

* Basic syntax & comments
* Declarations
* Variable scope
* Variable hoisting
* Data structures and types
* Literals
Control flow and error handling
-------------------------------

Overview: Control flow and error handling

* `if...else`
* `switch`
* `try`/`catch`/`throw`
* Error objects
Loops and iteration
-------------------

Overview: Loops and iteration

* `for`
* `while`
* `do...while`
* `continue`
* `break`
* `for..in`
* `for..of`
Functions
---------

Overview: Functions

* Defining functions
* Calling functions
* Function scope
* Closures
* Arguments & parameters
* Arrow functions
Expressions and operators
-------------------------

Overview: Expressions and operators

* Assignment & Comparisons
* Arithmetic operators
* Bitwise & logical operators
* Conditional (ternary) operator
Numbers and dates
-----------------

Overview: Numbers and dates

* Number literals
* `Number` object
* `Math` object
* `Date` object
Text formatting
---------------

Overview: Text formatting

* String literals
* `String` object
* Template literals
* Internationalization
* Regular Expressions
Indexed collections
-------------------

Overview: Indexed collections

* Arrays
* Typed arrays
Keyed collections
-----------------

Overview: Keyed collections

* `Map`
* `WeakMap`
* `Set`
* `WeakSet`
Working with objects
--------------------

Overview: Working with objects

* Objects and properties
* Creating objects
* Defining methods
* Getter and setter
Using classes
-------------

Overview: Using classes

* Declaring a class
* Various class features (en-US)
* Extends and inheritance (en-US)
* Why classes? (en-US)
Promises
--------

Overview: Promises

* Guarantees
* Chaining
* Error propagation
* Composition
* Timing
Iterators and generators
------------------------

Overview: Iterators and generators

* Iterators
* Iterables
* Generators
Meta programming
----------------

Overview: Meta programming (en-US)

* `Proxy` (en-US)
* Handlers and traps (en-US)
* Revocable Proxy (en-US)
* `Reflect` (en-US)
JavaScript modules
------------------

Overview: JavaScript modules

* Exporting
* Importing
* Default exports
* Renaming features
* Aggregating modules
* Dynamic module loading
* 다음 »


JavaScript 참고서 - JavaScript

JavaScript 참고서
==============

 이 페이지는 JavaScript 언어에 대한 정보 보관소입니다.
 이 참고서에 대해 더 읽어보세요.

내장 객체
-----

JavaScript 표준 내장 객체와 그 메서드 및 속성입니다.

### 값 속성

* `globalThis`
* `Infinity`
* `NaN`
* `undefined`
### 함수 속성

* `eval()`
* `isFinite()`
* `isNaN()`
* `parseFloat()`
* `parseInt()`
* `decodeURI()`
* `decodeURIComponent()`
* `encodeURI()`
* `encodeURIComponent()`
### 기초 객체

* `Object`
* `Function`
* `Boolean`
* `Symbol`
### 오류 객체

* `Error`
* `EvalError`
* `RangeError`
* `ReferenceError`
* `SyntaxError`
* `TypeError`
* `URIError`
* `AggregateError`
### 숫자와 날짜

* `Number`
* `BigInt`
* `Math`
* `Date`
### 텍스트 처리

* `String`
* `RegExp`
### 인덱스 콜렉션

* `Array`
* `Int8Array`
* `Uint8Array`
* `Uint8ClampedArray`
* `Int16Array`
* `Uint16Array`
* `Int32Array`
* `Uint32Array`
* `BigInt64Array`
* `BigUint64Array`
* `Float32Array`
* `Float64Array`
### 키 콜렉션

* `Map`
* `Set` (en-US)
* `WeakMap`
* `WeakSet`
### 구조화된 데이터

* `ArrayBuffer`
* `SharedArrayBuffer`
* `Atomics`
* `DataView`
* `JSON`
### 메모리 관리

* `WeakRef` (en-US)
* `FinalizationRegistry` (en-US)
### 제어 추상화

* `Promise`
* `GeneratorFunction`
* `AsyncGeneratorFunction` (en-US)
* `Generator`
* `AsyncGenerator` (en-US)
* `AsyncFunction`
### 리플렉션

* `Reflect`
* `Proxy`
### 국제화

* `Intl` (en-US)
* `Intl.Collator` (en-US)
* `Intl.DateTimeFormat`
* `Intl.DisplayNames`
* `Intl.ListFormat` (en-US)
* `Intl.Locale` (en-US)
* `Intl.NumberFormat`
* `Intl.PluralRules` (en-US)
* `Intl.RelativeTimeFormat` (en-US)
* `Intl.Segmenter` (en-US)
명령문
---

JavaScript 명령문과 선언문입니다.

### 흐름 제어

* 블록
* 빈 명령문
* `break`
* `continue`
* `if...else`
* `switch` (en-US)
* `throw`
* `try...catch` (en-US)
### 선언

* `var`
* `let`
* `const`
### 함수와 클래스

* `function`
* `function*`
* `async function`
* `return`
* `class`
### 순회와 반복

* `do...while`
* `for`
* `for each...in`
* `for...in`
* `for...of`
* `for await...of`
* `while`
### 기타

* `debugger`
* `export`
* `import`
* `label`
* `with` (en-US)
표현식 및 연산자
---------

JavaScript 표현식과 연산자입니다.

### 일차 표현식

* `this`
* `function`
* `class`
* `function*`
* `yield`
* `yield*`
* `async function`
* `async function*`
* `await`
* `[]`
* `{}`
* `/ab+c/i`
* `( )`
* `null`
### 좌변 표현식

* Property accessors
* `?.` (Optional chaining)
* `new`
* `new.target`
* `import.meta` (en-US)
* `super`
* `...obj`
### 증감

* `A++`
* `A--`
* `++A`
* `--A`
### 단항 연산자

* `delete` (en-US)
* `void`
* `typeof`
* `+`
* `-`
* `~`
* `!`
### 산술 연산자

* `+`
* `-`
* `/`
* `*`
* `%`
* `**`
### 관계 연산자

* `in`
* `instanceof`
* `<` (en-US)
* `>` (en-US)
* `<=` (en-US)
* `>=` (en-US)
### 동일 연산자

* `==`
* `!=`
* `===` (en-US)
* `!==` (en-US)
### 비트 시프트 연산자

* `<<`
* `>>` (en-US)
* `>>>` (en-US)
### 이진 비트 연산자

* `&`
* `|` (en-US)
* `^` (en-US)
### 이진 논리 연산자

* `&&`
* `||`
* `??`
### 조건(삼항) 연산자

* `(condition ? ifTrue : ifFalse)`
### 할당 연산자

* `=`
* `*=`
* `**=`
* `/=`
* `%=`
* `+=`
* `-=`
* `<<=`
* `>>=` (en-US)
* `>>>=`
* `&=`
* `^=` (en-US)
* `|=` (en-US)
* `&&=` (en-US)
* `||=` (en-US)
* `??=` (en-US)
* `[a, b] = arr`
* `{ a, b } = obj`
### 쉼표 연산자

* `,`
함수
--

 이 장은 응용 프로그램을 개발할 때 JavaScript 함수를 사용하는
 법을 설명합니다.

* `arguments`
* 화살표 함수
* 기본 매개변수
* 나머지 매개변수
추가 참고 페이지
---------

* 어휘 구문 (en-US)
* 자료형과 자료 구조
* 엄격 모드
* 사라진 기능 (en-US)


JavaScript 기술 개요 - JavaScript

JavaScript 기술 개요
================

개요
--

HTML이 웹 페이지의 구조와 콘텐츠를, CSS가 형식과 모양을 결정한다면, JavaScript는 상호작용성을 추가하여 풍부한 웹 애플리케이션을 만듭니다.

그러나, 웹 브라우저 맥락에서 포괄적으로 쓰이는 용어로서의 "JavaScript"는 사실 매우 다른 요소 여럿을 포함하는 것입니다. 그 중 하나는 핵심 언어(ECMAScript)고, 다른 하나는 DOM(Document Object Model)을 포함한 Web API입니다.

핵심 언어, JavaScript(ECMAScript)
-----------------------------

JavaScript의 핵심 언어는 ECMA TC39 위원회가 ECMAScript (en-US)라는 언어로 표준화했습니다. "ECMAScript"는 언어 표준을 나타내는 용어이지만, "ECMAScript"와 "JavaScript"는 같은 의미로 사용할 수 있습니다.

핵심 언어는 Node.js 등 비브라우저 환경에서도 사용됩니다.

### ECMAScript 범위에 포함되는 것은 무엇인가요?

무엇보다도, ECMAScript는 다음을 정의합니다.

* 언어 구문 (구문 분석 규칙, 키워드, 흐름 제어, 객체 리터럴 초기화 등)
* 오류 처리 원리 (`throw`, `try...catch` (en-US), 사용자 정의 `Error` 타입 등)
* 자료형 (불리언, 숫자, 문자열, 함수, 객체, ...)
* 프로토타입 기반 상속 원리
* 내장 객체 및 함수 (`JSON`, `Math`, `Array.prototype` 메서드, `Object` 내성검사 메서드 등)
* 엄격 모드
* 모듈 시스템
* 기본 메모리 모델
### 표준화 과정

ECMAScript 판들은 매년 ECMA 총회에서 표준으로 승인되고 게시됩니다. 모든 개발은 제안, 공식 명세서 문서 및 회의 노트를 호스팅하는 Ecma TC39 GitHub 조직에 공개됩니다.

ECMAScript 6판(ES6이라고 함) 이전에는, 명세가 몇 년에 한 번씩 게시되었으며, 일반적으로 메이저 버전 번호로(ES3, ES5 등)로 참조됩니다. ES6 이후에는 명세의 이름이 게시 연도인 ES2017, ES2018 등으로 지정됩니다. ES6는 ES2015와 동의어입니다. "ESNext"는 작성 당시의 다음 버전을 나타내는 동적인 이름입니다. 정의에 따라, 명세가 아직 확정되지 않았기 때문에, ESNext 기능은 제안이라고 부르는 것이 더 정확합니다.

ECMA-262의 현재 위원회 승인 스냅샷은 PDF 형식 및 HTML 형식에서 찾을 수 있습니다. ECMA-262 및 ECMA-402 버전은 명세 편집자들이 최신 상태로 유지합니다. TC39 웹사이트는 최신 ECMA-262 및 ECMA-402 버전을 호스팅합니다.

새로운 문법 및 API 도입, 기존 동작 수정 등 새로운 언어 기능은 제안 형식으로 논의됩니다. 각 제안은 4단계 프로세스를 거치며, 일반적으로 3단계 또는 4단계에서 JavavScript 엔진에 의해 구현되므로 공개적으로 사용할 수 있습니다.

ECMAScript 기록에 대한 자세한 내용은 Wikipedia ECMAScript 항목을 참조하세요.

### 국제화 API

ECMAScript 국제화 API 명세은 Ecma TC39에서 표준화된 것과 같이 ECMAScript 언어 명세에 추가된 것입니다. 국제화 API는 JavaScript 애플리케이션을 위한 콜레이션(문자열 비교), 숫자 형식 및 날짜 및 시간 형식을 제공하여, 애플리케이션이 언어를 선택하고 필요에 맞게 기능을 적용할 수 있는 방법입니다. 초기 표준안은 2012년 12월에 통과했으며 브라우저 구현 상태는 `Intl` (en-US) 객체 문서에서 추적 중입니다. 최근에는 국제화 명세도 매년 비준하고 있으며 브라우저 구현도 계속하여 개선 중입니다.

### 관련된 참고 자료

ECMAScript 언어 명세 및 ECMAScript 국제화 API 명세 및 관련 리소스에 참여하거나 현재 작업을 추적할 수 있는 다양한 방법이 있습니다.

* ECMAScript 언어 명세 repo
* ECMAScript 국제화 API 명세 repo
* ECMAScript 제안 repo
* ECMAScript 적합성 test suite repo
* TC39 회의록
* ECMAScript 명세 토론; 현재 메일링 리스트
* ECMAScript 명세 토론; 과거 메일링 리스트 아카이브(2021년 3월까지)
DOM API
-------

### WebIDL

WebIDL 명세는 DOM 기술과 ECMAScript 사이를 연결하는 역할을 합니다.

### DOM의 핵심

DOM(Document Object Model)은 HTML, XHTML, XML 문서의 객체를 나타내고 상호작용하기 위한, **언어에 제약되지 않는** 크로스 플랫폼 협약입니다. DOM 트리의 객체는 해당 객체의 메서드를 사용해 조작할 수 있습니다. W3C는 HTML와 XML 문서를 객체로 추상화하는 Core Document Object Model을 표준화하고, 추상화를 조작하기 위한 원리도 정의합니다. DOM에 정의된 내용 중 일부는 다음과 같습니다.

* 문서 구조, 트리 모델, DOM core의 DOM Event Architecture: `Node` (en-US), `Element` (en-US), `DocumentFragment`, `Document`, `DOMImplementation` (en-US), `Event`, `EventTarget`, …
* 덜 엄격한 DOM Event Architecture 정의, DOM events에 속하는 특정 이벤트.
* DOM Traversal, DOM Range 등

ECMAScript의 관점에서, DOM 명세에 정의된 객체는 "호스트 객체"라고 부릅니다.

### HTML DOM

웹 마크업 언어인 HTML은 DOM 코어에 정의된 추상화 개념에 엘리먼트의 의미를 더한 레이어라고 할 수 있습니다. HTML DOM은 HTML 요소의 `className` 같은 속성과 `document.body` 같은 API도 포함합니다.

HTML 명세는 문서의 제약도 정의합니다. 예를 들어, 순서가 없는 리스트를 나타내는 `<ul>` 요소의 모든 자식들은 리스트 요소를 나타내는 `<li>` 요소여야 합니다. 또한 표준에 정의되지 않은 요소와 속성을 사용하는 것도 금지됩니다.

`Document` 객체, `Window` 객체나 다른 DOM 요소들을 찾아보았나요? DOM 문서을 읽어보세요

알아둘만한 다른 API
------------

* `setTimeout` 과 `setInterval` 함수는 HTML 표준의 `Window` 인터페이스에 처음으로 정의되었습니다.
* XMLHttpRequest는 비동기적 HTTP 요청을 보낼 수 있게 해주는 API입니다.
* Fetch API는 네트워크 요청에 대해 보다 인체공학적인 추상화를 제공합니다.
* CSS Object Model는 CSS 규칙을 객체처럼 다룰 수 있도록 추상화합니다.
* WebWorkers는 병렬 처리를 가능하게 하는 API입니다.
* WebSockets는 양방향 저수준 통신을 가능하게 하는 API입니다.
* Canvas 2D Context는 `<canvas>` 요소에 그릴 수 있게 해주는 API입니다.
* WebAssembly 인터페이스는 JavaScript 코드와 WebAssembly 모듈 간의 통신을 위한 기능을 제공합니다.

비 브라우저 환경(예: Node.js)에는 DOM API가 없는 경우가 많은데, 문서와 상호 작용하지 않기 때문입니다. `fetch()` 및 `setTimeout()`과 같은 많은 웹 API를 구현합니다.

JavaScript 구현체
--------------

브라우저 환경과 그 너머에서 사용되는 세 가지 주요 JavaScript 구현체가 있습니다.

* Firefox에서 사용되는 Mozilla의 SpiderMonkey는 Netscape의 Brendan Eich가 만든 최초의 *ever* JavaScript 엔진이었습니다.
* Google의 V8, Google Chrome, Opera, Edge, Node.js, Deno, Electron 등이 있습니다.
* Apple Safari와 같은 WebKit 브라우저에서 사용되는 Apple의 JavaScriptCore(SquirrelFish/Nitro라고도 함) 및 Bun.

위의 구현체 외에도 다음과 같은 인기 있는 다른 JavaScript 엔진이 있습니다.

* Carakan, 이전 버전의 Opera에서 사용되었습니다.
* Internet Explorer에서 사용되는 Microsoft의 Chakra 엔진(상표권 문제를 피하기 위해 구현하는 언어를 공식적으로 "JScript"라고 합니다). 이전 버전의 Edge는 혼란스럽게도 Chakra라고도 하는 새로운 JavaScript 엔진을 사용했습니다.
* LibJS, SerenityOS의 브라우저 구현에 사용됩니다.
* Mozilla의 Rhino 엔진, Java로 작성된 JavaScript 구현으로 주로 Norris Boyd(역시 Netscape)가 만들었습니다.

비 브라우저용으로 특별히 제작된 일부 엔진이 있습니다.

* Engine262, JavaScript로 작성된 JavaScript 엔진입니다. JavaScript 개발자가 새로운 언어 기능을 탐색하고 명세에서 버그를 찾을 수 있도록 만들어졌습니다.
* IoT와 같은 임베디드 시스템에서 사용되는 Moddable XS.
* QuickJS, 작고 삽입 가능한 JavaScript 엔진입니다.
* Meta의 Hermes 엔진은 React Native에 최적화된 엔진입니다.
* Oracle의 GraalJS, Oracle Labs에서 GraalVM을 기반으로 구축한 고성능 구현체입니다.

JavaScript 엔진은 응용 프로그램 개발자가 JavaScript를 소프트웨어에 통합하는 데 사용할 수 있는 공용 API를 노출합니다. 지금까지 JavaScript의 가장 일반적인 호스트 환경은 웹 브라우저입니다. 웹 브라우저는 일반적으로 공용 API를 사용하여 DOM을 JavaScript에 반영하는 **호스트 객체**를 생성합니다.

JavaScript의 또 다른 일반적인 응용 프로그램은 (웹) 서버 측 스크립팅 언어입니다. JavaScript 웹 서버는 HTTP 요청 및 응답 객체를 나타내는 호스트 객체를 노출한 다음 JavaScript 프로그램에서 조작하여 웹 페이지를 동적으로 생성 할 수 있습니다. Node.js가 이에 대한 인기 있는 예입니다.

쉘
-

JavaScript 쉘을 사용하면 웹 페이지를 다시 로드하지 않고도 JavaScript 코드 조각을 빠르게 테스트 할 수 있습니다. 코드 개발 및 디버깅에 매우 유용합니다.

### 독립형 JavaScript 쉘

다음 JavaScript 쉘은 Perl 또는 Python과 같은 독립 실행형 환경입니다.

* Node.js - Node.js는 빠르고 확장 가능한 네트워크 애플리케이션을 쉽게 구축할 수 있는 플랫폼입니다.
* ShellJS - Node.js용 휴대용 Unix 셸 명령입니다.
### 브라우저 기반 JavaScript 쉘

다음 JavaScript 쉘은 브라우저의 JavaScript 엔진을 통해 코드를 실행합니다.

* Firefox에는 여러 줄 편집을 지원하는 내장 JavaScript 콘솔이 있습니다.
* Babel REPL - 미래의 JavaScript 실험을 위한 브라우저 기반 REPL입니다.
* TypeScript 플레이그라운드 — 새로운 JavaScript 기능(tsc 컴파일러를 통해)과 TypeScript 문법을 실험하기 위한 브라우저 기반 플레이그라운드입니다.
도구 및 리소스
--------

JavaScript 코드 작성 및 디버깅에 유용한 도구입니다.

Firefox 개발자 도구

웹 콘솔, JavaScript 프로파일러, 디버거 등.

JavaScript 배우기

웹 개발자 지망생을 위한 탁월한 리소스 — 자동화된 평가 안내에 따라 짧은 강의와 대화형 테스트를 통해 대화형 환경에서 JavaScript를 학습합니다. 처음 40개 레슨은 무료이며 소액의 일회성 결제로 전체 과정을 수강할 수 있습니다.

TogetherJS

협업이 쉬워집니다. 사이트에 TogetherJS를 추가하면 사용자가 웹사이트에서 실시간으로 서로 도울 수 있습니다!

Stack Overflow

"JavaScript"로 태그가 지정된 스택 오버플로 질문입니다.

JSFiddle

JavaScript, CSS 및 HTML을 편집하고 실시간 결과를 얻습니다. 외부 리소스를 사용하고 온라인에서 팀과 협업하십시오.

Plunker

Plunker는 웹 개발 아이디어를 만들고 협업하고 공유할 수 있는 온라인 커뮤니티입니다. JavaScript, CSS 및 HTML 파일을 편집하고 실시간 결과 및 파일 구조를 얻으십시오.

JSBin

JS Bin은 오픈 소스 협업 웹 개발 디버깅 도구입니다.

Codepen

Codepen은 라이브 결과 놀이터로 사용되는 또 다른 협업 웹 개발 도구입니다.

StackBlitz

StackBlitz는 React, Angular 등을 사용하여 전체 스택 애플리케이션을 호스팅하고 배포할 수 있는 또 다른 온라인 놀이터/디버깅 도구입니다.

RunJS

RunJS는 노드 및 브라우저 API 모두에 대한 실시간 결과와 액세스를 제공하는 데스크탑 플레이그라운드/스크래치패드 도구입니다.


상속과 프로토타입 - JavaScript

상속과 프로토타입
=========

JavaScript는 동적 타입이고 정적 타입이 없기 때문에, (Java 또는 C++와 같은) 클래스 기반 언어에 경험이 있는 개발자에게는 약간 혼란스럽습니다.

상속과 관련하여, JavaScript에는 객체라는 하나의 구조만 있습니다. 각 객체에는 **프로토타입**이라는 다른 객체에 대한 링크를 보유하는 비공개 속성이 있습니다. 그 프로토타입 객체도 자신만의 프로토타입을 가지고 있으며, 프로토타입으로 `null`을 가진 객체에 도달할 때까지 이 연결은 계속됩니다. 정의에 따르면 `null`에는 프로토타입이 없으며, 이 **프로토타입 체인**에서 최종 링크 역할을 합니다. 프로토타입 체인의 모든 구성요소들을 변경하거나 런타임 시 프로토타입을 교체할 수도 있으므로 JavaScript에는 정적 디스패칭(static dispatching)과 같은 개념이 없습니다.

이러한 혼란은 종종 JavaSCript의 약점 중 하나로 생각되어지지만, 프로토타입 상속 모델 자체는 사실 고전적인 모델보다 더 강력합니다. 예를 들어, classes가 구현되는 방식인 프로토타입 모델 위에 고전적인 모델을 구축하는 것은 매우 간단합니다.

클래스는 현재 널리 채택되어 JavaScript의 새로운 패러다임이 되었지만, 클래스는 새로운 상속 패턴을 가져오지 않습니다. 클래스는 대부분의 프로토타입 메커니즘을 추상화하지만, 내부에서 프로토타입이 작동하는 방식을 이해하는 것은 여전히 유용합니다.

프로토타입 체인을 이용한 상속
----------------

### 속성 상속

JavaScript 객체는 속성을 저장하는 동적인 "가방"과 (**자기만의 속성**이라고 부릅니다) 프로토타입 객체에 대한 링크를 가집니다. 객체의 어떤 속성에 접근하려할 때, 그 객체 자체 속성 뿐만 아니라 객체의 프로토타입, 그 프로토타입의 프로토타입 등 프로토타입 체인의 종단에 이를 때까지 그 속성을 탐색합니다.

>
> **Note:** ECMAScript 표준은 `someObject.[[Prototype]]`을 객체 `someObject`의 프로토타입을 지시하도록 명시하였습니다. `[[Prototype]]` 내부 슬롯은 각각 `Object.getPrototypeOf()`과 `Object.setPrototypeOf()` (en-US) 함수로 접근하고 수정할 수 있습니다. 이것은 JavaScript의 표준은 아니나 많은 브라우저에 구현되어 사실상의 표준이 된 속성 `__proto__`과 동일합니다. 간결함을 유지하고 혼동을 방지하기 위해 표기법에서 `obj.__proto__`를 사용하지 않고, `obj.[[Prototype]]`을 사용합니다. 이것은 `Object.getPrototypeOf(obj)`에 해당합니다.
>
>
> 생성자로 사용될 때 주어진 함수에 의해 생성된 객체의 모든 `인스턴스`에 `[[Prototype]]`이 할당되도록 지정하는 함수의 `func.prototype` 속성과 혼동해서는 안 됩니다. 나중 섹션에서 생성자 함수의 `prototype` 속성에 대해 논의할 것입니다.
>
>
>

 객체의 `[[Prototype]]`을 지정하는 방법에는 여러 가지가 있으며, 나중 섹션에 나열되어 있습니다. 지금은, 설명을 위해 `__proto__` 문법을 사용합니다. `{ __proto__: ... }` 구문이 표준이며, 더 이상 사용되지 않는 `obj.__proto__` 접근자와 다르다는 점을 참고해주십시오.
 `{ a: 1, b: 2, __proto__: c }`와 같은 객체 리터럴에서, 값 `c`(`null` 또는 다른 객체여야 합니다)는 해당 객체의 `[[Prototype]]`이 됩니다. 반면, `a` 및 `b`와 같은 다른 키는 해당 객체의 *자체 속성*이 됩니다. `[[Prototype]]`은 객체의 "내부 속성"일 뿐이므로, 이 구문은 매우 자연스럽게 읽히는 부분입니다.

다음은 속성에 접근하려고 할 때 발생하는 상황입니다.

```js
const o = {
  a: 1,
  b: 2,
  // \_\_proto\_\_는 [[Prototype]]을 설정합니다.
  // 여기에 다른 객체 리터럴로 지정되어 있습니다.
  \_\_proto\_\_: {
    b: 3,
    c: 4,
  },
};

// o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있습니다.
// o.[[Prototype]].[[Prototype]] 은 Object.prototype 입니다(무엇을 의미하는지 나중에 설명하겠습니다).
// 마지막으로, o.[[Prototype]].[[Prototype]].[[Prototype]]은 null입니다.
// null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없습니다.
// 그러면 전체 프로토타입 체인은 다음과 같습니다.
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null

console.log(o.a); // 1
// o에 'a' 자체 소유 속성이 있습니까? 네, 그 값은 1입니다.

console.log(o.b); // 2
// o에 'b'라는 자체 소유 속성이 있습니까? 네, 그 값은 2입니다.
// 프로토타입 역시 'b'라는 속성을 가지지만 이 값은 쓰이지 않습니다. 이것을 "속성의 가려짐(property shadowing)" 이라고 부릅니다.

console.log(o.c); // 4
// o는 'c'라는 속성을 소유하나요? 아니요, 프로토타입을 확인해보자.
// o.[[Prototype]]은 'c'라는 속성을 소유하나요? 네, 값은 4이다.

console.log(o.d); // undefined
// o에 'd' 자체 속성이 있습니까? 아니요, 프로토타입을 확인하세요.
// Is there a 'd' own property on o.[[Prototype]]? No, check its prototype.
// o.[[Prototype]]에 'd' 자체 소유 속성이 있습니까? 아니요, 프로토타입을 확인하세요.
// o.[[Prototype]].[[Prototype]]은 Object.prototype이고,
// 기본적으로 'd' 속성이 없습니다. 프로토타입을 확인하세요.
// o.[[Prototype]].[[Prototype]].[[Prototype]]은 null, 검색을 중지합니다,
// 속성을 찾을 수 없어서, undefined를 반환합니다.
```

객체의 속성에 값을 지정하면 "자기만의 속성"이 생성됩니다. 단, getter or setter가 적용되는 속성이 상속되는 경우 예외적인 규칙이 적용됩니다.

마찬가지로, 더 긴 프로토타입 체인을 만들 수 있으며, 모든 체인에서 속성을 찾을 수 있습니다.

```js
const o = {
  a: 1,
  b: 2,
  // \_\_proto\_\_는 [[Prototype]]을 설정합니다.
  // 여기에 다른 객체 리터럴로 지정되어 있습니다.
  \_\_proto\_\_: {
    b: 3,
    c: 4,
    \_\_proto\_\_: {
      d: 5,
    },
  },
};

// { a: 1, b: 2 } ---> { b: 3, c: 4 } ---> { d: 5 } ---> Object.prototype ---> null

console.log(o.d); // 5
```

### 메서드 상속

 JavaScript에는 클래스 기반 언어에서 정의하는 형식의 "메서드"가 없습니다. JavaScript에서는 모든 함수를 속성의 형태로 객체에 추가할 수 있습니다.
 상속된 함수는 위에 표시된 "속성의 가려짐"을 포함하여 다른 모든 속성처럼 작동합니다(단, 위에서 언급한 "속성의 가려짐" 대신 "메소드 오버라이딩, method overriding" 라는 용어를 사용한다).

상속된 함수가 실행 될 때, `this` 값은 함수가 자체 속성인 프로토타입 객체가 아니라 상속 객체를 가리킵니다.

```js
const parent = {
  value: 2,
  method() {
    return this.value + 1;
  },
};

console.log(parent.method()); // 3
// 이 경우 parent.method를 호출할 때, 'this'는 부모를 가리킵니다.

// 자식은 부모로부터 상속받는 객체입니다.
const child = {
  \_\_proto\_\_: parent,
};
console.log(child.method()); // 3
// child.method가 호출되면, 'this'는 자식을 가리킵니다.
// 자식이 부모의 메서드를 상속받을 때,
// 자식에서 'value' 속성을 찾습니다. 그러나 자식은 'value'라는 자체 속성이 없기 때문에,
// 해당 속성은 [[Prototype]]에서 찾을 수 있으며, 이는 parent.value입니다.

child.value = 4; // 자식의 속성 'value'에 값 4를 할당합니다.
// 이 코드는 부모의 'value' 속성을 숨깁니다.
// 자식 객체는 이제 다음과 같습니다.
// { value: 4, \_\_proto\_\_: { value: 2, method: [Function] } }
console.log(child.method()); // 5
// Since child now has the 'value' property, 'this.value' means
// 자식은 이제 'value' 속성을 가지므로 'this.value'는 child.value를 의미합니다.
```

생성자
---

모든 인스턴스가 동일한 몇가지 동일한 속성을 공유하는 경우, 프로토타입의 강점이 드러납니다. 이는 특히 메서드를 공유할 경우 더욱 두드러집니다. 예로, `getValue` 함수를 통해 접근할 수 있는 값을 포함하는 '상자' 객체를 여럿 만드는 경우를 생각해봅시다. 단순한 구현은 다음과 같습니다.

```js
const boxes = [
  { value: 1, getValue() { return this.value; } },
  { value: 2, getValue() { return this.value; } },
  { value: 3, getValue() { return this.value; } },
];
```

각 인스턴스에는 중복되고 불필요한 작업을 수행하는 고유한 함수 속성이 있기 때문에, 기대에 미치지 못하는 코드가 됩니다. 대신에, `getValue`를 모든 상자의 `[[Prototype]]`으로 이동할 수 있습니다.

```js
const boxPrototype = {
  getValue() {
    return this.value;
  },
};

const boxes = [
  { value: 1, \_\_proto\_\_: boxPrototype },
  { value: 2, \_\_proto\_\_: boxPrototype },
  { value: 3, \_\_proto\_\_: boxPrototype },
];
```

이렇게 하면, 모든 상자의 `getValue` 메서드가 동일한 함수를 참조하므로, 메모리의 사용량이 줄어듭니다. 그러나 모든 객체 생성에 대해 `__proto__`를 수동으로 바인딩하는 것은 여전히 매우 불편합니다. 이것은 생성된 모든 객체에 대해 `[[Prototype]]`을 자동으로 설정하는 constructor 함수를 사용하는 경우입니다. 생성자는 `new`로 호출되는 함수입니다.

```js
// 생성자 함수
function Box(value) {
  this.value = value;
}

// Box() 생성자에서 생성된 모든 속성
Box.prototype.getValue = function () {
  return this.value;
};

const boxes = [new Box(1), new Box(2), new Box(3)];
```

`new Box(1)`이 `Box` 생성자 함수에서 생성된 "인스턴스"라고 말할 수 있는데, `Box.prototype`은 이전에 생성한 `boxPrototype` 객체와 크게 다르지 않습니다. `Box.prototype`은 그냥 일반 객체입니다. 생성자 함수에서 생성된 모든 인스턴스는 자동으로 생성자의 `prototype` 속성을 `[[Prototype]]`으로 갖게 됩니다. 즉, `Object.getPrototypeOf(new Box()) === Box.prototype`입니다. 기본적으로 `Constructor.prototype`에는 생성자 함수 자체를 참조하는 `constructor` 속성이 하나 있습니다. 즉, `Box.prototype.constructor === Box`이기 때문에, 모든 인스턴스에서 원래 생성자에 접근할 수 있게 됩니다.

**참고:** 생성자 함수에서 반환된 값이 원시 값이 아니라면, 해당 값은 `new` 표현식의 결과가 됩니다. 이 경우, `[[Prototype]]`이 올바르게 바인딩되지 않을 수 있지만, 실제로는 많이 발생하지 않습니다.

위 생성자 함수는 classes에서 다음과 같이 다시 작성할 수 있습니다.

```js
class Box {
  constructor(value) {
    this.value = value;
  }

  // 메서드는 Box.prototype에 생성됩니다.
  getValue() {
    return this.value;
  }
}
```

클래스는 생성자 함수보다 문법적인 설탕입니다. 즉, 여전히 `Box.prototype`을 조작하여 모든 인스턴스의 동작을 변경할 수 있습니다. 그러나 클래스는 기본 프로토타입 메커니즘에 대한 추상화로 설계되었기 때문에, 이 자습서에서는 더 가벼운 생성자 함수 구문을 사용하여 프로토타입이 작동하는 방식을 보겠습니다.

`Box.prototype`은 모든 인스턴스의 `[[Prototype]]`과 동일한 객체를 참조하기 때문에, `Box.prototype`을 변경하여 모든 인스턴스의 동작을 변경할 수 있습니다.

```js
function Box(value) {
  this.value = value;
}
Box.prototype.getValue = function () {
  return this.value;
};
const box = new Box(1);

// 인스턴스가 이미 생성된 후, `Box.prototype`을 변경합니다.
Box.prototype.getValue = function () {
  return this.value + 1;
};
box.getValue(); // 2
```

결과적으로, 재할당 (`Constructor.prototype` (`Constructor.prototype = ...`))은 두 가지 이유로 나쁜 생각입니다.

* 재할당 전에 생성된 인스턴스의 `[[Prototype]]`은 이제 재할당 후 생성된 인스턴스의 `[[Prototype]]`과 다른 객체를 참조합니다. 하나의 `[[Prototype]]`을 변경해도 더 이상 다른 객체가 변경되지 않습니다.
* `constructor` 속성을 수동으로 재설정하지 않는 한, `instance.contructor`에서 더 이상 생성자 함수를 추적할 수 없어 동작 방식을 예상하기 어려워집니다. 일부 기본 제공 연산은 `constructor` 속성도 읽으며 설정되지 않은 경우, 예상대로 작동하지 않을 수 있습니다.

`Constructor.prototype`은 인스턴스를 구성할 때만 유용합니다. 이는 `Function.prototype` 생성자 함수의 자체 소유의 프로토타입인 `Constructor.[[Prototype]]`과 아무 관련이 없습니다. 즉, `Object.getPrototypeOf(Constructor) === Function.prototype`입니다.

### 리터럴의 암시적 생성자

JavaScript의 일부 리터럴 구문은 암시적으로 `[[Prototype]]`을 설정하는 인스턴스를 생성합니다. 예를 들어,

```js
// 객체 리터럴 (`\_\_proto\_\_` 키 없음)은 자동으로 `[[Prototype]]`으로 `Object.prototype`을 갖습니다.
const object = { a: 1 };
Object.getPrototypeOf(object) === Object.prototype; // true

// 배열 리터럴은 자동으로 `Array.prototype`을 `[[Prototype]]`으로 갖습니다.
const array = [1, 2, 3];
Object.getPrototypeOf(array) === Array.prototype; // true

// RegExp 리터럴은 자동으로 `RegExp.prototype`을 `[[Prototype]]`으로 갖습니다.
const regexp = /abc/;
Object.getPrototypeOf(regexp) === RegExp.prototype; // true
```

생성자 형태를 통해, "문법 설탕을 제거"할 수 있습니다.

```js
const array = new Array(1, 2, 3);
const regexp = new RegExp("abc");
```

예를 들어, `map()`과 같은 "배열 메서드"는 단순히 `Array.prototype`에 정의된 메서드입니다. 모든 배열 인스턴스에서 자동으로 사용할 수 있습니다.

**경고:** 널리 알려진 한 가지 잘못된 기능이 있습니다. 바로 `Object.prototype` 또는 다른 내장 프로토타입 중 하나를 확장하는 것입니다. 이 잘못된 기능의 예는 `Array.prototype.myMethod = function () {...}`를 정의한 다음 모든 배열 인스턴스에서 `myMethod`를 사용하는 것입니다.

이러한 잘못된 기능을 원숭이 패칭(monkey patching)이라고 합니다. 원숭이 패칭을 하게 되면, 상위 호환성에 문제가 발생합니다. 언어가 나중에 이 메서드를 추가하지만 다른 서명을 사용하면, 코드가 깨질 수 있기 대문입니다. 이로 인해, SmooshGate와 같은 사고가 발생했으며, JavaScript는 "웹을 중단하지 않으려" 시도하므로 언어가 발전하는 데 있어 큰 장애물이 될 수 있습니다.

내장 제공 프로토타입을 확장해도 좋은 `유일한` 경우는 최신 JavaScript 엔진의 기능을 이전 버전에서도 사용할 수 있게 해줄 때입니다(backport). 예로,`Array.prototype.forEach`가 있습니다.

흥미롭게도, 일부 내장 생성자의 `prototype` 속성은 역사적인 이유로 해당 인스턴스 자체입니다. 예를 들어, `Number.prototype`은 숫자 0이고, `Array.prototype`은 빈 배열이고, `RegExp.prototype`은 `/(?:)/`입니다.

```js
Number.prototype + 1; // 1
Array.prototype.map((x) => x + 1); // []
String.prototype + "a"; // "a"
RegExp.prototype.source; // "(?:)"
Function.prototype(); // Function.prototype은 자체로 프로그램에 아무 작업도 수행하지 말라고 지시하는 (no-operation, no-op) 함수입니다.
```

그러나, 이것은 사용자 정의 생성자나 `Map`과 같은 최신 생성자의 경우에는 해당되지 않습니다.

```js
Map.prototype.get(1);
// Uncaught TypeError: 호환되지 않는 Map.prototype에서 호출된 get 메서드
```

### 더 긴 상속 체인 구축

`Constructor.prototype` 속성은 `Constructor.prototype`의 자체 `[[Prototype]]`을 포함하여, 생성자 인스턴스의 `[[Prototype]]`이 됩니다. 기본적으로 `Constructor.prototype`은 일반 객체입니다. 즉, `Object.getPrototypeOf(Constructor.prototype) === Object.prototype` 입니다. 유일한 예외는 `Object.prototype` 자체이며, `[[Prototype]]`은 `null`입니다. 즉, `Object.getPrototypeOf(Object.prototype) === null`입니다. 따라서, 일반적인 생성자는 다음 프로토타입 체인을 빌드합니다.

```js
function Constructor() {}

const obj = new Constructor();
// obj ---> Constructor.prototype ---> Object.prototype ---> null
```

더 긴 프로토타입 체인을 구축하려면, `Object.setPrototypeOf()` (en-US) 함수를 통해 `Constructor.prototype`의 `[[Prototype]]`을 설정할 수 있습니다.

```js
function Base() {}
function Derived() {}
// `Derived.prototype`의 `[[Prototype]]`을 `Base.prototype`으로 설정합니다.
Object.setPrototypeOf(Derived.prototype, Base.prototype);

const obj = new Derived();
// obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null
```

클래스 문법 용어로, 이는 `extends` 구문을 사용하는 것과 동일합니다.

```js
class Base {}
class Derived extends Base {}

const obj = new Derived();
// obj ---> Derived.prototype ---> Base.prototype ---> Object.prototype ---> null
```

상속 체인을 구축하기 위해, `Object.create()`를 사용하는 일부 레거시 코드를 볼 수도 있습니다. 그러나 이것은 `prototype` 속성을 재할당하고 `constructor` 속성을 제거하기 때문에, 오류가 더 많이 발생할 수 있습니다. 생성자가 아직 인스턴스를 생성하지 않은 경우에는 성능에서 얻는 이점 또한 체감하기 어렵습니다.

```js
function Base() {}
function Derived() {}
// `[[Prototype]]`으로 `Base.prototype`을 사용하여 `Derived.prototype을 새로운 객체에 다시 할당합니다.
// 이렇게 하지 마세요, 대신 `Object.setPrototypeOf`를 사용하여 변경하세요.

Derived.prototype = Object.create(Base.prototype);
```

### 프로토타입 심층 분석

뒤에서 어떠한 일이 일어나는지 좀 더 자세히 살펴보겠습니다.

위에서 언급한 것처럼, JavaScript에서 함수는 속성을 가질 수 있습니다. 모든 함수에는 `prototype`이라는 특수한 속성이 있습니다. 아래의 예제 코드는 독립적이라는 것에 유의하세요(아래의 코드 이외에는 웹페이지에 다른 JavaScript가 없다고 가정해도 문제 없습니다).

최적의 실습을 위해, 콘솔을 열고 "console" 탭으로 이동하여 아래의 JavaScript 코드를 복사하여 붙여넣고, 엔터/Return 키를 눌러서 실행하는 것이 좋습니다(콘솔은 대부분 웹 브라우저의 개발자 도구에 포함되어 있습니다. 자세한 내용은 Firefox Developer Tools, Chrome DevTools 및 Edge DevTools을 참고하세요).

```js
function doSomething() {}
console.log(doSomething.prototype);
// 함수 선언 방법은 중요하지 않습니다. JavaScript의 함수는 항상 기본 프로토타입 속성을 갖습니다.
// 한 가지 예외가 있습니다. 화살표 함수에는 기본 프로토타입 속성이 없습니다.
const doSomethingFromArrowFunction = () => {};
console.log(doSomethingFromArrowFunction.prototype);
```

위 내용을 토대로, 콘솔을 보면 `doSomething()` 은 기본 `prototype` 속성을 가지고 있습니다. 코드를 실행한 뒤에 콘솔에서는 다음과 유사한 형태의 객체가 표시되어야 합니다.
```

{
  constructor: ƒ doSomething(),
  [[Prototype]]: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
  }
}
```

**참고:** Chrome 콘솔은 `[[Prototype]]`을 사용하여, 명세의 용어에 따라 객체의 프로토타입을 나타냅니다. Firefox는 `<prototype>`을 사용하는데, 일관성을 위해 `[[Prototype]]`을 사용합니다.

아래와 같이, `doSomething()`의 프로토타입에 속성을 추가할 수 있습니다.

```js
function doSomething() {}
doSomething.prototype.foo = "bar";
console.log(doSomething.prototype);
```

결과:
```

{
  foo: "bar",
  constructor: ƒ doSomething(),
  [[Prototype]]: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
  }
}
```

이제 `new` 연산자를 사용해서 프로토타입 기반의 `doSomething()` 인스턴스를 생성할 수 있습니다. new 연산자를 사용하려면 `new` 접두어를 제외하고 일반적으로 함수를 호출하세요. `new` 연산자로 함수를 호출하면 해당 함수의 인스턴스 객체를 반환받습니다. 그러면 속성들을 이 객체에 추가할 수 있습니다.

다음의 코드를 실행해봅시다.

```js
function doSomething() {}
doSomething.prototype.foo = "bar"; // 프로토타입에 속성 추가
const doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // 객체에 속성 추가
console.log(doSomeInstancing);
```

실행한 후에는 결과는 아래와 비슷할 겁니다.
```

{
  prop: "some value",
  [[Prototype]]: {
    foo: "bar",
    constructor: ƒ doSomething(),
    [[Prototype]]: {
      constructor: ƒ Object(),
      hasOwnProperty: ƒ hasOwnProperty(),
      isPrototypeOf: ƒ isPrototypeOf(),
      propertyIsEnumerable: ƒ propertyIsEnumerable(),
      toLocaleString: ƒ toLocaleString(),
      toString: ƒ toString(),
      valueOf: ƒ valueOf()
    }
  }
}
```

위에서 볼 수 있듯이, `doSomeInstancing`의 `[[Prototype]]`은 `doSomething.prototype`입니다. 그러나 이것은 어떤 역할을 하나요? `doSomeInstancing`의 속성에 접근하면 런타임은 먼저 `doSomeInstancing`에 해당 속성이 있는지 확인합니다.

`doSomeInstancing`에 속성이 없으면, 런타임은 `doSomeInstancing.[[Prototype]]` (`doSomething.prototype`)에서 속성을 찾습니다. `doSomeInstancing.[[Prototype]]`에 찾고 있는 속성이 있으면 `doSomeInstancing.[[Prototype]]`의 해당 속성이 사용됩니다.

그렇지 않고, `doSomeInstancing.[[Prototype]]`에 속성이 없으면, `doSomeInstancing.[[Prototype]].[[Prototype]]`에서 속성을 확인합니다. 기본적으로, 함수의 `prototype` 속성 중 `[[Prototype]]`은 `Object.prototype`입니다. 따라서, `doSomeInstancing.[[Prototype]].[[Prototype]]` (`doSomething.prototype.[[Prototype]]`(`Object.prototype`)) 은 검색 중인 속성을 찾습니다.

속성이 `doSomeInstancing.[[Prototype]].[[Prototype]]`에 없으면, `doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]]`을 통해 찾습니다. 그러나, `doSomeInstancing.[[Prototype]].[[Prototype]].[[Prototype]]`이 존재하지 않는 문제가 있는데, 그 이유는 `Object.prototype.[[Prototype]]`이 `null`이기 때문입니다. 그런 다음, `[[Prototype]]`의 전체 프로토타입 체인을 살펴본 후, 런타임은 속성이 존재하지 않는다고 주장하고 속성의 값이 `undefined`이라고 결정합니다.

콘솔에 코드를 추가로 입력해 봅시다.

```js
function doSomething() {}
doSomething.prototype.foo = "bar";
const doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: ", doSomeInstancing.prop);
console.log("doSomeInstancing.foo: ", doSomeInstancing.foo);
console.log("doSomething.prop: ", doSomething.prop);
console.log("doSomething.foo: ", doSomething.foo);
console.log("doSomething.prototype.prop:", doSomething.prototype.prop);
console.log("doSomething.prototype.foo: ", doSomething.prototype.foo);
```

코드의 결과는 아래와 같습니다.
```

doSomeInstancing.prop:      some value
doSomeInstancing.foo:       bar
doSomething.prop:           undefined
doSomething.foo:            undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo:  bar
```

프로토타입 체인을 만들고 변경하는 다양한 방법
-------------------------

객체를 생성하고 프로토타입 체인을 변경하는 다양한 방법을 만나보았습니다. 각 접근 방식의 장단점을 비교하여, 다양한 방식을 체계적으로 요약합니다.

### 문법 생성자로 객체 생성

```js
const o = { a: 1 };
// 새롭게 만들어진 각체 o는 Object.prototype을 [[Prototype]]으로 가지고 있습니다.
// Object.prototype의 프로토타입은 null 입니다.
// o ---> Object.prototype ---> null

const b = ["yo", "whadup", "?"];
// Array.prototype을 상속받은 배열도 마찬가지 입니다.
// (이번에는 indexOf, forEach 등의 메소드를 가집니다)
// 프로토타입 체인은 다음과 같습니다.
// b ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2;
}
// 함수는 Function.prototype 을 상속받습니다.
// (이 프로토타입은 call, bind 같은 메소드를 가집니다).
// f ---> Function.prototype ---> Object.prototype ---> null

const p = { b: 2, \_\_proto\_\_: o };

// 새로 생성된 객체의 [[Prototype]]이 \_\_proto\_\_ 리터럴 속성을 통해 다른 객체를 가리키도록 할 수 있습니다.
// (Object.prototype.\_\_proto\_\_ 접근자와 혼동하지 마세요).
// p ---> o ---> Object.prototype ---> null
```

객체 초기자에서 `__proto__` 키 사용의 장점과 단점| 장점 | 모든 최신 엔진에서 지원됩니다. 객체가 아닌 것을 `__proto__`키로 지정하면 예외를 발생시키지 않고 조용히 실패합니다. `Object.prototype.__proto__` 설정자와 반대로, 객체 리터럴 초기자의 `__proto__`가 표준화되고 최적화되었으며 `Object.create`보다 성능이 더 뛰어날 수 있습니다. 객체 생성 시 추가 자체 속성을 선언하는 것이 `Object.create`보다 편리합니다. |
| 단점 | IE10 이하에서는 지원하지 않습니다. 차이점을 모르는 사람들이 `Object.prototype.__proto__`와 혼동하기 쉽습니다. |

### 생성자 함수를 이용

JavaScript에서 생성자는 단지 new 연산자를 사용해 함수를 호출하면 된다.

```js
function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype.addVertex = function (v) {
  this.vertices.push(v);
};

const g = new Graph();
// g는 자체 속성으로 'vertices' 와 'edges'를 가지는 객체이다.
// g.[[Prototype]]은 new Graph()가 실행될 때 Graph.prototype의 값이 됩니다.
```

생성자 함수 사용의 장점과 단점| 장점 | 모든 엔진에서 지원합니다(IE 5.5까지도 지원). 또한, 매우 빠르고, 표준에 따르고, JIT 최적화가 가능합니다. |
| 단점 | * 이 방법을 사용하기 위해서는, 해당 함수를 초기화해야 합니다. 이 초기화 중에 생성자는 각각의 객체마다 생성해야 하는 고유한 정보를 저장할 수 있습니다. 이 고유한 정보는 한 번만 생성되고, 잠재적으로 문제를 발생시킬 수 있습니다.
* 생성자의 초기화는 원치 않는 메서드를 객체에 넣을 수 있습니다.

둘 다 실제로는 일반적으로 문제가 되지 않습니다. |

### Object.create 이용

`Object.create()`을 호출하면 새로운 객체가 생성됩니다. 이 객체의 `[[Prototype]]`은 함수의 첫 번째 인수입니다.

```js
const a = { a: 1 };
// a ---> Object.prototype ---> null

const b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)

const c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

const d = Object.create(null);
// d ---> null (d는 프로토타입으로 직접 null을 갖는 객체입니다)
console.log(d.hasOwnProperty);
// undefined, d는 Object.prototype에서 상속받지 않기 때문입니다.
```

`Object.create`의 장점과 단점| 장점 | 모든 최신 엔진을 지원합니다. 생성 시 객체의 `[[Prototype]]`을 직접 설정할 수 있으므로, 런타임에서 객체를 더욱 최적화할 수 있습니다. 또한 `Object.create(null)`를 사용하여 프로토타입 없이 객체를 생성할 수 있습니다. |
| 단점 | IE8 이하에서는 지원하지 않습니다. 그러나 Microsoft는 IE8 이하를 실행하는 시스템에 대한 확장 지원을 중단했기 때문에 대부분의 응용 프로그램에서는 문제가 되지 않습니다. 또한, 두 번째 인수를 사용하는 경우 느린 객체 초기화로 인해 성능이 저하될 수 있습니다. 각 객체 설명자 속성에는 자체적으로 구분된 설명자 객체가 있기 때문입니다. 객체 형태를 가지는 수십만 개의 객체 설명자를 처리할 때, 지연 시간이 심각한 문제가 될 수 있습니다. |

### class를 이용하는 방법

```js
class Rectangle {
  constructor(height, width) {
    this.name = "Rectangle";
    this.height = height;
    this.width = width;
  }
}

class FilledRectangle extends Rectangle {
  constructor(height, width, color) {
    super(height, width);
    this.name = "Filled rectangle";
    this.color = color;
  }
}

const filledRectangle = new FilledRectangle(5, 10, "blue");
// filledRectangle ---> FilledRectangle.prototype ---> Rectangle.prototype ---> Object.prototype ---> null
```

클래스의 장점과 단점| 장점 | 모든 최신 엔진에서 지원됩니다. 매우 높은 가독성과 유지 보수성. 비공개 속성은 프로토타입 상속에서 자잘한 대체가 없는 기능입니다. |
| 단점 | 특히 비공개 속성이 있는 클래스는 기존 클래스보다 덜 최적화되어 있습니다(엔진 구현자가 이를 개선하기 위해 노력하고 있습니다). 이전 환경에서는 지원되지 않으며 일반적으로 실제 서비스의 운영 환경에서 클래스를 사용하려면 트랜스파일러(transpilers)가 필요합니다. |

### Object.setPrototypeOf()를 이용하는 방법

위의 모든 메서드는 객체 생성 시 프로토타입 체인을 설정하지만, `Object.setPrototypeOf()` (en-US)는 이미 생성된 객체의 내부 `[[Prototype]]` 속성을 변경할 수 있습니다.

```js
const obj = { a: 1 };
const anotherObj = { b: 2 };
Object.setPrototypeOf(obj, anotherObj);
// obj ---> anotherObj ---> Object.prototype ---> null
```

`Object.setPrototypeOf` (en-US)의 장점과 단점| 장점 | 모든 최신 엔진에서 지원됩니다. 객체의 프로토타입을 동적으로 조작할 수 있으며 `Object.create(null)`로 만든 프로토타입이 없는 객체에 프로토타입을 적용할 수 있습니다. |
| 단점 | 성능이 좋지 않습니다. 객체 생성 시 프로토타입을 설정할 수 있는 경우 피해야 합니다. 많은 엔진들이 프로토타입을 최적화하고 미리 인스턴스를 호출할 때 메모리에서 메서드의 위치를 추측하려고 합니다. 그러나 프로토타입을 동적으로 설정하면 이러한 모든 최적화가 중단됩니다. 명세에 따라 작동하도록, 일부 엔진이 최적화 해제를 위해 코드를 다시 컴파일하게 할 수 있습니다. IE8 이하에서는 지원하지 않습니다. |

### \_\_proto\_\_ 접근자를 사용하는 방법

모든 객체는 `Object.prototype.__proto__` 설정자를 상속하며, 이는 기존 객체의 `[[Prototype]]`을 설정하는 데 사용할 수 있습니다(`__proto__` 키가 객체에서 재정의되지 않은 경우).

**경고:** `Object.prototype.__proto__` 접근자는 **비표준**이며 더 이상 사용되지 않습니다. 대신 거의 항상 `Object.setPrototypeOf`를 사용해야 합니다.

```js
const obj = {};
// 이것을 사용하지 마세요. 오직 예시일 뿐입니다.
obj.__proto__ = { barProp: "bar val" };
obj.__proto__.__proto__ = { fooProp: "foo val" };
console.log(obj.fooProp);
console.log(obj.barProp);
```

`__proto__` 속성 설정의 장점과 단점| 장점 | 모든 최신 엔진에서 지원됩니다. `__proto__`를 객체가 아닌 것으로 설정하면 조용히 실패합니다. 예외를 던지지 않습니다. |
| 단점 | 성능이 떨어지고 더 이상 사용되지 않습니다. 많은 엔진이 프로토타입을 최적화하고 미리 인스턴스를 호출할 때 메모리에서 메서드의 위치를 추측하려고 합니다. 그러나 프로토타입을 동적으로 설정하면 이러한 모든 최적화가 중단되고 일부 엔진이 명세에 따라 작동하도록 코드의 최적화 해제를 위해 다시 컴파일하도록 강제할 수 있습니다. IE10 이하에서는 지원하지 않습니다. `__proto__` 설정자는 표준 선택 사항이므로, 모든 플랫폼에서 작동하지 않을 수 있습니다. 대신 거의 항상 `Object.setPrototypeOf` (en-US)를 사용해야 합니다. |

성능
--

프로토타입 체인에서 상위에 있는 속성에 대한 조회 시간은 성능에 부정적인 영향을 미칠 수 있으며, 이는 성능 중심의 코드에선 심각한 문제입니다. 또한, 존재하지도 않는 속성에 접근하려는 시도는 항상 모든 프로토타입 체인인 전체를 탐색하게 됩니다.

또한, 객체의 속성을 반복할 때, 프로토타입 체인에 있는 **모든"** 열거 가능한 속성이 열거됩니다. 객체가 프로토타입 체인이 아닌 *itself*에 정의된 속성을 가지고 있는지 확인하려면, `hasOwnProperty` (en-US) 또는 `Object.hasOwn` (en-US) 메서드를 사용해야 합니다. `[[Prototype]]`으로 `null`이 있는 객체를 제외한 모든 객체는 프로토타입 체인에서 더 아래로 재정의되지 않는 한 `Object.prototype`에서 `hasOwnProperty` (en-US)를 상속합니다. 구체적인 예를 제공하기 위해 위의 그래프 예제 코드를 사용하여 설명하겠습니다.

```js
function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype.addVertex = function (v) {
  this.vertices.push(v);
};

const g = new Graph();
// g ---> Graph.prototype ---> Object.prototype ---> null

g.hasOwnProperty("vertices"); // true
Object.hasOwn(g, "vertices"); // true

g.hasOwnProperty("nope"); // false
Object.hasOwn(g, "nope"); // false

g.hasOwnProperty("addVertex"); // false
Object.hasOwn(g, "addVertex"); // false

Object.getPrototypeOf(g).hasOwnProperty("addVertex"); // true
```

참고: 속성이 `undefined`인지 확인하는 것만으로는 충분하지 않습니다. 속성이 존재하나 단순히 값이 `undefined`인 경우도 있습니다.

결론
--

JavaScript는 모두 동적이고 런타임이며 정적 타입이 전혀 없기 때문에, Java 또는 C++에서 온 개발자에게는 다소 혼란스러울 수 있습니다. 모든 것은 객체(인스턴스)이거나 함수(생성자)이며 함수 자체도 `Function` 생성자의 인스턴스입니다. 문법 구성인 "클래스"도 런타임에는 생성자 함수일 뿐입니다.

 JavaScript의 모든 생성자 함수에는 `new` 연산자와 함께 작동하는 `prototype`이라는 특수 속성이 있습니다.
 프로토타입 객체에 대한 참조는 새 인스턴스의 내부 `[[Prototype]]` 속성에 복사됩니다. 예를 들어, `const a1 = new A()`를 수행하면, JavaScript(메모리에 객체를 생성한 후 `this`를 정의한 `A()`를 실행하기 전에)는 `a1.[[Prototype]] = A.prototype`을 설정합니다. 그런 다음 인스턴스의 속성에 접근하면, JavaScript는 먼저 해당 객체에 직접 존재하는지 여부를 확인하고, 그렇지 않은 경우 `[[Prototype]]`에서 찾습니다. `[[Prototype]]`은 원하는 값을 찾을 때까지 재귀적으로 탐색합니다. 즉, `a1.doSomething`, `Object.getPrototypeOf(a1).doSomething`, `Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething` 순서로 탐색하며, 값을 찾거나 `Object.getPrototypeOf`는 `null`일 때 탐색을 종료합니다. 이는 `prototype`에 정의된 모든 속성이 모든 인스턴스에서 효과적으로 공유되며, 나중에 `prototype`의 일부를 변경하고 변경 사항이 모든 기존 인스턴스에 나타나도록 할 수 있다는 것을 의미합니다.

위의 예에서 `const a1 = new A(); const a2 = new A();`인 경우 `a1.doSomething`은 실제로 `Object.getPrototypeOf(a1).doSomething`을 참조하고, 이는 정의한 `A.prototype.doSomething`과 동일합니다. 즉, `Object.getPrototypeOf(a1).doSomething === Object.getPrototypeOf(a2).doSomething === A.prototype.doSomething`입니다.

프로토타입 상속 모델을 사용하는 복잡한 코드를 작성하기 전에 프로토타입 상속 모델을 이해하는 것이 필수적입니다. 또한, 코드에서 프로토타입 체인의 길이를 파악하여 필요한 경우 성능 문제를 방지하기 위해 분할하세요. 또한, 내장 프로토타입은 최신 JavaScript 기능과의 호환성을 위한 경우가 아니면 **절대** 확장해서는 안 됩니다.


JavaScript의 메모리 관리 - JavaScript

JavaScript의 메모리 관리
==================

C 언어같은 저수준 언어에서는, 메모리 관리를 위해 `malloc()` 과 `free()`를 사용합니다. 반면, JavaScript는 객체가 생성되었을 때 자동으로 메모리를 할당하고 더 이상 필요하지 않을 때 자동으로 해제합니다(가비지 컬렉션). 이러한 자동 메모리 관리는 잠재적 혼란의 원인이기도 한데, 개발자가 메모리 관리에 대해 고민할 필요가 없다는 잘못된 인식을 심어줄 수 있기 때문입니다.

메모리 생존주기
--------

메모리 생존주기는 대부분의 프로그래밍 언어에서 비슷합니다.

1. 필요할 때 할당합니다.
2. 할당된 메모리를 사용합니다. (읽기, 쓰기)
3. 더 이상 필요하지 않으면 해제합니다.

두 번째 부분은 모든 언어에서 명시적으로 사용됩니다. 그러나 첫 번째 부분과 마지막 부분은 저수준 언어에서는 명시적이며, JavaScript와 같은 대부분의 고수준 언어에서는 암묵적으로 작동합니다.

### JavaScript의 메모리 할당

#### 값 초기화

프로그래머가 메모리 할당을 신경을 쓸 필요가 없도록, JavaScript는 값을 선언할 때 자동으로 메모리를 할당합니다.

```js
const n = 123; // 정수를 담기 위한 메모리 할당
const s = "azerty"; // 문자열을 담기 위한 메모리 할당

const o = {
  a: 1,
  b: null,
}; // 오브젝트와 그 오브젝트에 포함된 값들을 담기 위한 메모리 할당

// (오브젝트처럼) 배열과 배열에 담긴 값들을 위한 메모리 할당
const a = [1, null, "abra"];

function f(a) {
  return a + 2;
} // 함수를 위한 할당(함수는 호출 가능한 오브젝트)

// 함수식 또한 오브젝트를 담기 위한 메모리를 할당합니다.
someElement.addEventListener(
  "click",
  () => {
    someElement.style.backgroundColor = "blue";
  },
  false,
);
```

#### 함수 호출을 통한 할당

함수 호출의 결과 메모리 할당이 일어나기도 합니다.

```js
const d = new Date(); // Date 개체를 위해 메모리를 할당

const e = document.createElement("div"); // DOM 엘리먼트를 위해 메모리를 할당
```

메소드가 새로운 값이나 오브젝트를 할당하기도 합니다.

```js
const s = "azerty";
const s2 = s.substr(0, 3); // s2는 새로운 문자열
// JavaScript에서 문자열은 immutable 값이기 때문에,
// 메모리를 새로 할당하지 않고 단순히 [0, 3] 이라는 범위만 저장합니다.

const a = ["ouais ouais", "nan nan"];
const a2 = ["generation", "nan nan"];
const a3 = a.concat(a2);
// a 와 a2 를 이어붙여, 4개의 원소를 가진 새로운 배열
```

### 값 사용

값 사용이란 기본적으로는 할당된 메모리를 읽고 쓰는 것을 의미합니다. 변수나 객체 속성의 값을 읽고 쓰거나 함수 호출 시 함수에 인수를 전달하여 수행할 수 있습니다.

### 할당된 메모리가 더 이상 필요없을 때 해제하기

이 단계에서 대부분의 메모리 관리 문제가 발생합니다. "할당된 메모리가 더 이상 필요없을 때"를 알아내기가 가장 어렵기 때문입니다.

저수준 언어에서는 메모리가 필요없어질 때를 개발자가 직접 결정하고 해제하는 방식을 사용합니다.

JavaScript와 같은 고수준 언어들은 "가비지 콜렉션(GC)"이라는 자동 메모리 관리 방법을 사용합니다. 가비지 콜렉터의 목적은 메모리 할당을 추적하고 할당된 메모리 블록이 더 이상 필요하지 않게 되었는지를 판단하여 회수하는 것입니다. 이러한 자동 메모리 관리 프로세스가 궁극의 방법은 아닙니다. 왜냐하면 어떤 메모리가 여전히 필요한지 아닌지를 판단하는 것은 비결정적 문제이기 때문입니다.

가비지 콜렉션
-------

위에서 언급한 것처럼 "더 이상 필요하지 않은" 모든 메모리를 찾는건 비결정적 문제입니다. 따라서 가비지 컬렉터들은 이 문제에 대한 제한적인 해결책을 구현합니다. 이 섹션에서는 주요한 가비지 컬렉션 알고리즘들과 그 한계를 이해하는데 필요한 개념을 설명합니다.

### 참조

가비지 콜렉션 알고리즘의 핵심 개념은 *참조*입니다. 메모리 관리 맥락에서, A라는 메모리를 통해 (명시적이든 암시적이든) B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다" 라고 합니다. 예를 들어, 모든 JavaScript 오브젝트는 prototype (implicit reference) (암시적 참조) 및 그 객체의 속성에 대한 참조 (명시적 참조)가 있습니다.

이 맥락에서 "객체"의 개념은 일반 JavaScript 객체와 함수 범위(또는 전역 어휘 범위)를 포함하여 더 넓게 확장됩니다.

### 참조-세기(Reference-counting) 가비지 콜렉션

**참고:** 최신 브라우저는 더 이상 참조-세기 가비지 콜렉션 방식을 사용하지 않습니다.

참조-세기 알고리즘은 가장 단순하게 구현된 알고리즘입니다. 이 알고리즘은 '어떤 다른 객체도 참조하지 않는 객체'를 '더 이상 필요 없는 객체'라고 여깁니다. 이 객체를 "가비지"라 부르며, 이를 참조하는 다른 객체가 하나도 없는 경우, 수집이 가능합니다.

예제:

```js
let x = {
  a: {
    b: 2,
  },
};
// 2개의 객체가 생성되었습니다. 하나의 객체는 다른 객체의 속성으로 참조됩니다.
// 나머지 하나는 'x' 변수에 할당되어 참조됩니다.
// 명백하게, 가비지 콜렉션 수행될 메모리는 하나도 없습니다.

let y = x;
// 'y' 변수는 위의 객체를 참조하는 두 번째 변수입니다.

x = 1;
// 이제 'y' 변수가 위의 객체를 참조하는 유일한 변수가 되었습니다.

let z = y.a;
// 위의 객체의 'a' 속성을 참조했습니다.
// 이제 이 객체는 두 개의 참조를 가집니다.
// 'y'가 속성으로 참조하고 'z'라는 변수가 참조합니다.

y = "mozilla";
// 이제 맨 처음 'x' 변수가 참조했던 객체에 대한 참조가 없기 때문에, 가비지 컬렉션이 가능합니다.
// 그러나, 객체의 'a' 속성이 여전히 'z' 변수에 의해 참조되므로
// 메모리를 해제할 수 없습니다.

z = null;
// 이제 맨 처음 'x' 변수가 참조했던 객체의 'a' 속성에는
// 참조가 없으므로 가비지 콜렉션이 수행될 수 있습니다.
```

순환 참조를 다루는 일에는 한계가 있습니다. 다음 예제에서는, 두 객체가 서로 참조하는 속성으로 생성되어 순환 구조를 생성합니다. 함수 호출이 완료되면 이 두 객체는 스코프를 벗어나게 될 것이며, 그 시점에서 두 객체는 불필요해지므로 할당된 메모리는 회수되어야 합니다. 그러나 두 객체가 서로를 참조하고 있으므로, 참조-세기 알고리즘은 둘 다 가비지 컬렉션의 대상으로 표시하지 않습니다. 이러한 순환 참조는 메모리 누수의 흔한 원인입니다.

```js
function f() {
  const x = {};
  const y = {};
  x.a = y; // x는 y를 참조합니다.
  y.a = x; // y는 x를 참조합니다.

  return "azerty";
}

f();
```

### 표시하고-쓸기(Mark-and-sweep) 알고리즘

이 알고리즘은 "더 이상 필요없는 객체"를 "도달할 수 없는 객체"로 정의합니다.

이 알고리즘은 "roots" 라는 객체의 집합을 가지고 있습니다. JavaScript에서 root는 전역 객체입니다. 주기적으로, 가비지 콜렉터는 roots로 부터 시작하여 roots가 참조하는 객체들, roots가 참조하는 객체가 참조하는 객체들 등을 찾습니다. roots로 부터 시작하여 가비지 콜렉터는 모든 도달할 수 있는 객체들을 찾고, 도달할 수 없는 모든 객체들을 수집합니다.

이 알고리즘은 위의 참조-세기 방식보다 개선된 형태라고 할 수 있는데, 그 이유는 참조가 없는 객체는 명확히 도달할 수 없기 때문입니다. 순환 참조에서 본 것처럼, 그 반대(명확히 도달할 수 없다면, 참조가 없다)는 사실이 아닙니다.

현재 모든 최신 엔진은 표시하고-쓸기 가비지 수집을 제공합니다. JavaScript 가비지 수집 필드(세대별/증분적/동시적/병렬적 가비지 수집)에서 지난 몇 년간의 모든 개선들은 이 알고리즘의 구현을 통한 개선이며, 가비지 수집 알고리즘이나 언제 "객체가 필요 없는지"에 대한 정의를 반영하는 부분에 있어서의 개선은 아닙니다.

이러한 접근방식의 직접적인 효용은 더 이상 주기가 문제가 되지 않는다는 점입니다. 위의 첫 예시에서, 함수 호출이 리턴되고 나서 두 개의 객체들은 더 이상 전역 객체에서 참조하고 있는 어떤 리소스에서도 참조하지 않습니다. 결과적으로, 두 객체들은 가비지 수집기가 도달할 수 없는 것으로 판명되고 할당되었던 메모리를 회수하게 됩니다.

그러나, 가비지 수집을 수동으로 조작할 수 없다는 문제가 남습니다. 어떤 경우에서는 수동으로 언제, 어떤 메모리를 반환할지 결정하는 것이 편리할 때가 있습니다. 객체의 메모리를 반환하기 위해서, 해당 객체는 명시적으로 도달할 수 없게 되어야 합니다. 또한 JavaScript에서 프로그래밍적으로 가비지 수집을 시작하는 것은 불가능하며, 비록 엔진은 플래그를 통해서 API를 제공할지 몰라도, 코어 언어단에서는 추후에도 지원하지 않을 것입니다.

엔진의 메모리 모델(memory model) 설정하기
-----------------------------

JavaScript 엔진은 주로 메모리 모델을 노출하는 플래그를 제공합니다. 예로, Node.js는 설정과 메모리 문제 디버깅을 위해 내부를 구성하는 V8 메커니즘을 노출하는 추가적인 옵션과 도구를 제공합니다. 이 설정은 브라우저에서는 대부분은 불가능하고, 웹 페이지(HTTP 헤더 등을 통해) 상에서는 더더욱 불가능합니다.

가용한 힙 메모리의 최대량은 아래와 같은 플래그를 통해 올릴 수 있습니다:

```bash
node --max-old-space-size=6000 index.js
```

또한 플래그나 Chrome Debugger를 사용해 메모리 문제를 디버깅하기 위한 가비지 컬렉터 정보를 보여줄 수 있습니다:

```bash
node --expose-gc --inspect index.js
```

메모리 관리를 돕는 데이터 구조
-----------------

비록 JavaScript가 가비지 수집기 API를 직접적으로 노출하지는 않더라도, JavaScript는 간접적으로 가비지 수집을 관찰하며 메모리 관리에 쓸 수 있는 몇 가지 데이터 구조를 제공합니다.

### WeakMaps과 WeakSets

`WeakMap`과 `WeakSet`은 각 non-weak에 대응되는 `Map`과 `Set` (en-US)의 API를 모방한 데이터 구조입니다. `WeakMap`은 키-값 쌍의 모음을 제공하는 반면, `WeakSet`은 유일한 값들로 이뤄진 모음을 제공합니다. 각각은 추가, 삭제, 조회 기능을 제공합니다.

`WeakMap`과 `WeakSet`의 이름은 "weakly held" 값이라는 개념에서 따왔습니다. 만약 `x`가 `y`에 의해 "weekly held"되었다라는 의미는, 비록 `y`를 통해 `x`의 값에 접근할 수 있지만, 표시하고-쓸기 알고리즘은 다른 것이 `x`를 \_strongly hold\* 하고 있지 않은 이상 도달할 수 있는 것으로 간주하지 않습니다. 여기서 논의되고 있는 것을 제외한, 대부분의 데이터 구조는 전달된 객체를 strongly hold하기에 객체에 언제든 접근할 수 있습니다. `WeakMap`과 `WeakSet`의 키는 프로그램의 어떤 것도 키를 참조하지 않는다면 가비지 콜렉션될 수 있습니다 (`WeakMap`에서 값은 그이후 가비지 콜렉션될 수 있음). 이 부분은 아래 두 가지 특성을 통해 보장됩니다:

* `WeakMap`과 `WeakSet`은 오직 객체 또는 기호만 저장합니다. 그 이유는 오직 객체들만이 가비지 콜렉트되고 원시값들은 복사되기에(`1 === 1`이나 `{} !== {}`이기에), 원시값들이 콜렉션에 영원히 남도록 합니다. Registered symbols (like `Symbol.for("key")`) 또한 복사될 수 있으므로, 가비지 수집이 불가능하지만, `Symbol("key")`로 생성된 기호는 가비지 수집이 가능합니다. `Symbol.iterator`와 같은 Well-known symbols은 고정된 세트로 제공되며 `Array.prototype`과 같은 고유 객체와 유사하게 프로그램 수명 동안 고유하므로 키로도 허용됩니다.
* `WeakMap`과 `WeakSet`은 순회할 수 없습니다. 이렇게 하면 `Array.from(map.keys()).length`을 사용해 객체의 상태를 관찰하거나 가비지 수집 대상이 되어야하는 임의의 키를 가지고 있어야 하는 것을 방지할 수 있습니다(가비지 콜렉션은 최대한 영향을 느끼지 못하도록 수행되어야 합니다).

주로 `WeakMap`과 `WeakSet`을 설명할 때, 보통 키가 먼저 가비지 수집되고 이후 값 또한 가비지 수집된다고 암시합니다. 그러나, 아래와 같이 키를 참조하는 값이 있는 케이스를 살펴보겠습니다.

```js
const wm = new WeakMap();
const key = {};
wm.set(key, { key });
// 값이 키를 참조하기에, `key`는 가비지 콜렉션 대상이 아니며
// 그 값은 map 안에서 strongly hold되어 있습니다.
```

만약 `key`가 실제 참조로 저장된다면, 다른 값이 `key`를 참조하지 않아도 순환 참조를 만들며 키와 값 모두 가비지 수집 대상이 아니도록 합니다. 그 이유는 만약 `key`가 가비지 수집되면, 특정 시점에 `value.key`는 존재하지 않는 주소를 가리키는 부적절한 상황이 될 수 있습니다. 이 점을 수정하기 위해, `WeakMap`과 `WeakSet`의 항목들은 실제 참조가 아니고 표시하고-쓸기 메커니즘의 보강점인 ephemerons입니다. Barros et al.은 해당 알고리즘에 대한 훌륭한 요약본을 제공합니다 (4 페이지). 한 문단을 인용해보겠습니다.

>
> Ephemerons은 키와 값 모두 weak 또는 strong으로 분류될 수 없는 약한 쌍(weak pairs)을 개선한 것입니다. 키의 연결성은 값의 연결성을 결정하나, 값의 연결성은 키의 연결성을 결정짓지 않습니다. […] 가비지 콜렉션이 ephemerons을 지원할 때, 가비지 콜렉션은 표시하기 및 쓸기의 2단계가 아니라 3단계에 걸쳐 진행됩니다.
>
>
>

대략적인 멘탈 모델로, `WeakMap`을 아래와 같은 구현체로 생각해보면:

>
> **Warning:** 아래는 폴리필도 아니고, 실제 가비지 콜렉션에 관계하는 엔진의 구현체 구조와는 거리가 멉니다.
>
>
>

```js
class MyWeakMap {
  #marker = Symbol("MyWeakMapData");
  get(key) {
    return key[this.#marker];
  }
  set(key, value) {
    key[this.#marker] = value;
  }
  has(key) {
    return this.#marker in key;
  }
  delete(key) {
    delete key[this.#marker];
  }
}
```

위에서와 같이, `MyWeakMap`는 키 콜렉션을 실제로 보유하지 않습니다. `MyWeakMap`은 단순히 전달받은 각 객체에 메타데이터를 추가합니다. 그 객체는 이후 표시하기-쓸기를 통해 가비지 콜렉션 가능하게 됩니다. 그러므로, `WeakMap`의 키를 순회하거나 `WeakMap`의 키를 모두 지우는 것(clear)은 불가능합니다(clear 역시도 전체 키 콜렉션 정보에 의존합니다).

좀 더 상세한 API는 keyed collections 가이드에서 확인하실 수 있습니다.

### WeakRefs와 FinalizationRegistry

>
> **Note:** `WeakRef`와 `FinalizationRegistry`는 가비지 수집 구조에 대한 직접적인 내부 탐색을 제공합니다. 런타임 시맨틱은 완전히 보장될 수 없기에 가능하면 사용하지 않는 것 (en-US)을 권장합니다.
>
>
>

값이 객체인 모든 변수들은 해당 객체에 대한 참조입니다. 그러나, 그러한 참조는 "strong" 합니다. 그런 변수가 있다면 가비지 컬렉터가 객체를 마킹하는 것을 막습니다. `WeakRef` (en-US)는 객체에 대한 "weak reference" 로 오브젝트가 가비지 수집될 수 있도록 하는 동시에 객체가 존재하는 동안 컨텐츠에 접근하는 것을 가능하게 합니다.

`WeakRef`의 한 가지 사용 사례는 문자열 URL을 큰 객체에 매핑하는 캐시시스템입니다. 이런 목적을 위해 `WeakMap`를 사용할 수는 없는데, 그 이유는 `WeakMap` 객체는 weakly held인 "keys" 를 가지나 "values" 은 그렇지 않기 때문입니다. 만약 키에 접근하면, 항상 결정적으로 값을 얻습니다 (키에 접근할 수 있다는 것이 키가 아직 존재한다는 것을 의미하기 때문에). 이 부분에서, 키에 접근 시 `undefined`를 얻는 것은 (만약 상응한 값도 메모리 상에 존재하지 않는다면) 다시 계산하면 되기에 괜찮으나, 우리는 도달할 수 없는 객체가 캐시 상에서 존재하기는 원하지 않습니다. 이러한 경우에, 우리는 일반적인 `Map`에 각각의 값이 객체의 실제값이 아닌 `WeakRef`이 되도록 구성할 수 있습니다.

```js
function cached(getter) {
  // 문자열 URL과 결과값의 WeakRefs를 연결하는 Map
  const cache = new Map();
  return async (key) => {
    if (cache.has(key)) {
      return cache.get(key).deref();
    }
    const value = await getter(key);
    cache.set(key, new WeakRef(value));
    return value;
  };
}

const getImage = cached((url) => fetch(url).then((res) => res.blob()));
```

`FinalizationRegistry` (en-US)는 가비지 수집을 관찰하기 위해 더욱 강력한 메커니즘을 제공합니다. `FinalizationRegistry`는 객체를 등록하고 가비지 수집되었을 때 알림을 받을 수 있도록 합니다. 예시로, 위의 예시로 작성된 캐시 시스템에서, 객체가 실제 값을 보유하고 있는지 여부에 상관없이 `WeakRef` 실제 값이 가비지 콜렉션 대상이 됩니다. 또한 시간이 흐르며 `Map`은 다량의 의미없는 항목들을 축적하게 됩니다. `FinalizationRegistry`를 사용하여 아래와 같이 해제를 할 수 있습니다.

```js
function cached(getter) {
  // 문자열 URL과 결과값의 WeakRefs를 연결하는 Map
  const cache = new Map();
  // 값이 가비지 수집될 때마다, 캐시의 키가 인수인 콜백이 호출되어
  // 캐시 항목을 제거할 수 있도록 함
  const registry = new FinalizationRegistry((key) => {
    // Note: WeakRef가 정말로 비었는지 테스트하는 것이 매우 중요합니다.
    // 그러지 않는다면, 콜백이 새로운 객체가 이 키로 추가된 이후 호출되고
    // 새롭게 생성된 메모리 상의 오브젝트가 삭제될 수 있습니다.
    if (!cache.get(key)?.deref()) {
      cache.delete(key);
    }
  });
  return async (key) => {
    if (cache.has(key)) {
      return cache.get(key).deref();
    }
    const value = await getter(key);
    cache.set(key, new WeakRef(value));
    registry.register(value, key);
    return value;
  };
}

const getImage = cached((url) => fetch(url).then((res) => res.blob()));
```

성능과 보안의 측면에서, 정확히 어느 시점에 콜백이 호출될지, 반드시 호출될지 보장할 수 없습니다. `FinalizationRegistry`는 오직 중요하지 않은 해제를 위해서만 사용되어야 합니다. 이 밖에도 항상 `finally` 블록을 수행하는 `try...finally` (en-US)와 같은 좀 더 결정적인 리소스 관리 방식이 있습니다. `WeakRef`와 `FinalizationRegistry`은 순전히 장시간 실행되는 프로그램의 메모리 사용 최적화를 위해 존재합니다.

`WeakRef` (en-US)와 `FinalizationRegistry` (en-US)를 통해 각각의 상세한 API를 추가적으로 확인하실 수 있습니다.