부록

프로토타입 상속

nullundefined를 제외하고 각 기본 데이터 유형에는 값으로 작업하는 메서드를 제공하는 상응하는 객체 래퍼인 프로토타입이 있습니다. 원시에서 메서드 또는 속성 조회가 호출되면 JavaScript는 백그라운드에서 원시를 래핑하고 대신 래퍼 객체에서 메서드를 호출하거나 속성 조회를 실행합니다.

예를 들어 문자열 리터럴에는 자체 메서드가 없지만 상응하는 String 객체 래퍼 덕분에 .toUpperCase() 메서드를 호출할 수 있습니다.

"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL

이를 프로토타입 상속이라고 합니다. 값의 상응하는 생성자에서 속성과 메서드를 상속합니다.

Number.prototype
> Number { 0 }
>  constructor: function Number()
>  toExponential: function toExponential()
>  toFixed: function toFixed()
>  toLocaleString: function toLocaleString()
>  toPrecision: function toPrecision()
>  toString: function toString()
>  valueOf: function valueOf()
>  <prototype>: Object {  }

값으로 정의하는 대신 이러한 생성자를 사용하여 프리미티브를 만들 수 있습니다. 예를 들어 String 생성자를 사용하면 문자열 리터럴이 아닌 문자열 객체가 생성됩니다. 문자열 값뿐만 아니라 생성자의 모든 상속된 속성과 메서드를 포함하는 객체입니다.

const myString = new String( "I'm a string." );

myString;
> String { "I'm a string." }

typeof myString;
> "object"

myString.valueOf();
> "I'm a string."

대부분의 경우 결과 객체는 객체를 정의하는 데 사용된 값으로 작동합니다. 예를 들어 new Number 생성자를 사용하여 숫자 값을 정의하면 Number 프로토타입의 모든 메서드와 속성이 포함된 객체가 생성되지만 숫자 리터럴에서와 마찬가지로 이러한 객체에 수학 연산자를 사용할 수 있습니다.

const numberOne = new Number(1);
const numberTwo = new Number(2);

numberOne;
> Number { 1 }

typeof numberOne;
> "object"

numberTwo;
> Number { 2 }

typeof numberTwo;
> "object"

numberOne + numberTwo;
> 3

JavaScript의 기본 제공 프로토타입 상속은 이러한 생성자가 실질적인 이점을 제공하지 않음을 의미하므로 이러한 생성자를 사용할 필요가 거의 없습니다. 생성자를 사용하여 프리미티브를 만들면 결과가 단순 리터럴이 아닌 객체이므로 예상치 못한 결과가 발생할 수도 있습니다.

let stringLiteral = "String literal."

typeof stringLiteral;
> "string"

let stringObject = new String( "String object." );

stringObject
> "object"

이로 인해 엄격한 비교 연산자의 사용이 복잡해질 수 있습니다.

const myStringLiteral = "My string";
const myStringObject = new String( "My string" );

myStringLiteral === "My string";
> true

myStringObject === "My string";
> false

자동 세미콜론 삽입 (ASI)

JavaScript 인터프리터는 스크립트를 파싱하는 동안 자동 세미콜론 삽입 (ASI)이라는 기능을 사용하여 누락된 세미콜론의 인스턴스를 수정할 수 있습니다. JavaScript 파서가 허용되지 않는 토큰을 발견하면 다음 조건 중 하나 이상이 참인 경우 해당 토큰 앞에 세미콜론을 추가하여 잠재적인 문법 오류를 수정하려고 시도합니다.

  • 이 토큰은 이전 토큰과 줄바꿈으로 구분됩니다.
  • 이 토큰은 }입니다.
  • 이전 토큰은 )이고 삽입된 세미콜론은 dowhile 문에서의 끝 세미콜론입니다.

자세한 내용은 ASI 규칙을 참고하세요.

예를 들어 다음 문 뒤에서 세미콜론을 생략해도 ASI로 인해 문법 오류가 발생하지 않습니다.

const myVariable = 2
myVariable + 3
> 5

그러나 ASI는 한 줄에 여러 개의 문을 고려할 수 없습니다. 한 줄에 두 개 이상의 문을 작성하는 경우 세미콜론으로 구분해야 합니다.

const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier

const myVariable = 2; myVariable + 3;
> 5

ASI는 JavaScript에 내장된 일종의 문법적 유연성이 아니라 오류 수정 시도입니다. 올바른 코드를 생성하는 데 의존하지 않도록 적절한 위치에 세미콜론을 사용해야 합니다.

엄격 모드

