How to work with private variables in ES6?

10,504

Solution 1

ES6 standard does not offer a new way for defining private variables.

It's a fact, that new ES6 class is simply syntactic sugar around regular prototype-based constructors.

get and set keywords are offering a way for simplified definition of ES5 custom getters and setters that were previously defined with a descriptor of Object.defineProperty()

The best you could do is to use those techniques with Symbols or WeakMaps

The example below features the use of a WeakMap for storing private properties.

// myModule.js
const first_name = new WeakMap();

class myClass {
     constructor (firstName) {
          first_name.set(this, firstName);
     }

     get name() {
          return first_name.get(this);
     }
}

export default myClass;

I'm referring to article, written by David Vujic What? Wait. Really? Oh no! (a post about ES6 classes and privacy) with the idea of using WeakMaps.

Solution 2

But in ES6, you can no longer declare vars outside the constructor

And you don't need to. You didn't do it in your ES5 constructor either. You can translate your code literally to

class Car {
    constructor() {
        // local variable
        var speed = 10;

        // public property
        this.model = "Batmobile";

        // public method
        this.init = () => {
            …
        }; // using an arrow function here simplifies things
    }
}

Solution 3

The same way than in ES5: define the methods that must access the private variables in the constructor instead of the prototype, thus making them privileged methods.

Otherwise there in no good way to allow prototypical methods to access private data, but still hide it from the outside. You can try symbols, weakmaps or handshakes, but IMO none is perfect. See accessing private member variables from prototype-defined functions for some ideas.

Solution 4

Update January 2016 - whilst I found the approach given in the accepted answer correct, I would like to state that using modules and symbols is an effective information hiding technique in ES2015+ (but Class attributes using Symbols will be hidden, not strictly private).

An effective, lightweight information hiding can be achieved through a combination of ES2015 modules (which would only export what you declare as exported) and ES2015 symbols. Symbol is a new built-in type. Every new Symbol value is unique. Hence can be used as a key on an object.

If the client calling code doesn't know the symbol used to access that key, they can't get hold of it since the symbol is not exported.

Quick example using your code:

vehicle.js

const s_make = Symbol();
const s_year = Symbol();

export class Vehicle {

  constructor(make, year) {
    this[s_make] = make;
    this[s_year] = year;
  }

  get make() {
    return this[s_make];
  }

  get year() {
    return this[s_year];
  }
}

and to use the module vehicle.js

client.js

import {Vehicle} from './vehicle';
const vehicle1 = new Vehicle('Ford', 2015);
console.log(vehicle1.make); //Ford
console.log(vehicle1.year); // 2015

However, symbols although unique, are not actually private since they are exposed via reflection features like Object.getOwnPropertySymbols...

const vals = Object.getOwnPropertySymbols(vehicle1);
vehicle1[vals[0]] = 'Volkswagon';
vehicle1[vals[1]] = 2013;
console.log(vehicle1.make); // Volkswagon
console.log(vehicle1.year); // 2013

Takeaway message - Modules in general are a great way to hide something because if not exported then not available for use outside the module, and used with privately stored Symbols to act as the keys, then class attributes too can become hidden (but not necessarily private). Class declarations are particularly well suited when considered as part of well constructed modules (amd, commonjs, or es6/2015).

Share:
10,504
Kokodoko
Author by

Kokodoko

Creative Technologist and Lecturer. I give workshops about building web apps, games and IoT devices with Typescript, Javascript, CSS, MakeCode, Arduino. Also dabbling in Machine Learning for the web.

Updated on June 17, 2022

