Translator: Li Songfeng

The purpose of this article is to try to provide the translation of the core terms in the ECMAScript specification for the evaluation of the peers.

V8. Dev /blog/unders…

To understand the specification, take a JavaScript feature we know about and see how it is specified.

Note that this article contains algorithms copied from the ECMAScript specification of February 2020, the official specification prevails.

We know that accessing an object’s properties requires a walk prototype chain. If there is no property on the object to read, it searches down the stereotype chain until it finds that property (or an object without a stereotype). This process can be called prototype chain-walking or prototype chain-walking. (Translator’s Note)

Such as:

const o1 = { foo: 99 }; const o2 = {}; Object.setPrototypeOf(o2, o1); o2.foo; / / - 99Copy the code

Where is this archetypal walkthrough defined?

The best place to start is with object internal methods.

There are two internal methods associated with finding properties: [[GetOwnProperty]] and [[Get]]. We’re interested in things that don’t limit their own properties, so search for [[Get]].

However, Property Descriptor specification types also have a field called [[Get]]. So be careful to distinguish between their different uses when searching.

[[Get]] is an essential interal mehtod. Ordinary objects must implement the default behavior defined by the basic internal methods. On the other hand, exotic Objects can define their own internal methods [[Get]] that behave differently from the default. This article deals only with ordinary objects, so it does not cover custom internal methods.

The default implementation of [[Get]] delegates to OrdinaryGet (an abstract operation). (Translator’s Note) :

[[Get]] (P, Receiver) When calling O’s internal method [[Get]] with the property key P and ECMAScript language value Receiver, perform the following steps: 1. Return? OrdinaryGet (O, P, Receiver).

Receiver is used as the this value when the getter for the accessor property is called. We’ll see that in a moment.

OrdinaryGet is defined as follows:

OrdinaryGet (O, P, Receiver) The following steps are performed when the abstract operation OrdinaryGet is called with the object O, the property key P, and the ECMAScript language value Receiver. Assertion: IsPropertyKey(P) is true; 2. What do you mean? O. [[GetOwnProperty]] (P “[GetOwnProperty]”); 3. If desc is undefined, a. Set parent as? O. [[GetPrototypeOf]] ([GetPrototypeOf] “”); B. If parent is null, return undefined. C. return? The parent. [[Get]] (P, Receiver “[Get]”); If IsDataDescriptor(desc) is true, return desc.[[Value]]; 5. Assert: IsAccessorDescriptor(desc) is true; Getdesc.[[Get]]; 7. If the getter is undefined, return undefined. 8. Return? Call (getter, Receiver).

The stereotype chain lookup is defined in step 3: if no proprietary property of the same name is found in the previous step, the [[Get]] method of the stereotype is called, which delegates to the OrdinaryGet abstract operation. If the attribute is not found on the first stereotype, the [[Get]] method of its stereotype is called, which delegates to the OrdinaryGet abstract operation again. Repeat until the property is found or an object with no archetype is encountered.

Let’s understand this algorithm by analyzing the process of accessing O2. Foo. First, call OrdinaryGet with o2 as O and “foo” as P. O.[[GetOwnProperty]](“foo”) returns undefined because O2 has no own property called “foo”. So we go to the branch of step 3. In 3.a, set parent to the prototype of O2, which is o1. Parent is not null and therefore will not be returned in 3.b. In 3.c, call parent’s [[Get]] method, pass “foo”, and return the result of the call.

Parent (o1) is an ordinary object, so its [[Get]] method calls OrdinaryGet again. This time O is o1 and P is “foo”. O1 has its own property called “foo”, so step 2 O.[[GetOwnProperty]](“foo”) returns the corresponding property descriptor and saves it in desc.

The property descriptor is a canonical type. The data property descriptor stores the Value of the property directly in the [[Value]] field. Accessor property descriptors store accessor functions in [[Get]] and/or [[Set]] fields. The property descriptor here associated with “foo” is a data property descriptor.

The data attribute descriptor stored in desc in step 2 is not undefined and therefore does not go to the branch of step 3. Then step 4, because the property descriptor is a data property descriptor, returns the Value 99 for its [[Value]] field. That’s the end of step 4.

ReceiverWhat is? Where did it come from?

The Receiver parameter is only used if step 8 of the algorithm is an accessor property. Receiver is used as the this value when the getter for the accessor property is called.

OrdinaryGet delivers the original Receiver consistently during (3.c) recursion without modification. Let’s see where this Receiver comes from!

By searching where the [[Get]] method is called, we find an abstract operation GetValue that operates on a reference. A reference is a canonical type that contains a base value, a referenced name, and a strict reference flag. For O2.foo, the base value is the object o2, the reference name is the string “foo”, and the strict reference flag is false (because strict mode is not enabled in the sample code).

Extended Learning: Why Cite and not record?

A reference is not a record, although it could be. A reference consists of three parts and can be represented by three named fields equivalently. References are not records that are left over from history.

Back to GetValue

Let’s look at the definition of GetValue:

GetValue (V) 1. Returnifabnormal (V); 2. If Type(V) is not referenced, return V; Set base to GetBase(V); 4. If IsUnresolvableReference(V) is true, raise ReferenceError; 5. If IsPropertyReference(V) is true, then A. If HasPrimitiveBase(V) is true, then I. Assertion: Base is not undefined or null; Set base to! ToObject (base); B. to return? Base. [[Get]] (GetReferencedName “[Get]” (V), GetThisValue (V)); 6. Otherwise a. Assert base as environment record; B. to return? Base. GetBindingValue (GetReferencedName (V), IsStrictReference (V)).

