Getters \ setters for dummies

Viewed 128809
14 Answers

In addition to @millimoose's answer, setters can also be used to update other values.

function Name(first, last) {
    this.first = first;
    this.last = last;
}

Name.prototype = {
    get fullName() {
        return this.first + " " + this.last;
    },

    set fullName(name) {
        var names = name.split(" ");
        this.first = names[0];
        this.last = names[1];
    }
};

Now, you can set fullName, and first and last will be updated and vice versa.

n = new Name('Claude', 'Monet')
n.first # "Claude"
n.last # "Monet"
n.fullName # "Claude Monet"
n.fullName = "Gustav Klimt"
n.first # "Gustav"
n.last # "Klimt"

You'd use them for instance to implement computed properties.

For example:

function Circle(radius) {
    this.radius = radius;
}

Object.defineProperty(Circle.prototype, 'circumference', {
    get: function() { return 2*Math.PI*this.radius; }
});

Object.defineProperty(Circle.prototype, 'area', {
    get: function() { return Math.PI*this.radius*this.radius; }
});

c = new Circle(10);
console.log(c.area); // Should output 314.159
console.log(c.circumference); // Should output 62.832

(CodePen)

Getters and setters really only make sense when you have private properties of classes. Since Javascript doesn't really have private class properties as you would normally think of from Object Oriented Languages, it can be hard to understand. Here is one example of a private counter object. The nice thing about this object is that the internal variable "count" cannot be accessed from outside the object.

var counter = function() {
    var count = 0;

    this.inc = function() {
        count++;
    };

    this.getCount = function() {
        return count;
    };
};

var i = new Counter();
i.inc();
i.inc();
// writes "2" to the document
document.write( i.getCount());

If you are still confused, take a look at Crockford's article on Private Members in Javascript.

I think the first article you link to states it pretty clearly:

The obvious advantage to writing JavaScript in this manner is that you can use it obscure values that you don't want the user to directly access.

The goal here is to encapsulate and abstract away the fields by only allowing access to them thru a get() or set() method. This way, you can store the field/data internally in whichever way you want, but outside components are only away of your published interface. This allows you to make internal changes without changing external interfaces, to do some validation or error-checking within the set() method, etc.

Although often we are used to seeing objects with public properties without any access control, JavaScript allows us to accurately describe properties. In fact, we can use descriptors in order to control how a property can be accessed and which logic we can apply to it. Consider the following example:

var employee = {
    first: "Boris",
    last: "Sergeev",
    get fullName() {
        return this.first + " " + this.last;
    },
    set fullName(value) {
        var parts = value.toString().split(" ");
        this.first = parts[0] || "";
        this.last = parts[1] || "";
    },
    email: "boris.sergeev@example.com"
};

The final result:

console.log(employee.fullName); //Boris Sergeev
employee.fullName = "Alex Makarenko";

console.log(employee.first);//Alex
console.log(employee.last);//Makarenko
console.log(employee.fullName);//Alex Makarenko

If you're referring to the concept of accessors, then the simple goal is to hide the underlying storage from arbitrary manipulation. The most extreme mechanism for this is

function Foo(someValue) {
    this.getValue = function() { return someValue; }
    return this;
}

var myFoo = new Foo(5);
/* We can read someValue through getValue(), but there is no mechanism
 * to modify it -- hurrah, we have achieved encapsulation!
 */
myFoo.getValue();

If you're referring to the actual JS getter/setter feature, eg. defineGetter/defineSetter, or { get Foo() { /* code */ } }, then it's worth noting that in most modern engines subsequent usage of those properties will be much much slower than it would otherwise be. eg. compare performance of

var a = { getValue: function(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.getValue();

vs.

var a = { get value(){ return 5; }; }
for (var i = 0; i < 100000; i++)
    a.value;

You can also use __defineGetter__:

function Vector2(x,y) {
    this.x = x;
    this.y = y;
}

Vector2.prototype.__defineGetter__("magnitude", function () {
   return Math.sqrt(this.x*this.x+this.y*this.y);
});

console.log(new Vector2(1,1).magnitude)

Or, if you prefer:

function Vector2(x,y) {
    this.x = x;
    this.y = y;
    this.__defineGetter__("magnitude", function () {
       return Math.sqrt(this.x*this.x+this.y*this.y);
    });
}



console.log(new Vector2(1,1).magnitude)

But this function has been flagged as "legacy" recently, being dropped in favor of Object.defineProperty().

There's no example here with ES6 class (which is not even 'new' now, it's the norm):

class Student {

    contructor(firstName, lastName){
        this.firstName = firstName
        this.lastName = lastName
        this.secretId = Math.random()    
    }
    
    get fullName() {
        return `${this.firstName} ${this.lastName}`; // this is backtick in js, u can check it out here: https://stackoverflow.com/a/27678299/12056841
    }

    set firstName(newFirstName) {
        // validate that newFirstName is a string (and maybe limit length)
        this.firstName = newFirstName
    }

    get studentId() { return this.secretId }
}

and no setter for secretId because we don't want anyone to change it.

** if secretId shouldn't be changed at all, a nice approach is to declare it as 'private' to this class by adding a '#' to it (e.g: this.#secretId = Math.random(), and return this.#secretId

Update: about backing fields You might need to rename your field - or your setter function but it makes more sense to me to change your field name. One option is like I mentioned above (using a # for declaring the field as 'private'). Another way is to just rename it (_firstName, firstName_...)

Related