Understanding the ECMAScript Specification, Part 1



Good day, friends!

In this article, we take a function from the specification and analyze its explanation. Go.

Foreword


Even if you know JavaScript well, reading the specification can be difficult. The following code demonstrates the use of Object.prototype.hasOwnProperty:

const o = {
    foo: 1
}
o.hasOwnProperty('foo') // true
o.hasOwnProperty('bar') // false

In the example, the “o” object does not have the “hasOwnProperty” method, so we refer to its prototype - “Object.prototype” (prototype chain).

To describe how Object.hasOwnProperty works, the specification uses the following pseudocode:

Object.prototype.hasOwnProperty (V)

When hasOwnProperty is called with argument V, the following steps are performed: ... and HasOwnProperty (O, P) The HasOwnProperty abstract operation is used to determine whether the object has its own property with a specific key. Boolean value is returned. The operation is called with arguments O and P. This operation consists of the following steps: What is an "abstract operation"? What [[ ]]? Why does a function have a question mark? What does “affirmation” mean? Let's find out.

  1. P ? ToPropertyKey(V)
  2. O ? ToObject( this)
  3. ? HasOwnProperty(O, P).







  1. (assert): Type(O) Object.
  2. : IsPropertyKey(P) true.
  3. desc ? O.[[GetOwnProperty]](P).
  4. desc undefined, false.
  5. true.





Types in the language and types in the specification


Let's start with something familiar. In the specification, there are such values ​​as undefined, true and false, which are known to us by JS. All of them are “language values” , “values ​​of language types” , which are also defined by the specification.

The specification uses built-in language values, for example, an internal data type may contain a field with a value of true or false. JS engines generally do not use linguistic meanings in their internal mechanisms. For example, if the JS engine is written in C ++, it will most likely use true and false from C ++, rather than the internal representation of Boolean values ​​from JS.

In addition to language types, the specification uses special types (specification types) - types that are used only in the specification. JS engines are not required to execute them (but they can). In this article we will get acquainted with the special type “Record” (record) and its subtype “Completion Record” (record of completion).

Abstract operations


Abstract operations are functions defined in the specification; they are defined in order to reduce specification. JS engines are not required to perform them as stand-alone functions. In JS, they cannot be called directly.

Internal slots and internal methods


Internal slots and internal methods are indicated by names enclosed in [[]].

Internal slots are data elements (sets) of a JS object or special type. They are used to store information about the state of the object. Internal methods are member functions of a JS object.

For example, each JS object has an internal [[Prototype]] slot and an internal [[GetOwnProperty]] method.

Internal slots and methods are not available in JS. For example, we cannot access o. [[Prototype]] or call o. [[GetOwnProperty]] (). The JS engine can execute them for its own (internal) needs, but is not required to do this.

Sometimes internal methods become abstract operations of the same name, as is the case with [[GetOwnProperty]]:

[[GetOwnProperty]] (P)

When the [[GetOwnProperty]] internal method of the “O” object is called with the key “P”, the following actions are performed: OrdinaryGetOwnProperty is not an internal method, since it is not associated with any object; the object with which it works is passed to it as a parameter. OrdinaryGetOwnProperty is called “ordinary” because it operates on ordinary objects. Objects in ECMAScript are ordinary and unusual (exotic). An object is ordinary if it behaves predictably in response to a set of methods called essential internal methods. Otherwise (when the object behaves unpredictably; not as expected; when the behavior of the object deviates from normal, is deviant), it is considered unusual.

  1. ! OrdinaryGetOwnProperty(O, P)





The most famous unusual object is Array, because its “length” property behaves non-standard: setting this property can remove elements from the array.

A list of basic internal methods can be found here .

Completion record


What about question and exclamation marks? To understand this, you need to understand what a completion record is .

A completion record is a special type (defined solely for specification purposes). The JS engine is not required to have the same internal data type.

A completion record is a data type that has a fixed set of named fields. The completion record has three fields:
[[Type]]normal, break, continue, return throw. , normal, « () » (abrupt comlpetions)
[[Value]], , , , ,
[[Target]]( )

Each abstract operation implicitly returns a completion record. Even if the result of the abstract operation is a simple logical value, it is wrapped in a completion record of type normal (see Implicit Completion Values ).

Note 1: the specification is not very consistent in this part; There are several helper functions that return bare values ​​that are used as is, without being retrieved from the completion record.

Note 2: Specification authors seek to make completion record processing more explicit.

If the algorithm throws an exception, it means that a completion record will be received with the type ([[Type]]) throw and the value ([[Value]]) as an exception object. We will not consider other types (break, continue and return) for now.

ReturnIfAbrupt (argument) means performing the following operations: This is what the completion record is; if it ends abruptly, immediately return. Otherwise, we extract the value from the completion record. ReturnIfAbrupt looks like a function call, but it is not. We call a function that returns ReturnIfAbrupt (), and not ReturnIfAbrupt itself. Its behavior is more like a macro in C-like programming languages. ReturnIfAbrupt can be used as follows: Here comes into play

  1. argument - (abrupt), argument.
  2. argument argument.[[Value]].







  1. obj Foo() (obj - ).
  2. ReturnIfAbrupt(obj).
  3. Bar(obj) ( , , obj - , ).

Question mark : record  ? Foo () is equivalent to ReturnIfAbrupt (Foo ()). Using this abbreviation has practical value: we do not need to write error handler code every time.

By analogy, let val be the entry ! Foo () is equivalent to the following: Using this knowledge, we can rewrite Object.prototype.hasOwnProperty as follows: Object.prototype.hasOwnProperty (P) ... HasOwnProperty can be rewritten like this: HasOwnProperty (O, P) We can also rewrite the internal method [ [GetOwnProperty]] without an exclamation mark: O. [[GetOWnProperty]] We assume that temp is a new temporary variable that does not interact with anything.

  1. val Foo().
  2. : val .
  3. val val.[[Value]].





  1. P ToProperty(V).
  2. P , P.
  3. P P.[[Value]].
  4. O ToObject( this).
  5. O , O.
  6. O O.[[Value]].
  7. temp HasOwnProperty(O, P).
  8. temp , temp.
  9. temp temp.[[Value]].
  10. NormalCompletion(temp).





  1. : Type(O) Object.
  2. : IsPropertyKey(P) true.
  3. desc O.[[GetOWnProperty]](P).
  4. desc , desc.
  5. desc desc.[[Value]].
  6. desc undefined, NormalCompletion(false).
  7. NormalCompletion(true).





  1. temp OrdinaryGetOwnProperty(O, P).
  2. : temp .
  3. temp temp.[[Value]].
  4. NormalCompletion(temp).



We also know that in the case where the return statement returns something other than a completion record, this something implicitly wraps itself in a NormalCompletion.

Fallback: Return? Foo ()


Does the specification use Return? Foo () - why is there a question mark here?

Record Return? Foo () can be expanded as follows: Return behavior? Foo () is the same for both normal and sudden termination. Record Return? Foo () allows you to more clearly indicate that Foo returns a completion record.

  1. temp Foo().
  2. temp , temp.
  3. temp temp.[[Value]].
  4. NormalCompletion.





Statements


Statements in the specification “assert” the invariant conditions of the algorithms. They are added to the specification for clarity, but do not contain any implementation requirements; therefore, they do not need to be verified by a specific implementation.

What's next?


We learned to read the specification for simple methods such as Object.prototype.hasOwnProperty and abstract operations such as HasOwnProperty. With this knowledge, we can understand what other abstract operations do, which will be discussed in the next part. Also in the next article, we will look at Property Descriptors, which are another special type.


Thank you for attention. Happy coding!

All Articles