Comments

  • Kokodoko
    Kokodoko almost 2 years

    In ES5, you could emulate a class with private and public variables like this:

    car.js

    function Car() {
        // using var causes speed to be only available inside Car (private)
        var speed = 10;
    
        // public variable - still accessible outside Car
        this.model = "Batmobile";
    
        // public method
        this.init = function(){
    
        }
    }
    

    But in ES6, you can no longer declare vars outside the constructor, making it actually HARDER to work with classes in a OOP way!?

    You can declare variables in the constructor using this, but that makes them public by default. This is very weird since ES6 DOES have a get / set keyword!

    class Vehicle {
        constructor(make, year) {
            // the underscore is nice, but these are still public!
            this._make = make;
            this._year = year;
        }
    
        // get and set can be handy, but would make more sense
        // if _make and _year were not accessible any other way!
        get make() {
            return this._make;
        }
    
        get year() {
            return this._year;
        }
    }
    
    • halfzebra
      halfzebra over 8 years
      this._make = make; has the same effect as this.model = "Batmobile";, you have to use completely different trick to define a private variable, see What? Wait. Really? Oh no! (a post about ES6 classes and privacy) for more.
    • Kokodoko
      Kokodoko over 8 years
      Yes, but the speed in the ES5 example is private. How to achieve this in ES6? Thanks for the link! It seems that ES6 classes still have a long way to go before we can consider them true OOP classes.
  • Kokodoko
    Kokodoko over 8 years
    Hm but now you define all your methods INSIDE the constructor, which seems very strange compared to other OOP languages. Outside the constructor you cannot access speed this way!
  • Kokodoko
    Kokodoko over 8 years
    I suppose that means that it's still simply not possible to write classes in ES6 like you do in other OOP languages, which is somewhat disappointing to learn...
  • Bergi
    Bergi over 8 years
    @Kokodoko: Of course, that's what you do when you want private variables. It's exactly the same as in ES5. If you want to use the prototype (performance differences will be negligible actually), you have to use public properties.
  • Kokodoko
    Kokodoko over 8 years
    I suppose this could work, but I'm not sure if this is so much better than the ES5 way.. it seems more convoluted while I was hoping that ES6 would simplify matters.
  • halfzebra
    halfzebra over 8 years
    @Kokodoko it depends on what you do, new standard offers many other nice features, but unfortunately real OOP classes are not one of them. Data encapsulation is much easier now with ES6 modules, JavaScript is definitely moving in the right direction.
  • Felix Kling
    Felix Kling over 8 years
    @Kokodoko: "but now you define all your methods INSIDE the constructor" That's exactly what you are doing in the first example (ES5) as well.
  • Kokodoko
    Kokodoko over 8 years
    Yes, but that example isn't wrapped in yet another function. This way it seems like you are abusing a class constructor to define an entire class. That is not how constructors are supposed to work, at least not in any other OOP language.
  • Felix Kling
    Felix Kling over 8 years
    @Kokodoko: "That is not how constructors are supposed to work" I agree. But since you are already "violating" that "rule" in ES5 you might as well do it in ES6. Or don't do it and don't try to make JS do something that it doesn't support.
  • Kokodoko
    Kokodoko over 8 years
    I just expected classes in ES6 to work like... you know, classes! I'm not sure why JS is not supposed to work like that, but even if it isn't - why did they add classes at all... it seems an ambiguous message to programmers coming from other languages.
  • Bergi
    Bergi over 8 years
    @Kokodoko: Well, classes are just classes, and nothing more. Access restriction is not a part of them - just like in Python. JS gives you closures for information hiding, if you really need it. Which you seldomly do.
  • Norguard
    Norguard over 8 years
    Object.getOwnPropertySymbols(obj) returns the list of symbols used in the object (like Object.keys(obj) does). At that point, it's just a matter of iterating over the list. Symbol has little to do with guaranteeing perfect privacy, and more to do with guaranteeing that each reference is unique, unless intentionally not so, while also keeping them out of loops and other constructs (again, unless intended).
  • arcseldon
    arcseldon over 8 years
    @Norguard - thanks very much for pointing this out. Have updated my answer - i was sitting on the fence with "information hiding" in the original answer. The Babel docs state the point you made too - babeljs.io/docs/learn-es2015
  • Kokodoko
    Kokodoko over 8 years
    Yes, modules are nice! It just seems weird that ES6 actually REMOVED the simple trick to make a var private! (var speed=10 in my first example). In other words, they made it harder, not easier, to use encapsulation.
  • Kokodoko
    Kokodoko over 8 years
    Yes, closures... those were a band-aid to a problem that you wouldn't have in other languages. it creates the famous 'closure pyramid', where you have closures inside closures inside closures. And now we have functions inside our constructors... Something tells me we're doing it wrong...
  • Kokodoko
    Kokodoko over 8 years
    Thanks for a very clear explanation of symbols! it's not exactly the same as private, but it can certainly serve the purpose of preventing accidental access to a variable.
  • halfzebra
    halfzebra over 8 years
    @Kokodoko actually the old ES5 trick is still there, I think you got it a bit wrong. You can define variables inside of constructor, but the ugly part is that you will have to define a custom getter/setter there as well, increasing the total fingerprint of an instance in the memory. Please see the Fiddle
  • Kokodoko
    Kokodoko over 8 years
    Yes, but like I said, now you have functions and variable initialisation inside your constructor. It works, but I think it goes against the idea of having a class with a constructor method. In other languages, a constructor is simply a function that is executed when you create a new instance. You can even leave out the constructor! This ES5 example is clearer, in my opinion: jsfiddle.net/35f9q7p6/2
  • peterflynn
    peterflynn over 6 years
    @Bergi: performance differences aren't necessarily negligible. The memory cost of each instance scales up with the size of your API, since each instance now has its own 'version' of each method (a unique method + closure pair). Keeping methods outside of the constructor makes for much lower memory usage if you'll have many instances of your class.