skip to Main Content

Why when we have this

function Car() {
  const fuel = 50
  return {
    fuel,
  }
}

const car = Car()
console.log(car.fuel) // 50
car.fuel = 3000
console.log(car.fuel) // 3000

And if we add a getter, the fuel property cannot be muted:

function Car() {
  const fuel = 50
  return {
    get fuel() {
      return fuel
    },
  }
}

const car = Car()
car.fuel = 3000
console.log(car.fuel) // 50

But then, if I try it on an isolated object it doesn’t work the same way:

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // prints 'John'
person._firstName = 'Juan';
console.log(person.fname); //prints 'Juan'

Context: I was trying to figure out why setters exist, I couldn’t wrap around the idea of it usefulness. I get why setters have a place. So I found this code snippet explaining that one of the advantages was to keep properties safe from changes. Reference here.

2

Answers


  1. I’m going to answer your deeper question. But first here’s the answer to your simple question:

    the fuel property cannot be muted

    It’s because it’s a const.

    The reason the first example can be mutated is because it’s not a setter. It’s just a property. We can rewrite the first example differently to make it more clear what’s going on:

    function Car() {
      const fuel = 50;
    
      let car_properties = new Object();
    
      car_properties.fuel = fuel;
    
      return car_properties;
    }
    

    It’s now obvious that the fuel returned in the first example has nothing to do with the const fuel except that its value is initialised by it.

    In the final example it’s because you haven’t specified the internal variables as const.

    Why Setters Exist

    It’s a bit of a historical hole in javascript that needed to be patched.

    In the browser, there are several places where the DOM API expose what look like properties but behave in weird ways. First is .innerHTML.

    The .innerHTML property looks like a string but it is not. It is an HTML compiler. Basically, it is a function, not a string. But it’s API is that of a plain string (exactly how setters and getters behave). For example, the following will not work how you’d expect if it’s a string:

    let div = document.getElementById('mydiv');
    
    div.innerHTML = '<table>';
    div.innerHTML += '<tr><td>Hello</td><td>World</td><tr>';
    div.innerHTML += '<tr><td>This is</td><td>a test</td><tr>';
    div.innerHTML += '</table>';
    

    Instead of a table that has two rows with two columns each, the code above will create an empty table (the first innerHTML line) which means it inserts <table></table> in the document. It auto-closes the table because it assumes you as a human have merely forgotten to close it (quirks mode).

    Then it will insert the text "HelloWorldThis isa test" after the table. The <tr> and <td> are ignored because they’re invalid HTML outside of tables.

    The last innerHTML line will be ignored because </table> on its own is invalid HTML.

    So basically .innerHTML is not a string. It is a function that is an HTML compiler. When you give it a value it will compile your HTML and perform all the necessary validations and discard invalid parts of your string. When you read it it will parse the live DOM and give you back the string representation. This is basically a setter and getter.

    Here’s another example. The cookie API.

    You can read cookies by reading the value of document.cookie. It will return a list of all the cookies on the page (that javascript has access to) in human readable form separated by "; " (note the space after the ; – the value is supposed to be human readable, not simply machine readable).

    To set a cookie you write to document.cookie. But document.cookie is not a string. It is a function that manages cookies with an API that imitates a string (basically a getter/setter). You can try this:

    document.cookie = 'a=hello';
    document.cookie = 'b=world';
    
    console.log(document.cookie); // prints out 'a=hello; b=world'
    

    Historically we’ve had these weird things in javascript that is impossible to implement in javascript. The way browsers implement these things is by writing C/C++ code that directly manipulate the structure of javascript variables in the interpreter.

    To fix this gap in the language getters and setters were introduced so now you can implement your own .innerHTML or document.cookie API. DON’T DO IT – as you’ve seen in the examples above this is very confusing to programmers especially beginner/junior programmers. With your own code (or 3rd party code) it’s also confusing to senior developers because nobody expects variables to behave like something else.

    But it was a gap in the language’s capabilities that needed to be closed.

    Login or Signup to reply.
  2. When you’re doing

    function Car() {
      const fuel = 50
      return {
        fuel,
      }
    }
    
    const car = Car()
    

    Car() is just returning { fuel: 50 }, a plain object.

    So when you set car.fuel = 3000, it just changes to { fuel: 3000 }.

    Let’s take a look at the second case.

    function Car() {
      const fuel = 50
      return {
        get fuel() {
          return fuel
        },
      }
    }
    
    const car = Car()
    

    Let me rename fuel and fuel() for clarity:

    function Car() {
      const a = 50
      return {
        get b() {
          return a
        },
      }
    }
    
    const car = Car()
    

    Here, when you do car = Car(), you can understand it as car = { get b(): {return a} }. When you define a getter function like this, you cannot change it.

    So when you do car.b = 3000, JS is saying "Oh, you’re trying to change the value of b, but that is a getter function, you’re not allowed to change it." JS being JS, it doesn’t throw you an error though.

    const person = {
      _firstName: 'John',
      _lastName: 'Doe',
      get fname() {
        return this._firstName;
      }
    }
     
    console.log(person.fname); // prints 'John'
    person._firstName = 'Juan';
    console.log(person.fname); //prints 'Juan'
    

    This one is pretty self evident, person._firstName is just plain old any other property in an object, so you can change it however you want. But, similarly to the second case, if you try this:

    const person = {
      _firstName: 'John',
      _lastName: 'Doe',
      get fname() {
        return this._firstName;
      }
    }
     
    console.log(person.fname); // prints 'John'
    person.fname = 'Juan';
    console.log(person.fname); //prints 'John'

    It will not change fname.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search