Over a million developers have joined DZone.

Why Getters and Setters Are a Bad Idea in JavaScript

Getters and setters are incredibly convenient, but they can also bring about hidden errors that may not be obvious at runtime. What are some solutions to these issues?

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

As you know, getters and setters are already a part of the JavaScript for sometime. They’re widely support in all major browsers even starting at IE8.

I don’t think that this concept is wrong in general, but I think it’s not very well suited for JavaScript. It might look like getters and setters are a time saver and simplification of your code, but actually they bring hidden errors which are not obvious from the first look.

How Do Getters and Setters Work?

First a small recap on what these things are:

Sometimes it is desirable to allow access to a property that returns a dynamically computed value, or you may want reflect the status of an internal variable without requiring the use of explicit method calls.

To illustrate how they work, let’s look at a person object which has two properties: firstName and lastName, and one computed value: fullName.

var obj = {
  firstName: "Maks",
  lastName: "Nemisj"
}

The computed value fullName would return a concatenation of both firstName and lastName.

Object.defineProperty(person, 'fullName', {
  get: function () {
    return this.firstName + ' ' + this.lastName;
  }
});

To get the computed value of fullName there is no more need for awful braces like person.fullName(), but a simple var fullName = person.fullName can be used.

The same applies to the setters, you could set a value by using the function:

Object.defineProperty(person, 'fullName', {
  set: function (value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
});

Usage is just as simple with getter: person.fullName = 'Boris Gorbachev'. This will call the function defined above and will split Boris Gorbachev into firstName and lastName.

Where is the Problem?

You maybe think: “Hey, I like setters and getters, they feel more natural, just like JSON.” You’re right, they do, but let’s step back for a moment and look how fullName worked before getters and setters.

For getting a value we would use something like getFullName() and for setting a value, person.setFullName('Maks Nemisj') would be used.

And what would happen if the name of the function is misspelled and person.getFullName() is written as person.getFulName()?

JavaScript would give an error:

person.getFulName();
       ^
TypeError: undefined is not a function

This error is triggered at the right place and at the right moment. Accessing non existing functions of an object will trigger an error – that’s good.

Now let’s see what happens when setter is used with the wrong name?

person.fulName = 'Boris Gorbachev';

Nothing. Objects are extensible and can have dynamically assigned keys and values, so no error will be thrown in runtime.

Such behavior means that errors might be visible somewhere in the user interface, or maybe, when some operation is performed on the wrong value, but not at the moment when the real typo occurred.

Tracing errors which should happen in the past but shown in the future of the code flow is "so fun".

Seal to the Rescue

This problem could be partially solved by seal API. Whenever an object is sealed, it can’t be mutated, which means that fulName will try to assign a new key to the person object and it will fail.

For some reason, when I was testing this in node.js v4.0, it didn’t worked the way I was expecting. So I'm not sure about this solution.

What is even more frustrating is that there is no solution for getters at all. As I already mentioned, objects are extensible and failsafe, which means accessing a non-existing key will not result in any error at all.

I wouldn’t bother writing this article if this situation would only apply to the object literals, but after the rise of ECMAScript 2015 (ES6) and the ability to define getters and setters within Classes, I’ve decided to blog about the possible pitfalls.

Classes to the Masses

I know that currently classes are not very welcome in some JavaScript communities. People are arguing about the need for them in a functional/prototype-based language like JavaScript. However, the fact is that classes are in ECMAScript 2015 (ES6) spec and are going to stay there for a while.

For me, Classes are the way to specify well defined APIs between the outside world (consumers) of the classes and the internals of the application. It is an abstraction which puts rules down in black and white, and assumes that these rules are not going to change any time soon.

Time to improve the person object and make a real class of it (as real as class can be in JavaScript).Person defines the interface for getting and setting fullName.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return this.firstName + ' ' + this.lastName;
  }

  setFullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

Classes define a strict interface description, but getters and setters make it less strict than it should be. We're already used to the swollen errors when typos occur in keys when working with object literals and with JSON. At least I was hoping that Classes would be more strict and provide better feedback to the developers in that sense.

Though this situation is not any different when defining getters and setters on a class. It will not stop others from making typos without any feedback.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }

  set fullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

Executing with a typo won’t give any error:

var person = new Person('Maks', 'Nemisj');

console.log(person.fulName);

The same non-strict, non-verbose, non-traceable behavior leading to possible error.

After I discovered this, my question was: is there anything to do in order to make classes more strict when using getters and setters? I found out: sure there is, but is this worth it? Adding an extra layer of complexity into code just to use fewer braces? It is also possible not to use getters and setters for API definition and that would solve the issue. Unless you’re a hardcore developer and willing to proceed, there is another solution, described below.

Proxy to the Rescue?

Besides setters and getters, ECMAScript 2015 (ES6) also comes with proxy objects. Proxies help you to define the delegator method which can be used to perform various actions before real access to the key is performed. Actually, it looks just like dynamic getters/setters.

Proxy objects can be used to trap any access to the instance of the Class and throw an error if a pre-defined getter or setter was not found in that Class.

In order to do this, two actions must be performed:

  • Create list of getters and setters based on the Person prototype.
  • Create Proxy object which will test against these lists.

Let’s implement it.

First, to find out what kind of getters and setters are available on the class Person, it’s
possible to use getOwnPropertyNames and getOwnPropertyDescriptor:

var names = Object.getOwnPropertyNames(Person.prototype);

var getters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.get;
});

var setters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.set;
});

After that, create a Proxy object, which will be tested against these lists:

var handler = {
  get(target, name) {
    if (getters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Getter "' + name + '" not found in "Person"');
  },

  set(target, name) {
    if (setters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Setter "' + name + '" not found in "Person"');
  }
};

person = new Proxy(person, handler);

Now, whenever you will try to access person.fulName, message Error: Getter "fulName" not found in "Person" will be shown.

I hope this article helped you to understand the whole picture about getters and setters, and the danger which they can bring into the code.

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:
javascript ,web dev ,functions

Published at DZone with permission of Maks Nemisj, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}