Are `getter` and `setter` necessary in JavaScript?

Are getter and setter necessary in JavaScript?

Necessary is a bit of an undefined word. Any problem can be solved without a getter and setter (usually by changing the interface to methods instead of direct property access), just like any problem can be solved without a for loop (by substituting a while loop and manually maintained loop variables), but both are useful features to have in a programming language because when they are a match for a use case, then they are the cleaner and more convenient way to program. Thus, they are "useful" and "helpful".

A getter or setter can be very useful at times, but they only need to be used when their specific functionality is required - otherwise plain property access without a getter or setter can be just fine.

A getter has use when you want to run some code every time a property is requested. In your example, the getter always returns an uppercase version of the name regardless of what the case of the name is, while reserving the actual case for internal use.

A setter has use when you want to run some code every time a property is set. In your case, it prevents the setting of a falsey name. You can't implement either of those features without a getter/setter.

Direct property access is a perfectly fine way to do things when you don't need getter or setter special logic.

It's also possible that getters and setters may get and set some property that is not publicly available (so not available at all via a normal property access), either because it's stored somewhere differently (not as a property on this object) or stored privately or even that it's stored else such as in some hardware device. In these cases, the getter and setter simulate the value being in a property when it actually isn't. So, they simplify the interface while allowing the actual value to be stored or retrieved from anywhere.


Necessary? No.

Are there scenarios that would best be expressed as a getter/setter? Yes. Consider "Computed Properties":

//declare an object
rectangle = {
   x: 10,
   y: 20,
   get area() { return this.x * this.y }  //won't occupy storage
}

//usage
log('the area is', rectangle.area)        //=> 'the area is 200'.
                                          //   cleaner syntax (no parentheses)

Consider the needs of API designers - most likely they'll be hyper-sensitive about how users perceive their API. Every bit (or in this case, parentheses) counts towards cleaning up the interface.


Yes they are very necessary. Just not for every property.

Here are two reasons to have setters:

1. You need to validate the incoming value.

Let's say that you have a value that must be an integer between 0 and 100. Your setter can validate that the incoming value is of the correct type and within correct range:

class Range {
  set value(newVal) {
    let val = Number(newVal);
    
    if( newVal == null || typeof newVal === 'string' || !Number.isInteger(val) || val < 0 || val > 100 ) {
      const err = `'value' must be an integer from 0 to 100 and not ${newVal}`;
      console.error(err);
      //throw new TypeError(err);
    }
    
    // save newVal
  }
}

const x = new Range();
x.value = 200;
x.value = 10;
x.value = "10";
x.value = new Number(22);

2. You need to do something every time a value changes

class ValOutput {
  constructor(el) {
    this._el = el;
  }

  set value(newVal) {
    this._el.innerHTML = `The value is ${newVal}`;
  }
}

const output = document.getElementById('output');
const x = new ValOutput(output);
x.value = 100;
setTimeout(() => {
  x.value="after timeout";
}, 2000);
<div id="output"></div>

Here are two reasons to have a getter:

1. The value is computed

class Rect {
  constructor(l = 0,t = 0,r = 0,b = 0) {
    this.left = l;
    this.top = t;
    this.right = r;
    this.bottom = b;
  }
  
  get height() {
    return this.bottom - this.top;
  }

  get width() {
    return this.right - this.left;
  }
}

let a = new Rect(0, 10, 33, 55);
console.log(a.width, a.height);

 a = new Rect(35, 50, 200, 200);
console.log(a.width, a.height);

2. You are proxying a value from another object

class OtherThing {
  constructor(a) {
    this.a = a;
  }
}

class MyObj {
  constructor(oVal = 0) {
    this._otherThing = new OtherThing(oVal);
  }
  
  get a() {
    return this._otherThing.a;
  }
}

const x = new MyObj();
console.log(x.a);

const y = new MyObj('tacos');
console.log(y.a);

Hiding private variables

getters and setter are a great way to hide private data. Yes I know that the ES spec is introducing private variable but here is an example that works until that spec is standard.

const privateVars = new WeakMap();

class MyObj {
  constructor(inVal) {
    const pVars = {
      age: inVal,
      dog: ''
    }
    
    privateVars.set(this, pVars);
  }
  
  get dog() {
    return privateVars.get(this).dog;
  }
  set dog(newVal) {
    privateVars.get(this).dog = newVal;
  }
}

const x = new MyObj(10);
const y = new MyObj(20);


x.dog = "woof";
y.dog = "bark";

console.log(x.dog);
console.log(y.dog);

Do you need getter and setters? No. Are they necessary? Yes.


One of the main reason the getter and setter feature was implemented was to close a feature gap that required people to hack a js interpreter/browser to achieve one feature that browsers could do:

element.innerHTML = "<div> some HTML string </dif>";

Now, innerHTML may look like a property but it actually behaves more like a function. It's actually an HTML compiler.

You can see this by trying to do:

element.innerHTML += "<table>";
element.innerHTML += "<tr><td>test</td></tr>"; // does not work

This is because the first "call" to innerHTML compiles the html to:

<table></table>

The second line will fail because <tr> outside of a table is invalid HTML.

With getter and setter you can finally implement a feature like this in pure javascript - make property access trigger a function call.

Of course, innerHTML is just one example of getters and setters and a very bad example of an API at that. It's up to your creativity what you can actually do with getters and setters.

Now, for my personal opinion (and it's only my opinion so take it for what it's worth): I think innerHTML provides a good example for why getter and setters should not normally be used. The confusion over <table> is one good example of breaking user's expectation. If innerHTML was implemented as element.appendHTML() it's behavior would be less surprising. As a programmer I'd be very surprised if a property access does something I don't expect.

That said, I am glad that getters and setters exist to make language library features self-implementable without needing to resort to hacking in C/C++.

Tags:

Javascript