Join the discussions on Hackernews and Reddit.

Getters and setters (also known as accessors) were introduced to JavaScript when ECMAScript 5 (2009) was released.

The thing is, there's a lot of confusion about their utility and why you would ever even want to use them.

I came across this reddit thread where the discussion was about if they were an anti-pattern.

Unfortunately, the general consensus of the thread was "yes". I think that's because the majority of your front-end programming on a daily basis doesn't call for the utility that getters and setters offer.

Though I disagree with getters and setters being an anti-pattern overall. They have a lot of utility in several different cases.

What are they for?

Getters and setters are another way for you to provide access to the properties of an object.

Trivial usage might look like this:

interface ITrackProps { name : string ; artist : string ; } class Track { private props : ITrackProps ; get name ( ) : string { return this . props . name ; } set name ( name : string ) { this . props . name = name ; } get artist ( ) : string { return this . props . artist ; } set artist ( artist : string ) { this . props . artist = artist ; } constructor ( props : ITrackProps ) { this . props = props ; } public play ( ) : void { console . log ( ` Playing ${ this . name } by ${ this . artist } ` ) ; } }

The question becomes: "why not just use regular class attributes?"

Well, in this case, we could.

interface ITrackProps { name : string ; artist : string ; } class Track { public name : string ; public artist : string ; constructor ( name : string , artist : string ; ) { this . name = name ; this . artist = artist ; } public play ( ) : void { console . log ( ` Playing ${ this . name } by ${ this . artist } ` ) ; } }

That's much simpler. And that's also a really simple use case. Let's look at scenarios that better describe why we might care about using getters and settters vs regular class attributes.

Preventing Anemic Domain models

Do you remember what an anemic domain model is? One of the earliest ways to sniff out an anemic domain model is if there are getters and setters for every single attribute of your domain entities (ie: set operations that don't make sense to the domain-specific language are exposed).

And if you don't explicitly use the get or set keywords, making everything public also has the same negative effect.

Consider this example:

class User { public id : UserId ; constuctor ( id : UserId ) { this . id = id ; } }

In Domain-Driven Design, to prevent an anemic domain model and push forward the creation of a domain-specific language it's really important for us to only expose operations that are valid to the domain.

That means understanding the domain that you're working in.

I'll put myself up for scrutiny. Let's take a look at the Vinyl class from White Label, an open-source vinyl-trading app built with TypeScript using Domain-Driven Design.

import { AggregateRoot } from "../../core/domain/AggregateRoot" ; import { UniqueEntityID } from "../../core/domain/UniqueEntityID" ; import { Result } from "../../core/Result" ; import { Artist } from "./artist" ; import { Genre } from "./genre" ; import { TraderId } from "../../trading/domain/traderId" ; import { Guard } from "../../core/Guard" ; import { VinylCreatedEvent } from "./events/vinylCreatedEvent" ; import { VinylId } from "./vinylId" ; interface VinylProps { traderId : TraderId ; title : string ; artist : Artist ; genres : Genre [ ] ; dateAdded ? : Date ; } export type VinylCollection = Vinyl [ ] ; export class Vinyl extends AggregateRoot < VinylProps > { public static MAX_NUMBER_GENRES_PER_VINYL = 3 ; get vinylId ( ) : VinylId { return VinylId . create ( this . id ) } get title ( ) : string { return this . props . title ; } get artist ( ) : Artist { return this . props . artist } get genres ( ) : Genre [ ] { return this . props . genres ; } get dateAdded ( ) : Date { return this . props . dateAdded ; } get traderId ( ) : TraderId { return this . props . traderId ; } public addGenre ( genre : Genre ) : void { const maxLengthExceeded = this . props . genres . length >= Vinyl . MAX_NUMBER_GENRES_PER_VINYL ; const alreadyAdded = this . props . genres . find ( ( g ) => g . id . equals ( genre . id ) ) ; if ( ! alreadyAdded && ! maxLengthExceeded ) { this . props . genres . push ( genre ) ; } } public removeGenre ( genre : Genre ) : void { this . props . genres = this . props . genres . filter ( ( g ) => ! g . id . equals ( genre . id ) ) ; } private constructor ( props : VinylProps , id ? : UniqueEntityID ) { super ( props , id ) ; } public static create ( props : VinylProps , id ? : UniqueEntityID ) : Result < Vinyl > { const propsResult = Guard . againstNullOrUndefinedBulk ( [ { argument : props . title , argumentName : 'title' } , { argument : props . artist , argumentName : 'artist' } , { argument : props . genres , argumentName : 'genres' } , { argument : props . traderId , argumentName : 'traderId' } ] ) ; if ( ! propsResult . succeeded ) { return Result . fail < Vinyl > ( propsResult . message ) } const vinyl = new Vinyl ( { ... props , dateAdded : props . dateAdded ? props . dateAdded : new Date ( ) , genres : Array . isArray ( props . genres ) ? props . genres : [ ] , } , id ) ; const isNewlyCreated = ! ! id === false ; if ( isNewlyCreated ) { vinyl . addDomainEvent ( new VinylCreatedEvent ( vinyl . vinylId ) ) } return Result . ok < Vinyl > ( vinyl ) ; } }

Acting as a facade, maintaining readonly values, enforcing model expressiveness, encapsulating collections, AND creating domain events are some very solid use cases for getters and setters in Domain-Driven Design.

Change detection in Vue.js

Vue.js, one of the newer front-end frameworks, prides itself with being very fast and reactive.

The reason why Vue.js does change detection so efficiently is because they use the Object.defineProperty() API to watch for changes to your View Models!

From the Vue.js docs on Reactivity,

When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty. The getter/setters are invisible to the user, but under the hood they enable Vue to perform dependency-tracking and change-notification when properties are accessed or modified. - Vue.js Docs: Reactivity

In conclusion, getters and setters do have a lot of utility for a lot of different problems. Those problems just don't occur a whole lot in modern front-end web development.