13

I'm just trying out ES6 and want to rewrite a part of code written in regular javascript to ES6. And now, I'm stuck while trying to re-write the private properties and methods in ES6 classes. It seems like classes in ES6 doesn't explicitly provide anything to have private data or methods.

Also, I checked this thread: Private properties in JavaScript ES6 classes and found out that we could use WeakMap to store private data. Which is sort of weird but still it can be a work around. And I did manage to use it for private data.

But what about private methods? What is the recommended way to have private methods (or even protected methods) in ES6 classes?

I would appreciate if anyone can show me a clean way to rewrite this part of code using ES6 class along with the private methods.

Thanks.

Here's the plain old javascript code:

function Deferred() {

    // Private data
    var isPending;
    var handlers = {
        resolve: [],
        reject: [],
        notify: []
    };

    // Initialize the instance
    init();

    function init() {
        isPending = true;
        this.promise = new Promise(this);
    }

    // Public methods
    this.resolve = function(value) {
        trigger('resolve', value);
    };

    this.reject = function(reason) {
        trigger('reject', reason);
    };

    this.notify = function(value) {
        trigger('notify', value);
    };

    this.on = function(event, handler) {
        ...
    };

    // Private method
    function trigger (event, params) {
        ...
    }
}
Community
  • 1
  • 1
kabirbaidhya
  • 3,264
  • 3
  • 34
  • 59
  • There should be sufficient information in the [*MDN:Classes*](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) article. – RobG Jan 19 '16 at 08:33

2 Answers2

15

It seems like classes in ES6 doesn't explicitly provide anything to have private data or methods.

Correct. The class syntax is for normal classes with prototype methods. If you want private variables, you put them in the constructor as always:

class Deferred {
    constructor() {
        // initialise private data
        var isPending = true;
        var handlers = {
            resolve: [],
            reject: [],
            notify: []
        };

        // Private method
        function trigger(event, params) {
            ...
        }

        // initialise public properties
        this.promise = new Promise(this);

        // and create privileged methods
        this.resolve = trigger.bind(null, 'resolve');
        this.reject = trigger.bind(null, 'reject');
        this.notify = trigger.bind(null, 'notify');

        this.on = function(event, handler) {
            …
        };
    }
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 4
    Seems to be okay. But, if this is the only way then, I think it would be more "cleaner" to stay with the old javascript `function()` style. As there isn't much we could do in this scenario with ES6 classes. – kabirbaidhya Jan 20 '16 at 12:28
  • Well I would expect that you had at least some prototype methods, don't you? – Bergi Jan 20 '16 at 16:15
  • 4
    The "private" stuff here is only available in the constructor. You have to forgo using class getters and setters and class methods for this to work (or at least ones that would involve the supposedly "private" members). In which case, you are reduced to a single object generating function, which is not really any different from "traditional" class functions. – Dave Cousineau Oct 02 '16 at 19:03
  • @Sahuagin Yes, that's why I wrote "*as always*" - if you need privileged methods/getters/setters etc to access the local variables, they have to go inside the constructor. – Bergi Oct 02 '16 at 19:06
  • the other answer has something that is basically equivalent to private members and does not forgo class getters/setters/methods – Dave Cousineau Oct 02 '16 at 19:17
  • 1
    @Sahuagin Symbols are not private. They're just as good as property names prefixed with an underscore. Yes, there are other approaches at implementing "privacy" in JS, but here the OP asked specifically for something that is equivalent to his ES5 code. – Bergi Oct 02 '16 at 19:18
  • "They're just as good as property names prefixed with an underscore." that doesn't seem to be true. underscore properties would still appear in intellisense, and be immediately available. symbol properties and methods... for one you would not export the symbols from a module, and for two, you'd have to explicitly use a different syntax or inspection mechanism to access them. The point is to strongly (but not necessarily perfectly) hide the members (ie encapsulation). the symbols approach seems like a much stronger way of hiding them. (not 100% sure about any of this only started ES6 yesterday) – Dave Cousineau Oct 02 '16 at 19:40
  • 1
    Well, yes, the syntax is different and they are hidden from the usual (ES5) introspection methods, but they aren't really inaccessible and not more secure than other properties. – Bergi Oct 02 '16 at 21:23
  • There is a new library out that supports private data while still allowing for the use of ES6 class syntax, i.e. the class functions can see the data on the `this` context, but nothing else can. https://github.com/anywhichway/privatize. – AnyWhichWay Feb 11 '19 at 02:37
  • @AnyWhichWay Looks pretty inefficient – Bergi Feb 11 '19 at 13:30
  • @Bergi Yeah, Proxies aren't exactly high performing. But, I think it is pretty clean and makes it hard to bypass except in a debugger. I often hand code private data using closure in a constructor that adds the methods to the instance ala Crockford's old but highly applicable guidance: https://crockford.com/javascript/private.html. I have found this to be the most performant but not cleanest from a code maintenance perspective. It's all trade-offs. I suppose if one were to write a transpiler for Babel, re-writing constructors to use wrappers with Crockford's style would be the way to go. – AnyWhichWay Mar 09 '19 at 15:14
5

You can use symbols to give a sort-of private member.

const KEY = Symbol( 'key' )
const ANOTHER = Symbol( 'another' )

class Foo {
  constructor() {
    this[ KEY ] = 'private'
  }

  say() {
    console.log( this[ KEY ] )
  }

  [ ANOTHER ] = 'transpilation required'
}

The 2nd symbol is added to the class using a class field, this is only in proposal and will require transpilation to work anywhere, but the rest works in node and new browsers.

Matt Styles
  • 2,442
  • 1
  • 18
  • 23
  • this is strange syntax, and feels slightly like a hack, but it definitely works and is basically equivalent to real private members. – Dave Cousineau Oct 02 '16 at 19:14
  • 2
    I though this was a good idea as well, but the property isn't really private. You cannot access the value with `instance.key`, but you still can using `instance[Object.getOwnPropertySymbols(instance).filter(s => s.toString().replace(/^Symbol\(([^)]+)\)$/, '$1') === 'key')[0]]` – Neovov Dec 17 '16 at 15:01
  • well, you could do that... – artparks Mar 31 '17 at 21:57