Accessors allow running logic when accessing variables. In situations where complex logic is involved, accessors can be used to simplify code. For illustration, let's consider the logic required to layout a UI in mobile web page on an iOS device.

Normally, web pages run in Safari which has an address bar. In this situation, window.innerWidth and window.innerHeight provide the dimensions of the usable space in the web page. However, it is possible to install a bookmark to the page on the home screen. When launching Safari using this shortcut, the web page behaves like an app. The address bar is hidden and the entire screen is usable space. In this mode, window.outerWidth and window.outerHeight provide the dimensions of usable space in the web page. By using accessors, we can simply the logic when doing calculations, such that the same calculation can be performed except the accessor determines whether to use inner or outer attributes.

First we need to detect if the mode of the browser. This is accomplished by inspecting the navigator.standalone attribute. For good measure we'll also check if Safari is being used by inspecting navigator.userAgent. The following test returns true if the web page has been launched from the home screen:

/Safari/.test(navigator.userAgent) && navigator.standalone

Without using accessors, we'd need to use complex logic to perform calculations. We could define functions to simplify the expressions, however, if there is a lot of code already using inner and outer that needs to be modified, a lot of code will need to be changed. If the logic is complex enough, it is easy to make mistakes replacing code with function calls. For instance,  this code from a ReactJS application detects if the device has been rotated:

handleResize() {
  if (window.innerWidth != this.state.DisplayWidth && 
      window.innerHeight != this.state.DisplayHeight) {
    this.setState({
      DisplayHeight: window.innerHeight,
      DisplayWidth: window.innerWidth
    });
  }
}

Let's define a function to modify this logic and use it:

launchedFromHomeScreen() {
  return /Safari/.test(navigator.userAgent) && navigator.standalone
}

handleResize() {
  if (this.launchedFromHomeScreen() {
    if (window.outerWidth != this.state.DisplayWidth && 
        window.outerHeight != this.state.DisplayHeight) {
      this.setState({
        DisplayHeight: window.outerHeight,
        DisplayWidth: window.outerWidth
      });
    }
  } else {
    if (window.innerWidth != this.state.DisplayWidth && 
        window.innerHeight != this.state.DisplayHeight) {
      this.setState({
        DisplayHeight: window.innerHeight,
        DisplayWidth: window.innerWidth
      });
    }
  }
}

As you can see, the amount of code has doubled. Also, any logic changes need to be made twice.

Accessors to the Rescue

Using defineProperty on the dim object creates accessors...

const dim = {};

Object.defineProperty(dim, "launchedFromHomeScreen", {
  get() {
    return /Safari/.test(navigator.userAgent) && navigator.standalone;
  },
});

Object.defineProperty(dim, "width", {
  get() {
    return dim.launchedFromHomeScreen
      ? window.outerWidth
      : window.innerWidth;
  },
});

Object.defineProperty(dim, "height", {
  get() {
    return dim.launchedFromHomeScreen
      ? window.outerHeight
      : window.innerHeight;
  },
});

Notice how just changing window.innerHeight to dim.height is a clean solution that maintains the original logic.

handleResize() {
  if (dim.width != this.state.DisplayWidth && 
      dim.height != this.state.DisplayHeight) {
    this.setState({
      DisplayHeight: dim.height,
      DisplayWidth: dim.width
    });
  }
}