Why does my javascript getter/setter require underscores?

Viewed 3168

Performing a [[Get]] or [[Put]] operation on a throws a RangeError that states Maximum call stack size exceeded pointing to the this.a in the getter and the = in the setter.

let someObject = {
  get a() {
    return this.a;
  },
  set a(val) {
    this.a = val;
  }
}

someObject.a;  // Error
someObject.a = 5;  // Error

If the properties inside the getter and setter are underscored they work however:

let someObject = {
  get a() {
    return this._a_;
  },
  set a(val) {
    this._a_ = val;
  }
}

I've tested this in both the node environment and the v10 engine in Chrome and the same thing happens.

Such confusion. Send help.

3 Answers

Because by doing return this.a you're accesing the a property, which triggers the getter function, which accesses the a property, which triggers the getter function, which accesses the a property, which triggers the getter function... Endless loop.

Same for setter.

You need to have a variable that has a different name then the getter/setter. The most common way is to prefix the variable with an _.

Otherwise if you don't do this you will have an endless loop of calls to the getter/setter.

class A {
    constructor() {
        this._a = '';
    }

    get a() {
        // If we don't call it like this
        // We will be calling the getter again
        // Which in return will call itself again
        return this._a
    }
}

Once an object has been defined with a setter for some property, that setter is always used to set the property value — even inside the setter itself! If you attempt to set the property with simple assignment inside the setter, JavaScript will call the setter again, and you get infinite recursion.

Using a leading special character like "_" for a second "secret name" is a common convention. However, that property is still externally visible (though it can be made non-enumerable). Another approach available in newer JavaScript environments is to use a Symbol instance instead of a property name. Symbol instances guarantee that your internal property identifier cannot ever collide with another property name from some other source in the application.

So:

let someObject = function() {
  var aKey = Symbol();
  return {
    get a() {
      return this[aKey];
    },
    set a(value) {
      this[aKey] = value;
    }
  };
}();

That code uses a closure to create a private variable for the actual property key for what externally looks like a property called "a". It won't be possible for any other code to accidentally "step on" the private actual property used by the setter/getter pair because that's what Symbol is for.

Related