What is the point of using getter and setter methods. What exactly do they do and what are the benefits. I tried to make sense of them but I just don't get it and what is with the underscore that is used with the above methods. Can someone explain it to me.
Thanks.
Accessor properties provide a way to call a function when a property is read (get) or written to (set), even though the code doing that is using property access or assignment syntax. Here's an example of a getter:
const example = {
get random() {
return Math.random();
}
};
const r = example.random;
console.log(r);
Notice that there is no () on random in that example, it looks like a straight data property access. But in fact it's a hidden function call.
Setters work the same way, but when you assign to the property rather than when you read it:
const example = {
set something(value) {
console.log(`setter called with ${value}`);
}
};
example.something = 42;
Because they're function calls, no value is stored unless the function(s) store it.
There are a few reasons you might want to have accessor properties rather than standard data properties. For instance:
Validation: Ensuring that the value being set is a valid value for the property.
Side-effects: Taking action when the property is set (or read). For instance, you might log the fact the property was set/gotten, or in an MVVM/MVC setting, you might use the fact a property was changed to re-render a view.
Here's an example of a property you can only set to a number:
class Example {
#storedValue = 0;
set value(value) {
const type = typeof value;
if (type !== "number") {
throw new Error(`Value must be of type number, not ${type}`);
}
this.#storedValue = value;
}
get value() {
return this.#storedValue;
}
}
const e = new Example();
e.value = 42;
console.log(e.value); // 42
e.value = "something"; // Throws error
That uses the fairly-new private fields feature to store the value where it can't be accessed from outside the class, so you have to go through the accessor property to get to it. That lets the class validate the values you assign.
Depends on how you use them.
If the getter/setter just reads/writes the value of a public field (fields are public by default), they are probably not really needed. But when you are using private fields (ie protecting fields from direct manipulation from outside the class), you have to use getters and setters to change their value.
Furthermore, getters and setters may have some more logic (sanitation, validity checks, additional updates in the class, ...). You can even have properties, which are only calculated at runtime and are not backed up by a field at all.
Of course you can achieve the same with functions. But it seems more natural to access something that is a property of an object by "field syntax" than via a function.
See the following example: #firstname and #lastname are private fields of the person class, ie you can't access p.#firstname. Thus, we need the FirstName and LastName properties to manipulate them. The getter just returns the value of the respective field, but the setter also checks, if the name is valid (although that specitic check may not make much sense, it's just an example). Furthermore, we have an additional getter for FullName which returns a combination of first- and lastname.
class Person {
#firstname;
#lastname;
get FirstName() {
return this.#firstname;
}
set FirstName(fn) {
if (!fn || fn.length < 3)
throw "invalid first name"
this.#firstname = fn;
}
get LastName() {
return this.#lastname;
}
set LastName(ln) {
if (!ln || ln.length < 3)
throw "invalid last name"
this.#lastname = ln;
}
get FullName() {
return `${this.#firstname} ${this.#lastname}`;
}
constructor(fn, ln) {
this.#firstname = fn;
this.#lastname = ln;
}
}
let p = new Person("John", "Doe");
//if you uncomment this, you will get a syntax error
//p.#firstname = "Jane";
console.log(p.FirstName);
console.log(p.LastName);
p.FirstName = "Jane";
console.log(p.FullName);
try {
p.LastName = "D."
} catch (e) {
console.log(e);
}
Related
I'm trying to use JS classes with private fields for a React app (because I still find it weird to use naked Object instances everywhere). React uses the concept of immutable state, so I have to clone my objects in order to change them. I'm using private fields - with getters and setters to access them. The problem I have is that private fields don't get cloned in Firefox, Chrome, or Node. Annoyingly, I had a false positive with my React project's Jest setup, where this code works as expected in unit tests.
Is there a way to get around this? Otherwise, it looks like I have to give up some of my (perceived) encapsulation safety and use underscore-prefixed "private" fields instead.
This is my cloning function:
const objclone = obj => {
const cloned = Object.assign(
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj),
),
obj,
);
return cloned;
};
This clones the getters and setters as well as the object properties and appears to work well until I use private fields.
Example:
class C {
#priv;
constructor() {
this.#priv = 'priv-value';
}
get priv() { return this.#priv; }
}
const obj = new C();
console.log("obj.priv", obj.priv);
const cloned = objclone(obj);
console.log("cloned.priv", cloned.priv);
Error messages that are shown when trying to access cloned.priv:
Firefox:
Uncaught TypeError: can't access private field or method: object is not the right class
Chrome and Node:
Uncaught TypeError: Cannot read private member #priv from an object whose class did not declare it
I solved it. It's not as simple as I'd like - and I don't know if it can be made any simpler, but it looks pretty good to me.
Keys in solving the problem:
Objects of the same class can access each other's private fields
The only way to get an object to define its private fields is by calling its constructor.
I created a Cloner class that can clone normal JS objects, but also object which implement one of two interfaces: cloneMe or copyOther. The cloneMe interface allows an object to create the clone, populate it and return it, while the copyOther interface lets the Cloner call new, which results in slightly less cloning code.
An object has to implement one of these interfaces, and it is responsible for manually copying the private fields over. With a bit of luck, the mental overhead is minimal.
I used Symbol to prevent identifier collisions. I hope I did it right, as I never used this before.
class Cloner {
static cloneMe = Symbol('clone');
static copyOther = Symbol('copy');
static clone(obj, init = []) {
if (!(obj instanceof Object)) {
// reject non-Object input
throw new Error(`Cannot clone non-Object of type ${typeof(obj)}`)
} else if (obj[this.cloneMe]) {
// self-cloning object
return obj[this.cloneMe](...init);
} else if (obj[this.copyOther]) {
// copier object
const cloned = Object.assign(new obj.constructor(...init), obj);
// ask the cloned object to copy the source
cloned[this.copyOther](obj);
return cloned;
} else {
// classic cloning
return Object.assign(Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj),
),
obj,
);
}
}
}
Example cloneMe implementation:
class MyClonerClass {
#priv;
constructor(init) {
this.#priv = init;
}
[Cloner.cloneMe](...init) {
const cloned = new this.constructor(...init);
cloned.#priv = this.#priv;
return cloned;
}
get a() {
return this.#priv;
}
set a(value) {
this.#priv = value;
}
}
Example copyOther implementation:
class MyCopierClass {
#priv;
constructor(init) {
this.#priv = init;
}
[Cloner.copyOther](src) {
this.#priv = src.#priv;
}
get a() {
return this.#priv;
}
set a(value) {
this.#priv = value;
}
}
Usage:
const copySrc = new MyCopierClass('copySrc.#a');
const copyDst = Cloner.clone(copySrc);
copyDst.a = 'copyDst.#a';
console.log(copySrc.a);
console.log(copyDst.a);
const cloneSrc = new MyClonerClass('cloneSrc.#a');
const cloneDst = Cloner.clone(cloneSrc);
cloneDst.a = 'cloneDst.#a';
console.log(cloneSrc.a);
console.log(cloneDst.a);
Not shown here is the init parameter of Cloner.clone. That can be used if the constructor expects certain parameters to exist, and a naked constructor wouldn't work.
The cloneMe interface can take an init via the Cloner, or could supply its own based on internal state, keeping things nicely encapsulated and nearby.
Extra credits
While figuring this out, I thought up a way to simplify the cloning code quite a bit, by keeping the private fields in a dictionary. This crushes the TC39 hopes and dreams of a fixed compile-time list of private fields that cannot be added to or removed from, but it makes things a bit more Javascript-y. Have a look at the copyOther implementation - that's pretty much all of it, ever.
class WeirdPrivPattern {
#priv = {}
constructor(a, b) {
this.#priv.a = a;
this.#priv.b = b;
}
get a() {return this.#priv.a;}
set a(value) {this.#priv.a = value;}
get b() {return this.#priv.b;}
set b(value) {this.#priv.b = value;}
[Cloner.copyOther](src) {
this.#priv = {...src.#priv}
}
}
A note on deep cloning: it is outside of the scope of this answer. I am not worried about deep cloning. I actually rely on child objects keeping their identity if not mutated.
I am working on a project and my setters just refuse to work whatsoever. I have tried multiple solutions like making it private (but that doesn't work since I need to access it), renaming the value under setters to something different and trying to call them with or without parenthesis.
class Car {
constructor(id, image, vPosition, wins, hPosition, finishedRace) { //there is a chance I may need to remove the last 3 parms (wins,hpos and finished race)
this.id = id;
this.image = image;
this.vPosition = vPosition;
this.hPosition = hPosition;
this.finishedRace = finishedRace;
this.wins = wins;
}
get getvPosition() {
return this.vPosition;
}
/**
* #param {any} vPos
*/
set setvPosition(vPos) {
this.vPosition1 = vPos;
}
get getHPosition()
{
return this.hPosition;
}
set setHPosition(hPos)
{
this.hPosition = hPos;
}
move(Car) {
Car.setHPosition(10);
}
this is my car class where I am creating the constructor and getter setters. with other methods
const cars = [
new Car("Classic", "images/car1.png", 150,0,0,0),
new Car("Bug", "images/car2.png", 350,0,0,0),
new Car("Hatchback", "images/car3.png", 550,0,0,0),
new Car("Sedan", "images/car4.png", 750,0,0,0)
];
Above array of object Car which is just outside of Car class.
take a look on that move method in Car class. I am using it later in the code like this:
cars[2].move;
let str = cars[2].getHPosition;
console.log(str);
Which should call setter on cars[2] and make it set its HPosition to 10
Instead it returns 0 as if the setter was never called. I know there must be some silly mistake here but I am so annoyed by it
Your mistakes
Multiple reasons why your code may behave unexpectedly:
You reference, but not call car[2].move. Call it like car[2].move()!
In move you are trying to call an instance method as if it was a static method! Refer to this instead of Car.
You are trying to call a JS setter! Instead of calling it (setHPosition(20)), assign a value to the property (setHPosition = val).
In setvPosition, you assign the value to the property vPosition1 (notice the 1).
The errors may come from misunderstanding certain JS topics.
Functions in JS
JS is a functional programming language, which means that functions are treated as so-called first-class citizens. For that reason, referencing a function (here: move) but not calling it is perfectly valid.
In your case, you actually want to call the function, which is done by appending an argument list in parentheses to the function: move() (here, no arguments are passed).
An easy way to test whether your function has been called would be to litter it with some console.logs, though e.g. Chrome also offers some debugging tools (which I personally have never used).
Constructor functions
Constructor functions are more the exceptions when it comes to calling them. You call constructor functions by prepending the keyword new to the function name, and can then call it like a regular function: new SomeConstructor(1, 2, 3).
(Most naming conventions have constructor functions capitalized.)
Alternatively—unlike regular functions—constructor functions can also be called by leaving out the argument list: new SomeConstructor. This is equivalent to calling the constructor without any arguments: new SomeConstructor().
Your move function is not called as a constructor, thus it has to have an argument list to be called: move().
Function context
Calling a function on an object will make the object the function's context. Otherwise, the surrounding context is used. On the top level (global scope), either globalThis or null is the context, depending on whether you are in "sloppy" mode or strict mode:
console.log("Global scope:");
(function sloppyMode() {
console.log("-sloppy: this === globalThis?", this === globalThis);
})();
(function strictMode() {
"use strict";
console.log("-strict: this === undefined?", this === undefined);
})();
console.log(""); // Ignore; for whitespace in console
const compareThis = function(compTo) { return this === compTo; };
const obj = { compareThis };
console.log("Function scope:");
console.log("-direct call: this === globalThis?", compareThis(globalThis));
console.log("-direct call: this === obj?", compareThis(obj));
console.log("-on obj: this === globalThis?", obj.compareThis(globalThis));
console.log("-on obj: this === obj?", obj.compareThis(obj));
.as-console-wrapper{top:0;max-height:unset!important}
For this reason, we don't need to expect a Car object to be passed to move, but can instead use this.
Also, the parameter Car would shadow the class Car since it is found earlier in the lexical scope-chain.
If you'd want to keep both accessible in the function, a good idea would be to name the parameter (lowercase) car. Generally it would be good to stick to one naming convention to not confuse yourself.
Instance vs static methods
A class in JS defines both instance and static methods, but static methods will only be defined on the class, and instance methods only on instances.
Example
Consider the following:
class Example {
static staticMethod() {}
instanceMethod() {}
}
staticMethod is only defined on the class object Example, but not on instances new Example():
Example.staticMethod(); // works
new Example().staticMethod(); // fails; staticMethod is undefined on instances
For instanceMethod it is the opposite: It is only defined on instances new Example(), but not on the class object Example:
Example.instanceMethod(); // fails; instanceMethod is undefined on the class object
new Example().instanceMethod(); // works
Getters/setters
JS features the get/set keywords, which cause the specified property to only act in specific situations:
get property functions are called when the property of similar name is accessed (read).
set property functions are called when the property of similar name is assigned to.
In both cases they are used like regular properties instead of methods:
const object = {
_name: "my name",
get name() {
console.log("object:", "get name");
return this._name;
},
set name(v) {
console.log("object:", "set name");
this._name = v;
}
};
console.log("log:", object.name); // Uses getter
object.name = "another name"; // Uses setter
console.log("log:", object.name); // Uses getter
// Complex example:
// JS assigns right-to-left, so the expression evaluates to using the string,
// not to getting the property. Because of this, it only...
console.log("log:", object.name = "third name"); // Uses setter
// See https://262.ecma-international.org/12.0/#sec-assignment-operators-runtime-semantics-evaluation
An object can have a getter and setter for the same property (as can be seen above, for name). But when a getter or a setter is defined, no data entry under the same name can be defined on the same object (which is why I instead used _name).
As you can see, these aren't get/set methods. Getters/setters are usually only used when additional functionality has to happen during access/assignment (e.g. caching, lazy initialization), or to have immutable property references, or ...
Conclusion
I don't see a reason to use getters/setters in your class. Additionally, none of your properties are private (available in classes; example: this.#aPrivateProperty), which is why I wouldn't even use get/set methods, but instead access the properties directly.
Also, what I personally like to do is define the properties in the class definition as well, instead of only in the constructor. Then your IDE should autocomplete to the properties, potentially reducing typos like your vPosition1.
Here's a reduced example of how I would write your code then:
class Car {
hPosition;
vPosition;
constructor(hPos, vPos) {
this.hPosition = hPos;
this.vPosition = vPos;
}
move() { // We don't expect `Car`; we can use `this` instead!
this.hPosition = 10; // Shouldn't this be "+=" ?
}
}
const cars = [
new Car(150, 0),
new Car(350, 0),
new Car(550, 0),
new Car(750, 0)
];
cars[2].move(); // Don't forget to call!
console.log(cars[2].hPosition);
If you however still want to use the getters/setters of your original code:
class Car {
constructor(vPosition, hPosition) {
this.vPosition = vPosition;
this.hPosition = hPosition;
}
get getVPosition() { return this.vPosition; }
set setVPosition(vPos) { this.vPosition = vPos; } // Fixed typo vPosition1
get getHPosition() { return this.hPosition; }
set setHPosition(hPos) { this.hPosition = hPos; }
/* This *instance method* doesn't use *this instance* at all, so why is it not a static method?
* Also, instead of using *this instance*, it uses a passed-in instance. Why?
*/
move(car) {
car.setHPosition = 10;
}
}
const cars = [
new Car(150, 0),
new Car(350, 0),
new Car(550, 0),
new Car(750, 0)
];
cars[2].move(cars[2]); // We need to call the instance method, and then pass it the object to mutate.
let str = cars[2].getHPosition; // str now holds a number, but name suggests it holds a string.
str = String(str); // *Now* it holds a string.
console.log(str);
I think in this case I would remove the dependency on getters/setters altogether, and just have simple class methods that you call normally. That way move can call setHPosition without any issues.
class Car {
constructor(id, image, vPosition, wins, hPosition, finishedRace) {
this.id = id;
this.image = image;
this.vPosition = vPosition;
this.hPosition = hPosition;
this.finishedRace = finishedRace;
this.wins = wins;
}
getvPosition() {
return this.vPosition;
}
setvPosition(vPos) {
this.vPosition1 = vPos;
}
getHPosition() {
return this.hPosition;
}
setHPosition(hPos) {
this.hPosition = hPos;
}
move(val) {
this.setHPosition(val);
}
}
const cars = [
new Car("Classic", "images/car1.png", 150, 0, 0, 0),
new Car("Bug", "images/car2.png", 350, 0, 0, 0),
new Car("Hatchback", "images/car3.png", 550, 0, 0, 0),
new Car("Sedan", "images/car4.png", 750, 0, 0, 0)
];
cars[2].move(10);
console.log(cars[2].getHPosition());
Your move function takes a Car parameter and moves that car rather than the current class instance this. So you should change the move function to take a distance value and then call this.setHPosition = this.getHPosition + distance, assuming you want to move the car by a displacement from its current position rather than to a fixed position. i.e.:
move(distance) {
this.setHPosition = this.getHPosition + distance; // or just use this.hPosition directly: this.hPosition += distance
}
Note that getters and setters are functions that are accessed like properties, so you don't use the parentheses, but simply assign to the property for a setter, or read the property for a getter. Its syntactic sugar, but if you use it, you gotta use it right! It's probably better not to prefix the setters/getters with 'get' and 'set' since that makes them look like functions and could be confusing.
I have been experimenting with getters and setters with the following pattern:
var mytab = {
_tab: undefined,
get: function () {
return this._tab;
},
set: function (tab) {
this._tab = tab;
return tab;
}
}
My question is, given you have to access those methods explicitly, ie:
mytab.get();
mytab.set('thistab');
Why bother having get or set at all? Why not call them whatever you like? ie:
var mytab = {
_tab: undefined,
getthetab: function () {
return this._tab;
},
setthetab: function (tab) {
this._tab = tab;
return tab;
}
}
I may have missed some fundamental principle here, but both these objects behave exactly the same.
I assumed having special 'setters' and 'getters' would allow the object to be modified using it's object name, ie:
var a = mytab;
mytab = 'thistab';
Or even:
var a = mytab();
mytab() = 'thistab';
This is what I expected, and what I wanted, however those instructions give errors, namely that mytab() is not a function.
I would appreciate some clarity on what special significance the set and get object methods actually have.
In your first example, you haven't declared getters/setters. You've created an object with two methods called get and set.
To declare getters/setters, you'll have to choose an arbitrary name, and prefix it with get or set, like:
var mytab = {
_tab: undefined,
get tab() {
return this._tab;
},
set tab(tab) {
this._tab = tab;
return tab;
}
}
In this case, they form a so-called accessor property, that has the chosen name:
console.log(mytab.get) //undefined
console.log(mytab.set) //undefined
mytab.tab = 'foo'
console.log(mytab._tab) //foo
mytab._tab = 'bar'
console.log(mytab.tab) //bar
console.log(Object.getOwnPropertyDescriptor(mytab, 'tab')) /*
{
get: function(){...},
set: function(tab){...},
...
}
*/
However, you cannot overload operators or otherwise define a single getter/setter pair for your objects, that would allow you to assign a value or read a value from the object itself.
You can only define getters/setters for the properties on the object.
So,
var a = mytab
or
mytab = a
cannot be intercepted, and doesn't do what you expect (the first assigns the object itself to another variable (a), while the second reassigns the variable mytab with the value of a without even affecting / interacting with the object).
The following use case can illustrate advantage of using getters and setters
var person = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(person.fullName);
Using getter we could use get fullName as if it was a property of person without the need of maintaining separate field.
I have the following classes, which are designed to build an email template for me.
EmailBuilder.js class
const Email = require("./Email");
class EmailBuilder {
templateId(templateId) {
this.templateId = templateId;
return this;
}
build() {
return new Email(this);
}
}
Email.js Class
class Email {
constructor(builder) {
if (!builder.templateId) {
throw new Error("You need a templateId");
}
this.templateId = builder.templateId;
}
}
module.exports = Email;
When I call the following line, I want an exception to be thrown, because a templateId was not provided, which is required.
const email = new EmailBuilder().build();
However, this doesn't occur because in the Email class, builder.templateId is a function and therefore not falsey.
So the problem here is that my object's property (templateId) in the EmailBuilder.js class shares the same name as the object's method (templateId()).
What is the best way to adjust my code to solve this problem? Should I just change the name of either the property (templateId) or method (templateId) i.e. make the property _templateId and the function templateId? Is there a way of distinguishing between an object's properties and the object's methods in Javascript that I'm not aware of?
Note that EmailBuilder is a builder and therefore it is called in the following manner.
const email = new EmailBuilder()
.templateId("Some templateId")
.build();
You can't have non-method properties and method properties with the same name on the same object.¹ They're all just properties, and you can only have one property with a given name.
Instead:
Have the caller assign to templateId directly
Use an accessor property with a private field (brand new, not quite in the spec yet):
class EmailBuilder {
#templateId: number;
// ^^^^^^^^^^^^^^^^^^^^
set templateId(templateId) {
this.#templateId = templateId;
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
return this;
}
// You might have a getter here as well
get templateId() {
return this.#templateId;
}
build() {
return new Email(this);
}
}
JavaScript private fields are finding their way into implementations now, and for environments that don't have them yet, can be used via a transpiler like Babel.
Use Java-like setter naming (not a popular option)
setTemplateId(templateId) {
this.templateId = templateId;
return this;
}
¹ "You can't have non-method properties and method properties with the same name on the same object." Okay, so, technically, you weren't trying to do that. :-D Technically, you had a templateId method on one object (EmailBuilder.prototype) and you were putting a templateId property on another object (the instance created by new EmailBuilder). Technically, you can do that, but in practical terms using the method once you've created the data property on the instance is far too convoluted for real-world code, because the instance property shadows the prototype method. For code to reliably get the method, it would need to do this:
const email = new EmailBuilder();
Object.getPrototypeOf(email).templateId.call(email, "someId");
I think we can probably agree that's just too much hassle...
// I really don't suggest doing this
class EmailBuilder {
templateId(templateId) {
this.templateId = templateId;
return this;
}
build() {
return new Email(this);
}
}
const email = new EmailBuilder();
console.log(email.templateId); // "templateId(templateId) { this.templateId = templateId; return this; }"
email.templateId("someId"); // Works, but...
try {
email.templateId("anotherId"); // Fails
} catch (e) {
}
Object.getPrototypeOf(email).templateId.call(email, "aThirdId"); // Works, but...blech
console.log(email.templateId); // "aThirdId"
Looks like templateId is a getter and should be renamed getTemplateId.
or rather you could create a setter for the email builder and use the super keyword to inherit the id from the template.
I am a bit lost with ES6 privacy on Classes. I have this Class:
class Whatever {
constructor(value) {
this._value = Number(value);
}
total() {
return this._value * 2;
}
}
let instance = new Whatever('1000');
console.log(instance.total() ); // 2000
console.log(instance._value ); // 1000
I am trying to learn how Symbol() works to keep data private (see link), and after reading the documentation I am trying to make a simple example about it, but does not seem to have any difference:
const _value = Symbol('value');
class Whatever {
constructor(value) {
this[_value] = Number(value);
}
total() {
return this[_value] * 2;
}
}
let instance = new Whatever('1000');
console.log(instance.total() ); // 2000
console.log(instance[_value] ); // 1000
Could someone explain me on a comprehensive way, what does privacy mean on this case, and why my idea of privatizing data (making data unaccesible from outside the Class) is wrong? I have not much experience on OOP.
Thank you.
No real difference in your example, assuming you don't loop through the enumerable properties of the class.
Consider the following:
function MyFunction () {
const _private = new Symbol();
this[_private] = 5;
this.public = 10;
}
const thing = new MyFunction();
for (let key in thing) { // prints 10
console.log(thing[key]);
}
console.log(thing.public); // prints 10
console.log(thing._private); // undefined
Properties named by symbols are not enumerable, so in a sense they're not "exposed". Another benefit of symbols is that they're guaranteed to create unique keys, so there's no worry of key name collisions.
I chose to illustrate it with a function rather than a class for simplicity sake. For a class you could do the following (it's all about scope for "protecting" the symbol):
const MyClass = (function () {
const _value = Symbol();
return class _MyClass {
constructor() {
this[_value] = 5;
}
}
})();
Disclaimer: It's not "true" privacy, since there are still ways to enumerate the symbols. I think the biggest advantage is for the name collisions.
There are a few subtle concepts at play here.
Symbols are globally unique; that is, if you call Symbol.create('somestring'); twice, it will produce two completely different symbols.
There is no way for "someone else" to access an object property whose key is a symbol** without access to that symbol in some other way. In your second example, code underneath the class still has lexical access to the symbol. However, this would not be the case were your class to be exported, whether through a module system or just returned to some other calling function without lexical access, even if they knew what string you used to label the symbol, because they can't re-create it (as mentioned above).
** edit 2: As others stated, including Dr. Rauschmayer, "others" can access the keys, including symbols, via Reflect.ownKeys(object). In this very deliberate case, one would hope that the other party knows what they're doing... so this whole paradigm of using symbols as keys is still good for ensuring non-clashing namespaces for keys (e.g., if someone else wanted to augment your object without accidentally overriding any of its important "internal" properties). However, it is not sufficient for 100% protection against deliberate access or modification. (There's a related ECMAScript proposal in the works, interesting read.)
Edit: here's an example
let Whatever = (function() {
const _value = Symbol('value');
class Whatever {
constructor(value) {
this[_value] = Number(value);
}
total() {
return this[_value] * 2;
}
}
return Whatever;
})(); // IIFE
let instance = new Whatever('1000');
console.log(instance.total()); // 2000
console.log(instance._value); // undefined
console.log(instance[_value]); // ReferenceError: _value is not defined