JavaScript 작성 방식을 규제하는 표준은 언어 초기 설계 시 고려된 것보다 훨씬 더 발전했습니다. JavaScript의 예상 동작에 대한 모든 새로운 변경사항은 이전 웹사이트에서 오류가 발생하지 않도록 해야 합니다.

ES5는 전체 스크립트 또는 개별 함수에 더 제한적인 언어 규칙 집합을 선택하는 방법인 'strict 모드'를 도입하여 기존 구현을 중단하지 않고 JavaScript 시맨틱스의 오래된 문제를 해결합니다. 엄격 모드를 사용 설정하려면 스크립트 또는 함수의 첫 번째 줄에서 문자열 리터럴 "use strict" 뒤에 세미콜론을 사용합니다.

"use strict";
function myFunction() {
  "use strict";
}

엄격 모드는 특정 '안전하지 않은' 작업이나 지원 중단된 기능을 방지하고, 일반적인 '사일런트' 오류 대신 명시적 오류를 발생시키며, 향후 언어 기능과 충돌할 수 있는 문법 사용을 금지합니다. 예를 들어 변수 범위에 관한 초기 설계 결정으로 인해 개발자가 포함 컨텍스트와 관계없이 변수를 선언할 때 var 키워드를 생략하여 전역 범위를 실수로 '오염'할 가능성이 높아졌습니다.

(function() {
  mySloppyGlobal = true;
}());

mySloppyGlobal;
> true

최신 JavaScript 런타임은 실수로 또는 의도적으로 이를 사용하는 웹사이트가 손상될 위험 없이 이 동작을 수정할 수 없습니다. 대신 최신 JavaScript는 개발자가 새 작업에 엄격 모드를 선택하도록 허용하고, 기존 구현을 중단하지 않는 새 언어 기능의 컨텍스트에서만 기본적으로 엄격 모드를 사용 설정하여 이를 방지합니다.

