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 the internal method [[Get]] of O is called with the property key P and ECMAScript language value Receiver, the following steps are performed:

  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:

  1. Assertion:IsPropertyKey(P)fortrue;
  2. makedescfor? O.[[GetOwnProperty]](P);
  3. ifdescforundefined,

    A. makeparentfor? O.[[GetPrototypeOf]]();

    If b.parentfornullTo return toundefined;

    C. return? parent.[[Get]](P, Receiver);
  4. ifIsDataDescriptor(desc)fortrueTo return todesc.[[Value]];
  5. Assertion:IsAccessorDescriptor(desc)fortrue;
  6. makegetterfordesc.[[Get]];
  7. ifgetterforundefinedTo return toundefined;
  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 toGetValue

Let’s look at the definition of GetValue:

GetValue (V)

  1. ReturnIfAbrupt(V);
  2. ifType(V)Non-reference, returnV;
  3. makebaseforGetBase(V);
  4. ifIsUnresolvableReference(V)fortrueThrown,ReferenceErrorThe exception;
  5. ifIsPropertyReference(V)fortrue,

    If a.HasPrimitiveBase(V)fortrue,

    I. Assert: At this point,baseIt is by no meansundefinedornull;

    Set ii.basefor! ToObject(base);

    B. return? base.[[Get]](GetReferencedName(V), GetThisValue(V));
  6. Otherwise,

    A. assertionbaseRecord for environment;

    B. 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. Assertion:IsPropertyReference(V)fortrue;
  2. ifIsSuperReference(V)fortrue,

    A. Return a referenceVthethisValueComponent value;
  3. returnGetBase(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. makebaseReferenceFor the evaluationMemberExpressionThe results;
  2. makebaseValuefor? GetValue(baseReference);
  3. ifMemberExpressionMatching code is strict mode code, letstrictfortrue; Otherwise thestrictforfalse;
  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 operations EvaluatePropertyAccessWithIdentifierKey receive value baseValue, Parse Node (Parse Node) identifierName and strict Boolean value as a parameter, perform the following steps:

  1. Assertion:identifierNameforIdentifierName;
  2. makebvfor? RequireObjectCoercible(baseValue);
  3. makepropertyNameStringforidentifierNametheStringValue;
  4. Returns a reference whose base value isbv, reference namepropertyNameStringStrict reference is marked asstrict.

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) directly returns a sudden completion ([[Type]] is “return”, [[Value]] is desc.[[Value]], and is unwrapped by runtime semantics.)

MemberExpressionAs 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. makerefFor the evaluationAssignmentExpressionThe results;
  2. makeargfor? GetValue(ref);
  3. Return contains only one itemargIn the list.

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.