DEV Community

ValPetal Tech Labs
ValPetal Tech Labs

Posted on

Javascript Question of the Day #15 [Talk::Overflow]

This post explains a quiz originally shared as a LinkedIn poll.


πŸ”Ή The Question

const api = {
  baseURL: "https://api.example.com",

  fetchUser: (id) => {
    return `${this.baseURL}/users/${id}`;
  },

  fetchPost(id) {
    return `${this.baseURL}/posts/${id}`;
  }
};

console.log(api.fetchUser(1));
console.log(api.fetchPost(2));
Enter fullscreen mode Exit fullscreen mode

Hint: Arrow functions don't create their own this β€” they inherit it from the enclosing lexical scope. What is this at the top level of a script?


πŸ”Ή Solution

Correct Answer: B) undefined/users/1 / https://api.example.com/posts/2

🧠 How this works

The core mechanism here is lexical this binding in arrow functions versus dynamic this binding in regular methods.

When you define a method using the arrow function syntax (fetchUser: (id) => { ... }), the arrow function does not get its own this. Instead, it captures this from the enclosing lexical scope β€” which in this case is the top-level script scope.

At the top level of a non-module script:

  • In a browser: this is the window object
  • In Node.js REPL: this is the global object

In both cases, this.baseURL is undefined because there is no baseURL property on the global object.

When you define a method using shorthand syntax (fetchPost(id) { ... }), it's a regular function. When called as api.fetchPost(2), this is dynamically bound to api β€” the object before the dot. So this.baseURL correctly resolves to "https://api.example.com".

Key insight: The object literal { ... } is not a scope. Arrow functions defined inside an object literal do not capture this as the object β€” they capture this from whatever scope the object literal is being evaluated in.

πŸ” Line-by-line explanation

  1. const api = { ... } β€” An object literal is created. This is an expression, not a scope boundary. The this value inside is inherited from the surrounding context.

  2. baseURL: "https://api.example.com" β€” A simple string property on the object.

  3. fetchUser: (id) => { ... } β€” An arrow function is assigned to the fetchUser property. At this point, the arrow function permanently captures this from the enclosing scope (the top-level script). this here is the global object (or undefined in strict mode / ES modules).

  4. fetchPost(id) { ... } β€” A regular method defined using shorthand syntax. It will receive this dynamically based on how it's called.

  5. api.fetchUser(1) β€” The arrow function runs. this is the global object (captured at definition time). this.baseURL is undefined. The template literal produces "undefined/users/1".

  6. api.fetchPost(2) β€” The regular method runs. this is api (because api. is before fetchPost). this.baseURL is "https://api.example.com". The template literal produces "https://api.example.com/posts/2".

The misleading part: It looks like both methods are defined "inside" the api object, so you'd expect both to have this === api. But object literals don't create a this context β€” only functions, classes, and the global scope do.


πŸ”Ή Key Takeaways

  1. Arrow functions capture this lexically β€” from the enclosing function or script scope, not from the object they're defined in. Object literals are not scopes.

  2. Use shorthand method syntax for object methods (method() { }) when you need this to reference the object. Arrow functions are the wrong tool for this job.

  3. Arrow functions shine in callbacks β€” inside a regular method, arrow functions correctly inherit this from that method, making them ideal for .map(), .filter(), setTimeout, and event handlers inside methods.

  4. This bug is silent β€” undefined coerces to a string without throwing, so broken arrow-function methods often produce subtly wrong output rather than crashing, making them hard to detect without tests.

  5. ESLint can catch this β€” The no-invalid-this rule and the class-methods-use-this rule can help flag arrow functions that reference this in unexpected scopes.


Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/

Top comments (1)

Collapse
 
mambuzrrr profile image
Rico

The result is undefined/users/1 and https://api.example.com/posts/2.

fetchUser is an arrow function, so it doesn’t have its own this. It inherits this from the outer (global) scope, where baseURL doesn’t exist, so it becomes undefined.

fetchPost is a regular method, so when it’s called as api.fetchPost(2), this correctly points to the api object and the URL is built properly.

Greetings :)