28

I am currently experimenting with ECMA6 classes. My current class looks like the following

class Player {
  constructor(id) {
    this.id = id;
    this.cash = 350;
  }

  get cash() {
    return this.cash;
  }

  set cash(value) { // line 19
    this.cash = value; // line 20
  }
};

When I am now creating a new Object by calling let playerObject = new Player(1); I receive the following error

...\node_modules\mysql\lib\protocol\Parser.js:82
        throw err;
              ^
RangeError: Maximum call stack size exceeded
    at Player.cash (player.js:19:11)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
    at Player.cash (player.js:20:15)
Press enter to exit

What does this have to do with the mysql library? Why does the error is multiple times in the same line? I am only calling it once.

Jannis Lehmann
  • 1,428
  • 3
  • 17
  • 31

5 Answers5

32

Your "cash" setter calls the "cash" setter, which calls the "cash" setter, which calls the "cash" setter...

Accessing the property setter by its own name inside the setter creates an infinite recursive function call.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • 3
    Renaming the "internal" variable. I changed `this.cash` to `this.playerCash`. Thanks Y'all. – Jannis Lehmann Jul 17 '15 at 21:03
  • 1
    @Cludch Not a problem. You should get in the habit of not reusing names for things, or if you really want to use that name add something like "get" or "set" in front, i.e "getCash" or "setCash". – blasko Jul 17 '15 at 21:07
18

I know I'm late, but I think I can clarify one or two points here:

First, there is the question of privacy, which is a long term discussion in the JavaScript community.

class Player {
   constructor(id) {
      this.cash = 350; // this._cash, alternatively
   }

   get cash() {
      return this.cash;
   }

   set cash(value) {
      this.cash = value;
   }
};

let player1 = new Player();

In this case, this.cash is a public property, so you don't really need a getter and a setter method to handle it, because you can get it with player1.cash and set it with player1.cash = newCash; and it is throwing the error because the getter and the setter are being called recursively, as mentioned by others.

However, if you simply rename the property to this._cash, you have to understand that this IS NOT A PRIVATE PROPERTY. If you try to access player1._cash, you will have access to the property value in the same way you will have with player1.cash.

So, how do we get privacy poperly implemented?

There are 2 main ways of doing this with ES6/ES2015: Using the new primitive type Symbol or using WeakMaps. I'm not going into details about this two new features of the language, but I'll show how this would be implemented in this case.

Using Symbols:

const CASH = Symbol();

class Player {

   constructor () {
      this[CASH] = 350;
   }

   get cash(){
      return this[CASH];
   }

   set cash(cash) {
      this[CASH] = cash;
   }

}

Using WeakMaps

let map =  new WeakMap();

class Player {

   constructor () {
      map.set(this, {
         cash: 350
      });    
   }

   get cash(){
      return map.get(this).cash;
   }

   set cash(cash) {
      map.get(this).cash = cash;
   }

}

IMPORTANT

While the syntax for Symbols are better, it requires native support of the browser to actually work. You can write it with a transpiler but, under the hood, it will mock it to old ES5 standards. The native support for WeakMaps are better and, on the other hand, this feature just plays with the GC and with the enumerable option of the objects properties. So, in the end, it's your choice.

Matheus Dal'Pizzol
  • 5,735
  • 3
  • 19
  • 29
  • Really annoying. If I do this, what happens when I want to serialize/deserialize the object? I believe it would be serialized as `"{'_cash': 350}"` which isn't ideal for sending over the wire. You will have to write a custom `toString()` function to deal with this. – zfj3ub94rf576hc4eegm Jun 10 '20 at 07:10
5

cash represents the getter/setter, _cash is the 'private' property.

  set cash(value) { // line 19
      this._cash = value; // line 20
  }

Have a look at this page for a clear example.

htatche
  • 693
  • 3
  • 17
4

You are calling recursively your getter.

It follows a possible alternative:

class Player {
    constructor(id) {
        this.id = id;
        this._cash = 350;
    }

    get cash() {
        return this._cash;
    }

    set cash(value) {
        this._cash = value;
    }
};

Another one using Object.defineProperty:

class Player {
    constructor(id) {
        this.id = id;

        var _cash = 350;
        Object.defineProperty(this, 'cash', {
            get: function() {
                return _cash;
            }

            set: function(v) {
                _cash = v;
            }
        });
    }
};
skypjack
  • 49,335
  • 19
  • 95
  • 187
0

Get & Set ES6 classes brings a new syntax for getters and setters on object properties. Get and set allows us to run code on the reading or writing of a property. ES5 had getters and setters as well but was not widely used because of older IE browsers. ES5 getters and setters did not have as nice of a syntax that ES6 brings us. So lets create a get and set for our name property.

Source: JavaScript ES6 Class Syntax

Example:

// ES6 get and set
class Person {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name.toUpperCase();
    }

    set name(newName) {
        this._name = newName;   // validation could be checked here such as only allowing non numerical values
    }

    walk() {
        console.log(this._name + ' is walking.');
    }
}

let bob = new Person('Bob');
console.log(bob.name);  // Outputs 'BOB'
d.danailov
  • 9,594
  • 4
  • 51
  • 36