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
I’m going to answer your deeper question. But first here’s the answer to your simple question:
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:
It’s now obvious that the
fuel
returned in the first example has nothing to do with theconst 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: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
. Butdocument.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: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
ordocument.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.
When you’re doing
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.
Let me rename
fuel
andfuel()
for clarity:Here, when you do
car = Car()
, you can understand it ascar = { 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 ofb
, but that is a getter function, you’re not allowed to change it." JS being JS, it doesn’t throw you an error though.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:It will not change
fname
.