In this article, we go back to the ECMAScript5 specification to see how this is determined when a function is called.

preface

As stated in the JavaScript Deep Execution Context Stack, when JavaScript code executes a executable code, an execution context is created.

For each execution context, there are three important properties

  • Variable Object (VO)
  • Scope chain
  • this

Today I will focus on this, but it is difficult to tell.

Because we’re going to start with the ECMASciript5 specification.

Address of the ECMAScript 5.1 specification:

English version: ES5.github. IO /#x15.1

Chinese version: yanhaijing.com/es5/#115

Let’s get started with the specification!

Types

The first is Chapter 8 Types:

Types are further subclassified into ECMAScript language types and specification types.

An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.

A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.

Let’s translate it briefly:

ECMAScript types are divided into language types and specification types.

ECMAScript language types are something developers can manipulate directly using ECMAScript. Undefined, Null, Boolean, String, Number, and Object.

Canonical types are equivalent to meta-values, which are used to describe ECMAScript language structures and ECMAScript language types algorithmatically. Specification types include: Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.

Don’t understand? It doesn’t matter, just know that there are other types in the ECMAScript specification that only exist in the specification and are used to describe the underlying behavioral logic of the language.

Today we are going to focus on the Reference type. It is closely related to the direction of this.

Reference

What is Reference?

Let’s look at Chapter 8.7 The Reference Specification Type:

The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.

So the Reference type is used to explain operations such as delete, typeof, and assignment.

Copied from the words of Yu Xi da, that is:

Reference is a Specification Type, an abstract Type that exists only in the Specification. They exist to better describe the underlying behavioral logic of the language, but they do not exist in actual JS code.

Reference:

A Reference is a resolved name binding.

A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.

The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).

A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.

This paragraph describes the composition of Reference, which consists of three parts:

  • base value
  • referenced name
  • strict reference

But what are these?

We simply understand:

Base value is the object where the property is or EnvironmentRecord, The value can only be undefined, an Object, a Boolean, a String, a Number, or an environment record.

Referenced name is the name of an attribute.

Here’s an example:

var foo = 1;

// Reference is:
var fooReference = {
    base: EnvironmentRecord,
    name: 'foo'.strict: false
};Copy the code

Here’s another example:

var foo = {
    bar: function () {
        return this; }}; foo.bar();// foo

// Reference is:
var BarReference = {
    base: foo,
    propertyName: 'bar'.strict: false
};Copy the code

The specification also provides methods to get the components of Reference, such as GetBase and IsPropertyReference.

These two methods are very simple, take a look:

1.GetBase

GetBase(V). Returns the base value component of the reference V.

Return the base value of reference.

2.IsPropertyReference

IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.

Simple to understand: Return true if base value is an object.

GetValue

In addition, the chapter 8.7.1 specification follows with GetValue, a method used to get the corresponding value from the Reference type.

Simple simulation of GetValue use:

var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo'.strict: false
};

GetValue(fooReference) / / 1.Copy the code

GetValue returns the real value of the object property, but note:

Call GetValue, which returns a concrete value instead of a Reference

This is important. This is important. This is important.

How do I determine the value of this

So much has been said about Reference. Why talk about Reference? What is the connection between Reference and the topic of this? If you have the patience to read through the previous content, here is the high-energy phase:

See specification 11.2.3 Function Calls:

Here’s how to determine the value of this when a function is called.

Just look at steps 1, 6, and 7:

1.Let ref be the result of evaluating MemberExpression.

6.If Type(ref) is Reference, then

  a.If IsPropertyReference(ref) is true, then

      i.Let thisValue be GetBase(ref).

  b.Else, the base of ref is an Environment Record

      i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).Copy the code

7.Else, Type(ref) is not Reference.

  a. Let thisValue be undefined.Copy the code

Let’s describe:

1. Calculate the result of MemberExpression and assign the value to ref

2. Check whether ref is a Reference type

2.1If ref is Reference, and IsPropertyReference(ref) istrue, thenthisThe value of GetBase (ref)2.2If ref is Reference and base value is Environment Record, thenthisThe value of ImplicitThisValue (ref)2.3If ref is not Reference, thenthisThe value ofundefinedCopy the code

A concrete analysis

Let’s look at it step by step:

  1. The result of evaluating MemberExpression is assigned to ref

What is MemberExpression? Left-hand-side Expressions:

MemberExpression :

  • PrimaryExpression // See chapter 4 of the JavaScript Authority guide for the original expression.
  • FunctionExpression // Function definition expression
  • MemberExpression [Expression] // Attribute access Expression
  • Memberexpression. IdentifierName // Attribute access expression
  • New MemberExpression Arguments // Object creation expression

Here’s an example:

function foo() {
    console.log(this)
}

foo(); / / MemberExpression is foo

function foo() {
    return function() {
        console.log(this)
    }
}

foo()(); // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}

foo.bar(); / / MemberExpression foo barCopy the code

So a simple understanding of MemberExpression is actually the left part of ().

2. Check whether ref is a Reference type.

The key is to see how the specification handles the various Memberexpressions and whether the result returned is a Reference type.

Take one last example:

var value = 1;

var foo = {
  value: 2.bar: function () {
    return this.value; }}/ / sample 1
