How to put a function behind an ordinary property

A long time ago, I worked with a software toolkit for document scanning that had obviously been designed by a hardware engineer. It behaved like a circuit board, where you could adjust one input and all kinds of other state data would change. It was pretty awkward.

Sometimes, however, that’s exactly what you need. Take the case of setting the width or height of a picture while preserving the aspect ratio. If you change the width, the height should change proportionally. You could solve this problem by making setWidth(newValue) adjust the height and setHeight(newValue) adjust the width, but then you must call functions instead of accessing a property, which is just a little more awkward. Also, a property conveniently participates in serialization and JSON.stringify() but a function does not.

Wouldn’t it be nice if a property could behave like a function?

It can! Just use Object.defineProperty instead of tacking the property onto your object in the usual manner.

Here’s an example for our picture-sizing scenario, with the added feature that preserving the aspect ratio is optional.

function PictureSize(initialWidth, initialHeight) {
  var self = this,
      currentWidth = initialWidth, 
      currentHeight = initialHeight;

  this.lockAspectRatio = true;
  function defineProperty(propName, getter, setter) {
    Object.defineProperty(self, propName, {
      configurable: false,
      enumerable: true,
      get: getter,
      set: setter
    function() { return currentWidth; },
    function(newValue) {
      if (self.lockAspectRatio) {
        currentHeight *= newValue / currentWidth;
      return currentWidth = newValue;
    function() { return currentHeight; },
    function(newValue) {
      if (self.lockAspectRatio) {
        currentWidth *= newValue / currentHeight;
      return currentHeight = newValue;

As you can see, Object.defineProperty let us put getter and setter functions behind the width and height properties. Now we can do this:

var sz = new PictureSize(200, 400);
// {"lockAspectRatio":true,"width":200,"height":400}

sz.width = 100;
// {"lockAspectRatio":true,"width":100,"height":200}

sz.height = 1000;
// {"lockAspectRatio":true,"width":500,"height":1000}

sz.lockAspectRatio = false;
sz.height = 300;
// {"lockAspectRatio":false,"width":500,"height":300}

Object.defineProperty gives you some choices that you don’t get to make when defining a property the normal way.

Here, we have set enumerable to true so our properties will show up in loops as well as JSON.stringify().

We have also set configurable to false so nobody will be able to delete our properties or change their semantics.

There’s more about this useful addition to your toolbox on the Mozilla Developer Network.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.