0

It seems to me that using getters and setters for an object inside a class has no point to it. As I understand it, get/set is useful because it prevents someone outside the class changing something that shouldn't be changed or changing it to something it shouldn't be. However it seems pointless for objects. For example, I have a person with an address, I want to prevent editing the address, you can only view it:

class Person{
    constructor(name, address){
        this._name = name;
        this._address = address;
    }

    get address(){
        return this._address;
    }
}

let bob = new Person("bob", {
    street: "123 Main Street",
    city: "Los Angelos",
    state: "California"
});

But then you can still edit it like this:

let address = bob.address;
address.state = "New York";

To prevent this, I would think that you have to return a copy of the object instead of the reference. However, as far as i know, there is no standard way to deep clone an object. So you either have to shallow clone it, which seems not ideal if you have lots of nested references, or just return the reference to the object, which can be edited.

Am I missing something here?

Lee Morgan
  • 550
  • 1
  • 6
  • 26
  • *there is no standard way to deep clone an object*? What about object destructuring? –  Jan 12 '21 at 12:06
  • Does this answer your question? [Why use getters and setters in JavaScript?](https://stackoverflow.com/questions/42342623/why-use-getters-and-setters-in-javascript) – Sudhir Ojha Jan 12 '21 at 12:11
  • As far as I know you have to type out every property of the object to destructure, making it not useful as a general use case. Also, what if one of the properties is another object, I don't think that object gets a deep clone. I am admittedly not real familiar with destructuring. If there is a way to deep clone a generic object with destructuring, then I would love to know. – Lee Morgan Jan 12 '21 at 12:15
  • You can just do `this._address = Object.freeze(address);` inside the constructor. This will prevent it from changing. – Reyno Jan 12 '21 at 12:15
  • @SudhirOjha Not really, I understand the general idea for using getters and setters. To me, the best use for get/set is to protect the data from changes that shouldn't happen from outside of the classs. What I am asking if specifically if this is even possible with JS objects that are inside of classes. – Lee Morgan Jan 12 '21 at 12:23
  • @Reyno The problem that I see with this is sometimes I might want to change that object in the future, but freezing can never be undone. I simply want to prevent the object from being changed without going through the setter. Also, freeze is shallow, so you can still edit things nested further down. – Lee Morgan Jan 12 '21 at 12:29
  • @LeeMorgan That goes against what you just asked in the question, you wanted to prevent to `address` from changing. Which is what freeze does. You could easily write a recursive function to [deepFreeze](https://www.30secondsofcode.org/blog/s/javascript-deep-freeze-object) the object to prevent changes in child objects. If you want to change the object later you need to add a setter or another method in the class that will copy the object, [spreading](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works really well for this. – Reyno Jan 12 '21 at 12:37
  • @reyno It doesn't go against what I asked. I said that I want to prevent something outside of the class changing the object. It should have to go through the setter in order to change anything on that object. Object.freeze prevents any changing at all. – Lee Morgan Jan 12 '21 at 12:46

1 Answers1

2

Consider this class.

class Test {
    constructor(val) {
        this._foo = val;
    }

    set foo(val) {
        throw new Error("It's not possible to change the foo property!")
    }

    set boo(val) {
        console.log("setting _boo")
        this._boo = val;
    }

    get foo() {
        console.log("getting _foo");
        return this._foo;
    }
}

try {
    let test = new Test('foooooo');
    test.boo = "booooo";
    console.log(`foo: ${test.foo}`);
    test.foo = "bar";
} catch (e) {
    console.log(e);
}

With "Setter" it's possible to control initializing properties. You can see that it's possible to change the value of "boo" property but any attempt to change the value of the "foo" will throw an exception.

With "Getter" it's possible to control retrieving the value of properties. You can see that it's possible to retrieve the value of "foo" property but not "boo" and its value is private.

PS: Here are some examples to better understand JS behavior with objects and arrays:

//This works fine:

//Object

const obj = {};

obj.foo = 'bar';

console.log(obj); // {foo : 'bar'}

obj.foo = 'bar2';

console.log(obj); // {foo : 'bar2'}  

//---------------------------------------
//Array:

const arr = [];

arr.push('foo');

console.log(arr); // ['foo']

arr.unshift("foo2");

console.log(arr); // ['foo2', 'foo']

arr.pop();

console.log(arr); // ['foo2']

//===========================================
//but these won't work:

const obj = {};
obj = {foo: 'bar'}; // error - re-assigning

const arr = ['foo'];
const arr = ['bar']; // error - re-declaring

const foo = 'bar'; 
foo = 'bar2';       // error - can not re-assign
var foo = 'bar3';   // error - already declared
function foo() {};  // error - already declared

New Example:

class A {
    constructor(val) {
        this._foo = val;
    }

    set foo(val) {
        throw new Error("It's not possible to change the foo property!")
    }

    get foo() {
        return this._foo;
    }
}

class B {
    constructor(val) {
        this._obj = new A(val);
    }

    get Obj() {
        return this._obj;
    }
}

let b = new B('Test');
b.Obj.foo = 'new value';
console.log(b.Obj.foo);

In this manner, it's not possible to change the values ​​of the internal object.

  • What if this._foo is an object. Can't I just do something like `let foo = this._foo; foo.name = "bar";`? I am specifically talking about objects. So in this case, because an object is passed by reference, I can side-step any setter for foo.name. – Lee Morgan Jan 12 '21 at 12:50
  • Yes, it's possible and it's because of JavaScript nature. Note that even with constants JS behaves the same. const obj = {"name": "Lee", lName: "Morgan"}; if you try to change the obj itself, JS will throw an error that you can't change the constant value, but you can change obj fields. The same in this situation if you try to change the "foo" there will be an error< but changing fields of the object is possible. PS: JavaScript treats the Array in the same way; – Hamed Motallebi Jan 13 '21 at 04:12
  • Thanks for the explanation. So it seems to me that the nature of JS just means that I just have to accept that I can't really protect data/objects the same way as many other OOP languages. So maybe there isn't much point to getters/setters or const when dealing with an object or array? – Lee Morgan Jan 13 '21 at 07:41
  • 1
    One of the ways is to for every field of every class in your code (even for fields of class properties) you have to define getters/setters to specify field identifiers : (public, private, readable, writable, read-only, ...). I added an example at the end of the answer to clarify this. – Hamed Motallebi Jan 18 '21 at 04:42