console.log(foo.bar());
/ / sample 2
console.log((foo.bar)());
/ / sample 3
console.log((foo.bar = foo.bar)());
/ / sample 4
console.log((false || foo.bar)());
/ / example 5
console.log((foo.bar, foo.bar)());Copy the code

foo.bar()

In example 1, the MemberExpression evaluates to foo.bar. Is foo.bar a Reference?

Look at the specification 11.2.1 Property Accessors, which shows the calculation process, but leave it at that last step:

Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

We learn that this expression returns a Reference type!

From the previous content, we know that this value is:

var Reference = {
  base: foo,
  name: 'bar'.strict: false
};Copy the code

Then follow the judgment process in 2.1:

2.1 If ref is Reference and IsPropertyReference(ref) is true, then the value of this is GetBase(ref).

This value is of Reference type, so what is the result of IsPropertyReference(ref)?

We’ve foiled the IsPropertyReference method, which returns true if the base value is an object.

Base value is foo, which is an object, so IsPropertyReference(ref) results in true.

At this point we can determine the value of this:

this= GetBase (ref),Copy the code

GetBase has also been foiled to get the base value, which in this case is foo, so this is foo, and the result of example 1 is 2!

Oops, I’m so tired trying to prove that this refers to foo! But knowing how it works, the rest is faster.

(foo.bar)()

Look at Example 2:

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

Foo. bar is encapsulated by (), see 11.1.6 The Grouping Operator

Go straight to the results section:

Return the result of evaluating Expression. This may be of type Reference.

NOTE This algorithm does not apply GetValue to the result of evaluating Expression.

In fact, () does not evaluate MemberExpression, so the result is the same as in Example 1.

(foo.bar = foo.bar)()

Looking at example 3, with the Assignment operator, see the specification 11.13.1 Simple Assignment (=):

Step 3 of the calculation:

3.Let rval be GetValue(rref).

Because GetValue is used, the value returned is not of Reference type,

According to the judgment logic mentioned earlier:

2.3 If ref is not Reference, this is undefined

This is undefined. In non-strict mode, when this is undefined, its value is implicitly converted to a global object.

(false || foo.bar)()

See Example 4, Logic and Algorithms, to see the spec 11.11 Binary Logical Operators:

Calculate the second step:

2.Let lval be GetValue(lref).

Because GetValue is used, the return type is not Reference; this is undefined

(foo.bar, foo.bar)()

See Example 5, Comma Operator, for the specification 11.14 Comma Operator (,)

Calculate the second step:

2.Call GetValue(lref).

Because GetValue is used, the return type is not Reference; this is undefined

Announced the result

So the result of the last example is:


var value = 1;

var foo = {
  value: 2.bar: function () {
    return this.value; }}/ / sample 1
console.log(foo.bar()); / / 2
/ / sample 2
console.log((foo.bar)()); / / 2
/ / sample 3
console.log((foo.bar = foo.bar)()); / / 1
/ / sample 4
console.log((false || foo.bar)()); / / 1
/ / example 5
console.log((foo.bar, foo.bar)()); / / 1Copy the code

Note: This is in non-strict mode, where this returns undefined, so example 3 will report an error.

supplement

Finally, I forgot the most common case:

function foo() {
    console.log(this)
}

foo();Copy the code

MemberExpression is foo, which parses the Identifier and returns a value of the Reference type when looking at the specification 10.3.1 Identifier Resolution:

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo'.strict: false
};Copy the code

Next, judge:

2.1 If ref is Reference and IsPropertyReference(ref) is true, then the value of this is GetBase(ref).

Because base Value is EnvironmentRecord, it is not an Object type. Remember the possible value of base value? The value can only be undefined, an Object, a Boolean, a String, a Number, or an environment record.

IsPropertyReference(ref) results in false and goes to the next judgment:

2.2 If ref is Reference and base value is Environment Record, then this value is ImplicitThisValue(ref).

The base value is the Environment Record, so it will call ImplicitThisValue(ref)

See specification 10.2.1.1.6, ImplicitThisValue method description: This function always returns undefined.

So finally the value of this is undefined.

Many say

Although it is easy to think of this as the object on which the function is called, if so, what about the following example?

var value = 1;

var foo = {
  value: 2.bar: function () {
    return this.value; }}console.log((false || foo.bar)()); / / 1Copy the code

Furthermore, how do you determine who is calling the function? At the beginning of writing this article, I was faced with these problems, and finally gave up explaining the idea of this pointing in multiple situations, but traced back to the ECMASciript specification to explain the idea of this pointing. Although it was difficult to write and read from this Angle, once I read it several times and understood the principle, Will definitely give you a whole new perspective on this. You can see that although foo() and (foo.bar = foo.bar)() both end up pointing to undefined, they are fundamentally different from each other from a specification perspective.

This article covers the this of execution context. If you don’t understand the content of this article, it still doesn’t affect the rest of the topic of execution context. So, can still feel at ease to read the next article.

Next article

JavaScript Deep Execution Context

In-depth series

JavaScript In-depth series directory address: github.com/mqyqingfeng… .

This in-depth JavaScript series is designed to help you understand the basics of JavaScript, including prototypes, scopes, execution contexts, variable objects, this, closures, passing by value, call, apply, bind, new, and inheritance.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.