The astute reader will note that we haven’t eliminated the problem as we can still set
_userGear
directly:
car1._userGear = 'X'
. In this example, we’re using “poor man’s
access restriction”—prefixing properties we consider private with an underscore. This
is protection by convention only, allowing you to quickly spot code that’s accessing
properties that it shouldn’t be.
If you really need to enforce privacy, you can use an instance of
WeakMap
(see
) that’s protected by scope (if we don’t use
WeakMap
, our private properties will
never go out of scope, even if the instances they refer to do). Here’s how we can mod‐
ify our
Car
class to make the underlying current gear property truly private:
const
Car
=
(
function
() {
const
carProps
=
new
WeakMap
();
class
Car
{
constructor
(
make
,
model
) {
this
.
make
=
make
;
this
.
model
=
model
;
this
.
_userGears
=
[
'P'
,
'N'
,
'R'
,
'D'
];
carProps
.
set
(
this
, {
userGear
:
this
.
_userGears
[
0
] });
}
get
userGear
() {
return
carProps
.
get
(
this
).
userGear
; }
set
userGear
(
value
) {
if
(
this
.
_userGears
.
indexOf
(
value
)
<
0
)
throw
new
Error(
`Invalid gear:
${
value
}
`
);
carProps
.
get
(
this
).
userGear
=
value
;
}
shift
(
gear
) {
this
.
userGear
=
gear
; }
}
return
Car
;
})();
Here we’re using an immediately invoked function expression (see
ensconce our
WeakMap
in a closure that can’t be accessed by the outside world. That
WeakMap
can then safely store any properties that we don’t want accessed outside of
the class.
Another approach is to use symbols as property names; they also provide a measure
of protection from accidental use, but the symbol properties in a class can be
accessed, meaning even this approach can be circumvented.
Classes Are Functions
Prior to the
class
keyword introduced in ES6, to create a class you would create a
function that served as the class constructor. While the
class
syntax is much more
152 | Chapter 9: Objects and Object-Oriented Programming