The reference in our example is O2. Foo, which is a property reference. So, step 5. 5.a will not be entered because base is not a primitive value (Number, String, Symbol, BigInt, Boolean, Undefined, or Null).

[[Get]] is then called in 5.b, and Receiver is passed in via GetThisValue(V). In this case, it is the base value for the reference.

GetThisValue(V) 1. assert: IsPropertyReference(V) is true; 2. If IsSuperReference(V) is true, a. Returns the value of thisValue component referencing V; 3. Return GetBase(V).

For O2.foo, it does not go to step 2 because it is not a superclass (super) reference (such as super.foo), but goes to step 3 and returns the underlying value o2 for the reference.

In summary, Receiver is the base value of the original reference, which remains constant during prototype chain-walking. If you are looking for an accessor property, the Receiver is used as this value in the call to get the accessor.

Note that the this value in the get function refers to the original object from which we want to get the property, not the object from which we found the property when the prototype walked.

Here’s an example:

const o1 = { x: 10, get foo() { return this.x; }}; const o2 = { x: 50 }; Object.setPrototypeOf(o2, o1); o2.foo; / / to 50Copy the code

Here we define an accessor property foo by defining the get function. The getfunction returns this.x.

Then, access O2.foo. Which value do you think this fetch function will return?

We find that when we call the get function, the this value is the object from which we originally tried to get the property, not the object from which we found the property. Specifically, this is o2, not o1. We can tell by whether o2.x or o1.x is returned: o2.x is returned.

We can predict the behavior of this code by reading the specification!

Why is it called when accessing a property[[Get]]?

Where does the specification say that an object’s internal method [[Get]] is called when accessing properties such as O2.foo? Yeah, it must be regulated somewhere. Don’t believe what other people tell you!

We find that the object internal method [[Get]] is called in the abstract operation GetValue, which is a reference. So where does GetValue get called?

MemberExpressionThe runtime semantics of

Canonical grammar rules define the grammar of a language. Runtime semantics define the “meaning” of syntactic constructs (how they are evaluated at run time).

If you are not familiar with context-free grammars, now is the time to click on the link.

We’ll delve into the relevant grammar rules later in this article, but keep it simple for now. For example, you can omit subscripts (Yield, Await, and so on) in the production.

The following production describes MemberExpression:

MemberExpression :
PrimaryExpression
MemberExpression [ Expression ]
MemberExpression . IdentifierName
MemberExpression TemplateLiteral
SuperProperty
MetaProperty
new MemberExpression Arguments
Copy the code

There are seven generation expressions of memberexpressions. That is, MemberExpression can be just a PrimaryExpression. In addition, MemberExpression can be constructed from a combination of another MemberExpression and Expression: MemberExpression [Expression], such as O2 [‘foo’]. Alternatively, it can be expressed as memberExpression. IdentifierName, as in O2.foo (which is the relevant production for our example).

The runtime semantics of MemberExpression. IdentifierName define the steps when evaluating it:

Runtime semantics: Evaluate MemberExpression: MemberExpression. IdentifierName 1. Let baseReference be the result of evaluating MemberExpression; 2. Set baseValue to? GetValue (baseReference); 3. If MemberExpression matches the strict mode code, set strict to true. Otherwise, make strict false; 4. Return? EvaluatePropertyAccessWithIdentifierKey (baseValue IdentifierName, strict).

This algorithm is delegated to the abstract EvaluatePropertyAccessWithIdentifierKey operation, so also want to look at its definition:

EvaluatePropertyAccessWithIdentifierKey( baseValue, identifierName, Strict) abstract baseValue EvaluatePropertyAccessWithIdentifierKey receiving value operation, resolution Node (Parse Node) identifierName and strict Boolean value as a parameter, Perform the following steps: 1. Assertion: identifierName is identifierName; 2. Let bv be? RequireObjectCoercible (baseValue); 3. Set propertyNameString to StringValue of identifierName. 4. Return a reference with the base value bV, the reference name propertyNameString, and the strict reference flag strict.

EvaluatePropertyAccessWithIdentifierKey, in other words, to build a reference, to provide baseValue as the foundation, to identifierName as attribute names, with strict as strict mode.

Eventually the reference is passed to GetValue. There are several places in the specification where GetValue is called, and the difference is how the reference is used in the end.

The translator the appended drawings

The second step of the above algorithm uses? GetValue(baseReference) returns hard completion ([[Type]] is “return”, [[Value]] is desc.[[Value]], and is unwrapped by runtime semantics.)

MemberExpression as a parameter

It is also possible to use property access as an argument in real code:

console.log(o2.foo);
Copy the code

At this point, the related behavior is defined by the runtime semantics of the ArgumentList production, which calls GetValue for arguments:

Runtime Semantics: ArgumentListEvaluation ArgumentList : AssignmentExpression 1. Let ref be the result of evaluating AssignmentExpression; 2. Make ARG? GetValue (ref); 3. Return the list of arGs with only one entry.

O2. Foo does not look like AssignmentExpression, but it is, which is why this production applies. To see why, look at the footnote: “Why is O2. foo AssignmentExpression?” .

AssignmentExpression in Step 1 is O2.foo. The result of evaluating O2. Foo is ref, the reference mentioned above. Step 2 calls GetValue on this reference. This way we know that the object’s internal method [[Get]] will be called and the prototype walkthrough will take place.

summary

This article explored how the specification defines a language feature, known as stereotype lookup, across different levels of abstraction, including the syntactic structure that triggers the feature and the algorithm that defines it.