부록

프로토타입 상속

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)

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

  • 이 토큰은 줄바꿈으로 이전 토큰과 구분됩니다.
  • 해당 토큰은 }입니다.
  • 이전 토큰은 )이며 삽입된 세미콜론은 do...while 문의 끝 세미콜론이 됩니다.

자세한 내용은 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 작성 방법을 관리하는 표준은 언어 초기 설계 단계에서 고려한 것을 훨씬 넘어섰습니다. 자바스크립트의 예상 동작을 새로 변경할 때마다 이전 웹사이트에서 오류가 발생하지 않아야 합니다.

ES5는 전체 스크립트 또는 개별 함수에 보다 제한적인 언어 규칙 집합을 선택할 수 있는 방법인 '엄격 모드'를 도입하여 기존 구현을 해치지 않고 자바스크립트 의미 체계와 관련된 오래된 문제를 해결합니다. 엄격 모드를 사용 설정하려면 스크립트 또는 함수의 첫 번째 줄에 문자열 리터럴 "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

참조 기준, 값 기준

객체의 속성, 함수 매개변수, 배열, 집합, 매핑의 요소를 포함한 모든 변수는 기본 값 또는 참조 값을 포함할 수 있습니다.

원시 값이 한 변수에서 다른 변수로 할당되면 자바스크립트 엔진은 그 값의 복사본을 만들어 변수에 할당합니다.

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

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에 대한 액세스 권한이 크게 감소했거나 아예 없습니다.
  • 기본 스레드와 통신할 수 있는 방법이 제한됩니다.

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

호출 스택

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

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

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

이벤트 루프 및 콜백 큐

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

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