In this article, we take a simple function from the standard and try to understand its annotations. Let’s go!
preface
Even if you are familiar with JavaScript, it can be very difficult when you read the standard of this programming Language (EMCAScript Language specfication). At least for me, that’s how it felt when I first read the standards.
Now, let’s walk through the ECMA standard and try to understand it with a concrete example. The following code shows the Object. The prototype. The hasOwnProperty usage:
const o = { foo: 1 };
o.hasOwnProperty('foo'); // true
o.hasOwnProperty('bar'); // false
Copy the code
In this example, O does not have a property called hasOwnProperty, so we look up its prototype chain for properties. We found this hasOwnProperty in the Object. Prototype of o,
In order to describe the Object. The prototype. HasOwnProperty how this method works, ECMA standards used for code to illustrate the following:
Object.prototype.hasOwnProperty(V)
When the hasOwnProperty method is called with argument V, the following steps are taken:
- Let P be ? ToPropertyKey(V).\
- Let O be ? ToObject(this value).\
- Return ? HasOwnProperty(O, P).
HasOwnProperty(O, P) The abstract operation HasOwnProperty is used to determine whether an object has an own property with the specified property key. A Boolean value is returned. The operation is called with arguments O and P where O is the object and P is the property key. This abstract operation performs the following steps:
- Assert: Type(O) is Object.
- Assert: IsPropertyKey(P) is true.
- Let desc be ? O.[[GetOwnProperty]](P).
- If desc is undefined, return false.
- Return true.
And so on. Here are a few questions:
- What is “abstract operation”?
- What the hell is that thing between square brackets [[]]?
- Why is there a “hello” before the function?
- What does this assertion mean
Let’s get these questions straight!
Language Types and Specification Types
Let’s start with something that looks familiar. The standard uses values such as undefined, true, and false, which we are already familiar with in JavaScript. They are language values (values corresponding to language types defined in the standard)
The standard also uses language values internally, for example: an internal data type may contain a field with a value of true or false. In contrast, JavaScript engines typically do not use Language values internally. For example, if a JavaScript engine is written in C++, it will usually use true and false in the C++ language (not JavaScript’s own true and false).
Note: The author understands that some data types in the standard may refer to other data types defined by it. The data type of the JS engine depends on the data type of the language implementing the engine itself.
In addition to language types, specification types are also used in the standard, which only appear in the standard, not in the JavaScript language, meaning that the JavaScript engine does not need to implement these types. In this article, we’ll look at the Record type in specification Type (its subtype Completion Record)
Abstract Operations
Abstract Operations are functions defined in the ECMAScript standard. They are designed to make it easy to write standards. JavaScript engines do not implement them as separate functions. These functions also cannot be called in JavaScript.
Internal slots and Internal Methods
Internal slots and Internal Methods are things surrounded by square brackets [[]]
Internal slots are data members of JavaScript objects or Specification Types. They are used to store the state of objects.
Internal methods are member methods of JavaScript objects.
For example, every JavaScript object has an internal slot [[Prototype]] and an internal Method [[GetOwnProperty]].
Internal slots and internal Methods cannot be read by JavaScript. For example, you can’t access the o.[[Prototype]] property or call the O.[[GetOwnProperty]]() method. JavaScript engines can choose to implement them for internal use, but this is not required.
Sometimes internal methods delegate to abstract operations with similar names, for example: [[GetOwnProperty]]
[GetOwnProperty]
When the [[GetOwnProperty]] internal method of O is called with property key P, the following steps are taken:
- Return ! OrdinaryGetOwnProperty(O, P).
(We’ll find that exclamation point in the next chapter! Meaning of expression)
OrdinaryGetOwnProperty This is not an internal method because it is not associated with any object. Instead, the object it operates on is passed as a parameter.
OrdinaryGetOwnProperty is called “ordinary” because it operates on an ordinary object. ECMAScript objects can be ordinary or Exotic. Ordinary objects must have default behavior for a set of methods called essential Internal Methods. If an object deviates from these default behaviors, the object is Exotic.
The most familiar object is Array, because the behavior of its length property is non-default: setting the length property removes elements from the Array.
For a list of Essential Internal Methods, click here
Completion Records
The “?” we saw earlier. And “!” What exactly are these two symbols? To figure this out, we need to turn to Completion Records!
Completion Records is a Specification Type (again, specification Types are only types defined for the purpose of expressing standards). There is no equivalent internal data type in the JavaScript engine
A Completion Record is a Record type, which is a data type that has a fixed field. A Completion Record has three fields:
Name | Description |
---|---|
[[Type]] | One of: normal, break, continue, return, or throw. All other types except normal are abrupt completions. |
[[Value]] | The value that was produced when the completion occurred, for example, the return value of a function or the exception (if one is thrown). |
[[Target]] | Used for directed control transfers (not relevant for this blog post). |
Each abstraction implicitly returns a Completion Record. Even an abstract operation that looks like it returns a simple type (Boolean) is implicitly wrapped as a Normal Completion Record (see Implicit Completion Values).
If an algorithm throws an exception, that means that the [[Value]] thrown by the returned Completion Record that has [[Type]] is an exception object. We ignore the break, continue, and return types.
ReturnIfAbrupt(argument) means to follow the following steps:
- If the argument is abnormal, return to the argument
- Set argument to argument.[[Value]]
The above abstract operation means that we examine a Completion Record, and if it is an abrupt Completion, we return it directly. Otherwise, we extract its value from the Completion Record.
ReturnIfAbrupt looks like a function call, but it’s not. It causes the ReturnIfAbrupt function to return. It’s more like a macro in C.
ReturnIfAbrupt may be used as follows:
- Let obj be Foo(). (obj is a Completion Record.)
- ReturnIfAbrupt(obj).
- Bar(obj). (If we’re still here, obj is the value extracted from the Completion Record.)
Now it’s time to reveal “?” The meaning of? Foo() is equivalent to ReturnIfAbrupt(Foo()). Using the abbreviation is very elegant: we don’t need to explicitly write error-handling code every time.
Similarly, Val be! Foo() is equivalent to:
Let val be Foo().
Assert: val is not an abrupt completion. Set val to val.[[Value]].
Using this knowledge, we can rewrite the Object. The prototype. The hasOwnProperty, like this:
- Let P be ToPropertyKey(V).
- If P is an abrupt completion, return P
- Set P to P.[[Value]]
- Let O be ToObject(this value).
- If O is an abrupt completion, return O
- Set O to O.[[Value]]
- Let temp be HasOwnProperty(O, P).
- If temp is an abrupt completion, return temp
- Let temp be temp.[[Value]]
- Return NormalCompletion(temp)
We can then rewrite HasOwnProperty as follows:
- Assert: Type(O) is Object.
- Assert: IsPropertyKey(P) is true.
- Let desc be O.[GetOwnProperty].
- If desc is an abrupt completion, return desc
- Set desc to desc.[[Value]]
- If desc is undefined, return > NormalCompletion(false).
- Return NormalCompletion(true).
We can still do without exclamation marks! Override the [GetOwnProperty] internal method
- Let temp be OrdinaryGetOwnProperty(O, P).
- Assert: temp is not an abrupt completion.
- Let temp be temp.[[Value]].
- Return NormalCompletion(temp).
We assume that temp is a completely new temporary variable that does not conflict with any other variables.
Here we also use the knowledge that when a return statement returns a type other than a Completion Record, we implicitly wrap it as a NormalCompletion type.
Side track: Return ? Foo()
The standard uses Return? Foo() — Why use “?”
Return ? Foo() can be expanded to the following statement:
- Let temp be Foo().
- If temp is an abrupt completion, return temp.
- Set temp to temp.[[Value]].
- Return NormalCompletion(temp).
It’s the same thing as Return Foo(), “?” For abrupt and normal types of Completion, you can make them behave the same way.
Return ? Foo() is only there for editing reasons, which makes it clear what Foo means when returning a Completion Record.
Asserts
In the criteria, implies an algorithm invariant condition. They are added for clarity, but don’t add any dependencies like the implementation does, and the implementation doesn’t need to check for them.
More and more
Abstract operations delegate to other abstract operations (see figure below), and based on this article, we should be able to figure out what they do. We’ll encounter property descriptors, which are just another canonical type.
conclusion
The way we read out a simple Object. The prototype. The hasOwnProperty standards and its associated some abstract operation. Are we familiar with these abbreviations for handling errors: “?” And “!” ? We also encountered language types, Specification types, Internal slots, and Internal methods.
The author summarizes
Language type: indicates the built-in type of the language, such as number, string, Boolean in JS. The corresponding language value is: 1, ‘a’, true…
Specification Types are the types of language standards that are designed to help describe the content of the standard and are not implemented by the language engine.
Internal slots: We use [[]] to represent attributes that we can’t access through JavaScript. JavaScript engines can choose to implement them for internal use, but this is not required.
Internal methods: Similar to internal slots, which use [[]] to represent some methods, we can not use JavaScript to call these methods. Again, the JavaScript engine can choose to implement these methods, but it doesn’t have to.
! And? : shorthand notation, commonly used to deal with error code, and C language “macro” type, see above.
The original address