(function() {
    "use strict";
    mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal

"use strict"문자열 리터럴로 작성해야 합니다. 템플릿 문자열(use strict)은 작동하지 않습니다. 또한 의도한 컨텍스트에서 실행 가능한 코드 앞에 "use strict"를 포함해야 합니다. 그렇지 않으면 인터프리터에서 무시합니다.

(function() {
    "use strict";
    let myVariable = "String.";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal

(function() {
    let myVariable = "String.";
    "use strict";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope

참조 방식, 값 방식

객체의 속성, 함수 매개변수, 배열, 세트 또는 의 요소를 비롯한 모든 변수는 원시 값 또는 참조 값을 포함할 수 있습니다.

원시 값이 한 변수에서 다른 변수에 할당되면 JavaScript 엔진은 해당 값의 사본을 만들고 변수에 할당합니다.

객체 (클래스 인스턴스, 배열, 함수)를 변수에 할당하면 해당 객체의 새 사본을 만드는 대신 변수에 메모리에 저장된 객체의 위치에 대한 참조가 포함됩니다. 따라서 변수가 참조하는 객체를 변경하면 해당 변수에 포함된 값뿐만 아니라 참조되는 객체도 변경됩니다. 예를 들어 객체 참조가 포함된 변수로 새 변수를 초기화한 다음 새 변수를 사용하여 해당 객체에 속성을 추가하면 속성과 그 값이 원래 객체에 추가됩니다.

const myObject = {};
const myObjectReference = myObject;

myObjectReference.myProperty = true;

myObject;
> Object { myProperty: true }

이는 객체를 변경하는 데뿐만 아니라 엄격한 비교를 실행하는 데도 중요합니다. 객체 간의 엄격한 등식은 두 변수가 모두 true로 평가되도록 동일한 객체를 참조해야 하기 때문입니다. 구조적으로 동일하더라도 다른 객체를 참조할 수 없습니다.

const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};

myObject === myNewObject;
> false

myObject === myReferencedObject;
> true

메모리 할당

JavaScript는 자동 메모리 관리를 사용하므로 개발 과정에서 메모리를 명시적으로 할당하거나 할당 해제할 필요가 없습니다. JavaScript 엔진의 메모리 관리 접근 방식에 관한 세부정보는 이 모듈의 범위를 벗어나지만 메모리가 할당되는 방식을 이해하면 참조 값을 사용하는 데 유용한 컨텍스트를 얻을 수 있습니다.

메모리에는 '스택'과 '힙'이라는 두 가지 '영역'이 있습니다. 스택은 정적 데이터(원시 값 및 객체 참조)를 저장합니다. 이 데이터를 저장하는 데 필요한 고정된 양의 공간은 스크립트가 실행되기 전에 할당할 수 있기 때문입니다. 힙은 실행 중에 크기가 변경될 수 있으므로 동적으로 할당된 공간이 필요한 객체를 저장합니다. 메모리는 '가비지 컬렉션'이라는 프로세스에 의해 해제되며, 이 프로세스는 메모리에서 참조가 없는 객체를 삭제합니다.

기본 스레드

JavaScript는 기본적으로 '동기' 실행 모델을 사용하는 단일 스레드 언어입니다. 즉, 한 번에 하나의 작업만 실행할 수 있습니다. 이 순차 실행 컨텍스트를 기본 스레드라고 합니다.

기본 스레드는 HTML 파싱, 페이지의 일부 렌더링 및 재렌더링, CSS 애니메이션 실행, 간단한 작업 (예: 텍스트 강조 표시)부터 복잡한 작업 (예: 양식 요소와 상호작용)에 이르기까지 다른 브라우저 작업에서 공유됩니다. 브라우저 공급업체는 기본 스레드에서 실행되는 작업을 최적화하는 방법을 찾았지만 더 복잡한 스크립트는 여전히 기본 스레드의 리소스를 너무 많이 사용하고 전반적인 페이지 성능에 영향을 줄 수 있습니다.

일부 작업은 웹 워커라는 백그라운드 스레드에서 실행할 수 있지만 다음과 같은 제한사항이 있습니다.

  • 작업자 스레드는 독립형 JavaScript 파일에서만 작동할 수 있습니다.
  • 브라우저 창과 UI에 대한 액세스가 크게 제한되거나 차단됩니다.
  • 기본 스레드와 통신하는 방법이 제한됩니다.

이러한 제한사항으로 인해 기본 스레드를 차지할 수 있는 집중적이고 리소스 집약적인 작업에 적합합니다.

호출 스택

'실행 컨텍스트'(실제로 실행 중인 코드)를 관리하는 데 사용되는 데이터 구조는 호출 스택(종종 '스택'이라고만 함)이라는 목록입니다. 스크립트가 처음 실행되면 JavaScript 인터프리터는 '전역 실행 컨텍스트'를 만들고 이를 호출 스택에 푸시합니다. 이 전역 컨텍스트 내의 문이 한 번에 하나씩 위에서 아래로 실행됩니다. 인터프리터가 전역 컨텍스트를 실행하는 동안 함수 호출을 발견하면 해당 호출의 '함수 실행 컨텍스트'를 스택의 맨 위에 푸시하고 전역 실행 컨텍스트를 일시중지한 후 함수 실행 컨텍스트를 실행합니다.

함수가 호출될 때마다 해당 호출의 함수 실행 컨텍스트가 현재 실행 컨텍스트 바로 위에 있는 스택 상단으로 푸시됩니다. 호출 스택은 'LIFO(후입선출)' 방식으로 작동합니다. 즉, 스택에서 가장 높은 위치에 있는 가장 최근 함수 호출이 실행되고 해결될 때까지 계속됩니다. 이 함수가 완료되면 인터프리터는 호출 스택에서 이 함수를 삭제하고 이 함수 호출을 포함하는 실행 컨텍스트가 스택에서 가장 높은 항목이 되어 다시 실행을 재개합니다.

이러한 실행 컨텍스트는 실행에 필요한 모든 값을 캡처합니다. 또한 상위 컨텍스트를 기반으로 함수 범위 내에서 사용할 수 있는 변수와 함수를 설정하고 함수 컨텍스트에서 this 키워드의 값을 결정하고 설정합니다.

이벤트 루프 및 콜백 대기열

이 순차 실행은 서버에서 데이터를 가져오거나, 사용자 상호작용에 응답하거나, setTimeout 또는 setInterval로 설정된 타이머를 기다리는 등 콜백 함수가 포함된 비동기 작업이 작업이 완료될 때까지 기본 스레드를 차단하거나 콜백 함수의 실행 컨텍스트가 스택에 추가되는 순간 예기치 않게 현재 실행 컨텍스트를 중단한다는 것을 의미합니다. 이를 해결하기 위해 JavaScript는 '이벤트 루프'와 '콜백 큐'('메시지 큐'라고도 함)로 구성된 이벤트 기반 '동시 실행 모델'을 사용하여 비동기 작업을 관리합니다.

비동기 작업이 기본 스레드에서 실행되면 콜백 함수의 실행 컨텍스트가 호출 스택 위에 있는 것이 아니라 콜백 대기열에 배치됩니다. 이벤트 루프는 리액터라고도 하는 패턴으로, 호출 스택과 콜백 큐의 상태를 지속적으로 폴링합니다. 콜백 대기열에 작업이 있고 이벤트 루프에서 호출 스택이 비어 있다고 판단되면 콜백 대기열의 작업이 실행되도록 한 번에 하나씩 스택에 푸시됩니다.