You might also be able to find a solution fromAI solution orcommunity posts

BFE.dev solution for JavaScript Quiz
19. `this`

Let's break down the code and do the analysis.

a

const obj = {  a: 1,};console.log(obj.a); // 1

This is very simple, but have you wondered how it actually works?

According to ECMA spec, obj.a returns a Reference Record in following form.

{
  [[Base]]: baseValue,
  [[ReferencedName]]: propertyNameString,
  [[Strict]]: strict,
  [[ThisValue]]: empty
}

In our case, [[Base]] is the obj, and [[ReferencedName]] is "a".

Now in console.log(obj.a), a value is needed from the reference record, so the evaluation steps are taken to generate the value.

  1. If V is not a Reference Record, return V.
  2. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
  3. If IsPropertyReference(V) is true, then
    1. Let baseObj be ? ToObject(V.[[Base]]).
    2. If IsPrivateReference(V) is true, then
      1. Return ? PrivateGet(baseObj, V.[[ReferencedName]]).
    3. If V.[[ReferencedName]] is not a property key, then
      1. Set V.[[ReferencedName]] to ? ToPropertyKey(V.[[ReferencedName]]).
    4. Return ? baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)).
  4. Else,
    1. Let base be V.[[Base]].
    2. Assert: base is an Environment Record.
    3. Return ? base.GetBindingValue(V.[[ReferencedName]], V.[[Strict]])

It might looks intimidating but don't be. We can just focus on the highlighted line - Return ? baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)).

[[Get]] is one of the essential internal methods which does following:

[[Get]] (propertyKey, Receiver) → any Return the value of the property whose key is propertyKey from this object. If any ECMAScript code must be executed to retrieve the property value, Receiver is used as the this value when evaluating the code.

So for obj.a, it just returns its value 1.

b

const obj = {  a: 1,  b: function () {    console.log(this.a);  },};obj.b(); // 1;(obj.b)() // 1const b = obj.bb() // undefinedobj.b.apply({a: 2}) // 2

For obj.b() it is a Function Call, here is its evaluation steps.

  1. If ref is a Reference Record, then
    1. If IsPropertyReference(ref) is true, then
      1. Let thisValue be GetThisValue(ref).
    2. Else,
      1. Let refEnv be ref.[[Base]].
      2. Assert: refEnv is an Environment Record.
      3. Let thisValue be refEnv.WithBaseObject().
  2. Else,
    1. Let thisValue be undefined.
  3. Let argList be ? ArgumentListEvaluation of arguments.
  4. If func is not an Object, throw a TypeError exception.
  5. If IsCallable(func) is false, throw a TypeError exception.
  6. If tailPosition is true, perform PrepareForTailCall().
  7. Return ? Call(func, thisValue, argList).

Following the highlighted lines, since obj.b is a Reference Record, this will be derived from obj.b by GetThisValue, and then the function is triggered by Call(func, thisValue, argList).

GetThisValue returns [[Base]] because [[ThisValue]] in the record is empty, and [[Base]] is obj.

So when obj.b() is evaluated, obj is provided to the function as this and then gets run by [[Call]]. [[Call]] is internal method to run a function, it binds this for the local function body to the provided thisValue, that's why we can access this.a in the function. Thus obj.b() logs 1.

;(obj.b)(); // 1

Regarding (obj.b)(), it doesn't make any difference. According to spec, adding parenthesis doesn't alter how its expression inside is evaluated.

const b = obj.b;b(); // undefined

Now it is not obj.b() but b(). obj.b is used in const b = obj.b, which is Let and Const Declarations, and here is how it is evaluated.

  1. Let bindingId be the StringValue of BindingIdentifier.
  2. Let lhs be ! ResolveBinding(bindingId).
  3. If IsAnonymousFunctionDefinition(Initializer) is true, then
    1. Let value be ? NamedEvaluation of Initializer with argument bindingId.
  4. Else,
    1. Let rhs be ? Evaluation of Initializer.
    2. Let value be ? GetValue(rhs).
  5. Perform ! InitializeReferencedBinding(lhs, value).
  6. Return empty.

So obj.b is evaluated as a reference record firs, and GetValue returns its value to be bound to b in the scope. Different from obj.b(), for b() there is no this bound, so this falls back to the global scope and it is undefined.

obj.b.apply({ a: 2 }); // 2

Similar to obj.b(), obj.b.apply is a reference record. Its first part obj.b is evaluated, and since it is also reference record, it returns its value - the function with no this bound. Here apply is the prototype method of Function, when it is called, Call(func, thisArg) is applied.

So this inside points to the {a: 2} thus 2 is logged.

c

const obj = {  a: 1,  c() {    console.log(this.a);  },};obj.c(); // 1

Different from b, c is defined in a shorthand syntax - c() {...}. This called Method Definition. From MethodDefinitionEvaluation we can see that it is not that different from a normal property.

So obj.c() behaves the same as obj.b() which logs 1.

d

const obj = {  a: 1,  d: () => {    console.log(this.a);  },};obj.d(); // undefinedobj.d(); // undefinedobj.d.apply({ a: 2 }); // undefined

Different from b or c, d is now an Arrow Function .

An ArrowFunction does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.

It means arrow function doesn't provide this bounding, when this is used inside, it tries to find the this in the outer scope. In our case, it is global window because there is no function scope. So all log undefined.

e

const obj = {  a: 1,  e: (function () {    return () => {      console.log(this.a);    };  })(),};obj.e(); // undefinedobj.e(); // undefinedobj.e.call({ a: 2 }); // undefined

This is a bit tricky. First of all, the e has value of arrow function. As we've explained, arrow function doesn't have this, so its this is bound to the this of outer function. But when it is executed, it doesn't have this bound, and this goes to global window, so this inside arrow function goes to global window too.

f

const obj = {  a: 1,  f: function () {    return () => {      console.log(this.a);    };  },};obj.f()(); // 1obj.f()(); // 1obj.f().call({ a: 2 }); // 1

Similar to e, this inside the arrow function is resolved to the this of outer function, which is f. For f, this depends how it is called.

obj.f() - as we've discussed before, obj is the this, so is the this in arrow function. Since arrow function doesn't bind this, Function.prototype.call is unable to change its resolving. So all log 1.