이 글은 How to Read the ECMAScript Specification(Timothy Gu)을 번역한 것입니다. 원문은 다음 링크에서 찾아보실 수 있습니다. 번역이 완벽하지 않을 수 있습니다. 혹시 잘못된 부분이 있다면 댓글이나 이메일로 알려주세요!
분량이 많아 부분별로 나눠서 올릴 예정입니다 :) 이번 글은 2장 Runtime semantics 중 1절부터 3절에 해당하는 부분입니다.
언어와 API의 런타임 semantics는 스펙에서 가장 큰 부분을 차지하며서 동시에 많은 사람들이 어려워하는 부분이기도 합니다.
전반적으로 스펙에서 이 부분들을 읽는 것은 꽤 간단합니다. 하지만 스펙에는 처음 시작한 사람(최소한 저에게는)이 읽기에는 꽤 별로인 표현들이 많습니다. 이제부터 이런 컨벤션 중 일부를 설명하고 평소 워크플로에서 이런 것들이 어떻게 작동하는지 설명해보도록 하겠습니다.
ECMAScript의 런타임 semantics 중 대부분은 의사코드와는 다르게 훨씬 더 정확한 알고리즘 단계들로 명세되어 있습니다.
알고리즘 단계의 예시로는:
1. a를 1이라 하자
2. b를 a+a라 하자
3. 만약 b가 2이면
1. 야호! 계산이 잘 되었습니다.
4. 아닐 경우
1. 펑!
더 자세히 읽기: §5.2 Algorithm Conventions
문서를 읽다 보면 가끔 함수와 비슷한 무엇인가가 불리는 것을 볼 수 있습니다. Boolean()
함수의 첫 단계는:
Boolean
이 인수인 value와 함께 호출되면 다음 과정이 수행됩니다:
여기서 “ToBoolean” 함수가 바로 추상 연산이라고 불립니다: 추상적이라 불리는 이유는 실제 JavaScript 코드에 함수로 드러나있지 않기 때문이죠. 단지 스펙을 만드는 사람들이 같은 것을 반복해서 쓰지 않기 위해 만든 것입니다.
참고: 지금은 ToBoolean 앞의 ! 를 신경쓰지 마세요. 이후 § 2.4 Completion Records; ? and !에서 논의할 것입니다.
더 자세히 읽기: §5.2.1 Abstract Operations
읽다 보면 가끔 "Let proto be obj.[[Prototype]]."과 같이 [[표기]]와 같은 것이 사용되는 것을 볼 수 있습니다. 이 표기는 맥락에 따라 기술적으로 다양한 것을 의미할 수 있지만 결국 돌고 돌아 JavaScript 코드상 관찰할 수 없는 어떤 내부 프로퍼티에 관한 것임을 알 수 있습니다.
정확히는 3가지를 의미할 수 있으며 스펙에서의 예시와 함께 설명해드리도록 하겠습니다. 하지만 지금은 그냥 넘겨도 괜찮습니다.
ECMAScript에서 레코드라는 용어는 고정된 키들로 이뤄진 키-값 쌍을 지칭합니다—C와 같은 언어에서 structure와 비슷하죠. 레코드의 각 키-값 쌍은 필드로 지칭합니다. 레코드들은 스펙에서만 볼 수 있고 실제 JavaScript 코드에는 없기 때문에 [[표기]]를 레코드의 필드를 지칭하는 것은 타당합니다.
특히 속성 기술자(Property Descriptors)는 [[Value]], [[Writable]], [[Get]], [[Set]], [[Enumerable]], 그리고 [[Configurable]]와 같은 레코드의 필드로도 모델화되어 있습니다. IsDataDescriptor 추상 연산은 이 표기를 광범위하게 사용합니다.
추상 연산 IsDataDescriptor가 속성 기술자인 Desc와 함께 호출되면 다음과 같은 작업이 수행됩니다:
1. Desc가 undefined일 경우 false를 리턴
2. Desc.[[Value]]와 Desc.[[Writable]]이 모두 없을 경우 false를 리턴
3. true를 리턴
레코드의 또다른 구체적인 예시는 다음 섹션인 § 2.4 Completion Records; ? and !에서 찾아볼 수 있습니다.
JavaScript 객체에는 스펙이 데이터를 그 내부에 보관하는데 사용하는 내부 슬롯이 있을 수 있습니다. 레코드 필드와 같이 이러한 내부 슬롯은 JavaScript로 관찰될 수는 없지만 구글 크롬 DevTools와 같은 특정 툴로 일부가 확인될 수 있습니다. 따라서 내부 슬롯을 표현하기 위해 [[표기]]를 사용하는 것은 타당합니다.
내부 슬롯에 관한 구체적인 것들은 § 2.5 JavaScript 객체에서 다뤄질 것입니다. 지금은 무엇을 위해 사용되는지 너무 걱정하지 말고 예시를 잘 살펴보세요.
대부분의 JavaScript 객체는 상속받은 객체를 참조하는 내부 슬롯인 [[Prototype]]을 가지고 있습니다. 이 내부 슬롯의 값은 대체로 Object.getPrototypeOf()
가 반환하는 값입니다. OrdinaryGetPrototypeOf 추상 연산에서는 이 내부 슬롯 값에 접근합니다:
OrdinaryGetPrototypeOf 추상 연산이 객체 O와 함께 호출될 경우 다음 작업이 수행됩니다.
1. O.[[Prototype]]를 반환
참고: 객체의 내부 슬롯과 레코드의 필드는 똑같이 생겼지만 이 표기의 선행자(점 앞에 있는 부분)이 객체인지 레코드인지 보면 명확히 할 수 있습니다. 주변 맥랙을 보면 보통 명백합니다.
JavaScript 객체는 내부 메소드라 불리는 것들을 가질 수 있습니다. 내부 슬롯과 마찬가지로 내부 메소드는 JavaScript상에서 직접적으로 관찰될 수 없습니다. 따라서 내부 메소드를 표현하기 위해서도 [[표기]]를 사용하는 것은 타당합니다.
내부 메소드에 관한 구체적인 것들은 § 2.5 JavaScript 객체에서 다뤄질 것입니다. 지금은 무엇을 위해 사용되는지 너무 걱정하지 말고 예시를 잘 살펴보세요.
모든 JavaScript 함수들은 해당 함수를 실행하는 내부 메소드인 [[Call]]을 가지고 있습니다. Call 추상 연산은 다음과 같은 과정을 수행합니다.
- ? F. [[Call]](V, 인자 리스트)를 반환
여기에서 F는 JavaScript 함수 객체를 의미합니다. 이 경우 F의 내부 메소드인 [[Call]] 자체가 인자들인 V와 인자 리스트와 함께 호출됩니다.
참고: [[표기]]의 세 번째 의미인 내부 메소드는 함수 호출처럼 보이는 형식으로 인해 다른 두 가지와 구별될 수 있습니다.