Search code examples
javascriptgetgetter-settergetter

How come assigning a property causes "Error: Maximum call stack size exceeded" error?


I'm trying to better understand JavaScript getters using Mozilla's documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

I modified the code snippet in the article's IDE to this:

const obj = {
  get log() {
    return this.log = 1;
  }
};

console.log(obj.log);

and it worked, yielding this:

> 1

But when I try to separate out the return line to this:

const obj = {
  get log() {
    this.log = 1;
    return this.log;
  }
};

console.log(obj.log);

it errors out with:

Error: Maximum call stack size exceeded

I can't figure out why these seemingly equivalent snippets of code would behave differently. Any advice for a JavaScript newbie?

Thank you for your time 🙏🏻

Update:
@CertainPerformance's answer cleared everything up for me.

I'm going to add the following example code to make it easier for me to understand in the future.

This is why the second version did not work:

const obj = {
  get log() {
    this.log = 100;
    return this.log; // will go back to line 2 `get log()`
  }
};

console.log(obj.log);

The first version works because it is calling the setter which returns the value assigned so no getter was recursively called.

If there is a reason to need the second version, then using a different property name will avoid the recursive calling.

const obj = {
  get log() {
    this._log = 100;
    return this._log;
  }
};

console.log(obj.log);
console.log(obj._log);

Solution

  • When log is a getter, but you have no associated setter, doing

    return this.log = 1;
    

    tries to access log as a setter (since it's a setter/getter property) - but no setter exists, so it'll either

    • fail silently in sloppy mode
    • throw an error in strict mode

    'use strict';
    const obj = {
      get log() {
        return this.log = 1;
      }
    };
    
    console.log(obj.log);

    In contrast, doing

    return this.log;
    

    invokes the getter on log too. But you're already in log's getter, resulting in an infinite loop.

    You probably want to use a different property name for the internal property and the external property - like _log, or with private fields, or something like that.