4

I'm trying to protect a property of an object from being overwritten by the console. e.g. I have a person object, with a list of allergies as a property. The list of allergies should be able to be modified, however it should always be a list and an error should be thrown if a user tries writing 'person.allergies = "nonsense" '

I've tried looking into Object.freeze() and Object.seal() but cannot get these to work for this purpose, as I don't believe there is a way to unfreeze an object.

class Person {
    constructor(name){
        this.name = name
        this.allergies = []
    }

    addAllergy(allergy){
        this.allergies.push(allergy)
        return allergy
    }
}

ben = new Person('Ben')     //  Creating a new Object
ben.addAllergy('Dairy')     //  Giving the object a new allergy
ben.allergies               //  Should output ['Dairy']
ben.allergies = 'Soy'       //  Should make no changes to ben object.
Michael Storey
  • 139
  • 1
  • 3
  • 6
  • It doesn't matter whether the value is written "by the console" or not, you cannot distinguish this. Either it's writable or it is not. What exactly are you trying to achieve, what problem do you want to solve? – Bergi Jan 05 '19 at 19:21
  • `Object.freeze`ing `ben` (or any other `Person` instance) doesn't make the `ben.allergies` list non-modifiable. Sounds like this is exactly what you want. – Bergi Jan 05 '19 at 19:23

2 Answers2

3

You could make allergies a non writable property with Object.defineProperty:

class Person {
    constructor(name){
        this.name = name
        Object.defineProperty(this, 'allergies', {
            value: [],
            writable: false
          });
    }

    addAllergy(allergy){
        this.allergies.push(allergy)
        return allergy
    }
}

ben = new Person('Ben')     //  Creating a new Object
ben.addAllergy('Dairy')     //  Giving the object a new allergy
console.log(ben.allergies)  //  Should output ['Dairy']
ben.allergies = 'Soy'       //  Should make no changes to ben object.
ben.addAllergy('Peanutes')
console.log(ben.allergies)  // still array

writable defaults to false so you don't need to explicitly set it, but I think it makes the intention clearer. configurable also defaults to false which means you can't redefine the property with another call to Object.defineProperty().

Mark
  • 90,562
  • 7
  • 108
  • 148
  • 1
    You beat me to it with 10 seconds :) – trincot Jan 05 '19 at 19:14
  • the best of it is that you cannot redefine the property anymore, as long as it was defined. You could break any protection logic from the original post with `Object.defineProperty(ben, 'allergies', { get() { return 'abc' } })` – smnbbrv Jan 05 '19 at 19:17
  • 1
    Thanks for your solution. - Using the Object.defineProperty to set the writable as false worked perfectly! Much appreciated :) – Michael Storey Jan 06 '19 at 20:18
1

Use private properties:

class Person {
    #allergies;

    constructor(name){
        this.name = name
        this.#allergies = []
    }

    addAllergy(allergy){
        this.#allergies.push(allergy)
        return allergy
    }

    get allergies() {
      // you might want to deep clone it here
      // to fully protect from changes the array behind
      return this.#allergies; 
    }

    set allergies(value) {
      throw new Error('haha, I got you!');
    }
}

Private fields are being implemented in the ECMA standard. You can start using them today with babel 7 and stage 3 preset

Source

smnbbrv
  • 23,502
  • 9
  • 78
  • 109